Skip to content

Commit

Permalink
fix: fix support for paths to support all usages supported by the N3 …
Browse files Browse the repository at this point in the history
…spec
  • Loading branch information
jeswr committed Mar 24, 2023
1 parent 925d66f commit be1bcd9
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 13 deletions.
27 changes: 21 additions & 6 deletions src/N3Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<parent list element> rdf:first <this list>.`.
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 `<parent list element> rdf:first <this list>.`.
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 `<parent list element> rdf:first <this list>.`.
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;
Expand Down Expand Up @@ -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;
Expand Down
164 changes: 157 additions & 7 deletions test/N3Parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', ''],
Expand Down Expand Up @@ -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 : <ex:>. @prefix fam: <f:>.' +
'(: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 : <ex:>. @prefix fam: <f:>.' +
'(: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 : <ex:>. @prefix fam: <f:>.' +
'()!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 : <ex:>. @prefix fam: <f:>.' +
'( :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 : <ex:>. @prefix fam: <f:>.' +
'(( :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 : <ex:>. @prefix fam: <f:>.' +
'(()!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 : <ex:>. @prefix fam: <f:>.' +
'(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 : <ex:>. @prefix fam: <f:>.' +
'(()!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 : <ex:>. @prefix fam: <f:>.' +
'((: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: <http://xmlns.com/foaf/0.1/> .' +
'@prefix math: <http://www.w3.org/2000/10/swap/math#> .' +
'@prefix : <http://example.org/> .' +
'' +
'{' +
' ?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> <findAll> ( <b> { <b> a <type>. <b> <something> <foo> } <o> ).',
['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']
));
Expand Down Expand Up @@ -2120,6 +2210,55 @@ describe('Parser', () => {
it('should not parse nested quads',
shouldNotParse(parser, '<<_:a <http://ex.org/b> _:b <http://ex.org/b>>> <http://ex.org/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>', '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 => `<l> (${x}) <m> .`, ['l', '_:b0', 'm']],
[x => `<l> <is> (${x}) .`, ['l', 'is', '_:b0']],
]) {
// eslint-disable-next-line no-inner-declarations
function check(content, ...triples) {
describe(`should parse [${f(content)}]`,
shouldParse(parser, `@prefix : <ex:>. @prefix fam: <f:>.${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 <x> <y>`, ...list(['_:b0', '_:b1'], ['_:b2', 'x'], ['_:b3', 'y']), son('1'));
check(`<x> ${elem}${pathType}fam:son <y>`, ...list(['_:b0', 'x'], ['_:b1', '_:b2'], ['_:b3', 'y']), son('2'));
check(`<x> <y> ${elem}${pathType}fam:son`, ...list(['_:b0', 'x'], ['_:b1', 'y'], ['_:b2', '_:b3']), son('3'));

check(`(${elem}${pathType}fam:son) <x> <y>`,
...list(['_:b0', '_:b1'], ['_:b3', 'x'], ['_:b4', 'y']),
...list(['_:b1', '_:b2']),
son('2'));
check(`<x> (${elem}${pathType}fam:son) <y>`,
...list(['_:b0', 'x'], ['_:b1', '_:b2'], ['_:b4', 'y']),
...list(['_:b2', '_:b3']),
son('3'));
check(`<x> <y> (${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', () => {
Expand Down Expand Up @@ -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) + '<br />');

// If we still have chars to work with in the right side then keep splitting
if (right.length > 1) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

0 comments on commit be1bcd9

Please sign in to comment.