Skip to content

Commit be1bcd9

Browse files
committed
fix: fix support for paths to support all usages supported by the N3 spec
1 parent 925d66f commit be1bcd9

2 files changed

Lines changed: 178 additions & 13 deletions

File tree

src/N3Parser.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -443,14 +443,29 @@ export default class N3Parser {
443443
case ')':
444444
// Closing the list; restore the parent context
445445
this._restoreContext('list', token);
446-
// If this list is contained within a parent list, return the membership quad here.
447-
// This will be `<parent list element> rdf:first <this list>.`.
448-
if (stack.length !== 0 && stack[stack.length - 1].type === 'list')
449-
this._emit(this._subject, this._predicate, this._object, this._graph);
446+
if (stack.length !== 0 && stack[stack.length - 1].type === 'list') {
447+
if (this._n3Mode) {
448+
const { _subject, _predicate, _graph } = this;
449+
if (this._object !== this.RDF_NIL) {
450+
this._emit(previousList, this.RDF_REST, this.RDF_NIL, _graph);
451+
}
452+
return this._getPathReader(tk => {
453+
// If this list is contained within a parent list, return the membership quad here.
454+
// This will be `<parent list element> rdf:first <this list>.`.
455+
this._emit(_subject, _predicate, this._object, _graph);
456+
return this._readListItem(tk);
457+
});
458+
}
459+
else {
460+
// If this list is contained within a parent list, return the membership quad here.
461+
// This will be `<parent list element> rdf:first <this list>.`.
462+
this._emit(this._subject, this._predicate, this._object, this._graph);
463+
}
464+
}
450465
// Was this list the parent's subject?
451466
if (this._predicate === null) {
452467
// The next token is the predicate
453-
next = this._readPredicate;
468+
next = this._n3Mode ? this._getPathReader(this._readPredicateOrNamedGraph) : this._readPredicate;
454469
// No list tail if this was an empty list
455470
if (this._subject === this.RDF_NIL)
456471
return next;
@@ -518,7 +533,7 @@ export default class N3Parser {
518533
// If an item was read, add it to the list
519534
if (item !== null) {
520535
// In N3 mode, the item might be a path
521-
if (this._n3Mode && (token.type === 'IRI' || token.type === 'prefixed')) {
536+
if (this._n3Mode) {
522537
// Create a new context to add the item's path
523538
this._saveContext('item', this._graph, list, this.RDF_FIRST, item);
524539
this._subject = item, this._predicate = null;

test/N3Parser-test.js

Lines changed: 157 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,7 +1851,7 @@ describe('Parser', () => {
18511851
['?g', '?h', '?i', '_:b4'],
18521852
['?j', '?k', '?l', '_:b5']));
18531853

1854-
it('should not reuse identifiers of blank nodes within and outside of formulas',
1854+
describe('should not reuse identifiers of blank nodes within and outside of formulas',
18551855
shouldParse(parser, '_:a _:b _:c. { _:a _:b _:c } => { { _:a _:b _:c } => { _:a _:b _:c } }.',
18561856
['_:b0_a', '_:b0_b', '_:b0_c'],
18571857
['_:b0', 'http://www.w3.org/2000/10/swap/log#implies', '_:b1', ''],
@@ -2058,13 +2058,103 @@ describe('Parser', () => {
20582058
['_:b3', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
20592059
['_:b2', 'f:son', 'ex:joe']));
20602060

2061+
describe('should parse a ! path of length 2 as subject in a list',
2062+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2063+
'(:joe!fam:mother) a fam:Person.',
2064+
['ex:joe', 'f:mother', '_:b1'],
2065+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b1'],
2066+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
2067+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']));
2068+
2069+
describe('should parse a !^ path of length 3 as subject in a list',
2070+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2071+
'(:joe!fam:mother^fam:father) a fam:Person.',
2072+
['ex:joe', 'f:mother', '_:b1'],
2073+
['_:b2', 'f:father', '_:b1'],
2074+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b2'],
2075+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
2076+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']));
2077+
2078+
describe('should parse a ! path of length 2 starting with an empty list',
2079+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2080+
'()!fam:mother a fam:Person.',
2081+
['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b0'],
2082+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']));
2083+
2084+
describe('should parse a ! path of length 2 starting with a non-empty list',
2085+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2086+
'( :a )!fam:mother a fam:Person.',
2087+
...list(['_:b0', 'ex:a']),
2088+
['_:b0', 'f:mother', '_:b1'],
2089+
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']));
2090+
2091+
describe('should parse a ! path of length 2 starting with a non-empty list in another list',
2092+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2093+
'(( :a )!fam:mother 1) a fam:Person.',
2094+
...list(['_:b0', '_:b2'], ['_:b3', '"1"^^http://www.w3.org/2001/XMLSchema#integer']),
2095+
...list(['_:b1', 'ex:a']),
2096+
['_:b1', 'f:mother', '_:b2'],
2097+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person']));
2098+
2099+
describe('should parse a ! path of length 2 starting with an empty list in another list',
2100+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2101+
'(()!fam:mother 1) a fam:Person.',
2102+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'],
2103+
...list(['_:b0', '_:b1'], ['_:b2', '"1"^^http://www.w3.org/2001/XMLSchema#integer']),
2104+
['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b1']
2105+
));
2106+
2107+
describe('should parse a ! path of length 2 starting with an empty list in another list as second element',
2108+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2109+
'(1 ()!fam:mother) a fam:Person.',
2110+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'],
2111+
...list(['_:b0', '"1"^^http://www.w3.org/2001/XMLSchema#integer'], ['_:b1', '_:b2']),
2112+
['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b2']
2113+
));
2114+
2115+
describe('should parse a ! path of length 2 starting with an empty list in another list of one element',
2116+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2117+
'(()!fam:mother) a fam:Person.',
2118+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'],
2119+
...list(['_:b0', '_:b1']),
2120+
['http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', 'f:mother', '_:b1']));
2121+
2122+
describe('should parse a ! path of length 2 as nested subject in a list',
2123+
shouldParse(parser, '@prefix : <ex:>. @prefix fam: <f:>.' +
2124+
'((:joe!fam:mother) 1) a fam:Person.',
2125+
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'f:Person'],
2126+
...list(['_:b0', '_:b1'], ['_:b3', '"1"^^http://www.w3.org/2001/XMLSchema#integer']),
2127+
...list(['_:b1', '_:b2']),
2128+
['ex:joe', 'f:mother', '_:b2']));
2129+
2130+
describe('should parse a birthday rule',
2131+
shouldParse(parser,
2132+
'@prefix foaf: <http://xmlns.com/foaf/0.1/> .' +
2133+
'@prefix math: <http://www.w3.org/2000/10/swap/math#> .' +
2134+
'@prefix : <http://example.org/> .' +
2135+
'' +
2136+
'{' +
2137+
' ?x :trueOnDate ?date.' +
2138+
'} <= {' +
2139+
' ((?date ?s!foaf:birthday)!math:difference 31622400) math:integerQuotient ?age .' +
2140+
'} .',
2141+
['?x', 'http://example.org/trueOnDate', '?date', '_:b0'],
2142+
// eslint-disable-next-line no-warning-comments
2143+
// FIXME: when merging with https://github.com/rdfjs/N3.js/pull/327
2144+
['_:b1', 'http://www.w3.org/2000/10/swap/log#implies', '_:b0'],
2145+
...[
2146+
...list(['_:b2', '_:b6'], ['_:b7', '"31622400"^^http://www.w3.org/2001/XMLSchema#integer']),
2147+
['_:b2', 'http://www.w3.org/2000/10/swap/math#integerQuotient', '?age'],
2148+
...list(['_:b3', '?date'], ['_:b4', '_:b5']),
2149+
['?s', 'http://xmlns.com/foaf/0.1/birthday', '_:b5'],
2150+
['_:b3', 'http://www.w3.org/2000/10/swap/math#difference', '_:b6'],
2151+
].map(elem => [...elem, '_:b1'])
2152+
));
2153+
20612154
describe('should parse a formula as list item',
20622155
shouldParse(parser, '<a> <findAll> ( <b> { <b> a <type>. <b> <something> <foo> } <o> ).',
20632156
['a', 'findAll', '_:b0'],
2064-
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'b'],
2065-
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', '_:b2'],
2066-
['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', 'o'],
2067-
['_:b2', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
2157+
...list(['_:b0', 'b'], ['_:b2', 'o']),
20682158
['b', 'something', 'foo', '_:b1'],
20692159
['b', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'type', '_:b1']
20702160
));
@@ -2120,6 +2210,55 @@ describe('Parser', () => {
21202210
it('should not parse nested quads',
21212211
shouldNotParse(parser, '<<_:a <http://ex.org/b> _:b <http://ex.org/b>>> <http://ex.org/b> "c" .',
21222212
'Expected >> to follow "_:.b" but got IRI on line 1.'));
2213+
2214+
for (const [elem, value] of [
2215+
['()', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
2216+
['( )', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
2217+
['( )', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
2218+
['<http://www.w3.org/1999/02/22-rdf-syntax-ns#nil>', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
2219+
[':joe', 'ex:joe'],
2220+
['<<:joe a :Person>>', ['ex:joe', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:Person']],
2221+
]) {
2222+
for (const pathType of ['!', '^']) {
2223+
// eslint-disable-next-line no-inner-declarations
2224+
function son(bnode) {
2225+
return pathType === '!' ? [value, 'f:son', `_:b${bnode}`] : [`_:b${bnode}`, 'f:son', value];
2226+
}
2227+
2228+
for (const [f, triple] of [
2229+
[x => `(${x}) a :List .`, ['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'ex:List']],
2230+
// [x => `<l> (${x}) <m> .`, ['l', '_:b0', 'm']],
2231+
[x => `<l> <is> (${x}) .`, ['l', 'is', '_:b0']],
2232+
]) {
2233+
// eslint-disable-next-line no-inner-declarations
2234+
function check(content, ...triples) {
2235+
describe(`should parse [${f(content)}]`,
2236+
shouldParse(parser, `@prefix : <ex:>. @prefix fam: <f:>.${f(content)}`,
2237+
triple, ...triples));
2238+
}
2239+
2240+
check(`${elem}${pathType}fam:son`, ...list(['_:b0', '_:b1']), son('1'));
2241+
check(`(${elem}${pathType}fam:son)`, ...list(['_:b0', '_:b1']), ...list(['_:b1', '_:b2']), son('2'));
2242+
2243+
check(`${elem}${pathType}fam:son <x> <y>`, ...list(['_:b0', '_:b1'], ['_:b2', 'x'], ['_:b3', 'y']), son('1'));
2244+
check(`<x> ${elem}${pathType}fam:son <y>`, ...list(['_:b0', 'x'], ['_:b1', '_:b2'], ['_:b3', 'y']), son('2'));
2245+
check(`<x> <y> ${elem}${pathType}fam:son`, ...list(['_:b0', 'x'], ['_:b1', 'y'], ['_:b2', '_:b3']), son('3'));
2246+
2247+
check(`(${elem}${pathType}fam:son) <x> <y>`,
2248+
...list(['_:b0', '_:b1'], ['_:b3', 'x'], ['_:b4', 'y']),
2249+
...list(['_:b1', '_:b2']),
2250+
son('2'));
2251+
check(`<x> (${elem}${pathType}fam:son) <y>`,
2252+
...list(['_:b0', 'x'], ['_:b1', '_:b2'], ['_:b4', 'y']),
2253+
...list(['_:b2', '_:b3']),
2254+
son('3'));
2255+
check(`<x> <y> (${elem}${pathType}fam:son)`,
2256+
...list(['_:b0', 'x'], ['_:b1', 'y'], ['_:b2', '_:b3']),
2257+
...list(['_:b3', '_:b4']),
2258+
son('4'));
2259+
}
2260+
}
2261+
}
21232262
});
21242263

21252264
describe('A Parser instance for the N3 format with the explicitQuantifiers option', () => {
@@ -2638,7 +2777,6 @@ describe('Parser', () => {
26382777
function splitAllWays(result, left, right, chunkSize) {
26392778
// Push current left + right to the result list
26402779
result.push(left.concat(right));
2641-
// document.write(left.concat(right) + '<br />');
26422780

26432781
// If we still have chars to work with in the right side then keep splitting
26442782
if (right.length > 1) {
@@ -2705,7 +2843,7 @@ function _shouldParseChunks(parser, input, items) {
27052843
function shouldParse(parser, input) {
27062844
return () => {
27072845
const expected = Array.prototype.slice.call(arguments, 1);
2708-
// Shift parameters as necessary
2846+
// Shift parameters as necessary
27092847
if (parser.call)
27102848
expected.shift();
27112849
else
@@ -2804,3 +2942,15 @@ function itShouldResolve(baseIRI, relativeIri, expected) {
28042942
});
28052943
});
28062944
}
2945+
2946+
// creates an RDF list from the input
2947+
function list(...elems) {
2948+
const arr = [];
2949+
for (let i = 0; i < elems.length; i++) {
2950+
arr.push(
2951+
[elems[i][0], 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', elems[i][1]],
2952+
[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]]
2953+
);
2954+
}
2955+
return arr;
2956+
}

0 commit comments

Comments
 (0)