From 33bacf95ca8310058afd44aaf63f447f9dbe3d40 Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Wed, 2 Oct 2019 10:17:34 +0200 Subject: [PATCH] Let formula, store/indexedFormula implement a proper datafactory The current implementation contains some methods which are like the spec DataFactory interface but have small distinctions. E.g. `namedNode` is called `sym`, `literal` accepts three rather than two arguments. This commit applies the canonical methods of the rdfjs tf DataFactory interface to the store. End-users can also overwrite the used factory via a constructor argument. --- src/data-factory-internal.js | 57 ++++++++++++++++++++++++++++++++ src/data-factory.js | 64 +++++++++++++----------------------- src/formula.js | 24 ++++++++------ src/literal.js | 2 +- src/n3parser.js | 12 +++---- src/rdfxmlparser.js | 2 +- src/serializer.js | 16 +++++---- src/sparql-to-query.js | 3 +- src/store.js | 13 +++++--- src/xsd-internal.js | 11 +++++++ src/xsd.js | 31 ++++++++++------- 11 files changed, 151 insertions(+), 84 deletions(-) create mode 100644 src/data-factory-internal.js create mode 100644 src/xsd-internal.js diff --git a/src/data-factory-internal.js b/src/data-factory-internal.js new file mode 100644 index 000000000..f846d498a --- /dev/null +++ b/src/data-factory-internal.js @@ -0,0 +1,57 @@ +import BlankNode from './blank-node' +import DefaultGraph from './default-graph' +import Literal from './literal' +import NamedNode from './named-node' +import Statement from './statement' +import Variable from './variable' + +function blankNode (value) { + return new BlankNode(value) +} + +function defaultGraph () { + return new DefaultGraph() +} + +function literal (value, languageOrDatatype) { + if (typeof value !== "string" && !languageOrDatatype) { + return Literal.fromValue(value) + } + + const strValue = typeof value === 'string' ? value : '' + value + if (typeof languageOrDatatype === 'string') { + if (languageOrDatatype.indexOf(':') === -1) { + return new Literal(strValue, languageOrDatatype) + } else { + return new Literal(strValue, null, namedNode(languageOrDatatype)) + } + } else { + return new Literal(strValue, null, languageOrDatatype) + } +} +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 variable (name) { + return new Variable(name) +} + +/** Only contains the factory methods as defined in the spec */ +export default { + blankNode, + defaultGraph, + literal, + namedNode, + quad, + variable, + supports: { + COLLECTIONS: false, + DEFAULT_GRAPH_TYPE: true, + EQUALS_METHOD: true, + VARIABLE_TYPE: true, + } +} diff --git a/src/data-factory.js b/src/data-factory.js index 74e1c04ef..4ce9b0a75 100644 --- a/src/data-factory.js +++ b/src/data-factory.js @@ -1,71 +1,53 @@ 'use strict' -import BlankNode from './blank-node' import Collection from './collection' -import DefaultGraph from './default-graph' +import CanonicalDataFactory from './data-factory-internal' import Fetcher from './fetcher' -import IndexedFormula from './store' import Literal from './literal' -import NamedNode from './named-node' import Statement from './statement' -import Variable from './variable' +import IndexedFormula from './store' +/** + * Data factory which also supports Collections + * + * Necessary for preventing circular dependencies. + */ +const ExtendedTermFactory = { + ...CanonicalDataFactory, + collection, + supports: { + COLLECTIONS: true, + DEFAULT_GRAPH_TYPE: true, + EQUALS_METHOD: true, + VARIABLE_TYPE: true, + } +} + +/** Full RDFLib.js Data Factory */ const DataFactory = { - blankNode, - defaultGraph, + ...ExtendedTermFactory, fetcher, graph, lit, - literal, - namedNode, - quad, st, triple, - variable, } export default DataFactory -function blankNode (value) { - return new BlankNode(value) -} function collection (elements) { return new Collection(elements) } -function defaultGraph () { - return new DefaultGraph() -} function fetcher (store, options) { return new Fetcher(store, options) } -function graph () { - return new IndexedFormula() +function graph (features = undefined, opts = undefined) { + return new IndexedFormula(features, opts || { rdfFactory: ExtendedTermFactory }) } function lit (val, lang, dt) { return new Literal('' + val, lang, dt) } -function literal (value, languageOrDatatype) { - if (typeof languageOrDatatype === 'string') { - if (languageOrDatatype.indexOf(':') === -1) { - return new Literal(value, languageOrDatatype) - } else { - return new Literal(value, null, namedNode(languageOrDatatype)) - } - } else { - return new Literal(value, null, languageOrDatatype) - } -} -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) { return new Statement(subject, predicate, object, graph) } function triple (subject, predicate, object) { - return quad(subject, predicate, object) -} -function variable (name) { - return new Variable(name) + return CanonicalDataFactory.quad(subject, predicate, object) } diff --git a/src/formula.js b/src/formula.js index b5c0e32fb..ea2e99244 100644 --- a/src/formula.js +++ b/src/formula.js @@ -2,7 +2,7 @@ import BlankNode from './blank-node' import ClassOrder from './class-order' import Collection from './collection' -import Literal from './literal' +import CanonicalDataFactory from './data-factory-internal' import log from './log' import NamedNode from './named-node' import Namespace from './namespace' @@ -20,14 +20,21 @@ export default class Formula extends Node { * @param constraints - initial array of constraints * @param initBindings - initial bindings used in Query * @param optional - optional + * @param opts + * @param {DataFactory} opts.rdfFactory - The rdf factory that should be used by the store */ - constructor (statements, constraints, initBindings, optional) { + constructor (statements, constraints, initBindings, optional, opts = {}) { super() this.termType = Formula.termType this.statements = statements || [] this.constraints = constraints || [] this.initBindings = initBindings || [] this.optional = optional || [] + + this.rdfFactory = (opts && opts.rdfFactory) || CanonicalDataFactory + for(const factoryMethod of Object.keys(this.rdfFactory)) { + this[factoryMethod] = this.rdfFactory[factoryMethod] + } } /** Add a statement from its parts * @param {Node} subject - the first part of the statemnt @@ -36,7 +43,7 @@ export default class Formula extends Node { * @param {Node} graph - the last part of the statemnt */ add (subject, predicate, object, graph) { - return this.statements.push(new Statement(subject, predicate, object, graph)) + return this.statements.push(this.rdfFactory.quad(subject, predicate, object, graph)) } /** Add a statment object * @param {Statement} statement - an existing constructed statement to add @@ -45,7 +52,7 @@ export default class Formula extends Node { return this.statements.push(st) } bnode (id) { - return new BlankNode(id) + return this.rdfFactory.blankNode(id) } addAll (statements) { @@ -443,9 +450,9 @@ export default class Formula extends Node { str = str.replace(/\\"/g, '"') str = str.replace(/\\n/g, '\n') str = str.replace(/\\\\/g, '\\') - return this.literal(str, lang, dt) + return this.literal(str, lang || dt) case '_': - return new BlankNode(str.slice(2)) + return this.rdfFactory.blankNode(str.slice(2)) case '?': return new Variable(str.slice(1)) } @@ -485,9 +492,6 @@ export default class Formula extends Node { }) return collection } - literal (val, lang, dt) { - return new Literal('' + val, lang, dt) - } /** * transform a collection of NTriple URIs into their URI strings * @param t some iterable colletion of NTriple URI strings @@ -547,7 +551,7 @@ export default class Formula extends Node { if (name) { throw new Error('This feature (kb.sym with 2 args) is removed. Do not assume prefix mappings.') } - return new NamedNode(uri) + return this.rdfFactory.namedNode(uri) } the (s, p, o, g) { var x = this.any(s, p, o, g) diff --git a/src/literal.js b/src/literal.js index c1e89f027..bc3cd2028 100644 --- a/src/literal.js +++ b/src/literal.js @@ -2,7 +2,7 @@ import ClassOrder from './class-order' import NamedNode from './named-node' import Node from './node-internal' -import XSD from './xsd' +import XSD from './xsd-internal' export default class Literal extends Node { constructor (value, language, datatype) { diff --git a/src/n3parser.js b/src/n3parser.js index ecc68a093..56152a9a6 100644 --- a/src/n3parser.js +++ b/src/n3parser.js @@ -1316,9 +1316,9 @@ __SinkParser.prototype.nodeOrLiteral = function(str, i, res) { var val = m[0]; j = i + val.length; if ((val.indexOf("T") >= 0)) { - res.push(this._store.literal(val, undefined, this._store.sym(DATETIME_DATATYPE))); + res.push(this._store.literal(val, this._store.sym(DATETIME_DATATYPE))); } else { - res.push(this._store.literal(val, undefined, this._store.sym(DATE_DATATYPE))); + res.push(this._store.literal(val, this._store.sym(DATE_DATATYPE))); } } else { @@ -1330,13 +1330,13 @@ __SinkParser.prototype.nodeOrLiteral = function(str, i, res) { j = ( i + number_syntax.lastIndex ) ; var val = str.slice( i, j); if ((val.indexOf("e") >= 0)) { - res.push(this._store.literal(parseFloat(val), undefined, this._store.sym(FLOAT_DATATYPE))); + res.push(this._store.literal(parseFloat(val), this._store.sym(FLOAT_DATATYPE))); } else if ((str.slice( i, j).indexOf(".") >= 0)) { - res.push(this._store.literal(parseFloat(val), undefined, this._store.sym(DECIMAL_DATATYPE))); + res.push(this._store.literal(parseFloat(val), this._store.sym(DECIMAL_DATATYPE))); } else { - res.push(this._store.literal(parseInt(val), undefined, this._store.sym(INTEGER_DATATYPE))); + res.push(this._store.literal(parseInt(val), this._store.sym(INTEGER_DATATYPE))); } }; return j; // Where we have got up to @@ -1371,7 +1371,7 @@ __SinkParser.prototype.nodeOrLiteral = function(str, i, res) { var j = this.uri_ref2(str, ( j + 2 ) , res2); var dt = res2[0]; } - res.push(this._store.literal(s, lang, dt)); + res.push(this._store.literal(s, lang || dt)); return j; } else { diff --git a/src/rdfxmlparser.js b/src/rdfxmlparser.js index ed04932df..73bba6867 100644 --- a/src/rdfxmlparser.js +++ b/src/rdfxmlparser.js @@ -154,7 +154,7 @@ export default function RDFParser(store) { this.addSymbol(this.ARC, uri) }, /** Add a literal to this frame */'addLiteral': function (value) { if (this.parent.datatype) { - this.node = this.store.literal(value, '', this.store.sym(this.parent.datatype)) + this.node = this.store.literal(value, this.store.sym(this.parent.datatype)) } else { this.node = this.store.literal(value, this.lang) } diff --git a/src/serializer.js b/src/serializer.js index 1c579b2df..4983e2c25 100644 --- a/src/serializer.js +++ b/src/serializer.js @@ -9,7 +9,8 @@ import NamedNode from './named-node' import BlankNode from './blank-node' import * as Uri from './uri' import * as Util from './util' -import XSD from './xsd' +import CanonicalDataFactory from './data-factory-internal' +import { createXSD } from './xsd' export default (function () { var __Serializer = function (store) { @@ -28,6 +29,8 @@ export default (function () { this.incoming = null // Array not calculated yet this.formulas = [] // remebering original formulae from hashes this.store = store + this.rdfFactory = store.rdfFactory || CanonicalDataFactory + this.xsd = createXSD(this.rdfFactory) } __Serializer.prototype.setBase = function (base) { this.base = base; return this } @@ -209,6 +212,7 @@ export default (function () { var rdfns = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' var self = this var kb = this.store + var factory = this.rdfFactory var termToNT = function (x) { if (x.termType !== 'Collection') { return self.atomicTermToN3(x) @@ -216,7 +220,7 @@ export default (function () { var list = x.elements var rest = kb.sym(rdfns + 'nill') for (var i = list.length - 1; i >= 0; i--) { - var bnode = new BlankNode() + var bnode = factory.blankNode() str += termToNT(bnode) + ' ' + termToNT(kb.sym(rdfns + 'first')) + ' ' + termToNT(list[i]) + '.\n' str += termToNT(bnode) + ' ' + termToNT(kb.sym(rdfns + 'rest')) + ' ' + termToNT(rest) + '.\n' rest = bnode @@ -480,7 +484,7 @@ export default (function () { var str = this.stringToN3(expr.value) if (expr.language) { str += '@' + expr.language - } else if (!expr.datatype.equals(XSD.string)) { + } else if (!expr.datatype.equals(this.xsd.string)) { str += '^^' + this.atomicTermToN3(expr.datatype, stats) } return str @@ -804,7 +808,7 @@ export default (function () { if (number === intNumber.toString()) { // was numeric; don't need to worry about ordering since we've already // sorted the statements - pred = new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#li') + pred = this.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#li') } } @@ -826,7 +830,7 @@ export default (function () { break case 'Literal': results = results.concat(['<' + t + - (st.object.datatype.equals(XSD.string) + (st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"') + (st.object.language ? ' xml:lang="' + st.object.language + '"' : '') + @@ -892,7 +896,7 @@ export default (function () { break case 'Literal': results = results.concat(['<' + qname(st.predicate) + - (st.object.datatype.equals(XSD.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.value) + '"') + + (st.object.datatype.equals(this.xsd.string) ? '' : ' rdf:datatype="' + escapeForXML(st.object.datatype.value) + '"') + (st.object.language ? ' xml:lang="' + st.object.language + '"' : '') + '>' + escapeForXML(st.object.value) + '']) diff --git a/src/sparql-to-query.js b/src/sparql-to-query.js index 632f0762d..0ef9a8218 100644 --- a/src/sparql-to-query.js +++ b/src/sparql-to-query.js @@ -110,7 +110,6 @@ export default function SPARQLToQuery (SPARQL, testMode, kb) { // alert(end2) res[1] = kb.literal( str.slice(ind + 1, ind + 1 + end), - '', kb.sym(removeBrackets( str.slice(ind + 4 + end, ind + 2 + end + end2)) ) @@ -129,7 +128,7 @@ export default function SPARQLToQuery (SPARQL, testMode, kb) { parseLiterals(str.slice(end + ind + 2 + end2)) ) } else { - res[1] = kb.literal(str.slice(ind + 1, ind + 1 + end), '', null) + res[1] = kb.literal(str.slice(ind + 1, ind + 1 + end)) log.info('Literal found: ' + res[1]) res = res.concat(parseLiterals(str.slice(end + ind + 2))) // finds any other literals } diff --git a/src/store.js b/src/store.js index 7c1ec52b5..09db8b7d6 100644 --- a/src/store.js +++ b/src/store.js @@ -74,9 +74,11 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @constructor * @param {Array} features - What sort of autmatic processing to do? Array of string * @param {Boolean} features.sameAs - Smush together A and B nodes whenever { A sameAs B } + * @param opts + * @param {CanonicalDataFactory} opts.rdfFactory - The data factory that should be used by the store */ - constructor (features) { - super() + constructor (features, opts = {}) { + super(undefined, undefined, undefined, undefined, opts) this.propertyActions = [] // Array of functions to call when getting statement with {s X o} // maps to [f(F,s,p,o),...] @@ -98,8 +100,9 @@ export default class IndexedFormula extends Formula { // IN future - allow pass this.features = features || [ 'sameAs', 'InverseFunctionalProperty', - 'FunctionalProperty' + 'FunctionalProperty', ] + this.initPropertyActions(this.features) } @@ -282,12 +285,12 @@ export default class IndexedFormula extends Formula { // IN future - allow pass return null // @@better to return self in all cases? } // If we are tracking provenance, every thing should be loaded into the store - // if (done) return new Statement(subj, pred, obj, why) + // if (done) return this.rdfFactory.quad(subj, pred, obj, why) // Don't put it in the store // still return this statement for owl:sameAs input var hash = [ this.canon(subj).hashString(), predHash, this.canon(obj).hashString(), this.canon(why).hashString()] - st = new Statement(subj, pred, obj, why) + st = this.rdfFactory.quad(subj, pred, obj, why) for (i = 0; i < 4; i++) { var ix = this.index[i] var h = hash[i] diff --git a/src/xsd-internal.js b/src/xsd-internal.js new file mode 100644 index 000000000..4ff6a2989 --- /dev/null +++ b/src/xsd-internal.js @@ -0,0 +1,11 @@ +import NamedNode from './named-node'; + +export default { + boolean: new NamedNode('http://www.w3.org/2001/XMLSchema#boolean'), + dateTime: new NamedNode('http://www.w3.org/2001/XMLSchema#dateTime'), + decimal: new NamedNode('http://www.w3.org/2001/XMLSchema#decimal'), + double: new NamedNode('http://www.w3.org/2001/XMLSchema#double'), + integer: new NamedNode('http://www.w3.org/2001/XMLSchema#integer'), + langString: new NamedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString'), + string: new NamedNode('http://www.w3.org/2001/XMLSchema#string'), +} diff --git a/src/xsd.js b/src/xsd.js index 07da88afc..8e852ca22 100644 --- a/src/xsd.js +++ b/src/xsd.js @@ -1,12 +1,19 @@ -import NamedNode from './named-node' - -export default class XSD {} - -XSD.boolean = new NamedNode('http://www.w3.org/2001/XMLSchema#boolean') -XSD.dateTime = new NamedNode('http://www.w3.org/2001/XMLSchema#dateTime') -XSD.decimal = new NamedNode('http://www.w3.org/2001/XMLSchema#decimal') -XSD.double = new NamedNode('http://www.w3.org/2001/XMLSchema#double') -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') +import CanonicalDataFactory from './data-factory-internal' + +export function createXSD(localFactory = CanonicalDataFactory) { + class XSD {} + + XSD.boolean = localFactory.namedNode('http://www.w3.org/2001/XMLSchema#boolean') + XSD.dateTime = localFactory.namedNode('http://www.w3.org/2001/XMLSchema#dateTime') + XSD.decimal = localFactory.namedNode('http://www.w3.org/2001/XMLSchema#decimal') + XSD.double = localFactory.namedNode('http://www.w3.org/2001/XMLSchema#double') + XSD.integer = localFactory.namedNode('http://www.w3.org/2001/XMLSchema#integer') + XSD.langString = localFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString') + XSD.string = localFactory.namedNode('http://www.w3.org/2001/XMLSchema#string') + + return XSD +} + +const defaultXSD = createXSD(CanonicalDataFactory) + +export default defaultXSD