diff --git a/README.md b/README.md
index bec8efe2..8365734f 100644
--- a/README.md
+++ b/README.md
@@ -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,
diff --git a/src/N3Parser.js b/src/N3Parser.js
index 878dad2c..abca9229 100644
--- a/src/N3Parser.js
+++ b/src/N3Parser.js
@@ -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;
@@ -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);
+ }
}
}
diff --git a/test/N3Parser-test.js b/test/N3Parser-test.js
index 963e9ee4..18ada579 100644
--- a/test/N3Parser-test.js
+++ b/test/N3Parser-test.js
@@ -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',
@@ -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, '
(_: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, '
(_:a). _:a [ (_: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, ' (_:a). _:a [ _: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
(_:a). _:a
_: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
(_:a). _:a