UNPKG

@rmlio/yarrrml-parser

Version:

Parse YARRRML descriptions into RML RDF statements

210 lines (187 loc) 5.58 kB
const assert = require('assert'); const convertYAMLtoRML = require('./rml-generator.js'); const convertYAMLtoR2RML = require('./r2rml-generator.js'); const fs = require("fs"); const N3 = require("n3"); const makeReadable = require("./readable-rml.js").makeReadable; const { isomorphic } = require("rdf-isomorphic"); const Logger = require('./logger'); const ttlRead = require('@graphy/content.ttl.read'); const ttlWrite = require('@graphy/content.ttl.write'); const dataset = require('@graphy/memory.dataset.fast'); const { Readable } = require('stream'); const namespaces = require('./namespaces').asMap(); function compareY2RFiles(ymlPaths, ttlPath, options, cb) { const yamls = []; if (typeof ymlPaths === 'string' || ymlPaths instanceof String) { ymlPaths = [ymlPaths]; } for (const ymlPath of ymlPaths) { const yarrrml = fs.readFileSync(ymlPath, 'utf8'); yamls.push({ yarrrml }); } const ttl = fs.readFileSync(ttlPath, 'utf8'); compareY2RData(yamls, ttl, options, cb); } function compareY2R2RFiles(ymlPath, ttlPath, options, cb) { const yaml = fs.readFileSync(ymlPath, 'utf8'); const ttl = fs.readFileSync(ttlPath, 'utf8'); compareY2R2RData(yaml, ttl, options, cb); } function compareY2RData(yaml, ttl, options, cb) { const y2r = new convertYAMLtoRML(options); const yamlQuads = y2r.convert(yaml); const rmlQuads = []; const parser = new N3.Parser(); parser.parse(ttl, function (error, quad, prefixes) { if (error) { throw new Error(`Could not parse the test turtle file: ${error}`); } if (quad) rmlQuads.push(quad); else { logCanonical(rmlQuads, y2r.getPrefixes(), y2r.getBaseIRI()).then(() => { try { assert(isomorphic(yamlQuads, rmlQuads), true); } catch (e) { assert.equal(yamlQuads.map(q => quadToBadString(q)).sort().join('\n'), rmlQuads.map(q => quadToBadString(q)).sort().join('\n')); } cb(); }).catch(cb) } }); } function quadToBadString(quad) { return `<${quad.subject.value}> <${quad.predicate.value}> <${quad.object.value}>` } function compareY2R2RData(yaml, ttl, options, cb) { const y2r = new convertYAMLtoR2RML(options); const yamlQuads = y2r.convert(yaml); const rmlQuads = []; const parser = new N3.Parser(); parser.parse(ttl, function (error, quad, prefixes) { if (quad) rmlQuads.push(quad); else { logCanonical(rmlQuads, y2r.getPrefixes(), y2r.getBaseIRI()).then(() => { try { assert(isomorphic(yamlQuads, rmlQuads), true); } catch (e) { assert.deepStrictEqual(yamlQuads.map(q => q.toString()).sort(), rmlQuads.map(q => q.toString()).sort()); } cb(); }) } }); } /** * Log canonicalized turtle from quads and (optional) prefixes and base * @param {*} triples * @param {*} prefixes * @param {*} base * @returns */ async function logCanonical(triples, prefixes = {}, base = null) { let myPrefixes = { rr: namespaces.rr, rdf: namespaces.rdf, rdfs: namespaces.rdfs, fnml: namespaces.fnml, fno: namespaces.fno, d2rq: namespaces.d2rq, void: namespaces.void, dc: namespaces.dc, foaf: namespaces.foaf, rml: namespaces.rml, ql: namespaces.ql, }; if (base) { myPrefixes[''] = base; } myPrefixes = Object.assign({}, myPrefixes, prefixes); let result = await quadsToTurtle(triples, myPrefixes); result = await canonicalize(result); // Uncomment below if you want to inspect generated RML // console.error(result) } function logReadable(triples, base = null, cb = (err, result) => { Logger.log(result); }) { let prefixes = { rr: 'http://www.w3.org/ns/r2rml#', rml: 'http://semweb.mmlab.be/ns/rml#', ql: 'http://semweb.mmlab.be/ns/ql#' }; if (base) { prefixes[''] = base; } let writer = new N3.Writer({ prefixes }); makeReadable(triples, writer); compareY2RFiles writer.end(cb); } /** * Create a turtle string from quads and (optional) prefixes * @param {*} triples * @param {*} prefixes * @returns */ async function quadsToTurtle(triples = [], prefixes = {}) { return new Promise((resolve, reject) => { const writer = new N3.Writer({ prefixes }); writer.addQuads(triples); writer.end(async (error, result) => { if (error) { reject(error); return; } resolve(result); return; }); }); } /** * Create a pretty-printed turtle string from an ugly turtle string * @param {*} ttlString * @returns */ async function canonicalize(ttlString) { const stream = Readable.from([ttlString]) .pipe(ttlRead()) .pipe(dataset({ canonicalize: false, })) .pipe(ttlWrite()); return new Promise((resolve, reject) => { stream.on('error', reject); streamToString(stream).then(resolve, reject); }); } async function streamToString(stream) { const chunks = []; for await (const chunk of stream) { chunks.push(chunk); } return Buffer.concat(chunks).toString('utf8'); } /** * transform Quads to uniform Turtle (making it easier to compare as strings) * @param {Quad[]} quads * @returns {Promise<string>} Turtle string */ async function quadsToPrettyTurtle(quads) { return new Promise((resolve, reject) => { const writer = new N3.Writer(); writer.addQuads(quads); writer.end(async (error, result) => { result = await canonicalize(result); // resolve promise resolve(result); }); }); } module.exports = { canonicalize, compareY2RFiles, compareY2R2RFiles, };