-
Notifications
You must be signed in to change notification settings - Fork 141
Expand file tree
/
Copy pathBaseIRI.js
More file actions
111 lines (97 loc) · 3.66 KB
/
BaseIRI.js
File metadata and controls
111 lines (97 loc) · 3.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { escapeRegex } from './Util';
// Do not handle base IRIs without scheme, and currently unsupported cases:
// - file: IRIs (which could also use backslashes)
// - IRIs containing /. or /.. or //
const BASE_UNSUPPORTED = /^:?[^:?#]*(?:[?#]|$)|^file:|^[^:]*:\/*[^?#]+?\/(?:\.\.?(?:\/|$)|\/)/i;
const SUFFIX_SUPPORTED = /^(?:(?:[^/?#]{3,}|\.?[^/?#.]\.?)(?:\/[^/?#]{3,}|\.?[^/?#.]\.?)*\/?)?(?:[?#]|$)/;
const CURRENT = './';
const ORIGIN = '/';
const PARENT = '../';
const QUERY = '?';
const FRAGMENT = '#';
export default class BaseIRI {
constructor(base) {
this.base = base;
this._baseLength = 0;
this._baseMatcher = null;
this._pathReplacements = new Array(base.length + 1);
}
static supports(base) {
return !BASE_UNSUPPORTED.test(base);
}
_getBaseMatcher() {
if (this._baseMatcher)
return this._baseMatcher;
if (!BaseIRI.supports(this.base))
return this._baseMatcher = /.^/;
// Extract the scheme
const scheme = /^[^:]*:\/*/.exec(this.base)[0];
const regexHead = ['^', escapeRegex(scheme)];
const regexTail = [];
// Generate a regex for every path segment
const segments = [], segmenter = /[^/?#]*([/?#])/y;
let segment, query = 0, fragment = 0, last = segmenter.lastIndex = scheme.length;
while (!query && !fragment && (segment = segmenter.exec(this.base))) {
// Truncate base resolution path at fragment start
if (segment[1] === FRAGMENT)
fragment = segmenter.lastIndex - 1;
else {
// Create regex that matches the segment
regexHead.push(escapeRegex(segment[0]), '(?:');
regexTail.push(')?');
// Create dedicated query string replacement
if (segment[1] !== QUERY)
segments.push(last = segmenter.lastIndex);
else {
query = last = segmenter.lastIndex;
fragment = this.base.indexOf(FRAGMENT, query);
this._pathReplacements[query] = QUERY;
}
}
}
// Precalculate parent path substitutions
for (let i = 0; i < segments.length; i++) {
const parentLength = 3 * (segments.length - i - 1);
const baseLength = segments[i] - segments[0];
if (parentLength < baseLength) {
this._pathReplacements[segments[i]] = PARENT.repeat(segments.length - i - 1);
}
else {
this._pathReplacements[segments[i]] = ORIGIN + this.base.slice(segments[0], segments[i]);
}
}
this._pathReplacements[segments[segments.length - 1]] = CURRENT;
// Add the remainder of the base IRI (without fragment) to the regex
this._baseLength = fragment > 0 ? fragment : this.base.length;
regexHead.push(
escapeRegex(this.base.substring(last, this._baseLength)),
query ? '(?:#|$)' : '(?:[?#]|$)',
);
return this._baseMatcher = new RegExp([...regexHead, ...regexTail].join(''));
}
toRelative(iri) {
// Unsupported or non-matching base IRI
const match = this._getBaseMatcher().exec(iri);
if (!match)
return iri;
// Exact base IRI match
const length = match[0].length;
if (length === this._baseLength && length === iri.length)
return '';
// Parent path match
const parentPath = this._pathReplacements[length];
if (parentPath) {
const suffix = iri.substring(length);
// Don't abbreviate unsupported path
if (parentPath !== QUERY && !SUFFIX_SUPPORTED.test(suffix))
return iri;
// Omit ./ with fragment or query string
if (parentPath === CURRENT && /^[^?#]/.test(suffix))
return suffix;
// Append suffix to relative parent path
return parentPath + suffix;
}
// Fragment or query string, so include delimiter
return iri.substring(length - 1);
}
}