n3 1.25.0 → 1.25.2

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,90 @@ 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 BASE_UNSUPPORTED = /^:?[^:?#]*(?:[?#]|$)|^file:|^[^:]*:\/*[^?#]+?\/(?:\.\.?(?:\/|$)|\/)/i;
12
+ const SUFFIX_SUPPORTED = /^(?:(?:[^/?#]{3,}|\.?[^/?#.]\.?)(?:\/[^/?#]{3,}|\.?[^/?#.]\.?)*\/?)?(?:[?#]|$)/;
13
+ const CURRENT = './';
14
+ const PARENT = '../';
15
+ const QUERY = '?';
16
+ const FRAGMENT = '#';
7
17
  class BaseIRI {
8
- constructor(iri) {
9
- if (iri.startsWith('file://')) {
10
- // Base IRIs starting with file:// are not supported. Silently fail.
11
- return;
12
- }
18
+ constructor(base) {
19
+ this.base = base;
20
+ this._baseLength = 0;
21
+ this._baseMatcher = null;
22
+ this._pathReplacements = new Array(base.length + 1);
23
+ }
24
+ static supports(base) {
25
+ return !BASE_UNSUPPORTED.test(base);
26
+ }
27
+ _getBaseMatcher() {
28
+ if (this._baseMatcher) return this._baseMatcher;
29
+ if (!BaseIRI.supports(this.base)) return this._baseMatcher = /.^/;
13
30
 
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;
31
+ // Extract the scheme
32
+ const scheme = /^[^:]*:\/*/.exec(this.base)[0];
33
+ const regexHead = ['^', (0, _Util.escapeRegex)(scheme)];
34
+ const regexTail = [];
35
+
36
+ // Generate a regex for every path segment
37
+ const segments = [],
38
+ segmenter = /[^/?#]*([/?#])/y;
39
+ let segment,
40
+ query = 0,
41
+ fragment = 0,
42
+ last = segmenter.lastIndex = scheme.length;
43
+ while (!query && !fragment && (segment = segmenter.exec(this.base))) {
44
+ // Truncate base resolution path at fragment start
45
+ if (segment[1] === FRAGMENT) fragment = segmenter.lastIndex - 1;else {
46
+ // Create regex that matches the segment
47
+ regexHead.push((0, _Util.escapeRegex)(segment[0]), '(?:');
48
+ regexTail.push(')?');
49
+
50
+ // Create dedicated query string replacement
51
+ if (segment[1] !== QUERY) segments.push(last = segmenter.lastIndex);else {
52
+ query = last = segmenter.lastIndex;
53
+ fragment = this.base.indexOf(FRAGMENT, query);
54
+ this._pathReplacements[query] = QUERY;
61
55
  }
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
56
  }
83
- i = end;
84
57
  }
85
58
 
86
- // Complete the optional groups for the segments
87
- baseIRIRegex += ')?'.repeat(segmentsCount);
59
+ // Precalculate parent path substitutions
60
+ for (let i = 0; i < segments.length; i++) this._pathReplacements[segments[i]] = PARENT.repeat(segments.length - i - 1);
61
+ this._pathReplacements[segments[segments.length - 1]] = CURRENT;
88
62
 
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);
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((0, _Util.escapeRegex)(this.base.substring(last, this._baseLength)), query ? '(?:#|$)' : '(?:[?#]|$)');
66
+ return this._baseMatcher = new RegExp([...regexHead, ...regexTail].join(''));
102
67
  }
103
68
  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
- }
69
+ // Unsupported or non-matching base IRI
70
+ const match = this._getBaseMatcher().exec(iri);
71
+ if (!match) return iri;
72
+
73
+ // Exact base IRI match
115
74
  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;
75
+ if (length === this._baseLength && length === iri.length) return '';
76
+
77
+ // Parent path match
78
+ const parentPath = this._pathReplacements[length];
79
+ if (parentPath) {
80
+ const suffix = iri.substring(length);
81
+ // Don't abbreviate unsupported path
82
+ if (parentPath !== QUERY && !SUFFIX_SUPPORTED.test(suffix)) return iri;
83
+ // Omit ./ with fragment or query string
84
+ if (parentPath === CURRENT && /^[^?#]/.test(suffix)) return suffix;
85
+ // Append suffix to relative parent path
86
+ return parentPath + suffix;
126
87
  }
127
- // Matched the [?#], so make sure to add the delimiter
88
+
89
+ // Fragment or query string, so include delimiter
128
90
  return iri.substring(length - 1);
129
91
  }
130
92
  }
131
- exports.default = BaseIRI;
132
- function escapeRegex(regex) {
133
- return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
134
- }
93
+ 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.2",
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,101 @@
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 BASE_UNSUPPORTED = /^:?[^:?#]*(?:[?#]|$)|^file:|^[^:]*:\/*[^?#]+?\/(?:\.\.?(?:\/|$)|\/)/i;
7
+ const SUFFIX_SUPPORTED = /^(?:(?:[^/?#]{3,}|\.?[^/?#.]\.?)(?:\/[^/?#]{3,}|\.?[^/?#.]\.?)*\/?)?(?:[?#]|$)/;
8
+ const CURRENT = './';
9
+ const PARENT = '../';
10
+ const QUERY = '?';
11
+ const FRAGMENT = '#';
12
+
1
13
  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
- }
14
+ constructor(base) {
15
+ this.base = base;
16
+ this._baseLength = 0;
17
+ this._baseMatcher = null;
18
+ this._pathReplacements = new Array(base.length + 1);
19
+ }
7
20
 
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
- }
21
+ static supports(base) {
22
+ return !BASE_UNSUPPORTED.test(base);
23
+ }
31
24
 
32
- if (/\/\.{0,2}\//.test(iri.substring(i))) {
33
- // Base IRIs containing `//`, `/./`, or `/../` are not supported. Silently fail.
34
- return;
35
- }
25
+ _getBaseMatcher() {
26
+ if (this._baseMatcher)
27
+ return this._baseMatcher;
28
+ if (!BaseIRI.supports(this.base))
29
+ return this._baseMatcher = /.^/;
36
30
 
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
- }
31
+ // Extract the scheme
32
+ const scheme = /^[^:]*:\/*/.exec(this.base)[0];
33
+ const regexHead = ['^', escapeRegex(scheme)];
34
+ const regexTail = [];
35
+
36
+ // Generate a regex for every path segment
37
+ const segments = [], segmenter = /[^/?#]*([/?#])/y;
38
+ let segment, query = 0, fragment = 0, last = segmenter.lastIndex = scheme.length;
39
+ while (!query && !fragment && (segment = segmenter.exec(this.base))) {
40
+ // Truncate base resolution path at fragment start
41
+ if (segment[1] === FRAGMENT)
42
+ fragment = segmenter.lastIndex - 1;
43
+ else {
44
+ // Create regex that matches the segment
45
+ regexHead.push(escapeRegex(segment[0]), '(?:');
46
+ regexTail.push(')?');
47
+
48
+ // Create dedicated query string replacement
49
+ if (segment[1] !== QUERY)
50
+ segments.push(last = segmenter.lastIndex);
47
51
  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;
52
+ query = last = segmenter.lastIndex;
53
+ fragment = this.base.indexOf(FRAGMENT, query);
54
+ this._pathReplacements[query] = QUERY;
60
55
  }
61
56
  }
62
- else {
63
- stage = 3;
64
- }
65
57
  }
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
58
 
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]] = './';
59
+ // Precalculate parent path substitutions
60
+ for (let i = 0; i < segments.length; i++)
61
+ this._pathReplacements[segments[i]] = PARENT.repeat(segments.length - i - 1);
62
+ this._pathReplacements[segments[segments.length - 1]] = CURRENT;
98
63
 
99
- // Set the baseMatcher
100
- this._baseMatcher = new RegExp(baseIRIRegex);
101
- this._baseLength = end;
102
- this.value = iri.substring(0, end);
64
+ // Add the remainder of the base IRI (without fragment) to the regex
65
+ this._baseLength = fragment > 0 ? fragment : this.base.length;
66
+ regexHead.push(
67
+ escapeRegex(this.base.substring(last, this._baseLength)),
68
+ query ? '(?:#|$)' : '(?:[?#]|$)',
69
+ );
70
+ return this._baseMatcher = new RegExp([...regexHead, ...regexTail].join(''));
103
71
  }
104
72
 
105
73
  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) {
74
+ // Unsupported or non-matching base IRI
75
+ const match = this._getBaseMatcher().exec(iri);
76
+ if (!match)
115
77
  return iri;
116
- }
78
+
79
+ // Exact base IRI match
117
80
  const length = match[0].length;
118
- if (length === this._baseLength && length === iri.length) {
81
+ if (length === this._baseLength && length === iri.length)
119
82
  return '';
83
+
84
+ // Parent path match
85
+ const parentPath = this._pathReplacements[length];
86
+ if (parentPath) {
87
+ const suffix = iri.substring(length);
88
+ // Don't abbreviate unsupported path
89
+ if (parentPath !== QUERY && !SUFFIX_SUPPORTED.test(suffix))
90
+ return iri;
91
+ // Omit ./ with fragment or query string
92
+ if (parentPath === CURRENT && /^[^?#]/.test(suffix))
93
+ return suffix;
94
+ // Append suffix to relative parent path
95
+ return parentPath + suffix;
120
96
  }
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
97
+
98
+ // Fragment or query string, so include delimiter
130
99
  return iri.substring(length - 1);
131
100
  }
132
101
  }
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
+ }