From 2f7d6e859e46645b7b54c37529d9e43f3c03d2ff Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 23 Nov 2022 13:57:38 +1100 Subject: [PATCH 01/47] chore: add nested list test --- test/N3Parser-test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 80cb2466..b3b8d8c7 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -476,6 +476,16 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_y'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + it('should parse a nested list', + shouldParse(' ( ( ) ).', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'c'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'] + )); + + it('should parse statements with a nested empty list', shouldParse(' ( ()).', ['a', 'b', '_:b0'], From 37ab09c3d27550b505ef3ac6e61eaf378bc3810c Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 23 Nov 2022 14:48:43 +1100 Subject: [PATCH 02/47] fix: support quoted triples in list --- src/N3Parser.js | 17 +++++ test/N3Parser-test.js | 165 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 172 insertions(+), 10 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 1a7d7449..2045cd7d 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -473,6 +473,16 @@ export default class N3Parser { this._saveContext('formula', this._graph, this._subject, this._predicate, this._graph = this._blankNode()); return this._readSubject; + case '<<': + if (!this._supportsRDFStar) + return this._error('Unexpected RDF* syntax', token); + + this._saveContext('<<', this._graph, this._subject, null, null); + return this._readSubject; + case '>>': + item = this._graph; + this._graph = null; + break; default: if ((item = this._readEntity(token)) === undefined) return; @@ -854,6 +864,13 @@ export default class N3Parser { const quad = this._quad(this._subject, this._predicate, this._object, this._graph || this.DEFAULTGRAPH); this._restoreContext('<<', token); + + // If the triple is in a list then return to reading the remaining elements + if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === 'list') { + this._graph = quad; + return this._readListItem(token); + } + // If the triple was the subject, continue by reading the predicate. if (this._subject === null) { this._subject = quad; diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index b3b8d8c7..60935bff 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -478,13 +478,11 @@ describe('Parser', () => { it('should parse a nested list', shouldParse(' ( ( ) ).', - ['a', 'b', '_:b0'], - ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], - ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], - ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'c'], - ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'] - )); - + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'c'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); it('should parse statements with a nested empty list', shouldParse(' ( ()).', @@ -504,13 +502,152 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a blank node', + it('should parse statements with a list containing a quoted triple', + shouldParse(' ( << >> ) .', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a quoted triple and iri after', + shouldParse(' ( << >> ) .', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'f'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a quoted triple and iri before', + shouldParse(' ( << >> ) .', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'f'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a 2 quoted triples', + shouldParse(' ( << >> << >> ) .', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c1', 'd1', 'e1']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + + it('should parse statements with a list containing a 3 quoted triples', + shouldParse(' ( << >> << >> << >> ) .', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c1', 'd1', 'e1']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c2', 'd2', 'e2']], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a 1 quoted triple and 2 iris', + shouldParse(' ( << >> ) .', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'h'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'i'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a 1 quoted triple between 2 iris', + shouldParse(' ( << >> ) .', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'h'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'i'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + + it('should parse a nested list containing 1 quoted triple', + shouldParse(' ( ( << >> ) ).', + ['a', 'b', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a quoted triple with list as subject', + shouldParse('( << >> ) .', + ['_:b0', 'a', 'b'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a quoted triple and iri after with list as subject', + shouldParse('( << >> ) .', + ['_:b0', 'a', 'b'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'f'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a quoted triple and iri before with list as subject', + shouldParse('( << >> ) .', + ['_:b0', 'a', 'b'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'f'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a 2 quoted triples with list as subject', + shouldParse('( << >> << >> ) .', + ['_:b0', 'a', 'b'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c1', 'd1', 'e1']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + + it('should parse statements with a list containing a 3 quoted triples with list as subject', + shouldParse('( << >> << >> << >> ) .', + ['_:b0', 'a', 'b'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c1', 'd1', 'e1']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c2', 'd2', 'e2']], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a 1 quoted triple and 2 iris with list as subject', + shouldParse('( << >> ) .', + ['_:b0', 'a', 'b'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'h'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'i'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a 1 quoted triple between 2 iris with list as subject', + shouldParse('( << >> ) .', + ['_:b0', 'a', 'b'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'h'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'i'], + ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse a nested list containing 1 quoted triple with list as subject', + shouldParse('( ( << >> ) ) .', + ['_:b0', 'a', 'b'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); + + it('should parse statements with a list containing a blank node with list as subject', shouldParse('([]) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing multiple blank nodes', + it('should parse statements with a list containing multiple blank nodes with list as subject', shouldParse('([] [ ]) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], @@ -519,7 +656,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b3', 'x', 'y'])); - it('should parse statements with a blank node containing a list', + it('should parse statements with a blank node containing a list with list as subject', shouldParse('[ ()] .', ['_:b0', 'c', 'd'], ['_:b0', 'a', '_:b1'], @@ -1235,6 +1372,14 @@ describe('Parser', () => { it('should not parse RDF* in the object position', shouldNotParse(parser, ' < >>.', 'Unexpected RDF* syntax on line 1.')); + + it('should not parse RDF* in the object list', + shouldNotParse(parser, ' ( < >> ).', + 'Unexpected RDF* syntax on line 1.')); + + it('should not parse RDF* in the subject list', + shouldNotParse(parser, '( < >> ) .', + 'Unexpected RDF* syntax on line 1.')); }); describe('A Parser instance for the TurtleStar format', () => { From f837b8d46bd5f64b7001b02fccefc5d57dc76ccd Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:13:24 +1100 Subject: [PATCH 03/47] breaking: drop support for quads in quoted triples as they are forbidden in the rdf-star spec --- src/N3Parser.js | 15 ++------------- test/N3Parser-test.js | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 2045cd7d..1e10dbb4 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -845,21 +845,10 @@ export default class N3Parser { return this._readPath; } - // ### `_readRDFStarTailOrGraph` reads the graph of a nested RDF* quad or the end of a nested RDF* triple - _readRDFStarTailOrGraph(token) { - if (token.type !== '>>') { - // An entity means this is a quad (only allowed if not already inside a graph) - if (this._supportsQuads && this._graph === null && (this._graph = this._readEntity(token)) !== undefined) - return this._readRDFStarTail; - return this._error(`Expected >> to follow "${this._object.id}"`, token); - } - return this._readRDFStarTail(token); - } - // ### `_readRDFStarTail` reads the end of a nested RDF* triple _readRDFStarTail(token) { if (token.type !== '>>') - return this._error(`Expected >> but got ${token.type}`, token); + return this._error(`Expected >> to follow "${this._object.id}" but got ${token.type}`, token); // Read the quad and restore the previous context const quad = this._quad(this._subject, this._predicate, this._object, this._graph || this.DEFAULTGRAPH); @@ -897,7 +886,7 @@ export default class N3Parser { case 'formula': return this._readFormulaTail; case '<<': - return this._readRDFStarTailOrGraph; + return this._readRDFStarTail; } } diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 60935bff..a85d653e 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -933,6 +933,10 @@ describe('Parser', () => { shouldParse(' .', ['a', 'b', 'c', 'g'])); + it('should not parse a quad in a quoted triple', + shouldNotParse('<< >> .', + 'Expected >> to follow "http://example.org/c" but got IRI on line 1.')); + it('should parse a quad with 4 prefixed names', shouldParse('@prefix p: .\np:a p:b p:c p:g.', ['p#a', 'p#b', 'p#c', 'p#g'])); @@ -1124,12 +1128,12 @@ describe('Parser', () => { it('should not parse nested RDF* statements that are partially closed', shouldNotParse(' <<<< >> .', - 'Expected entity but got . on line 1.' + 'Expected >> to follow "http://example.org/g" but got . on line 1.' )); it('should not parse partially closed nested RDF* statements', shouldNotParse(' <<<< >>.', - 'Expected >> but got IRI on line 1.' + 'Expected >> to follow "http://example.org/c" but got IRI on line 1.' )); it('should not parse nested RDF* statements with too many closing tags', @@ -1167,13 +1171,9 @@ describe('Parser', () => { 'Unexpected . on line 1.' )); - it('should parse an RDF* quad', - shouldParse('<< >> .', - [['a', 'b', 'c', 'd'], 'a', 'b'])); - it('should not parse a malformed RDF* quad', shouldNotParse('<< >> .', - 'Expected >> but got IRI on line 1.')); + 'Expected >> to follow "http://example.org/c" but got IRI on line 1.')); it('should parse statements with a shared RDF* subject', shouldParse('<< >> ;\n .', @@ -1392,7 +1392,7 @@ describe('Parser', () => { it('should not parse nested quads', shouldNotParse(parser, '<<_:a _:b >> "c" .', - 'Expected >> to follow "_:b0_b" on line 1.')); + 'Expected >> to follow "_:b0_b" but got IRI on line 1.')); }); describe('A Parser instance for the TriG format', () => { @@ -1452,7 +1452,7 @@ describe('Parser', () => { it('should not parse nested quads', shouldNotParse(parser, '<<_:a _:b >> "c" .', - 'Expected >> to follow "_:b0_b" on line 1.')); + 'Expected >> to follow "_:b0_b" but got IRI on line 1.')); }); describe('A Parser instance for the N-Triples format', () => { @@ -1527,7 +1527,7 @@ describe('Parser', () => { it('should not parse nested quads', shouldNotParse(parser, '<<_:a _:b >> "c" .', - 'Expected >> to follow "_:b0_b" on line 1.')); + 'Expected >> to follow "_:b0_b" but got IRI on line 1.')); }); describe('A Parser instance for the N-Quads format', () => { @@ -1941,7 +1941,7 @@ describe('Parser', () => { it('should not parse nested quads', shouldNotParse(parser, '<<_:a _:b >> "c" .', - 'Expected >> to follow "_:.b" on line 1.')); + 'Expected >> to follow "_:.b" but got IRI on line 1.')); }); describe('A Parser instance for the N3 format with the explicitQuantifiers option', () => { From da945e9bb992b6bad3f4859adb193d8972d9a891 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 23 Nov 2022 16:32:40 +1100 Subject: [PATCH 04/47] feat: support annotated triples --- src/N3Lexer.js | 10 ++++++- src/N3Parser.js | 27 ++++++++++++++++++ test/N3Lexer-test.js | 46 ++++++++++++++++++++++++++++++ test/N3Parser-test.js | 65 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 146 insertions(+), 2 deletions(-) diff --git a/src/N3Lexer.js b/src/N3Lexer.js index eeb36bdd..19ceca32 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -302,12 +302,20 @@ export default class N3Lexer { case ')': case '{': case '}': + if (input.length > 1 && input[1] === '|') { + type = '{|', matchLength = 2; + break; + } if (!this._lineMode) { matchLength = 1; type = firstChar; } break; - + case '|': + if (input.length > 1 && input[1] === '}') { + type = '|}', matchLength = 2; + break; + } default: inconclusive = true; } diff --git a/src/N3Parser.js b/src/N3Parser.js index 1e10dbb4..4af2750b 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -624,6 +624,19 @@ export default class N3Parser { case ',': next = this._readObject; break; + case '{|': + if (!this._supportsRDFStar) + return this._error('Unexpected RDF* syntax', token); + + // TODO: Have error handling behavior here + // TODO: See if we can just emit and then save null context + this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); + + // Note - we always use the default graph for the quoted triple component + this._subject = this._quad(this._subject, this._predicate, this._object, this.DEFAULTGRAPH); + this._predicate = null; + this._object = null; + return this._readPredicate; default: // An entity means this is a quad (only allowed if not already inside a graph) if (this._supportsQuads && this._graph === null && (graph = this._readEntity(token)) !== undefined) { @@ -872,6 +885,18 @@ export default class N3Parser { } } + // ### `_readRDFStarTail` reads the end of a nested RDF* triple + _readAnnotatedTail(token) { + this._emit(this._subject, this._predicate, this._object, this._graph); + // if (this._subject && this._predicate && this._object) { + // this._emit(this._subject, this._predicate, this._object, this._graph); + // } + if (token.type !== '|}') + return this._readPredicate; + this._restoreContext('{|', token); + return this._getContextEndReader(); + } + // ### `_getContextEndReader` gets the next reader function at the end of a context _getContextEndReader() { const contextStack = this._contextStack; @@ -887,6 +912,8 @@ export default class N3Parser { return this._readFormulaTail; case '<<': return this._readRDFStarTail; + case '{|': + return this._readAnnotatedTail; } } diff --git a/test/N3Lexer-test.js b/test/N3Lexer-test.js index 03902d3f..8034f596 100644 --- a/test/N3Lexer-test.js +++ b/test/N3Lexer-test.js @@ -869,6 +869,52 @@ describe('Lexer', () => { shouldNotTokenize('<< \n\t \n\t> .', 'Unexpected ">" on line 3.')); + it('should tokenize an RDF-star annotated statement', + shouldTokenize(' {| |}', + { type: 'IRI', value: 'a', line: 1 }, + { type: 'IRI', value: 'b', line: 1 }, + { type: 'IRI', value: 'c', line: 1 }, + { type: '{|', line: 1 }, + { type: 'IRI', value: 'd', line: 1 }, + { type: 'IRI', value: 'e', line: 1 }, + { type: '|}', line: 1 }, + { type: 'eof', line: 1 })); + + it('should tokenize an RDF-star annotated statement with multiple annotations', + shouldTokenize(' {| ; |}', + { type: 'IRI', value: 'a', line: 1 }, + { type: 'IRI', value: 'b', line: 1 }, + { type: 'IRI', value: 'c', line: 1 }, + { type: '{|', line: 1 }, + { type: 'IRI', value: 'd', line: 1 }, + { type: 'IRI', value: 'e', line: 1 }, + { type: ';', line: 1 }, + { type: 'IRI', value: 'f', line: 1 }, + { type: 'IRI', value: 'g', line: 1 }, + { type: '|}', line: 1 }, + { type: 'eof', line: 1 })); + + it('should tokenize an RDF-star annotated statement with multiple annotations, one containing a blank node', + shouldTokenize(' {| [ "f" ]; |}', + { type: 'IRI', value: 'a', line: 1 }, + { type: 'IRI', value: 'b', line: 1 }, + { type: 'IRI', value: 'c', line: 1 }, + { type: '{|', line: 1 }, + { type: 'IRI', value: 'd', line: 1 }, + { type: '[', value: '', line: 1 }, + { type: 'IRI', value: 'e', line: 1 }, + { type: 'literal', value: 'f', line: 1 }, + { type: ']', value: '', line: 1 }, + { type: ';', line: 1 }, + { type: 'IRI', value: 'f', line: 1 }, + { type: 'IRI', value: 'g', line: 1 }, + { type: '|}', line: 1 }, + { type: 'eof', line: 1 })); + + it('should not tokenize an annotated statement that is not closed', + shouldNotTokenize(' {| [ "f" ]; |', + 'Unexpected "|" on line 1.')); + it('should tokenize a split RDF* statement with IRIs', shouldTokenize(streamOf('<', '< \n\t \n\t>> .'), { type: '<<', line: 1 }, diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index a85d653e..5b236b36 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1189,6 +1189,65 @@ describe('Parser', () => { shouldParse(' .\n<< >> .', ['a', 'b', 'c', 'g'], [['a', 'b', 'c'], 'd', 'e'])); + + it('should parse an explicit triple with reified annotation', + shouldParse(' {| |} .', + ['a', 'b', 'c'], + [['a', 'b', 'c'], 'd', 'e'])); + + const q = ['http://example.com/ns#s', 'http://example.com/ns#p', + ['http://example.com/ns#a', 'http://example.com/ns#b', 'http://example.com/ns#c']]; + + it('should parse an explicit triple with reified annotation containing prefixed iris', + shouldParse('PREFIX : \n :s :p <<:a :b :c>> {| :q :z |} .', + q, [q, 'http://example.com/ns#q', 'http://example.com/ns#z'])); + + it('should parse an explicit triple with 2 reified annotations', + shouldParse(' {| ; |} .', + ['a', 'b', 'c'], + [['a', 'b', 'c'], 'd', 'e'], + [['a', 'b', 'c'], 'f', 'g'])); + + // TODO: See if this is required by the spec tests + // it('should parse an explicit triple with reified annotation containing punctuation', + // shouldParse(' {| . |} .', + // ['a', 'b', 'c'], + // [['a', 'b', 'c'], 'd', 'e'])); + + it('should parse an explicit triple with reified annotation in a named graph', + shouldParse(' { {| |} . }', + ['a', 'b', 'c', 'G'], + [['a', 'b', 'c'], 'd', 'e', 'G'])); + + it('should parse an explicit triple with 2 reified annotations in a named graph', + shouldParse(' { {| ; |} . }', + ['a', 'b', 'c', 'G'], + [['a', 'b', 'c'], 'd', 'e', 'G'], + [['a', 'b', 'c'], 'f', 'g', 'G'])); + + it('should not parse an annotated object in list', + shouldNotParse(' ( {| |} )', + 'Expected entity but got {| on line 1.')); + + it('should not parse an annotated statement in list', + shouldNotParse(' ( {| |} )', + 'Expected entity but got {| on line 1.')); + + it('should not parse fourth term in quoted triple', + shouldNotParse('<< >>

', + 'Expected >> to follow "http://example.org/c" but got IRI on line 1.')); + + it('should not parse fourth term in quoted triple object', + shouldNotParse('

<< >>', + 'Expected >> to follow "http://example.org/c" but got IRI on line 1.')); + + it('should not parse quoted triple as predicate', + shouldNotParse('

<< >> ', + 'Expected entity but got << on line 1.')); + + it('should not parse quoted quad as predicate', + shouldNotParse('

<< >> ', + 'Expected entity but got << on line 1.')); }); describe('An Parser instance without document IRI', () => { @@ -1369,6 +1428,10 @@ describe('Parser', () => { shouldNotParse(parser, '<< >> .', 'Unexpected RDF* syntax on line 1.')); + it('should not parse annotated statement', + shouldNotParse(parser, ' {| |} .', + 'Unexpected RDF* syntax on line 1.')); + it('should not parse RDF* in the object position', shouldNotParse(parser, ' < >>.', 'Unexpected RDF* syntax on line 1.')); @@ -2494,7 +2557,7 @@ function mapToQuad(item) { function toSortedJSON(triples) { triples = triples.map(t => { return JSON.stringify([ - t.subject.toJSON(), t.predicate.toJSON(), t.object.toJSON(), t.graph.toJSON(), + t.subject && t.subject.toJSON(), t.predicate && t.predicate.toJSON(), t.object && t.object.toJSON(), t.graph && t.graph.toJSON(), ]); }); triples.sort(); From 624e3e1e1dfb47b684e60feca5505ecb8b2211bf Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 23 Nov 2022 17:26:54 +1100 Subject: [PATCH 05/47] chore: error on quoted compound bnodes --- package-lock.json | 530 ++++++++++++++---------------------------- package.json | 10 +- src/N3Parser.js | 13 +- test/N3Parser-test.js | 30 +++ 4 files changed, 224 insertions(+), 359 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3ff8bbe..c2f10e65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "mocha": "^8.0.0", "nyc": "^14.1.1", "pre-commit": "^1.2.2", - "rdf-test-suite": "^1.19.2", + "rdf-test-suite": "^1.20.0", "streamify-string": "^1.0.1", "uglify-js": "^3.14.3" }, @@ -1735,9 +1735,9 @@ "optional": true }, "node_modules/@rdfjs/types": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.0.1.tgz", - "integrity": "sha512-YxVkH0XrCNG3MWeZxfg596GFe+oorTVusmNxRP6ZHTsGczZ8AGvG3UchRNkg3Fy4MyysI7vBAA5YZbESL+VmHQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.1.0.tgz", + "integrity": "sha512-5zm8bN2/CC634dTcn/0AhTRLaQRjXDZs3QfcAsQKNturHT7XVWcKy/8p3P5gXl+YkZTAmy7T5M/LyiT/jbkENw==", "dev": true, "dependencies": { "@types/node": "*" @@ -1753,25 +1753,15 @@ } }, "node_modules/@types/json-stable-stringify": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz", - "integrity": "sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw==", + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz", + "integrity": "sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==", "dev": true }, - "node_modules/@types/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-uW/AGf/41aZ1c1dhZ3s063Ii2OqT8EQooZu3t4VCRyR3dqyA2Bg46BcKyZpnWTY7wzm6cayq4jzylnruu4KqSA==", - "deprecated": "This is a stub types definition. log-symbols provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "log-symbols": "*" - } - }, "node_modules/@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, "node_modules/@types/n3": { @@ -1785,25 +1775,31 @@ } }, "node_modules/@types/node": { - "version": "18.6.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.3.tgz", - "integrity": "sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg==", + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", "dev": true }, "node_modules/@types/readable-stream": { - "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.14.tgz", - "integrity": "sha512-8jQ5Mp7bsDJEnW/69i6nAaQMoLwAVJVc7ZRAVTrdh/o6XueQsX38TEvKuYyoQj76/mg7WdlRfMrtl9pDLCJWsg==", + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", "dev": true, "dependencies": { "@types/node": "*", - "safe-buffer": "*" + "safe-buffer": "~5.1.1" } }, + "node_modules/@types/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/@types/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-dqYdvN7Sbw8QT/0Ci5rhjE4/iCMJEM0Y9rHpCu+gGXD9Lwbz28t6HI2yegsB6BoV1sShRMU6lAmAcgRjmFy7LA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==", "dev": true, "dependencies": { "@types/node": "*" @@ -3999,9 +3995,9 @@ } }, "node_modules/http-link-header": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.0.5.tgz", - "integrity": "sha512-msKrMbv/xHzhdOD4sstbEr+NbGqpv8ZtZliiCeByGENJo1jK1GZ/81zHF9HpWtEH5ihovPpdqHXniwZapJCKEA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.0.tgz", + "integrity": "sha512-pj6N1yxOz/ANO8HHsWGg/OoIL1kmRYvQnXQ7PIRpgp+15AnEsRH8fmIJE6D1OdWG2Bov+BJHVla1fFXxg1JbbA==", "dev": true, "engines": { "node": ">=6.0.0" @@ -4466,18 +4462,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", @@ -4689,12 +4673,15 @@ "dev": true }, "node_modules/json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz", + "integrity": "sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g==", "dev": true, "dependencies": { - "jsonify": "~0.0.0" + "jsonify": "^0.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/json-stable-stringify-without-jsonify": { @@ -4731,18 +4718,18 @@ } }, "node_modules/jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", "dev": true, - "engines": { - "node": "*" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/jsonld-context-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jsonld-context-parser/-/jsonld-context-parser-2.2.0.tgz", - "integrity": "sha512-h4ykp8iUOV4Xm6MgS1zVrytyw/dNVgOeofMCcD/5mHPng3i49qAsaomLT0BOXqYas7lwITVT5c6NZIRVMdXfVQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/jsonld-context-parser/-/jsonld-context-parser-2.2.2.tgz", + "integrity": "sha512-3VWIg/4NCMTXP6NsI6O93spFTd4qIOucKEmD8I+Exhxk9ZUVrnkLp2G4f0toR5jVleZkiiB9YGPS+yT1wwMqnQ==", "dev": true, "dependencies": { "@types/http-link-header": "^1.0.1", @@ -4757,9 +4744,9 @@ } }, "node_modules/jsonld-streaming-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonld-streaming-parser/-/jsonld-streaming-parser-3.0.0.tgz", - "integrity": "sha512-n+IW+gTIw2UeXWXdN0ZlPY4DvKANUCrV0HOagXOsDUCvkO/SiDcYOZn2hrDkBGKm7yD5sefvvG3d/FxbeepbuA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonld-streaming-parser/-/jsonld-streaming-parser-3.0.1.tgz", + "integrity": "sha512-zSJlEgrKypQDk/85R+xkudeCZo6vmnvJuCPvcjk2BzHPLzv1yqiwoKQDyFzfgfgCHM0p7YCJBZl0liT9RMUZJw==", "dev": true, "dependencies": { "@rdfjs/types": "*", @@ -5616,30 +5603,16 @@ "dev": true }, "node_modules/n3": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/n3/-/n3-1.16.2.tgz", - "integrity": "sha512-5vYa2HuNEJ+a26FEs4FGgfFLgaPOODaZpJlc7FS0eUjDumc4uK0cvx216PjKXBkLzmAsSqGgQPwqztcLLvwDsw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.16.3.tgz", + "integrity": "sha512-9caLSZuMW1kdlPxEN4ka6E4E8a5QKoZ2emxpW+zHMofI+Bo92nJhN//wNub15S5T9I4c6saEqdGEu+YXJqMZVA==", "dev": true, "dependencies": { "queue-microtask": "^1.1.2", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/n3/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=12.0" } }, "node_modules/nanoid": { @@ -6209,7 +6182,7 @@ "node_modules/promise-polyfill": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-1.1.6.tgz", - "integrity": "sha1-zQTv9G9clcOn0EVZHXm14+AfEtc=", + "integrity": "sha512-7rrONfyLkDEc7OJ5QBkqa4KI4EBhCd340xRuIUPGCfu13znS+vx+VDdrT9ODAJHlXm7w4lbxN3DRjyv58EuzDg==", "dev": true }, "node_modules/pseudomap": { @@ -6314,9 +6287,9 @@ } }, "node_modules/rdf-isomorphic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rdf-isomorphic/-/rdf-isomorphic-1.3.0.tgz", - "integrity": "sha512-3BRwUwCNHHR8//bqmVH+knTFVbVfkp7CWyQk7qPHHA8JriXBYxrab21OomjJx/2KF21w8bWz344mgNYEaQABYQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rdf-isomorphic/-/rdf-isomorphic-1.3.1.tgz", + "integrity": "sha512-6uIhsXTVp2AtO6f41PdnRV5xZsa0zVZQDTBdn0br+DZuFf5M/YD+T6m8hKDUnALI6nFL/IujTMLgEs20MlNidQ==", "dev": true, "dependencies": { "@rdfjs/types": "*", @@ -6335,9 +6308,9 @@ } }, "node_modules/rdf-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rdf-literal/-/rdf-literal-1.3.0.tgz", - "integrity": "sha512-5u5L4kPYNZANie5AE4gCXqwpNO/p9E/nUcDurk05XAOJT/pt9rQlDk6+BX7j3dNSee3h9GS4xlLoWxQDj7sXtg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rdf-literal/-/rdf-literal-1.3.1.tgz", + "integrity": "sha512-+o/PGOfJchyay9Rjrvi/oveRJACnt2WFO3LhEvtPlsRD1tFmwVUCMU+s33FtQprMo+z1ohFrv/yfEQ6Eym4KgQ==", "dev": true, "dependencies": { "@rdfjs/types": "*", @@ -6345,9 +6318,9 @@ } }, "node_modules/rdf-object": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/rdf-object/-/rdf-object-1.13.1.tgz", - "integrity": "sha512-Sgq+GbsqdPsMYh+d4OZ4C9brXlzqa9MvfVHG4pkuT9p7o+AX39nqjTWE/8HVaXjjOZBIDe8T54WWTMWphu3BpA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/rdf-object/-/rdf-object-1.13.2.tgz", + "integrity": "sha512-DVLDCbxPOkhd/k43j9wcLU7CXe/gdldBBomMV3RyZ1G9E2zPa2FFNFijzMGgRGNY1OEyGmhBxw2eiJjUC7GVNw==", "dev": true, "dependencies": { "@rdfjs/types": "*", @@ -6379,9 +6352,9 @@ } }, "node_modules/rdf-terms": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/rdf-terms/-/rdf-terms-1.9.0.tgz", - "integrity": "sha512-FGMPOIpr6vEN8gWd/dVuPpcE/7k+u4Ufqi8FvM5lczjhduT1MN9Shmrw50fWCpHFVE4n0T3lV0qti1PCaHxAfg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/rdf-terms/-/rdf-terms-1.9.1.tgz", + "integrity": "sha512-GrE8CbQSvuVEFRCywMu6VOgV1AFE6X+nFYcAhEc5pwYKI13bUvz4voiVufQiy3V8rzQKu21Sgl+dS2qcJavy7w==", "dev": true, "dependencies": { "@rdfjs/types": "*", @@ -6390,14 +6363,13 @@ } }, "node_modules/rdf-test-suite": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/rdf-test-suite/-/rdf-test-suite-1.19.2.tgz", - "integrity": "sha512-Qvbf05SfcNcvwFzroBVSVf51zS6R74GaQmX43UwXKNxVWMoDyZlgXWLfznDtTJW2HfahnFkTsyosxrliN1zZ1Q==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/rdf-test-suite/-/rdf-test-suite-1.20.0.tgz", + "integrity": "sha512-5p26dxVT3Hxr0nV3fEmGHm/NYHr7i8R4zp1hijCNHVrwV22LhAGtYWqYUtRUe00HO0J0kUr+Hpxh3X/OejCLxA==", "dev": true, "dependencies": { "@rdfjs/types": "*", "@types/json-stable-stringify": "^1.0.32", - "@types/log-symbols": "^3.0.0", "@types/minimist": "^1.2.0", "@types/n3": "^1.10.3", "@types/sax": "^1.0.1", @@ -6428,70 +6400,12 @@ "rdf-test-suite": "bin/Runner.js" } }, - "node_modules/rdf-test-suite/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/rdf-test-suite/node_modules/arrayify-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arrayify-stream/-/arrayify-stream-2.0.0.tgz", - "integrity": "sha512-Z2NRtxpWQIz3NRA2bEZOziIungBH+fpsFFEolc5u8uVRheYitvsDNvejlfyh/hjZ9VyS9Ba62oY0zc5oa6Wu7g==", - "dev": true - }, - "node_modules/rdf-test-suite/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/rdf-test-suite/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/rdf-test-suite/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "resolved": "https://registry.npmjs.org/arrayify-stream/-/arrayify-stream-2.0.1.tgz", + "integrity": "sha512-z8fB6PtmnewQpFB53piS2d1KlUi3BPMICH2h7leCOUXpQcwvZ4GbHHSpdKoUrgLMR6b4Qan/uDe1St3Ao3yIHg==", "dev": true }, - "node_modules/rdf-test-suite/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/rdf-test-suite/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6504,38 +6418,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rdf-test-suite/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rdf-test-suite/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/rdfxml-streaming-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rdfxml-streaming-parser/-/rdfxml-streaming-parser-2.1.0.tgz", - "integrity": "sha512-G2kYXekAy7TUE5G6PAI5/Y/5ugqwFkA+305dcqbnRqqnK+a5gq2ubLGGmxJIIIEFbcFoUZ5UfQRHvqZdsWC8xQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/rdfxml-streaming-parser/-/rdfxml-streaming-parser-2.2.1.tgz", + "integrity": "sha512-1r7aXfSRCLkBYXGcko/GpSZdHxXKvYaeUi2ulEbB7cLvACD7DNoAA/uW6dsETEhgmsEipJZI7NLqBl2whOse8Q==", "dev": true, "dependencies": { "@rdfjs/types": "*", @@ -6544,7 +6430,8 @@ "rdf-data-factory": "^1.1.0", "readable-stream": "^4.0.0", "relative-to-absolute-iri": "^1.0.0", - "saxes": "^6.0.0" + "saxes": "^6.0.0", + "validate-iri": "^1.0.0" } }, "node_modules/rdfxml-streaming-parser/node_modules/buffer": { @@ -6777,9 +6664,9 @@ } }, "node_modules/relative-to-absolute-iri": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/relative-to-absolute-iri/-/relative-to-absolute-iri-1.0.6.tgz", - "integrity": "sha512-Xw5/Zx6iWSCMJUXwXVOjySjH8Xli4hVFL9QQFvkl1qEmFBG94J+QUI9emnoctOCD3285f1jNV+QWV9eDYwIdfQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/relative-to-absolute-iri/-/relative-to-absolute-iri-1.0.7.tgz", + "integrity": "sha512-Xjyl4HmIzg2jzK/Un2gELqbcE8Fxy85A/aLSHE6PE/3+OGsFwmKVA1vRyGaz6vLWSqLDMHA+5rjD/xbibSQN1Q==", "dev": true }, "node_modules/release-zalgo": { @@ -7102,9 +6989,9 @@ } }, "node_modules/sparqljson-parse": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sparqljson-parse/-/sparqljson-parse-2.1.0.tgz", - "integrity": "sha512-JKyoDNDR9xrJwR6x6N41UWfER6kfeirE9BRBHdSFSfQF3eF3pxpuUTcJ6QGwHQ4wC/JsQdG/4OGmzkuk1B+8gg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/sparqljson-parse/-/sparqljson-parse-2.1.2.tgz", + "integrity": "sha512-RqPeyy+RYQMeqgEsKPTY+ME5ZNXcgXJzg1v0o+tROiTntS9CwUW8mAY3wsx6seSvW3LVyNDEtsqOxnAokoGXOA==", "dev": true, "dependencies": { "@rdfjs/types": "*", @@ -7140,9 +7027,9 @@ } }, "node_modules/sparqlxml-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/sparqlxml-parse/-/sparqlxml-parse-2.0.1.tgz", - "integrity": "sha512-7HZMm0l9a+NQW6mEHzur+KEXA2/gpLYsyiq9yMLKa7Us8yfUJG/+fbHmuBoN7yE0t41SfCXtq4EU/nDjFSGudw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sparqlxml-parse/-/sparqlxml-parse-2.0.2.tgz", + "integrity": "sha512-Iqso0WSTCSuMUYoX2pOEJxteCq9U+7AkOqwlFcvFG1S1aM87xWrp28njQOIiyIrL7Y8CkVXBZG1ec+DhZYUNXA==", "dev": true, "dependencies": { "@rdfjs/types": "*", @@ -7868,6 +7755,12 @@ "uuid": "bin/uuid" } }, + "node_modules/validate-iri": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/validate-iri/-/validate-iri-1.0.1.tgz", + "integrity": "sha512-gLXi7351CoyVVQw8XE5sgpYawRKatxE7kj/xmCxXOZS1kMdtcqC0ILIqLuVEVnAUQSL/evOGG3eQ+8VgbdnstA==", + "dev": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -9383,9 +9276,9 @@ "optional": true }, "@rdfjs/types": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.0.1.tgz", - "integrity": "sha512-YxVkH0XrCNG3MWeZxfg596GFe+oorTVusmNxRP6ZHTsGczZ8AGvG3UchRNkg3Fy4MyysI7vBAA5YZbESL+VmHQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.1.0.tgz", + "integrity": "sha512-5zm8bN2/CC634dTcn/0AhTRLaQRjXDZs3QfcAsQKNturHT7XVWcKy/8p3P5gXl+YkZTAmy7T5M/LyiT/jbkENw==", "dev": true, "requires": { "@types/node": "*" @@ -9401,24 +9294,15 @@ } }, "@types/json-stable-stringify": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz", - "integrity": "sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw==", + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz", + "integrity": "sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==", "dev": true }, - "@types/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-uW/AGf/41aZ1c1dhZ3s063Ii2OqT8EQooZu3t4VCRyR3dqyA2Bg46BcKyZpnWTY7wzm6cayq4jzylnruu4KqSA==", - "dev": true, - "requires": { - "log-symbols": "*" - } - }, "@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, "@types/n3": { @@ -9432,25 +9316,33 @@ } }, "@types/node": { - "version": "18.6.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.3.tgz", - "integrity": "sha512-6qKpDtoaYLM+5+AFChLhHermMQxc3TOEFIDzrZLPRGHPrLEwqFkkT5Kx3ju05g6X7uDPazz3jHbKPX0KzCjntg==", + "version": "18.11.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", "dev": true }, "@types/readable-stream": { - "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.14.tgz", - "integrity": "sha512-8jQ5Mp7bsDJEnW/69i6nAaQMoLwAVJVc7ZRAVTrdh/o6XueQsX38TEvKuYyoQj76/mg7WdlRfMrtl9pDLCJWsg==", + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", "dev": true, "requires": { "@types/node": "*", - "safe-buffer": "*" + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } } }, "@types/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-dqYdvN7Sbw8QT/0Ci5rhjE4/iCMJEM0Y9rHpCu+gGXD9Lwbz28t6HI2yegsB6BoV1sShRMU6lAmAcgRjmFy7LA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==", "dev": true, "requires": { "@types/node": "*" @@ -11283,9 +11175,9 @@ "dev": true }, "http-link-header": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.0.5.tgz", - "integrity": "sha512-msKrMbv/xHzhdOD4sstbEr+NbGqpv8ZtZliiCeByGENJo1jK1GZ/81zHF9HpWtEH5ihovPpdqHXniwZapJCKEA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/http-link-header/-/http-link-header-1.1.0.tgz", + "integrity": "sha512-pj6N1yxOz/ANO8HHsWGg/OoIL1kmRYvQnXQ7PIRpgp+15AnEsRH8fmIJE6D1OdWG2Bov+BJHVla1fFXxg1JbbA==", "dev": true }, "https-browserify": { @@ -11606,12 +11498,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, "is-weakref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", @@ -11779,12 +11665,12 @@ "dev": true }, "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz", + "integrity": "sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g==", "dev": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "^0.0.1" } }, "json-stable-stringify-without-jsonify": { @@ -11813,15 +11699,15 @@ } }, "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", "dev": true }, "jsonld-context-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jsonld-context-parser/-/jsonld-context-parser-2.2.0.tgz", - "integrity": "sha512-h4ykp8iUOV4Xm6MgS1zVrytyw/dNVgOeofMCcD/5mHPng3i49qAsaomLT0BOXqYas7lwITVT5c6NZIRVMdXfVQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/jsonld-context-parser/-/jsonld-context-parser-2.2.2.tgz", + "integrity": "sha512-3VWIg/4NCMTXP6NsI6O93spFTd4qIOucKEmD8I+Exhxk9ZUVrnkLp2G4f0toR5jVleZkiiB9YGPS+yT1wwMqnQ==", "dev": true, "requires": { "@types/http-link-header": "^1.0.1", @@ -11833,9 +11719,9 @@ } }, "jsonld-streaming-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonld-streaming-parser/-/jsonld-streaming-parser-3.0.0.tgz", - "integrity": "sha512-n+IW+gTIw2UeXWXdN0ZlPY4DvKANUCrV0HOagXOsDUCvkO/SiDcYOZn2hrDkBGKm7yD5sefvvG3d/FxbeepbuA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonld-streaming-parser/-/jsonld-streaming-parser-3.0.1.tgz", + "integrity": "sha512-zSJlEgrKypQDk/85R+xkudeCZo6vmnvJuCPvcjk2BzHPLzv1yqiwoKQDyFzfgfgCHM0p7YCJBZl0liT9RMUZJw==", "dev": true, "requires": { "@rdfjs/types": "*", @@ -12493,26 +12379,13 @@ "dev": true }, "n3": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/n3/-/n3-1.16.2.tgz", - "integrity": "sha512-5vYa2HuNEJ+a26FEs4FGgfFLgaPOODaZpJlc7FS0eUjDumc4uK0cvx216PjKXBkLzmAsSqGgQPwqztcLLvwDsw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/n3/-/n3-1.16.3.tgz", + "integrity": "sha512-9caLSZuMW1kdlPxEN4ka6E4E8a5QKoZ2emxpW+zHMofI+Bo92nJhN//wNub15S5T9I4c6saEqdGEu+YXJqMZVA==", "dev": true, "requires": { "queue-microtask": "^1.1.2", - "readable-stream": "^3.6.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } + "readable-stream": "^4.0.0" } }, "nanoid": { @@ -12954,7 +12827,7 @@ "promise-polyfill": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-1.1.6.tgz", - "integrity": "sha1-zQTv9G9clcOn0EVZHXm14+AfEtc=", + "integrity": "sha512-7rrONfyLkDEc7OJ5QBkqa4KI4EBhCd340xRuIUPGCfu13znS+vx+VDdrT9ODAJHlXm7w4lbxN3DRjyv58EuzDg==", "dev": true }, "pseudomap": { @@ -13037,9 +12910,9 @@ } }, "rdf-isomorphic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rdf-isomorphic/-/rdf-isomorphic-1.3.0.tgz", - "integrity": "sha512-3BRwUwCNHHR8//bqmVH+knTFVbVfkp7CWyQk7qPHHA8JriXBYxrab21OomjJx/2KF21w8bWz344mgNYEaQABYQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rdf-isomorphic/-/rdf-isomorphic-1.3.1.tgz", + "integrity": "sha512-6uIhsXTVp2AtO6f41PdnRV5xZsa0zVZQDTBdn0br+DZuFf5M/YD+T6m8hKDUnALI6nFL/IujTMLgEs20MlNidQ==", "dev": true, "requires": { "@rdfjs/types": "*", @@ -13058,9 +12931,9 @@ } }, "rdf-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rdf-literal/-/rdf-literal-1.3.0.tgz", - "integrity": "sha512-5u5L4kPYNZANie5AE4gCXqwpNO/p9E/nUcDurk05XAOJT/pt9rQlDk6+BX7j3dNSee3h9GS4xlLoWxQDj7sXtg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rdf-literal/-/rdf-literal-1.3.1.tgz", + "integrity": "sha512-+o/PGOfJchyay9Rjrvi/oveRJACnt2WFO3LhEvtPlsRD1tFmwVUCMU+s33FtQprMo+z1ohFrv/yfEQ6Eym4KgQ==", "dev": true, "requires": { "@rdfjs/types": "*", @@ -13068,9 +12941,9 @@ } }, "rdf-object": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/rdf-object/-/rdf-object-1.13.1.tgz", - "integrity": "sha512-Sgq+GbsqdPsMYh+d4OZ4C9brXlzqa9MvfVHG4pkuT9p7o+AX39nqjTWE/8HVaXjjOZBIDe8T54WWTMWphu3BpA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/rdf-object/-/rdf-object-1.13.2.tgz", + "integrity": "sha512-DVLDCbxPOkhd/k43j9wcLU7CXe/gdldBBomMV3RyZ1G9E2zPa2FFNFijzMGgRGNY1OEyGmhBxw2eiJjUC7GVNw==", "dev": true, "requires": { "@rdfjs/types": "*", @@ -13102,9 +12975,9 @@ } }, "rdf-terms": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/rdf-terms/-/rdf-terms-1.9.0.tgz", - "integrity": "sha512-FGMPOIpr6vEN8gWd/dVuPpcE/7k+u4Ufqi8FvM5lczjhduT1MN9Shmrw50fWCpHFVE4n0T3lV0qti1PCaHxAfg==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/rdf-terms/-/rdf-terms-1.9.1.tgz", + "integrity": "sha512-GrE8CbQSvuVEFRCywMu6VOgV1AFE6X+nFYcAhEc5pwYKI13bUvz4voiVufQiy3V8rzQKu21Sgl+dS2qcJavy7w==", "dev": true, "requires": { "@rdfjs/types": "*", @@ -13113,14 +12986,13 @@ } }, "rdf-test-suite": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/rdf-test-suite/-/rdf-test-suite-1.19.2.tgz", - "integrity": "sha512-Qvbf05SfcNcvwFzroBVSVf51zS6R74GaQmX43UwXKNxVWMoDyZlgXWLfznDtTJW2HfahnFkTsyosxrliN1zZ1Q==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/rdf-test-suite/-/rdf-test-suite-1.20.0.tgz", + "integrity": "sha512-5p26dxVT3Hxr0nV3fEmGHm/NYHr7i8R4zp1hijCNHVrwV22LhAGtYWqYUtRUe00HO0J0kUr+Hpxh3X/OejCLxA==", "dev": true, "requires": { "@rdfjs/types": "*", "@types/json-stable-stringify": "^1.0.32", - "@types/log-symbols": "^3.0.0", "@types/minimist": "^1.2.0", "@types/n3": "^1.10.3", "@types/sax": "^1.0.1", @@ -13148,50 +13020,10 @@ "streamify-string": "^1.0.1" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "arrayify-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arrayify-stream/-/arrayify-stream-2.0.0.tgz", - "integrity": "sha512-Z2NRtxpWQIz3NRA2bEZOziIungBH+fpsFFEolc5u8uVRheYitvsDNvejlfyh/hjZ9VyS9Ba62oY0zc5oa6Wu7g==", - "dev": true - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/arrayify-stream/-/arrayify-stream-2.0.1.tgz", + "integrity": "sha512-z8fB6PtmnewQpFB53piS2d1KlUi3BPMICH2h7leCOUXpQcwvZ4GbHHSpdKoUrgLMR6b4Qan/uDe1St3Ao3yIHg==", "dev": true }, "is-stream": { @@ -13199,32 +13031,13 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, "rdfxml-streaming-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rdfxml-streaming-parser/-/rdfxml-streaming-parser-2.1.0.tgz", - "integrity": "sha512-G2kYXekAy7TUE5G6PAI5/Y/5ugqwFkA+305dcqbnRqqnK+a5gq2ubLGGmxJIIIEFbcFoUZ5UfQRHvqZdsWC8xQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/rdfxml-streaming-parser/-/rdfxml-streaming-parser-2.2.1.tgz", + "integrity": "sha512-1r7aXfSRCLkBYXGcko/GpSZdHxXKvYaeUi2ulEbB7cLvACD7DNoAA/uW6dsETEhgmsEipJZI7NLqBl2whOse8Q==", "dev": true, "requires": { "@rdfjs/types": "*", @@ -13233,7 +13046,8 @@ "rdf-data-factory": "^1.1.0", "readable-stream": "^4.0.0", "relative-to-absolute-iri": "^1.0.0", - "saxes": "^6.0.0" + "saxes": "^6.0.0", + "validate-iri": "^1.0.0" }, "dependencies": { "buffer": { @@ -13423,9 +13237,9 @@ } }, "relative-to-absolute-iri": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/relative-to-absolute-iri/-/relative-to-absolute-iri-1.0.6.tgz", - "integrity": "sha512-Xw5/Zx6iWSCMJUXwXVOjySjH8Xli4hVFL9QQFvkl1qEmFBG94J+QUI9emnoctOCD3285f1jNV+QWV9eDYwIdfQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/relative-to-absolute-iri/-/relative-to-absolute-iri-1.0.7.tgz", + "integrity": "sha512-Xjyl4HmIzg2jzK/Un2gELqbcE8Fxy85A/aLSHE6PE/3+OGsFwmKVA1vRyGaz6vLWSqLDMHA+5rjD/xbibSQN1Q==", "dev": true }, "release-zalgo": { @@ -13665,9 +13479,9 @@ } }, "sparqljson-parse": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sparqljson-parse/-/sparqljson-parse-2.1.0.tgz", - "integrity": "sha512-JKyoDNDR9xrJwR6x6N41UWfER6kfeirE9BRBHdSFSfQF3eF3pxpuUTcJ6QGwHQ4wC/JsQdG/4OGmzkuk1B+8gg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/sparqljson-parse/-/sparqljson-parse-2.1.2.tgz", + "integrity": "sha512-RqPeyy+RYQMeqgEsKPTY+ME5ZNXcgXJzg1v0o+tROiTntS9CwUW8mAY3wsx6seSvW3LVyNDEtsqOxnAokoGXOA==", "dev": true, "requires": { "@rdfjs/types": "*", @@ -13691,9 +13505,9 @@ } }, "sparqlxml-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/sparqlxml-parse/-/sparqlxml-parse-2.0.1.tgz", - "integrity": "sha512-7HZMm0l9a+NQW6mEHzur+KEXA2/gpLYsyiq9yMLKa7Us8yfUJG/+fbHmuBoN7yE0t41SfCXtq4EU/nDjFSGudw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sparqlxml-parse/-/sparqlxml-parse-2.0.2.tgz", + "integrity": "sha512-Iqso0WSTCSuMUYoX2pOEJxteCq9U+7AkOqwlFcvFG1S1aM87xWrp28njQOIiyIrL7Y8CkVXBZG1ec+DhZYUNXA==", "dev": true, "requires": { "@rdfjs/types": "*", @@ -14326,6 +14140,12 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, + "validate-iri": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/validate-iri/-/validate-iri-1.0.1.tgz", + "integrity": "sha512-gLXi7351CoyVVQw8XE5sgpYawRKatxE7kj/xmCxXOZS1kMdtcqC0ILIqLuVEVnAUQSL/evOGG3eQ+8VgbdnstA==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 50cb7554..efde53b7 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "mocha": "^8.0.0", "nyc": "^14.1.1", "pre-commit": "^1.2.2", - "rdf-test-suite": "^1.19.2", + "rdf-test-suite": "^1.20.0", "streamify-string": "^1.0.1", "uglify-js": "^3.14.3" }, @@ -54,7 +54,7 @@ "mocha": "mocha", "lint": "eslint src perf test spec", "prepare": "npm run build", - "spec": "npm run spec-turtle && npm run spec-ntriples && npm run spec-nquads && npm run spec-trig", + "spec": "npm run spec-turtle && npm run spec-ntriples && npm run spec-nquads && npm run spec-trig && npm run spec-rdf-star", "spec-earl": "npm run spec-earl-turtle && npm run spec-earl-ntriples && npm run spec-earl-nquads && npm run spec-earl-trig", "spec-ntriples": "rdf-test-suite spec/parser.js http://w3c.github.io/rdf-tests/ntriples/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/", "spec-nquads": "rdf-test-suite spec/parser.js http://w3c.github.io/rdf-tests/nquads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/", @@ -64,6 +64,12 @@ "spec-earl-nquads": "rdf-test-suite spec/parser.js http://w3c.github.io/rdf-tests/nquads/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-nquads.ttl", "spec-earl-turtle": "rdf-test-suite spec/parser.js http://w3c.github.io/rdf-tests/turtle/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-turtle.ttl", "spec-earl-trig": "rdf-test-suite spec/parser.js http://w3c.github.io/rdf-tests/trig/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/ -o earl -p spec/earl-meta.json > spec/earl-trig.ttl", + "spec-rdf-star": "npm run spec-trig-rdf-star && npm run spec-trig-eval-rdf-star && npm run spec-turtle-rdf-star && npm run spec-turtle-eval-rdf-star", + "spec-trig-rdf-star": "node ../rdf-test-suite.js/bin/Runner.js spec/parser.js https://w3c.github.io/rdf-star/tests/trig/syntax/manifest.jsonld -i '{ \"format\": \"trig-star\" }' -c .rdf-test-suite-cache/", + "spec-trig-eval-rdf-star": "node ../rdf-test-suite.js/bin/Runner.js spec/parser.js https://w3c.github.io/rdf-star/tests/trig/eval/manifest.jsonld -i '{ \"format\": \"trig-star\" }' -c .rdf-test-suite-cache/", + "spec-turtle-rdf-star": "node ../rdf-test-suite.js/bin/Runner.js spec/parser.js https://w3c.github.io/rdf-star/tests/turtle/syntax/manifest.jsonld -i '{ \"format\": \"turtle-star\" }' -c .rdf-test-suite-cache/", + "spec-turtle-eval-rdf-star": "node ../rdf-test-suite.js/bin/Runner.js spec/parser.js https://w3c.github.io/rdf-star/tests/turtle/eval/manifest.jsonld -i '{ \"format\": \"turtle-star\" }' -c .rdf-test-suite-cache/", + "spec-ntriples-rdf-star": "node ../rdf-test-suite.js/bin/Runner.js spec/parser.js https://w3c.github.io/rdf-star/tests/nt/syntax/manifest.jsonld -i '{ \"format\": \"n-quads-star\" }' -c .rdf-test-suite-cache/", "spec-clean": "rm -r .rdf-test-suite-cache/", "docs": "cd src && docco *.js -o ../docs && cd .." }, diff --git a/src/N3Parser.js b/src/N3Parser.js index 4af2750b..d8efe834 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -197,6 +197,10 @@ export default class N3Parser { this._subject = this._blankNode(), null, null); return this._readBlankNodeHead; case '(': + // Lists are not allowed inside quoted triples + if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === '<<') { + return this._error(`Unexpected list inside quoted triple`, token); + } // Start a new list this._saveContext('list', this._graph, this.RDF_NIL, null, null); this._subject = null; @@ -315,6 +319,10 @@ export default class N3Parser { this._subject = this._blankNode()); return this._readBlankNodeHead; case '(': + // Lists are not allowed inside quoted triples + if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === '<<') { + return this._error(`Unexpected list inside quoted triple`, token); + } // Start a new list this._saveContext('list', this._graph, this._subject, this._predicate, this.RDF_NIL); this._subject = null; @@ -362,8 +370,9 @@ export default class N3Parser { if (token.type === ']') { this._subject = null; return this._readBlankNodeTail(token); - } - else { + } else if (this._contextStack.length > 1 && this._contextStack[this._contextStack.length - 2].type === '<<') { + return this._error('Compound blank node expressions not permitted within quoted triples', token) + } else { this._predicate = null; return this._readPredicate(token); } diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 5b236b36..05ac15cb 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1126,6 +1126,36 @@ describe('Parser', () => { shouldParse(' <<<< >> >>.', ['d', 'e', [['a', 'b', 'c'], 'f', 'g']])); + it('should not parse empty list inside quoted triple subject', + shouldNotParse('<< () >> .', + 'Unexpected list inside quoted triple on line 1.' + )); + + it('should not parse non-empty list inside quoted triple subject', + shouldNotParse('<< ( ) >> .', + 'Unexpected list inside quoted triple on line 1.' + )); + + it('should not parse empty list inside quoted triple predicate', + shouldNotParse('<< () >> .', + 'Expected entity but got ( on line 1.' + )); + + it('should not parse non-empty list inside quoted triple predicate', + shouldNotParse('<< ( ) >> .', + 'Expected entity but got ( on line 1.' + )); + + it('should not parse empty list inside quoted triple object', + shouldNotParse('<< () >> .', + 'Unexpected list inside quoted triple on line 1.' + )); + + it('should not parse non-empty list inside quoted triple object', + shouldNotParse('<< ( ) >> .', + 'Unexpected list inside quoted triple on line 1.' + )); + it('should not parse nested RDF* statements that are partially closed', shouldNotParse(' <<<< >> .', 'Expected >> to follow "http://example.org/g" but got . on line 1.' From d27b9203519a34a37aca59aa01c8f14cc8a1bb09 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 23 Nov 2022 17:52:35 +1100 Subject: [PATCH 06/47] feat: turtle-star spec tests are passing --- src/N3Parser.js | 25 +++++++++++++++++++------ test/N3Parser-test.js | 12 +++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index d8efe834..c734818b 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -199,7 +199,7 @@ export default class N3Parser { case '(': // Lists are not allowed inside quoted triples if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === '<<') { - return this._error(`Unexpected list inside quoted triple`, token); + return this._error('Unexpected list inside quoted triple', token); } // Start a new list this._saveContext('list', this._graph, this.RDF_NIL, null, null); @@ -321,7 +321,7 @@ export default class N3Parser { case '(': // Lists are not allowed inside quoted triples if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === '<<') { - return this._error(`Unexpected list inside quoted triple`, token); + return this._error('Unexpected list inside quoted triple', token); } // Start a new list this._saveContext('list', this._graph, this._subject, this._predicate, this.RDF_NIL); @@ -370,9 +370,11 @@ export default class N3Parser { if (token.type === ']') { this._subject = null; return this._readBlankNodeTail(token); - } else if (this._contextStack.length > 1 && this._contextStack[this._contextStack.length - 2].type === '<<') { - return this._error('Compound blank node expressions not permitted within quoted triples', token) - } else { + } + else if (this._contextStack.length > 1 && this._contextStack[this._contextStack.length - 2].type === '<<') { + return this._error('Compound blank node expressions not permitted within quoted triples', token); + } + else { this._predicate = null; return this._readPredicate(token); } @@ -896,10 +898,21 @@ export default class N3Parser { // ### `_readRDFStarTail` reads the end of a nested RDF* triple _readAnnotatedTail(token) { - this._emit(this._subject, this._predicate, this._object, this._graph); // if (this._subject && this._predicate && this._object) { // this._emit(this._subject, this._predicate, this._object, this._graph); // } + if (token.type === '{|') { + this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); + + // Note - we always use the default graph for the quoted triple component + this._subject = this._quad(this._subject, this._predicate, this._object, this.DEFAULTGRAPH); + this._predicate = null; + this._object = null; + return this._readPredicate; + } else { + this._emit(this._subject, this._predicate, this._object, this._graph); + } + if (token.type !== '|}') return this._readPredicate; this._restoreContext('{|', token); diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 05ac15cb..4a841a92 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1130,7 +1130,7 @@ describe('Parser', () => { shouldNotParse('<< () >> .', 'Unexpected list inside quoted triple on line 1.' )); - + it('should not parse non-empty list inside quoted triple subject', shouldNotParse('<< ( ) >> .', 'Unexpected list inside quoted triple on line 1.' @@ -1140,7 +1140,7 @@ describe('Parser', () => { shouldNotParse('<< () >> .', 'Expected entity but got ( on line 1.' )); - + it('should not parse non-empty list inside quoted triple predicate', shouldNotParse('<< ( ) >> .', 'Expected entity but got ( on line 1.' @@ -1150,7 +1150,7 @@ describe('Parser', () => { shouldNotParse('<< () >> .', 'Unexpected list inside quoted triple on line 1.' )); - + it('should not parse non-empty list inside quoted triple object', shouldNotParse('<< ( ) >> .', 'Unexpected list inside quoted triple on line 1.' @@ -1225,6 +1225,12 @@ describe('Parser', () => { ['a', 'b', 'c'], [['a', 'b', 'c'], 'd', 'e'])); + it('should parse an explicit triple with nested reified annotation', + shouldParse(' {| {| |} |} .', + ['a', 'b', 'c'], + [['a', 'b', 'c'], 'd', 'e'], + [[['a', 'b', 'c'], 'd', 'e'], 'f', 'g'])); + const q = ['http://example.com/ns#s', 'http://example.com/ns#p', ['http://example.com/ns#a', 'http://example.com/ns#b', 'http://example.com/ns#c']]; From 2af5e0531024e46f1d1cc2241d8a9503ee2ff9ca Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 23 Nov 2022 18:38:44 +1100 Subject: [PATCH 07/47] chore: fix lint and coverage errors --- src/N3Parser.js | 7 +++---- test/N3Parser-test.js | 21 +++++++++++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index c734818b..c3ec120a 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -372,7 +372,7 @@ export default class N3Parser { return this._readBlankNodeTail(token); } else if (this._contextStack.length > 1 && this._contextStack[this._contextStack.length - 2].type === '<<') { - return this._error('Compound blank node expressions not permitted within quoted triples', token); + return this._error('Compound blank node expressions not permitted within quoted triple', token); } else { this._predicate = null; @@ -639,8 +639,6 @@ export default class N3Parser { if (!this._supportsRDFStar) return this._error('Unexpected RDF* syntax', token); - // TODO: Have error handling behavior here - // TODO: See if we can just emit and then save null context this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); // Note - we always use the default graph for the quoted triple component @@ -909,7 +907,8 @@ export default class N3Parser { this._predicate = null; this._object = null; return this._readPredicate; - } else { + } + else { this._emit(this._subject, this._predicate, this._object, this._graph); } diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 4a841a92..0ceecf19 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1126,6 +1126,21 @@ describe('Parser', () => { shouldParse(' <<<< >> >>.', ['d', 'e', [['a', 'b', 'c'], 'f', 'g']])); + it('should not parse compound blank node inside quoted triple subject', + shouldNotParse('<< [ ] >> .', + 'Compound blank node expressions not permitted within quoted triple on line 1.' + )); + + it('should not parse compound blank node inside quoted triple predicate', + shouldNotParse('<< [ ] >> .', + 'Disallowed blank node as predicate on line 1.' + )); + + it('should not parse compound blank node inside quoted triple object', + shouldNotParse('<< [ ] >> .', + 'Compound blank node expressions not permitted within quoted triple on line 1.' + )); + it('should not parse empty list inside quoted triple subject', shouldNotParse('<< () >> .', 'Unexpected list inside quoted triple on line 1.' @@ -1244,12 +1259,6 @@ describe('Parser', () => { [['a', 'b', 'c'], 'd', 'e'], [['a', 'b', 'c'], 'f', 'g'])); - // TODO: See if this is required by the spec tests - // it('should parse an explicit triple with reified annotation containing punctuation', - // shouldParse(' {| . |} .', - // ['a', 'b', 'c'], - // [['a', 'b', 'c'], 'd', 'e'])); - it('should parse an explicit triple with reified annotation in a named graph', shouldParse(' { {| |} . }', ['a', 'b', 'c', 'G'], From 0ac4c46acbaf93cc74741c419126786b8fc7f065 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 23 Nov 2022 18:41:20 +1100 Subject: [PATCH 08/47] chore: remove commented code --- src/N3Parser.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index c3ec120a..a5155f19 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -896,9 +896,6 @@ export default class N3Parser { // ### `_readRDFStarTail` reads the end of a nested RDF* triple _readAnnotatedTail(token) { - // if (this._subject && this._predicate && this._object) { - // this._emit(this._subject, this._predicate, this._object, this._graph); - // } if (token.type === '{|') { this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); From 0337ed8b9b5e5f95384774b9b79ceb49cdca707a Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 24 Nov 2022 08:51:13 +1100 Subject: [PATCH 09/47] chore: rename RDF* -> RDF-star --- README.md | 10 ++--- src/N3DataFactory.js | 2 +- src/N3Parser.js | 12 +++--- src/N3Writer.js | 2 +- test/N3Lexer-test.js | 32 +++++++-------- test/N3Parser-test.js | 96 +++++++++++++++++++++---------------------- test/N3Store-test.js | 4 +- 7 files changed, 79 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index cf5e554d..6e2704ee 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ It offers: [TriG](https://www.w3.org/TR/trig/), [N-Triples](https://www.w3.org/TR/n-triples/), [N-Quads](https://www.w3.org/TR/n-quads/), - [RDF*](https://blog.liu.se/olafhartig/2019/01/10/position-statement-rdf-star-and-sparql-star/) + [RDF-star](https://blog.liu.se/olafhartig/2019/01/10/position-statement-rdf-star-and-sparql-star/) and [Notation3 (N3)](https://www.w3.org/TeamSubmission/n3/) - [**Writing**](#writing) triples/quads to [Turtle](https://www.w3.org/TR/turtle/), [TriG](https://www.w3.org/TR/trig/), [N-Triples](https://www.w3.org/TR/n-triples/), [N-Quads](https://www.w3.org/TR/n-quads/) - and [RDF*](https://blog.liu.se/olafhartig/2019/01/10/position-statement-rdf-star-and-sparql-star/) + and [RDF-star](https://blog.liu.se/olafhartig/2019/01/10/position-statement-rdf-star-and-sparql-star/) - [**Storage**](#storing) of triples/quads in memory Parsing and writing is: @@ -358,16 +358,16 @@ The N3.js parser and writer is fully compatible with the following W3C specifica In addition, the N3.js parser also supports [Notation3 (N3)](https://www.w3.org/TeamSubmission/n3/) (no official specification yet). -The N3.js parser and writer are also fully compatible with the RDF* variants +The N3.js parser and writer are also fully compatible with the RDF-star variants of the W3C specifications. The default mode is permissive -and allows a mixture of different syntaxes, including RDF*. +and allows a mixture of different syntaxes, including RDF-star. Pass a `format` option to the constructor with the name or MIME type of a format for strict, fault-intolerant behavior. If a format string contains `star` or `*` (e.g., `turtlestar` or `TriG*`), -RDF* support for that format will be enabled. +RDF-star support for that format will be enabled. ### Interface specifications The N3.js submodules are compatible with the following [RDF.js](http://rdf.js.org) interfaces: diff --git a/src/N3DataFactory.js b/src/N3DataFactory.js index dec7aaef..f37bc0c5 100644 --- a/src/N3DataFactory.js +++ b/src/N3DataFactory.js @@ -247,7 +247,7 @@ export function termToId(term) { term.language ? `@${term.language}` : (term.datatype && term.datatype.value !== xsd.string ? `^^${term.datatype.value}` : '')}`; case 'Quad': - // To identify RDF* quad components, we escape quotes by doubling them. + // To identify RDF-star quad components, we escape quotes by doubling them. // This avoids the overhead of backslash parsing of Turtle-like syntaxes. return `<<${ escapeQuotes(termToId(term.subject)) diff --git a/src/N3Parser.js b/src/N3Parser.js index a5155f19..22cfb331 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -243,7 +243,7 @@ export default class N3Parser { break; case '<<': if (!this._supportsRDFStar) - return this._error('Unexpected RDF* syntax', token); + return this._error('Unexpected RDF-star syntax', token); this._saveContext('<<', this._graph, null, null, null); this._graph = null; return this._readSubject; @@ -336,7 +336,7 @@ export default class N3Parser { return this._readSubject; case '<<': if (!this._supportsRDFStar) - return this._error('Unexpected RDF* syntax', token); + return this._error('Unexpected RDF-star syntax', token); this._saveContext('<<', this._graph, this._subject, this._predicate, null); this._graph = null; return this._readSubject; @@ -486,7 +486,7 @@ export default class N3Parser { return this._readSubject; case '<<': if (!this._supportsRDFStar) - return this._error('Unexpected RDF* syntax', token); + return this._error('Unexpected RDF-star syntax', token); this._saveContext('<<', this._graph, this._subject, null, null); return this._readSubject; @@ -637,7 +637,7 @@ export default class N3Parser { break; case '{|': if (!this._supportsRDFStar) - return this._error('Unexpected RDF* syntax', token); + return this._error('Unexpected RDF-star syntax', token); this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); @@ -867,7 +867,7 @@ export default class N3Parser { return this._readPath; } - // ### `_readRDFStarTail` reads the end of a nested RDF* triple + // ### `_readRDFStarTail` reads the end of a nested RDF-star triple _readRDFStarTail(token) { if (token.type !== '>>') return this._error(`Expected >> to follow "${this._object.id}" but got ${token.type}`, token); @@ -894,7 +894,7 @@ export default class N3Parser { } } - // ### `_readRDFStarTail` reads the end of a nested RDF* triple + // ### `_readRDFStarTail` reads the end of a nested RDF-star triple _readAnnotatedTail(token) { if (token.type === '{|') { this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); diff --git a/src/N3Writer.js b/src/N3Writer.js index abc3852f..8fc4f282 100644 --- a/src/N3Writer.js +++ b/src/N3Writer.js @@ -225,7 +225,7 @@ export default class N3Writer { } } - // ### `_encodeQuad` encodes an RDF* quad + // ### `_encodeQuad` encodes an RDF-star quad _encodeQuad({ subject, predicate, object, graph }) { return `<<${ this._encodeSubject(subject)} ${ diff --git a/test/N3Lexer-test.js b/test/N3Lexer-test.js index 8034f596..13b6730b 100644 --- a/test/N3Lexer-test.js +++ b/test/N3Lexer-test.js @@ -855,7 +855,7 @@ describe('Lexer', () => { { type: '>>', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with IRIs', + it('should tokenize an RDF-star statement with IRIs', shouldTokenize('<< \n\t \n\t>> .', { type: '<<', line: 1 }, { type: 'IRI', value: 'http://ex.org/?bla#foo', line: 1 }, @@ -865,7 +865,7 @@ describe('Lexer', () => { { type: '.', line: 3 }, { type: 'eof', line: 3 })); - it('should not tokenize a wrongly closed RDF* statement with IRIs', + it('should not tokenize a wrongly closed RDF-star statement with IRIs', shouldNotTokenize('<< \n\t \n\t> .', 'Unexpected ">" on line 3.')); @@ -915,7 +915,7 @@ describe('Lexer', () => { shouldNotTokenize(' {| [ "f" ]; |', 'Unexpected "|" on line 1.')); - it('should tokenize a split RDF* statement with IRIs', + it('should tokenize a split RDF-star statement with IRIs', shouldTokenize(streamOf('<', '< \n\t \n\t>> .'), { type: '<<', line: 1 }, { type: 'IRI', value: 'http://ex.org/?bla#foo', line: 1 }, @@ -925,7 +925,7 @@ describe('Lexer', () => { { type: '.', line: 3 }, { type: 'eof', line: 3 })); - it('should tokenize an RDF* statement with string literals', + it('should tokenize an RDF-star statement with string literals', shouldTokenize('<<"string"@en "string"@nl-be "string"@EN>> .', { type: '<<', line: 1 }, { type: 'literal', value: 'string', line: 1 }, @@ -938,7 +938,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with integers', + it('should tokenize an RDF-star statement with integers', shouldTokenize('<<1 2 3>>.', { type: '<<', line: 1 }, { type: 'literal', value: '1', prefix: 'http://www.w3.org/2001/XMLSchema#integer', line: 1 }, @@ -948,7 +948,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with decimals', + it('should tokenize an RDF-star statement with decimals', shouldTokenize('<<1.2 3.4 5.6>>.', { type: '<<', line: 1 }, { type: 'literal', value: '1.2', prefix: 'http://www.w3.org/2001/XMLSchema#decimal', line: 1 }, @@ -958,7 +958,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with booleans', + it('should tokenize an RDF-star statement with booleans', shouldTokenize('<>.', { type: '<<', line: 1 }, { type: 'literal', value: 'true', prefix: 'http://www.w3.org/2001/XMLSchema#boolean', line: 1 }, @@ -975,7 +975,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with prefixed names', + it('should tokenize an RDF-star statement with prefixed names', shouldTokenize('<> .', { type: '<<', line: 1 }, { type: 'prefixed', prefix: 'a', value: 'a', line: 1 }, @@ -985,7 +985,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with blank nodes', + it('should tokenize an RDF-star statement with blank nodes', shouldTokenize('<<_:a _:b _:c>> .', { type: '<<', line: 1 }, { type: 'blank', prefix: '_', value: 'a', line: 1 }, @@ -995,7 +995,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with variables', + it('should tokenize an RDF-star statement with variables', shouldTokenize('<> .', { type: '<<', line: 1 }, { type: 'var', value: '?a', line: 1 }, @@ -1005,7 +1005,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with mixed types', + it('should tokenize an RDF-star statement with mixed types', shouldTokenize('<< "string"@nl-be c:c>> .', { type: '<<', line: 1 }, { type: 'IRI', value: 'http://ex.org/?bla#foo', line: 1 }, @@ -1016,7 +1016,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with mixed types', + it('should tokenize an RDF-star statement with mixed types', shouldTokenize('<<_:a a:a "string"@EN>> .', { type: '<<', line: 1 }, { type: 'blank', prefix: '_', value: 'a', line: 1 }, @@ -1027,7 +1027,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize an RDF* statement with mixed types', + it('should tokenize an RDF-star statement with mixed types', shouldTokenize('<<"literal"@AU _:a>> .', { type: '<<', line: 1 }, { type: 'literal', value: 'literal', line: 1 }, @@ -1038,7 +1038,7 @@ describe('Lexer', () => { { type: '.', line: 1 }, { type: 'eof', line: 1 })); - it('should tokenize RDF* statements with shared subjects', + it('should tokenize RDF-star statements with shared subjects', shouldTokenize('<< ;\n >>.', { type: '<<', line: 1 }, { type: 'IRI', value: 'a', line: 1 }, @@ -1051,7 +1051,7 @@ describe('Lexer', () => { { type: '.', line: 2 }, { type: 'eof', line: 2 })); - it('should tokenize RDF* statements with shared subjects and predicates', + it('should tokenize RDF-star statements with shared subjects and predicates', shouldTokenize('<< ,\n>>.', { type: '<<', line: 1 }, { type: 'IRI', value: 'a', line: 1 }, @@ -1063,7 +1063,7 @@ describe('Lexer', () => { { type: '.', line: 2 }, { type: 'eof', line: 2 })); - it('should tokenize an RDF* statement with shared subjects and predicates and prefixed names', + it('should tokenize an RDF-star statement with shared subjects and predicates and prefixed names', shouldTokenize('<> .', { type: '<<', line: 1 }, { type: 'prefixed', prefix: 'a', value: 'a', line: 1 }, diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 0ceecf19..40012632 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1084,32 +1084,32 @@ describe('Parser', () => { .should.throw('Expected entity but got eof on line 1'); }); - it('should parse an RDF* triple with a triple with iris as subject correctly', () => { + it('should parse an RDF-star triple with a triple with iris as subject correctly', () => { shouldParse('<< >> .', [['a', 'b', 'c'], 'b', 'c']); }); - it('should not parse an RDF* triple with a triple as predicate', + it('should not parse an RDF-star triple with a triple as predicate', shouldNotParse(' << >> ', 'Expected entity but got << on line 1.')); - it('should parse an RDF* triple with a triple with blanknodes as subject correctly', + it('should parse an RDF-star triple with a triple with blanknodes as subject correctly', shouldParse('<<_:a _:c>> .', [['_:b0_a', 'b', '_:b0_c'], 'b', 'c'])); - it('should parse an RDF* triple with a triple with blanknodes and literals as subject correctly', + it('should parse an RDF-star triple with a triple with blanknodes and literals as subject correctly', shouldParse('<<_:a "c"^^>> .', [['_:b0_a', 'b', '"c"^^http://example.org/d'], 'b', 'c'])); - it('should parse an RDF* triple with a triple as object correctly', + it('should parse an RDF-star triple with a triple as object correctly', shouldParse(' << >>.', ['a', 'b', ['a', 'b', 'c']])); - it('should parse an RDF* triple with a triple as object correctly', + it('should parse an RDF-star triple with a triple as object correctly', shouldParse(' <<_:a _:c>>.', ['a', 'b', ['_:b0_a', 'b', '_:b0_c']])); - it('should parse an RDF* triple with a triple as object correctly', + it('should parse an RDF-star triple with a triple as object correctly', shouldParse(' <<_:a "c"^^>>.', ['a', 'b', ['_:b0_a', 'b', '"c"^^http://example.org/d']])); @@ -1171,42 +1171,42 @@ describe('Parser', () => { 'Unexpected list inside quoted triple on line 1.' )); - it('should not parse nested RDF* statements that are partially closed', + it('should not parse nested RDF-star statements that are partially closed', shouldNotParse(' <<<< >> .', 'Expected >> to follow "http://example.org/g" but got . on line 1.' )); - it('should not parse partially closed nested RDF* statements', + it('should not parse partially closed nested RDF-star statements', shouldNotParse(' <<<< >>.', 'Expected >> to follow "http://example.org/c" but got IRI on line 1.' )); - it('should not parse nested RDF* statements with too many closing tags', + it('should not parse nested RDF-star statements with too many closing tags', shouldNotParse(' <<<< >>>> >>.', 'Expected entity but got >> on line 1.' )); - it('should not parse nested RDF* statements with too many closing tags', + it('should not parse nested RDF-star statements with too many closing tags', shouldNotParse(' <<<< >> >>>>.', 'Expected entity but got >> on line 1.' )); - it('should not parse RDF* statements with too many closing tags', + it('should not parse RDF-star statements with too many closing tags', shouldNotParse(' >>.', 'Expected entity but got >> on line 1.' )); - it('should not parse incomplete RDF* statements', + it('should not parse incomplete RDF-star statements', shouldNotParse(' << >>.', 'Expected entity but got >> on line 1.' )); - it('should not parse incomplete RDF* statements', + it('should not parse incomplete RDF-star statements', shouldNotParse('<< >> .', 'Expected entity but got >> on line 1.' )); - it('should not parse incorrectly nested RDF* statements', + it('should not parse incorrectly nested RDF-star statements', shouldNotParse('>> <<', 'Expected entity but got >> on line 1.' )); @@ -1216,16 +1216,16 @@ describe('Parser', () => { 'Unexpected . on line 1.' )); - it('should not parse a malformed RDF* quad', + it('should not parse a malformed RDF-star quad', shouldNotParse('<< >> .', 'Expected >> to follow "http://example.org/c" but got IRI on line 1.')); - it('should parse statements with a shared RDF* subject', + it('should parse statements with a shared RDF-star subject', shouldParse('<< >> ;\n .', [['a', 'b', 'c'], 'b', 'c'], [['a', 'b', 'c'], 'd', 'c'])); - it('should parse statements with a shared RDF* subject', + it('should parse statements with a shared RDF-star subject', shouldParse('<< >> ;\n << >>.', [['a', 'b', 'c'], 'b', 'c'], [['a', 'b', 'c'], 'd', ['a', 'b', 'c']])); @@ -1469,31 +1469,31 @@ describe('Parser', () => { shouldNotParse(parser, '1 .', 'Unexpected literal on line 1.')); - it('should not parse RDF* in the subject position', + it('should not parse RDF-star in the subject position', shouldNotParse(parser, '<< >> .', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); it('should not parse annotated statement', shouldNotParse(parser, ' {| |} .', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); - it('should not parse RDF* in the object position', + it('should not parse RDF-star in the object position', shouldNotParse(parser, ' < >>.', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); - it('should not parse RDF* in the object list', + it('should not parse RDF-star in the object list', shouldNotParse(parser, ' ( < >> ).', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); - it('should not parse RDF* in the subject list', + it('should not parse RDF-star in the subject list', shouldNotParse(parser, '( < >> ) .', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); }); describe('A Parser instance for the TurtleStar format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TurtleStar' }); } - it('should parse RDF*', + it('should parse RDF-star', shouldParse(parser, '<< >> .', [['a', 'b', 'c'], 'b', 'c'])); @@ -1542,19 +1542,19 @@ describe('Parser', () => { it('should not parse @forAll', shouldNotParse(parser, '@forAll .', 'Unexpected "@forAll" on line 1.')); - it('should not parse RDF* in the subject position', + it('should not parse RDF-star in the subject position', shouldNotParse(parser, '<< >> .', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); - it('should not parse RDF* in the object position', + it('should not parse RDF-star in the object position', shouldNotParse(parser, ' << >>.', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); }); describe('A Parser instance for the TriGStar format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TriGStar' }); } - it('should parse RDF*', + it('should parse RDF-star', shouldParse(parser, '<< >> .', [['a', 'b', 'c'], 'a', 'b'])); @@ -1617,19 +1617,19 @@ describe('Parser', () => { it('should not parse @forAll', shouldNotParse(parser, '@forAll .', 'Unexpected "@forAll" on line 1.')); - it('should not parse RDF* in the subject position', + it('should not parse RDF-star in the subject position', shouldNotParse(parser, '<< >> .', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); - it('should not parse RDF* in the object position', + it('should not parse RDF-star in the object position', shouldNotParse(parser, ' << >>.', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); }); describe('A Parser instance for the N-TriplesStar format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-TriplesStar' }); } - it('should parse RDF*', + it('should parse RDF-star', shouldParse(parser, '<<_:a _:c>> _:b .', [['_:b0_a', 'b', '_:b0_c'], 'a', '_:b0_b'])); @@ -1676,19 +1676,19 @@ describe('Parser', () => { it('should not parse @forAll', shouldNotParse(parser, '@forAll .', 'Unexpected "@forAll" on line 1.')); - it('should not parse RDF* in the subject position', + it('should not parse RDF-star in the subject position', shouldNotParse(parser, '<< >> .', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); - it('should not parse RDF* in the object position', + it('should not parse RDF-star in the object position', shouldNotParse(parser, '_:a << >>.', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); }); describe('A Parser instance for the N-QuadsStar format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-QuadsStar' }); } - it('should parse RDF*', + it('should parse RDF-star', shouldParse(parser, '<<_:a _:c>> _:c .', [['_:b0_a', 'b', '_:b0_c'], 'a', '_:b0_c'])); }); @@ -2031,19 +2031,19 @@ describe('Parser', () => { ['"bonjour"@fr', 'sameAs', '"hello"@en', '_:b0'] )); - it('should not parse RDF* in the subject position', + it('should not parse RDF-star in the subject position', shouldNotParse(parser, '<< >> .', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); - it('should not parse RDF* in the object position', + it('should not parse RDF-star in the object position', shouldNotParse(parser, ' << >>.', - 'Unexpected RDF* syntax on line 1.')); + 'Unexpected RDF-star syntax on line 1.')); }); describe('A Parser instance for the N3Star format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3Star' }); } - it('should parse RDF*', + it('should parse RDF-star', shouldParse(parser, '<< >> .', [['a', 'b', 'c'], 'a', 'b'])); diff --git a/test/N3Store-test.js b/test/N3Store-test.js index b7ca2dd9..10b1f29f 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -275,7 +275,7 @@ describe('Store', () => { }); }); - describe('removing matching quads for RDF*', () => { + describe('removing matching quads for RDF-star', () => { let store; beforeEach(() => { store = new Store([ @@ -295,7 +295,7 @@ describe('Store', () => { store.size.should.eql(5); }); - it('should match RDF* and normal quads at the same time', done => { + it('should match RDF-star and normal quads at the same time', done => { const stream = store.removeMatches(null, 'p1', 'o2'); stream.on('end', () => { store.size.should.eql(3); From 99d6b72b9f9466e7f4c87bb292db9820baa60493 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 24 Nov 2022 11:03:58 +1100 Subject: [PATCH 10/47] chore: update RDF-star reference in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e2704ee..60886684 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ It offers: [TriG](https://www.w3.org/TR/trig/), [N-Triples](https://www.w3.org/TR/n-triples/), [N-Quads](https://www.w3.org/TR/n-quads/), - [RDF-star](https://blog.liu.se/olafhartig/2019/01/10/position-statement-rdf-star-and-sparql-star/) + [RDF-star](https://www.w3.org/2021/12/rdf-star.html) and [Notation3 (N3)](https://www.w3.org/TeamSubmission/n3/) - [**Writing**](#writing) triples/quads to [Turtle](https://www.w3.org/TR/turtle/), [TriG](https://www.w3.org/TR/trig/), [N-Triples](https://www.w3.org/TR/n-triples/), [N-Quads](https://www.w3.org/TR/n-quads/) - and [RDF-star](https://blog.liu.se/olafhartig/2019/01/10/position-statement-rdf-star-and-sparql-star/) + and [RDF-star](https://www.w3.org/2021/12/rdf-star.html) - [**Storage**](#storing) of triples/quads in memory Parsing and writing is: From d258f2212fc2543e602455512d4a6b66e15931f6 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 24 Nov 2022 17:19:14 +1100 Subject: [PATCH 11/47] chore: fix round trip on deeply nested rdfstar triples --- src/N3DataFactory.js | 45 ++++++++++++----------- test/Term-test.js | 87 ++++++++++++++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/src/N3DataFactory.js b/src/N3DataFactory.js index f37bc0c5..786a774b 100644 --- a/src/N3DataFactory.js +++ b/src/N3DataFactory.js @@ -10,7 +10,6 @@ let DEFAULTGRAPH; let _blankNodeCounter = 0; const escapedLiteral = /^"(.*".*)(?="[^"]*$)/; -const quadId = /^<<("(?:""|[^"])*"[^ ]*|[^ ]+) ("(?:""|[^"])*"[^ ]*|[^ ]+) ("(?:""|[^"])*"[^ ]*|[^ ]+) ?("(?:""|[^"])*"[^ ]*|[^ ]+)?>>$/; // ## DataFactory singleton const DataFactory = { @@ -188,9 +187,8 @@ export class DefaultGraph extends Term { // ## DefaultGraph singleton DEFAULTGRAPH = new DefaultGraph(); - // ### Constructs a term from the given internal string ID -export function termFromId(id, factory) { +export function termFromId(id, factory, nested) { factory = factory || DataFactory; // Falsy value or empty string indicate the default graph @@ -215,21 +213,24 @@ export function termFromId(id, factory) { return factory.literal(id.substr(1, endPos - 1), id[endPos + 1] === '@' ? id.substr(endPos + 2) : factory.namedNode(id.substr(endPos + 3))); - case '<': - const components = quadId.exec(id); - return factory.quad( - termFromId(unescapeQuotes(components[1]), factory), - termFromId(unescapeQuotes(components[2]), factory), - termFromId(unescapeQuotes(components[3]), factory), - components[4] && termFromId(unescapeQuotes(components[4]), factory) - ); + case '[': + id = JSON.parse(id); + break; default: - return factory.namedNode(id); + if (!nested || !Array.isArray(id)) { + return factory.namedNode(id); + } } + return factory.quad( + termFromId(id[0], factory, true), + termFromId(id[1], factory, true), + termFromId(id[2], factory, true), + id[3] && termFromId(id[3], factory, true) + ); } // ### Constructs an internal string ID from the given term or ID string -export function termToId(term) { +export function termToId(term, nested) { if (typeof term === 'string') return term; if (term instanceof Term && term.termType !== 'Quad') @@ -249,15 +250,15 @@ export function termToId(term) { case 'Quad': // To identify RDF-star quad components, we escape quotes by doubling them. // This avoids the overhead of backslash parsing of Turtle-like syntaxes. - return `<<${ - escapeQuotes(termToId(term.subject)) - } ${ - escapeQuotes(termToId(term.predicate)) - } ${ - escapeQuotes(termToId(term.object)) - }${ - (isDefaultGraph(term.graph)) ? '' : ` ${termToId(term.graph)}` - }>>`; + const res = [ + termToId(term.subject, true), + termToId(term.predicate, true), + termToId(term.object, true), + ]; + if (!isDefaultGraph(term.graph)) { + res.push(termToId(term.graph, true)); + } + return nested ? res : JSON.stringify(res); default: throw new Error(`Unexpected termType: ${term.termType}`); } } diff --git a/test/Term-test.js b/test/Term-test.js index d052216d..6798a81b 100644 --- a/test/Term-test.js +++ b/test/Term-test.js @@ -81,16 +81,17 @@ describe('Term', () => { }); it('should create a Quad with the default graph if the id doesnt specify the graph', () => { - termFromId('<>').should.deep.equal(new Quad( + const q = new Quad( new NamedNode('http://ex.org/a'), new NamedNode('http://ex.org/b'), new Literal('"abc"@en-us'), new DefaultGraph() - )); + ); + expect(q.equals(termFromId(termToId(q)))).equals(true); }); it('should create a Quad with the correct graph if the id specifies a graph', () => { - const id = '<>'; + const id = '["http://ex.org/a", "http://ex.org/b", "\\"abc\\"@en-us", "http://ex.org/d"]'; termFromId(id).should.deep.equal(new Quad( new NamedNode('http://ex.org/a'), new NamedNode('http://ex.org/b'), @@ -100,7 +101,7 @@ describe('Term', () => { }); it('should create a Quad correctly', () => { - const id = '<>'; + const id = '["http://ex.org/a", "http://ex.org/b", "http://ex.org/c"]'; termFromId(id).should.deep.equal(new Quad( new NamedNode('http://ex.org/a'), new NamedNode('http://ex.org/b'), @@ -110,7 +111,7 @@ describe('Term', () => { }); it('should create a Quad correctly', () => { - const id = '<<_:n3-123 ?var-a ?var-b _:n3-000>>'; + const id = '["_:n3-123", "?var-a", "?var-b", "_:n3-000"]'; termFromId(id).should.deep.equal(new Quad( new BlankNode('n3-123'), new Variable('var-a'), @@ -120,7 +121,7 @@ describe('Term', () => { }); it('should create a Quad correctly', () => { - const id = '<>'; + const id = '["?var-a", "?var-b", "\\"abc\\"@en-us", "?var-d"]'; termFromId(id).should.deep.equal(new Quad( new Variable('var-a'), new Variable('var-b'), @@ -130,7 +131,7 @@ describe('Term', () => { }); it('should create a Quad correctly', () => { - const id = '<<_:n3-000 ?var-b _:n3-123 http://ex.org/d>>'; + const id = '["_:n3-000", "?var-b", "_:n3-123", "http://ex.org/d"]'; termFromId(id).should.deep.equal(new Quad( new BlankNode('n3-000'), new Variable('var-b'), @@ -140,7 +141,7 @@ describe('Term', () => { }); it('should create a Quad correctly from literal containing escaped quotes', () => { - const id = '<<_:n3-000 ?var-b "Hello ""W""orl""d!"@en-us http://ex.org/d>>'; + const id = '["_:n3-000", "?var-b", "\\"Hello \\"W\\"orl\\"d!\\"@en-us", "http://ex.org/d"]'; termFromId(id).should.deep.equal(new Quad( new BlankNode('n3-000'), new Variable('var-b'), @@ -150,13 +151,14 @@ describe('Term', () => { }); it('should create a Quad correctly from literal containing escaped quotes', () => { - const id = '<<"Hello ""W""orl""d!"@en-us http://ex.org/b http://ex.org/c>>'; - termFromId(id).should.deep.equal(new Quad( + const q = new Quad( new Literal('"Hello "W"orl"d!"@en-us'), new NamedNode('http://ex.org/b'), new NamedNode('http://ex.org/c'), new DefaultGraph() - )); + ); + + termFromId(termToId(q)).should.deep.equal(q); }); describe('with a custom factory', () => { @@ -283,7 +285,7 @@ describe('Term', () => { new NamedNode('http://ex.org/b'), new Literal('"abc"@en-us'), new DefaultGraph() - )).should.equal('<>'); + )).should.equal('["http://ex.org/a","http://ex.org/b","\\"abc\\"@en-us"]'); }); it('should create an id from a Quad', () => { @@ -292,7 +294,7 @@ describe('Term', () => { new NamedNode('http://ex.org/b'), new Literal('"abc"@en-us'), new NamedNode('http://ex.org/d') - )).should.equal('<>'); + )).should.equal('["http://ex.org/a","http://ex.org/b","\\"abc\\"@en-us","http://ex.org/d"]'); }); it('should create an id from a manually created Quad', () => { @@ -303,7 +305,7 @@ describe('Term', () => { graph: new NamedNode('http://ex.org/d'), termType: 'Quad', value: '', - }).should.equal('<>'); + }).should.equal('["http://ex.org/a","http://ex.org/b","\\"abc\\"@en-us","http://ex.org/d"]'); }); it('should create an id with escaped literals from a Quad', () => { @@ -312,7 +314,7 @@ describe('Term', () => { new Variable('var-b'), new Literal('"Hello "W"orl"d!"@en-us'), new NamedNode('http://ex.org/d') - )).should.equal('<<_:n3-000 ?var-b "Hello ""W""orl""d!"@en-us http://ex.org/d>>'); + )).should.equal('["_:n3-000","?var-b","\\"Hello \\"W\\"orl\\"d!\\"@en-us","http://ex.org/d"]'); }); it('should create an id without graph from a Quad with default graph and Quad as subject', () => { @@ -326,7 +328,7 @@ describe('Term', () => { new NamedNode('http://ex.org/b'), new Literal('"abc"@en-us'), new DefaultGraph() - )).should.equal('<<<<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/b "abc"@en-us>>'); + )).should.equal('[["_:n3-000","?var-b","\\"abc\\"@en-us","http://ex.org/d"],"http://ex.org/b","\\"abc\\"@en-us"]'); }); it('should create an id without graph from a Quad with default graph and Quad as object', () => { @@ -340,7 +342,7 @@ describe('Term', () => { new NamedNode('http://ex.org/d') ), new DefaultGraph() - )).should.equal('<<"abc"@en-us http://ex.org/b <<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>>>>'); + )).should.equal('["\\"abc\\"@en-us","http://ex.org/b",["_:n3-000","?var-b","\\"abc\\"@en-us","http://ex.org/d"]]'); }); it('should create an id without graph from a Quad with default graph and Quad as subject and object', () => { @@ -359,7 +361,7 @@ describe('Term', () => { new NamedNode('http://ex.org/d') ), new DefaultGraph() - )).should.equal('<<<<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/b <<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>>>>'); + )).should.equal('[["_:n3-000","?var-b","\\"abc\\"@en-us","http://ex.org/d"],"http://ex.org/b",["_:n3-000","?var-b","\\"abc\\"@en-us","http://ex.org/d"]]'); }); it('should create an id without graph from a Quad with Quad as subject', () => { @@ -373,7 +375,7 @@ describe('Term', () => { new NamedNode('http://ex.org/b'), new Literal('"abc"@en-us'), new NamedNode('http://ex.org/d') - )).should.equal('<<<<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/b "abc"@en-us http://ex.org/d>>'); + )).should.equal('[["_:n3-000","?var-b","\\"abc\\"@en-us","http://ex.org/d"],"http://ex.org/b","\\"abc\\"@en-us","http://ex.org/d"]'); }); it('should create an id without graph from a Quad with Quad as object', () => { @@ -387,7 +389,7 @@ describe('Term', () => { new NamedNode('http://ex.org/d') ), new NamedNode('http://ex.org/d') - )).should.equal('<<"abc"@en-us http://ex.org/b <<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/d>>'); + )).should.equal('["\\"abc\\"@en-us","http://ex.org/b",["_:n3-000","?var-b","\\"abc\\"@en-us","http://ex.org/d"],"http://ex.org/d"]'); }); it('should create an id from a Quad with Quad as subject and object', () => { @@ -406,7 +408,7 @@ describe('Term', () => { new NamedNode('http://ex.org/d') ), new NamedNode('http://ex.org/d') - )).should.equal('<<<<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/b <<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/d>>'); + )).should.equal('[["_:n3-000","?var-b","\\"abc\\"@en-us","http://ex.org/d"],"http://ex.org/b",["_:n3-000","?var-b","\\"abc\\"@en-us","http://ex.org/d"],"http://ex.org/d"]'); }); it('should escape literals in nested Quads', () => { @@ -425,11 +427,46 @@ describe('Term', () => { new NamedNode('http://ex.org/d') ), new DefaultGraph() - )).should.equal('<<<<_:n3-000 ?var-b "Hello ""W""orl""d!"@en-us http://ex.org/d>> http://ex.org/b <<_:n3-000 ?var-b "Hello ""W""orl""d!"@en-us http://ex.org/d>>>>'); + )).should.equal('[["_:n3-000","?var-b","\\"Hello \\"W\\"orl\\"d!\\"@en-us","http://ex.org/d"],"http://ex.org/b",["_:n3-000","?var-b","\\"Hello \\"W\\"orl\\"d!\\"@en-us","http://ex.org/d"]]'); + }); + + + it('should termToId <-> termFromId should roundtrip on deeply nested quad', () => { + const q = new Quad( + new Quad( + new NamedNode('http://example.org/s1'), + new NamedNode('http://example.org/p1'), + new NamedNode('http://example.org/o1') + ), + new NamedNode('http://example.org/p1'), + new Quad( + new Quad( + new Literal('"s1"'), + new NamedNode('http://example.org/p1'), + new BlankNode('o1') + ), + new NamedNode('p2'), + new Quad( + new Quad( + new Literal('"s1"'), + new NamedNode('http://example.org/p1'), + new BlankNode('o1') + ), + new NamedNode('http://example.org/p1'), + new NamedNode('http://example.org/o1') + ) + ) + ); + + expect(q).deep.equals(termFromId(termToId(q))); + expect(termFromId(termToId(q))).deep.equals(q); + expect(q.equals(termFromId(termToId(q)))).equal(true); + expect(termFromId(termToId(q)).equals(q)).equal(true); + expect(termFromId(termToId(q)).equals(termFromId(termToId(q)))).equal(true); }); it('should correctly handle deeply nested quads', () => { - termToId(new Quad( + const q = new Quad( new Quad( new Quad( new Quad( @@ -474,7 +511,9 @@ describe('Term', () => { new NamedNode('http://ex.org/d') ), new NamedNode('http://ex.org/d') - )).should.equal('<<<<<<<<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> ?var-b <<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/d>> ?var-b <<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/d>> http://ex.org/b <<<<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> ?var-b <<_:n3-000 ?var-b "abc"@en-us http://ex.org/d>> http://ex.org/d>> http://ex.org/d>>'); + ); + + expect(q.equals(termFromId(termToId(q)))).equal(true); }); it('should throw on an unknown type', () => { From 56cc184f78761db27751aa4a8489239e0c287666 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 27 Nov 2022 00:38:09 +1100 Subject: [PATCH 12/47] chore: add tests from https://github.com/rdfjs/N3.js/pull/303 --- test/N3Store-test.js | 26 ++++++++++++++++++++++ test/Term-test.js | 53 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 10b1f29f..424880e2 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -1653,6 +1653,32 @@ describe('Store', () => { [...store.match(null, null, null, null)].should.have.length(3); }); + it('should include added elements in match if iteration has not yet started (deeply nested)', () => { + const m = store.match(null, null, null, null); + store.add(new Quad( + new NamedNode('s1'), + new NamedNode('p1'), + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o3')) + ) + ); + store.add(new Quad( + new NamedNode('s1'), + new NamedNode('p1'), + new Quad( + new NamedNode('s1'), + new NamedNode('p1'), + new Quad( + new NamedNode('s1'), + new NamedNode('p1'), + new NamedNode('o3') + ) + ) + ) + ); + [...m].should.have.length(4); + [...store.match(null, null, null, null)].should.have.length(4); + }); + it('should still include results of original match after iterating while adding new data', () => { const m = store.match(null, null, null, null)[Symbol.iterator](); m.next().value.should.deep.equal(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1'))); diff --git a/test/Term-test.js b/test/Term-test.js index 6798a81b..750bc3e5 100644 --- a/test/Term-test.js +++ b/test/Term-test.js @@ -15,6 +15,54 @@ import { unescapeQuotes, } from '../src/N3DataFactory'; + +const DEEP_TRIPLE = new Quad( + new Quad( + new Quad( + new Quad( + new BlankNode('n3-000'), + new Variable('var-b'), + new Literal('"abc"@en-us'), + new NamedNode('http://ex.org/d') + ), + new Variable('var-b'), + new Quad( + new BlankNode('n3-000'), + new Variable('var-b'), + new Literal('"abc"@en-us'), + new NamedNode('http://ex.org/d') + ), + new NamedNode('http://ex.org/d') + ), + new Variable('var-b'), + new Quad( + new BlankNode('n3-000'), + new Variable('var-b'), + new Literal('"abc"@en-us'), + new NamedNode('http://ex.org/d') + ), + new NamedNode('http://ex.org/d') + ), + new NamedNode('http://ex.org/b'), + new Quad( + new Quad( + new BlankNode('n3-000'), + new Variable('var-b'), + new Literal('"abc"@en-us'), + new NamedNode('http://ex.org/d') + ), + new Variable('var-b'), + new Quad( + new BlankNode('n3-000'), + new Variable('var-b'), + new Literal('"abc"@en-us'), + new NamedNode('http://ex.org/d') + ), + new NamedNode('http://ex.org/d') + ), + new NamedNode('http://ex.org/d') +); + describe('Term', () => { describe('The Term module', () => { it('should be a function', () => { @@ -161,6 +209,11 @@ describe('Term', () => { termFromId(termToId(q)).should.deep.equal(q); }); + it('should correctly handle deeply nested quads', () => { + DEEP_TRIPLE.equals(termFromId(termToId(DEEP_TRIPLE))).should.equal(true); + termFromId(termToId(DEEP_TRIPLE)).equals(DEEP_TRIPLE).should.equal(true); + }); + describe('with a custom factory', () => { const factory = { defaultGraph: function () { return ['d']; }, From 2f0f57d6708eefa9426df5c62515ca28270a81e7 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 4 Jan 2023 11:03:03 +1100 Subject: [PATCH 13/47] chore: describe quoted triple predicate parsing Co-authored-by: Ruben Verborgh --- src/N3Parser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/N3Parser.js b/src/N3Parser.js index 22cfb331..97b36b37 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -909,6 +909,7 @@ export default class N3Parser { this._emit(this._subject, this._predicate, this._object, this._graph); } + // If the quoted triple is not finished, the next token must be a predicate if (token.type !== '|}') return this._readPredicate; this._restoreContext('{|', token); From 0539be97902ca83afa7f879b67b231d26728237e Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 4 Jan 2023 11:09:36 +1100 Subject: [PATCH 14/47] chore: clarify use of graph term in quoted quads --- src/N3Parser.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 97b36b37..66eb7a07 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -641,7 +641,8 @@ export default class N3Parser { this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); - // Note - we always use the default graph for the quoted triple component + // As a convention we use set the graph term as the Default Graph in quads representing quoted triples + // see https://github.com/rdfjs/N3.js/pull/311#discussion_r1061039556 for details this._subject = this._quad(this._subject, this._predicate, this._object, this.DEFAULTGRAPH); this._predicate = null; this._object = null; @@ -899,7 +900,8 @@ export default class N3Parser { if (token.type === '{|') { this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); - // Note - we always use the default graph for the quoted triple component + // As a convention we use set the graph term as the Default Graph in quads representing quoted triples + // see https://github.com/rdfjs/N3.js/pull/311#discussion_r1061039556 for details this._subject = this._quad(this._subject, this._predicate, this._object, this.DEFAULTGRAPH); this._predicate = null; this._object = null; From e7646d9809056ae98f22fb176a93d9ff92f593ad Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 4 Jan 2023 14:29:14 +1100 Subject: [PATCH 15/47] fix: allow a split between '|' and '}' (see https://github.com/rdfjs/N3.js/pull/311#discussion_r1060687020) --- src/N3Lexer.js | 6 +- test/N3Parser-test.js | 534 ++++++++++++++++++++++++++---------------- 2 files changed, 342 insertions(+), 198 deletions(-) diff --git a/src/N3Lexer.js b/src/N3Lexer.js index 19ceca32..6bfa2c81 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -306,7 +306,11 @@ export default class N3Lexer { type = '{|', matchLength = 2; break; } - if (!this._lineMode) { + if ( + !this._lineMode && + // The token might actually be {| and we just have not encountered the pipe yet + (input !== '{' || input.length > 1) + ) { matchLength = 1; type = firstChar; } diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 40012632..08a3bad9 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -22,45 +22,45 @@ describe('Parser', () => { }); describe('A Parser instance', () => { - it('should parse the empty string', + describe('should parse the empty string', shouldParse('' /* no triples */)); - it('should parse a whitespace string', + describe('should parse a whitespace string', shouldParse(' \t \n ' /* no triples */)); - it('should parse a single triple', + describe('should parse a single triple', shouldParse(' .', ['a', 'b', 'c'])); - it('should parse three triples', + describe('should parse three triples', shouldParse(' .\n .\n .', ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'])); - it('should parse a triple with a literal', + describe('should parse a triple with a literal', shouldParse(' "string".', ['a', 'b', '"string"'])); - it('should parse a triple with a numeric literal', + describe('should parse a triple with a numeric literal', shouldParse(' 3.0.', ['a', 'b', '"3.0"^^http://www.w3.org/2001/XMLSchema#decimal'])); - it('should parse a triple with an integer literal', + describe('should parse a triple with an integer literal', shouldParse(' 3.', ['a', 'b', '"3"^^http://www.w3.org/2001/XMLSchema#integer'])); - it('should parse a triple with a floating point literal', + describe('should parse a triple with a floating point literal', shouldParse(' 1.3e2.', ['a', 'b', '"1.3e2"^^http://www.w3.org/2001/XMLSchema#double'])); - it('should parse a triple with a boolean literal', + describe('should parse a triple with a boolean literal', shouldParse(' true.', ['a', 'b', '"true"^^http://www.w3.org/2001/XMLSchema#boolean'])); - it('should parse a triple with a literal and a language code', + describe('should parse a triple with a literal and a language code', shouldParse(' "string"@en.', ['a', 'b', '"string"@en'])); @@ -68,11 +68,11 @@ describe('Parser', () => { shouldParse(' "string"@EN.', ['a', 'b', '"string"@en'])); - it('should parse a triple with a literal and an IRI type', + describe('should parse a triple with a literal and an IRI type', shouldParse(' "string"^^.', ['a', 'b', '"string"^^http://example.org/type'])); - it('should parse a triple with a literal and a prefixed name type', + describe('should parse a triple with a literal and a prefixed name type', shouldParse('@prefix x: . "string"^^x:z.', ['a', 'b', '"string"^^urn:x:y#z'])); @@ -103,27 +103,27 @@ describe('Parser', () => { }, })); - it('should parse a triple with the "a" shorthand predicate', + describe('should parse a triple with the "a" shorthand predicate', shouldParse(' a .', ['a', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 't'])); - it('should parse triples with prefixes', + describe('should parse triples with prefixes', shouldParse('@prefix : <#>.\n' + '@prefix a: .\n' + ':x a:a a:b.', ['#x', 'a#a', 'a#b'])); - it('should parse triples with the prefix "prefix"', + describe('should parse triples with the prefix "prefix"', shouldParse('@prefix prefix: .' + 'prefix:a prefix:b prefix:c.', ['http://prefix.cc/a', 'http://prefix.cc/b', 'http://prefix.cc/c'])); - it('should parse triples with the prefix "base"', + describe('should parse triples with the prefix "base"', shouldParse('PREFIX base: ' + 'base:a base:b base:c.', ['http://prefix.cc/a', 'http://prefix.cc/b', 'http://prefix.cc/c'])); - it('should parse triples with the prefix "graph"', + describe('should parse triples with the prefix "graph"', shouldParse('PREFIX graph: ' + 'graph:a graph:b graph:c.', ['http://prefix.cc/a', 'http://prefix.cc/b', 'http://prefix.cc/c'])); @@ -143,7 +143,7 @@ describe('Parser', () => { line: 1, })); - it('should parse triples with prefixes and different punctuation', + describe('should parse triples with prefixes and different punctuation', shouldParse('@prefix : <#>.\n' + '@prefix a: .\n' + ':x a:a a:b;a:c a:d,a:e.', @@ -171,7 +171,7 @@ describe('Parser', () => { shouldNotParse(' "c"^^d:e ', 'Undefined prefix "d:" on line 1.')); - it('should parse triples with SPARQL prefixes', + describe('should parse triples with SPARQL prefixes', shouldParse('PREFIX : <#>\n' + 'PrEfIX a: ' + ':x a:a a:b.', @@ -189,22 +189,22 @@ describe('Parser', () => { shouldNotParse('@prefix : ;', 'Expected declaration to end with a dot on line 1.')); - it('should parse statements with shared subjects', + describe('should parse statements with shared subjects', shouldParse(' ;\n .', ['a', 'b', 'c'], ['a', 'd', 'e'])); - it('should parse statements with shared subjects and trailing semicolon', + describe('should parse statements with shared subjects and trailing semicolon', shouldParse(' ;\n ;\n.', ['a', 'b', 'c'], ['a', 'd', 'e'])); - it('should parse statements with shared subjects and multiple semicolons', + describe('should parse statements with shared subjects and multiple semicolons', shouldParse(' ;;\n .', ['a', 'b', 'c'], ['a', 'd', 'e'])); - it('should parse statements with shared subjects and predicates', + describe('should parse statements with shared subjects and predicates', shouldParse(' , .', ['a', 'b', 'c'], ['a', 'b', 'd'])); @@ -217,7 +217,7 @@ describe('Parser', () => { shouldNotParse(' . , .', 'Expected entity but got , on line 1.')); - it('should parse diamonds', + describe('should parse diamonds', shouldParse('<> <> <> <>.\n(<>) <> (<>) <>.', [BASE_IRI, BASE_IRI, BASE_IRI, BASE_IRI], ['_:b0', BASE_IRI, '_:b1', BASE_IRI], @@ -226,7 +226,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', BASE_IRI], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with named blank nodes', + describe('should parse statements with named blank nodes', shouldParse('_:a _:c.', ['_:b0_a', 'b', '_:b0_c'])); @@ -274,21 +274,21 @@ describe('Parser', () => { }, })); - it('should parse statements with empty blank nodes', + describe('should parse statements with empty blank nodes', shouldParse('[] [].', ['_:b0', 'b', '_:b1'])); - it('should parse statements with unnamed blank nodes in the subject', + describe('should parse statements with unnamed blank nodes in the subject', shouldParse('[ ] .', ['_:b0', 'c', 'd'], ['_:b0', 'a', 'b'])); - it('should parse statements with unnamed blank nodes in the object', + describe('should parse statements with unnamed blank nodes in the object', shouldParse(' [ ].', ['a', 'b', '_:b0'], ['_:b0', 'c', 'd'])); - it('should parse statements with unnamed blank nodes with a string object', + describe('should parse statements with unnamed blank nodes with a string object', shouldParse(' [ "x"].', ['a', 'b', '_:b0'], ['_:b0', 'c', '"x"'])); @@ -305,65 +305,65 @@ describe('Parser', () => { shouldNotParse(' ; ]', 'Unexpected ] on line 1.')); - it('should parse a blank node with a trailing semicolon', + describe('should parse a blank node with a trailing semicolon', shouldParse(' [ ; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'])); - it('should parse a blank node with multiple trailing semicolons', + describe('should parse a blank node with multiple trailing semicolons', shouldParse(' [ ;;; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'])); - it('should parse a multi-predicate blank node', + describe('should parse a multi-predicate blank node', shouldParse(' [ ; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', 'z'])); - it('should parse a multi-predicate blank node with multiple semicolons', + describe('should parse a multi-predicate blank node with multiple semicolons', shouldParse(' [ ;;; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', 'z'])); - it('should parse a multi-object blank node', + describe('should parse a multi-object blank node', shouldParse(' [ , ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'u', 'z'])); - it('should parse a multi-statement blank node ending with a literal', + describe('should parse a multi-statement blank node ending with a literal', shouldParse(' [ ; "z" ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', '"z"'])); - it('should parse a multi-statement blank node ending with a typed literal', + describe('should parse a multi-statement blank node ending with a typed literal', shouldParse(' [ ; "z"^^ ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', '"z"^^http://example.org/t'])); - it('should parse a multi-statement blank node ending with a string with language', + describe('should parse a multi-statement blank node ending with a string with language', shouldParse(' [ ; "z"^^ ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', '"z"^^http://example.org/t'])); - it('should parse a multi-statement blank node with trailing semicolon', + describe('should parse a multi-statement blank node with trailing semicolon', shouldParse(' [ ; ; ].', ['a', 'b', '_:b0'], ['_:b0', 'u', 'v'], ['_:b0', 'w', 'z'])); - it('should parse statements with nested blank nodes in the subject', + describe('should parse statements with nested blank nodes in the subject', shouldParse('[ [ ]] .', ['_:b0', 'c', 'd'], ['_:b0', 'a', '_:b1'], ['_:b1', 'x', 'y'])); - it('should parse statements with nested blank nodes in the object', + describe('should parse statements with nested blank nodes in the object', shouldParse(' [ [ ]].', ['a', 'b', '_:b0'], ['_:b0', 'c', '_:b1'], @@ -378,7 +378,7 @@ describe('Parser', () => { shouldNotParse('[ .', 'Expected punctuation to follow "http://example.org/b" on line 1.')); - it('should parse a statements with only an anonymous node', + describe('should parse a statements with only an anonymous node', shouldParse('[

].', ['_:b0', 'p', 'o'])); @@ -390,45 +390,45 @@ describe('Parser', () => { shouldNotParse('[[

]].', 'Disallowed blank node as predicate on line 1.')); - it('should parse statements with an empty list in the subject', + describe('should parse statements with an empty list in the subject', shouldParse('() .', ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'a', 'b'])); - it('should parse statements with an empty list in the object', + describe('should parse statements with an empty list in the object', shouldParse(' ().', ['a', 'b', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a single-element list in the subject', + describe('should parse statements with a single-element list in the subject', shouldParse('() .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a single-element list in the object', + describe('should parse statements with a single-element list in the object', shouldParse(' ().', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse a list with a literal', + describe('should parse a list with a literal', shouldParse(' ("x").', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"x"'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse a list with a typed literal', + describe('should parse a list with a typed literal', shouldParse(' ("x"^^).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"x"^^http://example.org/y'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse a list with a language-tagged literal', + describe('should parse a list with a language-tagged literal', shouldParse(' ("x"@en-GB).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"x"@en-gb'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a multi-element list in the subject', + describe('should parse statements with a multi-element list in the subject', shouldParse('( ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], @@ -436,7 +436,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a multi-element list in the object', + describe('should parse statements with a multi-element list in the object', shouldParse(' ( ).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], @@ -444,7 +444,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a multi-element literal list in the object', + describe('should parse statements with a multi-element literal list in the object', shouldParse(' ("x" "y"@en-GB 1 "z"^^).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"x"'], @@ -456,7 +456,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '"z"^^http://example.org/t'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with prefixed names in lists', + describe('should parse statements with prefixed names in lists', shouldParse('@prefix a: . (a:x a:y).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a#x'], @@ -468,7 +468,7 @@ describe('Parser', () => { shouldNotParse(' (a:x a:y).', 'Undefined prefix "a:" on line 1.')); - it('should parse statements with blank nodes in lists', + describe('should parse statements with blank nodes in lists', shouldParse(' (_:x _:y).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_x'], @@ -476,7 +476,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_y'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse a nested list', + describe('should parse a nested list', shouldParse(' ( ( ) ).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], @@ -484,7 +484,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'c'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a nested empty list', + describe('should parse statements with a nested empty list', shouldParse(' ( ()).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], @@ -492,7 +492,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with non-empty nested lists', + describe('should parse statements with non-empty nested lists', shouldParse(' ( ()).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x'], @@ -502,13 +502,13 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'y'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a quoted triple', + describe('should parse statements with a list containing a quoted triple', shouldParse(' ( << >> ) .', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a quoted triple and iri after', + describe('should parse statements with a list containing a quoted triple and iri after', shouldParse(' ( << >> ) .', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], @@ -516,7 +516,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'f'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a quoted triple and iri before', + describe('should parse statements with a list containing a quoted triple and iri before', shouldParse(' ( << >> ) .', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'f'], @@ -524,7 +524,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a 2 quoted triples', + describe('should parse statements with a list containing a 2 quoted triples', shouldParse(' ( << >> << >> ) .', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], @@ -533,7 +533,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a 3 quoted triples', + describe('should parse statements with a list containing a 3 quoted triples', shouldParse(' ( << >> << >> << >> ) .', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], @@ -543,7 +543,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c2', 'd2', 'e2']], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a 1 quoted triple and 2 iris', + describe('should parse statements with a list containing a 1 quoted triple and 2 iris', shouldParse(' ( << >> ) .', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], @@ -553,7 +553,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'i'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a 1 quoted triple between 2 iris', + describe('should parse statements with a list containing a 1 quoted triple between 2 iris', shouldParse(' ( << >> ) .', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'h'], @@ -564,7 +564,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse a nested list containing 1 quoted triple', + describe('should parse a nested list containing 1 quoted triple', shouldParse(' ( ( << >> ) ).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], @@ -572,13 +572,13 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a quoted triple with list as subject', + describe('should parse statements with a list containing a quoted triple with list as subject', shouldParse('( << >> ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a quoted triple and iri after with list as subject', + describe('should parse statements with a list containing a quoted triple and iri after with list as subject', shouldParse('( << >> ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], @@ -586,7 +586,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'f'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a quoted triple and iri before with list as subject', + describe('should parse statements with a list containing a quoted triple and iri before with list as subject', shouldParse('( << >> ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'f'], @@ -594,7 +594,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a 2 quoted triples with list as subject', + describe('should parse statements with a list containing a 2 quoted triples with list as subject', shouldParse('( << >> << >> ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], @@ -603,7 +603,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a 3 quoted triples with list as subject', + describe('should parse statements with a list containing a 3 quoted triples with list as subject', shouldParse('( << >> << >> << >> ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], @@ -613,7 +613,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c2', 'd2', 'e2']], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a 1 quoted triple and 2 iris with list as subject', + describe('should parse statements with a list containing a 1 quoted triple and 2 iris with list as subject', shouldParse('( << >> ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], @@ -623,7 +623,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'i'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a 1 quoted triple between 2 iris with list as subject', + describe('should parse statements with a list containing a 1 quoted triple between 2 iris with list as subject', shouldParse('( << >> ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'h'], @@ -633,7 +633,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'i'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse a nested list containing 1 quoted triple with list as subject', + describe('should parse a nested list containing 1 quoted triple with list as subject', shouldParse('( ( << >> ) ) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], @@ -641,13 +641,13 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', ['c', 'd', 'e']], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing a blank node with list as subject', + describe('should parse statements with a list containing a blank node with list as subject', shouldParse('([]) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should parse statements with a list containing multiple blank nodes with list as subject', + describe('should parse statements with a list containing multiple blank nodes with list as subject', shouldParse('([] [ ]) .', ['_:b0', 'a', 'b'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], @@ -656,7 +656,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b3', 'x', 'y'])); - it('should parse statements with a blank node containing a list with list as subject', + describe('should parse statements with a blank node containing a list with list as subject', shouldParse('[ ()] .', ['_:b0', 'c', 'd'], ['_:b0', 'a', '_:b1'], @@ -746,142 +746,142 @@ describe('Parser', () => { 'ex:a ex:b ex:c .', ['http://ex.org/a/bb/ccc/../a', 'http://ex.org/a/bb/ccc/../b', 'http://ex.org/a/bb/ccc/../c'])); - it('should parse an empty default graph', + describe('should parse an empty default graph', shouldParse('{}')); - it('should parse a one-triple default graph ending without a dot', + describe('should parse a one-triple default graph ending without a dot', shouldParse('{ }', ['a', 'b', 'c'])); - it('should parse a one-triple default graph ending with a dot', + describe('should parse a one-triple default graph ending with a dot', shouldParse('{ .}', ['a', 'b', 'c'])); - it('should parse a three-triple default graph ending without a dot', + describe('should parse a three-triple default graph ending without a dot', shouldParse('{ ; ,}', ['a', 'b', 'c'], ['a', 'd', 'e'], ['a', 'd', 'f'])); - it('should parse a three-triple default graph ending with a dot', + describe('should parse a three-triple default graph ending with a dot', shouldParse('{ ; ,.}', ['a', 'b', 'c'], ['a', 'd', 'e'], ['a', 'd', 'f'])); - it('should parse a three-triple default graph ending with a semicolon', + describe('should parse a three-triple default graph ending with a semicolon', shouldParse('{ ; ,;}', ['a', 'b', 'c'], ['a', 'd', 'e'], ['a', 'd', 'f'])); - it('should parse a default graph with a blank node ending with a dot', + describe('should parse a default graph with a blank node ending with a dot', shouldParse('{ [

]. }', ['_:b0', 'p', 'o'])); - it('should parse a default graph with a blank node ending without a dot', + describe('should parse a default graph with a blank node ending without a dot', shouldParse('{ [

] }', ['_:b0', 'p', 'o'])); - it('should parse an empty named graph with an IRI', + describe('should parse an empty named graph with an IRI', shouldParse('{}')); - it('should parse a one-triple named graph with an IRI ending without a dot', + describe('should parse a one-triple named graph with an IRI ending without a dot', shouldParse(' { }', ['a', 'b', 'c', 'g'])); - it('should parse a one-triple named graph with an IRI ending with a dot', + describe('should parse a one-triple named graph with an IRI ending with a dot', shouldParse('{ .}', ['a', 'b', 'c', 'g'])); - it('should parse a three-triple named graph with an IRI ending without a dot', + describe('should parse a three-triple named graph with an IRI ending without a dot', shouldParse(' { ; ,}', ['a', 'b', 'c', 'g'], ['a', 'd', 'e', 'g'], ['a', 'd', 'f', 'g'])); - it('should parse a three-triple named graph with an IRI ending with a dot', + describe('should parse a three-triple named graph with an IRI ending with a dot', shouldParse('{ ; ,.}', ['a', 'b', 'c', 'g'], ['a', 'd', 'e', 'g'], ['a', 'd', 'f', 'g'])); - it('should parse an empty named graph with a prefixed name', + describe('should parse an empty named graph with a prefixed name', shouldParse('@prefix g: .\ng:h {}')); - it('should parse a one-triple named graph with a prefixed name ending without a dot', + describe('should parse a one-triple named graph with a prefixed name ending without a dot', shouldParse('@prefix g: .\ng:h { }', ['a', 'b', 'c', 'g#h'])); - it('should parse a one-triple named graph with a prefixed name ending with a dot', + describe('should parse a one-triple named graph with a prefixed name ending with a dot', shouldParse('@prefix g: .\ng:h{ .}', ['a', 'b', 'c', 'g#h'])); - it('should parse a three-triple named graph with a prefixed name ending without a dot', + describe('should parse a three-triple named graph with a prefixed name ending without a dot', shouldParse('@prefix g: .\ng:h { ; ,}', ['a', 'b', 'c', 'g#h'], ['a', 'd', 'e', 'g#h'], ['a', 'd', 'f', 'g#h'])); - it('should parse a three-triple named graph with a prefixed name ending with a dot', + describe('should parse a three-triple named graph with a prefixed name ending with a dot', shouldParse('@prefix g: .\ng:h{ ; ,.}', ['a', 'b', 'c', 'g#h'], ['a', 'd', 'e', 'g#h'], ['a', 'd', 'f', 'g#h'])); - it('should parse a named graph with a blank node ending with a dot', + describe('should parse a named graph with a blank node ending with a dot', shouldParse(' { [

]. }', ['_:b0', 'p', 'o', 'g'])); - it('should parse a named graph with a blank node ending without a dot', + describe('should parse a named graph with a blank node ending without a dot', shouldParse(' { [

] }', ['_:b0', 'p', 'o', 'g'])); - it('should parse an empty anonymous graph', + describe('should parse an empty anonymous graph', shouldParse('[] {}')); - it('should parse a one-triple anonymous graph ending without a dot', + describe('should parse a one-triple anonymous graph ending without a dot', shouldParse('[] { }', ['a', 'b', 'c', '_:b0'])); - it('should parse a one-triple anonymous graph ending with a dot', + describe('should parse a one-triple anonymous graph ending with a dot', shouldParse('[]{ .}', ['a', 'b', 'c', '_:b0'])); - it('should parse a three-triple anonymous graph ending without a dot', + describe('should parse a three-triple anonymous graph ending without a dot', shouldParse('[] { ; ,}', ['a', 'b', 'c', '_:b0'], ['a', 'd', 'e', '_:b0'], ['a', 'd', 'f', '_:b0'])); - it('should parse a three-triple anonymous graph ending with a dot', + describe('should parse a three-triple anonymous graph ending with a dot', shouldParse('[]{ ; ,.}', ['a', 'b', 'c', '_:b0'], ['a', 'd', 'e', '_:b0'], ['a', 'd', 'f', '_:b0'])); - it('should parse an empty named graph with an IRI and the GRAPH keyword', + describe('should parse an empty named graph with an IRI and the GRAPH keyword', shouldParse('GRAPH {}')); - it('should parse an empty named graph with a prefixed name and the GRAPH keyword', + describe('should parse an empty named graph with a prefixed name and the GRAPH keyword', shouldParse('@prefix g: .\nGRAPH g:h {}')); - it('should parse an empty anonymous graph and the GRAPH keyword', + describe('should parse an empty anonymous graph and the GRAPH keyword', shouldParse('GRAPH [] {}')); - it('should parse a one-triple named graph with an IRI and the GRAPH keyword', + describe('should parse a one-triple named graph with an IRI and the GRAPH keyword', shouldParse('GRAPH { }', ['a', 'b', 'c', 'g'])); - it('should parse a one-triple named graph with a prefixed name and the GRAPH keyword', + describe('should parse a one-triple named graph with a prefixed name and the GRAPH keyword', shouldParse('@prefix g: .\nGRAPH g:h { }', ['a', 'b', 'c', 'g#h'])); - it('should parse a one-triple anonymous graph and the GRAPH keyword', + describe('should parse a one-triple anonymous graph and the GRAPH keyword', shouldParse('GRAPH [] { }', ['a', 'b', 'c', '_:b0'])); - it('should parse a graph with 8-bit unicode escape sequences', + describe('should parse a graph with 8-bit unicode escape sequences', shouldParse('<\\U0001d400> {\n<\\U0001d400> <\\U0001d400> "\\U0001d400"^^<\\U0001d400>\n}\n', ['\ud835\udC00', '\ud835\udc00', '"\ud835\udc00"^^http://example.org/\ud835\udc00', '\ud835\udc00'])); @@ -891,7 +891,7 @@ describe('Parser', () => { it('should not parse a single opening brace', shouldNotParse('{', - 'Expected entity but got eof on line 1.')); + 'Unexpected "{" on line 1.')); it('should not parse a superfluous closing brace ', shouldNotParse('{}}', @@ -929,7 +929,7 @@ describe('Parser', () => { shouldNotParse('GRAPH GRAPH {}', 'Invalid graph label on line 1.')); - it('should parse a quad with 4 IRIs', + describe('should parse a quad with 4 IRIs', shouldParse(' .', ['a', 'b', 'c', 'g'])); @@ -937,7 +937,7 @@ describe('Parser', () => { shouldNotParse('<< >> .', 'Expected >> to follow "http://example.org/c" but got IRI on line 1.')); - it('should parse a quad with 4 prefixed names', + describe('should parse a quad with 4 prefixed names', shouldParse('@prefix p: .\np:a p:b p:c p:g.', ['p#a', 'p#b', 'p#c', 'p#g'])); @@ -945,11 +945,11 @@ describe('Parser', () => { shouldNotParse(' p:g.', 'Undefined prefix "p:" on line 1.')); - it('should parse a quad with 3 IRIs and a literal', + describe('should parse a quad with 3 IRIs and a literal', shouldParse(' "c"^^ .', ['a', 'b', '"c"^^http://example.org/d', 'g'])); - it('should parse a quad with 2 blank nodes and a literal', + describe('should parse a quad with 2 blank nodes and a literal', shouldParse('_:a "c"^^ _:g.', ['_:b0_a', 'b', '"c"^^http://example.org/d', '_:b0_g'])); @@ -1066,7 +1066,7 @@ describe('Parser', () => { } }); - it('should parse a string synchronously if no callback is given', () => { + describe('should parse a string synchronously if no callback is given', () => { const triples = new Parser().parse('@prefix a: . a:a a:b a:c.'); triples.should.deep.equal([ new Quad(termFromId('urn:a:a'), termFromId('urn:a:b'), @@ -1084,7 +1084,7 @@ describe('Parser', () => { .should.throw('Expected entity but got eof on line 1'); }); - it('should parse an RDF-star triple with a triple with iris as subject correctly', () => { + describe('should parse an RDF-star triple with a triple with iris as subject correctly', () => { shouldParse('<< >> .', [['a', 'b', 'c'], 'b', 'c']); }); @@ -1093,36 +1093,36 @@ describe('Parser', () => { shouldNotParse(' << >> ', 'Expected entity but got << on line 1.')); - it('should parse an RDF-star triple with a triple with blanknodes as subject correctly', + describe('should parse an RDF-star triple with a triple with blanknodes as subject correctly', shouldParse('<<_:a _:c>> .', [['_:b0_a', 'b', '_:b0_c'], 'b', 'c'])); - it('should parse an RDF-star triple with a triple with blanknodes and literals as subject correctly', + describe('should parse an RDF-star triple with a triple with blanknodes and literals as subject correctly', shouldParse('<<_:a "c"^^>> .', [['_:b0_a', 'b', '"c"^^http://example.org/d'], 'b', 'c'])); - it('should parse an RDF-star triple with a triple as object correctly', + describe('should parse an RDF-star triple with a triple as object correctly', shouldParse(' << >>.', ['a', 'b', ['a', 'b', 'c']])); - it('should parse an RDF-star triple with a triple as object correctly', + describe('should parse an RDF-star triple with a triple as object correctly', shouldParse(' <<_:a _:c>>.', ['a', 'b', ['_:b0_a', 'b', '_:b0_c']])); - it('should parse an RDF-star triple with a triple as object correctly', + describe('should parse an RDF-star triple with a triple as object correctly', shouldParse(' <<_:a "c"^^>>.', ['a', 'b', ['_:b0_a', 'b', '"c"^^http://example.org/d']])); - it('should parse nested triples correctly', + describe('should parse nested triples correctly', shouldParse('<<<< >> >> .', [[['a', 'b', 'c'], 'f', 'g'], 'd', 'e'])); - it('should parse nested triples correctly', + describe('should parse nested triples correctly', shouldParse(' << << >>>>.', ['d', 'e', ['f', 'g', ['a', 'b', 'c']]])); - it('should parse nested triples correctly', + describe('should parse nested triples correctly', shouldParse('<< << >>>> .', [['f', 'g', ['a', 'b', 'c']], 'd', 'e'])); - it('should parse nested triples correctly', + describe('should parse nested triples correctly', shouldParse(' <<<< >> >>.', ['d', 'e', [['a', 'b', 'c'], 'f', 'g']])); @@ -1220,12 +1220,25 @@ describe('Parser', () => { shouldNotParse('<< >> .', 'Expected >> to follow "http://example.org/c" but got IRI on line 1.')); - it('should parse statements with a shared RDF-star subject', + describe('should parse statements with a shared RDF-star subject', shouldParse('<< >> ;\n .', [['a', 'b', 'c'], 'b', 'c'], [['a', 'b', 'c'], 'd', 'c'])); - it('should parse statements with a shared RDF-star subject', + // it('should parse no chunks (i.e. onEnd called immediately)', + // shouldParseChunks([])); + + it('should parse statements with a shared RDF-star subject that is chunked at double quotes', + shouldParseChunks(['<', '< >> ;\n .'], + [['a', 'b', 'c'], 'b', 'c'], + [['a', 'b', 'c'], 'd', 'c'])); + + it('should parse statements with a shared RDF-star subject that is chunked at every character', + shouldParseChunks('<< >> ;\n .'.split(''), + [['a', 'b', 'c'], 'b', 'c'], + [['a', 'b', 'c'], 'd', 'c'])); + + describe('should parse statements with a shared RDF-star subject', shouldParse('<< >> ;\n << >>.', [['a', 'b', 'c'], 'b', 'c'], [['a', 'b', 'c'], 'd', ['a', 'b', 'c']])); @@ -1235,12 +1248,40 @@ describe('Parser', () => { ['a', 'b', 'c', 'g'], [['a', 'b', 'c'], 'd', 'e'])); - it('should parse an explicit triple with reified annotation', + describe('should parse an explicit triple with reified annotation', shouldParse(' {| |} .', ['a', 'b', 'c'], [['a', 'b', 'c'], 'd', 'e'])); - it('should parse an explicit triple with nested reified annotation', + it('should parse an explicit triple with reified annotation that is chunked at the pipe', + shouldParseChunks([' {| |', '} .'], + ['a', 'b', 'c'], + [['a', 'b', 'c'], 'd', 'e'])); + + it('should parse an explicit triple with reified annotation that is chunked at the pipe and each character after', + shouldParseChunks([' {| |', '}', ' ', '.', ''], + ['a', 'b', 'c'], + [['a', 'b', 'c'], 'd', 'e'])); + + it('should parse an explicit triple with reified annotation that is chunked at each annotation', + shouldParseChunks([' {', '| |', '} .'], + ['a', 'b', 'c'], + [['a', 'b', 'c'], 'd', 'e'])); + + it('should parse an quoted triple that is chunked on first quote', + shouldParseChunks(['<', '< >> .'], + [['a', 'b', 'c'], 'd', 'e'])); + + it('should parse an quoted triple that is chunked on ending quote', + shouldParseChunks(['<< >', '> .'], + [['a', 'b', 'c'], 'd', 'e'])); + + it('should parse an explicit triple with reified annotation that is chunked before annotation', + shouldParseChunks([' ', '{| |} .'], + ['a', 'b', 'c'], + [['a', 'b', 'c'], 'd', 'e'])); + + describe('should parse an explicit triple with nested reified annotation', shouldParse(' {| {| |} |} .', ['a', 'b', 'c'], [['a', 'b', 'c'], 'd', 'e'], @@ -1249,22 +1290,22 @@ describe('Parser', () => { const q = ['http://example.com/ns#s', 'http://example.com/ns#p', ['http://example.com/ns#a', 'http://example.com/ns#b', 'http://example.com/ns#c']]; - it('should parse an explicit triple with reified annotation containing prefixed iris', + describe('should parse an explicit triple with reified annotation containing prefixed iris', shouldParse('PREFIX : \n :s :p <<:a :b :c>> {| :q :z |} .', q, [q, 'http://example.com/ns#q', 'http://example.com/ns#z'])); - it('should parse an explicit triple with 2 reified annotations', + describe('should parse an explicit triple with 2 reified annotations', shouldParse(' {| ; |} .', ['a', 'b', 'c'], [['a', 'b', 'c'], 'd', 'e'], [['a', 'b', 'c'], 'f', 'g'])); - it('should parse an explicit triple with reified annotation in a named graph', + describe('should parse an explicit triple with reified annotation in a named graph', shouldParse(' { {| |} . }', ['a', 'b', 'c', 'G'], [['a', 'b', 'c'], 'd', 'e', 'G'])); - it('should parse an explicit triple with 2 reified annotations in a named graph', + describe('should parse an explicit triple with 2 reified annotations in a named graph', shouldParse(' { {| ; |} . }', ['a', 'b', 'c', 'G'], [['a', 'b', 'c'], 'd', 'e', 'G'], @@ -1397,7 +1438,7 @@ describe('Parser', () => { describe('A Parser instance with a blank node prefix', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, blankNodePrefix: '_:blank' }); } - it('should use the given prefix for blank nodes', + describe('should use the given prefix for blank nodes', shouldParse(parser, '_:a _:c.\n', ['_:blanka', 'b', '_:blankc'])); @@ -1406,7 +1447,7 @@ describe('Parser', () => { describe('A Parser instance with an empty blank node prefix', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, blankNodePrefix: '' }); } - it('should not use a prefix for blank nodes', + describe('should not use a prefix for blank nodes', shouldParse(parser, '_:a _:c.\n', ['_:a', 'b', '_:c'])); @@ -1415,17 +1456,17 @@ describe('Parser', () => { describe('A Parser instance with a non-string format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 1 }); } - it('should parse a single triple', + describe('should parse a single triple', shouldParse(parser, ' .', ['a', 'b', 'c'])); - it('should parse a graph', + describe('should parse a graph', shouldParse(parser, '{ }', ['a', 'b', 'c'])); }); describe('A Parser instance for the Turtle format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'Turtle' }); } - it('should parse a single triple', + describe('should parse a single triple', shouldParse(parser, ' .', ['a', 'b', 'c'])); it('should not parse a default graph', @@ -1493,7 +1534,7 @@ describe('Parser', () => { describe('A Parser instance for the TurtleStar format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TurtleStar' }); } - it('should parse RDF-star', + describe('should parse RDF-star', shouldParse(parser, '<< >> .', [['a', 'b', 'c'], 'b', 'c'])); @@ -1506,16 +1547,22 @@ describe('Parser', () => { describe('A Parser instance for the TriG format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TriG' }); } + it('should parse a single triple chunked before a closing bracket', + shouldParseChunks(parser, [' .'], ['a', 'b', 'c'])); + + it('should parse a single triple chunked after an opening bracket', + shouldParseChunks(parser, [' <', 'b> .'], ['a', 'b', 'c'])); + it('should parse a single triple', shouldParse(parser, ' .', ['a', 'b', 'c'])); - it('should parse a default graph', + describe('should parse a default graph', shouldParse(parser, '{}')); - it('should parse a named graph', + describe('should parse a named graph', shouldParse(parser, ' {}')); - it('should parse a named graph with the GRAPH keyword', + describe('should parse a named graph with the GRAPH keyword', shouldParse(parser, 'GRAPH {}')); it('should not parse a quad', @@ -1554,7 +1601,7 @@ describe('Parser', () => { describe('A Parser instance for the TriGStar format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TriGStar' }); } - it('should parse RDF-star', + describe('should parse RDF-star', shouldParse(parser, '<< >> .', [['a', 'b', 'c'], 'a', 'b'])); @@ -1566,11 +1613,11 @@ describe('Parser', () => { describe('A Parser instance for the N-Triples format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-Triples' }); } - it('should parse a single triple', + describe('should parse a single triple', shouldParse(parser, '_:a "c".', ['_:b0_a', 'http://ex.org/b', '"c"'])); - it('should parse a single triple starting with Bom', + describe('should parse a single triple starting with Bom', shouldParse(parser, '\ufeff_:a "c".', ['_:b0_a', 'http://ex.org/b', '"c"'])); @@ -1629,7 +1676,7 @@ describe('Parser', () => { describe('A Parser instance for the N-TriplesStar format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-TriplesStar' }); } - it('should parse RDF-star', + describe('should parse RDF-star', shouldParse(parser, '<<_:a _:c>> _:b .', [['_:b0_a', 'b', '_:b0_c'], 'a', '_:b0_b'])); @@ -1641,11 +1688,11 @@ describe('Parser', () => { describe('A Parser instance for the N-Quads format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-Quads' }); } - it('should parse a single triple', + describe('should parse a single triple', shouldParse(parser, '_:a "c".', ['_:b0_a', 'http://ex.org/b', '"c"'])); - it('should parse a single quad', + describe('should parse a single quad', shouldParse(parser, '_:a "c" .', ['_:b0_a', 'http://ex.org/b', '"c"', 'http://ex.org/g'])); @@ -1688,7 +1735,7 @@ describe('Parser', () => { describe('A Parser instance for the N-QuadsStar format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-QuadsStar' }); } - it('should parse RDF-star', + describe('should parse RDF-star', shouldParse(parser, '<<_:a _:c>> _:c .', [['_:b0_a', 'b', '_:b0_c'], 'a', '_:b0_c'])); }); @@ -1696,7 +1743,7 @@ describe('Parser', () => { describe('A Parser instance for the N3 format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3' }); } - it('should parse a single triple', + describe('should parse a single triple', shouldParse(parser, ' .', ['a', 'b', 'c'])); it('should not parse a default graph', @@ -1722,28 +1769,35 @@ describe('Parser', () => { ['a', '_:b0', 'c'], ['_:b0', 'p', 'o'])); - it('should parse a variable', + describe('should parse a variable', shouldParse(parser, '?a ?b ?c.', ['?a', '?b', '?c'])); - it('should parse a simple equality', + describe('should parse a simple equality', shouldParse(parser, ' = .', ['a', 'http://www.w3.org/2002/07/owl#sameAs', 'b'])); - it('should parse a simple right implication', + describe('should parse a simple right implication', shouldParse(parser, ' => .', ['a', 'http://www.w3.org/2000/10/swap/log#implies', 'b'])); - it('should parse a simple left implication', + describe('should parse a simple left implication', shouldParse(parser, ' <= .', ['b', 'http://www.w3.org/2000/10/swap/log#implies', 'a'])); - it('should parse a right implication between one-triple graphs', + describe('should parse a right implication between one-triple graphs', shouldParse(parser, '{ ?a ?b . } => { ?a }.', ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1'])); - it('should parse a right implication between two-triple graphs', + it('should parse a right implication between one-triple graphs with chunk at first bracket', + shouldParseChunks(parser, ['{', ' ?a ?b . } => { ?a }.'], + ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], + ['?a', '?b', 'c', '_:b0'], + ['d', 'e', '?a', '_:b1'])); + + + describe('should parse a right implication between two-triple graphs', shouldParse(parser, '{ ?a ?b . . } => { ?a, }.', ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], ['?a', '?b', 'c', '_:b0'], @@ -1751,13 +1805,13 @@ describe('Parser', () => { ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1'])); - it('should parse a left implication between one-triple graphs', + describe('should parse a left implication between one-triple graphs', shouldParse(parser, '{ ?a ?b . } <= { ?a }.', ['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1'])); - it('should parse a left implication between two-triple graphs', + describe('should parse a left implication between two-triple graphs', shouldParse(parser, '{ ?a ?b . . } <= { ?a, }.', ['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], ['?a', '?b', 'c', '_:b0'], @@ -1765,13 +1819,13 @@ describe('Parser', () => { ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1'])); - it('should parse an equality of one-triple graphs', + describe('should parse an equality of one-triple graphs', shouldParse(parser, '{ ?a ?b . } = { ?a }.', ['_:b0', 'http://www.w3.org/2002/07/owl#sameAs', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1'])); - it('should parse an equality of two-triple graphs', + describe('should parse an equality of two-triple graphs', shouldParse(parser, '{ ?a ?b . . } = { ?a, }.', ['_:b0', 'http://www.w3.org/2002/07/owl#sameAs', '_:b1'], ['?a', '?b', 'c', '_:b0'], @@ -1779,7 +1833,7 @@ describe('Parser', () => { ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1'])); - it('should parse nested implication graphs', + describe('should parse nested implication graphs', shouldParse(parser, '{ { ?a ?b ?c }<={ ?d ?e ?f }. } <= { { ?g ?h ?i } => { ?j ?k ?l } }.', ['_:b3', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], ['_:b2', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1', '_:b0'], @@ -1798,11 +1852,11 @@ describe('Parser', () => { ['_:b2.a', '_:b2.b', '_:b2.c', '_:b2'], ['_:b3.a', '_:b3.b', '_:b3.c', '_:b3'])); - it('should parse a @forSome statement', + describe('should parse a @forSome statement', shouldParse(parser, '@forSome . .', ['_:b0', '_:b0', '_:b0'])); - it('should parse a @forSome statement with multiple entities', + describe('should parse a @forSome statement with multiple entities', shouldParse(parser, '@prefix a: . @base . @forSome a:x, , a:z. a:x a:z.', ['_:b0', '_:b1', '_:b2'])); @@ -1824,11 +1878,11 @@ describe('Parser', () => { ['_:b2', '_:b2', '_:b2', '_:b1'], ['_:b0', '_:b0', '_:b0'])); - it('should parse a @forAll statement', + describe('should parse a @forAll statement', shouldParse(parser, '@forAll . .', ['?b0', '?b0', '?b0'])); - it('should parse a @forAll statement with multiple entities', + describe('should parse a @forAll statement with multiple entities', shouldParse(parser, '@prefix a: . @base . @forAll a:x, , a:z. a:x a:z.', ['?b0', '?b1', '?b2'])); @@ -1850,13 +1904,13 @@ describe('Parser', () => { ['?b2', '?b2', '?b2', '_:b1'], ['?b0', '?b0', '?b0'])); - it('should parse a ! path of length 2 as subject', + describe('should parse a ! path of length 2 as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + ':joe!fam:mother a fam:Person.', ['ex:joe', 'f:mother', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - it('should parse a ! path of length 4 as subject', + describe('should parse a ! path of length 4 as subject', shouldParse(parser, '@prefix : . @prefix fam: . @prefix loc: .' + ':joe!fam:mother!loc:office!loc:zip loc:code 1234.', ['ex:joe', 'f:mother', '_:b0'], @@ -1864,13 +1918,13 @@ describe('Parser', () => { ['_:b1', 'l:zip', '_:b2'], ['_:b2', 'l:code', '"1234"^^http://www.w3.org/2001/XMLSchema#integer'])); - it('should parse a ! path of length 2 as object', + describe('should parse a ! path of length 2 as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' :joe!fam:mother.', ['x', 'is', '_:b0'], ['ex:joe', 'f:mother', '_:b0'])); - it('should parse a ! path of length 4 as object', + describe('should parse a ! path of length 4 as object', shouldParse(parser, '@prefix : . @prefix fam: . @prefix loc: .' + ' :joe!fam:mother!loc:office!loc:zip.', ['x', 'is', '_:b2'], @@ -1878,13 +1932,13 @@ describe('Parser', () => { ['_:b0', 'l:office', '_:b1'], ['_:b1', 'l:zip', '_:b2'])); - it('should parse a ^ path of length 2 as subject', + describe('should parse a ^ path of length 2 as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + ':joe^fam:son a fam:Person.', ['_:b0', 'f:son', 'ex:joe'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - it('should parse a ^ path of length 4 as subject', + describe('should parse a ^ path of length 4 as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + ':joe^fam:son^fam:sister^fam:mother a fam:Person.', ['_:b0', 'f:son', 'ex:joe'], @@ -1892,13 +1946,13 @@ describe('Parser', () => { ['_:b2', 'f:mother', '_:b1'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - it('should parse a ^ path of length 2 as object', + describe('should parse a ^ path of length 2 as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' :joe^fam:son.', ['x', 'is', '_:b0'], ['_:b0', 'f:son', 'ex:joe'])); - it('should parse a ^ path of length 4 as object', + describe('should parse a ^ path of length 4 as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' :joe^fam:son^fam:sister^fam:mother.', ['x', 'is', '_:b2'], @@ -1906,49 +1960,49 @@ describe('Parser', () => { ['_:b1', 'f:sister', '_:b0'], ['_:b2', 'f:mother', '_:b1'])); - it('should parse mixed !/^ paths as subject', + describe('should parse mixed !/^ paths as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + ':joe!fam:mother^fam:mother a fam:Person.', ['ex:joe', 'f:mother', '_:b0'], ['_:b1', 'f:mother', '_:b0'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - it('should parse mixed !/^ paths as object', + describe('should parse mixed !/^ paths as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' :joe!fam:mother^fam:mother.', ['x', 'is', '_:b1'], ['ex:joe', 'f:mother', '_:b0'], ['_:b1', 'f:mother', '_:b0'])); - it('should parse a ! path in a blank node as subject', + describe('should parse a ! path in a blank node as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '[fam:knows :joe!fam:mother] a fam:Person.', ['_:b0', 'f:knows', '_:b1'], ['ex:joe', 'f:mother', '_:b1'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - it('should parse a ! path in a blank node as object', + describe('should parse a ! path in a blank node as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' [fam:knows :joe!fam:mother].', ['x', 'is', '_:b0'], ['_:b0', 'f:knows', '_:b1'], ['ex:joe', 'f:mother', '_:b1'])); - it('should parse a ^ path in a blank node as subject', + describe('should parse a ^ path in a blank node as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '[fam:knows :joe^fam:son] a fam:Person.', ['_:b0', 'f:knows', '_:b1'], ['_:b1', 'f:son', 'ex:joe'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - it('should parse a ^ path in a blank node as object', + describe('should parse a ^ path in a blank node as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' [fam:knows :joe^fam:son].', ['x', 'is', '_:b0'], ['_:b0', 'f:knows', '_:b1'], ['_:b1', 'f:son', 'ex:joe'])); - it('should parse a ! path in a list as subject', + describe('should parse a ! path in a list as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '( :joe!fam:mother ) a :List.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List'], @@ -1960,7 +2014,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['ex:joe', 'f:mother', '_:b2'])); - it('should parse a ! path in a list as object', + describe('should parse a ! path in a list as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' ( :joe!fam:mother ).', ['l', 'is', '_:b0'], @@ -1972,7 +2026,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['ex:joe', 'f:mother', '_:b2'])); - it('should parse a ^ path in a list as subject', + describe('should parse a ^ path in a list as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '( :joe^fam:son ) a :List.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List'], @@ -1984,7 +2038,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b2', 'f:son', 'ex:joe'])); - it('should parse a ^ path in a list as object', + describe('should parse a ^ path in a list as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' ( :joe^fam:son ).', ['l', 'is', '_:b0'], @@ -1996,7 +2050,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b2', 'f:son', 'ex:joe'])); - it('should parse a formula as list item', + describe('should parse a formula as list item', shouldParse(parser, ' ( { a . } ).', ['a', 'findAll', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'b'], @@ -2013,19 +2067,19 @@ describe('Parser', () => { it('should not parse an invalid ^ path', shouldNotParse(parser, '^"invalid" ', 'Expected entity but got literal on line 1.')); - it('should parse literal as subject', + describe('should parse literal as subject', shouldParse(parser, ' {1 0}.', ['a', 'b', '_:b0'], ['"1"^^http://www.w3.org/2001/XMLSchema#integer', 'greaterThan', '"0"^^http://www.w3.org/2001/XMLSchema#integer', '_:b0'] )); - it('should parse literals with datatype as subject', + describe('should parse literals with datatype as subject', shouldParse(parser, ' {"a"^^ "b"^^}.', ['a', 'b', '_:b0'], ['"a"^^http://example.org/c', 'greaterThan', '"b"^^http://example.org/c', '_:b0'] )); - it('should parse literals with language as subject', + describe('should parse literals with language as subject', shouldParse(parser, ' {"bonjour"@fr "hello"@en}.', ['a', 'b', '_:b0'], ['"bonjour"@fr', 'sameAs', '"hello"@en', '_:b0'] @@ -2043,7 +2097,7 @@ describe('Parser', () => { describe('A Parser instance for the N3Star format', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3Star' }); } - it('should parse RDF-star', + describe('should parse RDF-star', shouldParse(parser, '<< >> .', [['a', 'b', 'c'], 'a', 'b'])); @@ -2055,14 +2109,14 @@ describe('Parser', () => { describe('A Parser instance for the N3 format with the explicitQuantifiers option', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3', explicitQuantifiers: true }); } - it('should parse a @forSome statement', + describe('should parse a @forSome statement', shouldParse(parser, '@forSome . .', ['', 'http://www.w3.org/2000/10/swap/reify#forSome', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['x', 'x', 'x'])); - it('should parse a @forSome statement with multiple entities', + describe('should parse a @forSome statement with multiple entities', shouldParse(parser, '@prefix a: . @base . @forSome a:x, , a:z. a:x a:z.', ['', 'http://www.w3.org/2000/10/swap/reify#forSome', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a:x', 'urn:n3:quantifiers'], @@ -2085,14 +2139,14 @@ describe('Parser', () => { ['x', 'x', 'x', '_:b1'], ['x', 'x', 'x'])); - it('should parse a @forAll statement', + describe('should parse a @forAll statement', shouldParse(parser, '@forAll . .', ['', 'http://www.w3.org/2000/10/swap/reify#forAll', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['x', 'x', 'x'])); - it('should parse a @forAll statement with multiple entities', + describe('should parse a @forAll statement with multiple entities', shouldParse(parser, '@prefix a: . @base . @forAll a:x, , a:z. a:x a:z.', ['', 'http://www.w3.org/2000/10/swap/reify#forAll', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'a:x', 'urn:n3:quantifiers'], @@ -2564,7 +2618,31 @@ describe('Parser', () => { }); }); -function shouldParse(parser, input) { +// Split string into all combinations possible +function splitAllWays(result, left, right, chunkSize) { + // Push current left + right to the result list + result.push(left.concat(right)); + // document.write(left.concat(right) + '
'); + + // If we still have chars to work with in the right side then keep splitting + if (right.length > 1) { + // For each combination left/right split call splitAllWays() + for (let i = chunkSize; i < right.length; i += chunkSize) { + splitAllWays(result, left.concat(right.substring(0, i)), right.substring(i), chunkSize); + } + } + + // Return result + return result; +} + +// Return a large number of combinations for splitting the string to test chunking - any thing with 5 characters +// or less will test every permutation of splits possible on the string +function getSplits(str) { + return splitAllWays([], [], str, Math.max(Math.floor(str.length / 6), 1)); +} + +function shouldParseChunks(parser, input) { const expected = Array.prototype.slice.call(arguments, 1); // Shift parameters as necessary if (parser.call) @@ -2572,15 +2650,77 @@ function shouldParse(parser, input) { else input = parser, parser = Parser; + const items = expected.map(mapToQuad); + + return _shouldParseChunks(parser, input, items); +} + +function _shouldParseChunks(parser, input, items) { return function (done) { - const results = []; + const results2 = []; + + let onData, onEnd; + new parser({ baseIRI: BASE_IRI }).parse({ + baseIRI: BASE_IRI, + on: (event, callback) => { + switch (event) { + case 'data': onData = callback; break; + case 'end': onEnd = callback; break; + } + }, + }, + (error, triple) => { + expect(error).not.to.exist; + if (triple) + results2.push(triple); + else + toSortedJSON(results2).should.equal(toSortedJSON(items)), done(); + } + ); + + for (const chunk of input) { + onData(chunk); + } + + onEnd(); + }; +} + +function shouldParse(parser, input) { + return () => { + const expected = Array.prototype.slice.call(arguments, 1); + // Shift parameters as necessary + if (parser.call) + expected.shift(); + else + input = parser, parser = Parser; + const items = expected.map(mapToQuad); - new parser({ baseIRI: BASE_IRI }).parse(input, (error, triple) => { - expect(error).not.to.exist; - if (triple) - results.push(triple); - else + + for (const chunk of [ + // Split at every character + input.split(''), + // Random splits + ...getSplits(input), + // Exactly one split in each position + ...input.split('').map((_, i) => [input.slice(0, i), input.slice(i)]), + ] + // Ignore degenerate cases (for now) + .filter(arr => arr.length > 0 && (arr.length !== 1 || arr[0] !== '')) + ) { + it(`should run on chunking ${JSON.stringify(chunk)}`, _shouldParseChunks(parser, chunk, items)); + } + + it('should run on full string', done => { + // Test parsing of whole string + const results = []; + new parser({ baseIRI: BASE_IRI }).parse(input, (error, triple) => { + expect(error).not.to.exist; + if (triple) + results.push(triple); + else toSortedJSON(results).should.equal(toSortedJSON(items)), done(); + }); }); }; } From 6140e86140d0698a303555d82e5df738d7160962 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 4 Jan 2023 14:31:17 +1100 Subject: [PATCH 16/47] chore: remove doubling comment --- src/N3DataFactory.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/N3DataFactory.js b/src/N3DataFactory.js index 786a774b..ce472554 100644 --- a/src/N3DataFactory.js +++ b/src/N3DataFactory.js @@ -248,8 +248,6 @@ export function termToId(term, nested) { term.language ? `@${term.language}` : (term.datatype && term.datatype.value !== xsd.string ? `^^${term.datatype.value}` : '')}`; case 'Quad': - // To identify RDF-star quad components, we escape quotes by doubling them. - // This avoids the overhead of backslash parsing of Turtle-like syntaxes. const res = [ termToId(term.subject, true), termToId(term.predicate, true), From bec8395f00aa52317dcc4088cb00560634c8bd5d Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 4 Jan 2023 14:34:34 +1100 Subject: [PATCH 17/47] chore: add comment about nested parameter --- src/N3DataFactory.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/N3DataFactory.js b/src/N3DataFactory.js index ce472554..4c253a7c 100644 --- a/src/N3DataFactory.js +++ b/src/N3DataFactory.js @@ -188,6 +188,10 @@ export class DefaultGraph extends Term { DEFAULTGRAPH = new DefaultGraph(); // ### Constructs a term from the given internal string ID +// The third 'nested' parameter of this function is to aid +// with recursion over nested terms. It should not be used +// by consumers of this library. +// See https://github.com/rdfjs/N3.js/pull/311#discussion_r1061042725 export function termFromId(id, factory, nested) { factory = factory || DataFactory; @@ -230,6 +234,10 @@ export function termFromId(id, factory, nested) { } // ### Constructs an internal string ID from the given term or ID string +// The third 'nested' parameter of this function is to aid +// with recursion over nested terms. It should not be used +// by consumers of this library. +// See https://github.com/rdfjs/N3.js/pull/311#discussion_r1061042725 export function termToId(term, nested) { if (typeof term === 'string') return term; From 8ce8428800b2e76038801896b66e35801af6c1da Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 4 Jan 2023 15:00:45 +1100 Subject: [PATCH 18/47] fix: use describe for all shouldParse test suites --- test/N3Parser-test.js | 69 +++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 08a3bad9..1303665c 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -64,7 +64,7 @@ describe('Parser', () => { shouldParse('
"string"@en.', ['a', 'b', '"string"@en'])); - it('should normalize language codes to lowercase', + describe('should normalize language codes to lowercase', shouldParse(' "string"@EN.', ['a', 'b', '"string"@en'])); @@ -76,7 +76,7 @@ describe('Parser', () => { shouldParse('@prefix x: . "string"^^x:z.', ['a', 'b', '"string"^^urn:x:y#z'])); - it('should differentiate between IRI and prefixed name types', + describe('should differentiate between IRI and prefixed name types', shouldParse('@prefix : . :a :b "x"^^. :a :b "x"^^:urn:foo.', ['noturn:a', 'noturn:b', '"x"^^urn:foo'], ['noturn:a', 'noturn:b', '"x"^^noturn:urn:foo'])); @@ -369,7 +369,7 @@ describe('Parser', () => { ['_:b0', 'c', '_:b1'], ['_:b1', 'd', 'e'])); - it('should reuse identifiers of blank nodes within and outside of graphs', + describe('should reuse identifiers of blank nodes within and outside of graphs', shouldParse('_:a _:c. { _:a _:c }', ['_:b0_a', 'b', '_:b0_c'], ['_:b0_a', 'b', '_:b0_c', 'g'])); @@ -477,7 +477,7 @@ describe('Parser', () => { ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); describe('should parse a nested list', - shouldParse(' ( ( ) ).', + shouldParse(' ( ( ) ).', ['a', 'b', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], @@ -667,7 +667,7 @@ describe('Parser', () => { shouldNotParse(' (]).', 'Expected entity but got ] on line 1.')); - it('should resolve IRIs against @base', + describe('should resolve IRIs against @base', shouldParse('@base .\n' + ' .\n' + '@base .\n' + @@ -679,7 +679,7 @@ describe('Parser', () => { shouldNotParse('@BASE .', 'Expected entity but got @BASE on line 1.')); - it('should resolve IRIs against SPARQL base', + describe('should resolve IRIs against SPARQL base', shouldParse('BASE \n' + ' . ' + 'BASE ' + @@ -687,7 +687,7 @@ describe('Parser', () => { ['http://ex.org/a', 'http://ex.org/b', 'http://ex.org/c'], ['http://ex.org/d/e', 'http://ex.org/d/f', 'http://ex.org/d/g'])); - it('should resolve IRIs against a @base with query string', + describe('should resolve IRIs against a @base with query string', shouldParse('@base .\n' + '<> .\n' + '@base .\n' + @@ -695,7 +695,7 @@ describe('Parser', () => { ['http://ex.org/?foo', 'http://ex.org/b', 'http://ex.org/c'], ['http://ex.org/d/?bar', 'http://ex.org/d/f', 'http://ex.org/d/g'])); - it('should resolve IRIs with query string against @base', + describe('should resolve IRIs with query string against @base', shouldParse('@base .\n' + ' .\n' + '@base .\n' + @@ -706,7 +706,7 @@ describe('Parser', () => { ['http://ex.org/d?', 'http://ex.org/d?a', 'http://ex.org/d?a=b'], ['http://ex.org/d?e', 'http://ex.org/d?a', 'http://ex.org/d?a=b'])); - it('should not resolve IRIs with colons', + describe('should not resolve IRIs with colons', shouldParse('@base .\n' + ' .\n' + ' .\n' + @@ -723,7 +723,7 @@ describe('Parser', () => { shouldNotParse('@base .', 'Expected valid IRI to follow base declaration on line 1.')); - it('should resolve datatype IRIs against @base', + describe('should resolve datatype IRIs against @base', shouldParse('@base .\n' + ' "c"^^.\n' + '@base .\n' + @@ -731,17 +731,17 @@ describe('Parser', () => { ['http://ex.org/a', 'http://ex.org/b', '"c"^^http://ex.org/d'], ['http://ex.org/d/e', 'http://ex.org/d/f', '"g"^^http://ex.org/d/h'])); - it('should resolve IRIs against a base with a fragment', + describe('should resolve IRIs against a base with a fragment', shouldParse('@base .\n' + ' <#c>.\n', ['http://ex.org/a', 'http://ex.org/b', 'http://ex.org/foo#c'])); - it('should resolve IRIs with an empty fragment', + describe('should resolve IRIs with an empty fragment', shouldParse('@base .\n' + '<#> <#c>.\n', ['http://ex.org/foo#', 'http://ex.org/b#', 'http://ex.org/foo#c'])); - it('should not resolve prefixed names', + describe('should not resolve prefixed names', shouldParse('PREFIX ex: \n' + 'ex:a ex:b ex:c .', ['http://ex.org/a/bb/ccc/../a', 'http://ex.org/a/bb/ccc/../b', 'http://ex.org/a/bb/ccc/../c'])); @@ -1243,7 +1243,7 @@ describe('Parser', () => { [['a', 'b', 'c'], 'b', 'c'], [['a', 'b', 'c'], 'd', ['a', 'b', 'c']])); - it('should put nested triples in the default graph', + describe('should put nested triples in the default graph', shouldParse(' .\n<< >> .', ['a', 'b', 'c', 'g'], [['a', 'b', 'c'], 'd', 'e'])); @@ -1339,7 +1339,7 @@ describe('Parser', () => { describe('An Parser instance without document IRI', () => { function parser() { return new Parser(); } - it('should keep relative IRIs', + describe('should keep relative IRIs', shouldParse(parser, '@prefix : <#>.\n' + ' .\n' + @@ -1347,7 +1347,7 @@ describe('Parser', () => { [termFromId('a'), termFromId('b'), termFromId('c'), termFromId('g')], [termFromId('#d'), termFromId('#e'), termFromId('#f'), termFromId('#g')])); - it('should keep empty IRIs', + describe('should keep empty IRIs', shouldParse(parser, '@prefix : <>.\n' + '<> <> <> <>.\n' + @@ -1359,7 +1359,7 @@ describe('Parser', () => { describe('An Parser instance with a document IRI', () => { function parser() { return new Parser({ baseIRI: 'http://ex.org/x/yy/zzz/f.ttl' }); } - it('should resolve IRIs against the document IRI', + describe('should resolve IRIs against the document IRI', shouldParse(parser, '@prefix : <#>.\n' + ' .\n' + @@ -1367,47 +1367,47 @@ describe('Parser', () => { ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/b', 'http://ex.org/x/yy/zzz/c', 'http://ex.org/x/yy/zzz/g'], ['http://ex.org/x/yy/zzz/f.ttl#d', 'http://ex.org/x/yy/zzz/f.ttl#e', 'http://ex.org/x/yy/zzz/f.ttl#f', 'http://ex.org/x/yy/zzz/f.ttl#g'])); - it('should resolve IRIs with a trailing slash against the document IRI', + describe('should resolve IRIs with a trailing slash against the document IRI', shouldParse(parser, ' .\n', ['http://ex.org/a', 'http://ex.org/a/b', 'http://ex.org/a/b/c'])); - it('should resolve IRIs starting with ./ against the document IRI', + describe('should resolve IRIs starting with ./ against the document IRI', shouldParse(parser, '<./a> <./a/b> <./a/b/c>.\n', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/a/b', 'http://ex.org/x/yy/zzz/a/b/c'])); - it('should resolve IRIs starting with multiple ./ sequences against the document IRI', + describe('should resolve IRIs starting with multiple ./ sequences against the document IRI', shouldParse(parser, '<./././a> <./././././a/b> <././././././a/b/c>.\n', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/a/b', 'http://ex.org/x/yy/zzz/a/b/c'])); - it('should resolve IRIs starting with ../ against the document IRI', + describe('should resolve IRIs starting with ../ against the document IRI', shouldParse(parser, '<../a> <../a/b> <../a/b/c>.\n', ['http://ex.org/x/yy/a', 'http://ex.org/x/yy/a/b', 'http://ex.org/x/yy/a/b/c'])); - it('should resolve IRIs starting multiple ../ sequences against the document IRI', + describe('should resolve IRIs starting multiple ../ sequences against the document IRI', shouldParse(parser, '<../../a> <../../../a/b> <../../../../../../../../a/b/c>.\n', ['http://ex.org/x/a', 'http://ex.org/a/b', 'http://ex.org/a/b/c'])); - it('should resolve IRIs starting with mixes of ./ and ../ sequences against the document IRI', + describe('should resolve IRIs starting with mixes of ./ and ../ sequences against the document IRI', shouldParse(parser, '<.././a> <./.././a/b> <./.././.././a/b/c>.\n', ['http://ex.org/x/yy/a', 'http://ex.org/x/yy/a/b', 'http://ex.org/x/a/b/c'])); - it('should resolve IRIs starting with .x, ..x, or .../ against the document IRI', + describe('should resolve IRIs starting with .x, ..x, or .../ against the document IRI', shouldParse(parser, '<.x/a> <..x/a/b> <.../a/b/c>.\n', ['http://ex.org/x/yy/zzz/.x/a', 'http://ex.org/x/yy/zzz/..x/a/b', 'http://ex.org/x/yy/zzz/.../a/b/c'])); - it('should resolve datatype IRIs against the document IRI', + describe('should resolve datatype IRIs against the document IRI', shouldParse(parser, ' "c"^^.', ['http://ex.org/x/yy/zzz/a', 'http://ex.org/x/yy/zzz/b', '"c"^^http://ex.org/x/yy/zzz/d'])); - it('should resolve IRIs in lists against the document IRI', + describe('should resolve IRIs in lists against the document IRI', shouldParse(parser, '( )

( ).', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/a'], @@ -1420,7 +1420,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'http://ex.org/x/yy/zzz/d'], ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'])); - it('should respect @base statements', + describe('should respect @base statements', shouldParse(parser, ' .\n' + '@base .\n' + @@ -1553,7 +1553,7 @@ describe('Parser', () => { it('should parse a single triple chunked after an opening bracket', shouldParseChunks(parser, [' <', 'b> .'], ['a', 'b', 'c'])); - it('should parse a single triple', + describe('should parse a single triple', shouldParse(parser, ' .', ['a', 'b', 'c'])); describe('should parse a default graph', @@ -1758,13 +1758,13 @@ describe('Parser', () => { it('should not parse a quad', shouldNotParse(parser, ' .', 'Expected punctuation to follow "http://example.org/c" on line 1.')); - it('allows a blank node in predicate position', + describe('allows a blank node in predicate position', shouldParse(parser, ' [] .', ['a', '_:b0', 'c'])); - it('allows a blank node label in predicate position', + describe('allows a blank node label in predicate position', shouldParse(parser, ' _:b .', ['a', '_:b0_b', 'c'])); - it('allows a blank node with properties in predicate position', + describe('allows a blank node with properties in predicate position', shouldParse(parser, ' [

] .', ['a', '_:b0', 'c'], ['_:b0', 'p', 'o'])); @@ -1796,7 +1796,6 @@ describe('Parser', () => { ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1'])); - describe('should parse a right implication between two-triple graphs', shouldParse(parser, '{ ?a ?b . . } => { ?a, }.', ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], @@ -1872,7 +1871,7 @@ describe('Parser', () => { shouldNotParse(parser, '@forSome ?a.', 'Unexpected var on line 1.')); - it('should correctly scope @forSome statements', + describe('should correctly scope @forSome statements', shouldParse(parser, '@forSome . { @forSome . . }. .', ['_:b0', '_:b0', '_:b1'], ['_:b2', '_:b2', '_:b2', '_:b1'], @@ -1898,7 +1897,7 @@ describe('Parser', () => { shouldNotParse(parser, '@forAll ?a.', 'Unexpected var on line 1.')); - it('should correctly scope @forAll statements', + describe('should correctly scope @forAll statements', shouldParse(parser, '@forAll . { @forAll . . }. .', ['?b0', '?b0', '_:b1'], ['?b2', '?b2', '?b2', '_:b1'], @@ -2127,7 +2126,7 @@ describe('Parser', () => { ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'urn:n3:quantifiers'], ['a:x', 'b:y', 'a:z'])); - it('should correctly scope @forSome statements', + describe('should correctly scope @forSome statements', shouldParse(parser, '@forSome . { @forSome . . }. .', ['', 'http://www.w3.org/2000/10/swap/reify#forSome', '_:b0', 'urn:n3:quantifiers'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'x', 'urn:n3:quantifiers'], From 211bf07ea86f247001698561e910450a88572fa7 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 4 Jan 2023 15:09:59 +1100 Subject: [PATCH 19/47] fix: dont interpret }| as {| --- src/N3Lexer.js | 11 ++++++----- test/N3Parser-test.js | 6 ++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/N3Lexer.js b/src/N3Lexer.js index 6bfa2c81..97a692d0 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -294,18 +294,19 @@ export default class N3Lexer { case '!': if (!this._n3Mode) break; + case '{': + // Note the input[0] === '{' is required as this could be a fall-through from the above case + if (input.length > 1 && input[0] === '{' && input[1] === '|') { + type = '{|', matchLength = 2; + break; + } case ',': case ';': case '[': case ']': case '(': case ')': - case '{': case '}': - if (input.length > 1 && input[1] === '|') { - type = '{|', matchLength = 2; - break; - } if ( !this._lineMode && // The token might actually be {| and we just have not encountered the pipe yet diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 1303665c..92bc8970 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1253,6 +1253,12 @@ describe('Parser', () => { ['a', 'b', 'c'], [['a', 'b', 'c'], 'd', 'e'])); + it('should not parse }|', + shouldNotParse(' }| |} .', 'Unexpected graph closing on line 1.')); + + it('should not parse |{', + shouldNotParse(' {| |{ .', 'Unexpected "|{" on line 1.')); + it('should parse an explicit triple with reified annotation that is chunked at the pipe', shouldParseChunks([' {| |', '} .'], ['a', 'b', 'c'], From 9d8afd8250303c6e9ab2355d9f33bb19cb97d1f8 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Wed, 4 Jan 2023 21:10:48 +1100 Subject: [PATCH 20/47] perf: mint quad ids using term ids --- src/N3Store.js | 97 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index fb404b91..4f0e20cd 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -1,5 +1,6 @@ // **N3Store** objects store N3 quads by graph in memory. import { default as N3DataFactory, termToId, termFromId } from './N3DataFactory'; +import { isDefaultGraph } from './N3Util'; import { Readable } from 'readable-stream'; import namespaces from './IRIs'; @@ -30,6 +31,42 @@ export default class N3Store { this.addQuads(quads); } + _termFromId(id, factory) { + 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 termFromId(id, factory); + } + + _termToId(term) { + if (term.termType === 'Quad') { + const subject = this._termToId(term.subject), + predicate = this._termToId(term.predicate), + object = this._termToId(term.object), + graph = isDefaultGraph(term.graph) && this._termToId(term.graph), + ids = this._ids, + entities = this._entities; + + const res = `_${ + ids[subject] || (ids[entities[++this._id] = subject] = this._id) + }_${ + ids[predicate] || (ids[entities[++this._id] = predicate] = this._id) + }_${ + ids[object] || (ids[entities[++this._id] = object] = this._id) + }${graph ? ('_' + (ids[graph] || (ids[entities[++this._id] = graph] = this._id))) : ''}`; + return res; + } + return termToId(term); + } + // ## Public properties // ### `size` returns the number of quads in the store @@ -88,24 +125,24 @@ export default class N3Store { *_findInIndex(index0, key0, key1, key2, name0, name1, name2, graphId) { let tmp, index1, index2; const entityKeys = this._entities; - const graph = termFromId(graphId, this._factory); + const graph = this._termFromId(graphId, this._factory); 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] = termFromId(entityKeys[value0], this._factory); + parts[name0] = this._termFromId(entityKeys[value0], this._factory); // 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] = termFromId(entityKeys[value1], this._factory); + parts[name1] = this._termFromId(entityKeys[value1], this._factory); // 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] = termFromId(entityKeys[values[l]], this._factory); + parts[name2] = this._termFromId(entityKeys[values[l]], this._factory); yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph); } } @@ -190,7 +227,7 @@ export default class N3Store { return id => { if (!(id in uniqueIds)) { uniqueIds[id] = true; - callback(termFromId(this._entities[id], this._factory)); + callback(this._termFromId(this._entities[id], this._factory)); } }; } @@ -214,10 +251,10 @@ export default class N3Store { predicate = subject.predicate, subject = subject.subject; // Convert terms to internal string representation - subject = termToId(subject); - predicate = termToId(predicate); - object = termToId(object); - graph = termToId(graph); + subject = this._termToId(subject); + predicate = this._termToId(predicate); + object = this._termToId(object); + graph = this._termToId(graph); // Find the graph that will contain the triple let graphItem = this._graphs[graph]; @@ -281,10 +318,10 @@ export default class N3Store { predicate = subject.predicate, subject = subject.subject; // Convert terms to internal string representation - subject = termToId(subject); - predicate = termToId(predicate); - object = termToId(object); - graph = termToId(graph); + subject = this._termToId(subject); + predicate = this._termToId(predicate); + object = this._termToId(object); + graph = this._termToId(graph); // Find internal identifiers for all components // and verify the quad exists. @@ -350,10 +387,10 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. *readQuads(subject, predicate, object, graph) { // Convert terms to internal string representation - subject = subject && termToId(subject); - predicate = predicate && termToId(predicate); - object = object && termToId(object); - graph = graph && termToId(graph); + subject = subject && this._termToId(subject); + predicate = predicate && this._termToId(predicate); + object = object && this._termToId(object); + graph = graph && this._termToId(graph); const graphs = this._getGraphs(graph), ids = this._ids; let content, subjectId, predicateId, objectId; @@ -408,10 +445,10 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. countQuads(subject, predicate, object, graph) { // Convert terms to internal string representation - subject = subject && termToId(subject); - predicate = predicate && termToId(predicate); - object = object && termToId(object); - graph = graph && termToId(graph); + subject = subject && this._termToId(subject); + predicate = predicate && this._termToId(predicate); + object = object && this._termToId(object); + graph = graph && this._termToId(graph); const graphs = this._getGraphs(graph), ids = this._ids; let count = 0, content, subjectId, predicateId, objectId; @@ -490,9 +527,9 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forSubjects(callback, predicate, object, graph) { // Convert terms to internal string representation - predicate = predicate && termToId(predicate); - object = object && termToId(object); - graph = graph && termToId(graph); + predicate = predicate && this._termToId(predicate); + object = object && this._termToId(object); + graph = graph && this._termToId(graph); const ids = this._ids, graphs = this._getGraphs(graph); let content, predicateId, objectId; @@ -537,9 +574,9 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forPredicates(callback, subject, object, graph) { // Convert terms to internal string representation - subject = subject && termToId(subject); - object = object && termToId(object); - graph = graph && termToId(graph); + subject = subject && this._termToId(subject); + object = object && this._termToId(object); + graph = graph && this._termToId(graph); const ids = this._ids, graphs = this._getGraphs(graph); let content, subjectId, objectId; @@ -584,9 +621,9 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forObjects(callback, subject, predicate, graph) { // Convert terms to internal string representation - subject = subject && termToId(subject); - predicate = predicate && termToId(predicate); - graph = graph && termToId(graph); + subject = subject && this._termToId(subject); + predicate = predicate && this._termToId(predicate); + graph = graph && this._termToId(graph); const ids = this._ids, graphs = this._getGraphs(graph); let content, subjectId, predicateId; From 4489772688cc21390e1b804d9d3f42da1fc665a3 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 12:08:39 +1100 Subject: [PATCH 21/47] Update test/N3Parser-test.js Co-authored-by: Ted Thibodeau Jr --- test/N3Parser-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 92bc8970..52bfea4c 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -2641,8 +2641,8 @@ function splitAllWays(result, left, right, chunkSize) { return result; } -// Return a large number of combinations for splitting the string to test chunking - any thing with 5 characters -// or less will test every permutation of splits possible on the string +// Return a large number of combinations for splitting the string to test chunking - anything with 5 or fewer +// characters will test every permutation of splits possible on the string function getSplits(str) { return splitAllWays([], [], str, Math.max(Math.floor(str.length / 6), 1)); } From c76f3fdf1e489c530d2a658c0c463191d11af69d Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 12:08:53 +1100 Subject: [PATCH 22/47] Update src/N3Parser.js Co-authored-by: Ted Thibodeau Jr --- src/N3Parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 66eb7a07..1dc681fb 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -900,7 +900,7 @@ export default class N3Parser { if (token.type === '{|') { this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); - // As a convention we use set the graph term as the Default Graph in quads representing quoted triples + // As a convention, we set the graph term as the Default Graph in quads representing quoted triples // see https://github.com/rdfjs/N3.js/pull/311#discussion_r1061039556 for details this._subject = this._quad(this._subject, this._predicate, this._object, this.DEFAULTGRAPH); this._predicate = null; From 8a6dcbb853b9008189028869c7f6b483c1154270 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 12:09:06 +1100 Subject: [PATCH 23/47] Update src/N3Parser.js Co-authored-by: Ted Thibodeau Jr --- src/N3Parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 1dc681fb..131b31d8 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -641,7 +641,7 @@ export default class N3Parser { this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); - // As a convention we use set the graph term as the Default Graph in quads representing quoted triples + // As a convention, we set the graph term as the Default Graph in quads representing quoted triples // see https://github.com/rdfjs/N3.js/pull/311#discussion_r1061039556 for details this._subject = this._quad(this._subject, this._predicate, this._object, this.DEFAULTGRAPH); this._predicate = null; From d422319787a933191750a70488c233a6a249762d Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 12:19:39 +1100 Subject: [PATCH 24/47] fix: fix broken N3Store tests --- src/N3Store.js | 36 ++++++++++++++++++------------------ test/N3Store-test.js | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 4f0e20cd..d9f4e5d4 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -32,36 +32,36 @@ export default class N3Store { } _termFromId(id, factory) { - if (id[0] === '_') { + if (id[0] === '.') { const entities = this._entities; - const terms = id.split('_'); + 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]]), - ) + terms[4] && this._termFromId(entities[terms[4]]) + ); return q; } return termFromId(id, factory); } _termToId(term) { - if (term.termType === 'Quad') { + if (term && (term.termType === 'Quad')) { const subject = this._termToId(term.subject), - predicate = this._termToId(term.predicate), - object = this._termToId(term.object), - graph = isDefaultGraph(term.graph) && this._termToId(term.graph), - ids = this._ids, - entities = this._entities; - - const res = `_${ - ids[subject] || (ids[entities[++this._id] = subject] = this._id) - }_${ - ids[predicate] || (ids[entities[++this._id] = predicate] = this._id) - }_${ - ids[object] || (ids[entities[++this._id] = object] = this._id) - }${graph ? ('_' + (ids[graph] || (ids[entities[++this._id] = graph] = this._id))) : ''}`; + predicate = this._termToId(term.predicate), + object = this._termToId(term.object), + graph = !isDefaultGraph(term.graph) && this._termToId(term.graph), + ids = this._ids, + entities = this._entities; + + const res = `.${ + ids[subject] || (ids[entities[++this._id] = subject] = this._id) + }.${ + ids[predicate] || (ids[entities[++this._id] = predicate] = this._id) + }.${ + ids[object] || (ids[entities[++this._id] = object] = this._id) + }${graph ? (`.${ids[graph] || (ids[entities[++this._id] = graph] = this._id)}`) : ''}`; return res; } return termToId(term); diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 424880e2..9a223037 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -304,7 +304,7 @@ describe('Store', () => { }); it('should allow matching using a quad', done => { - const stream = store.removeMatches(termToId(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')))); + const stream = store.removeMatches(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1'))); stream.on('end', () => { store.size.should.eql(1); done(); From 49e9fb6f9b846a4a36eb9edc03ab95770e912acf Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 12:33:26 +1100 Subject: [PATCH 25/47] chore: improve tests range --- test/N3Store-test.js | 57 +++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 9a223037..55307e4a 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -277,28 +277,41 @@ describe('Store', () => { describe('removing matching quads for RDF-star', () => { let store; - beforeEach(() => { - store = new Store([ - new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p2'), new NamedNode('o1')), - new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p1'), new NamedNode('o1')), - new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p2'), new NamedNode('o2')), - new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p1'), new NamedNode('o2')), - new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')), - ]); + const allQuads = [ + new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p2'), new NamedNode('o1')), + new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p1'), new NamedNode('o1')), + new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p2'), new NamedNode('o2')), + new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p1'), new NamedNode('o2')), + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')), + ] + before(() => { + store = new Store(allQuads); }); - it('should return the removed quads', - forResultStream(shouldIncludeAll, () => { return store.removeMatches(null, 'p2', 'o2'); }, - [termToId(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1'))), 'p2', 'o2'])); + it('should start with the correct size', () => { + store.size.should.eql(5); + }); + + it('should return the removed quads', async () => { + const quads = await arrayifyStream(store.removeMatches(null, 'p2', 'o2')); + quads.length.should.equal(1); + quads[0].equals( + new Quad( + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), + new NamedNode('p2'), + new NamedNode('o2') + ) + ).should.equal(true); + }) it('should decrease the size', () => { - store.size.should.eql(5); + store.size.should.eql(4); }); it('should match RDF-star and normal quads at the same time', done => { const stream = store.removeMatches(null, 'p1', 'o2'); stream.on('end', () => { - store.size.should.eql(3); + store.size.should.eql(2); done(); }); }); @@ -306,7 +319,23 @@ describe('Store', () => { it('should allow matching using a quad', done => { const stream = store.removeMatches(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1'))); stream.on('end', () => { - store.size.should.eql(1); + store.size.should.eql(0); + done(); + }); + }); + + it('should allow matching using a quad and only match against relevant quads', done => { + const s2 = new Store([ + ...allQuads, + new Quad( + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')), + new NamedNode('p1'), + new NamedNode('o2')) + ]); + + const stream = s2.removeMatches(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1'))); + stream.on('end', () => { + s2.size.should.eql(2); done(); }); }); From 19ed891351b901bbd9cb7c96706c27fd7cdd07a5 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 13:35:52 +1100 Subject: [PATCH 26/47] chore: use _termToNumericId to convert Ids --- src/N3Store.js | 109 ++++++++++++++++++++----------------------- test/N3Store-test.js | 15 +++--- 2 files changed, 57 insertions(+), 67 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index d9f4e5d4..167ea2f7 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -1,6 +1,5 @@ // **N3Store** objects store N3 quads by graph in memory. import { default as N3DataFactory, termToId, termFromId } from './N3DataFactory'; -import { isDefaultGraph } from './N3Util'; import { Readable } from 'readable-stream'; import namespaces from './IRIs'; @@ -15,7 +14,6 @@ export default class N3Store { // saving memory by using only numbers as keys in `_graphs` this._id = 0; this._ids = Object.create(null); - this._ids['><'] = 0; // dummy entry, so the first actual key is non-zero this._entities = Object.create(null); // inverse of `_ids` // `_blankNodeIndex` is the index of the last automatically named blank node this._blankNodeIndex = 0; @@ -38,8 +36,7 @@ export default class N3Store { 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]]) + this._termFromId(entities[terms[3]]) ); return q; } @@ -47,26 +44,40 @@ export default class N3Store { } _termToId(term) { - if (term && (term.termType === 'Quad')) { - const subject = this._termToId(term.subject), - predicate = this._termToId(term.predicate), - object = this._termToId(term.object), - graph = !isDefaultGraph(term.graph) && this._termToId(term.graph), - ids = this._ids, - entities = this._entities; - + if (term && term.termType === 'Quad') { const res = `.${ - ids[subject] || (ids[entities[++this._id] = subject] = this._id) + this._termToNewNumericId(term.subject) }.${ - ids[predicate] || (ids[entities[++this._id] = predicate] = this._id) + this._termToNewNumericId(term.predicate) }.${ - ids[object] || (ids[entities[++this._id] = object] = this._id) - }${graph ? (`.${ids[graph] || (ids[entities[++this._id] = graph] = this._id)}`) : ''}`; + this._termToNewNumericId(term.object) + }`; return res; } return termToId(term); } + _termToNumericId(term) { + if (term.termType === 'Quad') { + const s = this._termToNumericId(term.subject), + p = this._termToNumericId(term.predicate), + o = this._termToNumericId(term.object); + + return s && p && o && this._ids[`.${s}.${p}.${o}`]; + } + + return this._ids[this._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)}` + : termToId(term); + + return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id); + } + // ## Public properties // ### `size` returns the number of quads in the store @@ -251,9 +262,6 @@ export default class N3Store { predicate = subject.predicate, subject = subject.subject; // Convert terms to internal string representation - subject = this._termToId(subject); - predicate = this._termToId(predicate); - object = this._termToId(object); graph = this._termToId(graph); // Find the graph that will contain the triple @@ -269,11 +277,9 @@ export default class N3Store { // 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. - const ids = this._ids; - const entities = this._entities; - subject = ids[subject] || (ids[entities[++this._id] = subject] = this._id); - predicate = ids[predicate] || (ids[entities[++this._id] = predicate] = this._id); - object = ids[object] || (ids[entities[++this._id] = object] = this._id); + subject = this._termToNewNumericId(subject); + predicate = this._termToNewNumericId(predicate); + object = this._termToNewNumericId(object); const changed = this._addToIndex(graphItem.subjects, subject, predicate, object); this._addToIndex(graphItem.predicates, predicate, object, subject); @@ -318,17 +324,14 @@ export default class N3Store { predicate = subject.predicate, subject = subject.subject; // Convert terms to internal string representation - subject = this._termToId(subject); - predicate = this._termToId(predicate); - object = this._termToId(object); graph = this._termToId(graph); // Find internal identifiers for all components // and verify the quad exists. - const ids = this._ids, graphs = this._graphs; + const graphs = this._graphs; let graphItem, subjects, predicates; - if (!(subject = ids[subject]) || !(predicate = ids[predicate]) || - !(object = ids[object]) || !(graphItem = graphs[graph]) || + if (!(subject = this._termToNewNumericId(subject)) || !(predicate = this._termToNewNumericId(predicate)) || + !(object = this._termToNewNumericId(object)) || !(graphItem = graphs[graph]) || !(subjects = graphItem.subjects[subject]) || !(predicates = subjects[predicate]) || !(object in predicates)) @@ -387,18 +390,15 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. *readQuads(subject, predicate, object, graph) { // Convert terms to internal string representation - subject = subject && this._termToId(subject); - predicate = predicate && this._termToId(predicate); - object = object && this._termToId(object); graph = graph && this._termToId(graph); - const graphs = this._getGraphs(graph), ids = this._ids; + const graphs = this._getGraphs(graph); let content, subjectId, predicateId, objectId; // Translate IRIs to internal index keys. - if (isString(subject) && !(subjectId = ids[subject]) || - isString(predicate) && !(predicateId = ids[predicate]) || - isString(object) && !(objectId = ids[object])) + if (subject && !(subjectId = this._termToNewNumericId(subject)) || + predicate && !(predicateId = this._termToNewNumericId(predicate)) || + object && !(objectId = this._termToNewNumericId(object))) return; for (const graphId in graphs) { @@ -445,18 +445,15 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. countQuads(subject, predicate, object, graph) { // Convert terms to internal string representation - subject = subject && this._termToId(subject); - predicate = predicate && this._termToId(predicate); - object = object && this._termToId(object); graph = graph && this._termToId(graph); - const graphs = this._getGraphs(graph), ids = this._ids; + const graphs = this._getGraphs(graph); let count = 0, content, subjectId, predicateId, objectId; // Translate IRIs to internal index keys. - if (isString(subject) && !(subjectId = ids[subject]) || - isString(predicate) && !(predicateId = ids[predicate]) || - isString(object) && !(objectId = ids[object])) + if (subject && !(subjectId = this._termToNewNumericId(subject)) || + predicate && !(predicateId = this._termToNewNumericId(predicate)) || + object && !(objectId = this._termToNewNumericId(object))) return 0; for (const graphId in graphs) { @@ -527,17 +524,15 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forSubjects(callback, predicate, object, graph) { // Convert terms to internal string representation - predicate = predicate && this._termToId(predicate); - object = object && this._termToId(object); graph = graph && this._termToId(graph); - const ids = this._ids, graphs = this._getGraphs(graph); + const graphs = this._getGraphs(graph); let content, predicateId, objectId; callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. - if (isString(predicate) && !(predicateId = ids[predicate]) || - isString(object) && !(objectId = ids[object])) + if (predicate && !(predicateId = this._termToNewNumericId(predicate)) || + object && !(objectId = this._termToNewNumericId(object))) return; for (graph in graphs) { @@ -574,17 +569,15 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forPredicates(callback, subject, object, graph) { // Convert terms to internal string representation - subject = subject && this._termToId(subject); - object = object && this._termToId(object); graph = graph && this._termToId(graph); - const ids = this._ids, graphs = this._getGraphs(graph); + const graphs = this._getGraphs(graph); let content, subjectId, objectId; callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. - if (isString(subject) && !(subjectId = ids[subject]) || - isString(object) && !(objectId = ids[object])) + if (subject && !(subjectId = this._termToNewNumericId(subject)) || + object && !(objectId = this._termToNewNumericId(object))) return; for (graph in graphs) { @@ -621,17 +614,15 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forObjects(callback, subject, predicate, graph) { // Convert terms to internal string representation - subject = subject && this._termToId(subject); - predicate = predicate && this._termToId(predicate); graph = graph && this._termToId(graph); - const ids = this._ids, graphs = this._getGraphs(graph); + const graphs = this._getGraphs(graph); let content, subjectId, predicateId; callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. - if (isString(subject) && !(subjectId = ids[subject]) || - isString(predicate) && !(predicateId = ids[predicate])) + if (subject && !(subjectId = this._termToNewNumericId(subject)) || + predicate && !(predicateId = this._termToNewNumericId(predicate))) return; for (graph in graphs) { diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 55307e4a..4f2b4779 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -283,7 +283,7 @@ describe('Store', () => { new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p2'), new NamedNode('o2')), new Quad(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), new NamedNode('p1'), new NamedNode('o2')), new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')), - ] + ]; before(() => { store = new Store(allQuads); }); @@ -292,17 +292,16 @@ describe('Store', () => { store.size.should.eql(5); }); - it('should return the removed quads', async () => { - const quads = await arrayifyStream(store.removeMatches(null, 'p2', 'o2')); + it('should return the removed quads', () => arrayifyStream(store.removeMatches(null, 'p2', 'o2')).then(quads => { quads.length.should.equal(1); quads[0].equals( new Quad( - new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), - new NamedNode('p2'), + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1')), + new NamedNode('p2'), new NamedNode('o2') ) ).should.equal(true); - }) + })); it('should decrease the size', () => { store.size.should.eql(4); @@ -330,8 +329,8 @@ describe('Store', () => { new Quad( new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')), new NamedNode('p1'), - new NamedNode('o2')) - ]); + new NamedNode('o2')), + ]); const stream = s2.removeMatches(new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o1'))); stream.on('end', () => { From 4ef4fc060eda0da2f8622ce308fa9d5d6c1accc4 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 14:26:58 +1100 Subject: [PATCH 27/47] chore: don't mint new ids unecessarily --- src/N3Store.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 167ea2f7..e09e88b0 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -37,6 +37,7 @@ export default class N3Store { this._termFromId(entities[terms[1]]), this._termFromId(entities[terms[2]]), this._termFromId(entities[terms[3]]) + // terms[4] && this._termFromId(entities[terms[4]]) ); return q; } @@ -45,6 +46,7 @@ export default class N3Store { _termToId(term) { if (term && term.termType === 'Quad') { + // const g = this._termToNewNumericId(term.graph) const res = `.${ this._termToNewNumericId(term.subject) }.${ @@ -330,8 +332,8 @@ export default class N3Store { // and verify the quad exists. const graphs = this._graphs; let graphItem, subjects, predicates; - if (!(subject = this._termToNewNumericId(subject)) || !(predicate = this._termToNewNumericId(predicate)) || - !(object = this._termToNewNumericId(object)) || !(graphItem = graphs[graph]) || + 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)) @@ -396,9 +398,9 @@ export default class N3Store { let content, subjectId, predicateId, objectId; // Translate IRIs to internal index keys. - if (subject && !(subjectId = this._termToNewNumericId(subject)) || - predicate && !(predicateId = this._termToNewNumericId(predicate)) || - object && !(objectId = this._termToNewNumericId(object))) + if (subject && !(subjectId = this._termToNumericId(subject)) || + predicate && !(predicateId = this._termToNumericId(predicate)) || + object && !(objectId = this._termToNumericId(object))) return; for (const graphId in graphs) { @@ -451,9 +453,9 @@ export default class N3Store { let count = 0, content, subjectId, predicateId, objectId; // Translate IRIs to internal index keys. - if (subject && !(subjectId = this._termToNewNumericId(subject)) || - predicate && !(predicateId = this._termToNewNumericId(predicate)) || - object && !(objectId = this._termToNewNumericId(object))) + if (subject && !(subjectId = this._termToNumericId(subject)) || + predicate && !(predicateId = this._termToNumericId(predicate)) || + object && !(objectId = this._termToNumericId(object))) return 0; for (const graphId in graphs) { @@ -531,8 +533,8 @@ export default class N3Store { callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. - if (predicate && !(predicateId = this._termToNewNumericId(predicate)) || - object && !(objectId = this._termToNewNumericId(object))) + if (predicate && !(predicateId = this._termToNumericId(predicate)) || + object && !(objectId = this._termToNumericId(object))) return; for (graph in graphs) { @@ -576,8 +578,8 @@ export default class N3Store { callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. - if (subject && !(subjectId = this._termToNewNumericId(subject)) || - object && !(objectId = this._termToNewNumericId(object))) + if (subject && !(subjectId = this._termToNumericId(subject)) || + object && !(objectId = this._termToNumericId(object))) return; for (graph in graphs) { @@ -621,8 +623,8 @@ export default class N3Store { callback = this._uniqueEntities(callback); // Translate IRIs to internal index keys. - if (subject && !(subjectId = this._termToNewNumericId(subject)) || - predicate && !(predicateId = this._termToNewNumericId(predicate))) + if (subject && !(subjectId = this._termToNumericId(subject)) || + predicate && !(predicateId = this._termToNumericId(predicate))) return; for (graph in graphs) { From 78f5acf19c2b5e0d60d4a23e2b756b5fb77cae62 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:44:41 +1100 Subject: [PATCH 28/47] feat: allow nested graph terms --- src/N3Store.js | 45 +++++++++++++++++--------------------------- test/N3Store-test.js | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index e09e88b0..aac1c4c7 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -2,6 +2,7 @@ import { default as N3DataFactory, termToId, termFromId } from './N3DataFactory'; import { Readable } from 'readable-stream'; import namespaces from './IRIs'; +import { isDefaultGraph } from './N3Util'; // ## Constructor export default class N3Store { @@ -36,45 +37,33 @@ export default class N3Store { 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]]) + this._termFromId(entities[terms[3]]), + terms[4] && this._termFromId(entities[terms[4]]) ); return q; } return termFromId(id, factory); } - _termToId(term) { - if (term && term.termType === 'Quad') { - // const g = this._termToNewNumericId(term.graph) - const res = `.${ - this._termToNewNumericId(term.subject) - }.${ - this._termToNewNumericId(term.predicate) - }.${ - this._termToNewNumericId(term.object) - }`; - return res; - } - return termToId(term); - } - _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 && this._ids[`.${s}.${p}.${o}`]; + return s && p && o && (isDefaultGraph(term.graph) || (g = this._termToNumericId(term.graph))) && + this._ids[g ? `.${s}.${p}.${o}.${g}` : `.${s}.${p}.${o}`]; } - - return this._ids[this._termToId(term)]; + return this._ids[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)}` + `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${ + isDefaultGraph(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}` + }` : termToId(term); return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id); @@ -264,7 +253,7 @@ export default class N3Store { predicate = subject.predicate, subject = subject.subject; // Convert terms to internal string representation - graph = this._termToId(graph); + graph = termToId(graph); // Find the graph that will contain the triple let graphItem = this._graphs[graph]; @@ -326,7 +315,7 @@ export default class N3Store { predicate = subject.predicate, subject = subject.subject; // Convert terms to internal string representation - graph = this._termToId(graph); + graph = termToId(graph); // Find internal identifiers for all components // and verify the quad exists. @@ -392,7 +381,7 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. *readQuads(subject, predicate, object, graph) { // Convert terms to internal string representation - graph = graph && this._termToId(graph); + graph = graph && termToId(graph); const graphs = this._getGraphs(graph); let content, subjectId, predicateId, objectId; @@ -447,7 +436,7 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. countQuads(subject, predicate, object, graph) { // Convert terms to internal string representation - graph = graph && this._termToId(graph); + graph = graph && termToId(graph); const graphs = this._getGraphs(graph); let count = 0, content, subjectId, predicateId, objectId; @@ -526,7 +515,7 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forSubjects(callback, predicate, object, graph) { // Convert terms to internal string representation - graph = graph && this._termToId(graph); + graph = graph && termToId(graph); const graphs = this._getGraphs(graph); let content, predicateId, objectId; @@ -571,7 +560,7 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forPredicates(callback, subject, object, graph) { // Convert terms to internal string representation - graph = graph && this._termToId(graph); + graph = graph && termToId(graph); const graphs = this._getGraphs(graph); let content, subjectId, objectId; @@ -616,7 +605,7 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. forObjects(callback, subject, predicate, graph) { // Convert terms to internal string representation - graph = graph && this._termToId(graph); + graph = graph && termToId(graph); const graphs = this._getGraphs(graph); let content, subjectId, predicateId; diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 4f2b4779..6de8a54d 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -340,6 +340,50 @@ describe('Store', () => { }); }); + // These tests should probably be broken in the future; they are here to serve to use that we should to a mver bump + // at such a time + describe('A store with quoted quads', () => { + let store; + beforeEach(() => { + store = new Store([ + new Quad( + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')), + new NamedNode('p1'), + new NamedNode('o2')), + new Quad( + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2'), new NamedNode('g')), + new NamedNode('p1'), + new NamedNode('o2')), + ]); + }); + + it('should have the correct size', () => { + store.size.should.equal(2); + }); + + it('should get all quads with shared predicate', () => { + store.getQuads(null, new NamedNode('p1'), null).length.should.equal(2); + }); + + it('should get all quads with shared predicate 2', () => { + store.getQuads( + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2')), + new NamedNode('p1'), + null + ).length.should.equal(1); + store.getQuads( + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2'), new NamedNode('g')), + new NamedNode('p1'), + null + ).length.should.equal(1); + store.getQuads( + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o2'), new NamedNode('g2')), + new NamedNode('p1'), + null + ).length.should.equal(0); + }); + }); + describe('A Store with 7 elements', () => { const store = new Store(); store.addQuad('s1', 'p1', 'o1').should.be.true; From 6b25c800567ac01bad2341aa63737716f77904a5 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:49:20 +1100 Subject: [PATCH 29/47] Update src/N3Parser.js Co-authored-by: Ted Thibodeau Jr --- src/N3Parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 66eb7a07..1dc681fb 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -900,7 +900,7 @@ export default class N3Parser { if (token.type === '{|') { this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); - // As a convention we use set the graph term as the Default Graph in quads representing quoted triples + // As a convention, we set the graph term as the Default Graph in quads representing quoted triples // see https://github.com/rdfjs/N3.js/pull/311#discussion_r1061039556 for details this._subject = this._quad(this._subject, this._predicate, this._object, this.DEFAULTGRAPH); this._predicate = null; From bf957a8e78b01a2f6b125ce562c6e2e1e7994c63 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:49:28 +1100 Subject: [PATCH 30/47] Update src/N3Parser.js Co-authored-by: Ted Thibodeau Jr --- src/N3Parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 1dc681fb..131b31d8 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -641,7 +641,7 @@ export default class N3Parser { this._saveContext('{|', this._graph, this._subject, this._predicate, this._object); - // As a convention we use set the graph term as the Default Graph in quads representing quoted triples + // As a convention, we set the graph term as the Default Graph in quads representing quoted triples // see https://github.com/rdfjs/N3.js/pull/311#discussion_r1061039556 for details this._subject = this._quad(this._subject, this._predicate, this._object, this.DEFAULTGRAPH); this._predicate = null; From a25f9e9ad7c140ef79a216b546fb8845e103e7fa Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 20:33:05 +1100 Subject: [PATCH 31/47] chore: add performance testing --- perf/N3StoreStar-perf.js | 118 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 perf/N3StoreStar-perf.js diff --git a/perf/N3StoreStar-perf.js b/perf/N3StoreStar-perf.js new file mode 100644 index 00000000..67e9d1a9 --- /dev/null +++ b/perf/N3StoreStar-perf.js @@ -0,0 +1,118 @@ +#!/usr/bin/env node +const N3 = require('..'); +const assert = require('assert'); + +console.log('N3Store performance test'); + +const prefix = 'http://example.org/#'; + +/* Test triples */ +const dim = Number.parseInt(process.argv[2], 10) || 22; +const dimSquared = dim * dim; +const dimCubed = dimSquared * dim; +const dimToTheFour = dimCubed * dim; +const dimToTheFive = dimToTheFour * dim; + +const store = new N3.Store(); +let TEST = `- Adding ${dimToTheFive} triples to the default graph`; +console.time(TEST); +let i, j, k, l, m; +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + for (l = 0; l < dim; l++) + for (m = 0; m < dim; m++) + store.addQuad( + N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), + N3.DataFactory.namedNode(prefix + l), + N3.DataFactory.namedNode(prefix + m) + ); +console.timeEnd(TEST); + +console.log(`* Memory usage for triples: ${Math.round(process.memoryUsage().rss / 1024 / 1024)}MB`); + +TEST = `- Finding all ${dimToTheFive} triples in the default graph ${dimSquared * 1} times (0 variables)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + for (l = 0; l < dim; l++) + for (m = 0; m < dim; m++) + assert.equal(store.getQuads( + N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), + N3.DataFactory.namedNode(prefix + l), + N3.DataFactory.namedNode(prefix + m) + ).length, 1); +console.timeEnd(TEST); + +TEST = `- Finding all ${dimCubed} triples in the default graph ${dimSquared * 2} times (1 variable subject)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + assert.equal(store.getQuads(null, N3.DataFactory.namedNode(prefix + i), N3.DataFactory.namedNode(prefix + j)).length, dimCubed); +console.timeEnd(TEST); + +TEST = `- Finding all ${0} triples in the default graph ${dimSquared * 2} times (1 variable predicate)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + assert.equal(store.getQuads(N3.DataFactory.namedNode(prefix + i), null, N3.DataFactory.namedNode(prefix + j)).length, 0); +console.timeEnd(TEST); + +TEST = `- Finding all ${dim} triples in the default graph ${dimSquared * 4} times (1 variable predicate)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + for (l = 0; l < dim; l++) + assert.equal(store.getQuads(N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), null, N3.DataFactory.namedNode(prefix + l)).length, dim); +console.timeEnd(TEST); + +TEST = `- Finding all ${0} triples in the default graph ${dimSquared * 2} times (1 variable object)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + assert.equal(store.getQuads(N3.DataFactory.namedNode(prefix + i), N3.DataFactory.namedNode(prefix + j), null).length, 0); +console.timeEnd(TEST); + +TEST = `- Finding all ${dim} triples in the default graph ${dimSquared * 4} times (1 variable objects)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + for (l = 0; l < dim; l++) + assert.equal(store.getQuads(N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), N3.DataFactory.namedNode(prefix + l), null).length, dim); +console.timeEnd(TEST); + +TEST = `- Finding all ${dimSquared} triples in the default graph ${dimSquared * 1} times (2 variables)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + assert.equal(store.getQuads( + N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), + null, + null + ).length, + dimSquared); +console.timeEnd(TEST); From a089fc74b0c6a7e5a1a01dc9020d3d1491d9302d Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 5 Jan 2023 21:38:23 +1100 Subject: [PATCH 32/47] chore: add performance test for limited annotations --- perf/N3StoreStarViews-perf.js | 118 ++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 perf/N3StoreStarViews-perf.js diff --git a/perf/N3StoreStarViews-perf.js b/perf/N3StoreStarViews-perf.js new file mode 100644 index 00000000..c3a8e08e --- /dev/null +++ b/perf/N3StoreStarViews-perf.js @@ -0,0 +1,118 @@ +#!/usr/bin/env node +const N3 = require('../lib'); +const assert = require('assert'); + +console.log('N3Store performance test'); + +const prefix = 'http://example.org/#'; + +/* Test triples */ +const dim = Number.parseInt(process.argv[2], 10) || 64; +const dimSquared = dim * dim; +const dimCubed = dimSquared * dim; +const dimToTheFour = dimCubed * dim; +const dimToTheFive = dimToTheFour * dim; + +const store = new N3.Store(); +let TEST = `- Adding ${dimToTheFive} triples to the default graph`; +console.time(TEST); +let i, j, k, l, m; +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + for (l = 0; l < 3; l++) + for (m = 0; m < 3; m++) + store.addQuad( + N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), + N3.DataFactory.namedNode(prefix + l), + N3.DataFactory.namedNode(prefix + m) + ); +console.timeEnd(TEST); + +console.log(`* Memory usage for triples: ${Math.round(process.memoryUsage().rss / 1024 / 1024)}MB`); + +TEST = `- Finding all ${dimToTheFive} triples in the default graph ${dimSquared * 1} times (0 variables)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + for (l = 0; l < 3; l++) + for (m = 0; m < 3; m++) + assert.equal(store.getQuads( + N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), + N3.DataFactory.namedNode(prefix + l), + N3.DataFactory.namedNode(prefix + m) + ).length, 1); +console.timeEnd(TEST); + +TEST = `- Finding all ${dimCubed} triples in the default graph ${dimSquared * 2} times (1 variable subject)`; +console.time(TEST); +for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + assert.equal(store.getQuads(null, N3.DataFactory.namedNode(prefix + i), N3.DataFactory.namedNode(prefix + j)).length, dimCubed); +console.timeEnd(TEST); + +TEST = `- Finding all ${0} triples in the default graph ${dimSquared * 2} times (1 variable predicate)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + assert.equal(store.getQuads(N3.DataFactory.namedNode(prefix + i), null, N3.DataFactory.namedNode(prefix + j)).length, 0); +console.timeEnd(TEST); + +TEST = `- Finding all ${3} triples in the default graph ${dimCubed * 3} times (1 variable predicate)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + for (l = 0; l < 3; l++) + assert.equal(store.getQuads(N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), null, N3.DataFactory.namedNode(prefix + l)).length, 3); +console.timeEnd(TEST); + +TEST = `- Finding all ${0} triples in the default graph ${dimSquared * 2} times (1 variable object)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + assert.equal(store.getQuads(N3.DataFactory.namedNode(prefix + i), N3.DataFactory.namedNode(prefix + j), null).length, 0); +console.timeEnd(TEST); + +TEST = `- Finding all ${3} triples in the default graph ${dimCubed * 3} times (1 variable objects)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + for (l = 0; l < 3; l++) + assert.equal(store.getQuads(N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), N3.DataFactory.namedNode(prefix + l), null).length, 3); +console.timeEnd(TEST); + +TEST = `- Finding all ${9} triples in the default graph ${dimCubed} times (2 variables)`; +console.time(TEST); +for (i = 0; i < dim; i++) + for (j = 0; j < dim; j++) + for (k = 0; k < dim; k++) + assert.equal(store.getQuads( + N3.DataFactory.quad( + N3.DataFactory.namedNode(prefix + i), + N3.DataFactory.namedNode(prefix + j), + N3.DataFactory.namedNode(prefix + k) + ), + null, + null + ).length, + 9); +console.timeEnd(TEST); From 7df44bc10d3dcd417e35719e39bb627d8fa434c9 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sat, 25 Feb 2023 15:26:27 +1100 Subject: [PATCH 33/47] chore: document writing rdf-star --- README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 60886684..e435a599 100644 --- a/README.md +++ b/README.md @@ -206,16 +206,28 @@ const writer2 = new N3.Writer({ format: 'application/trig' }); ```JavaScript const writer = new N3.Writer(process.stdout, { end: false, prefixes: { c: 'http://example.org/cartoons#' } }); -writer.addQuad( +writer.add(quad( namedNode('http://example.org/cartoons#Tom'), namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), namedNode('http://example.org/cartoons#Cat') -); -writer.addQuad(quad( +)); +writer.add(quad( namedNode('http://example.org/cartoons#Tom'), namedNode('http://example.org/cartoons#name'), literal('Tom') )); + +// Writing a quoted rdf-star triple +writer.add(quad( + quad( + namedNode('http://example.org/animals#Elephants'), + namedNode('http://example.org/skinAttribute#colour'), + namedNode('http://example.org/colours#blue'), + ), + namedNode('http://example.org/saidBy'), + namedNode('http://example.org/Jesse') +)); + writer.end(); ``` From 545e646e94dd87b313ab3a6a7a5a22bc65e1d4df Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sat, 25 Feb 2023 15:54:49 +1100 Subject: [PATCH 34/47] BREAKING CHANGE: enable rdfStar support by default --- src/N3Parser.js | 2 +- test/N3Parser-test.js | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 131b31d8..126f7d16 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -28,7 +28,7 @@ export default class N3Parser { // Support triples in other graphs this._supportsQuads = !(isTurtle || isTriG || isNTriples || isN3); // Support nesting of triples - this._supportsRDFStar = format === '' || /star|\*$/.test(format); + this._supportsRDFStar = options.rdfStar !== false; // Disable relative IRIs in N-Triples or N-Quads mode if (isLineMode) this._resolveRelativeIRI = iri => { return null; }; diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 52bfea4c..a4ce8377 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1470,7 +1470,7 @@ describe('Parser', () => { }); describe('A Parser instance for the Turtle format', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'Turtle' }); } + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'Turtle', rdfStar: false }); } describe('should parse a single triple', shouldParse(parser, ' .', ['a', 'b', 'c'])); @@ -1550,8 +1550,8 @@ describe('Parser', () => { 'Expected >> to follow "_:b0_b" but got IRI on line 1.')); }); - describe('A Parser instance for the TriG format', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TriG' }); } + describe('A Parser instance for the TriG format with rdfStar support disabled', () => { + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TriG', rdfStar: false }); } it('should parse a single triple chunked before a closing bracket', shouldParseChunks(parser, [' .'], ['a', 'b', 'c'])); @@ -1604,8 +1604,8 @@ describe('Parser', () => { 'Unexpected RDF-star syntax on line 1.')); }); - describe('A Parser instance for the TriGStar format', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TriGStar' }); } + describe('A Parser instance for the TriGS format testing rdfStar support', () => { + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'TriG' }); } describe('should parse RDF-star', shouldParse(parser, '<< >> .', @@ -1616,8 +1616,8 @@ describe('Parser', () => { 'Expected >> to follow "_:b0_b" but got IRI on line 1.')); }); - describe('A Parser instance for the N-Triples format', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-Triples' }); } + describe('A Parser instance for the N-Triples format with rdfStar support disabled', () => { + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-Triples', rdfStar: false }); } describe('should parse a single triple', shouldParse(parser, '_:a "c".', @@ -1679,8 +1679,8 @@ describe('Parser', () => { 'Unexpected RDF-star syntax on line 1.')); }); - describe('A Parser instance for the N-TriplesStar format', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-TriplesStar' }); } + describe('A Parser instance for the N-Triples format to test rdfStar support', () => { + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-Triples' }); } describe('should parse RDF-star', shouldParse(parser, '<<_:a _:c>> _:b .', @@ -1691,8 +1691,8 @@ describe('Parser', () => { 'Expected >> to follow "_:b0_b" but got IRI on line 1.')); }); - describe('A Parser instance for the N-Quads format', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-Quads' }); } + describe('A Parser instance for the N-Quads format with rdfStar support disabled', () => { + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N-Quads', rdfStar: false }); } describe('should parse a single triple', shouldParse(parser, '_:a "c".', @@ -1746,8 +1746,8 @@ describe('Parser', () => { [['_:b0_a', 'b', '_:b0_c'], 'a', '_:b0_c'])); }); - describe('A Parser instance for the N3 format', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3' }); } + describe('A Parser instance for the N3 format with rdfStar support disabled', () => { + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3', rdfStar: false }); } describe('should parse a single triple', shouldParse(parser, ' .', ['a', 'b', 'c'])); @@ -2099,8 +2099,8 @@ describe('Parser', () => { 'Unexpected RDF-star syntax on line 1.')); }); - describe('A Parser instance for the N3Star format', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3Star' }); } + describe('A Parser instance for the N3 format testing rdfStar support', () => { + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3' }); } describe('should parse RDF-star', shouldParse(parser, '<< >> .', From 90e15b9476698b16a0a1e290ac5117201be7c7b0 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sat, 25 Feb 2023 15:56:18 +1100 Subject: [PATCH 35/47] chore: update docs to not rdfStar default --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index e435a599..3eb3b824 100644 --- a/README.md +++ b/README.md @@ -377,9 +377,7 @@ The default mode is permissive and allows a mixture of different syntaxes, including RDF-star. Pass a `format` option to the constructor with the name or MIME type of a format for strict, fault-intolerant behavior. -If a format string contains `star` or `*` -(e.g., `turtlestar` or `TriG*`), -RDF-star support for that format will be enabled. +To disable RDF-star support pass `rdfStar` to `false` in the constructor. ### Interface specifications The N3.js submodules are compatible with the following [RDF.js](http://rdf.js.org) interfaces: From 6907296c805f0e84c9109e1f5bfadec2270e1c6e Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sat, 25 Feb 2023 16:13:57 +1100 Subject: [PATCH 36/47] chore: refactor lexer --- src/N3Lexer.js | 32 +++++++++++++++++--------------- src/N3Parser.js | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/N3Lexer.js b/src/N3Lexer.js index 97a692d0..cefe773a 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -53,6 +53,8 @@ export default class N3Lexer { this._endOfFile = /^(?:#[^\n\r]*)?$/; options = options || {}; + this._supportsRDFStar = options.rdfStar; + // In line mode (N-Triples or N-Quads), only simple features may be parsed if (this._lineMode = !!options.lineMode) { this._n3Mode = false; @@ -290,16 +292,22 @@ export default class N3Lexer { matchLength = 2, value = '>'; } break; - + case '{': + // We can properly evaluate this case if we are + // not in rdfStar mode or we can look ahead to + // see if there is a pipe following the { + if (input.length > 1 || !this._supportsRDFStar) { + if (input[1] === '|') { + type = '{|', matchLength = 2; + } + else if (!this._lineMode) { + matchLength = 1, type = firstChar; + } + } + break; case '!': if (!this._n3Mode) break; - case '{': - // Note the input[0] === '{' is required as this could be a fall-through from the above case - if (input.length > 1 && input[0] === '{' && input[1] === '|') { - type = '{|', matchLength = 2; - break; - } case ',': case ';': case '[': @@ -307,14 +315,8 @@ export default class N3Lexer { case '(': case ')': case '}': - if ( - !this._lineMode && - // The token might actually be {| and we just have not encountered the pipe yet - (input !== '{' || input.length > 1) - ) { - matchLength = 1; - type = firstChar; - } + matchLength = 1; + type = firstChar; break; case '|': if (input.length > 1 && input[1] === '}') { diff --git a/src/N3Parser.js b/src/N3Parser.js index 126f7d16..1aa32947 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -34,7 +34,7 @@ export default class N3Parser { this._resolveRelativeIRI = iri => { return null; }; this._blankNodePrefix = typeof options.blankNodePrefix !== 'string' ? '' : options.blankNodePrefix.replace(/^(?!_:)/, '_:'); - this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode, n3: isN3 }); + this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode, n3: isN3, rdfStar: this._supportsRDFStar }); // Disable explicit quantifiers by default this._explicitQuantifiers = !!options.explicitQuantifiers; } From 73791d82add83dbb1fdf5ce04915d31090daa94f Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 26 Feb 2023 11:54:26 +1100 Subject: [PATCH 37/47] fix: re-enable line mode check --- src/N3Lexer.js | 6 ++++-- test/N3Parser-test.js | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/N3Lexer.js b/src/N3Lexer.js index cefe773a..6ae7b559 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -315,8 +315,10 @@ export default class N3Lexer { case '(': case ')': case '}': - matchLength = 1; - type = firstChar; + if (!this._lineMode) { + matchLength = 1; + type = firstChar; + } break; case '|': if (input.length > 1 && input[1] === '}') { diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index a4ce8377..2166077e 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1670,6 +1670,9 @@ describe('Parser', () => { it('should not parse @forAll', shouldNotParse(parser, '@forAll .', 'Unexpected "@forAll" on line 1.')); + it('should not parse an object list', + shouldNotParse(parser, ' , .', 'Invalid IRI on line 1.')); + it('should not parse RDF-star in the subject position', shouldNotParse(parser, '<< >> .', 'Unexpected RDF-star syntax on line 1.')); From 8e3b997e144f38e0b49540243c748cfacd45ab0c Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 26 Feb 2023 12:07:04 +1100 Subject: [PATCH 38/47] chore: refactor lexer --- src/N3Lexer.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/N3Lexer.js b/src/N3Lexer.js index 6ae7b559..64080dee 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -296,14 +296,11 @@ export default class N3Lexer { // We can properly evaluate this case if we are // not in rdfStar mode or we can look ahead to // see if there is a pipe following the { - if (input.length > 1 || !this._supportsRDFStar) { - if (input[1] === '|') { - type = '{|', matchLength = 2; - } - else if (!this._lineMode) { - matchLength = 1, type = firstChar; - } - } + const long = input.length !== 1; + if (long && input[1] === '|') + matchLength = 2, type = '{|'; + else if (!this._lineMode && (long || !this._supportsRDFStar)) + matchLength = 1, type = firstChar; break; case '!': if (!this._n3Mode) From 2e655fba7fef05973102d45b8c352e662751cf72 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:04:55 +1100 Subject: [PATCH 39/47] fix: make rdf-star work with n3 paths --- src/N3Parser.js | 6 +++++- test/N3Parser-test.js | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 1aa32947..9dcd5c4c 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -886,7 +886,11 @@ export default class N3Parser { // If the triple was the subject, continue by reading the predicate. if (this._subject === null) { this._subject = quad; - return this._readPredicate; + // In N3 mode, the subject might be a path + if (this._n3Mode) + return this._getPathReader(this._readPredicateOrNamedGraph); + else + return this._readPredicate; } // If the triple was the object, read context end. else { diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 2166077e..85c5c474 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -2105,6 +2105,14 @@ describe('Parser', () => { describe('A Parser instance for the N3 format testing rdfStar support', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3' }); } + describe('should parse RDF-star path', + shouldParse(parser, '<< >>! .', + [['a', 'b', 'c'], 'p1', '_:b0'], ['_:b0', 'p2', 'o'])); + + describe('should parse RDF-star path', + shouldParse(parser, '<< >>!^ .', + [['a', 'b', 'c'], 'p1', '_:b0'], ['_:b1', 'p2', '_:b0'], ['_:b1', 'p3', 'o'])); + describe('should parse RDF-star', shouldParse(parser, '<< >> .', [['a', 'b', 'c'], 'a', 'b'])); From be1bcd92059f02b4b9e03421be783844a178eb96 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sat, 25 Mar 2023 03:12:29 +1100 Subject: [PATCH 40/47] fix: fix support for paths to support all usages supported by the N3 spec --- src/N3Parser.js | 27 +++++-- test/N3Parser-test.js | 164 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 178 insertions(+), 13 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 9dcd5c4c..5a3acd5d 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -443,14 +443,29 @@ export default class N3Parser { case ')': // Closing the list; restore the parent context this._restoreContext('list', token); - // If this list is contained within a parent list, return the membership quad here. - // This will be ` rdf:first .`. - if (stack.length !== 0 && stack[stack.length - 1].type === 'list') - this._emit(this._subject, this._predicate, this._object, this._graph); + if (stack.length !== 0 && stack[stack.length - 1].type === 'list') { + if (this._n3Mode) { + const { _subject, _predicate, _graph } = this; + if (this._object !== this.RDF_NIL) { + this._emit(previousList, this.RDF_REST, this.RDF_NIL, _graph); + } + return this._getPathReader(tk => { + // If this list is contained within a parent list, return the membership quad here. + // This will be ` rdf:first .`. + this._emit(_subject, _predicate, this._object, _graph); + return this._readListItem(tk); + }); + } + else { + // If this list is contained within a parent list, return the membership quad here. + // This will be ` rdf:first .`. + this._emit(this._subject, this._predicate, this._object, this._graph); + } + } // Was this list the parent's subject? if (this._predicate === null) { // The next token is the predicate - next = this._readPredicate; + next = this._n3Mode ? this._getPathReader(this._readPredicateOrNamedGraph) : this._readPredicate; // No list tail if this was an empty list if (this._subject === this.RDF_NIL) return next; @@ -518,7 +533,7 @@ export default class N3Parser { // If an item was read, add it to the list if (item !== null) { // In N3 mode, the item might be a path - if (this._n3Mode && (token.type === 'IRI' || token.type === 'prefixed')) { + if (this._n3Mode) { // Create a new context to add the item's path this._saveContext('item', this._graph, list, this.RDF_FIRST, item); this._subject = item, this._predicate = null; diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 85c5c474..df0b09e2 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1851,7 +1851,7 @@ describe('Parser', () => { ['?g', '?h', '?i', '_:b4'], ['?j', '?k', '?l', '_:b5'])); - it('should not reuse identifiers of blank nodes within and outside of formulas', + describe('should not reuse identifiers of blank nodes within and outside of formulas', shouldParse(parser, '_:a _:b _:c. { _:a _:b _:c } => { { _:a _:b _:c } => { _:a _:b _:c } }.', ['_:b0_a', '_:b0_b', '_:b0_c'], ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1', ''], @@ -2058,13 +2058,103 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b2', 'f:son', 'ex:joe'])); + describe('should parse a ! path of length 2 as subject in a list', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '(:joe!fam:mother) a fam:Person.', + ['ex:joe', 'f:mother', '_:b1'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); + + describe('should parse a !^ path of length 3 as subject in a list', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '(:joe!fam:mother^fam:father) a fam:Person.', + ['ex:joe', 'f:mother', '_:b1'], + ['_:b2', 'f:father', '_:b1'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b2'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); + + describe('should parse a ! path of length 2 starting with an empty list', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '()!fam:mother a fam:Person.', + ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b0'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); + + describe('should parse a ! path of length 2 starting with a non-empty list', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '( :a )!fam:mother a fam:Person.', + ...list(['_:b0', 'ex:a']), + ['_:b0', 'f:mother', '_:b1'], + ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); + + describe('should parse a ! path of length 2 starting with a non-empty list in another list', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '(( :a )!fam:mother 1) a fam:Person.', + ...list(['_:b0', '_:b2'], ['_:b3', '"1"^^http://www.w3.org/2001/XMLSchema#integer']), + ...list(['_:b1', 'ex:a']), + ['_:b1', 'f:mother', '_:b2'], + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); + + describe('should parse a ! path of length 2 starting with an empty list in another list', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '(()!fam:mother 1) a fam:Person.', + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'], + ...list(['_:b0', '_:b1'], ['_:b2', '"1"^^http://www.w3.org/2001/XMLSchema#integer']), + ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b1'] + )); + + describe('should parse a ! path of length 2 starting with an empty list in another list as second element', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '(1 ()!fam:mother) a fam:Person.', + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'], + ...list(['_:b0', '"1"^^http://www.w3.org/2001/XMLSchema#integer'], ['_:b1', '_:b2']), + ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b2'] + )); + + describe('should parse a ! path of length 2 starting with an empty list in another list of one element', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '(()!fam:mother) a fam:Person.', + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'], + ...list(['_:b0', '_:b1']), + ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b1'])); + + describe('should parse a ! path of length 2 as nested subject in a list', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '((:joe!fam:mother) 1) a fam:Person.', + ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'], + ...list(['_:b0', '_:b1'], ['_:b3', '"1"^^http://www.w3.org/2001/XMLSchema#integer']), + ...list(['_:b1', '_:b2']), + ['ex:joe', 'f:mother', '_:b2'])); + + describe('should parse a birthday rule', + shouldParse(parser, + '@prefix foaf: .' + + '@prefix math: .' + + '@prefix : .' + + '' + + '{' + + ' ?x :trueOnDate ?date.' + + '} <= {' + + ' ((?date ?s!foaf:birthday)!math:difference 31622400) math:integerQuotient ?age .' + + '} .', + ['?x', 'http://example.org/trueOnDate', '?date', '_:b0'], + // eslint-disable-next-line no-warning-comments + // FIXME: when merging with https://github.com/rdfjs/N3.js/pull/327 + ['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], + ...[ + ...list(['_:b2', '_:b6'], ['_:b7', '"31622400"^^http://www.w3.org/2001/XMLSchema#integer']), + ['_:b2', 'http://www.w3.org/2000/10/swap/math#integerQuotient', '?age'], + ...list(['_:b3', '?date'], ['_:b4', '_:b5']), + ['?s', 'http://xmlns.com/foaf/0.1/birthday', '_:b5'], + ['_:b3', 'http://www.w3.org/2000/10/swap/math#difference', '_:b6'], + ].map(elem => [...elem, '_:b1']) + )); + describe('should parse a formula as list item', shouldParse(parser, ' ( { a . } ).', ['a', 'findAll', '_:b0'], - ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'b'], - ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'], - ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'o'], - ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ...list(['_:b0', 'b'], ['_:b2', 'o']), ['b', 'something', 'foo', '_:b1'], ['b', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'type', '_:b1'] )); @@ -2120,6 +2210,55 @@ describe('Parser', () => { it('should not parse nested quads', shouldNotParse(parser, '<<_:a _:b >> "c" .', 'Expected >> to follow "_:.b" but got IRI on line 1.')); + + for (const [elem, value] of [ + ['()', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['( )', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['( )', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + ['', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], + [':joe', 'ex:joe'], + ['<<:joe a :Person>>', ['ex:joe', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:Person']], + ]) { + for (const pathType of ['!', '^']) { + // eslint-disable-next-line no-inner-declarations + function son(bnode) { + return pathType === '!' ? [value, 'f:son', `_:b${bnode}`] : [`_:b${bnode}`, 'f:son', value]; + } + + for (const [f, triple] of [ + [x => `(${x}) a :List .`, ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List']], + // [x => ` (${x}) .`, ['l', '_:b0', 'm']], + [x => ` (${x}) .`, ['l', 'is', '_:b0']], + ]) { + // eslint-disable-next-line no-inner-declarations + function check(content, ...triples) { + describe(`should parse [${f(content)}]`, + shouldParse(parser, `@prefix : . @prefix fam: .${f(content)}`, + triple, ...triples)); + } + + check(`${elem}${pathType}fam:son`, ...list(['_:b0', '_:b1']), son('1')); + check(`(${elem}${pathType}fam:son)`, ...list(['_:b0', '_:b1']), ...list(['_:b1', '_:b2']), son('2')); + + check(`${elem}${pathType}fam:son `, ...list(['_:b0', '_:b1'], ['_:b2', 'x'], ['_:b3', 'y']), son('1')); + check(` ${elem}${pathType}fam:son `, ...list(['_:b0', 'x'], ['_:b1', '_:b2'], ['_:b3', 'y']), son('2')); + check(` ${elem}${pathType}fam:son`, ...list(['_:b0', 'x'], ['_:b1', 'y'], ['_:b2', '_:b3']), son('3')); + + check(`(${elem}${pathType}fam:son) `, + ...list(['_:b0', '_:b1'], ['_:b3', 'x'], ['_:b4', 'y']), + ...list(['_:b1', '_:b2']), + son('2')); + check(` (${elem}${pathType}fam:son) `, + ...list(['_:b0', 'x'], ['_:b1', '_:b2'], ['_:b4', 'y']), + ...list(['_:b2', '_:b3']), + son('3')); + check(` (${elem}${pathType}fam:son)`, + ...list(['_:b0', 'x'], ['_:b1', 'y'], ['_:b2', '_:b3']), + ...list(['_:b3', '_:b4']), + son('4')); + } + } + } }); describe('A Parser instance for the N3 format with the explicitQuantifiers option', () => { @@ -2638,7 +2777,6 @@ describe('Parser', () => { function splitAllWays(result, left, right, chunkSize) { // Push current left + right to the result list result.push(left.concat(right)); - // document.write(left.concat(right) + '
'); // If we still have chars to work with in the right side then keep splitting if (right.length > 1) { @@ -2705,7 +2843,7 @@ function _shouldParseChunks(parser, input, items) { function shouldParse(parser, input) { return () => { const expected = Array.prototype.slice.call(arguments, 1); - // Shift parameters as necessary + // Shift parameters as necessary if (parser.call) expected.shift(); else @@ -2804,3 +2942,15 @@ function itShouldResolve(baseIRI, relativeIri, expected) { }); }); } + +// creates an RDF list from the input +function list(...elems) { + const arr = []; + for (let i = 0; i < elems.length; i++) { + arr.push( + [elems[i][0], 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', elems[i][1]], + [elems[i][0], 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', i + 1 === elems.length ? 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil' : elems[i + 1][0]] + ); + } + return arr; +} From 18826de0f1c0addc915c5c7086e95bf604c21943 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:21:40 +1100 Subject: [PATCH 41/47] fix: correctly handle named graphs in lists * chore: add test cases for named nodes in lists * chore: add more graph in list test cases * fix: correctly handle named graphs in lists --- src/N3Parser.js | 17 ++++++++++++----- test/N3Parser-test.js | 44 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 5a3acd5d..94ec5d84 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -418,9 +418,8 @@ export default class N3Parser { } // ### `_readListItem` reads items from a list - _readListItem(token) { - let item = null, // The item of the list - list = null, // The list itself + _readListItem(token, item = null /* The list item */) { + let list = null, // The list itself next = this._readListItem; // The next function to execute const previousList = this._subject, // The previous list that contains this list stack = this._contextStack, // The stack of parent contexts @@ -510,7 +509,7 @@ export default class N3Parser { this._graph = null; break; default: - if ((item = this._readEntity(token)) === undefined) + if (item === null && (item = this._readEntity(token)) === undefined) return; } @@ -613,12 +612,20 @@ export default class N3Parser { if (token.type !== '}') return this._readPunctuation(token); + const graph = this._graph; + // Store the last quad of the formula if (this._subject !== null) - this._emit(this._subject, this._predicate, this._object, this._graph); + this._emit(this._subject, this._predicate, this._object, graph); // Restore the parent context containing this formula this._restoreContext('formula', token); + + // If the formula was in a list context, continue reading the list + if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === 'list') { + return this._readListItem(token, graph); + } + // If the formula was the subject, continue reading the predicate. // If the formula was the object, read punctuation. return this._object === null ? this._readPredicate : this._getContextEndReader(); diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index df0b09e2..50640cf8 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1864,6 +1864,48 @@ describe('Parser', () => { shouldParse(parser, '@forSome . .', ['_:b0', '_:b0', '_:b0'])); + describe('should parse a named graph in a list', + shouldParse(parser, '

({ }) .', + ['s', 'p', '_:b1'], + ...list(['_:b1', '_:b0']), + ['a', 'b', 'c', '_:b0'] + )); + + describe('should parse a named graph as the second element in a list', + shouldParse(parser, '

( { }) .', + ['s', 'p', '_:b0'], + ...list(['_:b0', 'x'], ['_:b2', '_:b1']), + ['a', 'b', 'c', '_:b1'] + )); + + describe('should parse a named graph as the second element in a list of 3 elements', + shouldParse(parser, '

( { } ) .', + ['s', 'p', '_:b0'], + ...list(['_:b0', 'x'], ['_:b2', '_:b1'], ['_:b3', 'y']), + ['a', 'b', 'c', '_:b1'] + )); + + describe('should parse a named graph in a subject list', + shouldParse(parser, '({ })

.', + ['_:b1', 'p', 'o'], + ...list(['_:b1', '_:b0']), + ['a', 'b', 'c', '_:b0'] + )); + + describe('should parse a named graph as the second element in a subject list', + shouldParse(parser, '( { })

.', + ['_:b0', 'p', 'o'], + ...list(['_:b0', 'x'], ['_:b2', '_:b1']), + ['a', 'b', 'c', '_:b1'] + )); + + describe('should parse a named graph as the second element in a subject list with 3 elements', + shouldParse(parser, '( { } )

.', + ['_:b0', 'p', 'o'], + ...list(['_:b0', 'x'], ['_:b2', '_:b1'], ['_:b3', 'y']), + ['a', 'b', 'c', '_:b1'] + )); + describe('should parse a @forSome statement with multiple entities', shouldParse(parser, '@prefix a: . @base . @forSome a:x, , a:z. a:x a:z.', ['_:b0', '_:b1', '_:b2'])); @@ -2154,7 +2196,7 @@ describe('Parser', () => { describe('should parse a formula as list item', shouldParse(parser, ' ( { a . } ).', ['a', 'findAll', '_:b0'], - ...list(['_:b0', 'b'], ['_:b2', 'o']), + ...list(['_:b0', 'b'], ['_:b2', '_:b1'], ['_:b3', 'o']), ['b', 'something', 'foo', '_:b1'], ['b', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'type', '_:b1'] )); From 5d498131d8800e5d72800bd68b36478a11129367 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:21:57 +1100 Subject: [PATCH 42/47] Update README.md Co-authored-by: Ted Thibodeau Jr --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3eb3b824..ec5219f5 100644 --- a/README.md +++ b/README.md @@ -377,7 +377,7 @@ The default mode is permissive and allows a mixture of different syntaxes, including RDF-star. Pass a `format` option to the constructor with the name or MIME type of a format for strict, fault-intolerant behavior. -To disable RDF-star support pass `rdfStar` to `false` in the constructor. +To disable RDF-star support, pass `rdfStar` to `false` in the constructor. ### Interface specifications The N3.js submodules are compatible with the following [RDF.js](http://rdf.js.org) interfaces: From 47e41f0d09ebda80c3a46609e2b79363c85779f1 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:32:51 +1100 Subject: [PATCH 43/47] fix: support predicates in the list position in Notation3 (#345) * fix: support predicates in the list position in Notation3 * chore: remove TODOs --- src/N3Parser.js | 47 +++++++++++++++++++++++++++---------------- test/N3Parser-test.js | 42 +++++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 94ec5d84..db123a34 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -187,6 +187,17 @@ export default class N3Parser { return value; } + _readList(token, subject, predicate, object) { + // Lists are not allowed inside quoted triples + if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === '<<') { + return this._error('Unexpected list inside quoted triple', token); + } + // Start a new list + this._saveContext('list', this._graph, subject, predicate, object); + this._subject = null; + return this._readListItem; + } + // ### `_readSubject` reads a quad's subject _readSubject(token) { this._predicate = null; @@ -197,14 +208,7 @@ export default class N3Parser { this._subject = this._blankNode(), null, null); return this._readBlankNodeHead; case '(': - // Lists are not allowed inside quoted triples - if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === '<<') { - return this._error('Unexpected list inside quoted triple', token); - } - // Start a new list - this._saveContext('list', this._graph, this.RDF_NIL, null, null); - this._subject = null; - return this._readListItem; + return this._readList(token, this.RDF_NIL, null, null); case '{': // Start a new formula if (!this._n3Mode) @@ -282,6 +286,10 @@ export default class N3Parser { // Additional semicolons can be safely ignored return this._predicate !== null ? this._readPredicate : this._error('Expected predicate but got ;', token); + case '(': + return this._n3Mode ? + this._readList(token, this._subject, this.RDF_NIL, null) : + this._error(`Expected entity but got ${type}`, token); case '[': if (this._n3Mode) { // Start a new quad with a new blank node as subject @@ -319,14 +327,7 @@ export default class N3Parser { this._subject = this._blankNode()); return this._readBlankNodeHead; case '(': - // Lists are not allowed inside quoted triples - if (this._contextStack.length > 0 && this._contextStack[this._contextStack.length - 1].type === '<<') { - return this._error('Unexpected list inside quoted triple', token); - } - // Start a new list - this._saveContext('list', this._graph, this._subject, this._predicate, this.RDF_NIL); - this._subject = null; - return this._readListItem; + return this._readList(token, this._subject, this._predicate, this.RDF_NIL); case '{': // Start a new formula if (!this._n3Mode) @@ -468,6 +469,14 @@ export default class N3Parser { // No list tail if this was an empty list if (this._subject === this.RDF_NIL) return next; + // Was this list the parent's predicate? + } + else if (this._object === null) { + next = this._readObject; + + // No list tail if this was an empty list + if (this._predicate === this.RDF_NIL) + return next; } // The list was in the parent context's object else { @@ -519,9 +528,13 @@ export default class N3Parser { // Is this the first element of the list? if (previousList === null) { - // This list is either the subject or the object of its parent + // This list is the subject of the parent if (parent.predicate === null) parent.subject = list; + // The list is the predicate of the parent + else if (parent.object === null) + parent.predicate = list; + // The list is the object of the parent else parent.object = list; } diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 50640cf8..0fcd8366 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -2052,6 +2052,46 @@ describe('Parser', () => { ['_:b0', 'f:knows', '_:b1'], ['_:b1', 'f:son', 'ex:joe'])); + describe('should parse an empty list in the subject position', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '()

.', + ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'p', 'o'] + )); + + describe('should parse an empty list in the predicate position', + shouldParse(parser, '@prefix : . @prefix fam: .' + + ' () .', + ['s', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'o'] + )); + + describe('should parse an empty list in the object position', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '

() .', + ['s', 'p', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'] + )); + + describe('should parse a single element list in the subject position', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '( )

.', + ...list(['_:b0', 's']), + ['_:b0', 'p', 'o'] + )); + + + describe('should parse a single element list in the predicate position', + shouldParse(parser, '@prefix : . @prefix fam: .' + + ' (

) .', + ...list(['_:b0', 'p']), + ['s', '_:b0', 'o'] + )); + + describe('should parse a single element list in the object position', + shouldParse(parser, '@prefix : . @prefix fam: .' + + '

( ) .', + ...list(['_:b0', 'o']), + ['s', 'p', '_:b0'] + )); + describe('should parse a ! path in a list as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '( :joe!fam:mother ) a :List.', @@ -2269,7 +2309,7 @@ describe('Parser', () => { for (const [f, triple] of [ [x => `(${x}) a :List .`, ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List']], - // [x => ` (${x}) .`, ['l', '_:b0', 'm']], + [x => ` (${x}) .`, ['l', '_:b0', 'm']], [x => ` (${x}) .`, ['l', 'is', '_:b0']], ]) { // eslint-disable-next-line no-inner-declarations From e733c1f2e7127b6867a1781ea6a7311735ebe6f6 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:53:12 +1100 Subject: [PATCH 44/47] feat: parse subject and predicate literals in N3 mode (https://github.com/rdfjs/N3.js/pull/337) --- src/N3Parser.js | 41 +++++++++++++++++++++++++++--- test/N3Parser-test.js | 59 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index db123a34..4e544fa9 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -237,10 +237,12 @@ export default class N3Parser { if (!this._n3Mode) return this._error('Unexpected literal', token); + // Regular literal, can still get a datatype or language if (token.prefix.length === 0) { this._literalValue = token.value; return this._completeSubjectLiteral; } + // Pre-datatyped string literal (prefix stores the datatype) else this._subject = this._literal(token.value, this._namedNode(token.prefix)); @@ -274,6 +276,20 @@ export default class N3Parser { case 'abbreviation': this._predicate = this.ABBREVIATIONS[token.value]; break; + case 'literal': + if (!this._n3Mode) + return this._error('Unexpected literal', token); + + // Regular literal, can still get a datatype or language + if (token.prefix.length === 0) { + this._literalValue = token.value; + return this._completePredicateLiteral; + } + // Pre-datatyped string literal (prefix stores the datatype) + else + this._predicate = this._literal(token.value, this._namedNode(token.prefix)); + + break; case '.': case ']': case '}': @@ -593,10 +609,27 @@ export default class N3Parser { return { token, literal }; } - // Completes a literal in subject position - _completeSubjectLiteral(token) { - this._subject = this._completeLiteral(token).literal; - return this._readPredicateOrNamedGraph; + _completeSubjectLiteral(tkn) { + const { literal, token } = this._completeLiteral(tkn); + this._subject = literal; + + return token === null ? + // If the token was consumed, continue with the rest of the input + this._readPredicateOrNamedGraph : + // Otherwise, consume the token now + this._readPredicateOrNamedGraph(token); + } + + // Completes a literal in predicate position + _completePredicateLiteral(tkn) { + const { literal, token } = this._completeLiteral(tkn); + this._predicate = literal; + + return token === null ? + // If the token was consumed, continue with the rest of the input + this._readObject : + // Otherwise, consume the token now + this._readObject(token); } // Completes a literal in object position diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 0fcd8366..973f2198 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -81,6 +81,12 @@ describe('Parser', () => { ['noturn:a', 'noturn:b', '"x"^^urn:foo'], ['noturn:a', 'noturn:b', '"x"^^noturn:urn:foo'])); + it('should not parse literals with datatype as predicate', + shouldNotParse(' "a"^^ "b"^^.', 'Unexpected literal on line 1.')); + + it('should not parse literals without datatype as predicate', + shouldNotParse(' "a" "b".', 'Unexpected literal on line 1.')); + it('should not parse a triple with a literal and a prefixed name type with an inexistent prefix', shouldNotParse(' "string"^^x:z.', 'Undefined prefix "x:" on line 1.', { @@ -2254,6 +2260,59 @@ describe('Parser', () => { )); describe('should parse literals with datatype as subject', + shouldParse(parser, '"a"^^ .', + ['"a"^^http://example.org/c', 'greaterThan', 'd'] + )); + + + describe('should parse literals with datatype as subject and object', + shouldParse(parser, '"a"^^ "b"^^.', + ['"a"^^http://example.org/c', 'greaterThan', '"b"^^http://example.org/c'] + )); + + describe('should parse literals without datatype as subject and object', + shouldParse(parser, '"a" "b".', + ['"a"', 'greaterThan', '"b"'] + )); + + describe('should parse literals without datatype as subject', + shouldParse(parser, '"a" .', + ['"a"', 'greaterThan', 'b'] + )); + + describe('should parse literals with datatype as predicate', + shouldParse(parser, ' "a"^^ "b"^^.', + ['greaterThan', '"a"^^http://example.org/c', '"b"^^http://example.org/c'] + )); + + describe('should parse literals without datatype as predicate', + shouldParse(parser, ' "a" "b".', + ['greaterThan', '"a"', '"b"'] + )); + + describe('should parse subject, predicate, and object as integer', + shouldParse(parser, '1 1 1.', + ['"1"^^http://www.w3.org/2001/XMLSchema#integer', '"1"^^http://www.w3.org/2001/XMLSchema#integer', '"1"^^http://www.w3.org/2001/XMLSchema#integer'] + )); + + describe('should parse literals with integer as predicate', + shouldParse(parser, ' 1 "b".', + ['greaterThan', '"1"^^http://www.w3.org/2001/XMLSchema#integer', '"b"'] + )); + + describe('should parse literals with datatype as predicate in graph', + shouldParse(parser, ' { "a"^^ "b"^^}.', + ['x', 'y', '_:b0'], + ['greaterThan', '"a"^^http://example.org/c', '"b"^^http://example.org/c', '_:b0'] + )); + + describe('should parse literals without datatype as predicate in graph', + shouldParse(parser, ' { "a" "b"}.', + ['x', 'y', '_:b0'], + ['greaterThan', '"a"', '"b"', '_:b0'] + )); + + describe('should parse literals with datatype as subject in graph', shouldParse(parser, ' {"a"^^ "b"^^}.', ['a', 'b', '_:b0'], ['"a"^^http://example.org/c', 'greaterThan', '"b"^^http://example.org/c', '_:b0'] From f4887f353e9e21d0654fd2f41ad1d0d294f18ab2 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:59:33 +1100 Subject: [PATCH 45/47] BREAKING CHANGE: Make class Term constructors internal (#338) These constructors take the `id` at face value so can be interpreted as the wrong term type if an invalid value is given; for instance a `NamedNode` can be interpred as a `Literal` if instantiated with `new NamedNode('"hellow world"')`. Consumers of the library should use the `DataFactory` instead to create new terms. --- src/index.js | 27 --------------------------- test/BlankNode-test.js | 2 +- test/DefaultGraph-test.js | 2 +- test/Literal-test.js | 2 +- test/N3DataFactory-test.js | 4 ++-- test/N3Parser-test.js | 3 ++- test/N3Store-test.js | 6 ++++-- test/N3StreamParser-test.js | 3 ++- test/N3StreamWriter-test.js | 3 ++- test/N3Writer-test.js | 8 ++++---- test/NamedNode-test.js | 2 +- test/Quad-test.js | 2 +- test/Term-test.js | 10 +++++----- test/Variable-test.js | 2 +- 14 files changed, 27 insertions(+), 49 deletions(-) diff --git a/src/index.js b/src/index.js index 8307958e..579206a5 100644 --- a/src/index.js +++ b/src/index.js @@ -9,15 +9,6 @@ import * as Util from './N3Util'; import { default as DataFactory, - Term, - NamedNode, - Literal, - BlankNode, - Variable, - DefaultGraph, - Quad, - Triple, - termFromId, termToId, } from './N3DataFactory'; @@ -34,15 +25,6 @@ export { DataFactory, - Term, - NamedNode, - Literal, - BlankNode, - Variable, - DefaultGraph, - Quad, - Triple, - termFromId, termToId, }; @@ -59,15 +41,6 @@ export default { DataFactory, - Term, - NamedNode, - Literal, - BlankNode, - Variable, - DefaultGraph, - Quad, - Triple, - termFromId, termToId, }; diff --git a/test/BlankNode-test.js b/test/BlankNode-test.js index 093e16c9..ba0f70de 100644 --- a/test/BlankNode-test.js +++ b/test/BlankNode-test.js @@ -1,4 +1,4 @@ -import { BlankNode, Term } from '../src/'; +import { BlankNode, Term } from '../src/N3DataFactory'; describe('BlankNode', () => { describe('The BlankNode module', () => { diff --git a/test/DefaultGraph-test.js b/test/DefaultGraph-test.js index 93b54a2c..bdcc108e 100644 --- a/test/DefaultGraph-test.js +++ b/test/DefaultGraph-test.js @@ -1,4 +1,4 @@ -import { DefaultGraph, Term } from '../src/'; +import { DefaultGraph, Term } from '../src/N3DataFactory'; describe('DefaultGraph', () => { describe('The DefaultGraph module', () => { diff --git a/test/Literal-test.js b/test/Literal-test.js index 425477ab..4c9ce7cd 100644 --- a/test/Literal-test.js +++ b/test/Literal-test.js @@ -1,4 +1,4 @@ -import { Literal, NamedNode, Term } from '../src/'; +import { Literal, NamedNode, Term } from '../src/N3DataFactory'; describe('Literal', () => { describe('The Literal module', () => { diff --git a/test/N3DataFactory-test.js b/test/N3DataFactory-test.js index 6f55e358..583d300c 100644 --- a/test/N3DataFactory-test.js +++ b/test/N3DataFactory-test.js @@ -1,12 +1,12 @@ import { - DataFactory, NamedNode, Literal, BlankNode, Variable, DefaultGraph, Quad, -} from '../src/'; +} from '../src/N3DataFactory'; +import { DataFactory } from '../src'; describe('DataFactory', () => { describe('namedNode', () => { diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 973f2198..5e0f7114 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1,4 +1,5 @@ -import { Parser, NamedNode, BlankNode, Quad, termFromId } from '../src/'; +import { Parser, termFromId } from '../src/'; +import { NamedNode, BlankNode, Quad } from '../src/N3DataFactory'; const BASE_IRI = 'http://example.org/'; diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 6de8a54d..27e61e58 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -1,11 +1,13 @@ import { Store, + termFromId, termToId, +} from '../src/'; +import { NamedNode, Literal, DefaultGraph, Quad, - termFromId, termToId, -} from '../src/'; +} from '../src/N3DataFactory'; import namespaces from '../src/IRIs'; import chai, { expect } from 'chai'; import { Readable } from 'readable-stream'; diff --git a/test/N3StreamParser-test.js b/test/N3StreamParser-test.js index 355768d2..d78e4c65 100644 --- a/test/N3StreamParser-test.js +++ b/test/N3StreamParser-test.js @@ -1,4 +1,5 @@ -import { StreamParser, NamedNode } from '../src/'; +import { StreamParser } from '../src/'; +import { NamedNode } from '../src/N3DataFactory'; import { Readable, Writable } from 'readable-stream'; describe('StreamParser', () => { diff --git a/test/N3StreamWriter-test.js b/test/N3StreamWriter-test.js index c9796343..86ae9cee 100644 --- a/test/N3StreamWriter-test.js +++ b/test/N3StreamWriter-test.js @@ -1,4 +1,5 @@ -import { StreamWriter, Quad, NamedNode, termFromId } from '../src/'; +import { StreamWriter, termFromId } from '../src/'; +import { Quad, NamedNode } from '../src/N3DataFactory'; import { Readable, Writable } from 'readable-stream'; describe('StreamWriter', () => { diff --git a/test/N3Writer-test.js b/test/N3Writer-test.js index 33660145..e5281730 100644 --- a/test/N3Writer-test.js +++ b/test/N3Writer-test.js @@ -1,11 +1,11 @@ import { Writer, - NamedNode, - BlankNode, - Literal, - Quad, termFromId, } from '../src/'; +import { NamedNode, + BlankNode, + Literal, + Quad } from '../src/N3DataFactory'; import namespaces from '../src/IRIs'; const { xsd } = namespaces; diff --git a/test/NamedNode-test.js b/test/NamedNode-test.js index 64e06161..e0598f72 100644 --- a/test/NamedNode-test.js +++ b/test/NamedNode-test.js @@ -1,4 +1,4 @@ -import { NamedNode, Term } from '../src/'; +import { NamedNode, Term } from '../src/N3DataFactory'; describe('NamedNode', () => { describe('The NamedNode module', () => { diff --git a/test/Quad-test.js b/test/Quad-test.js index ec28d4f4..9ce0ce90 100644 --- a/test/Quad-test.js +++ b/test/Quad-test.js @@ -1,4 +1,4 @@ -import { Quad, Triple, DefaultGraph, termFromId, Term } from '../src/'; +import { Quad, Triple, DefaultGraph, termFromId, Term } from '../src/N3DataFactory'; describe('Quad', () => { describe('The Quad module', () => { diff --git a/test/Term-test.js b/test/Term-test.js index 750bc3e5..158bdccb 100644 --- a/test/Term-test.js +++ b/test/Term-test.js @@ -1,3 +1,8 @@ +import { + termToId, + termFromId, +} from '../src/'; + import { Term, NamedNode, @@ -6,11 +11,6 @@ import { Variable, DefaultGraph, Quad, - termToId, - termFromId, -} from '../src/'; - -import { escapeQuotes, unescapeQuotes, } from '../src/N3DataFactory'; diff --git a/test/Variable-test.js b/test/Variable-test.js index 8d7f64be..3343c8df 100644 --- a/test/Variable-test.js +++ b/test/Variable-test.js @@ -1,4 +1,4 @@ -import { Variable, Term } from '../src/'; +import { Variable, Term } from '../src/N3DataFactory'; describe('Variable', () => { describe('The Variable module', () => { From f3898fd705fff233344cf6a2f5e3aec4b564dde7 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:23:48 +1100 Subject: [PATCH 46/47] fix: use BlankNode according to RDF/JS spec (#346) Co-authored-by: Tomasz Pluskiewicz --- package-lock.json | 22 ++++++++++++++++++++++ package.json | 1 + src/N3Parser.js | 2 +- test/N3Parser-test.js | 17 +++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index c2f10e65..7e35113b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@babel/core": "^7.16.0", "@babel/preset-env": "^7.16.0", "@babel/register": "^7.16.0", + "@rdfjs/data-model": "^1", "arrayify-stream": "^1.0.0", "browserify": "^17.0.0", "chai": "^4.0.2", @@ -1734,6 +1735,18 @@ "dev": true, "optional": true }, + "node_modules/@rdfjs/data-model": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@rdfjs/data-model/-/data-model-1.3.4.tgz", + "integrity": "sha512-iKzNcKvJotgbFDdti7GTQDCYmL7GsGldkYStiP0K8EYtN7deJu5t7U11rKTz+nR7RtesUggT+lriZ7BakFv8QQ==", + "dev": true, + "dependencies": { + "@rdfjs/types": ">=1.0.1" + }, + "bin": { + "rdfjs-data-model-test": "bin/test.js" + } + }, "node_modules/@rdfjs/types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.1.0.tgz", @@ -9275,6 +9288,15 @@ "dev": true, "optional": true }, + "@rdfjs/data-model": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@rdfjs/data-model/-/data-model-1.3.4.tgz", + "integrity": "sha512-iKzNcKvJotgbFDdti7GTQDCYmL7GsGldkYStiP0K8EYtN7deJu5t7U11rKTz+nR7RtesUggT+lriZ7BakFv8QQ==", + "dev": true, + "requires": { + "@rdfjs/types": ">=1.0.1" + } + }, "@rdfjs/types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.1.0.tgz", diff --git a/package.json b/package.json index efde53b7..c7f999bf 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@babel/core": "^7.16.0", "@babel/preset-env": "^7.16.0", "@babel/register": "^7.16.0", + "@rdfjs/data-model": "^1", "arrayify-stream": "^1.0.0", "browserify": "^17.0.0", "chai": "^4.0.2", diff --git a/src/N3Parser.js b/src/N3Parser.js index 4e544fa9..d3b3bf38 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -86,7 +86,7 @@ export default class N3Parser { this._inversePredicate = false; // In N3, blank nodes are scoped to a formula // (using a dot as separator, as a blank node label cannot start with it) - this._prefixes._ = (this._graph ? `${this._graph.id.substr(2)}.` : '.'); + this._prefixes._ = (this._graph ? `${this._graph.value}.` : '.'); // Quantifiers are scoped to a formula this._quantified = Object.create(this._quantified); } diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 5e0f7114..d91250a6 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1,3 +1,4 @@ +import rdfDataModel from '@rdfjs/data-model'; import { Parser, termFromId } from '../src/'; import { NamedNode, BlankNode, Quad } from '../src/N3DataFactory'; @@ -2486,6 +2487,22 @@ describe('Parser', () => { }); }); + describe('A parser instance with external data factory', () => { + it('should parse', () => { + const parser = new Parser({ + baseIRI: BASE_IRI, + format: 'n3', + factory: rdfDataModel, + }); + const quads = parser.parse(` + @prefix : . + { :weather a :Raining } => { :weather a :Cloudy } . + `); + + quads.length.should.be.gt(0); + }); + }); + describe('IRI resolution', () => { describe('RFC3986 normal examples', () => { itShouldResolve('http://a/bb/ccc/d;p?q', 'g:h', 'g:h'); From fd6e7f1d5982fa113349a6ad03ad99e52db898d7 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:58:19 +1100 Subject: [PATCH 47/47] BREAKING CHANGE: parse <= as log:isImpliedBy (https://github.com/rdfjs/N3.js/pull/327) --- src/IRIs.js | 1 + src/N3Lexer.js | 10 +- src/N3Parser.js | 5 +- test/N3Lexer-test.js | 24 ++++- test/N3Parser-test.js | 209 +++++++++++++++++++++--------------------- 5 files changed, 139 insertions(+), 110 deletions(-) diff --git a/src/IRIs.js b/src/IRIs.js index 774d8575..0120a9eb 100644 --- a/src/IRIs.js +++ b/src/IRIs.js @@ -26,5 +26,6 @@ export default { }, log: { implies: `${SWAP}log#implies`, + isImpliedBy: `${SWAP}log#isImpliedBy`, }, }; diff --git a/src/N3Lexer.js b/src/N3Lexer.js index 64080dee..9844a55d 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -53,6 +53,9 @@ export default class N3Lexer { this._endOfFile = /^(?:#[^\n\r]*)?$/; options = options || {}; + // Whether the log:isImpliedBy predicate is supported + this._isImpliedBy = options.isImpliedBy !== false; + this._supportsRDFStar = options.rdfStar; // In line mode (N-Triples or N-Quads), only simple features may be parsed @@ -153,8 +156,11 @@ export default class N3Lexer { else if (input.length > 1 && input[1] === '<') type = '<<', matchLength = 2; // Try to find a backwards implication arrow - else if (this._n3Mode && input.length > 1 && input[1] === '=') - type = 'inverse', matchLength = 2, value = '>'; + else if (this._n3Mode && input.length > 1 && input[1] === '=') { + matchLength = 2; + if (this._isImpliedBy) type = 'abbreviation', value = '<'; + else type = 'inverse', value = '>'; + } break; case '>': diff --git a/src/N3Parser.js b/src/N3Parser.js index d3b3bf38..7537661b 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -29,12 +29,14 @@ export default class N3Parser { this._supportsQuads = !(isTurtle || isTriG || isNTriples || isN3); // Support nesting of triples this._supportsRDFStar = options.rdfStar !== false; + // Whether the log:isImpliedBy predicate is supported + this._isImpliedBy = options.isImpliedBy !== false; // Disable relative IRIs in N-Triples or N-Quads mode if (isLineMode) this._resolveRelativeIRI = iri => { return null; }; this._blankNodePrefix = typeof options.blankNodePrefix !== 'string' ? '' : options.blankNodePrefix.replace(/^(?!_:)/, '_:'); - this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode, n3: isN3, rdfStar: this._supportsRDFStar }); + this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode, n3: isN3, rdfStar: this._supportsRDFStar, isImpliedBy: this._isImpliedBy }); // Disable explicit quantifiers by default this._explicitQuantifiers = !!options.explicitQuantifiers; } @@ -1180,6 +1182,7 @@ function initDataFactory(parser, factory) { 'a': namedNode(namespaces.rdf.type), '=': namedNode(namespaces.owl.sameAs), '>': namedNode(namespaces.log.implies), + '<': namedNode(namespaces.log.isImpliedBy), }; parser.QUANTIFIERS_GRAPH = namedNode('urn:n3:quantifiers'); } diff --git a/test/N3Lexer-test.js b/test/N3Lexer-test.js index 13b6730b..ffffc3dc 100644 --- a/test/N3Lexer-test.js +++ b/test/N3Lexer-test.js @@ -802,16 +802,30 @@ describe('Lexer', () => { it('should tokenize the left implication', shouldTokenize(' <= ', { type: 'IRI', value: 'a', line: 1 }, - { type: 'inverse', value: '>', line: 1 }, + { type: 'abbreviation', value: '<', line: 1 }, { type: 'IRI', value: 'b', line: 1 }, { type: 'eof', line: 1 })); it('should tokenize a split left implication', shouldTokenize(streamOf(' <', '= '), - { type: 'IRI', value: 'a', line: 1 }, - { type: 'inverse', value: '>', line: 1 }, - { type: 'IRI', value: 'b', line: 1 }, - { type: 'eof', line: 1 })); + { type: 'IRI', value: 'a', line: 1 }, + { type: 'abbreviation', value: '<', line: 1 }, + { type: 'IRI', value: 'b', line: 1 }, + { type: 'eof', line: 1 })); + + it('should tokenize a split left implication as inverse of [=>] when isImpliedBy is disabled', + shouldTokenize(new Lexer({ isImpliedBy: false }), streamOf(' <', '= '), + { type: 'IRI', value: 'a', line: 1 }, + { type: 'inverse', value: '>', line: 1 }, + { type: 'IRI', value: 'b', line: 1 }, + { type: 'eof', line: 1 })); + + it('should tokenize a left implication as inverse of [=>] when isImpliedBy is disabled', + shouldTokenize(new Lexer({ isImpliedBy: false }), streamOf(' <= '), + { type: 'IRI', value: 'a', line: 1 }, + { type: 'inverse', value: '>', line: 1 }, + { type: 'IRI', value: 'b', line: 1 }, + { type: 'eof', line: 1 })); it('should tokenize paths', shouldTokenize(':joe!fam:mother!loc:office!loc:zip :joe!fam:mother^fam:mother', diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index d91250a6..5a5eca47 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1757,63 +1757,69 @@ describe('Parser', () => { [['_:b0_a', 'b', '_:b0_c'], 'a', '_:b0_c'])); }); - describe('A Parser instance for the N3 format with rdfStar support disabled', () => { - function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3', rdfStar: false }); } + for (const isImpliedBy of [true, false]) { + // eslint-disable-next-line no-inner-declarations + function implies(from, to) { + return isImpliedBy ? [to, 'http://www.w3.org/2000/10/swap/log#isImpliedBy', from] : [from, 'http://www.w3.org/2000/10/swap/log#implies', to]; + } - describe('should parse a single triple', + describe(`A Parser instance for the N3 format with rdfStar support disabled and with ${isImpliedBy ? 'enabled' : 'disabled'}`, () => { + function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3', rdfStar: false, isImpliedBy }); } + + describe('should parse a single triple', shouldParse(parser, ' .', ['a', 'b', 'c'])); - it('should not parse a default graph', + it('should not parse a default graph', shouldNotParse(parser, '{}', 'Expected entity but got eof on line 1.')); - it('should not parse a named graph', + it('should not parse a named graph', shouldNotParse(parser, ' {}', 'Expected entity but got { on line 1.')); - it('should not parse a named graph with the GRAPH keyword', + it('should not parse a named graph with the GRAPH keyword', shouldNotParse(parser, 'GRAPH {}', 'Expected entity but got GRAPH on line 1.')); - it('should not parse a quad', + it('should not parse a quad', shouldNotParse(parser, ' .', 'Expected punctuation to follow "http://example.org/c" on line 1.')); - describe('allows a blank node in predicate position', + describe('allows a blank node in predicate position', shouldParse(parser, ' [] .', ['a', '_:b0', 'c'])); - describe('allows a blank node label in predicate position', + describe('allows a blank node label in predicate position', shouldParse(parser, ' _:b .', ['a', '_:b0_b', 'c'])); - describe('allows a blank node with properties in predicate position', + describe('allows a blank node with properties in predicate position', shouldParse(parser, ' [

] .', ['a', '_:b0', 'c'], ['_:b0', 'p', 'o'])); - describe('should parse a variable', + describe('should parse a variable', shouldParse(parser, '?a ?b ?c.', ['?a', '?b', '?c'])); - describe('should parse a simple equality', + describe('should parse a simple equality', shouldParse(parser, ' = .', ['a', 'http://www.w3.org/2002/07/owl#sameAs', 'b'])); - describe('should parse a simple right implication', + describe('should parse a simple right implication', shouldParse(parser, ' => .', ['a', 'http://www.w3.org/2000/10/swap/log#implies', 'b'])); - describe('should parse a simple left implication', + describe('should parse a simple left implication', shouldParse(parser, ' <= .', - ['b', 'http://www.w3.org/2000/10/swap/log#implies', 'a'])); + implies('b', 'a'))); - describe('should parse a right implication between one-triple graphs', + describe('should parse a right implication between one-triple graphs', shouldParse(parser, '{ ?a ?b . } => { ?a }.', ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1'])); - it('should parse a right implication between one-triple graphs with chunk at first bracket', + it('should parse a right implication between one-triple graphs with chunk at first bracket', shouldParseChunks(parser, ['{', ' ?a ?b . } => { ?a }.'], ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1'])); - describe('should parse a right implication between two-triple graphs', + describe('should parse a right implication between two-triple graphs', shouldParse(parser, '{ ?a ?b . . } => { ?a, }.', ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1'], ['?a', '?b', 'c', '_:b0'], @@ -1821,27 +1827,27 @@ describe('Parser', () => { ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1'])); - describe('should parse a left implication between one-triple graphs', + describe('should parse a left implication between one-triple graphs', shouldParse(parser, '{ ?a ?b . } <= { ?a }.', - ['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], + implies('_:b1', '_:b0'), ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1'])); - describe('should parse a left implication between two-triple graphs', + describe('should parse a left implication between two-triple graphs', shouldParse(parser, '{ ?a ?b . . } <= { ?a, }.', - ['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], + implies('_:b1', '_:b0'), ['?a', '?b', 'c', '_:b0'], ['d', 'e', 'f', '_:b0'], ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1'])); - describe('should parse an equality of one-triple graphs', + describe('should parse an equality of one-triple graphs', shouldParse(parser, '{ ?a ?b . } = { ?a }.', ['_:b0', 'http://www.w3.org/2002/07/owl#sameAs', '_:b1'], ['?a', '?b', 'c', '_:b0'], ['d', 'e', '?a', '_:b1'])); - describe('should parse an equality of two-triple graphs', + describe('should parse an equality of two-triple graphs', shouldParse(parser, '{ ?a ?b . . } = { ?a, }.', ['_:b0', 'http://www.w3.org/2002/07/owl#sameAs', '_:b1'], ['?a', '?b', 'c', '_:b0'], @@ -1849,17 +1855,17 @@ describe('Parser', () => { ['d', 'e', '?a', '_:b1'], ['d', 'e', 'f', '_:b1'])); - describe('should parse nested implication graphs', + describe('should parse nested implication graphs', shouldParse(parser, '{ { ?a ?b ?c }<={ ?d ?e ?f }. } <= { { ?g ?h ?i } => { ?j ?k ?l } }.', - ['_:b3', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], - ['_:b2', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1', '_:b0'], + implies('_:b3', '_:b0'), + [...implies('_:b2', '_:b1'), '_:b0'], ['?a', '?b', '?c', '_:b1'], ['?d', '?e', '?f', '_:b2'], ['_:b4', 'http://www.w3.org/2000/10/swap/log#implies', '_:b5', '_:b3'], ['?g', '?h', '?i', '_:b4'], ['?j', '?k', '?l', '_:b5'])); - describe('should not reuse identifiers of blank nodes within and outside of formulas', + describe('should not reuse identifiers of blank nodes within and outside of formulas', shouldParse(parser, '_:a _:b _:c. { _:a _:b _:c } => { { _:a _:b _:c } => { _:a _:b _:c } }.', ['_:b0_a', '_:b0_b', '_:b0_c'], ['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1', ''], @@ -1868,107 +1874,107 @@ describe('Parser', () => { ['_:b2.a', '_:b2.b', '_:b2.c', '_:b2'], ['_:b3.a', '_:b3.b', '_:b3.c', '_:b3'])); - describe('should parse a @forSome statement', + describe('should parse a @forSome statement', shouldParse(parser, '@forSome . .', ['_:b0', '_:b0', '_:b0'])); - describe('should parse a named graph in a list', + describe('should parse a named graph in a list', shouldParse(parser, '

({ }) .', ['s', 'p', '_:b1'], ...list(['_:b1', '_:b0']), ['a', 'b', 'c', '_:b0'] )); - describe('should parse a named graph as the second element in a list', + describe('should parse a named graph as the second element in a list', shouldParse(parser, '

( { }) .', ['s', 'p', '_:b0'], ...list(['_:b0', 'x'], ['_:b2', '_:b1']), ['a', 'b', 'c', '_:b1'] )); - describe('should parse a named graph as the second element in a list of 3 elements', + describe('should parse a named graph as the second element in a list of 3 elements', shouldParse(parser, '

( { } ) .', ['s', 'p', '_:b0'], ...list(['_:b0', 'x'], ['_:b2', '_:b1'], ['_:b3', 'y']), ['a', 'b', 'c', '_:b1'] )); - describe('should parse a named graph in a subject list', + describe('should parse a named graph in a subject list', shouldParse(parser, '({ })

.', ['_:b1', 'p', 'o'], ...list(['_:b1', '_:b0']), ['a', 'b', 'c', '_:b0'] )); - describe('should parse a named graph as the second element in a subject list', + describe('should parse a named graph as the second element in a subject list', shouldParse(parser, '( { })

.', ['_:b0', 'p', 'o'], ...list(['_:b0', 'x'], ['_:b2', '_:b1']), ['a', 'b', 'c', '_:b1'] )); - describe('should parse a named graph as the second element in a subject list with 3 elements', + describe('should parse a named graph as the second element in a subject list with 3 elements', shouldParse(parser, '( { } )

.', ['_:b0', 'p', 'o'], ...list(['_:b0', 'x'], ['_:b2', '_:b1'], ['_:b3', 'y']), ['a', 'b', 'c', '_:b1'] )); - describe('should parse a @forSome statement with multiple entities', + describe('should parse a @forSome statement with multiple entities', shouldParse(parser, '@prefix a: . @base . @forSome a:x, , a:z. a:x a:z.', ['_:b0', '_:b1', '_:b2'])); - it('should not parse a @forSome statement with an invalid prefix', + it('should not parse a @forSome statement with an invalid prefix', shouldNotParse(parser, '@forSome a:b.', 'Undefined prefix "a:" on line 1.')); - it('should not parse a @forSome statement with a blank node', + it('should not parse a @forSome statement with a blank node', shouldNotParse(parser, '@forSome _:a.', 'Unexpected blank on line 1.')); - it('should not parse a @forSome statement with a variable', + it('should not parse a @forSome statement with a variable', shouldNotParse(parser, '@forSome ?a.', 'Unexpected var on line 1.')); - describe('should correctly scope @forSome statements', + describe('should correctly scope @forSome statements', shouldParse(parser, '@forSome . { @forSome . . }. .', ['_:b0', '_:b0', '_:b1'], ['_:b2', '_:b2', '_:b2', '_:b1'], ['_:b0', '_:b0', '_:b0'])); - describe('should parse a @forAll statement', + describe('should parse a @forAll statement', shouldParse(parser, '@forAll . .', ['?b0', '?b0', '?b0'])); - describe('should parse a @forAll statement with multiple entities', + describe('should parse a @forAll statement with multiple entities', shouldParse(parser, '@prefix a: . @base . @forAll a:x, , a:z. a:x a:z.', ['?b0', '?b1', '?b2'])); - it('should not parse a @forAll statement with an invalid prefix', + it('should not parse a @forAll statement with an invalid prefix', shouldNotParse(parser, '@forAll a:b.', 'Undefined prefix "a:" on line 1.')); - it('should not parse a @forAll statement with a blank node', + it('should not parse a @forAll statement with a blank node', shouldNotParse(parser, '@forAll _:a.', 'Unexpected blank on line 1.')); - it('should not parse a @forAll statement with a variable', + it('should not parse a @forAll statement with a variable', shouldNotParse(parser, '@forAll ?a.', 'Unexpected var on line 1.')); - describe('should correctly scope @forAll statements', + describe('should correctly scope @forAll statements', shouldParse(parser, '@forAll . { @forAll . . }. .', ['?b0', '?b0', '_:b1'], ['?b2', '?b2', '?b2', '_:b1'], ['?b0', '?b0', '?b0'])); - describe('should parse a ! path of length 2 as subject', + describe('should parse a ! path of length 2 as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + ':joe!fam:mother a fam:Person.', ['ex:joe', 'f:mother', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ! path of length 4 as subject', + describe('should parse a ! path of length 4 as subject', shouldParse(parser, '@prefix : . @prefix fam: . @prefix loc: .' + ':joe!fam:mother!loc:office!loc:zip loc:code 1234.', ['ex:joe', 'f:mother', '_:b0'], @@ -1976,13 +1982,13 @@ describe('Parser', () => { ['_:b1', 'l:zip', '_:b2'], ['_:b2', 'l:code', '"1234"^^http://www.w3.org/2001/XMLSchema#integer'])); - describe('should parse a ! path of length 2 as object', + describe('should parse a ! path of length 2 as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' :joe!fam:mother.', ['x', 'is', '_:b0'], ['ex:joe', 'f:mother', '_:b0'])); - describe('should parse a ! path of length 4 as object', + describe('should parse a ! path of length 4 as object', shouldParse(parser, '@prefix : . @prefix fam: . @prefix loc: .' + ' :joe!fam:mother!loc:office!loc:zip.', ['x', 'is', '_:b2'], @@ -1990,13 +1996,13 @@ describe('Parser', () => { ['_:b0', 'l:office', '_:b1'], ['_:b1', 'l:zip', '_:b2'])); - describe('should parse a ^ path of length 2 as subject', + describe('should parse a ^ path of length 2 as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + ':joe^fam:son a fam:Person.', ['_:b0', 'f:son', 'ex:joe'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ^ path of length 4 as subject', + describe('should parse a ^ path of length 4 as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + ':joe^fam:son^fam:sister^fam:mother a fam:Person.', ['_:b0', 'f:son', 'ex:joe'], @@ -2004,13 +2010,13 @@ describe('Parser', () => { ['_:b2', 'f:mother', '_:b1'], ['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ^ path of length 2 as object', + describe('should parse a ^ path of length 2 as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' :joe^fam:son.', ['x', 'is', '_:b0'], ['_:b0', 'f:son', 'ex:joe'])); - describe('should parse a ^ path of length 4 as object', + describe('should parse a ^ path of length 4 as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' :joe^fam:son^fam:sister^fam:mother.', ['x', 'is', '_:b2'], @@ -2018,67 +2024,67 @@ describe('Parser', () => { ['_:b1', 'f:sister', '_:b0'], ['_:b2', 'f:mother', '_:b1'])); - describe('should parse mixed !/^ paths as subject', + describe('should parse mixed !/^ paths as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + ':joe!fam:mother^fam:mother a fam:Person.', ['ex:joe', 'f:mother', '_:b0'], ['_:b1', 'f:mother', '_:b0'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse mixed !/^ paths as object', + describe('should parse mixed !/^ paths as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' :joe!fam:mother^fam:mother.', ['x', 'is', '_:b1'], ['ex:joe', 'f:mother', '_:b0'], ['_:b1', 'f:mother', '_:b0'])); - describe('should parse a ! path in a blank node as subject', + describe('should parse a ! path in a blank node as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '[fam:knows :joe!fam:mother] a fam:Person.', ['_:b0', 'f:knows', '_:b1'], ['ex:joe', 'f:mother', '_:b1'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ! path in a blank node as object', + describe('should parse a ! path in a blank node as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' [fam:knows :joe!fam:mother].', ['x', 'is', '_:b0'], ['_:b0', 'f:knows', '_:b1'], ['ex:joe', 'f:mother', '_:b1'])); - describe('should parse a ^ path in a blank node as subject', + describe('should parse a ^ path in a blank node as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '[fam:knows :joe^fam:son] a fam:Person.', ['_:b0', 'f:knows', '_:b1'], ['_:b1', 'f:son', 'ex:joe'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ^ path in a blank node as object', + describe('should parse a ^ path in a blank node as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' [fam:knows :joe^fam:son].', ['x', 'is', '_:b0'], ['_:b0', 'f:knows', '_:b1'], ['_:b1', 'f:son', 'ex:joe'])); - describe('should parse an empty list in the subject position', + describe('should parse an empty list in the subject position', shouldParse(parser, '@prefix : . @prefix fam: .' + '()

.', ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'p', 'o'] )); - describe('should parse an empty list in the predicate position', + describe('should parse an empty list in the predicate position', shouldParse(parser, '@prefix : . @prefix fam: .' + ' () .', ['s', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'o'] )); - describe('should parse an empty list in the object position', + describe('should parse an empty list in the object position', shouldParse(parser, '@prefix : . @prefix fam: .' + '

() .', ['s', 'p', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'] )); - describe('should parse a single element list in the subject position', + describe('should parse a single element list in the subject position', shouldParse(parser, '@prefix : . @prefix fam: .' + '( )

.', ...list(['_:b0', 's']), @@ -2086,21 +2092,21 @@ describe('Parser', () => { )); - describe('should parse a single element list in the predicate position', + describe('should parse a single element list in the predicate position', shouldParse(parser, '@prefix : . @prefix fam: .' + ' (

) .', ...list(['_:b0', 'p']), ['s', '_:b0', 'o'] )); - describe('should parse a single element list in the object position', + describe('should parse a single element list in the object position', shouldParse(parser, '@prefix : . @prefix fam: .' + '

( ) .', ...list(['_:b0', 'o']), ['s', 'p', '_:b0'] )); - describe('should parse a ! path in a list as subject', + describe('should parse a ! path in a list as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '( :joe!fam:mother ) a :List.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List'], @@ -2112,7 +2118,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['ex:joe', 'f:mother', '_:b2'])); - describe('should parse a ! path in a list as object', + describe('should parse a ! path in a list as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' ( :joe!fam:mother ).', ['l', 'is', '_:b0'], @@ -2124,7 +2130,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['ex:joe', 'f:mother', '_:b2'])); - describe('should parse a ^ path in a list as subject', + describe('should parse a ^ path in a list as subject', shouldParse(parser, '@prefix : . @prefix fam: .' + '( :joe^fam:son ) a :List.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List'], @@ -2136,7 +2142,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b2', 'f:son', 'ex:joe'])); - describe('should parse a ^ path in a list as object', + describe('should parse a ^ path in a list as object', shouldParse(parser, '@prefix : . @prefix fam: .' + ' ( :joe^fam:son ).', ['l', 'is', '_:b0'], @@ -2148,7 +2154,7 @@ describe('Parser', () => { ['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b2', 'f:son', 'ex:joe'])); - describe('should parse a ! path of length 2 as subject in a list', + describe('should parse a ! path of length 2 as subject in a list', shouldParse(parser, '@prefix : . @prefix fam: .' + '(:joe!fam:mother) a fam:Person.', ['ex:joe', 'f:mother', '_:b1'], @@ -2156,7 +2162,7 @@ describe('Parser', () => { ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a !^ path of length 3 as subject in a list', + describe('should parse a !^ path of length 3 as subject in a list', shouldParse(parser, '@prefix : . @prefix fam: .' + '(:joe!fam:mother^fam:father) a fam:Person.', ['ex:joe', 'f:mother', '_:b1'], @@ -2165,20 +2171,20 @@ describe('Parser', () => { ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ! path of length 2 starting with an empty list', + describe('should parse a ! path of length 2 starting with an empty list', shouldParse(parser, '@prefix : . @prefix fam: .' + '()!fam:mother a fam:Person.', ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b0'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ! path of length 2 starting with a non-empty list', + describe('should parse a ! path of length 2 starting with a non-empty list', shouldParse(parser, '@prefix : . @prefix fam: .' + '( :a )!fam:mother a fam:Person.', ...list(['_:b0', 'ex:a']), ['_:b0', 'f:mother', '_:b1'], ['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ! path of length 2 starting with a non-empty list in another list', + describe('should parse a ! path of length 2 starting with a non-empty list in another list', shouldParse(parser, '@prefix : . @prefix fam: .' + '(( :a )!fam:mother 1) a fam:Person.', ...list(['_:b0', '_:b2'], ['_:b3', '"1"^^http://www.w3.org/2001/XMLSchema#integer']), @@ -2186,7 +2192,7 @@ describe('Parser', () => { ['_:b1', 'f:mother', '_:b2'], ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'])); - describe('should parse a ! path of length 2 starting with an empty list in another list', + describe('should parse a ! path of length 2 starting with an empty list in another list', shouldParse(parser, '@prefix : . @prefix fam: .' + '(()!fam:mother 1) a fam:Person.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'], @@ -2194,7 +2200,7 @@ describe('Parser', () => { ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b1'] )); - describe('should parse a ! path of length 2 starting with an empty list in another list as second element', + describe('should parse a ! path of length 2 starting with an empty list in another list as second element', shouldParse(parser, '@prefix : . @prefix fam: .' + '(1 ()!fam:mother) a fam:Person.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'], @@ -2202,14 +2208,14 @@ describe('Parser', () => { ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b2'] )); - describe('should parse a ! path of length 2 starting with an empty list in another list of one element', + describe('should parse a ! path of length 2 starting with an empty list in another list of one element', shouldParse(parser, '@prefix : . @prefix fam: .' + '(()!fam:mother) a fam:Person.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'], ...list(['_:b0', '_:b1']), ['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b1'])); - describe('should parse a ! path of length 2 as nested subject in a list', + describe('should parse a ! path of length 2 as nested subject in a list', shouldParse(parser, '@prefix : . @prefix fam: .' + '((:joe!fam:mother) 1) a fam:Person.', ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'], @@ -2217,7 +2223,7 @@ describe('Parser', () => { ...list(['_:b1', '_:b2']), ['ex:joe', 'f:mother', '_:b2'])); - describe('should parse a birthday rule', + describe('should parse a birthday rule', shouldParse(parser, '@prefix foaf: .' + '@prefix math: .' + @@ -2229,9 +2235,7 @@ describe('Parser', () => { ' ((?date ?s!foaf:birthday)!math:difference 31622400) math:integerQuotient ?age .' + '} .', ['?x', 'http://example.org/trueOnDate', '?date', '_:b0'], - // eslint-disable-next-line no-warning-comments - // FIXME: when merging with https://github.com/rdfjs/N3.js/pull/327 - ['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'], + implies('_:b1', '_:b0'), ...[ ...list(['_:b2', '_:b6'], ['_:b7', '"31622400"^^http://www.w3.org/2001/XMLSchema#integer']), ['_:b2', 'http://www.w3.org/2000/10/swap/math#integerQuotient', '?age'], @@ -2241,7 +2245,7 @@ describe('Parser', () => { ].map(elem => [...elem, '_:b1']) )); - describe('should parse a formula as list item', + describe('should parse a formula as list item', shouldParse(parser, ' ( { a . } ).', ['a', 'findAll', '_:b0'], ...list(['_:b0', 'b'], ['_:b2', '_:b1'], ['_:b3', 'o']), @@ -2249,91 +2253,92 @@ describe('Parser', () => { ['b', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'type', '_:b1'] )); - it('should not parse an invalid ! path', + it('should not parse an invalid ! path', shouldNotParse(parser, '!"invalid" ', 'Expected entity but got literal on line 1.')); - it('should not parse an invalid ^ path', + it('should not parse an invalid ^ path', shouldNotParse(parser, '^"invalid" ', 'Expected entity but got literal on line 1.')); - describe('should parse literal as subject', + describe('should parse literal as subject', shouldParse(parser, ' {1 0}.', ['a', 'b', '_:b0'], ['"1"^^http://www.w3.org/2001/XMLSchema#integer', 'greaterThan', '"0"^^http://www.w3.org/2001/XMLSchema#integer', '_:b0'] )); - describe('should parse literals with datatype as subject', + describe('should parse literals with datatype as subject', shouldParse(parser, '"a"^^ .', ['"a"^^http://example.org/c', 'greaterThan', 'd'] )); - describe('should parse literals with datatype as subject and object', + describe('should parse literals with datatype as subject and object', shouldParse(parser, '"a"^^ "b"^^.', ['"a"^^http://example.org/c', 'greaterThan', '"b"^^http://example.org/c'] )); - describe('should parse literals without datatype as subject and object', + describe('should parse literals without datatype as subject and object', shouldParse(parser, '"a" "b".', ['"a"', 'greaterThan', '"b"'] )); - describe('should parse literals without datatype as subject', + describe('should parse literals without datatype as subject', shouldParse(parser, '"a" .', ['"a"', 'greaterThan', 'b'] )); - describe('should parse literals with datatype as predicate', + describe('should parse literals with datatype as predicate', shouldParse(parser, ' "a"^^ "b"^^.', ['greaterThan', '"a"^^http://example.org/c', '"b"^^http://example.org/c'] )); - describe('should parse literals without datatype as predicate', + describe('should parse literals without datatype as predicate', shouldParse(parser, ' "a" "b".', ['greaterThan', '"a"', '"b"'] )); - describe('should parse subject, predicate, and object as integer', + describe('should parse subject, predicate, and object as integer', shouldParse(parser, '1 1 1.', ['"1"^^http://www.w3.org/2001/XMLSchema#integer', '"1"^^http://www.w3.org/2001/XMLSchema#integer', '"1"^^http://www.w3.org/2001/XMLSchema#integer'] )); - describe('should parse literals with integer as predicate', + describe('should parse literals with integer as predicate', shouldParse(parser, ' 1 "b".', ['greaterThan', '"1"^^http://www.w3.org/2001/XMLSchema#integer', '"b"'] )); - describe('should parse literals with datatype as predicate in graph', + describe('should parse literals with datatype as predicate in graph', shouldParse(parser, ' { "a"^^ "b"^^}.', ['x', 'y', '_:b0'], ['greaterThan', '"a"^^http://example.org/c', '"b"^^http://example.org/c', '_:b0'] )); - describe('should parse literals without datatype as predicate in graph', + describe('should parse literals without datatype as predicate in graph', shouldParse(parser, ' { "a" "b"}.', ['x', 'y', '_:b0'], ['greaterThan', '"a"', '"b"', '_:b0'] )); - describe('should parse literals with datatype as subject in graph', + describe('should parse literals with datatype as subject in graph', shouldParse(parser, ' {"a"^^ "b"^^}.', ['a', 'b', '_:b0'], ['"a"^^http://example.org/c', 'greaterThan', '"b"^^http://example.org/c', '_:b0'] )); - describe('should parse literals with language as subject', + describe('should parse literals with language as subject', shouldParse(parser, ' {"bonjour"@fr "hello"@en}.', ['a', 'b', '_:b0'], ['"bonjour"@fr', 'sameAs', '"hello"@en', '_:b0'] )); - it('should not parse RDF-star in the subject position', + it('should not parse RDF-star in the subject position', shouldNotParse(parser, '<< >> .', 'Unexpected RDF-star syntax on line 1.')); - it('should not parse RDF-star in the object position', + it('should not parse RDF-star in the object position', shouldNotParse(parser, ' << >>.', 'Unexpected RDF-star syntax on line 1.')); - }); + }); + } describe('A Parser instance for the N3 format testing rdfStar support', () => { function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3' }); }