UNPKG

n3

Version:

Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.

1,199 lines (1,105 loc) 46.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.N3EntityIndex = void 0; var _readableStream = require("readable-stream"); var _N3DataFactory = _interopRequireWildcard(require("./N3DataFactory")); var _IRIs = _interopRequireDefault(require("./IRIs")); var _N3Util = require("./N3Util"); var _N3Writer = _interopRequireDefault(require("./N3Writer")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } // **N3Store** objects store N3 quads by graph in memory. const ITERATOR = Symbol('iter'); function merge(target, source, depth = 4) { if (depth === 0) return Object.assign(target, source); for (const key in source) target[key] = merge(target[key] || Object.create(null), source[key], depth - 1); return target; } /** * Determines the intersection of the `_graphs` index s1 and s2. * s1 and s2 *must* belong to Stores that share an `_entityIndex`. * * False is returned when there is no intersection; this should * *not* be set as the value for an index. */ function intersect(s1, s2, depth = 4) { let target = false; for (const key in s1) { if (key in s2) { const intersection = depth === 0 ? null : intersect(s1[key], s2[key], depth - 1); if (intersection !== false) { target = target || Object.create(null); target[key] = intersection; } // Depth 3 is the 'subjects', 'predicates' and 'objects' keys. // If the 'subjects' index is empty, so will the 'predicates' and 'objects' index. else if (depth === 3) { return false; } } } return target; } /** * Determines the difference of the `_graphs` index s1 and s2. * s1 and s2 *must* belong to Stores that share an `_entityIndex`. * * False is returned when there is no difference; this should * *not* be set as the value for an index. */ function difference(s1, s2, depth = 4) { let target = false; for (const key in s1) { // When the key is not in the index, then none of the triples defined by s1[key] are // in s2 and so we want to copy them over to the resultant store. if (!(key in s2)) { target = target || Object.create(null); target[key] = depth === 0 ? null : merge({}, s1[key], depth - 1); } else if (depth !== 0) { const diff = difference(s1[key], s2[key], depth - 1); if (diff !== false) { target = target || Object.create(null); target[key] = diff; } // Depth 3 is the 'subjects', 'predicates' and 'objects' keys. // If the 'subjects' index is empty, so will the 'predicates' and 'objects' index. else if (depth === 3) { return false; } } } return target; } // ## Constructor class N3EntityIndex { constructor(options = {}) { this._id = 1; // `_ids` maps entities such as `http://xmlns.com/foaf/0.1/name` to numbers, // saving memory by using only numbers as keys in `_graphs` this._ids = Object.create(null); this._ids[''] = 1; // inverse of `_ids` this._entities = Object.create(null); this._entities[1] = ''; // `_blankNodeIndex` is the index of the last automatically named blank node this._blankNodeIndex = 0; this._factory = options.factory || _N3DataFactory.default; } _termFromId(id) { if (id[0] === '.') { const entities = this._entities; const terms = id.split('.'); const q = this._factory.quad(this._termFromId(entities[terms[1]]), this._termFromId(entities[terms[2]]), this._termFromId(entities[terms[3]]), terms[4] && this._termFromId(entities[terms[4]])); return q; } return (0, _N3DataFactory.termFromId)(id, this._factory); } _termToNumericId(term) { if (term.termType === 'Quad') { const s = this._termToNumericId(term.subject), p = this._termToNumericId(term.predicate), o = this._termToNumericId(term.object); let g; return s && p && o && ((0, _N3Util.isDefaultGraph)(term.graph) || (g = this._termToNumericId(term.graph))) && this._ids[g ? `.${s}.${p}.${o}.${g}` : `.${s}.${p}.${o}`]; } return this._ids[(0, _N3DataFactory.termToId)(term)]; } _termToNewNumericId(term) { // This assumes that no graph term is present - we may wish to error if there is one const str = term && term.termType === 'Quad' ? `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${(0, _N3Util.isDefaultGraph)(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}`}` : (0, _N3DataFactory.termToId)(term); return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id); } createBlankNode(suggestedName) { let name, index; // Generate a name based on the suggested name if (suggestedName) { name = suggestedName = `_:${suggestedName}`, index = 1; while (this._ids[name]) name = suggestedName + index++; } // Generate a generic blank node name else { do { name = `_:b${this._blankNodeIndex++}`; } while (this._ids[name]); } // Add the blank node to the entities, avoiding the generation of duplicates this._ids[name] = ++this._id; this._entities[this._id] = name; return this._factory.blankNode(name.substr(2)); } } // ## Constructor exports.N3EntityIndex = N3EntityIndex; class N3Store { constructor(quads, options) { // The number of quads is initially zero this._size = 0; // `_graphs` contains subject, predicate, and object indexes per graph this._graphs = Object.create(null); // Shift parameters if `quads` is not given if (!options && quads && !quads[0] && !(typeof quads.match === 'function')) options = quads, quads = null; options = options || {}; this._factory = options.factory || _N3DataFactory.default; this._entityIndex = options.entityIndex || new N3EntityIndex({ factory: this._factory }); this._entities = this._entityIndex._entities; this._termFromId = this._entityIndex._termFromId.bind(this._entityIndex); this._termToNumericId = this._entityIndex._termToNumericId.bind(this._entityIndex); this._termToNewNumericId = this._entityIndex._termToNewNumericId.bind(this._entityIndex); // Add quads if passed if (quads) this.addAll(quads); } // ## Public properties // ### `size` returns the number of quads in the store get size() { // Return the quad count if if was cached let size = this._size; if (size !== null) return size; // Calculate the number of quads by counting to the deepest level size = 0; const graphs = this._graphs; let subjects, subject; for (const graphKey in graphs) for (const subjectKey in subjects = graphs[graphKey].subjects) for (const predicateKey in subject = subjects[subjectKey]) size += Object.keys(subject[predicateKey]).length; return this._size = size; } // ## Private methods // ### `_addToIndex` adds a quad to a three-layered index. // Returns if the index has changed, if the entry did not already exist. _addToIndex(index0, key0, key1, key2) { // Create layers as necessary const index1 = index0[key0] || (index0[key0] = {}); const index2 = index1[key1] || (index1[key1] = {}); // Setting the key to _any_ value signals the presence of the quad const existed = key2 in index2; if (!existed) index2[key2] = null; return !existed; } // ### `_removeFromIndex` removes a quad from a three-layered index _removeFromIndex(index0, key0, key1, key2) { // Remove the quad from the index const index1 = index0[key0], index2 = index1[key1]; delete index2[key2]; // Remove intermediary index layers if they are empty for (const key in index2) return; delete index1[key1]; for (const key in index1) return; delete index0[key0]; } // ### `_findInIndex` finds a set of quads in a three-layered index. // The index base is `index0` and the keys at each level are `key0`, `key1`, and `key2`. // Any of these keys can be undefined, which is interpreted as a wildcard. // `name0`, `name1`, and `name2` are the names of the keys at each level, // used when reconstructing the resulting quad // (for instance: _subject_, _predicate_, and _object_). // Finally, `graphId` will be the graph of the created quads. *_findInIndex(index0, key0, key1, key2, name0, name1, name2, graphId) { let tmp, index1, index2; const entityKeys = this._entities; const graph = this._termFromId(entityKeys[graphId]); const parts = { subject: null, predicate: null, object: null }; // If a key is specified, use only that part of index 0. if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0]; for (const value0 in index0) { if (index1 = index0[value0]) { parts[name0] = this._termFromId(entityKeys[value0]); // If a key is specified, use only that part of index 1. if (key1) (tmp = index1, index1 = {})[key1] = tmp[key1]; for (const value1 in index1) { if (index2 = index1[value1]) { parts[name1] = this._termFromId(entityKeys[value1]); // If a key is specified, use only that part of index 2, if it exists. const values = key2 ? key2 in index2 ? [key2] : [] : Object.keys(index2); // Create quads for all items found in index 2. for (let l = 0; l < values.length; l++) { parts[name2] = this._termFromId(entityKeys[values[l]]); yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph); } } } } } } // ### `_loop` executes the callback on all keys of index 0 _loop(index0, callback) { for (const key0 in index0) callback(key0); } // ### `_loopByKey0` executes the callback on all keys of a certain entry in index 0 _loopByKey0(index0, key0, callback) { let index1, key1; if (index1 = index0[key0]) { for (key1 in index1) callback(key1); } } // ### `_loopByKey1` executes the callback on given keys of all entries in index 0 _loopByKey1(index0, key1, callback) { let key0, index1; for (key0 in index0) { index1 = index0[key0]; if (index1[key1]) callback(key0); } } // ### `_loopBy2Keys` executes the callback on given keys of certain entries in index 2 _loopBy2Keys(index0, key0, key1, callback) { let index1, index2, key2; if ((index1 = index0[key0]) && (index2 = index1[key1])) { for (key2 in index2) callback(key2); } } // ### `_countInIndex` counts matching quads in a three-layered index. // The index base is `index0` and the keys at each level are `key0`, `key1`, and `key2`. // Any of these keys can be undefined, which is interpreted as a wildcard. _countInIndex(index0, key0, key1, key2) { let count = 0, tmp, index1, index2; // If a key is specified, count only that part of index 0 if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0]; for (const value0 in index0) { if (index1 = index0[value0]) { // If a key is specified, count only that part of index 1 if (key1) (tmp = index1, index1 = {})[key1] = tmp[key1]; for (const value1 in index1) { if (index2 = index1[value1]) { // If a key is specified, count the quad if it exists if (key2) key2 in index2 && count++; // Otherwise, count all quads else count += Object.keys(index2).length; } } } } return count; } // ### `_getGraphs` returns an array with the given graph, // or all graphs if the argument is null or undefined. _getGraphs(graph) { graph = graph === '' ? 1 : graph && (this._termToNumericId(graph) || -1); return typeof graph !== 'number' ? this._graphs : { [graph]: this._graphs[graph] }; } // ### `_uniqueEntities` returns a function that accepts an entity ID // and passes the corresponding entity to callback if it hasn't occurred before. _uniqueEntities(callback) { const uniqueIds = Object.create(null); return id => { if (!(id in uniqueIds)) { uniqueIds[id] = true; callback(this._termFromId(this._entities[id], this._factory)); } }; } // ## Public methods // ### `add` adds the specified quad to the dataset. // Returns the dataset instance it was called on. // Existing quads, as defined in Quad.equals, will be ignored. add(quad) { this.addQuad(quad); return this; } // ### `addQuad` adds a new quad to the store. // Returns if the quad index has changed, if the quad did not already exist. addQuad(subject, predicate, object, graph) { // Shift arguments if a quad object is given instead of components if (!predicate) graph = subject.graph, object = subject.object, predicate = subject.predicate, subject = subject.subject; // Convert terms to internal string representation graph = graph ? this._termToNewNumericId(graph) : 1; // Find the graph that will contain the triple let graphItem = this._graphs[graph]; // Create the graph if it doesn't exist yet if (!graphItem) { graphItem = this._graphs[graph] = { subjects: {}, predicates: {}, objects: {} }; // Freezing a graph helps subsequent `add` performance, // and properties will never be modified anyway Object.freeze(graphItem); } // Since entities can often be long IRIs, we avoid storing them in every index. // Instead, we have a separate index that maps entities to numbers, // which are then used as keys in the other indexes. subject = this._termToNewNumericId(subject); predicate = this._termToNewNumericId(predicate); object = this._termToNewNumericId(object); if (!this._addToIndex(graphItem.subjects, subject, predicate, object)) return false; this._addToIndex(graphItem.predicates, predicate, object, subject); this._addToIndex(graphItem.objects, object, subject, predicate); // The cached quad count is now invalid this._size = null; return true; } // ### `addQuads` adds multiple quads to the store addQuads(quads) { for (let i = 0; i < quads.length; i++) this.addQuad(quads[i]); } // ### `delete` removes the specified quad from the dataset. // Returns the dataset instance it was called on. delete(quad) { this.removeQuad(quad); return this; } // ### `has` determines whether a dataset includes a certain quad or quad pattern. has(subjectOrQuad, predicate, object, graph) { if (subjectOrQuad && subjectOrQuad.subject) ({ subject: subjectOrQuad, predicate, object, graph } = subjectOrQuad); return !this.readQuads(subjectOrQuad, predicate, object, graph).next().done; } // ### `import` adds a stream of quads to the store import(stream) { stream.on('data', quad => { this.addQuad(quad); }); return stream; } // ### `removeQuad` removes a quad from the store if it exists removeQuad(subject, predicate, object, graph) { // Shift arguments if a quad object is given instead of components if (!predicate) ({ subject, predicate, object, graph } = subject); // Convert terms to internal string representation graph = graph ? this._termToNumericId(graph) : 1; // Find internal identifiers for all components // and verify the quad exists. const graphs = this._graphs; let graphItem, subjects, predicates; if (!(subject = subject && this._termToNumericId(subject)) || !(predicate = predicate && this._termToNumericId(predicate)) || !(object = object && this._termToNumericId(object)) || !(graphItem = graphs[graph]) || !(subjects = graphItem.subjects[subject]) || !(predicates = subjects[predicate]) || !(object in predicates)) return false; // Remove it from all indexes this._removeFromIndex(graphItem.subjects, subject, predicate, object); this._removeFromIndex(graphItem.predicates, predicate, object, subject); this._removeFromIndex(graphItem.objects, object, subject, predicate); if (this._size !== null) this._size--; // Remove the graph if it is empty for (subject in graphItem.subjects) return true; delete graphs[graph]; return true; } // ### `removeQuads` removes multiple quads from the store removeQuads(quads) { for (let i = 0; i < quads.length; i++) this.removeQuad(quads[i]); } // ### `remove` removes a stream of quads from the store remove(stream) { stream.on('data', quad => { this.removeQuad(quad); }); return stream; } // ### `removeMatches` removes all matching quads from the store // Setting any field to `undefined` or `null` indicates a wildcard. removeMatches(subject, predicate, object, graph) { const stream = new _readableStream.Readable({ objectMode: true }); const iterable = this.readQuads(subject, predicate, object, graph); stream._read = size => { while (--size >= 0) { const { done, value } = iterable.next(); if (done) { stream.push(null); return; } stream.push(value); } }; return this.remove(stream); } // ### `deleteGraph` removes all triples with the given graph from the store deleteGraph(graph) { return this.removeMatches(null, null, null, graph); } // ### `getQuads` returns an array of quads matching a pattern. // Setting any field to `undefined` or `null` indicates a wildcard. getQuads(subject, predicate, object, graph) { return [...this.readQuads(subject, predicate, object, graph)]; } /** * `readQuads` returns a generator of quads matching a pattern. * Setting any field to `undefined` or `null` indicates a wildcard. * @deprecated Use `match` instead. */ *readQuads(subject, predicate, object, graph) { const graphs = this._getGraphs(graph); let content, subjectId, predicateId, objectId; // Translate IRIs to internal index keys. if (subject && !(subjectId = this._termToNumericId(subject)) || predicate && !(predicateId = this._termToNumericId(predicate)) || object && !(objectId = this._termToNumericId(object))) return; for (const graphId in graphs) { // Only if the specified graph contains triples, there can be results if (content = graphs[graphId]) { // Choose the optimal index, based on what fields are present if (subjectId) { if (objectId) // If subject and object are given, the object index will be the fastest yield* this._findInIndex(content.objects, objectId, subjectId, predicateId, 'object', 'subject', 'predicate', graphId);else // If only subject and possibly predicate are given, the subject index will be the fastest yield* this._findInIndex(content.subjects, subjectId, predicateId, null, 'subject', 'predicate', 'object', graphId); } else if (predicateId) // If only predicate and possibly object are given, the predicate index will be the fastest yield* this._findInIndex(content.predicates, predicateId, objectId, null, 'predicate', 'object', 'subject', graphId);else if (objectId) // If only object is given, the object index will be the fastest yield* this._findInIndex(content.objects, objectId, null, null, 'object', 'subject', 'predicate', graphId);else // If nothing is given, iterate subjects and predicates first yield* this._findInIndex(content.subjects, null, null, null, 'subject', 'predicate', 'object', graphId); } } } // ### `match` returns a new dataset that is comprised of all quads in the current instance matching the given arguments. // The logic described in Quad Matching is applied for each quad in this dataset to check if it should be included in the output dataset. // Note: This method always returns a new DatasetCore, even if that dataset contains no quads. // Note: Since a DatasetCore is an unordered set, the order of the quads within the returned sequence is arbitrary. // Setting any field to `undefined` or `null` indicates a wildcard. // For backwards compatibility, the object return also implements the Readable stream interface. match(subject, predicate, object, graph) { return new DatasetCoreAndReadableStream(this, subject, predicate, object, graph, { entityIndex: this._entityIndex }); } // ### `countQuads` returns the number of quads matching a pattern. // Setting any field to `undefined` or `null` indicates a wildcard. countQuads(subject, predicate, object, graph) { const graphs = this._getGraphs(graph); let count = 0, content, subjectId, predicateId, objectId; // Translate IRIs to internal index keys. if (subject && !(subjectId = this._termToNumericId(subject)) || predicate && !(predicateId = this._termToNumericId(predicate)) || object && !(objectId = this._termToNumericId(object))) return 0; for (const graphId in graphs) { // Only if the specified graph contains triples, there can be results if (content = graphs[graphId]) { // Choose the optimal index, based on what fields are present if (subject) { if (object) // If subject and object are given, the object index will be the fastest count += this._countInIndex(content.objects, objectId, subjectId, predicateId);else // If only subject and possibly predicate are given, the subject index will be the fastest count += this._countInIndex(content.subjects, subjectId, predicateId, objectId); } else if (predicate) { // If only predicate and possibly object are given, the predicate index will be the fastest count += this._countInIndex(content.predicates, predicateId, objectId, subjectId); } else { // If only object is possibly given, the object index will be the fastest count += this._countInIndex(content.objects, objectId, subjectId, predicateId); } } } return count; } // ### `forEach` executes the callback on all quads. // Setting any field to `undefined` or `null` indicates a wildcard. forEach(callback, subject, predicate, object, graph) { this.some(quad => { callback(quad, this); return false; }, subject, predicate, object, graph); } // ### `every` executes the callback on all quads, // and returns `true` if it returns truthy for all them. // Setting any field to `undefined` or `null` indicates a wildcard. every(callback, subject, predicate, object, graph) { return !this.some(quad => !callback(quad, this), subject, predicate, object, graph); } // ### `some` executes the callback on all quads, // and returns `true` if it returns truthy for any of them. // Setting any field to `undefined` or `null` indicates a wildcard. some(callback, subject, predicate, object, graph) { for (const quad of this.readQuads(subject, predicate, object, graph)) if (callback(quad, this)) return true; return false; } // ### `getSubjects` returns all subjects that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. getSubjects(predicate, object, graph) { const results = []; this.forSubjects(s => { results.push(s); }, predicate, object, graph); return results; } // ### `forSubjects` executes the callback on all subjects that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. forSubjects(callback, predicate, object, graph) { const graphs = this._getGraphs(graph); let content, predicateId, objectId; callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. if (predicate && !(predicateId = this._termToNumericId(predicate)) || object && !(objectId = this._termToNumericId(object))) return; for (graph in graphs) { // Only if the specified graph contains triples, there can be results if (content = graphs[graph]) { // Choose optimal index based on which fields are wildcards if (predicateId) { if (objectId) // If predicate and object are given, the POS index is best. this._loopBy2Keys(content.predicates, predicateId, objectId, callback);else // If only predicate is given, the SPO index is best. this._loopByKey1(content.subjects, predicateId, callback); } else if (objectId) // If only object is given, the OSP index is best. this._loopByKey0(content.objects, objectId, callback);else // If no params given, iterate all the subjects this._loop(content.subjects, callback); } } } // ### `getPredicates` returns all predicates that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. getPredicates(subject, object, graph) { const results = []; this.forPredicates(p => { results.push(p); }, subject, object, graph); return results; } // ### `forPredicates` executes the callback on all predicates that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. forPredicates(callback, subject, object, graph) { const graphs = this._getGraphs(graph); let content, subjectId, objectId; callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. if (subject && !(subjectId = this._termToNumericId(subject)) || object && !(objectId = this._termToNumericId(object))) return; for (graph in graphs) { // Only if the specified graph contains triples, there can be results if (content = graphs[graph]) { // Choose optimal index based on which fields are wildcards if (subjectId) { if (objectId) // If subject and object are given, the OSP index is best. this._loopBy2Keys(content.objects, objectId, subjectId, callback);else // If only subject is given, the SPO index is best. this._loopByKey0(content.subjects, subjectId, callback); } else if (objectId) // If only object is given, the POS index is best. this._loopByKey1(content.predicates, objectId, callback);else // If no params given, iterate all the predicates. this._loop(content.predicates, callback); } } } // ### `getObjects` returns all objects that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. getObjects(subject, predicate, graph) { const results = []; this.forObjects(o => { results.push(o); }, subject, predicate, graph); return results; } // ### `forObjects` executes the callback on all objects that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. forObjects(callback, subject, predicate, graph) { const graphs = this._getGraphs(graph); let content, subjectId, predicateId; callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. if (subject && !(subjectId = this._termToNumericId(subject)) || predicate && !(predicateId = this._termToNumericId(predicate))) return; for (graph in graphs) { // Only if the specified graph contains triples, there can be results if (content = graphs[graph]) { // Choose optimal index based on which fields are wildcards if (subjectId) { if (predicateId) // If subject and predicate are given, the SPO index is best. this._loopBy2Keys(content.subjects, subjectId, predicateId, callback);else // If only subject is given, the OSP index is best. this._loopByKey1(content.objects, subjectId, callback); } else if (predicateId) // If only predicate is given, the POS index is best. this._loopByKey0(content.predicates, predicateId, callback);else // If no params given, iterate all the objects. this._loop(content.objects, callback); } } } // ### `getGraphs` returns all graphs that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. getGraphs(subject, predicate, object) { const results = []; this.forGraphs(g => { results.push(g); }, subject, predicate, object); return results; } // ### `forGraphs` executes the callback on all graphs that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. forGraphs(callback, subject, predicate, object) { for (const graph in this._graphs) { this.some(quad => { callback(quad.graph); return true; // Halt iteration of some() }, subject, predicate, object, this._termFromId(this._entities[graph])); } } // ### `createBlankNode` creates a new blank node, returning its name createBlankNode(suggestedName) { return this._entityIndex.createBlankNode(suggestedName); } // ### `extractLists` finds and removes all list triples // and returns the items per list. extractLists({ remove = false, ignoreErrors = false } = {}) { const lists = {}; // has scalar keys so could be a simple Object const onError = ignoreErrors ? () => true : (node, message) => { throw new Error(`${node.value} ${message}`); }; // Traverse each list from its tail const tails = this.getQuads(null, _IRIs.default.rdf.rest, _IRIs.default.rdf.nil, null); const toRemove = remove ? [...tails] : []; tails.forEach(tailQuad => { const items = []; // the members found as objects of rdf:first quads let malformed = false; // signals whether the current list is malformed let head; // the head of the list (_:b1 in above example) let headPos; // set to subject or object when head is set const graph = tailQuad.graph; // make sure list is in exactly one graph // Traverse the list from tail to end let current = tailQuad.subject; while (current && !malformed) { const objectQuads = this.getQuads(null, null, current, null); const subjectQuads = this.getQuads(current, null, null, null); let quad, first = null, rest = null, parent = null; // Find the first and rest of this list node for (let i = 0; i < subjectQuads.length && !malformed; i++) { quad = subjectQuads[i]; if (!quad.graph.equals(graph)) malformed = onError(current, 'not confined to single graph');else if (head) malformed = onError(current, 'has non-list arcs out'); // one rdf:first else if (quad.predicate.value === _IRIs.default.rdf.first) { if (first) malformed = onError(current, 'has multiple rdf:first arcs');else toRemove.push(first = quad); } // one rdf:rest else if (quad.predicate.value === _IRIs.default.rdf.rest) { if (rest) malformed = onError(current, 'has multiple rdf:rest arcs');else toRemove.push(rest = quad); } // alien triple else if (objectQuads.length) malformed = onError(current, 'can\'t be subject and object');else { head = quad; // e.g. { (1 2 3) :p :o } headPos = 'subject'; } } // { :s :p (1 2) } arrives here with no head // { (1 2) :p :o } arrives here with head set to the list. for (let i = 0; i < objectQuads.length && !malformed; ++i) { quad = objectQuads[i]; if (head) malformed = onError(current, 'can\'t have coreferences'); // one rdf:rest else if (quad.predicate.value === _IRIs.default.rdf.rest) { if (parent) malformed = onError(current, 'has incoming rdf:rest arcs');else parent = quad; } else { head = quad; // e.g. { :s :p (1 2) } headPos = 'object'; } } // Store the list item and continue with parent if (!first) malformed = onError(current, 'has no list head');else items.unshift(first.object); current = parent && parent.subject; } // Don't remove any quads if the list is malformed if (malformed) remove = false; // Store the list under the value of its head else if (head) lists[head[headPos].value] = items; }); // Remove list quads if requested if (remove) this.removeQuads(toRemove); return lists; } /** * Returns `true` if the current dataset is a superset of the given dataset; in other words, returns `true` if * the given dataset is a subset of, i.e., is contained within, the current dataset. * * Blank Nodes will be normalized. */ addAll(quads) { if (quads instanceof DatasetCoreAndReadableStream) quads = quads.filtered; if (Array.isArray(quads)) this.addQuads(quads);else if (quads instanceof N3Store && quads._entityIndex === this._entityIndex) { if (quads._size !== 0) { this._graphs = merge(this._graphs, quads._graphs); this._size = null; // Invalidate the cached size } } else { for (const quad of quads) this.add(quad); } return this; } /** * Returns `true` if the current dataset is a superset of the given dataset; in other words, returns `true` if * the given dataset is a subset of, i.e., is contained within, the current dataset. * * Blank Nodes will be normalized. */ contains(other) { if (other instanceof DatasetCoreAndReadableStream) other = other.filtered; if (other === this) return true; if (!(other instanceof N3Store) || this._entityIndex !== other._entityIndex) return other.every(quad => this.has(quad)); const g1 = this._graphs, g2 = other._graphs; let s1, s2, p1, p2, o1; for (const graph in g2) { if (!(s1 = g1[graph])) return false; s1 = s1.subjects; for (const subject in s2 = g2[graph].subjects) { if (!(p1 = s1[subject])) return false; for (const predicate in p2 = s2[subject]) { if (!(o1 = p1[predicate])) return false; for (const object in p2[predicate]) if (!(object in o1)) return false; } } } return true; } /** * This method removes the quads in the current dataset that match the given arguments. * * The logic described in {@link https://rdf.js.org/dataset-spec/#quad-matching|Quad Matching} is applied for each * quad in this dataset, to select the quads which will be deleted. * * @param subject The optional exact subject to match. * @param predicate The optional exact predicate to match. * @param object The optional exact object to match. * @param graph The optional exact graph to match. */ deleteMatches(subject, predicate, object, graph) { for (const quad of this.match(subject, predicate, object, graph)) this.removeQuad(quad); return this; } /** * Returns a new dataset that contains all quads from the current dataset that are not included in the given dataset. */ difference(other) { if (other && other instanceof DatasetCoreAndReadableStream) other = other.filtered; if (other === this) return new N3Store({ entityIndex: this._entityIndex }); if (other instanceof N3Store && other._entityIndex === this._entityIndex) { const store = new N3Store({ entityIndex: this._entityIndex }); const graphs = difference(this._graphs, other._graphs); if (graphs) { store._graphs = graphs; store._size = null; } return store; } return this.filter(quad => !other.has(quad)); } /** * Returns true if the current dataset contains the same graph structure as the given dataset. * * Blank Nodes will be normalized. */ equals(other) { if (other instanceof DatasetCoreAndReadableStream) other = other.filtered; return other === this || this.size === other.size && this.contains(other); } /** * Creates a new dataset with all the quads that pass the test implemented by the provided `iteratee`. * * This method is aligned with Array.prototype.filter() in ECMAScript-262. */ filter(iteratee) { const store = new N3Store({ entityIndex: this._entityIndex }); for (const quad of this) if (iteratee(quad, this)) store.add(quad); return store; } /** * Returns a new dataset containing all quads from the current dataset that are also included in the given dataset. */ intersection(other) { if (other instanceof DatasetCoreAndReadableStream) other = other.filtered; if (other === this) { const store = new N3Store({ entityIndex: this._entityIndex }); store._graphs = merge(Object.create(null), this._graphs); store._size = this._size; return store; } else if (other instanceof N3Store && this._entityIndex === other._entityIndex) { const store = new N3Store({ entityIndex: this._entityIndex }); const graphs = intersect(other._graphs, this._graphs); if (graphs) { store._graphs = graphs; store._size = null; } return store; } return this.filter(quad => other.has(quad)); } /** * Returns a new dataset containing all quads returned by applying `iteratee` to each quad in the current dataset. */ map(iteratee) { const store = new N3Store({ entityIndex: this._entityIndex }); for (const quad of this) store.add(iteratee(quad, this)); return store; } /** * This method calls the `iteratee` method on each `quad` of the `Dataset`. The first time the `iteratee` method * is called, the `accumulator` value is the `initialValue`, or, if not given, equals the first quad of the `Dataset`. * The return value of each call to the `iteratee` method is used as the `accumulator` value for the next call. * * This method returns the return value of the last `iteratee` call. * * This method is aligned with `Array.prototype.reduce()` in ECMAScript-262. */ reduce(callback, initialValue) { const iter = this.readQuads(); let accumulator = initialValue === undefined ? iter.next().value : initialValue; for (const quad of iter) accumulator = callback(accumulator, quad, this); return accumulator; } /** * Returns the set of quads within the dataset as a host-language-native sequence, for example an `Array` in * ECMAScript-262. * * Since a `Dataset` is an unordered set, the order of the quads within the returned sequence is arbitrary. */ toArray() { return this.getQuads(); } /** * Returns an N-Quads string representation of the dataset, preprocessed with the * {@link https://json-ld.github.io/normalization/spec/|RDF Dataset Normalization} algorithm. */ toCanonical() { throw new Error('not implemented'); } /** * Returns a stream that contains all quads of the dataset. */ toStream() { return this.match(); } /** * Returns an N-Quads string representation of the dataset. * * No prior normalization is required, therefore the results for the same quads may vary depending on the `Dataset` * implementation. */ toString() { return new _N3Writer.default().quadsToString(this); } /** * Returns a new `Dataset` that is a concatenation of this dataset and the quads given as an argument. */ union(quads) { const store = new N3Store({ entityIndex: this._entityIndex }); store._graphs = merge(Object.create(null), this._graphs); store._size = this._size; store.addAll(quads); return store; } // ### Store is an iterable. // Can be used where iterables are expected: for...of loops, array spread operator, // `yield*`, and destructuring assignment (order is not guaranteed). *[Symbol.iterator]() { yield* this.readQuads(); } } /** * Returns a subset of the `index` with that part of the index * matching the `ids` array. `ids` contains 3 elements that are * either numerical ids; or `null`. * * `false` is returned when there are no matching indices; this should * *not* be set as the value for an index. */ exports.default = N3Store; function indexMatch(index, ids, depth = 0) { const ind = ids[depth]; if (ind && !(ind in index)) return false; let target = false; for (const key in ind ? { [ind]: index[ind] } : index) { const result = depth === 2 ? null : indexMatch(index[key], ids, depth + 1); if (result !== false) { target = target || Object.create(null); target[key] = result; } } return target; } /** * A class that implements both DatasetCore and Readable. */ class DatasetCoreAndReadableStream extends _readableStream.Readable { constructor(n3Store, subject, predicate, object, graph, options) { super({ objectMode: true }); Object.assign(this, { n3Store, subject, predicate, object, graph, options }); } get filtered() { if (!this._filtered) { const { n3Store, graph, object, predicate, subject } = this; const newStore = this._filtered = new N3Store({ factory: n3Store._factory, entityIndex: this.options.entityIndex }); let subjectId, predicateId, objectId; // Translate IRIs to internal index keys. if (subject && !(subjectId = newStore._termToNumericId(subject)) || predicate && !(predicateId = newStore._termToNumericId(predicate)) || object && !(objectId = newStore._termToNumericId(object))) return newStore; const graphs = n3Store._getGraphs(graph); for (const graphKey in graphs) { let subjects, predicates, objects, content; if (content = graphs[graphKey]) { if (!subjectId && predicateId) { if (predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId])) { subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId]); objects = indexMatch(content.objects, [objectId, subjectId, predicateId]); } } else if (objectId) { if (objects = indexMatch(content.objects, [objectId, subjectId, predicateId])) { subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId]); predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId]); } } else if (subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId])) { predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId]); objects = indexMatch(content.objects, [objectId, subjectId, predicateId]); } if (subjects) newStore._graphs[graphKey] = { subjects, predicates, objects }; } } newStore._size = null; } return this._filtered; } get size() { return this.filtered.size; } _read(size) { if (size > 0 && !this[ITERATOR]) this[ITERATOR] = this[Symbol.iterator](); const iterable = this[ITERATOR]; while (--size >= 0) { const { done, value } = iterable.next(); if (done) { this.push(null); return; } this.push(value); } } addAll(quads) { return this.filtered.addAll(quads); } contains(other) { return this.filtered.contains(other); } deleteMatches(subject, predicate, object, graph) { return this.filtered.deleteMatches(subject, predicate, object, graph); } difference(other) { return this.filtered.difference(other); } equals(other) { return this.filtered.equals(other); } every(callback, subject, predicate, object, graph) { return this.filtered.every(callback, subject, predicate, object, graph); } filter(iteratee) { return this.filtered.filter(iteratee); } forEach(callback, subject, predicate, object, graph) { return this.filtered.forEach(callback, subject, predicate, object, graph); } import(stream) { return this.filtered.import(stream); } intersection(other) { return this.filtered.intersection(other); } map(iteratee) { return this.filtered.map(iteratee); } some(callback, subject, predicate, object, graph) { return this.filtered.some(callback, subject, predicate, object, graph); } toCanonical() { return this.filtered.toCanonical(); } toStream() { return this._filtered ? this._filtered.toStream() : this.n3Store.match(this.subject, this.predicate, this.object, this.graph); } union(quads) { return this._filtered ? this._filtered.union(quads) : this.n3Store.match(this.subject, this.predicate, this.object, this.graph).addAll(quads); } toArray() { return this._filtered ? this._filtered.toArray() : this.n3Store.getQuads(this.subject, this.predicate, this.object, this.graph); } reduce(callback, initialValue) { return this.filtered.reduce(callback, initialValue); } toString() { return new _N3Writer.default().quadsToString(this); } add(quad) { return this.filtered.add(quad); } delete(quad) { return this.filtered.delete(quad); } has(quad) { return this.filtered.has(quad); } match(subject, predicate, object, graph) { return new DatasetCoreAndReadableStream(this.filtered, subject, predicate, object, graph, this.options); } *[Symbol.iterator]() { yield* this._filtered || this.n3Store.readQuads(this.subject, this.predicate, this.object, this.graph); } }