Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/N3Lexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default class N3Lexer {
this._number = /^[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?|\d*(\.)?)\d+(?=\.?[,;:\s#()\[\]\{\}"'<>])/;
this._boolean = /^(?:true|false)(?=[.,;\s#()\[\]\{\}"'<>])/;
this._keyword = /^@[a-z]+(?=[\s#<:])/i;
this._sparqlKeyword = /^(?:PREFIX|BASE|GRAPH)(?=[\s#<])/i;
this._sparqlKeyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i;
this._shortPredicates = /^a(?=[\s#()\[\]\{\}"'<>])/;
this._newline = /^[ \t]*(?:#[^\n\r]*)?(?:\r\n|\n|\r)[ \t]*/;
this._comment = /#([^\n\r]*)/;
Expand Down Expand Up @@ -218,7 +218,7 @@ export default class N3Lexer {

case '@':
// Try to find a language code
if (this._previousMarker === 'literal' && (match = this._langcode.exec(input)))
if (this._previousMarker === 'literal' && (match = this._langcode.exec(input)) && match[1] !== 'version')
type = 'langcode', value = match[1];
// Try to find a keyword
else if (match = this._keyword.exec(input))
Expand Down Expand Up @@ -270,6 +270,8 @@ export default class N3Lexer {
case 'P':
case 'G':
case 'g':
case 'V':
case 'v':
// Try to find a SPARQL-style keyword
if (match = this._sparqlKeyword.exec(input))
type = match[0].toUpperCase();
Expand Down
24 changes: 21 additions & 3 deletions src/N3Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ export default class N3Parser {
this._sparqlStyle = true;
case '@base':
return this._readBaseIRI;
// It could be a version declaration
case 'VERSION':
this._sparqlStyle = true;
case '@version':
return this._readVersion;
// It could be a graph
case '{':
if (this._supportsNamedGraphs) {
Expand Down Expand Up @@ -772,6 +777,16 @@ export default class N3Parser {
return this._readDeclarationPunctuation;
}

// ### `_readVersion` reads version string declaration
_readVersion(token) {
if (token.type !== 'literal')
return this._error('Expected literal to follow version declaration', token);
if ((token.end - token.start) !== token.value.length + 2)
return this._error('Version declarations must use single quotes', token);
this._prefixCallback(token.value);
return this._readDeclarationPunctuation;
}

// ### `_readNamedGraphLabel` reads the label of a named graph
_readNamedGraphLabel(token) {
switch (token.type) {
Expand Down Expand Up @@ -1137,18 +1152,20 @@ export default class N3Parser {
// ## Public methods

// ### `parse` parses the N3 input and emits each parsed quad through the onQuad callback.
parse(input, quadCallback, prefixCallback) {
parse(input, quadCallback, prefixCallback, versionCallback) {
// 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)) {
let onQuad, onPrefix, onComment, onVersion;
if (quadCallback && (quadCallback.onQuad || quadCallback.onPrefix || quadCallback.onComment || quadCallback.onVersion)) {
onQuad = quadCallback.onQuad;
onPrefix = quadCallback.onPrefix;
onComment = quadCallback.onComment;
onVersion = quadCallback.onVersion;
}
else {
onQuad = quadCallback;
onPrefix = prefixCallback;
onVersion = versionCallback;
}
// The read callback is the next function to be executed when a token arrives.
// We start reading in the top context.
Expand All @@ -1158,6 +1175,7 @@ export default class N3Parser {
this._prefixes._ = this._blankNodePrefix ? this._blankNodePrefix.substr(2)
: `b${blankNodePrefix++}_`;
this._prefixCallback = onPrefix || noop;
this._versionCallback = onVersion || noop;
this._inversePredicate = false;
this._quantified = Object.create(null);

Expand Down
38 changes: 37 additions & 1 deletion test/N3Lexer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,11 +711,12 @@ describe('Lexer', () => {

it(
'should tokenize @ keywords',
shouldTokenize('@prefix @base @forSome @forAll ',
shouldTokenize('@prefix @base @forSome @forAll @version ',
{ type: '@prefix', line: 1 },
{ type: '@base', line: 1 },
{ type: '@forSome', line: 1 },
{ type: '@forAll', line: 1 },
{ type: '@version', line: 1 },
{ type: 'eof', line: 1 }),
);

Expand Down Expand Up @@ -755,6 +756,31 @@ describe('Lexer', () => {
{ type: 'eof', line: 2 }),
);

it(
'should tokenize @version declarations',
shouldTokenize('@version "1.2".\n@version \'1.2-basic\'.',
{ type: '@version', line: 1 },
{ type: 'literal', value: '1.2', line: 1 },
{ type: '.', line: 1 },
{ type: '@version', line: 2 },
{ type: 'literal', value: '1.2-basic', line: 2 },
{ type: '.', line: 2 },
{ type: 'eof', line: 2 }),
);

it(
'should tokenize mixed VERSION @version declarations',
shouldTokenize('VERSION "1.2"\nversion "1.2"\n@version "1.2" .',
{ type: 'VERSION', line: 1 },
{ type: 'literal', value: '1.2', line: 1 },
{ type: 'VERSION', line: 2 },
{ type: 'literal', value: '1.2', line: 2 },
{ type: '@version', line: 3 },
{ type: 'literal', value: '1.2', line: 3 },
{ type: '.', line: 3 },
{ type: 'eof', line: 3 }),
);

it(
'should tokenize PREFIX declarations',
shouldTokenize('PREFIX : <http://iri.org/#>\npreFiX abc: <http://iri.org/#>',
Expand All @@ -777,6 +803,16 @@ describe('Lexer', () => {
{ type: 'eof', line: 2 }),
);

it(
'should tokenize VERSION declarations',
shouldTokenize('VERSION "1.2"\nVERSION \'1.2-basic\'',
{ type: 'VERSION', line: 1 },
{ type: 'literal', value: '1.2', line: 1 },
{ type: 'VERSION', line: 2 },
{ type: 'literal', value: '1.2-basic', line: 2 },
{ type: 'eof', line: 2 }),
);

it(
'should tokenize blank nodes',
shouldTokenize('[] [<a> <b>] [a:b "c"^^d:e][a:b[]] _:a:b.',
Expand Down
44 changes: 44 additions & 0 deletions test/N3Parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,50 @@ describe('Parser', () => {
['http://ex.org/a/bb/ccc/../a', 'http://ex.org/a/bb/ccc/../b', 'http://ex.org/a/bb/ccc/../c']),
);

it(
'should handle VERSION',
shouldParse('VERSION "1.2"'),
);

it(
'should handle @version',
shouldParse('@version "1.2".'),
);

it(
'should handle multiple version declarations',
shouldParse('VERSION "1.2"\n' +
'version "1.2"\n' +
'@version "1.2" .'),
);

it(
'should handle multiple version declarations followed by a triple',
shouldParse('VERSION "1.2"\n' +
'version "1.2"\n' +
'@version "1.2" .\n' +
'<ex:a> <ex:b> <ex:c> .',
['ex:a', 'ex:b', 'ex:c']),
);

it(
'should not allow VERSION with an IRI',
shouldNotParse('VERSION <ex:abc>',
'Expected literal to follow version declaration on line 1.'),
);

it(
'should not allow VERSION with a non-string',
shouldNotParse('VERSION 1.2',
'Version declarations must use single quotes on line 1.'),
);

it(
'should not allow VERSION with a long string',
shouldNotParse('VERSION """1.2"""',
'Version declarations must use single quotes on line 1.'),
);

it('should parse an empty default graph', shouldParse('{}'));

it(
Expand Down