UNPKG

@rmlio/yarrrml-parser

Version:

Parse YARRRML descriptions into RML RDF statements

1,411 lines (1,217 loc) 43.6 kB
/** * author: Pieter Heyvaert (pheyvaer.heyvaert@ugent.be) * Ghent University - imec - IDLab */ const namespaces = require('./namespaces').asMap(); const AbstractGenerator = require('./abstract-generator.js'); const formulations = require('./formulations.json'); const { DataFactory } = require('n3'); const {namedNode, literal, quad} = DataFactory; const jdbcDrivers = require('./jdbc-drivers'); const Logger = require('./logger'); class RMLGenerator extends AbstractGenerator { authsIRIMap = {}; constructor(options = null) { super(options); } convertExpandedJSON(yarrrml) { super.convertExpandedJSON(yarrrml); if (yarrrml.base) { this.baseIRI = yarrrml.base; } if (yarrrml.authentications) { Object.keys(yarrrml.authentications).forEach(authName => { this.authsIRIMap[authName] = this.generateAuth(yarrrml.authentications[authName], authName); }); } const sourcesIRIMap = {}; if (yarrrml.sources) { Object.keys(yarrrml.sources).forEach(sourceName => { sourcesIRIMap[sourceName] = this.generateSource(yarrrml.sources[sourceName], undefined, sourceName); }); } const targetsIRIMap = {}; if (yarrrml.targets) { Object.keys(yarrrml.targets).forEach(targetName => { targetsIRIMap[targetName] = this.generateTarget(yarrrml.targets[targetName], targetName); }); } if (!yarrrml.mappings) { return this.quads; } Object.keys(yarrrml.mappings).forEach(mappingName => { const mapping = yarrrml.mappings[mappingName]; let skipMapping = false; // Becomes true when a dynamic mapping is encountered: it already generates quads /* Collect all targets inline in terms */ let targets = []; if(mapping.subjects) { // Subject inline targets mapping.subjects.forEach((s) => { if (s.targets) { if (s.thisMappingVar) { this.generateTargetTriplesMap(s.targets[0], s.value, s.thisMappingVar, mappingName, sourcesIRIMap); skipMapping = true; // don't do the rest of the mapping (skip this iteration in forEach loop } else { for (let i = 0; i < s.targets.length; i++) { if (typeof s.targets[i] === 'string') { continue; } let targetName = this._generateTargetId(s.targets[i]) targetsIRIMap[targetName] = this.generateTarget(s.targets[i], targetName); s.targets[i] = targetName } } } }); } if (skipMapping) { return; } if(mapping.predicateobjects) { mapping.predicateobjects.forEach((po) => { let p = po.predicates; let o = po.objects; if (Array.isArray(po.predicates)) { // Predicate inline targets po.predicates.forEach((p) => { if (typeof p === 'object' && p.targets) { for (let i = 0; i < p.targets.length; i++) { if (typeof p.targets[i] === 'string') { continue; } let targetName = this._generateTargetId(p.targets[i]) targetsIRIMap[targetName] = this.generateTarget(p.targets[i], targetName); p.targets[i] = targetName } } }); // Object inline targets po.objects.forEach((o) => { if (typeof o === 'object' && o.targets) { for (let i = 0; i < o.targets.length; i++) { if (typeof o.targets[i] === 'string') { continue; } let targetName = this._generateTargetId(o.targets[i]) targetsIRIMap[targetName] = this.generateTarget(o.targets[i], targetName); o.targets[i] = targetName } } // Language inline targets if (o.language && o.language.targets) { for (let i = 0; i < o.language.targets.length; i++) { if (typeof o.language.targets[i] === 'string') { continue; } let targetName = this._generateTargetId(o.language.targets[i]) targetsIRIMap[targetName] = this.generateTarget(o.language.targets[i], targetName); o.language.targets[i] = targetName } } }); } }); } if (mapping.graphs) { // Graph maps inline targets mapping.graphs.forEach((g) => { if (g.targets) { for (let i = 0; i < g.targets.length; i++) { if (typeof g.targets[i] === 'string') { continue; } let targetName = this._generateTargetId(g.targets[i]) targetsIRIMap[targetName] = this.generateTarget(g.targets[i], targetName); g.targets[i] = targetName } } }); } if (mapping.sources) { mapping.sources = [].concat(mapping.sources); mapping.sources.forEach(source => { const tmSubject = namedNode(this.baseIRI + this.getUniqueID('map_' + mappingName)); this.addMappingIRI(mappingName, tmSubject); let sourceSubject; if (typeof source === 'string') { sourceSubject = sourcesIRIMap[source]; if(!sourceSubject) { Logger.error(`No source definition found for the source tag "${source}"`); } this.quads.push(quad( tmSubject, namedNode(namespaces.rml + 'logicalSource'), sourceSubject )); } else { sourceSubject = this.generateSource(source, tmSubject); } this.generateMapping(tmSubject, mapping, mappingName, sourceSubject, targetsIRIMap); }); } else { const tmSubject = namedNode(this.baseIRI + mappingName); this.generateMapping(tmSubject, mapping, mappingName, undefined, targetsIRIMap); } }); this.generateAllReferencingObjectMap(); return this.quads; } _generateTargetId(target) { if (typeof target === 'object') { let id = '' Object.keys(target).forEach(key => { if (key === 'ldes') { id += 'ldes'; } else { id += target[key]; } id += '-' }); id = id.slice(0, -1); // remove last '-' return id; } else { return target; } } generateAuth(authentication, authName) { const authSubject = namedNode(this.baseIRI + this.getUniqueID('auth')); this.prefixes['rmle'] = namespaces.rmle; // determine type (class) of authentication. // Only supported type is cssclientcredentials at the moment. if (authentication.type === 'cssclientcredentials') { this.quads.push(quad( authSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rmle + 'CssClientCredentialsAuthentication') )); } else { Logger.error("Unsupported authentication type " + authentication.type + " for " + authName + ". Only 'cssclientcredentials' is supported."); } // email if (authentication.email) { this.quads.push(quad( authSubject, namedNode(namespaces.rmle + 'authEmail'), literal(authentication.email) )); } else { Logger.error("'email' required for authentication " + authName); } // passwd if (authentication.password ) { this.quads.push(quad( authSubject, namedNode(namespaces.rmle + 'authPassword'), literal(authentication.password) )); } else { Logger.error("'password' required for authentication " + authName); } // oidcIssuer if (authentication.oidcIssuer ) { this.quads.push(quad( authSubject, namedNode(namespaces.rmle + 'authOidcIssuer'), namedNode(authentication.oidcIssuer) )); } else { Logger.error("'oidcIssuer' required for authentication " + authName); } // oidcIssuer if (authentication.webId ) { this.quads.push(quad( authSubject, namedNode(namespaces.rmle + 'authWebId'), namedNode(authentication.webId) )); } else { Logger.error("'webId' required for authentication " + authName); } return authSubject; } generateMapping(tmSubject, mapping, mappingName, sourceSubject, targetsIRIMap) { mapping = JSON.parse(JSON.stringify(mapping)); this.quads.push(quad( tmSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rr + 'TriplesMap') )); super.generateMapping(tmSubject, mapping, mappingName, sourceSubject, targetsIRIMap); } generateSource(source, tmSubject, sourceName) { const sSubject = namedNode(this.baseIRI + this.getUniqueID('source')); if (tmSubject) { this.quads.push(quad( tmSubject, namedNode(namespaces.rml + 'logicalSource'), sSubject )); } this.quads.push(quad( sSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rml + 'LogicalSource') )); if (sourceName) { this.quads.push(quad( sSubject, namedNode(namespaces.rdfs + 'label'), literal(sourceName) )); } if (!source.type) { if (source.referenceFormulation === 'csv' && source.delimiter !== undefined && source.delimiter !== ',') { // We need CSVW. this.prefixes['csvw'] = namespaces.csvw; const csvwSubject = namedNode(this.baseIRI + this.getUniqueID('csvw')); const dialectSubject = namedNode(this.baseIRI + this.getUniqueID('csvw-dialect')); this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'source'), csvwSubject )); this.quads.push(quad( csvwSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.csvw + 'Table') )); this.quads.push(quad( csvwSubject, namedNode(namespaces.csvw + 'url'), literal(source.access) )); this.quads.push(quad( csvwSubject, namedNode(namespaces.csvw + 'dialect'), dialectSubject )); this.quads.push(quad( dialectSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.csvw + 'Dialect') )); this.quads.push(quad( dialectSubject, namedNode(namespaces.csvw + 'delimiter'), literal(source.delimiter) )); } else { this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'source'), literal(source.access) )); } if (source.iterator) { this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'iterator'), literal(source.iterator) )); } } else { /* Web of Things */ if (source.type == 'wot') { const wotSubject = namedNode(this.baseIRI + this.getUniqueID('wot')); const formSubject = namedNode(this.baseIRI + this.getUniqueID('form')); const propertyAffordanceSubject = namedNode(this.baseIRI + this.getUniqueID('propertyAffordance')); const securitySubject = namedNode(this.baseIRI + this.getUniqueID('security')); this.prefixes['td'] = namespaces.td this.prefixes['wotsec'] = namespaces.wotsec this.prefixes['hctl'] = namespaces.hctl this.prefixes['idsa'] = namespaces.idsa this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'source'), propertyAffordanceSubject )); /* Build td:Thing */ this.quads.push(quad( wotSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.td + 'Thing') )); /* Build td:PropertyAffordance */ this.quads.push(quad( propertyAffordanceSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.td + 'PropertyAffordance') )); this.quads.push(quad( propertyAffordanceSubject, namedNode(namespaces.td + 'hasForm'), formSubject )); this.quads.push(quad( formSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.td + 'Form') )); this.quads.push(quad( formSubject, namedNode(namespaces.hctl + 'hasTarget'), literal(source.access) )); this.quads.push(quad( formSubject, namedNode(namespaces.hctl + 'forContentType'), literal(source.contentType) )); let operationType = null; switch(source.operationType) { case 'read': operationType = namedNode(namespaces.td + 'readproperty'); break case 'write': default: break } this.quads.push(quad( formSubject, namedNode(namespaces.hctl + 'hasOperationType'), operationType )); this.quads.push(quad( wotSubject, namedNode(namespaces.td + 'hasPropertyAffordance'), propertyAffordanceSubject )); // FIXME: W3C WoT Protocol Bindings /* Build wotsec:$SCHEMESecurityScheme */ if (source.security) { if (Array.isArray(source.security)) { Object.keys(source.security).forEach(index => { let security = source.security[index] // FIXME: support more security schemes if (security.type == 'apikey') { this.quads.push(quad( securitySubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.td + 'APISecurityScheme') )); this.quads.push(quad( securitySubject, namedNode(namespaces.wotsec + 'in'), literal(security.in) )); this.quads.push(quad( securitySubject, namedNode(namespaces.wotsec + 'name'), literal(security.name) )); this.quads.push(quad( securitySubject, namedNode(namespaces.idsa + 'tokenValue'), literal(security.value) )); } else if (security.type == 'none') { this.quads.push(quad( securitySubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.td + 'NoSecurityScheme') )); } }); } this.quads.push(quad( wotSubject, namedNode(namespaces.td + 'hasSecurityConfiguration'), securitySubject )); if (source.iterator) { this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'iterator'), literal(source.iterator) )); } } } /* Database */ else { const databaseSubject = namedNode(this.baseIRI + this.getUniqueID('database')); this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'source'), databaseSubject )); if (source.query) { this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'query'), literal(source.query.replace(/\s+/g, ' ').trim()) )); } if (source.queryFormulation) { let object = namedNode(formulations.query[source.queryFormulation]); this.quads.push(quad( sSubject, namedNode(namespaces.rr + 'sqlVersion'), object )); } this._generateDatabaseDescription(databaseSubject, source); } } let object = namedNode(formulations.reference[source.referenceFormulation]); this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'referenceFormulation'), object )); return sSubject; } generateTarget(target, targetName) { if(typeof target == 'string') { target = {access: target}; } const tSubject = namedNode(this.baseIRI + this.getUniqueID('target')); this.prefixes['rmlt'] = namespaces.rmlt; this.quads.push(quad( tSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rmlt + 'LogicalTarget') )); if (targetName) { this.quads.push(quad( tSubject, namedNode(namespaces.rdfs + 'label'), literal(targetName) )); } /* Serialization format */ let format = this._getSerializationFormat(target.serialization); this.prefixes['formats'] = namespaces.formats; this.quads.push(quad( tSubject, namedNode(namespaces.rmlt + 'serialization'), format )); /* Optionally apply compression */ if (target.compression) { /* See: http://semweb.mmlab.be/ns/rml-compression# for known algorithms */ let algorithms = ['gzip', 'zip', 'targzip', 'tarxz']; this.prefixes['comp'] = namespaces.comp; if (algorithms.indexOf(target.compression) > -1) { this.quads.push(quad( tSubject, namedNode(namespaces.rmlt + 'compression'), namedNode(namespaces.comp + target.compression) )); } else { Logger.error(`${target.compression} is not a known compression algorithm`); } } /* Path to file as string */ if (!target.type || target.type === 'localfile') { target.type = 'void' // localfile is a VoID dataset } if (target.type === 'void') { const voidSubject = namedNode(this.baseIRI + this.getUniqueID('void')); this.prefixes['void'] = namespaces.void; this.quads.push(quad( tSubject, namedNode(namespaces.rmlt + 'target'), voidSubject )); this.quads.push(quad( voidSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.void + 'Dataset') )); if (target.access) { this.quads.push(quad( voidSubject, namedNode(namespaces.void + 'dataDump'), namedNode('file://' + target.access) )); } else { Logger.error("Path to local file is required") } /* SPARQL endpoint */ } else if (target.type === 'sd' || target.type === 'sparql') { const sdSubject = namedNode(this.baseIRI + this.getUniqueID('sd')); this.prefixes['sd'] = namespaces.sd; this.quads.push(quad( tSubject, namedNode(namespaces.rmlt + 'target'), sdSubject )); this.quads.push(quad( sdSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.sd + 'Service') )); this.quads.push(quad( sdSubject, namedNode(namespaces.sd + 'supportedLanguage'), namedNode(namespaces.sd + 'SPARQL11Update') )); if (target.access) { this.quads.push(quad( sdSubject, namedNode(namespaces.sd + 'endpoint'), namedNode(target.access) )); } else { Logger.error("SPARQL endpoint URL is required"); } /* DCAT Dataset */ } else if (target.type === 'dcat') { const dcatSubject = namedNode(this.baseIRI + this.getUniqueID('dcat')); this.prefixes['dcat'] = namespaces.dcat; this.quads.push(quad( tSubject, namedNode(namespaces.rmlt + 'target'), dcatSubject )); this.quads.push(quad( dcatSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.dcat + 'Dataset') )); if (target.access) { this.quads.push(quad( dcatSubject, namedNode(namespaces.dcat + 'dataDump'), namedNode('file://' + target.access) )); } else { Logger.error("Path to VoID dataset is required") } /* HTTP request target */ } else if (target.type === 'directhttprequest') { this._generateHTTPRequestAccess(target, tSubject, targetName, false, true); } else if (target.type === 'linkedhttprequest') { this._generateHTTPRequestAccess(target, tSubject, targetName, false, false); } /* Invalid target */ else { Logger.error(`No (valid) target type found for target "${JSON.stringify(target)}"`); } // Add LDES specific triples, if any if (target.ldes) { this.prefixes['ldes'] = namespaces.ldes; this.prefixes['tree'] = namespaces.tree; // say it's an LDES logical target this.quads.push(quad( tSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rmlt + 'EventStreamTarget'), )); // add the required LDES "base" iri this.quads.push(quad( tSubject, namedNode(namespaces.rmlt + 'ldesBaseIRI'), namedNode(AbstractGenerator.expandPrefix(target.ldes.id)), )); // add the optional ldesGenerateImmutableIRI const generateImmutableIRI = (target.ldes.generateImmutableIRI !== null && target.ldes.generateImmutableIRI !== undefined) ? target.ldes.generateImmutableIRI : false; if (target.ldes.generateImmutableIRI) { this.quads.push(quad( tSubject, namedNode(namespaces.rmlt + 'ldesGenerateImmutableIRI'), literal(generateImmutableIRI, 'boolean'), )); } // add the ldes:EventStream object const eventStreamSubject = namedNode(this.baseIRI + this.getUniqueID('eventStream')); this.quads.push(quad( tSubject, namedNode(namespaces.rmlt + 'ldes'), eventStreamSubject, )); // Add the optional SHACL shape. if (target.ldes.shape) { this.quads.push(quad( eventStreamSubject, namedNode(namespaces.tree + 'shape'), namedNode(AbstractGenerator.expandPrefix(target.ldes.shape)), )); } // add the optional timestampPath if (target.ldes.timestampPath) { this.quads.push(quad( eventStreamSubject, namedNode(namespaces.ldes + 'timestampPath'), namedNode(AbstractGenerator.expandPrefix(target.ldes.timestampPath)), )); } // add the versionOfPath if (target.ldes.versionOfPath) { this.quads.push(quad( eventStreamSubject, namedNode(namespaces.ldes + 'versionOfPath'), namedNode(AbstractGenerator.expandPrefix(target.ldes.versionOfPath)), )); } } return tSubject; } generateTargetTriplesMap(target, logicalTargetTemplate, thisMappingVar, mappingName, sourcesIRIMap) { this.prefixes['rmle'] = namespaces.rmle; this.prefixes['rmlt'] = namespaces.rmlt; const tmSubject = namedNode(this.baseIRI + this.getUniqueID("map_generated_logical_target_tm")); this.quads.push(quad( tmSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rr + 'TriplesMap') )); this.quads.push(quad( tmSubject, namedNode(namespaces.rdfs + 'label'), literal(mappingName) )); // add logical source const sourceSubject = sourcesIRIMap[target.sources]; this.quads.push(quad( tmSubject, namedNode(namespaces.rml + 'logicalSource'), sourceSubject )); // write subject mapping const smSubject = namedNode(this.baseIRI + this.getUniqueID('s')); this.quads.push(quad( tmSubject, namedNode(namespaces.rr + 'subjectMap'), smSubject )); this.quads.push(quad( smSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rr + 'SubjectMap') )); // add subject template const {predicate, object} = this.getAppropriatePredicateAndObjectForValue(logicalTargetTemplate); this.quads.push(quad( smSubject, predicate, object )); // say that it's a logical target this.quads.push(quad( smSubject, namedNode(namespaces.rr + 'class'), namedNode(namespaces.rmlt + 'LogicalTarget') )); // add the ThisMapping (extension to indicate generated logical targets) this.quads.push(quad( smSubject, namedNode(namespaces.rml + 'logicalTarget'), namedNode(namespaces.rmle + 'ThisMapping') )); // Add the reference to the target (template) //const targetTemplate = 'generated_t' + logicalTargetTemplate.slice(12); const targetTemplate = 'generated_t' + target.generated_dynamic_target_key.slice(12); this._generatePOMap(tmSubject, namespaces.rmlt + "target", {value: targetTemplate, type: "iri"}); // Add serialization format let format = this._getSerializationFormat(target.serialization); this.prefixes['formats'] = namespaces.formats; this._generatePOMap(tmSubject, namespaces.rmlt + "serialization", {value: format.id, type: "iri"}); // Optionally apply compression if (target.compression) { /* See: http://semweb.mmlab.be/ns/rml-compression# for known algorithms */ let algorithms = ['gzip', 'zip', 'targzip', 'tarxz']; this.prefixes['comp'] = namespaces.comp; if (algorithms.indexOf(target.compression) > -1) { this._generatePOMap(tmSubject, namespaces.rmlt + "compression", target.compression); } else { Logger.error(`${target.compression} is not a known compression algorithm`); } } // Add the target map //const tMappingName = const tSubject = namedNode(this.baseIRI + this.getUniqueID("map_generated_target_tm")); this.quads.push(quad( tSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rr + 'TriplesMap') )); this.quads.push(quad( tSubject, namedNode(namespaces.rdfs + 'label'), literal(mappingName) )); // add logical source this.quads.push(quad( tSubject, namedNode(namespaces.rml + 'logicalSource'), sourceSubject )); // write subject mapping const sSubject = namedNode(this.baseIRI + this.getUniqueID('s')); this.quads.push(quad( tSubject, namedNode(namespaces.rr + 'subjectMap'), sSubject )); this.quads.push(quad( sSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rr + 'SubjectMap') )); // add subject template const po = this.getAppropriatePredicateAndObjectForValue(targetTemplate); this.quads.push(quad( sSubject, po.predicate, po.object )); // say that it's a target this.quads.push(quad( sSubject, namedNode(namespaces.rr + 'class'), namedNode(namespaces.rmlt + 'Target') )); // add the ThisMapping (extension to indicate generated logical targets) this.quads.push(quad( sSubject, namedNode(namespaces.rml + 'logicalTarget'), namedNode(namespaces.rmle + 'ThisMapping') )); // add PO mappings for the different types of targets. Similar to generating a target /* Path to file as string */ if (!target.type || target.type === 'localfile') { target.type = 'void' // localfile is a VoID dataset } if (target.type === 'void') { this.prefixes['void'] = namespaces.void; this.quads.push(quad( sSubject, namedNode(namespaces.rr + 'class'), namedNode(namespaces.void + 'Dataset') )); this._generatePOMap(tSubject, namespaces.void + "dataDump", { value: 'file://' + target.access, type: "literal" }); } /* SPARQL endpoint */ else if (target.type === 'sd' || target.type === 'sparql') { this.prefixes['sd'] = namespaces.sd; this.quads.push(quad( sSubject, namedNode(namespaces.rr + 'class'), namedNode(namespaces.sd + 'Service') )); this._generatePOMap(tSubject, namespaces.sd + "supportedLanguage", {value: namespaces.sd + "SPARQL11Update", type: "iri"}); if (target.access) { this._generatePOMap(tSubject, namespaces.sd + "endpoint", {value: target.access, type: "iri"}); } else { Logger.error("SPARQL endpoint URL is required"); } } /* DCAT Dataset */ else if (target.type === 'dcat') { this.prefixes['dcat'] = namespaces.dcat; this.quads.push(quad( sSubject, namedNode(namespaces.rr + 'class'), namedNode(namespaces.dcat + 'Dataset') )); if (target.access) { this._generatePOMap(tSubject, namespaces.dcat + "dataDump", { value: 'file://' + target.access, type: "iri" }); } else { Logger.error("Path to VoID dataset is required") } } /* HTTP request target */ else if (target.type === 'directhttprequest' || target.type === 'linkedhttprequest') { this.prefixes['htv'] = namespaces.htv; this.prefixes['rmle'] = namespaces.rmle; const isDirect = target.type === 'directhttprequest'; // add the subclass (type of http request) const subClassName = isDirect ? 'DirectHttpRequest' : 'LinkedHttpRequest'; this.quads.push(quad( sSubject, namedNode(namespaces.rr + 'class'), namedNode(namespaces.rmle + subClassName) )); // check for required 'access' key if (target.access) { const uriPrefix = isDirect ? namespaces.htv + 'absoluteURI' : namespaces.rmle + 'linkingAbsoluteURI'; this._generatePOMap(tSubject, uriPrefix, {value: target.access, type: 'literal'}); } else { Logger.error("'access' key required for HTTP request target " + tSubject); } // check for required 'rel' key if it's a linked request if (!isDirect) { if (target.rel) { this._generatePOMap(tSubject, namespaces.rmle + "linkRelation", {value: target.rel, type: 'literal'}); } else { Logger.error("'rel' key required for linked HTTP request target " + tSubject); } } // check for optional method name if (target.methodName) { this._generatePOMap(tSubject, namespaces.htv + "methodName", {value: target.methodName, type: 'literal'}); } // check for optional contentType if (target.contentType) { this._generatePOMap(tSubject, namespaces.rmle + "contentTypeHeader", {value: target.contentType, type: 'literal'}); } if (target.authentication) { const authIRI = this.authsIRIMap[target.authentication]; this._generatePOMap(tSubject, namespaces.rmle + "userAuthentication", {value: authIRI.id, type: 'iri'}); } } /* Invalid target */ else { Logger.error(`No (valid) target type found for target "${JSON.stringify(target)}"`); } // TODO: LDES ? } /** * * @param sourceOrTarget the object this access is part of * @param sourceOrTargetSubject the URI of the sourceOrTargetObject * @param sourceOrTargetName The name (label) of the source or target object * @param isSource true: sourceOrTarget is a source; false: sourceOrTarget is a target * @param isDirect true: the HTTP access is 'direct'; false: the HTTP access is 'linked' * @private */ _generateHTTPRequestAccess(sourceOrTarget, sourceOrTargetSubject, sourceOrTargetName, isSource, isDirect) { this.prefixes['htv'] = namespaces.htv; this.prefixes['rmle'] = namespaces.rmle; const httpSubject = namedNode(this.baseIRI + this.getUniqueID(sourceOrTarget.type)); const className = isSource ? 'source' : 'target'; this.quads.push(quad( sourceOrTargetSubject, namedNode(namespaces.rmlt + className), httpSubject )); // add the subclass const subClassName = isDirect ? 'DirectHttpRequest' : 'LinkedHttpRequest' this.quads.push(quad( httpSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rmle + subClassName) )); // check for required 'access' key if (sourceOrTarget.access) { const uriPrefix = isDirect ? namespaces.htv + 'absoluteURI' : namespaces.rmle + 'linkingAbsoluteURI'; this.quads.push(quad( httpSubject, namedNode(uriPrefix), literal(sourceOrTarget.access) )); } else { Logger.error("'access' key required for HTTP request " + className + " " + sourceOrTargetName); } // check for required 'rel' key if it's a linked request if (!isDirect) { if (sourceOrTarget.rel) { this.quads.push(quad( httpSubject, namedNode(namespaces.rmle + 'linkRelation'), literal(sourceOrTarget.rel) )); } else { Logger.error("'rel' key required for linked HTTP request target " + targetName); } } // check for optional method name if (sourceOrTarget.methodName) { this.quads.push(quad( httpSubject, namedNode(namespaces.htv + 'methodName'), literal(sourceOrTarget.methodName) )); } // check for optional contentType (if target) if (!isSource) { if (sourceOrTarget.contentType) { this.quads.push(quad( httpSubject, namedNode(namespaces.rmle + 'contentTypeHeader'), literal(sourceOrTarget.contentType) )); } } // check for optional accept header (if source) if (isSource) { if (sourceOrTarget.accept) { this.quads.push(quad( httpSubject, namedNode(namespaces.rmle + 'acceptTypeHeader'), literal(sourceOrTarget.accept) )); } } if (sourceOrTarget.authentication) { const authIRI = this.authsIRIMap[sourceOrTarget.authentication]; this.quads.push(quad( httpSubject, namedNode(namespaces.rmle + 'userAuthentication'), authIRI )); } } _generateDatabaseDescription(subject, source) { this.quads.push(quad( subject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.d2rq + 'Database') )); this.quads.push(quad( subject, namedNode(namespaces.d2rq + 'jdbcDSN'), literal(source.access) )); this.quads.push(quad( subject, namedNode(namespaces.d2rq + 'jdbcDriver'), literal(jdbcDrivers[source.type]) )); if (source.credentials) { if (source.credentials.username) { this.quads.push(quad( subject, namedNode(namespaces.d2rq + 'username'), literal(source.credentials.username) )); } if (source.credentials.password) { this.quads.push(quad( subject, namedNode(namespaces.d2rq + 'password'), literal(source.credentials.password) )); } } } generateFnSource(fnSubject, sourceSubject) { this.quads.push(quad( fnSubject, namedNode(namespaces.rml + 'logicalSource'), sourceSubject )); } /** * This method adds triples for a Language Map. * @param objectMap - The Object Map to which the Language Map has to be added. * @param value - The value for the language. * @return IRI - The Language Map IRI. */ generateLanguageTerms(objectMap, value) { const languageMapSubject = namedNode(this.baseIRI + this.getUniqueID('language')); this.quads.push(quad( objectMap, namedNode(namespaces.rml + 'languageMap'), languageMapSubject )); const {predicate, object} = this.getAppropriatePredicateAndObjectForValue(value); this.quads.push(quad( languageMapSubject, predicate, object )); return languageMapSubject } generateCondition(condition, omSubject) { if (condition.function === 'equal' && !this._parametersContainsFunction(condition.parameters) && !this._parametersContainsConstantValues(condition.parameters) && !this._parametersContainsTemplates(condition.parameters) ) { super.generateCondition(condition, omSubject); } else { if (condition.function === 'equal') { this.convertEqualToIDLabEqual(condition); } const joinConditionSubject = namedNode(this.baseIRI + this.getUniqueID('jc')); this.quads.push(quad( omSubject, namedNode(namespaces.rml + 'joinCondition'), joinConditionSubject )); this.generateFunctionTermMap(joinConditionSubject, condition); } } getReferenceOnlyPredicate() { return namedNode(namespaces.rml + 'reference'); } /** * This method returns true if there is at least one parameter that is a function. * @param parameters The list of parameters to check. * @returns {boolean} True if at least one parameter is found that is a function, else false. * @private */ _parametersContainsFunction(parameters) { let i = 0; while (i < parameters.length && typeof parameters[i].value === 'string') { i ++ } return i < parameters.length; } /** * This method returns true if there is at least one parameter that is a constant value. * @param parameters The list of parameters to check. * @returns {boolean} True if at least one parameter is found that is a constant value, else false. * @private */ _parametersContainsConstantValues(parameters) { let i = 0; while (i < parameters.length && (typeof parameters[i].value !== 'string' || (typeof parameters[i].value === 'string' && this.getAppropriatePredicateAndObjectForValue(parameters[i].value).predicate.value !== namespaces.rr + 'constant'))) { i ++ } return i < parameters.length; } /** * This method returns true if there is at least one parameter that is a template. * @param parameters The list of parameters to check. * @returns {boolean} True if at least one parameter is found that is a template, else false. * @private */ _parametersContainsTemplates(parameters) { let i = 0; while (i < parameters.length && (typeof parameters[i].value !== 'string' || (typeof parameters[i].value === 'string' && this.getAppropriatePredicateAndObjectForValue(parameters[i].value).predicate.value !== namespaces.rr + 'template'))) { i ++ } return i < parameters.length; } _getSerializationFormat(serialization) { let format = namedNode(namespaces.formats + 'N-Quads'); if (serialization) { switch (serialization) { case 'jsonld': format = namedNode(namespaces.formats + 'JSON-LD'); break; case 'n3': format = namedNode(namespaces.formats + 'N3'); break; case 'ntriples': format = namedNode(namespaces.formats + 'N-Triples'); break; case 'nquads': format = namedNode(namespaces.formats + 'N-Quads'); break; case 'ldpatch': format = namedNode(namespaces.formats + 'LD_Patch'); break; case 'microdata': format = namedNode(namespaces.formats + 'microdata'); break; case 'owlxml': format = namedNode(namespaces.formats + 'OWL_XML'); break; case 'owlfunctional': format = namedNode(namespaces.formats + 'OWL_Functional'); break; case 'owlmanchester': format = namedNode(namespaces.formats + 'OWL_Manchester'); break; case 'powder': format = namedNode(namespaces.formats + 'POWDER'); break; case 'powder-s': format = namedNode(namespaces.formats + 'POWDER-S'); break; case 'prov-n': format = namedNode(namespaces.formats + 'PROV-N'); break; case 'prov-xml': format = namedNode(namespaces.formats + 'PROV-XML'); break; case 'rdfa': format = namedNode(namespaces.formats + 'RDFa'); break; case 'rdfjson': format = namedNode(namespaces.formats + 'RDF_JSON'); break; case 'rdfxml': format = namedNode(namespaces.formats + 'RDF_XML'); break; case 'rifxml': format = namedNode(namespaces.formats + 'RIF_XML'); break; case 'sparqlxml': format = namedNode(namespaces.formats + 'SPARQL_Results_XML'); break; case 'sparqljson': format = namedNode(namespaces.formats + 'SPARQL_Results_JSON'); break; case 'sparqlcsv': format = namedNode(namespaces.formats + 'SPARQL_Results_CSV'); break; case 'sparqltsv': format = namedNode(namespaces.formats + 'SPARQL_Results_TSV'); break; case 'turtle': format = namedNode(namespaces.formats + 'Turtle'); break; case 'trig': format = namedNode(namespaces.formats + 'TriG'); break; default: format = namedNode(namespaces.formats + 'N-Quads'); break; } } return format; } /** * Generates a PO Map from a single predicate and a single obejct. * This is a simplified version of the code found in abstract-generator. * @param tmSubject * @param predicate * @param object * @private */ _generatePOMap(tmSubject, predicate, object) { const pomSubject = namedNode(this.baseIRI + this.getUniqueID('pom')); this.quads.push(quad( pomSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rr + 'PredicateObjectMap') )); this.quads.push(quad( tmSubject, namedNode(namespaces.rr + 'predicateObjectMap'), pomSubject )); // generate predicate map const pmSubject = namedNode(this.baseIRI + this.getUniqueID('pm')); this.quads.push(quad( pmSubject, namedNode(namespaces.rdf + 'type'), namedNode(namespaces.rr + 'PredicateMap') )); this.quads.push(quad( pomSubject, namedNode(namespaces.rr + 'predicateMap'), pmSubject )); let isPredicateRDFTYPE = false; let appropriatePO; if (predicate === 'a') { appropriatePO = { object: namedNode(namespaces.rdf + 'type'), predicate: namedNode(namespaces.rr + 'constant') }; isPredicateRDFTYPE = true; } else if (typeof predicate === 'object') { appropriatePO = this.getAppropriatePredicateAndObjectForValue(predicate.value, true); } else { appropriatePO = this.getAppropriatePredicateAndObjectForValue(predicate, true); } this.quads.push(quad( pmSubject, appropriatePO.predicate, appropriatePO.object )); // generate object map const omSubject = namedNode(this.baseIRI + this.getUniqueID('om')); this.quads.push(quad( pomSubject, namedNode(namespaces.rr + 'objectMap'), omSubject )); this.generateNormalObjectMap(omSubject, object); } } module.exports = RMLGenerator;