From febf91aed6d0838f7cd263862d50f0c055d71f6a Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sat, 24 Aug 2024 10:22:54 +0200 Subject: [PATCH 01/14] Added tests and support for a comments callback in the N3Parser --- src/N3Parser.js | 19 +++++++++++++++---- test/N3Parser-test.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 273846e3..133a941f 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -1010,8 +1010,8 @@ export default class N3Parser { // ## Public methods - // ### `parse` parses the N3 input and emits each parsed quad through the callback - parse(input, quadCallback, prefixCallback) { + // ### `parse` parses the N3 input and emits each parsed quad through the callback. Optionally you can set a prefixCallback and a commentCallback + parse(input, quadCallback, prefixCallback, commentCallback) { // The read callback is the next function to be executed when a token arrives. // We start reading in the top context. this._readCallback = this._readInTopContext; @@ -1023,6 +1023,9 @@ export default class N3Parser { this._inversePredicate = false; this._quantified = Object.create(null); + // If the parser is requested to provide comments through a callback, override the current lexer’s configuration parameter to emit comments + if (commentCallback) this._lexer._comments = true; + // Parse synchronously if no quad callback is given if (!quadCallback) { const quads = []; @@ -1040,8 +1043,16 @@ export default class N3Parser { this._lexer.tokenize(input, (error, token) => { if (error !== null) this._callback(error), this._callback = noop; - else if (this._readCallback) - this._readCallback = this._readCallback(token); + else if (this._readCallback) { + if (token.type === 'comment') { + // A comment token can only happen when the commentCallback is set, so checking whether commentCallback is set would be redundant + // if (commentCallback) + commentCallback(token.value); + } + else { + this._readCallback = this._readCallback(token); + } + } }); } } diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index a02de20e..5d2e9cf3 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -41,6 +41,20 @@ describe('Parser', () => { ['g', 'h', 'i']), ); + it( + 'should parse three triples with comments if no comment callback is set', + shouldParse(' #comment2\n . \n .\n .', + ['a', 'b', 'c'], + ['d', 'e', 'f'], + ['g', 'h', 'i']), + ); + + it( + 'should callback comments when a comment allback is set', + shouldCallbackComments('#comment1\n #comment2\n . \n .\n .', + 'comment1', 'comment2'), + ); + it('should parse a triple with a literal', shouldParse(' "string".', ['a', 'b', '"string"'])); @@ -3038,6 +3052,28 @@ function shouldParse(parser, input) { }; } + +function shouldCallbackComments(parser, input) { + const expected = Array.prototype.slice.call(arguments, 1); + // Shift parameters as necessary + if (parser.call) + expected.shift(); + else + input = parser, parser = Parser; + + return function (done) { + const items = expected; + const comments = []; + new parser({ baseIRI: BASE_IRI }).parse(input, (error, triple) => { + if (!triple) { + // Marks the end + expect(JSON.stringify(comments)).toBe(JSON.stringify(items)); + done(); + } + }, null, comment => { comments.push(comment); }); + }; +} + function mapToQuad(item) { item = item.map(t => { // don't touch if it's already an object From 2e99ef3879b0091b5bdb590fb8c59a159446dd17 Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sat, 24 Aug 2024 10:34:19 +0200 Subject: [PATCH 02/14] Added support and test for emitting comments in the N3StreamParser --- src/N3StreamParser.js | 1 + test/N3StreamParser-test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/N3StreamParser.js b/src/N3StreamParser.js index bd586352..ff968843 100644 --- a/src/N3StreamParser.js +++ b/src/N3StreamParser.js @@ -23,6 +23,7 @@ export default class N3StreamParser extends Transform { (error, quad) => { error && this.emit('error', error) || quad && this.push(quad); }, // Emit prefixes through the `prefix` event (prefix, uri) => { this.emit('prefix', prefix, uri); }, + comment => { this.emit('comment', comment); }, ); // Implement Transform methods through parser callbacks diff --git a/test/N3StreamParser-test.js b/test/N3StreamParser-test.js index 5ccfbb0c..8629ba14 100644 --- a/test/N3StreamParser-test.js +++ b/test/N3StreamParser-test.js @@ -64,6 +64,16 @@ describe('StreamParser', () => { { a: new NamedNode('http://a.org/#'), b: new NamedNode('http://b.org/#') }), ); + it( + 'parses two triples with comments', + shouldParse(['#comment1\n #comment2\n#comment3\n . .'], 2), + ); + + it( + 'emits "comment" events', + shouldEmitComments(['#comment1\n #comment2\n#comment3\n . .'], ['comment1', 'comment2', 'comment3']), + ); + it('passes an error', () => { const input = new Readable(), parser = new StreamParser(); let error = null; @@ -126,6 +136,22 @@ function shouldEmitPrefixes(chunks, expectedPrefixes) { }; } +function shouldEmitComments(chunks, expectedComments) { + return function (done) { + const comments = [], + parser = new StreamParser(), + inputStream = new ArrayReader(chunks); + inputStream.pipe(parser); + parser.on('data', () => {}); + parser.on('comment', comment => { comments.push(comment); }); + parser.on('error', done); + parser.on('end', error => { + expect(comments).toEqual(expectedComments); + done(error); + }); + }; +} + function ArrayReader(items) { const reader = new Readable(); reader._read = function () { this.push(items.shift() || null); }; From 8fcfb78249b23c5ae5afb0e6e18212343df8e0b2 Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sat, 24 Aug 2024 15:24:07 +0200 Subject: [PATCH 03/14] Added setter for enabling comments in lexer --- src/N3Lexer.js | 5 +++++ src/N3Parser.js | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/N3Lexer.js b/src/N3Lexer.js index 8424d727..e94d2f42 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -463,6 +463,11 @@ export default class N3Lexer { // ## Public methods + // ### `setComments` enables or disables creating a token per comment using a boolean. + setComments(enable) { + this._comments = enable; + } + // ### `tokenize` starts the transformation of an N3 document into an array of tokens. // The input can be a string or a stream. tokenize(input, callback) { diff --git a/src/N3Parser.js b/src/N3Parser.js index 133a941f..011d22a7 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -1023,8 +1023,8 @@ export default class N3Parser { this._inversePredicate = false; this._quantified = Object.create(null); - // If the parser is requested to provide comments through a callback, override the current lexer’s configuration parameter to emit comments - if (commentCallback) this._lexer._comments = true; + // If the parser is requested to provide comments through a callback, ask the lexer to return comments as tokens as well (disabled by default) + if (commentCallback) this._lexer.setComments(true); // Parse synchronously if no quad callback is given if (!quadCallback) { From cf213d9dc935be502cefbfaf24f044f86e848102 Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sat, 24 Aug 2024 15:59:17 +0200 Subject: [PATCH 04/14] Perf: no token check on comment when not in comment mode --- src/N3Parser.js | 41 ++++++++++++++++++++++++++--------------- test/N3Parser-test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 011d22a7..7d89f4de 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -1023,9 +1023,6 @@ export default class N3Parser { this._inversePredicate = false; this._quantified = Object.create(null); - // If the parser is requested to provide comments through a callback, ask the lexer to return comments as tokens as well (disabled by default) - if (commentCallback) this._lexer.setComments(true); - // Parse synchronously if no quad callback is given if (!quadCallback) { const quads = []; @@ -1038,22 +1035,36 @@ export default class N3Parser { return quads; } - // Parse asynchronously otherwise, executing the read callback when a token arrives - this._callback = quadCallback; - this._lexer.tokenize(input, (error, token) => { + let processNextToken = (error, token) => { if (error !== null) this._callback(error), this._callback = noop; else if (this._readCallback) { - if (token.type === 'comment') { - // A comment token can only happen when the commentCallback is set, so checking whether commentCallback is set would be redundant - // if (commentCallback) - commentCallback(token.value); - } - else { - this._readCallback = this._readCallback(token); - } + this._readCallback = this._readCallback(token); } - }); + }; + + // Enable checking for comments on every token when a commentCallback has been set + if (commentCallback) { + // Enable the lexer to return comments as tokens first (disabled by default) + this._lexer.setComments(true); + // Patch the processNextToken function + processNextToken = (error, token) => { + if (error !== null) + this._callback(error), this._callback = noop; + else if (this._readCallback) { + if (token.type === 'comment') { + commentCallback(token.value); + } + else { + this._readCallback = this._readCallback(token); + } + } + }; + } + + // Parse asynchronously otherwise, executing the read callback when a token arrives + this._callback = quadCallback; + this._lexer.tokenize(input, processNextToken); } } diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 5d2e9cf3..06613eb6 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -1646,6 +1646,12 @@ describe('Parser', () => { shouldNotParse(parser, '<<_:a _:b >> "c" .', 'Expected >> to follow "_:b0_b" on line 1.'), ); + + it( + 'should not parse nested quads with comments', + shouldNotParseWithComments(parser, '#comment1\n<<_:a _:b >> "c" .', + 'Expected >> to follow "_:b0_b" on line 2.'), + ); }); describe('A Parser instance for the TriG format', () => { @@ -3118,6 +3124,30 @@ function shouldNotParse(parser, input, expectedError, expectedContext) { }; } +function shouldNotParseWithComments(parser, input, expectedError, expectedContext) { + // Shift parameters if necessary + if (!parser.call) + expectedContext = expectedError, expectedError = input, input = parser, parser = Parser; + + return function (done) { + new parser({ baseIRI: BASE_IRI }).parse(input, (error, triple) => { + if (error) { + expect(triple).toBeFalsy(); + expect(error).toBeInstanceOf(Error); + expect(error.message).toEqual(expectedError); + if (expectedContext) expect(error.context).toEqual(expectedContext); + done(); + } + else if (!triple) + done(new Error(`Expected error ${expectedError}`)); + }, + null, + // Enables comment mode + () => {}, + ); + }; +} + function itShouldResolve(baseIRI, relativeIri, expected) { let result; describe(`resolving <${relativeIri}> against <${baseIRI}>`, () => { From 4411c307b304835ca5e099c1da02b42cbcb55be8 Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sat, 24 Aug 2024 16:45:06 +0200 Subject: [PATCH 05/14] Support new backward compatible function signature for parse() --- src/N3Parser.js | 26 +++++++++++++++++------- src/N3StreamParser.js | 10 ++++----- test/N3Parser-test.js | 47 +++++++++++++++++++++++-------------------- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 7d89f4de..92a38050 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -1010,8 +1010,20 @@ export default class N3Parser { // ## Public methods - // ### `parse` parses the N3 input and emits each parsed quad through the callback. Optionally you can set a prefixCallback and a commentCallback - parse(input, quadCallback, prefixCallback, commentCallback) { + // ### `parse` parses the N3 input and emits each parsed quad through the onQuad callback. + parse(input, quadCallback, prefixCallback) { + // The second parameter accepts an object { onQuad: ..., onPrefix: ..., onComment: ...} + // As a second and third parameter it still accepts a separate quadCallback and prefixCallback for backward compatibility as well + let onQuad, onPrefix, onComment; + if (quadCallback && (quadCallback.onQuad || quadCallback.onPrefix || quadCallback.onComment)) { + onQuad = quadCallback.onQuad; + onPrefix = quadCallback.onPrefix; + onComment = quadCallback.onComment; + } + else { + onQuad = quadCallback; + onPrefix = prefixCallback; + } // The read callback is the next function to be executed when a token arrives. // We start reading in the top context. this._readCallback = this._readInTopContext; @@ -1019,12 +1031,12 @@ export default class N3Parser { this._prefixes = Object.create(null); this._prefixes._ = this._blankNodePrefix ? this._blankNodePrefix.substr(2) : `b${blankNodePrefix++}_`; - this._prefixCallback = prefixCallback || noop; + this._prefixCallback = onPrefix || noop; this._inversePredicate = false; this._quantified = Object.create(null); // Parse synchronously if no quad callback is given - if (!quadCallback) { + if (!onQuad) { const quads = []; let error; this._callback = (e, t) => { e ? (error = e) : t && quads.push(t); }; @@ -1044,7 +1056,7 @@ export default class N3Parser { }; // Enable checking for comments on every token when a commentCallback has been set - if (commentCallback) { + if (onComment) { // Enable the lexer to return comments as tokens first (disabled by default) this._lexer.setComments(true); // Patch the processNextToken function @@ -1053,7 +1065,7 @@ export default class N3Parser { this._callback(error), this._callback = noop; else if (this._readCallback) { if (token.type === 'comment') { - commentCallback(token.value); + onComment(token.value); } else { this._readCallback = this._readCallback(token); @@ -1063,7 +1075,7 @@ export default class N3Parser { } // Parse asynchronously otherwise, executing the read callback when a token arrives - this._callback = quadCallback; + this._callback = onQuad; this._lexer.tokenize(input, processNextToken); } } diff --git a/src/N3StreamParser.js b/src/N3StreamParser.js index ff968843..e253dc73 100644 --- a/src/N3StreamParser.js +++ b/src/N3StreamParser.js @@ -18,13 +18,13 @@ export default class N3StreamParser extends Transform { case 'end': onEnd = callback; break; } }, - }, + }, { // Handle quads by pushing them down the pipeline - (error, quad) => { error && this.emit('error', error) || quad && this.push(quad); }, + onQuad: (error, quad) => { error && this.emit('error', error) || quad && this.push(quad); }, // Emit prefixes through the `prefix` event - (prefix, uri) => { this.emit('prefix', prefix, uri); }, - comment => { this.emit('comment', comment); }, - ); + onPrefix: (prefix, uri) => { this.emit('prefix', prefix, uri); }, + onComment: comment => { this.emit('comment', comment); }, + }); // Implement Transform methods through parser callbacks this._transform = (chunk, encoding, done) => { onData(chunk); done(); }; diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 06613eb6..9646da2e 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -3070,13 +3070,16 @@ function shouldCallbackComments(parser, input) { return function (done) { const items = expected; const comments = []; - new parser({ baseIRI: BASE_IRI }).parse(input, (error, triple) => { - if (!triple) { - // Marks the end - expect(JSON.stringify(comments)).toBe(JSON.stringify(items)); - done(); - } - }, null, comment => { comments.push(comment); }); + new parser({ baseIRI: BASE_IRI }).parse(input, { + onQuad: (error, triple) => { + if (!triple) { + // Marks the end + expect(JSON.stringify(comments)).toBe(JSON.stringify(items)); + done(); + } + }, + onComment: comment => { comments.push(comment); }, + }); }; } @@ -3130,21 +3133,21 @@ function shouldNotParseWithComments(parser, input, expectedError, expectedContex expectedContext = expectedError, expectedError = input, input = parser, parser = Parser; return function (done) { - new parser({ baseIRI: BASE_IRI }).parse(input, (error, triple) => { - if (error) { - expect(triple).toBeFalsy(); - expect(error).toBeInstanceOf(Error); - expect(error.message).toEqual(expectedError); - if (expectedContext) expect(error.context).toEqual(expectedContext); - done(); - } - else if (!triple) - done(new Error(`Expected error ${expectedError}`)); - }, - null, - // Enables comment mode - () => {}, - ); + new parser({ baseIRI: BASE_IRI }).parse(input, { + onQuad: (error, triple) => { + if (error) { + expect(triple).toBeFalsy(); + expect(error).toBeInstanceOf(Error); + expect(error.message).toEqual(expectedError); + if (expectedContext) expect(error.context).toEqual(expectedContext); + done(); + } + else if (!triple) + done(new Error(`Expected error ${expectedError}`)); + }, + // Enables comment mode + onComment: () => {}, + }); }; } From bca756ed27b4bb98460f70c29f5b9845e1032ba3 Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sat, 24 Aug 2024 20:08:47 +0200 Subject: [PATCH 06/14] =?UTF-8?q?keeping=20the=20{=E2=80=A6}=20off=20for?= =?UTF-8?q?=20single=20lines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/N3Parser.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/N3Parser.js b/src/N3Parser.js index 92a38050..8ff90730 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -1050,9 +1050,8 @@ export default class N3Parser { let processNextToken = (error, token) => { if (error !== null) this._callback(error), this._callback = noop; - else if (this._readCallback) { + else if (this._readCallback) this._readCallback = this._readCallback(token); - } }; // Enable checking for comments on every token when a commentCallback has been set @@ -1064,12 +1063,10 @@ export default class N3Parser { if (error !== null) this._callback(error), this._callback = noop; else if (this._readCallback) { - if (token.type === 'comment') { + if (token.type === 'comment') onComment(token.value); - } - else { + else this._readCallback = this._readCallback(token); - } } }; } From 44a663cbfc093ed6439b2ec091a4b0d392997c60 Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sat, 24 Aug 2024 20:28:58 +0200 Subject: [PATCH 07/14] Make emitting comments from N3StreamParser optional through a constructor option (off by default) --- src/N3StreamParser.js | 19 ++++++++++++------- test/N3StreamParser-test.js | 21 ++++++++++++++++++++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/N3StreamParser.js b/src/N3StreamParser.js index e253dc73..95610d1b 100644 --- a/src/N3StreamParser.js +++ b/src/N3StreamParser.js @@ -11,6 +11,17 @@ export default class N3StreamParser extends Transform { // Set up parser with dummy stream to obtain `data` and `end` callbacks const parser = new N3Parser(options); let onData, onEnd; + + const callbacks = { + // Handle quads by pushing them down the pipeline + onQuad: (error, quad) => { error && this.emit('error', error) || quad && this.push(quad); }, + // Emit prefixes through the `prefix` event + onPrefix: (prefix, uri) => { this.emit('prefix', prefix, uri); }, + }; + + if (options && options.comments) + callbacks.onComment = comment => { this.emit('comment', comment); }; + parser.parse({ on: (event, callback) => { switch (event) { @@ -18,13 +29,7 @@ export default class N3StreamParser extends Transform { case 'end': onEnd = callback; break; } }, - }, { - // Handle quads by pushing them down the pipeline - onQuad: (error, quad) => { error && this.emit('error', error) || quad && this.push(quad); }, - // Emit prefixes through the `prefix` event - onPrefix: (prefix, uri) => { this.emit('prefix', prefix, uri); }, - onComment: comment => { this.emit('comment', comment); }, - }); + }, callbacks); // Implement Transform methods through parser callbacks this._transform = (chunk, encoding, done) => { onData(chunk); done(); }; diff --git a/test/N3StreamParser-test.js b/test/N3StreamParser-test.js index 8629ba14..a04db547 100644 --- a/test/N3StreamParser-test.js +++ b/test/N3StreamParser-test.js @@ -74,6 +74,11 @@ describe('StreamParser', () => { shouldEmitComments(['#comment1\n #comment2\n#comment3\n . .'], ['comment1', 'comment2', 'comment3']), ); + it( + 'emits "comment" events', + shouldNotEmitCommentsWhenNotEnabled(['#comment1\n #comment2\n#comment3\n . .'], ['comment1', 'comment2', 'comment3']), + ); + it('passes an error', () => { const input = new Readable(), parser = new StreamParser(); let error = null; @@ -139,7 +144,7 @@ function shouldEmitPrefixes(chunks, expectedPrefixes) { function shouldEmitComments(chunks, expectedComments) { return function (done) { const comments = [], - parser = new StreamParser(), + parser = new StreamParser({ comments: true }), inputStream = new ArrayReader(chunks); inputStream.pipe(parser); parser.on('data', () => {}); @@ -152,6 +157,20 @@ function shouldEmitComments(chunks, expectedComments) { }; } +function shouldNotEmitCommentsWhenNotEnabled(chunks, expectedComments) { + return function (done) { + const parser = new StreamParser(), + inputStream = new ArrayReader(chunks); + inputStream.pipe(parser); + parser.on('data', () => {}); + parser.on('comment', comment => { done(new Error('Should not emit comments but it did')); }); + parser.on('error', done); + parser.on('end', error => { + done(); + }); + }; +} + function ArrayReader(items) { const reader = new Readable(); reader._read = function () { this.push(items.shift() || null); }; From 0f78e71920ac4f420df12fae31840c97e412de99 Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sat, 24 Aug 2024 20:34:32 +0200 Subject: [PATCH 08/14] Make comments a public var in N3Lexer --- src/N3Lexer.js | 11 +++-------- src/N3Parser.js | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/N3Lexer.js b/src/N3Lexer.js index e94d2f42..4e734a06 100644 --- a/src/N3Lexer.js +++ b/src/N3Lexer.js @@ -68,7 +68,7 @@ export default class N3Lexer { this._n3Mode = options.n3 !== false; } // Don't output comment tokens by default - this._comments = !!options.comments; + this.comments = !!options.comments; // Cache the last tested closing position of long literals this._literalClosingPos = 0; } @@ -85,7 +85,7 @@ export default class N3Lexer { let whiteSpaceMatch, comment; while (whiteSpaceMatch = this._newline.exec(input)) { // Try to find a comment - if (this._comments && (comment = this._comment.exec(whiteSpaceMatch[0]))) + if (this.comments && (comment = this._comment.exec(whiteSpaceMatch[0]))) emitToken('comment', comment[1], '', this._line, whiteSpaceMatch[0].length); // Advance the input input = input.substr(whiteSpaceMatch[0].length, input.length); @@ -101,7 +101,7 @@ export default class N3Lexer { // If the input is finished, emit EOF if (inputFinished) { // Try to find a final comment - if (this._comments && (comment = this._comment.exec(input))) + if (this.comments && (comment = this._comment.exec(input))) emitToken('comment', comment[1], '', this._line, input.length); input = null; emitToken('eof', '', '', this._line, 0); @@ -463,11 +463,6 @@ export default class N3Lexer { // ## Public methods - // ### `setComments` enables or disables creating a token per comment using a boolean. - setComments(enable) { - this._comments = enable; - } - // ### `tokenize` starts the transformation of an N3 document into an array of tokens. // The input can be a string or a stream. tokenize(input, callback) { diff --git a/src/N3Parser.js b/src/N3Parser.js index 8ff90730..fc4af341 100644 --- a/src/N3Parser.js +++ b/src/N3Parser.js @@ -1057,7 +1057,7 @@ export default class N3Parser { // Enable checking for comments on every token when a commentCallback has been set if (onComment) { // Enable the lexer to return comments as tokens first (disabled by default) - this._lexer.setComments(true); + this._lexer.comments = true; // Patch the processNextToken function processNextToken = (error, token) => { if (error !== null) From af4a2ae9a9696bcce98bb42b9ccf35806503eb22 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 25 Aug 2024 09:02:12 +1000 Subject: [PATCH 09/14] Update test/N3Parser-test.js Co-authored-by: Ruben Verborgh --- test/N3Parser-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index 9646da2e..fa3fddf1 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -50,7 +50,7 @@ describe('Parser', () => { ); it( - 'should callback comments when a comment allback is set', + 'should callback comments when a comment callback is set', shouldCallbackComments('#comment1\n #comment2\n . \n .\n .', 'comment1', 'comment2'), ); From 9df3f01ba502295f45e862e082ce14a8d135e1fd Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sun, 25 Aug 2024 10:31:48 +0200 Subject: [PATCH 10/14] Adding tests to fix coverage --- test/N3Parser-test.js | 50 ++++++++++++++++++++++++++++++++++++- test/N3StreamParser-test.js | 47 +++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js index fa3fddf1..97b7ec62 100644 --- a/test/N3Parser-test.js +++ b/test/N3Parser-test.js @@ -49,6 +49,14 @@ describe('Parser', () => { ['g', 'h', 'i']), ); + it( + 'should parse three triples with comments when comment callback is set', + shouldParseWithCommentsEnabled(' #comment2\n . \n .\n .', + ['a', 'b', 'c'], + ['d', 'e', 'f'], + ['g', 'h', 'i']), + ); + it( 'should callback comments when a comment callback is set', shouldCallbackComments('#comment1\n #comment2\n . \n .\n .', @@ -217,6 +225,12 @@ describe('Parser', () => { 'Undefined prefix "d:" on line 1.'), ); + it( + 'should not parse undefined prefix in datatype with comments enabled', + shouldNotParseWithComments('#comment\n "c"^^d:e ', + 'Undefined prefix "d:" on line 2.'), + ); + it( 'should parse triples with SPARQL prefixes', shouldParse('PREFIX : <#>\n' + @@ -1615,6 +1629,12 @@ describe('Parser', () => { 'Unexpected literal on line 1.'), ); + it( + 'should not parse a literal as subject', + shouldNotParseWithComments(parser, '1 .', + 'Unexpected literal on line 1.'), + ); + it( 'should not parse RDF-star in the subject position', shouldNotParse(parser, '<< >> .', @@ -3058,6 +3078,32 @@ function shouldParse(parser, input) { }; } +function shouldParseWithCommentsEnabled(parser, input) { + const expected = Array.prototype.slice.call(arguments, 1); + // Shift parameters as necessary + if (parser.call) + expected.shift(); + else + input = parser, parser = Parser; + + return function (done) { + const results = []; + const items = expected.map(mapToQuad); + new parser({ baseIRI: BASE_IRI }).parse(input, { + onQuad: (error, triple) => { + expect(error).toBeFalsy(); + if (triple) + results.push(triple); + else + expect(toSortedJSON(results)).toBe(toSortedJSON(items)), done(); + }, + onComment: comment => { + expect(comment).toBeDefined(); + }, + }); + }; +} + function shouldCallbackComments(parser, input) { const expected = Array.prototype.slice.call(arguments, 1); @@ -3146,7 +3192,9 @@ function shouldNotParseWithComments(parser, input, expectedError, expectedContex done(new Error(`Expected error ${expectedError}`)); }, // Enables comment mode - onComment: () => {}, + onComment: comment => { + expect(comment).toBeDefined(); + }, }); }; } diff --git a/test/N3StreamParser-test.js b/test/N3StreamParser-test.js index a04db547..b50a3d83 100644 --- a/test/N3StreamParser-test.js +++ b/test/N3StreamParser-test.js @@ -53,6 +53,12 @@ describe('StreamParser', () => { { token: undefined, line: 1, previousToken: undefined }, ); + it( + "doesn't parse an invalid stream with comments", + shouldNotParseWithComments(['z.'], 'Unexpected "z." on line 1.'), + { token: undefined, line: 1, previousToken: undefined }, + ); + it( 'Should Not parse Bom in middle stream', shouldNotParse([' ', '\ufeff', '.'], 'Unexpected "" on line 1.'), @@ -65,10 +71,15 @@ describe('StreamParser', () => { ); it( - 'parses two triples with comments', + 'parses two triples with comments when comments not enabled', shouldParse(['#comment1\n #comment2\n#comment3\n . .'], 2), ); + it( + 'parses two triples with comments when comments enabled', + shouldParseWithCommentsEnabled(['#comment1\n #comment2\n#comment3\n . .'], 2), + ); + it( 'emits "comment" events', shouldEmitComments(['#comment1\n #comment2\n#comment3\n . .'], ['comment1', 'comment2', 'comment3']), @@ -109,6 +120,24 @@ function shouldParse(chunks, expectedLength, validateTriples) { }; } +function shouldParseWithCommentsEnabled(chunks, expectedLength, validateTriples) { + return function (done) { + const triples = [], + inputStream = new ArrayReader(chunks), + parser = new StreamParser({ comments: true }), + outputStream = new ArrayWriter(triples); + expect(parser.import(inputStream)).toBe(parser); + parser.pipe(outputStream); + parser.on('comment', () => {}); + parser.on('error', done); + parser.on('end', () => { + expect(triples).toHaveLength(expectedLength); + if (validateTriples) validateTriples(triples); + done(); + }); + }; +} + function shouldNotParse(chunks, expectedMessage, expectedContext) { return function (done) { const inputStream = new ArrayReader(chunks), @@ -125,6 +154,22 @@ function shouldNotParse(chunks, expectedMessage, expectedContext) { }; } +function shouldNotParseWithComments(chunks, expectedMessage, expectedContext) { + return function (done) { + const inputStream = new ArrayReader(chunks), + parser = new StreamParser({ comments: true }), + outputStream = new ArrayWriter([]); + inputStream.pipe(parser); + parser.pipe(outputStream); + parser.on('error', error => { + expect(error).toBeInstanceOf(Error); + expect(error.message).toBe(expectedMessage); + if (expectedContext) expect(error.context).toEqual(expectedContext); + done(); + }); + }; +} + function shouldEmitPrefixes(chunks, expectedPrefixes) { return function (done) { const prefixes = {}, From 2c703bb9109882f40a1455c92073b2fbdee360d2 Mon Sep 17 00:00:00 2001 From: Pieter Colpaert Date: Sun, 25 Aug 2024 11:16:56 +0200 Subject: [PATCH 11/14] Updated documentation in the README.md --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index eda77a09..a8d5fbbe 100644 --- a/README.md +++ b/README.md @@ -89,19 +89,22 @@ parser.parse( c:Tom a c:Cat. c:Jerry a c:Mouse; c:smarterThan c:Tom.`, - (error, quad, prefixes) => { + (error, quad) => { if (quad) console.log(quad); else - console.log("# That's all, folks!", prefixes); + console.log("# That's all, folks!"); }); ``` The callback's first argument is an optional error value, the second is a quad. If there are no more quads, -the callback is invoked one last time with `null` for `quad` -and a hash of prefixes as third argument. +the callback is invoked one last time with `null` for `quad`.
-Pass a second callback to `parse` to retrieve prefixes as they are read. +In case you would also like to process prefixes, you can instead pass an object containing multiple callbacks. +The callback to retrieve the quads is called `onQuad`. +The callback to also retrieve prefixes as they are read is called `onPrefix`. +The first argument is the prefix, the second is the IRI. +There is also a third callback called `onComment` taking only one `comment` argument.
If no callbacks are provided, parsing happens synchronously. @@ -169,6 +172,8 @@ function SlowConsumer() { A dedicated `prefix` event signals every prefix with `prefix` and `term` arguments. +Also a `comment` event can be enabled through the options object of the N3.StreamParser constructor using: `comments: true`. + ## Writing ### From quads to a string From 7d61872f9cb8984ea6a0b273ee037a26de6bc079 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:41:37 +1000 Subject: [PATCH 12/14] chore: revert prefix arg drop --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a8d5fbbe..b6a9e7f1 100644 --- a/README.md +++ b/README.md @@ -89,16 +89,17 @@ parser.parse( c:Tom a c:Cat. c:Jerry a c:Mouse; c:smarterThan c:Tom.`, - (error, quad) => { + (error, quad, prefixes) => { if (quad) console.log(quad); else - console.log("# That's all, folks!"); + console.log("# That's all, folks!", prefixes); }); ``` The callback's first argument is an optional error value, the second is a quad. If there are no more quads, -the callback is invoked one last time with `null` for `quad`. +the callback is invoked one last time with `null` for `quad` +and a hash of prefixes as third argument.
In case you would also like to process prefixes, you can instead pass an object containing multiple callbacks. The callback to retrieve the quads is called `onQuad`. From d10c150456d50d5225fc401d4242351881812dcf Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:06:15 +1000 Subject: [PATCH 13/14] chore: cleanup documentation --- README.md | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b6a9e7f1..47edd69e 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,15 @@ we assume that a quad is simply a triple in a named or default graph. `N3.Parser` transforms Turtle, TriG, N-Triples, or N-Quads document into quads through a callback: ```JavaScript +const tomAndJerry = `PREFIX c: + # Tom is a cat + c:Tom a c:Cat. + c:Jerry a c:Mouse; + c:smarterThan c:Tom.` + const parser = new N3.Parser(); -parser.parse( - `PREFIX c: - c:Tom a c:Cat. - c:Jerry a c:Mouse; - c:smarterThan c:Tom.`, + +parser.parse(tomAndJerry, (error, quad, prefixes) => { if (quad) console.log(quad); @@ -101,13 +104,30 @@ If there are no more quads, the callback is invoked one last time with `null` for `quad` and a hash of prefixes as third argument.
-In case you would also like to process prefixes, you can instead pass an object containing multiple callbacks. -The callback to retrieve the quads is called `onQuad`. -The callback to also retrieve prefixes as they are read is called `onPrefix`. -The first argument is the prefix, the second is the IRI. -There is also a third callback called `onComment` taking only one `comment` argument. + +Alternatively, an object may be supplied, where `onQuad`, `onPrefix` and `onComment` may be used to listen for `quads`, `prefixes` and `comments` as follows: +```JavaScript +const parser = new N3.Parser(); + +parser.parse(tomAndJerry, { + // onQuad (required) accepts a listener of type (quad: RDF.Quad) => void + onQuad: (err, quad) => { console.log(quad); }, + // onPrefix (optional) accepts a listener of type (prefix: string, iri: NamedNode) => void + onPrefix: (prefix, iri) => { console.log(prefix, 'expands to', iri); }, + // onComment (optional) accepts a listener of type (comment: string) => void + onComment: (comment) => { console.log('#', comment); }, +}); +``` +
-If no callbacks are provided, parsing happens synchronously. +If no callbacks are provided, parsing happens synchronously returning an array of parsed quads. + +```JavaScript +const parser = new N3.Parser(); + +// An array of resultant Quads +const quadArray = parser.parse(tomAndJerry); +``` By default, `N3.Parser` parses a permissive superset of Turtle, TriG, N-Triples, and N-Quads.
@@ -173,7 +193,7 @@ function SlowConsumer() { A dedicated `prefix` event signals every prefix with `prefix` and `term` arguments. -Also a `comment` event can be enabled through the options object of the N3.StreamParser constructor using: `comments: true`. +A dedicated `comment` event may be enabled by setting `comments: true` in the N3.StreamParser constructor. ## Writing From b3a760877bfe3bbec98e18a3e44fba5336a0ced5 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:10:20 +1000 Subject: [PATCH 14/14] chore: make languaage precise --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 47edd69e..2ea9e5b0 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ the callback is invoked one last time with `null` for `quad` and a hash of prefixes as third argument.
-Alternatively, an object may be supplied, where `onQuad`, `onPrefix` and `onComment` may be used to listen for `quads`, `prefixes` and `comments` as follows: +Alternatively, an object can be supplied, where `onQuad`, `onPrefix` and `onComment` are used to listen for `quads`, `prefixes` and `comments` as follows: ```JavaScript const parser = new N3.Parser(); @@ -113,14 +113,14 @@ parser.parse(tomAndJerry, { // onQuad (required) accepts a listener of type (quad: RDF.Quad) => void onQuad: (err, quad) => { console.log(quad); }, // onPrefix (optional) accepts a listener of type (prefix: string, iri: NamedNode) => void - onPrefix: (prefix, iri) => { console.log(prefix, 'expands to', iri); }, + onPrefix: (prefix, iri) => { console.log(prefix, 'expands to', iri.value); }, // onComment (optional) accepts a listener of type (comment: string) => void onComment: (comment) => { console.log('#', comment); }, }); ```
-If no callbacks are provided, parsing happens synchronously returning an array of parsed quads. +If no callbacks are provided, parsing happens synchronously returning an array of quads. ```JavaScript const parser = new N3.Parser(); @@ -193,7 +193,7 @@ function SlowConsumer() { A dedicated `prefix` event signals every prefix with `prefix` and `term` arguments. -A dedicated `comment` event may be enabled by setting `comments: true` in the N3.StreamParser constructor. +A dedicated `comment` event can be enabled by setting `comments: true` in the N3.StreamParser constructor. ## Writing