n3 2.11.5 → 2.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/N3Parser.js CHANGED
@@ -93,9 +93,13 @@ class N3Parser {
93
93
  const queryPos = baseIRI.indexOf('?');
94
94
  const path = queryPos < 0 ? baseIRI : baseIRI.substr(0, queryPos);
95
95
  this._basePath = baseIRI.indexOf('/') < 0 ? baseIRI : path.substr(0, path.lastIndexOf('/') + 1);
96
- baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i);
96
+ const base = baseIRI;
97
+ baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(\/\/[^\/?#]*)?/i);
97
98
  this._baseRoot = baseIRI[0];
98
99
  this._baseScheme = baseIRI[1];
100
+ // If the base has an authority but an empty path,
101
+ // relative IRIs merge under the path '/' (RFC 3986 §5.2.3)
102
+ if (baseIRI[2] !== undefined && (base.length === this._baseRoot.length || base[this._baseRoot.length] === '?')) this._basePath = `${this._baseRoot}/`;
99
103
  }
100
104
  }
101
105
 
@@ -1430,8 +1434,11 @@ class N3Parser {
1430
1434
  }
1431
1435
  // Resolve root-relative IRIs at the root of the base IRI
1432
1436
  case '/':
1433
- // Resolve scheme-relative IRIs to the scheme
1434
- return (iri[1] === '/' ? this._baseScheme : this._baseRoot) + this._removeDotSegments(iri);
1437
+ // Resolve scheme-relative IRIs to the scheme,
1438
+ // keeping the reference's authority intact (RFC 3986 §5.2.4)
1439
+ if (iri[1] !== '/') return this._baseRoot + this._removeDotSegments(iri);
1440
+ // Without a base scheme, a leading ':' marks where the authority starts
1441
+ return this._baseScheme ? this._removeDotSegments(this._baseScheme + iri) : this._removeDotSegments(`:${iri}`).substr(1);
1435
1442
  // Resolve all other IRIs at the base IRI's path
1436
1443
  default:
1437
1444
  // Relative IRIs cannot contain a colon in the first path segment
