From 383f94ea667089e9599410e74a5d6488ee7f80ba Mon Sep 17 00:00:00 2001 From: Ieben Smessaert Date: Sun, 24 Nov 2024 12:48:25 +0100 Subject: [PATCH] fix: dont accidentally re-bind custom data factories (#467) * fix: Correctly handle custom data factories by binding functions * chore: only bind on non-native factories * chore: use direct calls rather than binding * chore: fix comment placement --------- Co-authored-by: Jesse Wright <63333554+jeswr@users.noreply.github.com> --- package-lock.json | 12 +++--- src/N3Parser.js | 89 ++++++++++++++++++++----------------------- test/N3Parser-test.js | 45 +++++++++++++++++++++- 3 files changed, 92 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b264873..7cd75c11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11100,9 +11100,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -19930,9 +19930,9 @@ "dev": true }, "typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "peer": true }, diff --git a/src/N3Parser.js b/src/N3Parser.js index fc4af341..33919405 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -159,7 +159,7 @@ export default class N3Parser { const iri = this._resolveIRI(token.value); if (iri === null) return this._error('Invalid IRI', token); - value = this._namedNode(iri); + value = this._factory.namedNode(iri); break; // Read a prefixed name case 'type': @@ -167,15 +167,15 @@ export default class N3Parser { const prefix = this._prefixes[token.prefix]; if (prefix === undefined) return this._error(`Undefined prefix "${token.prefix}:"`, token); - value = this._namedNode(prefix + token.value); + value = this._factory.namedNode(prefix + token.value); break; // Read a blank node case 'blank': - value = this._blankNode(this._prefixes[token.prefix] + token.value); + value = this._factory.blankNode(this._prefixes[token.prefix] + token.value); break; // Read a variable case 'var': - value = this._variable(token.value.substr(1)); + value = this._factory.variable(token.value.substr(1)); break; // Everything else is not an entity default: @@ -194,7 +194,7 @@ export default class N3Parser { case '[': // Start a new quad with a new blank node as subject this._saveContext('blank', this._graph, - this._subject = this._blankNode(), null, null); + this._subject = this._factory.blankNode(), null, null); return this._readBlankNodeHead; case '(': // Start a new list @@ -206,7 +206,7 @@ export default class N3Parser { if (!this._n3Mode) return this._error('Unexpected graph', token); this._saveContext('formula', this._graph, - this._graph = this._blankNode(), null, null); + this._graph = this._factory.blankNode(), null, null); return this._readSubject; case '}': // No subject; the graph in which we are reading is closed instead @@ -216,14 +216,14 @@ export default class N3Parser { return this._error('Unexpected "@forSome"', token); this._subject = null; this._predicate = this.N3_FORSOME; - this._quantifier = this._blankNode; + this._quantifier = 'blankNode'; return this._readQuantifierList; case '@forAll': if (!this._n3Mode) return this._error('Unexpected "@forAll"', token); this._subject = null; this._predicate = this.N3_FORALL; - this._quantifier = this._variable; + this._quantifier = 'variable'; return this._readQuantifierList; case 'literal': if (!this._n3Mode) @@ -234,7 +234,7 @@ export default class N3Parser { return this._completeSubjectLiteral; } else - this._subject = this._literal(token.value, this._namedNode(token.prefix)); + this._subject = this._factory.literal(token.value, this._factory.namedNode(token.prefix)); break; case '<<': @@ -282,7 +282,7 @@ export default class N3Parser { if (this._n3Mode) { // Start a new quad with a new blank node as subject this._saveContext('blank', this._graph, this._subject, - this._subject = this._blankNode(), null); + this._subject = this._factory.blankNode(), null); return this._readBlankNodeHead; } case 'blank': @@ -307,12 +307,12 @@ export default class N3Parser { } // Pre-datatyped string literal (prefix stores the datatype) else - this._object = this._literal(token.value, this._namedNode(token.prefix)); + this._object = this._factory.literal(token.value, this._factory.namedNode(token.prefix)); break; case '[': // Start a new quad with a new blank node as subject this._saveContext('blank', this._graph, this._subject, this._predicate, - this._subject = this._blankNode()); + this._subject = this._factory.blankNode()); return this._readBlankNodeHead; case '(': // Start a new list @@ -324,7 +324,7 @@ export default class N3Parser { if (!this._n3Mode) return this._error('Unexpected graph', token); this._saveContext('formula', this._graph, this._subject, this._predicate, - this._graph = this._blankNode()); + this._graph = this._factory.blankNode()); return this._readSubject; case '<<': if (!this._supportsRDFStar) @@ -419,14 +419,14 @@ export default class N3Parser { case '[': // Stack the current list quad and start a new quad with a blank node as subject this._saveContext('blank', this._graph, - list = this._blankNode(), this.RDF_FIRST, - this._subject = item = this._blankNode()); + list = this._factory.blankNode(), this.RDF_FIRST, + this._subject = item = this._factory.blankNode()); next = this._readBlankNodeHead; break; case '(': // Stack the current list quad and start a new list this._saveContext('list', this._graph, - list = this._blankNode(), this.RDF_FIRST, this.RDF_NIL); + list = this._factory.blankNode(), this.RDF_FIRST, this.RDF_NIL); this._subject = null; break; case ')': @@ -462,7 +462,7 @@ export default class N3Parser { } // Pre-datatyped string literal (prefix stores the datatype) else { - item = this._literal(token.value, this._namedNode(token.prefix)); + item = this._factory.literal(token.value, this._factory.namedNode(token.prefix)); next = this._getContextEndReader(); } break; @@ -471,7 +471,7 @@ export default class N3Parser { if (!this._n3Mode) return this._error('Unexpected graph', token); this._saveContext('formula', this._graph, this._subject, this._predicate, - this._graph = this._blankNode()); + this._graph = this._factory.blankNode()); return this._readSubject; default: if ((item = this._readEntity(token)) === undefined) @@ -480,7 +480,7 @@ export default class N3Parser { // Create a new blank node if no item head was assigned yet if (list === null) - this._subject = list = this._blankNode(); + this._subject = list = this._factory.blankNode(); // Is this the first element of the list? if (previousList === null) { @@ -524,7 +524,7 @@ export default class N3Parser { // ### `_completeLiteral` completes a literal with an optional datatype or language _completeLiteral(token) { // Create a simple string literal by default - let literal = this._literal(this._literalValue); + let literal = this._factory.literal(this._literalValue); switch (token.type) { // Create a datatyped literal @@ -532,12 +532,12 @@ export default class N3Parser { case 'typeIRI': const datatype = this._readEntity(token); if (datatype === undefined) return; // No datatype means an error occurred - literal = this._literal(this._literalValue, datatype); + literal = this._factory.literal(this._literalValue, datatype); token = null; break; // Create a language-tagged string case 'langcode': - literal = this._literal(this._literalValue, token.value); + literal = this._factory.literal(this._literalValue, token.value); token = null; break; } @@ -620,7 +620,7 @@ export default class N3Parser { return this._error('Unexpected RDF-star syntax', token); // Continue using the last triple as quoted triple subject for the predicate-object pairs. const predicate = this._predicate, object = this._object; - this._subject = this._quad(subject, predicate, object, this.DEFAULTGRAPH); + this._subject = this._factory.quad(subject, predicate, object, this.DEFAULTGRAPH); next = this._readPredicate; break; // |} means that the current quoted triple in annotation syntax is finalized. @@ -721,7 +721,7 @@ export default class N3Parser { _readNamedGraphBlankLabel(token) { if (token.type !== ']') return this._error('Invalid graph label', token); - this._subject = this._blankNode(); + this._subject = this._factory.blankNode(); return this._readGraph; } @@ -751,17 +751,17 @@ export default class N3Parser { } // Without explicit quantifiers, map entities to a quantified entity if (!this._explicitQuantifiers) - this._quantified[entity.id] = this._quantifier(this._blankNode().value); + this._quantified[entity.id] = this._factory[this._quantifier](this._factory.blankNode().value); // With explicit quantifiers, output the reified quantifier else { // If this is the first item, start a new quantifier list if (this._subject === null) this._emit(this._graph || this.DEFAULTGRAPH, this._predicate, - this._subject = this._blankNode(), this.QUANTIFIERS_GRAPH); + this._subject = this._factory.blankNode(), this.QUANTIFIERS_GRAPH); // Otherwise, continue the previous list else this._emit(this._subject, this.RDF_REST, - this._subject = this._blankNode(), this.QUANTIFIERS_GRAPH); + this._subject = this._factory.blankNode(), this.QUANTIFIERS_GRAPH); // Output the list item this._emit(this._subject, this.RDF_FIRST, entity, this.QUANTIFIERS_GRAPH); } @@ -818,7 +818,7 @@ export default class N3Parser { // ### `_readForwardPath` reads a '!' path _readForwardPath(token) { let subject, predicate; - const object = this._blankNode(); + const object = this._factory.blankNode(); // The next token is the predicate if ((predicate = this._readEntity(token)) === undefined) return; @@ -835,7 +835,7 @@ export default class N3Parser { // ### `_readBackwardPath` reads a '^' path _readBackwardPath(token) { - const subject = this._blankNode(); + const subject = this._factory.blankNode(); let predicate, object; // The next token is the predicate if ((predicate = this._readEntity(token)) === undefined) @@ -867,7 +867,7 @@ export default class N3Parser { if (token.type !== '>>') return this._error(`Expected >> but got ${token.type}`, token); // Read the quad and restore the previous context - const quad = this._quad(this._subject, this._predicate, this._object, + const quad = this._factory.quad(this._subject, this._predicate, this._object, this._graph || this.DEFAULTGRAPH); this._restoreContext('<<', token); // If the triple was the subject, continue by reading the predicate. @@ -902,7 +902,7 @@ export default class N3Parser { // ### `_emit` sends a quad through the callback _emit(subject, predicate, object, graph) { - this._callback(null, this._quad(subject, predicate, object, graph || this.DEFAULTGRAPH)); + this._callback(null, this._factory.quad(subject, predicate, object, graph || this.DEFAULTGRAPH)); } // ### `_error` emits an error message through the callback @@ -1082,26 +1082,21 @@ function noop() {} // Initializes the parser with the given data factory function initDataFactory(parser, factory) { - // Set factory methods - const namedNode = factory.namedNode; - parser._namedNode = namedNode; - parser._blankNode = factory.blankNode; - parser._literal = factory.literal; - parser._variable = factory.variable; - parser._quad = factory.quad; + parser._factory = factory; + parser.DEFAULTGRAPH = factory.defaultGraph(); // Set common named nodes - parser.RDF_FIRST = namedNode(namespaces.rdf.first); - parser.RDF_REST = namedNode(namespaces.rdf.rest); - parser.RDF_NIL = namedNode(namespaces.rdf.nil); - parser.N3_FORALL = namedNode(namespaces.r.forAll); - parser.N3_FORSOME = namedNode(namespaces.r.forSome); + parser.RDF_FIRST = factory.namedNode(namespaces.rdf.first); + parser.RDF_REST = factory.namedNode(namespaces.rdf.rest); + parser.RDF_NIL = factory.namedNode(namespaces.rdf.nil); + parser.N3_FORALL = factory.namedNode(namespaces.r.forAll); + parser.N3_FORSOME = factory.namedNode(namespaces.r.forSome); parser.ABBREVIATIONS = { - 'a': namedNode(namespaces.rdf.type), - '=': namedNode(namespaces.owl.sameAs), - '>': namedNode(namespaces.log.implies), + 'a': factory.namedNode(namespaces.rdf.type), + '=': factory.namedNode(namespaces.owl.sameAs), + '>': factory.namedNode(namespaces.log.implies), }; - parser.QUANTIFIERS_GRAPH = namedNode('urn:n3:quantifiers'); + parser.QUANTIFIERS_GRAPH = factory.namedNode('urn:n3:quantifiers'); } initDataFactory(N3Parser.prototype, N3DataFactory); diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 1d7cd6a4..961e8ad6 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -10,7 +10,7 @@ beforeEach(() => { blankId = 0; // reset per-node ID Parser._resetBlankNodePrefix(); // reset per-parser ID }); -Parser.prototype._blankNode = name => new BlankNode(name || `b${blankId++}`); +Parser.prototype._factory.blankNode = name => new BlankNode(name || `b${blankId++}`); describe('Parser', () => { describe('The Parser export', () => { @@ -2592,6 +2592,49 @@ describe('Parser', () => { ), ])).toBe(true); }); + + it('should use the internal `this` state', () => { + const customFactory = { + blankI: 0, + ...rdfDataModel, + blankNode: function () { + return DF.blankNode(`b-custom-${this.blankI++}`); + }, + }; + + const parser = new Parser({ + baseIRI: BASE_IRI, + format: 'n3', + factory: customFactory, + }); + + const quads = parser.parse(` + @prefix : . + [ :friend [ + :name "Thomas" ; + ] + ] . + `); + + expect(quads.length).toBeGreaterThan(0); + + // The blank node should be named b-custom-0 and b-custom-1 + const bnode1 = DF.blankNode('b-custom-0'); + const bnode2 = DF.blankNode('b-custom-1'); + + expect(isomorphic(quads, [ + DF.quad( + bnode1, + DF.namedNode('http://example.com/friend'), + bnode2, + ), + DF.quad( + bnode2, + DF.namedNode('http://example.com/name'), + DF.literal('Thomas'), + ), + ])).toBe(true); + }); }); describe('A turtle parser instance with external data factory', () => {