const fs = require('fs')
const path = require('path')
const N3 = require('n3')
const N3Parser = require('n3-parser.js').N3Parser
/**
* Handles all logic when dealing with n3 files or strings.
*/
class N3FileService {
/**
* The constructor initializes our N3 parsers and sets the location for storing temporary files
*/
constructor () {
this.parser = new N3.Parser()
this.parserN3 = new N3Parser()
this.tempFolder = 'data/temp/'
this.extensions = ['.ttl', '.n3', '.rdf']
}
/**
* Checks if file is an RDF file ('.ttl','.n3','.rdf').
* @param {String} file - Path to the file
* @returns {Boolean} - Returns true if the file is an RDF-file.
*/
isRDFFile (file) {
const current = path.extname(file)
if (this.extensions.indexOf(current) !== -1) {
return true
}
return false
}
/**
* Creates a new file or opens it if it already exists
*
* !!File handle must be closed or it might leak
*
* Uses NodeJS's `fs.promises.open`.
*
* @param {String} path - Path to the file were trying to create
* @returns {Promise} - Promise that resolves to the file handle of the file
*/
async newFile (path) {
// if file doesn't already exist, create the file
return fs.promises.open(path)
}
/**
* Deletes all content from a file
* @param {String} path - Path to the file
* @returns {Promise}
*/
async deleteContent (file) {
return fs.promises.truncate(file, 0)
.then(() => console.log('File ', file, ' truncated to 0'))
}
/**
* Convert contents of n3 file to an array of usable quad objects.
* @param {String} file - Name of the file we're trying to parse
* @returns {Array} - Array of Quads.
*/
async fileToQuads (file) {
// read file contents
// var content = fs.readFileSync(file, 'utf8');
// todo: check if file is proper RDF file
// parse the content to quads and return
try {
const content = await fs.promises.readFile(file, 'utf8')
return this.parser.parse(content)
} catch (e) {
fs.promises.writeFile(file, '', 'utf-8')
return this.parser.parse('')
}
}
/**
* Reads the contents of a file
* @param {String} file - Name of the file we're trying to parse
* @returns {String} - Contents of file
*/
async fileToString (file) {
// is rdf file
if (!this.isRDFFile(file)) { return '' }
// read file contents
const content = await fs.promises.readFile(file, 'utf8')
return content
}
/**
* Convert contents of an N3-string to an array of usable quad objects.
* @param {String} n3string - N3-string to parse
* @param {Array} - Array of Quads
*/
stringToQuads (n3string) {
// parse the content to quads and return
return this.parser.parse(n3string)
}
/**
* Converts array of quad objects to an n3string
* @param {Array} quads - Quads to convert
* @param {Object} prefixes - Object containing all necessary prefixes as key-value pairs
* @returns {String} - Equivalent N3-string of the input quads
*/
async quadsToString (quads, prefixes) {
return new Promise((resolve, reject) => {
// initialize writer
const writer = new N3.Writer({ prefixes: prefixes })
// add each of the quads to the writer
writer.addQuads(quads)
// close the writer and send the result
writer.end((error, result) => {
if (error) {
reject(error)
}
resolve(result)
})
})
}
/**
* Convert contents of n3 string to a JSONLD string
* @param {String} n3string
* @returns {String} Equivalent JSONLD of the input
*/
stringToJSONLD (n3string) {
return this.parserN3.toJSONLD(n3string)
}
/**
* Writes an n3string to an n3 file.
* This will overwrite the file losing any previous contents
* @param {String} file - Name of the file were writing to
* @param {String} n3string - New contents of the file
* @returns {Promise}
*/
async writeStringToFile (file, n3string) {
// creates file if it doesn't exist
return fs.promises.writeFile(file, n3string)
}
/**
* Writes an array of quads to an n3 file.
* This will overwrite the file losing any previous contents
* @param {String} file - Name of the file were writing to
* @param {Array} quads - List of Quads to be written
* @param {Object} prefixes - Object containing all necessary prefixes as key-value pairs
* @returns {Promise}
*/
async writeQuadsToFile (file, quads, prefixes) {
return new Promise(
(resolve, reject) => {
// open a writable stream
const access = fs.createWriteStream(file)
// todo: check if file is proper RDF file
// initialize N3 writer to write to our stream
const writer = new N3.Writer(access, { prefixes: prefixes })
writer.addQuads(quads)
writer.end((error, result) => {
// close the writer and resolve the result
access.end()
if (error) {
reject(error)
} else {
resolve(result)
}
})
})
}
/**
* Retrieves the prefixes from an n3 string, returns prefixes as json
* @param {String} n3string
* @returns {Object} - Prefixes as key-value pairs
*/
prefixesFromString (n3string) {
// Use RegExp to extract all prefixes
const result = n3string.match(/@prefix.*\./g)
// process the prefixes to json format, this way it will be readily usable by the N3 package
const prefixes = {}
for (const prefixKey in result) {
const prefix = result[prefixKey].match(/@prefix (.*): <(.*)>\./)
prefixes[prefix[1]] = prefix[2]
}
return prefixes
}
/**
* Retrieves the prefixes from a file as json.
* @param {String} file - Name of the file to be read.
* @returns {Object} - Prefixes as key-value pairs
*/
async prefixesFromFile (file) {
// first get n3string from file
const n3string = await this.fileToString(file)
return this.prefixesFromString(n3string)
}
/**
* Retrieve prefixes from n3string as string.
* @param {String} n3string
* @returns {String} Single string containing all prefixes
*/
prefixesFromStringAsString (n3string) {
// Use RegExp to extract all prefixes as array
const result = n3string.match(/@prefix.*\./g)
// convert to single string
return result.join('\r\n')
}
/**
* Adds an N3-string to existing file
* @param {String} file - Path to the file were appending to
* @param {String} n3string - N3-string to be added to the file
* @returns {Promise} Promise that resolves when the I/O has finished
*/
async appendN3stringToFile (file, n3string) {
// combine the quads from the file and n3 string
let quads = await this.fileToQuads(file)
quads = quads.concat(this.stringToQuads(n3string))
// retrieve prefixes from the n3 string
const prefixes = this.prefixesFromString(n3string)
return this.writeQuadsToFile(file, quads, prefixes)
}
/**
* Generate a temporary file and write the specified quads to the file
*
* !!Needs to be manually unlinked after use
* @param {Array} quads - Quads to be written
* @param {Object} prefixes - Object containing all necessary prefixes as key-value pairs
* @returns {String} the filepath relative to the server root
*/
async quadsToTempFile (quads, prefixes) {
// generate random string
const r = Math.random().toString(36).substr(2, 5)
// define the file we want to write to
var fileName = r + '.n3'
var file = this.tempFolder + fileName
// create new temp file
// await this.newFile(file).then(fi);
await this.writeQuadsToFile(file, quads, prefixes)
return file
}
}
module.exports = N3FileService