n3 1.25.0 → 1.25.1

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/BaseIRI.js CHANGED
@@ -4,131 +4,93 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
+ var _Util = require("./Util");
8
+ // Do not handle base IRIs without scheme, and currently unsupported cases:
9
+ // - file: IRIs (which could also use backslashes)
10
+ // - IRIs containing /. or /.. or //
11
+ const INVALID_OR_UNSUPPORTED = /^:?[^:?#]*(?:[?#]|$)|^file:|^[^:]*:\/*[^?#]+?\/(?:\.\.?(?:\/|$)|\/)/i;
12
+ const CURRENT = './';
13
+ const PARENT = '../';
14
+ const QUERY = '?';
15
+ const FRAGMENT = '#';
7
16
  class BaseIRI {
8
- constructor(iri) {
9
- if (iri.startsWith('file://')) {
10
- // Base IRIs starting with file:// are not supported. Silently fail.
11
- return;
12
- }
17
+ constructor(base) {
18
+ this.base = base;
19
+ this._baseLength = 0;
20
+ this._baseMatcher = null;
21
+ this._pathReplacements = new Array(base.length + 1);
22
+ }
23
+ static supports(base) {
24
+ return !INVALID_OR_UNSUPPORTED.test(base);
25
+ }
26
+ _getBaseMatcher() {
27
+ if (this._baseMatcher) return this._baseMatcher;
28
+ if (!BaseIRI.supports(this.base)) return this._baseMatcher = /.^/;
13
29
 
14
- // Generate regex for baseIRI with optional groups for segments
15
- // Stage 0: find the first scheme delimiter -> stage 1
16
- // Stage 1: find the next /, ?, or #. '/' -> new segment -> stage 1, '?' -> stage 2, '#' -> update end to position before hash -> stage 3, none -> stage 3
17
- // Stage 2: find the next #. '#' -> update end to position before hash -> stage 3, none -> stage 3
18
- // Stage 3: find the end of the string, add '$' to this last segment
19
- this._baseSubstitutions = {};
20
- let baseIRIRegex = '';
21
- let segmentsCount = 0;
22
- let stage = 0;
23
- const slashPositions = [];
24
- let i = 0;
25
- let containsQuery = false;
26
- // Stage 0
27
- const match = /:\/{0,2}/.exec(iri);
28
- if (match) {
29
- baseIRIRegex += escapeRegex(iri.substring(0, match.index + match[0].length));
30
- i = match.index + match[0].length;
31
- stage = 1;
32
- } else {
33
- // Base IRI should contain a scheme followed by its delimiter (e.g., http://). Silently fail.
34
- return;
35
- }
36
- if (/\/\.{0,2}\//.test(iri.substring(i))) {
37
- // Base IRIs containing `//`, `/./`, or `/../` are not supported. Silently fail.
38
- return;
39
- }
40
- let end = iri.length;
41
- while (stage === 1 && i < end) {
42
- // Stage 1
43
- const match = /[/?#]/.exec(iri.substring(i));
44
- if (match) {
45
- if (match[0] === '#') {
46
- // Stop at this hash.
47
- end = i + match.index;
48
- stage = 3;
49
- } else {
50
- baseIRIRegex += escapeRegex(iri.substring(i, i + match.index + 1));
51
- baseIRIRegex += '(';
52
- segmentsCount++;
53
- if (match[0] === '/') {
54
- slashPositions.push(i + match.index);
55
- } else {
56
- this._baseSubstitutions[i + match.index] = '?';
57
- containsQuery = true;
58
- stage = 2;
59
- }
60
- i += match.index + 1;
30
+ // Extract the scheme
31
+ const scheme = /^[^:]*:\/*/.exec(this.base)[0];
32
+ const regexHead = ['^', (0, _Util.escapeRegex)(scheme)];
33
+ const regexTail = [];
34
+
35
+ // Generate a regex for every path segment
36
+ const segments = [],
37
+ segmenter = /[^/?#]*([/?#])/y;
38
+ let segment,
39
+ query = 0,
40
+ fragment = 0,
41
+ last = segmenter.lastIndex = scheme.length;
42
+ while (!query && !fragment && (segment = segmenter.exec(this.base))) {
43
+ // Truncate base resolution path at fragment start
44
+ if (segment[1] === FRAGMENT) fragment = segmenter.lastIndex - 1;else {
45
+ // Create regex that matches the segment
46
+ regexHead.push((0, _Util.escapeRegex)(segment[0]), '(?:');
47
+ regexTail.push(')?');
48
+
49
+ // Create dedicated query string replacement
50
+ if (segment[1] !== QUERY) segments.push(last = segmenter.lastIndex);else {
51
+ query = last = segmenter.lastIndex;
52
+ fragment = this.base.indexOf(FRAGMENT, query);
53
+ this._pathReplacements[query] = QUERY;
61
54
  }
62
- } else {
63
- stage = 3;
64
- }
65
- }
66
- if (stage === 2) {
67
- // Stage 2
68
- const match = /#/.exec(iri.substring(i));
69
- if (match) {
70
- // Stop at this hash.
71
- end = i + match.index;
72
- }
73
- stage = 3;
74
- }
75
- if (stage === 3) {
76
- // Stage 3
77
- baseIRIRegex += escapeRegex(iri.substring(i, end));
78
- if (containsQuery) {
79
- baseIRIRegex += '(#|$)';
80
- } else {
81
- baseIRIRegex += '([?#]|$)';
82
55
  }
83
- i = end;
84
56
  }
85
57
 
86
- // Complete the optional groups for the segments
87
- baseIRIRegex += ')?'.repeat(segmentsCount);
58
+ // Precalculate parent path substitutions
59
+ for (let i = 0; i < segments.length; i++) this._pathReplacements[segments[i]] = PARENT.repeat(segments.length - i - 1);
60
+ this._pathReplacements[segments[segments.length - 1]] = CURRENT;
88
61
 
89
- // Precalculate the rest of the substitutions
90
- if (this._baseSubstitutions[end - 1] === undefined) {
91
- this._baseSubstitutions[end - 1] = '';
92
- }
93
- for (let i = 0; i < slashPositions.length; i++) {
94
- this._baseSubstitutions[slashPositions[i]] = '../'.repeat(slashPositions.length - i - 1);
95
- }
96
- this._baseSubstitutions[slashPositions[slashPositions.length - 1]] = './';
97
-
98
- // Set the baseMatcher
99
- this._baseMatcher = new RegExp(baseIRIRegex);
100
- this._baseLength = end;
101
- this.value = iri.substring(0, end);
62
+ // Add the remainder of the base IRI (without fragment) to the regex
63
+ this._baseLength = fragment > 0 ? fragment : this.base.length;
64
+ regexHead.push((0, _Util.escapeRegex)(this.base.substring(last, this._baseLength)), query ? '(?:#|$)' : '(?:[?#]|$)');
65
+ return this._baseMatcher = new RegExp([...regexHead, ...regexTail].join(''));
102
66
  }
103
67
  toRelative(iri) {
104
- if (iri.startsWith('file://')) {
105
- return iri;
106
- }
107
- const delimiterMatch = /:\/{0,2}/.exec(iri);
108
- if (!delimiterMatch || /\/\.{0,2}\//.test(iri.substring(delimiterMatch.index + delimiterMatch[0].length))) {
109
- return iri;
110
- }
111
- const match = this._baseMatcher.exec(iri);
112
- if (!match) {
113
- return iri;
114
- }
68
+ // Unsupported or non-matching base IRI
69
+ const match = this._getBaseMatcher().exec(iri);
70
+ if (!match) return iri;
71
+
72
+ // Exact base IRI match
115
73
  const length = match[0].length;
116
- if (length === this._baseLength && length === iri.length) {
117
- return '';
118
- }
119
- let substitution = this._baseSubstitutions[length - 1];
120
- if (substitution !== undefined) {
121
- const substr = iri.substring(length);
122
- if (substitution === './' && substr && (!substr.startsWith('#') && !substr.startsWith('?') || length === this._baseLength)) {
123
- substitution = '';
124
- }
125
- return substitution + substr;
74
+ if (length === this._baseLength && length === iri.length) return '';
75
+
76
+ // Parent path match
77
+ const parentPath = this._pathReplacements[length];
78
+ if (parentPath) {
79
+ const suffix = iri.substring(length);
80
+ // Don't abbreviate unsupported path
81
+ if (parentPath !== QUERY && /(?:^|\/)(?:\/|..?(?:[/#?]|$))/.test(suffix) &&
82
+ // fast test
83
+ /^(?:[^#?]*?\/)?(?:\/|\.\.?(?:[/#?]|$))/.test(suffix))
84
+ // rigorous test
85
+ return iri;
86
+ // Omit ./ with fragment or query string
87
+ if (parentPath === CURRENT && /^[^?#]/.test(suffix)) return suffix;
88
+ // Append suffix to relative parent path
89
+ return parentPath + suffix;
126
90
  }
127
- // Matched the [?#], so make sure to add the delimiter
91
+
92
+ // Fragment or query string, so include delimiter
128
93
  return iri.substring(length - 1);
129
94
  }
130
95
  }
131
- exports.default = BaseIRI;
132
- function escapeRegex(regex) {
133
- return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
134
- }
96
+ exports.default = BaseIRI;
package/lib/N3Writer.js CHANGED
@@ -8,6 +8,7 @@ var _IRIs = _interopRequireDefault(require("./IRIs"));
8
8
  var _N3DataFactory = _interopRequireWildcard(require("./N3DataFactory"));
9
9
  var _N3Util = require("./N3Util");
10
10
  var _BaseIRI = _interopRequireDefault(require("./BaseIRI"));
11
+ var _Util = require("./Util");
11
12
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
12
13
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
13
14
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -285,7 +286,7 @@ class N3Writer {
285
286
  IRIlist += IRIlist ? `|${prefixIRI}` : prefixIRI;
286
287
  prefixList += (prefixList ? '|' : '') + this._prefixIRIs[prefixIRI];
287
288
  }
288
- IRIlist = escapeRegex(IRIlist, /[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
289
+ IRIlist = (0, _Util.escapeRegex)(IRIlist, /[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
289
290
  this._prefixRegex = new RegExp(`^(?:${prefixList})[^\/]*$|` + `^(${IRIlist})([_a-zA-Z0-9][\\-_a-zA-Z0-9]*)$`);
290
291
  }
291
292
  // End a prefix block with a newline
@@ -381,7 +382,4 @@ function characterReplacer(character) {
381
382
  }
382
383
  }
383
384
  return result;
384
- }
385
- function escapeRegex(regex) {
386
- return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
387
385
  }
package/lib/Util.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.escapeRegex = escapeRegex;
7
+ function escapeRegex(regex) {
8
+ return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "1.25.0",
3
+ "version": "1.25.1",
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/BaseIRI.js CHANGED
@@ -1,136 +1,102 @@
1
+ import { escapeRegex } from './Util';
2
+
3
+ // Do not handle base IRIs without scheme, and currently unsupported cases:
4
+ // - file: IRIs (which could also use backslashes)
5
+ // - IRIs containing /. or /.. or //
6
+ const INVALID_OR_UNSUPPORTED = /^:?[^:?#]*(?:[?#]|$)|^file:|^[^:]*:\/*[^?#]+?\/(?:\.\.?(?:\/|$)|\/)/i;
7
+ const CURRENT = './';
8
+ const PARENT = '../';
9
+ const QUERY = '?';
10
+ const FRAGMENT = '#';
11
+
1
12
  export default class BaseIRI {
2
- constructor(iri) {
3
- if (iri.startsWith('file://')) {
4
- // Base IRIs starting with file:// are not supported. Silently fail.
5
- return;
6
- }
13
+ constructor(base) {
14
+ this.base = base;
15
+ this._baseLength = 0;
16
+ this._baseMatcher = null;
17
+ this._pathReplacements = new Array(base.length + 1);
18
+ }
7
19
 
8
- // Generate regex for baseIRI with optional groups for segments
9
- // Stage 0: find the first scheme delimiter -> stage 1
10
- // Stage 1: find the next /, ?, or #. '/' -> new segment -> stage 1, '?' -> stage 2, '#' -> update end to position before hash -> stage 3, none -> stage 3
11
- // Stage 2: find the next #. '#' -> update end to position before hash -> stage 3, none -> stage 3
12
- // Stage 3: find the end of the string, add '$' to this last segment
13
- this._baseSubstitutions = {};
14
- let baseIRIRegex = '';
15
- let segmentsCount = 0;
16
- let stage = 0;
17
- const slashPositions = [];
18
- let i = 0;
19
- let containsQuery = false;
20
- // Stage 0
21
- const match = /:\/{0,2}/.exec(iri);
22
- if (match) {
23
- baseIRIRegex += escapeRegex(iri.substring(0, match.index + match[0].length));
24
- i = match.index + match[0].length;
25
- stage = 1;
26
- }
27
- else {
28
- // Base IRI should contain a scheme followed by its delimiter (e.g., http://). Silently fail.
29
- return;
30
- }
20
+ static supports(base) {
21
+ return !INVALID_OR_UNSUPPORTED.test(base);
22
+ }
31
23
 
32
- if (/\/\.{0,2}\//.test(iri.substring(i))) {
33
- // Base IRIs containing `//`, `/./`, or `/../` are not supported. Silently fail.
34
- return;
35
- }
24
+ _getBaseMatcher() {
25
+ if (this._baseMatcher)
26
+ return this._baseMatcher;
27
+ if (!BaseIRI.supports(this.base))
28
+ return this._baseMatcher = /.^/;
36
29
 
37
- let end = iri.length;
38
- while (stage === 1 && i < end) {
39
- // Stage 1
40
- const match = /[/?#]/.exec(iri.substring(i));
41
- if (match) {
42
- if (match[0] === '#') {
43
- // Stop at this hash.
44
- end = i + match.index;
45
- stage = 3;
46
- }
30
+ // Extract the scheme
31
+ const scheme = /^[^:]*:\/*/.exec(this.base)[0];
32
+ const regexHead = ['^', escapeRegex(scheme)];
33
+ const regexTail = [];
34
+
35
+ // Generate a regex for every path segment
36
+ const segments = [], segmenter = /[^/?#]*([/?#])/y;
37
+ let segment, query = 0, fragment = 0, last = segmenter.lastIndex = scheme.length;
38
+ while (!query && !fragment && (segment = segmenter.exec(this.base))) {
39
+ // Truncate base resolution path at fragment start
40
+ if (segment[1] === FRAGMENT)
41
+ fragment = segmenter.lastIndex - 1;
42
+ else {
43
+ // Create regex that matches the segment
44
+ regexHead.push(escapeRegex(segment[0]), '(?:');
45
+ regexTail.push(')?');
46
+
47
+ // Create dedicated query string replacement
48
+ if (segment[1] !== QUERY)
49
+ segments.push(last = segmenter.lastIndex);
47
50
  else {
48
- baseIRIRegex += escapeRegex(iri.substring(i, i + match.index + 1));
49
- baseIRIRegex += '(';
50
- segmentsCount++;
51
- if (match[0] === '/') {
52
- slashPositions.push(i + match.index);
53
- }
54
- else {
55
- this._baseSubstitutions[i + match.index] = '?';
56
- containsQuery = true;
57
- stage = 2;
58
- }
59
- i += match.index + 1;
51
+ query = last = segmenter.lastIndex;
52
+ fragment = this.base.indexOf(FRAGMENT, query);
53
+ this._pathReplacements[query] = QUERY;
60
54
  }
61
55
  }
62
- else {
63
- stage = 3;
64
- }
65
56
  }
66
- if (stage === 2) {
67
- // Stage 2
68
- const match = /#/.exec(iri.substring(i));
69
- if (match) {
70
- // Stop at this hash.
71
- end = i + match.index;
72
- }
73
- stage = 3;
74
- }
75
- if (stage === 3) {
76
- // Stage 3
77
- baseIRIRegex += escapeRegex(iri.substring(i, end));
78
- if (containsQuery) {
79
- baseIRIRegex += '(#|$)';
80
- }
81
- else {
82
- baseIRIRegex += '([?#]|$)';
83
- }
84
- i = end;
85
- }
86
-
87
- // Complete the optional groups for the segments
88
- baseIRIRegex += ')?'.repeat(segmentsCount);
89
57
 
90
- // Precalculate the rest of the substitutions
91
- if (this._baseSubstitutions[end - 1] === undefined) {
92
- this._baseSubstitutions[end - 1] = '';
93
- }
94
- for (let i = 0; i < slashPositions.length; i++) {
95
- this._baseSubstitutions[slashPositions[i]] = '../'.repeat(slashPositions.length - i - 1);
96
- }
97
- this._baseSubstitutions[slashPositions[slashPositions.length - 1]] = './';
58
+ // Precalculate parent path substitutions
59
+ for (let i = 0; i < segments.length; i++)
60
+ this._pathReplacements[segments[i]] = PARENT.repeat(segments.length - i - 1);
61
+ this._pathReplacements[segments[segments.length - 1]] = CURRENT;
98
62
 
99
- // Set the baseMatcher
100
- this._baseMatcher = new RegExp(baseIRIRegex);
101
- this._baseLength = end;
102
- this.value = iri.substring(0, end);
63
+ // Add the remainder of the base IRI (without fragment) to the regex
64
+ this._baseLength = fragment > 0 ? fragment : this.base.length;
65
+ regexHead.push(
66
+ escapeRegex(this.base.substring(last, this._baseLength)),
67
+ query ? '(?:#|$)' : '(?:[?#]|$)',
68
+ );
69
+ return this._baseMatcher = new RegExp([...regexHead, ...regexTail].join(''));
103
70
  }
104
71
 
105
72
  toRelative(iri) {
106
- if (iri.startsWith('file://')) {
107
- return iri;
108
- }
109
- const delimiterMatch = /:\/{0,2}/.exec(iri);
110
- if (!delimiterMatch || /\/\.{0,2}\//.test(iri.substring(delimiterMatch.index + delimiterMatch[0].length))) {
111
- return iri;
112
- }
113
- const match = this._baseMatcher.exec(iri);
114
- if (!match) {
73
+ // Unsupported or non-matching base IRI
74
+ const match = this._getBaseMatcher().exec(iri);
75
+ if (!match)
115
76
  return iri;
116
- }
77
+
78
+ // Exact base IRI match
117
79
  const length = match[0].length;
118
- if (length === this._baseLength && length === iri.length) {
80
+ if (length === this._baseLength && length === iri.length)
119
81
  return '';
82
+
83
+ // Parent path match
84
+ const parentPath = this._pathReplacements[length];
85
+ if (parentPath) {
86
+ const suffix = iri.substring(length);
87
+ // Don't abbreviate unsupported path
88
+ if (parentPath !== QUERY &&
89
+ /(?:^|\/)(?:\/|..?(?:[/#?]|$))/.test(suffix) && // fast test
90
+ /^(?:[^#?]*?\/)?(?:\/|\.\.?(?:[/#?]|$))/.test(suffix)) // rigorous test
91
+ return iri;
92
+ // Omit ./ with fragment or query string
93
+ if (parentPath === CURRENT && /^[^?#]/.test(suffix))
94
+ return suffix;
95
+ // Append suffix to relative parent path
96
+ return parentPath + suffix;
120
97
  }
121
- let substitution = this._baseSubstitutions[length - 1];
122
- if (substitution !== undefined) {
123
- const substr = iri.substring(length);
124
- if (substitution === './' && substr && ((!substr.startsWith('#') && !substr.startsWith('?')) || length === this._baseLength)) {
125
- substitution = '';
126
- }
127
- return substitution + substr;
128
- }
129
- // Matched the [?#], so make sure to add the delimiter
98
+
99
+ // Fragment or query string, so include delimiter
130
100
  return iri.substring(length - 1);
131
101
  }
132
102
  }
133
-
134
- function escapeRegex(regex) {
135
- return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
136
- }
package/src/N3Writer.js CHANGED
@@ -3,6 +3,7 @@ import namespaces from './IRIs';
3
3
  import { default as N3DataFactory, Term } from './N3DataFactory';
4
4
  import { isDefaultGraph } from './N3Util';
5
5
  import BaseIRI from './BaseIRI';
6
+ import { escapeRegex } from './Util';
6
7
 
7
8
  const DEFAULTGRAPH = N3DataFactory.defaultGraph();
8
9
 
@@ -394,7 +395,3 @@ function characterReplacer(character) {
394
395
  }
395
396
  return result;
396
397
  }
397
-
398
- function escapeRegex(regex) {
399
- return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
400
- }
package/src/Util.js ADDED
@@ -0,0 +1,3 @@
1
+ export function escapeRegex(regex) {
2
+ return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
3
+ }