Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ The parser can output a backwards chaining rule such as `_:q <= _:p.` in two way
const parser = new N3.Parser({ isImpliedBy: true });
```

In N3 documents, the parser by default rescopes blank node labels
in lists and blank node property lists, in addition to formulas,
so `_:a` inside a list does not co-reference `_:a` outside of it.
The `formulaScopedBlankNodes` flag scopes blank node labels to formulas only,
matching N3's formula-scoped blank node semantics
(this will become the default in the next major version):
```JavaScript
const parser = new N3.Parser({ format: 'N3', formulaScopedBlankNodes: true });
```

### From an RDF stream to quads

`N3.Parser` can parse [Node.js streams](http://nodejs.org/api/stream.html) as they grow,
Expand Down
16 changes: 11 additions & 5 deletions src/N3Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export default class N3Parser {
this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode, n3: isN3, isImpliedBy: this._isImpliedBy });
// Disable explicit quantifiers by default
this._explicitQuantifiers = !!options.explicitQuantifiers;
// Disable formula-only blank node scoping by default
this._formulaScopedBlankNodes = !!options.formulaScopedBlankNodes;
// Disable parsing of unsupported versions by default
this._parseUnsupportedVersions = !!options.parseUnsupportedVersions;
this._version = options.version;
Expand Down Expand Up @@ -87,11 +89,15 @@ export default class N3Parser {
if (n3Mode) {
// Every new scope resets the predicate direction
this._inversePredicate = false;
// In N3, blank nodes are scoped to a formula
// (using a dot as separator, as a blank node label cannot start with it)
this._prefixes._ = (this._graph ? `${this._graph.value}.` : '.');
// Quantifiers are scoped to a formula
this._quantified = Object.create(this._quantified);
// In N3, blank nodes and quantifiers are scoped to a formula;
// with `formulaScopedBlankNodes`, lists and blank node property lists
// share the enclosing scope instead of rescoping
if (!this._formulaScopedBlankNodes || type === 'formula') {
// Label the scope with the enclosing formula's blank node
// (using a dot as separator, as a blank node label cannot start with it)
this._prefixes._ = (this._graph ? `${this._graph.value}.` : '.');
this._quantified = Object.create(this._quantified);
}
Comment thread
jeswr marked this conversation as resolved.
}
}

Expand Down
75 changes: 75 additions & 0 deletions test/N3Parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2447,6 +2447,7 @@ describe('Parser', () => {
describe('A Parser instance for the N3 format', () => {
function parser() { return new Parser({ baseIRI: BASE_IRI, format: 'N3' }); }
function parserIsImpliedBy() { return new Parser({ baseIRI: BASE_IRI, format: 'N3', isImpliedBy: true }); }
function parserFormulaScoped() { return new Parser({ baseIRI: BASE_IRI, format: 'N3', formulaScopedBlankNodes: true }); }

it(
'should parse a single triple',
Expand Down Expand Up @@ -2609,6 +2610,80 @@ describe('Parser', () => {
['_:b3.a', '_:b3.b', '_:b3.c', '_:b3']),
);

// The tests below pin the default behaviour of rescoping blank node
// labels in lists and blank node property lists (#332);
// the default flips to `formulaScopedBlankNodes` in a next major version (#630)

it(
'should rescope a blank node in a list by default',
shouldParse(parser, '<s> <p> (_:a).',
['s', 'p', '_:b0'],
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:.a'],
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']),
);

it(
'should not reuse identifiers of blank nodes within and outside of lists by default',
shouldParse(parser, '<s> <p> (_:a). _:a <b> <c>.',
['s', 'p', '_:b0'],
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:.a'],
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
['_:b0_a', 'b', 'c']),
);

it(
'should not reuse identifiers of blank nodes within and outside of blank node property lists by default',
shouldParse(parser, '_:a <p> [ <q> _:a ].',
['_:b0_a', 'p', '_:b0'],
['_:b0', 'q', '_:.a']),
);

it(
'should scope blank nodes in a list to the enclosing formula by default',
shouldParse(parser, '_:a <p> <o>. { <s> <q> (_:a). _:a <r> <o2>. } <d> <e>.',
['_:b0_a', 'p', 'o'],
['s', 'q', '_:b1', '_:b0'],
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0.a', '_:b0'],
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', '_:b0'],
['_:b0.a', 'r', 'o2', '_:b0'],
['_:b0', 'd', 'e']),
);

it(
'should parse a blank node in a list with formulaScopedBlankNodes',
shouldParse(parserFormulaScoped, '<s> <p> (_:a).',
['s', 'p', '_:b0'],
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_a'],
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil']),
);

it(
'should reuse identifiers of blank nodes within and outside of lists with formulaScopedBlankNodes',
shouldParse(parserFormulaScoped, '<s> <p> (_:a). _:a <b> <c>.',
['s', 'p', '_:b0'],
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0_a'],
['_:b0', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil'],
['_:b0_a', 'b', 'c']),
);

it(
'should reuse identifiers of blank nodes within and outside of blank node property lists with formulaScopedBlankNodes',
shouldParse(parserFormulaScoped, '_:a <p> [ <q> _:a ].',
['_:b0_a', 'p', '_:b0'],
['_:b0', 'q', '_:b0_a']),
);

it(
'should scope blank nodes in a list to the enclosing formula with formulaScopedBlankNodes',
shouldParse(parserFormulaScoped, '_:a <p> <o>. { <s> <q> (_:a). _:a <r> <o2>. } <d> <e>.',
['_:b0_a', 'p', 'o'],
['s', 'q', '_:b1', '_:b0'],
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', '_:b0.a', '_:b0'],
['_:b1', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', '_:b0'],
['_:b0.a', 'r', 'o2', '_:b0'],
['_:b0', 'd', 'e']),
);

it(
'should parse a @forSome statement',
shouldParse(parser, '@forSome <x>. <x> <x> <x>.',
Expand Down
23 changes: 23 additions & 0 deletions test/N3Writer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,29 @@ describe('Writer', () => {
});
});

it('should serialize a blank node in an N3 list with a valid label when formulaScopedBlankNodes is set', done => {
const quads = new Parser({ format: 'text/n3', formulaScopedBlankNodes: true }).parse('<a> <b> (_:x). _:x <c> <d>.');
const writer = new Writer();
writer.addQuads(quads);
writer.end((error, output) => {
// A label such as `_:.x` would fail to reparse (#332)
expect(() => new Parser().parse(output)).not.toThrow();
done(error);
});
});

it('should serialize a blank node in an N3 list with an invalid label by default', done => {
const quads = new Parser({ format: 'text/n3' }).parse('<a> <b> (_:x). _:x <c> <d>.');
const writer = new Writer();
writer.addQuads(quads);
writer.end((error, output) => {
// The default rescoping produces the label `_:.x`,
// which fails to reparse (#332; the default flips in #630)
expect(() => new Parser().parse(output)).toThrow();
done(error);
});
});

it(
'should serialize subject and object triples passed by options.listHeads',
done => {
Expand Down
Loading