UNPKG

rdf

Version:

RDF datatype integration, RDF Interfaces API, and utility functions

533 lines (514 loc) 20 kB
"use strict"; var RDFNodeEquals = require('./RDFNode.js').RDFNodeEquals; var RDFNode = require('./RDFNode.js').RDFNode; var NamedNode = require('./RDFNode.js').NamedNode; var BlankNode = require('./RDFNode.js').BlankNode; var defaults = require('./prefixes.js'); var encodeString = require('./encodeString.js'); var env = require('./environment.js').environment; var rdfnil = new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'); var rdffirst = new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#first'); var rdfrest = new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#rest'); var rdftype = new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'); function xsdns(v){ return 'http://www.w3.org/2001/XMLSchema#'+v; } function _(v) { return { writable:false, configurable:true, enumerable:false, value:v }; } function _getter(v) { return { configurable:true, enumerable:false, get:v }; } // Expands a Turtle/N3 keyword to an IRI // p: property name // profile: profile to use resolving prefixes function expandprop(name, profile) { if(name instanceof RDFNode) return name; if(name == 'a') return rdftype; name = name.replace('$',':'); var expanded = profile.resolve(name) || name; return new NamedNode(expanded); } function shrinkprop(ref, profile) { //if(rdftype.equals(ref)) return "a"; return createNodeFrom(ref).toTurtle(profile); } function expandnode(p, profile) { if(p instanceof RDFNode){ return p; }else if(p.substring(0,2)=='_:'){ return new BlankNode(p); }else{ return new NamedNode(profile.resolve(p) || p); } } function shrinknode(ref, profile) { return createNodeFrom(ref).toTurtle(profile); } function pad(v,l){ return ('0000'+v).substr(-(l||2)); } function n3(value, profile){ if(typeof value.n3=='function') return value.n3(profile); if(typeof value=='string') return exports.String.n3.call(value, profile); if(typeof value=='number') return exports.Number.n3.call(value, profile); if(typeof value=='boolean') return exports.Boolean.n3.call(value, profile); if(value instanceof Date) return exports.Date.n3.call(value, profile); throw new TypeError('Cannot create n3 from '+Object.toString.call(value, profile)); } function createNodeFrom(value){ if(value instanceof BlankNode) return value; if(value instanceof NamedNode) return value; if(typeof value!='string') throw new TypeError('Expected string got '+JSON.stringify(value)); if(value.substring(0,2)=='_:') return new BlankNode(value); else return new NamedNode(value); } // JS3/JSON-LD decoding //function graphify(o, base, parentProfile){ // if(!o.id) var o=o.ref(); // return o.graphify(parentProfile); //} exports.StructuredGraph = {}; exports.StructuredGraph.graphify = function graphifyObject(aliasmap){ var o = this; var graph = env.createGraph(); var profile = this.getProfile(aliasmap); //var idNode = o.id; var idNode = this.isNamed ? this.id : (this['$id'] || this['@id'] || this.id); function graphify_property(s1,p1,o1) { if(typeof s1=='string') var s1n = createNodeFrom(s1); else if(s1 instanceof NamedNode) var s1n = s1; else if(s1 instanceof BlankNode) var s1n = s1; else throw new Error('Expected string/NamedNode/BlankNode subject'); if(p1[0]=='@' || p1[0]=='$') return; if(typeof(o1)=='function' || typeof(o1)=='undefined') return; graphify_value(s1n, expandprop(p1,profile), o1); } function graphify_value(s1n,p1n,o1) { if(Array.isArray(o1) || o1['$list'] || o1['@list'] || o1['$set'] || o1['@set']) { if(o1['$list'] || o1['@list']){ var arr = o1['$list'] || o1['@list']; var list = true; }else if(o1['$set'] || o1['@set']){ var arr = o1['$set'] || o1['@set']; var list = false; }else{ var arr = o1; var list = false; } // o1 is a Collection or a multi-valued property if(!list) { // o1 is a multi-valued property arr.forEach( function(item) { graphify_value(s1n, p1n, item); }); } else { // o1 is an rdf:Collection if(o1.length == 0) { graph.add( env.createTriple(s1n, p1n, rdfnil ) ); } else { var bnode = env.createBlankNode(); graph.add( env.createTriple(s1n, p1n, bnode) ); arr.forEach(function(item,x) { graphify_property(bnode, rdffirst, item); var n = env.createBlankNode(); graph.add( env.createTriple(bnode, rdfrest, (x==arr.length-1) ? rdfnil : n ) ); bnode = n; }); } } }else if(o1 instanceof RDFNode){ graph.add( env.createTriple(s1n, p1n, o1 ) ); }else if(o1 instanceof Date){ var literal = exports.Date.toRDFNode.call(o1); graph.add( env.createTriple(s1n, p1n, literal ) ); }else if(typeof o1=='object' && !o1.id) { var id = o1.id || o1['$id'] || o1['@id']; if(typeof o1=='object' && !o1.nodeType){ // If the Object doesn't have a bnode, give it one o1 = ref.call(o1); id = id || o1.id; } // o1 is an Object, add triple and child triples graph.add( env.createTriple(s1n, p1n, createNodeFrom(id) ) ); graph.addAll( o1.graphify(profile) ); } else if(typeof o1=='string') { // o1 is a string (convert to NamedNode) or RDFNode (NamedNode, BlankNode, or Literal) graph.add( env.createTriple(s1n, p1n, expandnode(o1,profile) ) ); } else if(typeof o1=='number') { graph.add( env.createTriple(s1n, p1n, exports.Number.toRDFNode.call(o1) ) ); } else if(typeof o1=='boolean') { graph.add( env.createTriple(s1n, p1n, exports.Boolean.toRDFNode.call(o1) ) ); } else { throw new Error('Unknown type '+(typeof o1)); } } //if(typeof(id)=="object") throw new Error("Not an object: "+require('util').inspect(this)); Object.keys(o).forEach(function(p) { graphify_property(idNode, p, o[p]); }); return graph; }; exports.StructuredGraph.n3 = function toN3(aliasmap, padding) { padding = padding||'\n\t'; var outs = []; var o = this; // Determine the prefix/term profile this object is using var profile = exports.StructuredGraph.getProfile.call(o, aliasmap); var idNode = this.isNamed ? this.id : (this['$id'] || this['@id'] || this.id); // Go through each key and produce a predicate-object line Object.keys(this).forEach(toN3_property); function toN3_property(p) { // Ignore things beginning with @, they're keywords to be interperted if(p[0]=='$' || p[0]=='@' || (o.list&&p=='list')) return; var val = o[p]; // val can be anything: // undefined, null, string, number, boolean, function, or object (Array, RDFNode, Date, or plain object) // Ignore functions, they're from the prototype probably if(typeof val == 'function') return; if(val === undefined) return; if(val === null) return; // Determine the name to output. Generate a PrefixedName if possible, otherwise output an IRIREF var predicateIRI = expandprop(p, profile); var predicateString = shrinkprop(predicateIRI, profile); outs.push( predicateString + ' ' + toN3_value(val) ); } function toN3_value(val){ if(typeof val=='string'){ // If the value is an IRI, or an object without an IRI, recurse return shrinknode(expandnode(val, profile), profile); }else if(typeof val=='number' || typeof val=='boolean' || val instanceof Date){ // If the value is an IRI, or an object without an IRI, recurse return n3(val, profile); }else if(val && typeof val.nodeType=='function' && val.nodeType() == 'IRI'){ // If the value is a NamedNode instance, use that return shrinknode(val.toString(), profile); }else if(val && typeof val.id=='string'){ // If the value is an object with an "id" property, use that as the object // Then don't forget to serialize the object out. var objectIRI = expandnode(val.id, profile); var objectName = shrinknode(objectIRI); return objectName; } if(val && val.nodeType && val.nodeType == 'IRI'){ return val.n3(profile); }else if(Array.isArray(val)){ return val.map(function(item){ return toN3_value(item, profile, padding+'\t'); }).join(', '); }else if(typeof val=='object' && val['@list'] && Array.isArray(val['@list'])){ return '( ' + val['@list'].map(function(item){ return toN3_value(item, profile, padding+'\t'); }).join(' ') + ' )'; }else if(typeof val=='object' && val['@set'] && Array.isArray(val['@set'])){ return val['@set'].map(function(item){ return toN3_value(item, profile, padding+'\t'); }).join(', '); }else{ // Encode a Literal, or recursively encode the object and the statements in it. var valref = (typeof val.n3=='function') ? val : ref.call(val) ; return valref.n3(profile, padding+'\t'); } } if(this.isNamed){ if(outs.length > 1) return shrinknode(idNode,profile) + padding + outs.join(";"+padding) + ' .'; if(outs.length == 1) return shrinknode(idNode,profile) + ' ' + outs.join(";"+padding) + ' .'; else return ''; }else{ return '[' + padding + outs.join(';'+padding+'') + padding + ']'; } }; exports.StructuredGraph.getProfile = function(aliasmap) { var o = this; // Determine the prefix/term profile this object is using var profile = env.createProfile(); //profile.importProfile(env); defaults.loadRequiredPrefixMap(profile); if(o.aliasmap) profile.importProfile(o.aliasmap, true); if(aliasmap){ profile.importProfile(aliasmap, true); if(aliasmap.terms.vocabulary) profile.setDefaultVocabulary(aliasmap.terms.vocabulary); } var context = o['$context']||o['@context']; if(context){ for(var prefix in context){ if(prefix[0]=='@' || prefix[0]=='$'){ var keyword = prefix.substring(1); if(keyword=='vocab'){ profile.setDefaultVocabulary(context[prefix]); } }else{ profile.setPrefix(prefix, context[prefix]); } } } return profile; }; exports.ref = ref; function ref(id) { var copy = {}; for(var n in this) copy[n] = this[n]; //var copy = Object.create(this); Object.defineProperties(copy, { 'id': _( id ? (env.resolve(id)||id) : env.createBlankNode().toString() ), 'isNamed': _( !!id ), n3: _(exports.StructuredGraph.n3), toNT: _( function(a) { return this.graphify(a).toArray().join("\n"); }), graphify: _(exports.StructuredGraph.graphify), using: _( function() { Object.defineProperty(this,'aliasmap',_(Array.prototype.slice.call(arguments))); return this; }), getProfile: _(exports.StructuredGraph.getProfile), }); return copy; } // All exports.Object = { equals: RDFNodeEquals, ref: ref, }; exports.ObjectProperties = { equals: _(exports.Object.equals), ref: _(exports.Object.ref), }; exports.setObjectProperties = function setObjectProperties(o){ Object.defineProperties(o, exports.ObjectProperties); }; // String exports.String = { tl: function(t) { return env.createLiteral(this.toString(), null, t); }, l: function(l) { return env.createLiteral(this.toString(), l, null); }, resolve: function() { return env.resolve(this)||this.toString(); }, valueGetter: function(){ return this.toString(); }, nodeType: function() { //if(this.type) return 'TypedLiteral'; //if(this.language || this.indexOf(' ') >= 0 || this.indexOf(':') == -1 ) return 'PlainLiteral'; if(this.substr(0,2) == '_:') return 'BlankNode'; return 'IRI'; }, termTypeGetter: function() { if(this.substr(0,2) == '_:') return 'BlankNode'; return 'NamedNode'; }, n3: function(profile) { // FIXME we don't actually use the 'PlainLiteral' or 'TypedLiteral' productions. Either remove them, or re-add detection of them to String#nodeType() switch(this.nodeType()) { case 'PlainLiteral': return ('"'+encodeString(this)+'"'+(this.language?'@'+this.language:'')).toString(); case 'IRI': var resolved = this.resolve(); return (resolved == this) ? "<"+encodeString(resolved)+">" : this.toString(); case 'BlankNode': return this.toString(); case 'TypedLiteral': if(this.type.resolve() == env.resolve("rdf:PlainLiteral")) return '"'+encodeString(this)+'"'; return '"'+encodeString(this)+'"^^' + (new NamedNode(this.datatype)).n3(profile); } }, toNT: function() { switch(this.nodeType()) { case 'PlainLiteral': return ('"' + encodeString(this) + '"' + ( this.language ? '@' + this.language : '')).toString(); case 'IRI': return "<" + encodeString(this.resolve()) + ">"; case 'BlankNode': return this.toString(); case 'TypedLiteral': if(this.type.resolve() == env.resolve("rdf:PlainLiteral")) return '"' + encodeString(this) + '"'; return '"' + encodeString(this) + '"^^<' + this.datatype + '>'; } }, toCanonical: function() { return this.n3(); }, profile: env, }; exports.StringProperties = { tl: _(exports.String.tl), l: _(exports.String.l), resolve: _(exports.String.resolve), value: _getter(exports.String.valueGetter), nodeType: _(exports.String.nodeType), termType: _getter(exports.String.termTypeGetter), n3: _(exports.String.n3), toNT: _(exports.String.toNT), toCanonical: _(exports.String.toCanonical), profile: _(exports.String.profile), }; exports.setStringProperties = function setStringProperties(o){ Object.defineProperties(o, exports.StringProperties); }; // Array exports.Array = { toList: function() { this.list = true; return this; }, n3: function(a, padding) { padding = padding||'\n\t'; var outs = []; this.forEach( function(i) { if(typeof i == 'function') return; if(i.id && i.id.nodeType() == 'IRI') return outs.push( i.id.n3() ); if(!i.nodeType) ref.call(i); outs.push(i.n3(a, padding+'\t')); }); return this.list ? "("+padding+outs.join(padding)+" )" : outs.join(", "); }, }; exports.ArrayProperties = { toList: _(exports.Array.toList), n3: _(exports.Array.n3), }; exports.setArrayProperties = function setArrayProperties(o){ Object.defineProperties(o, exports.ArrayProperties); }; // Boolean exports.Boolean = { datatype: xsdns("boolean"), valueGetter: function(){ return this; }, nodeType: function() { return "TypedLiteral"; }, termType: "Literal", termTypeGetter: function() { return "Literal"; }, n3: function() { return this.valueOf(); }, toNT: function() { return '"' + this.valueOf() + '"' + "^^<" + this.datatype + '>'; }, toRDFNode: function() { return env.createTypedLiteral(this.valueOf().toString(), xsdns("boolean")); }, toCanonical: function() { return this.toNT(); }, }; exports.BooleanProperties = { datatype: _(exports.Boolean.datatype), value: _getter(exports.Boolean.valueGetter), nodeType: _(exports.Boolean.nodeType), termType: _getter( function() { return "Literal"; } ), n3: _(exports.Boolean.n3), toNT: _(exports.Boolean.toNT), toRDFNode: _(exports.Boolean.toRDFNode), toCanonical: _(exports.Boolean.toCanonical), }; exports.setBooleanProperties = function setBooleanProperties(o){ Object.defineProperties(o, exports.BooleanProperties); }; // Date exports.Date = { datatype: xsdns("dateTime"), valueGetter: function(){return this; }, nodeType: function() { return "TypedLiteral"; }, termTypeGetter: function() { return "Literal"; }, n3: function(profile) { if(!this.getTime()) return '"NaN"^^<' + (new NamedNode(xsdns('double'))).n3(profile) + '>'; return '"' + this.getUTCFullYear()+'-' + pad(this.getUTCMonth()+1)+'-' + pad(this.getUTCDate())+'T' + pad(this.getUTCHours())+':' + pad(this.getUTCMinutes())+':' + pad(this.getUTCSeconds())+'Z"^^' + (new NamedNode(xsdns('dateTime'))).n3(profile); }, toNT: function() { return this.n3(); }, toRDFNode: function() { return env.createTypedLiteral( this.getUTCFullYear()+'-' + pad(this.getUTCMonth()+1)+'-' + pad(this.getUTCDate())+'T' + pad(this.getUTCHours())+':' + pad(this.getUTCMinutes())+':' + pad(this.getUTCSeconds())+'Z', xsdns("dateTime") ); }, toCanonical: function() { return this.n3(); }, }; exports.DateProperties = { datatype: _(exports.Date.datatype), value: _getter(exports.Date.valueGetter), nodeType: _(exports.Date.nodeType), termType: _getter(exports.Date.termTypeGetter), n3: _(exports.Date.n3), toNT: _(exports.Date.toNT), toRDFNode: _(exports.Date.toRDFNode), toCanonical: _(exports.Date.toCanonical), }; exports.setDateProperties = function setDateProperties(o){ Object.defineProperties(o, exports.DateProperties); }; // Number var INTEGER = new RegExp("^(-|\\+)?[0-9]+$", ""); var DOUBLE = new RegExp("^(-|\\+)?(([0-9]+\\.[0-9]*[eE]{1}(-|\\+)?[0-9]+)|(\\.[0-9]+[eE]{1}(-|\\+)?[0-9]+)|([0-9]+[eE]{1}(-|\\+)?[0-9]+))$", ""); var DECIMAL = new RegExp("^(-|\\+)?[0-9]*\\.[0-9]+?$", ""); exports.Number = { datatypeGetter: function() { if(this == Number.POSITIVE_INFINITY) return xsdns('double'); if(this == Number.NEGATIVE_INFINITY) return xsdns('double'); if(isNaN(this)) return xsdns('double'); var n = this.toString(); if(INTEGER.test(n)) return xsdns('integer'); if(DECIMAL.test(n)) return xsdns('decimal'); if(DOUBLE.test(n)) return xsdns('double'); }, valueGetter: function(){ return this; }, nodeType: function() { return "TypedLiteral"; }, termTypeGetter: function() { return "Literal"; }, n3: function() { if(this == Number.POSITIVE_INFINITY) return '"INF"^^<' + xsdns('double') + '>'; if(this == Number.NEGATIVE_INFINITY) return '"-INF"^^<' + xsdns('double') + '>'; if(isNaN(this)) return '"NaN"^^<' + xsdns('double') + '>'; return this.toString(); }, toNT: function() { if(this == Number.POSITIVE_INFINITY) return '"INF"^^<' + xsdns('double') + '>'; if(this == Number.NEGATIVE_INFINITY) return '"-INF"^^<' + xsdns('double') + '>'; if(isNaN(this)) return '"NaN"^^<' + xsdns('double') + '>'; return '"' + this.toString() + '"' + "^^<" + exports.Number.datatypeGetter.call(this) + '>'; }, toRDFNode: function() { if(this == Number.POSITIVE_INFINITY) return env.createTypedLiteral('INF', xsdns('double')); if(this == Number.NEGATIVE_INFINITY) return env.createTypedLiteral('-INF', xsdns('double')); if(isNaN(this)) return env.createTypedLiteral('NaN', xsdns('double')); return env.createTypedLiteral(this.toString(), exports.Number.datatypeGetter.call(this)); }, toCanonical: function() { return this.nt(); }, toTL: function() { return this.nt(); }, }; exports.NumberProperties = { datatype: { configurable:true, enumerable:false, get: exports.Number.datatypeGetter, }, value: _getter(exports.Number.valueGetter), nodeType: _( function() { return "TypedLiteral"; } ), termType: _getter(exports.Number.termTypeGetter), n3: _(exports.Number.n3), toNT: _(exports.Number.toNT), toRDFNode: _(exports.Number.toRDFNode), toCanonical: _(exports.Number.toCanonical), toTL: _(exports.Number.toTL), }; exports.setNumberProperties = function setNumberProperties(o){ Object.defineProperties(o, exports.NumberProperties); }; // Sometimes the standard API context isn't global, and an Object in one context isn't an Object in another. // For these cases, you'll need to call these functions by hand. exports.setBuiltins = function setBuiltins(){ function setOn(map, target){ for(var n in map) if(target[n]!==undefined) throw new Error('Builtin already set'); } setOn(exports.ObjectProperties, Object.prototype); setOn(exports.StringProperties, String.prototype); setOn(exports.ArrayProperties, Array.prototype); setOn(exports.BooleanProperties, Boolean.prototype); setOn(exports.DateProperties, Date.prototype); setOn(exports.NumberProperties, Number.prototype); exports.setObjectProperties(Object.prototype); exports.setStringProperties(String.prototype); exports.setArrayProperties(Array.prototype); exports.setBooleanProperties(Boolean.prototype); exports.setDateProperties(Date.prototype); exports.setNumberProperties(Number.prototype); }; exports.unsetBuiltins = function unsetBuiltins(){ function unsetOn(map, target){ for(var n in map) if(target[n]===undefined) throw new Error('Builtin '+JSON.stringify(n)+' not set'); for(var n in map){ Object.defineProperty(target, n, {configurable:true, value:null}); delete target[n]; } } unsetOn(exports.ObjectProperties, Object.prototype); unsetOn(exports.StringProperties, String.prototype); unsetOn(exports.ArrayProperties, Array.prototype); unsetOn(exports.BooleanProperties, Boolean.prototype); unsetOn(exports.DateProperties, Date.prototype); unsetOn(exports.NumberProperties, Number.prototype); };