diff --git a/example/people/social_book.js b/example/people/social_book.js index a70e949f0..3625bcff4 100644 --- a/example/people/social_book.js +++ b/example/people/social_book.js @@ -23,7 +23,7 @@ var cardTemplate = "" /** * run on initialization - */ + */ function initialize() { cardTemplate = $("#user_wrapper").clone() //todo: place the proxy default somewhere else ( where?) so it can be easy to configure @@ -39,7 +39,7 @@ function card(who,kb) { /** mailboxes in foaf are usually written as . This * function removes the 'mailto:' part, if it exists */ function removeProtocol(uri) { - if (uri && uri.termType === 'symbol') { + if (uri && uri.termType === 'NamedNode') { var parts= uri.split(":") if (parts.length > 1) return parts[1] else return "unset" @@ -145,7 +145,7 @@ function friends (person,kb,col) { for (i = 0; i < n; i++) { friend = friends[i]; - if (friend && friend.termType === 'symbol') { //only show people with a WebID for the moment. + if (friend && friend.termType === 'NamedNode') { //only show people with a WebID for the moment. var name = kb.any(friend, FOAF('name')) if (!name) { name = friend.uri @@ -164,9 +164,9 @@ function friends (person,kb,col) { } /** - * redraw the screen when the selected user identified by webid is pressed in + * redraw the screen when the selected user identified by webid is pressed in * explorer column col - */ + */ function redraw(webid, col) { if (!col) col = 0 var person = $rdf.sym(webid); @@ -176,7 +176,7 @@ function redraw(webid, col) { docURI = webid.slice(0, indexOf) else docURI = webid var kb = graphs[docURI] - if (!kb) { + if (!kb) { //if the knowledge base was not initialised fetch info from the web (if need CORS go through CORS proxy) kb = graphs[docURI] = new $rdf.IndexedFormula(); var fetch = $rdf.fetcher(kb); diff --git a/index.js b/index.js index 07facae1d..e96be8de0 100644 --- a/index.js +++ b/index.js @@ -45,6 +45,7 @@ $rdf.variable = $rdf.DataFactory.variable // RDFJS DataFactory interface $rdf.blankNode = $rdf.DataFactory.blankNode +$rdf.defaultGraph = $rdf.DataFactory.defaultGraph $rdf.literal = $rdf.DataFactory.literal $rdf.namedNode = $rdf.DataFactory.namedNode $rdf.quad = $rdf.DataFactory.quad diff --git a/src/blank-node.js b/src/blank-node.js index 2f2c767f6..b342171b9 100644 --- a/src/blank-node.js +++ b/src/blank-node.js @@ -29,12 +29,15 @@ class BlankNode extends Node { formula.copyTo(this, bnodeNew) return bnodeNew } + toCanonical () { + return '_:' + this.value + } toString () { return BlankNode.NTAnonymousNodePrefix + this.id } } BlankNode.nextId = 0 -BlankNode.termType = 'bnode' +BlankNode.termType = 'BlankNode' BlankNode.NTAnonymousNodePrefix = '_:n' BlankNode.prototype.classOrder = ClassOrder['BlankNode'] BlankNode.prototype.isBlank = 1 diff --git a/src/data-factory.js b/src/data-factory.js index 984a5d1cd..dff1eb140 100644 --- a/src/data-factory.js +++ b/src/data-factory.js @@ -1,6 +1,7 @@ 'use strict' const BlankNode = require('./blank-node') const Collection = require('./collection') +const DefaultGraph = require('./default-graph') const Fetcher = require('./fetcher') const IndexedFormula = require('./indexed-formula') const Literal = require('./literal') @@ -14,6 +15,9 @@ function blankNode (value) { function collection (elements) { return new Collection(elements) } +function defaultGraph () { + return new DefaultGraph() +} function fetcher (store, timeout, async) { return new Fetcher(store, timeout, async) } @@ -38,6 +42,7 @@ function namedNode (value) { return new NamedNode(value) } function quad (subject, predicate, object, graph) { + graph = graph || new DefaultGraph() return new Statement(subject, predicate, object, graph) } function st (subject, predicate, object, graph) { @@ -52,6 +57,7 @@ function variable (name) { // rdfjs spec factory methods module.exports.blankNode = blankNode +module.exports.defaultGraph = defaultGraph module.exports.graph = graph module.exports.literal = literal module.exports.namedNode = namedNode diff --git a/src/default-graph.js b/src/default-graph.js new file mode 100644 index 000000000..0c94d96e6 --- /dev/null +++ b/src/default-graph.js @@ -0,0 +1,15 @@ +'use strict' +const Node = require('./node') + +class DefaultGraph extends Node { + constructor () { + super() + this.termType = 'DefaultGraph' + this.value = '' + } + toCanonical () { + return this.value + } +} + +module.exports = DefaultGraph diff --git a/src/formula.js b/src/formula.js index dfe6839d4..f31b7718b 100644 --- a/src/formula.js +++ b/src/formula.js @@ -25,19 +25,6 @@ class Formula extends Node { addStatement (st) { return this.statements.push(st) } - any (s, p, o, g) { - var st = this.anyStatementMatching(s, p, o, g) - if (st == null) { - return void 0 - } else if (s == null) { - return st.subject - } else if (p == null) { - return st.predicate - } else if (o == null) { - return st.object - } - return void 0 - } bnode (id) { return new BlankNode(id) } diff --git a/src/indexed-formula.js b/src/indexed-formula.js index 2bb9d7393..72640abdf 100644 --- a/src/indexed-formula.js +++ b/src/indexed-formula.js @@ -269,7 +269,19 @@ class IndexedFormula extends Formula { this.add(quad.subject, quad.predicate, quad.object, quad.graph) }) } - + any (s, p, o, g) { + var st = this.anyStatementMatching(s, p, o, g) + if (st == null) { + return void 0 + } else if (s == null) { + return st.subject + } else if (p == null) { + return st.predicate + } else if (o == null) { + return st.object + } + return void 0 + } anyStatementMatching (subj, pred, obj, why) { var x = this.statementsMatching(subj, pred, obj, why, true) if (!x || x.length === 0) { @@ -361,11 +373,11 @@ class IndexedFormula extends Formula { for (var i = 0;i < statList.length;i++) { var st = statList[i] switch (st.object.termType) { - case 'symbol': + case 'NamedNode': this.add(target, st.predicate, st.object) break - case 'literal': - case 'bnode': + case 'Literal': + case 'BlankNode': case 'collection': this.add(target, st.predicate, st.object.copy(this)) } diff --git a/src/jsonparser.js b/src/jsonparser.js index 783adfd16..9e28a9174 100644 --- a/src/jsonparser.js +++ b/src/jsonparser.js @@ -24,7 +24,7 @@ var jsonParser = (function () { if (obj.type === 'uri') { object = store.sym(obj.value) store.add(subject, predicate, object, why) - } else if (obj.type === 'bnode') { + } else if (obj.type === 'BlankNode') { if (bnodes[obj.value]) { object = bnodes[obj.value] } else { @@ -32,7 +32,7 @@ var jsonParser = (function () { bnodes[obj.value] = object } store.add(subject, predicate, object, why) - } else if (obj.type === 'literal') { + } else if (obj.type === 'Literal') { // var datatype if (obj.datatype) { object = store.literal(obj.value, undefined, store.sym(obj.datatype)) diff --git a/src/literal.js b/src/literal.js index ce7673635..4931aa87b 100644 --- a/src/literal.js +++ b/src/literal.js @@ -1,5 +1,6 @@ 'use strict' const ClassOrder = require('./class-order') +const NamedNode = require('./named-node') const Node = require('./node') const XSD = require('./xsd') @@ -8,9 +9,14 @@ class Literal extends Node { super() this.termType = Literal.termType this.value = value - this.lang = language // property currently used by rdflib - this.language = language // rdfjs property - this.datatype = datatype + if (language) { + this.lang = language + datatype = XSD.langString + } + // If not specified, a literal has the implied XSD.string default datatype + if (datatype) { + this.datatype = NamedNode.fromValue(datatype) + } } copy () { return new Literal(this.value, this.lang, this.datatype) @@ -25,6 +31,12 @@ class Literal extends Node { ((!this.datatype && !other.datatype) || (this.datatype && this.datatype.equals(other.datatype))) } + get language () { + return this.lang + } + set language (language) { + this.lang = language || '' + } toNT () { if (typeof this.value === 'number') { return this.toString() @@ -37,11 +49,12 @@ class Literal extends Node { str = str.replace(/\"/g, '\\"') str = str.replace(/\n/g, '\\n') str = '"' + str + '"' - if (this.datatype) { - str += '^^' + this.datatype.toNT() - } + if (this.language) { str += '@' + this.language + } else if (!this.datatype.equals(XSD.string)) { + // Only add datatype if it's not a string + str += '^^' + this.datatype.toCanonical() } return str } @@ -56,7 +69,7 @@ class Literal extends Node { */ static fromBoolean (value) { let strValue = value ? '1' : '0' - return new Literal(strValue, void 0, XSD.boolean) + return new Literal(strValue, null, XSD.boolean) } /** * @method fromDate @@ -74,7 +87,7 @@ class Literal extends Node { let date = '' + value.getUTCFullYear() + '-' + d2(value.getUTCMonth() + 1) + '-' + d2(value.getUTCDate()) + 'T' + d2(value.getUTCHours()) + ':' + d2(value.getUTCMinutes()) + ':' + d2(value.getUTCSeconds()) + 'Z' - return new Literal(date, void 0, XSD.dateTime) + return new Literal(date, null, XSD.dateTime) } /** * @method fromNumber @@ -94,7 +107,7 @@ class Literal extends Node { } else { datatype = XSD.integer } - return new Literal('' + value, void 0, datatype) + return new Literal('' + value, null, datatype) } /** * @method fromValue @@ -102,10 +115,10 @@ class Literal extends Node { * @return {Literal} */ static fromValue (value) { - if (value instanceof Node) { + if (typeof value === 'undefined' || value === null) { return value } - if (typeof value === 'undefined' || value === null) { + if (value && value.termType) { // this is a Node instance return value } switch (typeof value) { @@ -125,8 +138,10 @@ class Literal extends Node { } } -Literal.termType = 'literal' +Literal.termType = 'Literal' Literal.prototype.classOrder = ClassOrder['Literal'] +Literal.prototype.datatype = XSD.string +Literal.prototype.lang = '' Literal.prototype.isVar = 0 module.exports = Literal diff --git a/src/named-node.js b/src/named-node.js index 3ca4ddaa7..cb8f51383 100644 --- a/src/named-node.js +++ b/src/named-node.js @@ -14,7 +14,6 @@ class NamedNode extends Node { constructor (iri) { super() this.termType = NamedNode.termType - this.uri = iri this.value = iri } /** @@ -37,8 +36,28 @@ class NamedNode extends Node { toString () { return '<' + this.uri + '>' } + + /** + * Legacy getter and setter alias, node.uri + */ + get uri () { + return this.value + } + set uri (uri) { + this.value = uri + } + static fromValue (value) { + if (typeof value === 'undefined' || value === null) { + return value + } + const isNode = value && value.termType + if (isNode) { + return value + } + return new NamedNode(value) + } } -NamedNode.termType = 'symbol' +NamedNode.termType = 'NamedNode' NamedNode.prototype.classOrder = ClassOrder['NamedNode'] NamedNode.prototype.isVar = 0 diff --git a/src/node.js b/src/node.js index 93df9fd3e..4431c30b9 100644 --- a/src/node.js +++ b/src/node.js @@ -60,10 +60,11 @@ Node.fromValue = function fromValue (value) { const Collection = require('./collection') const Literal = require('./literal') const NamedNode = require('./named-node') - if (value instanceof Node || value instanceof Collection) { + if (typeof value === 'undefined' || value === null) { return value } - if (typeof value === 'undefined' || value === null) { + const isNode = value && value.termType + if (isNode) { // a Node subclass or a Collection return value } if (Array.isArray(value)) { diff --git a/src/serializer.js b/src/serializer.js index 8e7adeb1e..5de774882 100644 --- a/src/serializer.js +++ b/src/serializer.js @@ -10,6 +10,7 @@ const NamedNode = require('./named-node') const Uri = require('./uri') const Util = require('./util') +const XSD = require('./xsd') var Serializer = function() { var __Serializer = function( store ){ @@ -149,7 +150,7 @@ __Serializer.prototype.rootSubjects = function(sts) { for (var i = 0; i', subjectXMLTree(st.object, stats), @@ -856,16 +857,18 @@ __Serializer.prototype.statementsToXML = function(sts) { +st.object.toNT().slice(2)+'"/>']); } break; - case 'symbol': + case 'NamedNode': results = results.concat(['<'+ t +' rdf:resource="' + relURI(st.object)+'"/>']); break; - case 'literal': - results = results.concat(['<'+ t - + (st.object.datatype ? ' rdf:datatype="'+escapeForXML(st.object.datatype.uri)+'"' : '') - + (st.object.lang ? ' xml:lang="'+st.object.lang+'"' : '') - + '>' + escapeForXML(st.object.value) - + '']); + case 'Literal': + results = results.concat(['<'+ t + + (st.object.datatype.equals(XSD.string) + ? '' + : ' rdf:datatype="'+escapeForXML(st.object.datatype.uri)+'"') + + (st.object.language ? ' xml:lang="'+st.object.language+'"' : '') + + '>' + escapeForXML(st.object.value) + + '']); break; case 'collection': results = results.concat(['<'+ t +' rdf:parseType="Collection">', @@ -880,7 +883,7 @@ __Serializer.prototype.statementsToXML = function(sts) { var tag = type ? qname(type) : 'rdf:Description'; var attrs = ''; - if (subject.termType == 'bnode') { + if (subject.termType == 'BlankNode') { if(!stats.incoming[subject] || stats.incoming[subject].length != 1) { // not an anonymous bnode attrs = ' rdf:nodeID="'+subject.toNT().slice(2)+'"'; } @@ -910,7 +913,7 @@ __Serializer.prototype.statementsToXML = function(sts) { for (var i=0; i', '']); @@ -920,14 +923,14 @@ __Serializer.prototype.statementsToXML = function(sts) { '']); } break; - case 'symbol': + case 'NamedNode': results = results.concat(['<'+qname(st.predicate)+' rdf:resource="' + relURI(st.object)+'"/>']); break; - case 'literal': + case 'Literal': results = results.concat(['<'+qname(st.predicate) - + (st.object.datatype ? ' rdf:datatype="'+escapeForXML(st.object.datatype.uri)+'"' : '') - + (st.object.lang ? ' xml:lang="'+st.object.lang+'"' : '') + + (st.object.datatype.equals(XSD.string) ? '' : ' rdf:datatype="'+escapeForXML(st.object.datatype.value)+'"') + + (st.object.language ? ' xml:lang="'+st.object.language+'"' : '') + '>' + escapeForXML(st.object.value) + '']); break; diff --git a/src/sparql-to-query.js b/src/sparql-to-query.js index 6c02cc0bd..6522d4bbf 100644 --- a/src/sparql-to-query.js +++ b/src/sparql-to-query.js @@ -361,8 +361,8 @@ function SPARQLToQuery (SPARQL, testMode, kb) { } function setConstraint (input, pat) { - if (input.length === 3 && input[0].termType === 'variable' && - (input[2].termType === 'symbol' || input[2].termType === 'literal')) { + if (input.length === 3 && input[0].termType === 'Variable' && + (input[2].termType === 'NamedNode' || input[2].termType === 'Literal')) { if (input[1] === '=') { log.debug('Constraint added: ' + input) pat.constraints[input[0]] = new ConstraintEqualTo(input[2]) @@ -378,7 +378,7 @@ function SPARQLToQuery (SPARQL, testMode, kb) { } else if (input.length === 6 && typeof input[0] === 'string' && input[0].toLowerCase() === 'regexp' && input[1] === '(' && input[5] === ')' && input[3] === ',' && - input[4].termType === 'variable' && input[2].termType === 'literal') { + input[4].termType === 'Variable' && input[2].termType === 'Literal') { log.debug('Constraint added: ' + input) pat.constraints[input[4]] = new ConstraintRegexp(input[2].value) } @@ -501,14 +501,14 @@ function SPARQLToQuery (SPARQL, testMode, kb) { for (var x in q.pat.statements) { var st = q.pat.statements[x] - if (st.subject.termType === 'symbol') { + if (st.subject.termType === 'NamedNode') { /* && sf.isPending(st.subject.uri) */ // This doesn't work. // sf.requestURI(st.subject.uri,"sparql:"+st.subject) Kenny: I remove these two if (fetcher) { fetcher.lookUpThing(st.subject, 'sparql:' + st.subject) } } - if (st.object.termType === 'symbol') { + if (st.object.termType === 'NamedNode') { /* && sf.isPending(st.object.uri) */ // sf.requestURI(st.object.uri,"sparql:"+st.object) if (fetcher) { diff --git a/src/statement.js b/src/statement.js index e6d82832d..e8d8556c9 100644 --- a/src/statement.js +++ b/src/statement.js @@ -26,7 +26,15 @@ class Statement { this.why) } toCanonical () { - return this.toNT() + let terms = [ + this.subject.toCanonical(), + this.predicate.toCanonical(), + this.object.toCanonical() + ] + if (this.graph && this.graph.termType !== 'DefaultGraph') { + terms.push(this.graph.toCanonical()) + } + return terms.join(' ') + ' .' } toNT () { return [this.subject.toNT(), this.predicate.toNT(), diff --git a/src/util.js b/src/util.js index 1ca5484be..f90ceb4a5 100644 --- a/src/util.js +++ b/src/util.js @@ -37,7 +37,7 @@ function ajarHandleNewTerm (kb, p, requestedBy) { } else { return } - if (p.termType !== 'symbol') return + if (p.termType !== 'NamedNode') return var docuri = docpart(p.uri) var fixuri if (p.uri.indexOf('#') < 0) { // No hash @@ -290,7 +290,7 @@ function getHTTPHeaders (xhr) { */ function heavyCompare (x, y, g) { var nonBlank = function (x) { - return (x.termType === 'bnode') ? null : x + return (x.termType === 'BlankNode') ? null : x } var signature = function (b) { var lis = g.statementsMatching(x).map(function (st) { @@ -303,7 +303,7 @@ function heavyCompare (x, y, g) { lis.sort() return lis.join('\n') } - if ((x.termType === 'bnode') || (y.termType === 'bnode')) { + if ((x.termType === 'BlankNode') || (y.termType === 'BlankNode')) { if (x.compareTerm(y) === 0) return 0 // Same if (signature(x) > signature(y)) return +1 if (signature(x) < signature(y)) return -1 diff --git a/src/variable.js b/src/variable.js index 6ba3d2a37..e1b74fda8 100644 --- a/src/variable.js +++ b/src/variable.js @@ -4,25 +4,26 @@ const Node = require('./node') const Uri = require('./uri') /** - * @class Variable * Variables are placeholders used in patterns to be matched. * In cwm they are symbols which are the formula's list of quantified variables. - * In sparl they are not visibily URIs. Here we compromise, by having + * In sparql they are not visibly URIs. Here we compromise, by having * a common special base URI for variables. Their names are uris, - * but the ? nottaion has an implicit base uri of 'varid:' + * but the ? notation has an implicit base uri of 'varid:' + * @class Variable */ class Variable extends Node { - constructor (rel) { + constructor (name = '') { super() this.termType = Variable.termType + this.value = name this.base = 'varid:' - this.uri = Uri.join(rel, this.base) + this.uri = Uri.join(name, this.base) } equals (other) { if (!other) { return false } - return (this.termType === other.termType) && (this.uri === other.uri) + return (this.termType === other.termType) && (this.value === other.value) } hashString () { return this.toString() @@ -39,7 +40,7 @@ class Variable extends Node { } } -Variable.termType = 'variable' +Variable.termType = 'Variable' Variable.prototype.classOrder = ClassOrder['Variable'] Variable.prototype.isVar = 1 diff --git a/src/xsd.js b/src/xsd.js index dec6c486a..f278194fa 100644 --- a/src/xsd.js +++ b/src/xsd.js @@ -7,5 +7,8 @@ XSD.dateTime = new NamedNode('http://www.w3.org/2001/XMLSchema#dateTime') XSD.decimal = new NamedNode('http://www.w3.org/2001/XMLSchema#decimal') XSD.float = new NamedNode('http://www.w3.org/2001/XMLSchema#float') XSD.integer = new NamedNode('http://www.w3.org/2001/XMLSchema#integer') +XSD.langString = + new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString') +XSD.string = new NamedNode('http://www.w3.org/2001/XMLSchema#string') module.exports = XSD diff --git a/tests/unit/match-test.js b/tests/unit/match-test.js index 2437d8bfb..2c33fbce4 100644 --- a/tests/unit/match-test.js +++ b/tests/unit/match-test.js @@ -31,7 +31,7 @@ test('empty .match()', t => { test('match on S', t => { let kb = rdf.graph() kb.addAll([ triple1, triple2, triple3, triple4 ]) - let s = 'https://example.com/subject1' + let s = rdf.namedNode('https://example.com/subject1') let matches = kb.match(s) t.equals(matches.length, 2, 'match(subject) should return 2 triples') matches.sort() @@ -56,9 +56,9 @@ test('match on SO', t => { let kb = rdf.graph() kb.addAll([ triple1, triple2, triple3, triple4 ]) let matches = kb.match( - 'https://example.com/subject1', + rdf.namedNode('https://example.com/subject1'), null, - 'https://example.com/object1' + rdf.namedNode('https://example.com/object1') ) t.equals(matches.length, 1, 'match(s, null, o) should return 1 triple') t.equals(matches[0].subject, s1)