Skip to content

Commit 1d2a4e5

Browse files
committed
Add support for the annotated triples syntax
1 parent 312d5d5 commit 1d2a4e5

File tree

4 files changed

+144
-2
lines changed

4 files changed

+144
-2
lines changed

src/N3Lexer.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,13 +300,35 @@ export default class N3Lexer {
300300
case ']':
301301
case '(':
302302
case ')':
303-
case '{':
304303
case '}':
304+
matchLength = 1;
305+
type = firstChar;
306+
break;
307+
case '{':
305308
if (!this._lineMode) {
309+
// We need at least 2 tokens lookahead to distinguish "{|" and "{ "
310+
if (input.length < 2)
311+
break;
312+
313+
// Try to find a quoted triple annotation start
314+
if (input.length > 1 && input[1] === '|') {
315+
type = '{|', matchLength = 2;
316+
break;
317+
}
318+
306319
matchLength = 1;
307320
type = firstChar;
308321
}
309322
break;
323+
case '|':
324+
// We need at least 2 tokens lookahead to distinguish "|}" and "|"
325+
if (input.length < 2)
326+
break;
327+
// Try to find a quoted triple annotation end
328+
if (input[0] === '|' && input.length > 1 && input[1] === '}') {
329+
type = '|}', matchLength = 2;
330+
break;
331+
}
310332

311333
default:
312334
inconclusive = true;

src/N3Parser.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,23 @@ export default class N3Parser {
614614
case ',':
615615
next = this._readObject;
616616
break;
617+
// {| means that the current triple is annotated with predicate-object pairs.
618+
case '{|':
619+
if (!this._supportsRDFStar)
620+
return this._error('Unexpected RDF* syntax', token);
621+
622+
// Continue using the last triple as quoted triple subject for the predicate-object pairs.
623+
const predicate = this._predicate, object = this._object;
624+
this._subject = this._quad(subject, predicate, object, this.DEFAULTGRAPH);
625+
next = this._readPredicate;
626+
break;
627+
// |} means that the current quoted triple in annotation syntax is finalized.
628+
case '|}':
629+
if (this._subject.termType !== 'Quad')
630+
return this._error('Unexpected asserted triple closing', token);
631+
this._subject = null;
632+
next = this._readPunctuation;
633+
break;
617634
default:
618635
// An entity means this is a quad (only allowed if not already inside a graph)
619636
if (this._supportsQuads && this._graph === null && (graph = this._readEntity(token)) !== undefined) {

test/N3Lexer-test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,49 @@ describe('Lexer', () => {
10901090
{ type: '.', line: 1 },
10911091
{ type: 'eof', line: 1 }));
10921092

1093+
it('should tokenize a quoted triple annotation start',
1094+
shouldTokenize('{|',
1095+
{ type: '{|', line: 1 },
1096+
{ type: 'eof', line: 1 }));
1097+
1098+
it('should tokenize a split quoted triple annotation start',
1099+
shouldTokenize(streamOf('{', '|'),
1100+
{ type: '{|', line: 1 },
1101+
{ type: 'eof', line: 1 }));
1102+
1103+
it('should tokenize a quoted triple annotation end',
1104+
shouldTokenize('|}',
1105+
{ type: '|}', line: 1 },
1106+
{ type: 'eof', line: 1 }));
1107+
1108+
it('should tokenize a split quoted triple annotation end',
1109+
shouldTokenize(streamOf('|', '}'),
1110+
{ type: '|}', line: 1 },
1111+
{ type: 'eof', line: 1 }));
1112+
1113+
it('should tokenize an empty quoted triple annotation',
1114+
shouldTokenize('{| |}',
1115+
{ type: '{|', line: 1 },
1116+
{ type: '|}', line: 1 },
1117+
{ type: 'eof', line: 1 }));
1118+
1119+
it('should tokenize a non-empty quoted triple annotation',
1120+
shouldTokenize('{| <http://ex.org/?bla#bar> \n\t<http://ex.org/?bla#boo> |}.',
1121+
{ type: '{|', line: 1 },
1122+
{ type: 'IRI', value: 'http://ex.org/?bla#bar', line: 1 },
1123+
{ type: 'IRI', value: 'http://ex.org/?bla#boo', line: 2 },
1124+
{ type: '|}', line: 2 },
1125+
{ type: '.', line: 2 },
1126+
{ type: 'eof', line: 2 }));
1127+
1128+
it('should not tokenize an incomplete closing triple annotation',
1129+
shouldNotTokenize('{| |',
1130+
'Unexpected "|" on line 1.'));
1131+
1132+
it('should not tokenize an invalid closing triple annotation',
1133+
shouldNotTokenize('{| ||',
1134+
'Unexpected "||" on line 1.'));
1135+
10931136
it('returns start and end index for every token', () => {
10941137
const tokens = new Lexer().tokenize('<a:a> <b:c> "lit"@EN.');
10951138
tokens.should.deep.equal([

test/N3Parser-test.js

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ describe('Parser', () => {
746746

747747
it('should not parse a single opening brace',
748748
shouldNotParse('{',
749-
'Expected entity but got eof on line 1.'));
749+
'Unexpected "{" on line 1.'));
750750

751751
it('should not parse a superfluous closing brace ',
752752
shouldNotParse('{}}',
@@ -1044,6 +1044,46 @@ describe('Parser', () => {
10441044
shouldParse('<a> <b> <c> <g>.\n<<<a> <b> <c>>> <d> <e>.',
10451045
['a', 'b', 'c', 'g'],
10461046
[['a', 'b', 'c'], 'd', 'e']));
1047+
1048+
it('should parse an RDF* triple using annotation syntax with one predicate-object',
1049+
shouldParse('<a> <b> <c> {| <b> <c> |}.',
1050+
['a', 'b', 'c'], [['a', 'b', 'c'], 'b', 'c']));
1051+
1052+
it('should parse an RDF* triple using annotation syntax with two predicate-objects',
1053+
shouldParse('<a> <b> <c> {| <b1> <c1>; <b2> <c2> |}.',
1054+
['a', 'b', 'c'], [['a', 'b', 'c'], 'b1', 'c1'], [['a', 'b', 'c'], 'b2', 'c2']));
1055+
1056+
it('should parse an RDF* triple using annotation syntax with one predicate-object followed by regular triples',
1057+
shouldParse('<a> <b> <c> {| <b> <c> |}.\n<a2> <b2> <c2>.',
1058+
['a', 'b', 'c'], [['a', 'b', 'c'], 'b', 'c'], ['a2', 'b2', 'c2']));
1059+
1060+
it('should not parse an RDF* triple using annotation syntax with zero predicate-objects',
1061+
shouldNotParse('<a> <b> <c> {| |}',
1062+
'Expected entity but got |} on line 1.'));
1063+
1064+
it('should not parse an RDF* triple using an incomplete annotation syntax',
1065+
shouldNotParse('<a> <b> <c> {| <b> |}',
1066+
'Expected entity but got |} on line 1.'));
1067+
1068+
it('should not parse an RDF* triple using an incomplete annotation syntax after a semicolon',
1069+
shouldNotParse('<a> <b> <c> {| <b1> <c1>; |}',
1070+
'Expected entity but got |} on line 1.'));
1071+
1072+
it('should not parse an RDF* triple using an incomplete annotation syntax after a semicolon and entity',
1073+
shouldNotParse('<a> <b> <c> {| <b1> <c1>; <b2> |}',
1074+
'Expected entity but got |} on line 1.'));
1075+
1076+
it('should not parse an RDF* triple using an incomplete annotation syntax that misses |}',
1077+
shouldNotParse('<a> <b> <c> {| <b1> <c1>',
1078+
'Expected entity but got eof on line 1.'));
1079+
1080+
it('should not parse an RDF* triple using an incomplete annotation syntax that misses |} and starts a new subject',
1081+
shouldNotParse('<a> <b> <c> {| <b1> <c1>. <a2> <b2> <c2>',
1082+
'Expected entity but got eof on line 1.'));
1083+
1084+
it('should not parse an out of place |}',
1085+
shouldNotParse('<a> <b> <c> |}',
1086+
'Unexpected asserted triple closing on line 1.'));
10471087
});
10481088

10491089
describe('An Parser instance without document IRI', () => {
@@ -1227,6 +1267,10 @@ describe('Parser', () => {
12271267
it('should not parse RDF* in the object position',
12281268
shouldNotParse(parser, '<a> <b> <<a> <b> <c>>>.',
12291269
'Unexpected RDF* syntax on line 1.'));
1270+
1271+
it('should not parse RDF* with annotated syntax',
1272+
shouldNotParse(parser, '<a> <b> <c> {| <b> <c> |}.',
1273+
'Unexpected RDF* syntax on line 1.'));
12301274
});
12311275

12321276
describe('A Parser instance for the TurtleStar format', () => {
@@ -1288,6 +1332,10 @@ describe('Parser', () => {
12881332
it('should not parse RDF* in the object position',
12891333
shouldNotParse(parser, '<a> <b> <<<a> <b> <c>>>.',
12901334
'Unexpected RDF* syntax on line 1.'));
1335+
1336+
it('should not parse RDF* with annotated syntax',
1337+
shouldNotParse(parser, '<a> <b> <c> {| <b> <c> |}.',
1338+
'Unexpected RDF* syntax on line 1.'));
12911339
});
12921340

12931341
describe('A Parser instance for the TriGStar format', () => {
@@ -1375,6 +1423,10 @@ describe('Parser', () => {
13751423
it('should not parse nested quads',
13761424
shouldNotParse(parser, '<<_:a <http://ex.org/b> _:b <http://ex.org/b>>> <http://ex.org/b> "c" .',
13771425
'Expected >> to follow "_:b0_b" on line 1.'));
1426+
1427+
it('should not parse annotated triples',
1428+
shouldNotParse(parser, '_:a <http://ex.org/b> _:c {| <http://ex.org/b1> "c1" |} .',
1429+
'Unexpected "{|" on line 1.'));
13781430
});
13791431

13801432
describe('A Parser instance for the N-Quads format', () => {
@@ -1430,6 +1482,10 @@ describe('Parser', () => {
14301482
it('should parse RDF*',
14311483
shouldParse(parser, '<<_:a <http://example.org/b> _:c>> <http://example.org/a> _:c .',
14321484
[['_:b0_a', 'b', '_:b0_c'], 'a', '_:b0_c']));
1485+
1486+
it('should not parse annotated triples',
1487+
shouldNotParse(parser, '_:a <http://ex.org/b> _:c {| <http://ex.org/b1> "c1" |} .',
1488+
'Unexpected "{|" on line 1.'));
14331489
});
14341490

14351491
describe('A Parser instance for the N3 format', () => {
@@ -1777,6 +1833,10 @@ describe('Parser', () => {
17771833
it('should not parse RDF* in the object position',
17781834
shouldNotParse(parser, '<a> <b> <<<a> <b> <c>>>.',
17791835
'Unexpected RDF* syntax on line 1.'));
1836+
1837+
it('should not parse RDF* with annotated syntax',
1838+
shouldNotParse(parser, '<a> <b> <c> {| <b> <c> |}.',
1839+
'Unexpected RDF* syntax on line 1.'));
17801840
});
17811841

17821842
describe('A Parser instance for the N3Star format', () => {

0 commit comments

Comments
 (0)