@@ -1458,8 +1465,8 @@ class N3Parser {
1458
1465
  if (pathStart < 0) {
1459
1466
  // Skip two slashes before the authority
1460
1467
  if (iri[++i] === '/' && iri[++i] === '/')
1461
- // Skip to slash after the authority
1462
- while ((pathStart = i + 1) < length && iri[pathStart] !== '/') i = pathStart;
1468
+ // Skip to the end of the authority
1469
+ while ((pathStart = i + 1) < length && iri[pathStart] !== '/' && iri[pathStart] !== '?' && iri[pathStart] !== '#') i = pathStart;
1463
1470
  }
1464
1471
  break;
1465
1472
  // Don't modify a query string or fragment
package/lib/N3Store.js CHANGED
@@ -116,6 +116,31 @@ function difference(s1, s2, depth = 4) {
116
116
  return target;
117
117
  }
118
118
 
119
+ // Returns the key of a term in the entity index.
120
+ // Keys mark the term type by their first character, so the IRI of a named node
121
+ // that starts with such a marker (as relative IRIs can) is wrapped in < and >.
122
+ const markedIRI = /^[?_"[.<]/;
123
+ function entityKey(term) {
124
+ // Strings are term ids, which only need wrapping when they are IRIs starting with <
125
+ if (typeof term === 'string') return term.charCodeAt(0) !== 0x3C || term === '<>' ? term : `<${term}>`;
126
+ // IDs of IRIs usually start with a lowercase scheme letter, which never marks a term type
127
+ const id = (0, _N3DataFactory.termToId)(term);
128
+ if (id.charCodeAt(0) >= 0x61 || !term) return id;
129
+ // Keys of quoted triples are built from the keys of their components
130
+ if (term.termType === 'Quad') return JSON.stringify(quadKeyParts(term));
131
+ return term.termType !== 'NamedNode' || !markedIRI.test(term.value) ? id : `<${term.value}>`;
132
+ }
133
+
134
+ // Returns the keys of the components of a quad, nested like the parts of its internal id
135
+ function quadKeyParts(quad) {
136
+ const parts = [nestedKey(quad.subject), nestedKey(quad.predicate), nestedKey(quad.object)];
137
+ if (quad.graph && !(0, _N3Util.isDefaultGraph)(quad.graph)) parts.push(nestedKey(quad.graph));
138
+ return parts;
139
+ }
140
+ function nestedKey(term) {
141
+ return term.termType === 'Quad' ? quadKeyParts(term) : entityKey(term);
142
+ }
143
+
119
144
  // ## Constructor
120
145
  class N3EntityIndex {
121
146
  constructor(options = {}) {
@@ -132,6 +157,9 @@ class N3EntityIndex {
132
157
  this._factory = options.factory || _N3DataFactory.default;
133
158
  }
134
159
  _termFromId(id) {
160
+ // A key in < and > is a named node
161
+ if (id[0] === '<') return this._factory.namedNode(id.substring(1, id.length - 1));
162
+ // A key starting with . is a quoted triple
135
163
  if (id[0] === '.') {
136
164
  const entities = this._entities;
137
165
  const terms = id.split('.');
@@ -148,12 +176,25 @@ class N3EntityIndex {
148
176
  let g;
149
177
  return s && p && o && ((0, _N3Util.isDefaultGraph)(term.graph) || (g = this._termToNumericId(term.graph))) && this._ids[g ? `.${s}.${p}.${o}.${g}` : `.${s}.${p}.${o}`];
150
178
  }
151
- return this._ids[(0, _N3DataFactory.termToId)(term)];
179
+ return typeof term === 'string' ? this._stringToNumericId(term) : this._ids[entityKey(term)];
180
+ }
181
+
182
+ // Returns the numeric id of a term given as a string id
183
+ _stringToNumericId(term) {
184
+ // The string is the key unless it is an IRI starting with <. Read the first character
185
+ // from the stored key after a match, which is cheaper than from a concatenated string.
186
+ const id = this._ids[term];
187
+ if (id ? this._entities[id].charCodeAt(0) !== 0x3C : term.charCodeAt(0) !== 0x3C) return id;
188
+ return term === '<>' ? id : this._ids[`<${term}>`];
152
189
  }
153
190
  _termToNewNumericId(term) {
191
+ if (typeof term === 'string') return this._stringToNumericId(term) || this._addEntity(entityKey(term));
154
192
  // This assumes that no graph term is present - we may wish to error if there is one
155
- const str = term && term.termType === 'Quad' ? `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${(0, _N3Util.isDefaultGraph)(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}`}` : (0, _N3DataFactory.termToId)(term);
156
- return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id);
193
+ const str = term && term.termType === 'Quad' ? `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${(0, _N3Util.isDefaultGraph)(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}`}` : entityKey(term);
194
+ return this._ids[str] || this._addEntity(str);
195
+ }
196
+ _addEntity(key) {
197
+ return this._ids[this._entities[++this._id] = key] = this._id;
157
198
  }
158
199
  createBlankNode(suggestedName) {
159
200
  let name, index;
@@ -1305,7 +1346,7 @@ function intersectMatchPatterns(left, right) {
1305
1346
  for (let i = 0; i < 4; i++) {
1306
1347
  const leftTerm = left[i],
1307
1348
  rightTerm = right[i];
1308
- if (leftTerm === null || leftTerm === undefined) result[i] = rightTerm;else if (rightTerm === null || rightTerm === undefined || (0, _N3DataFactory.termToId)(leftTerm) === (0, _N3DataFactory.termToId)(rightTerm)) result[i] = leftTerm;else return false;
1349
+ if (leftTerm === null || leftTerm === undefined) result[i] = rightTerm;else if (rightTerm === null || rightTerm === undefined || entityKey(leftTerm) === entityKey(rightTerm)) result[i] = leftTerm;else return false;
1309
1350
  }
1310
1351
  return result;
1311
1352
  }
@@ -1368,7 +1409,7 @@ class DatasetCoreAndReadableStream extends _readableStream.Readable {
1368
1409
  object,
1369
1410
  graph
1370
1411
  } = this;
1371
- return !this._matchesNothing && (subject === null || subject === undefined || (0, _N3DataFactory.termToId)(subject) === (0, _N3DataFactory.termToId)(quad.subject)) && (predicate === null || predicate === undefined || (0, _N3DataFactory.termToId)(predicate) === (0, _N3DataFactory.termToId)(quad.predicate)) && (object === null || object === undefined || (0, _N3DataFactory.termToId)(object) === (0, _N3DataFactory.termToId)(quad.object)) && (graph === null || graph === undefined || (0, _N3DataFactory.termToId)(graph) === (0, _N3DataFactory.termToId)(quad.graph));
1412
+ return !this._matchesNothing && (subject === null || subject === undefined || entityKey(subject) === entityKey(quad.subject)) && (predicate === null || predicate === undefined || entityKey(predicate) === entityKey(quad.predicate)) && (object === null || object === undefined || entityKey(object) === entityKey(quad.object)) && (graph === null || graph === undefined || entityKey(graph) === entityKey(quad.graph));
1372
1413
  }
1373
1414
 
1374
1415
  // ### `_assertMatchesPattern` rejects a Quad outside this view.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.11.5",
3
+ "version": "2.11.6",
4
4
  "description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.",
5
5
  "author": "Ruben Verborgh <ruben.verborgh@gmail.com>",
6
6
  "keywords": [
package/src/N3Parser.js CHANGED
@@ -86,9 +86,14 @@ export default class N3Parser {
86
86
  const path = queryPos < 0 ? baseIRI : baseIRI.substr(0, queryPos);
87
87
  this._basePath = baseIRI.indexOf('/') < 0 ? baseIRI :
88
88
  path.substr(0, path.lastIndexOf('/') + 1);
89
- baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i);
89
+ const base = baseIRI;
90
+ baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(\/\/[^\/?#]*)?/i);
90
91
  this._baseRoot = baseIRI[0];
91
92
  this._baseScheme = baseIRI[1];
93
+ // If the base has an authority but an empty path,
94
+ // relative IRIs merge under the path '/' (RFC 3986 §5.2.3)
95
+ if (baseIRI[2] !== undefined && (base.length === this._baseRoot.length || base[this._baseRoot.length] === '?'))
96
+ this._basePath = `${this._baseRoot}/`;
92
97
  }
93
98
  }
94
99
 
@@ -1546,8 +1551,13 @@ export default class N3Parser {
1546
1551
  }
1547
1552
  // Resolve root-relative IRIs at the root of the base IRI
1548
1553
  case '/':
1549
- // Resolve scheme-relative IRIs to the scheme
1550
- return (iri[1] === '/' ? this._baseScheme : this._baseRoot) + this._removeDotSegments(iri);
1554
+ // Resolve scheme-relative IRIs to the scheme,
1555
+ // keeping the reference's authority intact (RFC 3986 §5.2.4)
1556
+ if (iri[1] !== '/')
1557
+ return this._baseRoot + this._removeDotSegments(iri);
1558
+ // Without a base scheme, a leading ':' marks where the authority starts
1559
+ return this._baseScheme ? this._removeDotSegments(this._baseScheme + iri) :
1560
+ this._removeDotSegments(`:${iri}`).substr(1);
1551
1561
  // Resolve all other IRIs at the base IRI's path
1552
1562
  default:
1553
1563
  // Relative IRIs cannot contain a colon in the first path segment
@@ -1572,8 +1582,8 @@ export default class N3Parser {
1572
1582
  if (pathStart < 0) {
1573
1583
  // Skip two slashes before the authority
1574
1584
  if (iri[++i] === '/' && iri[++i] === '/')
1575
- // Skip to slash after the authority
1576
- while ((pathStart = i + 1) < length && iri[pathStart] !== '/')
1585
+ // Skip to the end of the authority
1586
+ while ((pathStart = i + 1) < length && iri[pathStart] !== '/' && iri[pathStart] !== '?' && iri[pathStart] !== '#')
1577
1587
  i = pathStart;
1578
1588
  }
1579
1589
  break;
package/src/N3Store.js CHANGED
@@ -118,6 +118,35 @@ function difference(s1, s2, depth = 4) {
118
118
  return target;
119
119
  }
120
120
 
121
+ // Returns the key of a term in the entity index.
122
+ // Keys mark the term type by their first character, so the IRI of a named node
123
+ // that starts with such a marker (as relative IRIs can) is wrapped in < and >.
124
+ const markedIRI = /^[?_"[.<]/;
125
+ function entityKey(term) {
126
+ // Strings are term ids, which only need wrapping when they are IRIs starting with <
127
+ if (typeof term === 'string')
128
+ return term.charCodeAt(0) !== 0x3C || term === '<>' ? term : `<${term}>`;
129
+ // IDs of IRIs usually start with a lowercase scheme letter, which never marks a term type
130
+ const id = termToId(term);
131
+ if (id.charCodeAt(0) >= 0x61 || !term)
132
+ return id;
133
+ // Keys of quoted triples are built from the keys of their components
134
+ if (term.termType === 'Quad')
135
+ return JSON.stringify(quadKeyParts(term));
136
+ return term.termType !== 'NamedNode' || !markedIRI.test(term.value) ? id : `<${term.value}>`;
137
+ }
138
+
139
+ // Returns the keys of the components of a quad, nested like the parts of its internal id
140
+ function quadKeyParts(quad) {
141
+ const parts = [nestedKey(quad.subject), nestedKey(quad.predicate), nestedKey(quad.object)];
142
+ if (quad.graph && !isDefaultGraph(quad.graph))
143
+ parts.push(nestedKey(quad.graph));
144
+ return parts;
145
+ }
146
+ function nestedKey(term) {
147
+ return term.termType === 'Quad' ? quadKeyParts(term) : entityKey(term);
148
+ }
149
+
121
150
  // ## Constructor
122
151
  export class N3EntityIndex {
123
152
  constructor(options = {}) {
@@ -135,6 +164,10 @@ export class N3EntityIndex {
135
164
  }
136
165
 
137
166
  _termFromId(id) {
167
+ // A key in < and > is a named node
168
+ if (id[0] === '<')
169
+ return this._factory.namedNode(id.substring(1, id.length - 1));
170
+ // A key starting with . is a quoted triple
138
171
  if (id[0] === '.') {
139
172
  const entities = this._entities;
140
173
  const terms = id.split('.');
@@ -159,18 +192,34 @@ export class N3EntityIndex {
159
192
  return s && p && o && (isDefaultGraph(term.graph) || (g = this._termToNumericId(term.graph))) &&
160
193
  this._ids[g ? `.${s}.${p}.${o}.${g}` : `.${s}.${p}.${o}`];
161
194
  }
162
- return this._ids[termToId(term)];
195
+ return typeof term === 'string' ? this._stringToNumericId(term) : this._ids[entityKey(term)];
196
+ }
197
+
198
+ // Returns the numeric id of a term given as a string id
199
+ _stringToNumericId(term) {
200
+ // The string is the key unless it is an IRI starting with <. Read the first character
201
+ // from the stored key after a match, which is cheaper than from a concatenated string.
202
+ const id = this._ids[term];
203
+ if (id ? this._entities[id].charCodeAt(0) !== 0x3C : term.charCodeAt(0) !== 0x3C)
204
+ return id;
205
+ return term === '<>' ? id : this._ids[`<${term}>`];
163
206
  }
164
207
 
165
208
  _termToNewNumericId(term) {
209
+ if (typeof term === 'string')
210
+ return this._stringToNumericId(term) || this._addEntity(entityKey(term));
166
211
  // This assumes that no graph term is present - we may wish to error if there is one
167
212
  const str = term && term.termType === 'Quad' ?
168
213
  `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${
169
214
  isDefaultGraph(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}`
170
215
  }`
171
- : termToId(term);
216
+ : entityKey(term);
217
+
218
+ return this._ids[str] || this._addEntity(str);
219
+ }
172
220
 
173
- return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id);
221
+ _addEntity(key) {
222
+ return this._ids[this._entities[++this._id] = key] = this._id;
174
223
  }
175
224
 
176
225
  createBlankNode(suggestedName) {
@@ -1382,7 +1431,7 @@ function intersectMatchPatterns(left, right) {
1382
1431
  const leftTerm = left[i], rightTerm = right[i];
1383
1432
  if (leftTerm === null || leftTerm === undefined)
1384
1433
  result[i] = rightTerm;
1385
- else if (rightTerm === null || rightTerm === undefined || termToId(leftTerm) === termToId(rightTerm))
1434
+ else if (rightTerm === null || rightTerm === undefined || entityKey(leftTerm) === entityKey(rightTerm))
1386
1435
  result[i] = leftTerm;
1387
1436
  else
1388
1437
  return false;
@@ -1433,10 +1482,10 @@ class DatasetCoreAndReadableStream extends Readable {
1433
1482
  _matchesQuad(quad) {
1434
1483
  const { subject, predicate, object, graph } = this;
1435
1484
  return !this._matchesNothing &&
1436
- (subject === null || subject === undefined || termToId(subject) === termToId(quad.subject)) &&
1437
- (predicate === null || predicate === undefined || termToId(predicate) === termToId(quad.predicate)) &&
1438
- (object === null || object === undefined || termToId(object) === termToId(quad.object)) &&
1439
- (graph === null || graph === undefined || termToId(graph) === termToId(quad.graph));
1485
+ (subject === null || subject === undefined || entityKey(subject) === entityKey(quad.subject)) &&
1486
+ (predicate === null || predicate === undefined || entityKey(predicate) === entityKey(quad.predicate)) &&
1487
+ (object === null || object === undefined || entityKey(object) === entityKey(quad.object)) &&
1488
+ (graph === null || graph === undefined || entityKey(graph) === entityKey(quad.graph));
1440
1489
  }
1441
1490
 
1442
1491
  // ### `_assertMatchesPattern` rejects a Quad outside this view.