]>
Lady’s Gitweb - Pisces/blob - iri.js
f1abe7806fabb64324177a7bf8ed28aff240d3bd
2 // ====================================================================
4 // Copyright © 2020, 2022 Lady [@ Lady’s Computer].
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
10 const sub
·delims
= String
.raw
`[!\$&'()*+,;=]`;
11 const gen
·delims
= String
.raw
`[:/?#\[\]@]`;
12 //deno-lint-ignore no-unused-vars
13 const reserved
= String
.raw
`${gen·delims}|${sub·delims}`;
14 const unreserved
= String
.raw
`[A-Za-z0-9\-\._~]`;
15 const pct
·encoded
= String
.raw
`%[0-9A-Fa-f][0-9A-Fa-f]`;
16 const dec
·octet
= String
.raw
17 `[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]`;
18 const IPv4address
= String
.raw
19 `(?:${dec·octet})\.(?:${dec·octet})\.(?:${dec·octet})\.(?:${dec·octet})`;
20 const h16
= String
.raw
`[0-9A-Fa-f]{1,4}`;
21 const ls32
= String
.raw
`(?:${h16}):(?:${h16})|${IPv4address}`;
22 const IPv6address
= String
.raw
23 `(?:(?:${h16}):){6}(?:${ls32})|::(?:(?:${h16}):){5}(?:${ls32})|(?:${h16})?::(?:(?:${h16}):){4}(?:${ls32})|(?:(?:(?:${h16}):){0,1}(?:${h16}))?::(?:(?:${h16}):){3}(?:${ls32})|(?:(?:(?:${h16}):){0,2}(?:${h16}))?::(?:(?:${h16}):){2}(?:${ls32})|(?:(?:(?:${h16}):){0,3}(?:${h16}))?::(?:${h16}):(?:${ls32})|(?:(?:(?:${h16}):){0,4}(?:${h16}))?::(?:${ls32})|(?:(?:(?:${h16}):){0,5}(?:${h16}))?::(?:${h16})|(?:(?:(?:${h16}):){0,6}(?:${h16}))?::`;
24 const IPvFuture
= String
.raw
25 `v[0-9A-Fa-f]{1,}\.(?:${unreserved}|${sub·delims}|:)`;
26 const IP
·literal
= String
.raw
`\[(?:${IPv6address}|${IPvFuture})\]`;
27 const port
= String
.raw
`[0-9]*`;
28 const scheme
= String
.raw
`[A-Za-z][A-Za-z0-9+\-\.]*`;
29 const pchar
= String
.raw
30 `${unreserved}|${pct·encoded}|${sub·delims}|[:@]`;
31 const fragment
= String
.raw
`(?:${pchar}|[/?])*`;
32 const query
= String
.raw
`(?:${pchar}|[/?])*`;
33 const segment
·nz
·nc
= String
.raw
34 `(?:${unreserved}|${pct·encoded}|${sub·delims}|@)+`;
35 const segment
·nz
= String
.raw
`(?:${pchar})+`;
36 const segment
= String
.raw
`(?:${pchar})*`;
37 const path
·empty
= String
.raw
``;
38 const path
·rootless
= String
.raw
39 `(?:${segment·nz})(?:/(?:${segment}))*`;
40 const path
·noscheme
= String
.raw
41 `(?:${segment·nz·nc})(?:/(?:${segment}))*`;
42 const path
·absolute
= String
.raw
43 `/(?:(?:${segment·nz})(?:/(?:${segment}))*)?`;
44 const path
·abempty
= String
.raw
`(?:/(?:${segment}))*`;
45 //deno-lint-ignore no-unused-vars
46 const path
= String
.raw
47 `${path·abempty}|${path·absolute}|${path·noscheme}|${path·rootless}|${path·empty}`;
48 const reg
·name
= String
.raw
49 `(?:${unreserved}|${pct·encoded}|${sub·delims})*`;
50 const host
= String
.raw
`${IP·literal}|${IPv4address}|${reg·name}`;
51 const userinfo
= String
.raw
52 `(?:${unreserved}|${pct·encoded}|${sub·delims}|:)*`;
53 const authority
= String
.raw
54 `(?:(?:${userinfo})@)?(?:${host})(?::(?:${port}))?`;
55 const relative
·part
= String
.raw
56 `//(?:${authority})(?:${path·abempty})|(?:${path·absolute})|(?:${path·noscheme})|(?:${path·empty})`;
57 const relative
·ref
= String
.raw
58 `(?:${relative·part})(?:\?(?:${query}))?(?:#(?:${fragment}))?`;
59 const hier
·part
= String
.raw
60 `//(?:${authority})(?:${path·abempty})|(?:${path·absolute})|(?:${path·rootless})|(?:${path·empty})`;
61 const absolute
·URI
= String
.raw
62 `(?:${scheme}):(?:${hier·part})(?:\?(?:${query}))?`;
63 const URI
= String
.raw
64 `(?:${scheme}):(?:${hier·part})(?:\?(?:${query}))?(?:#(?:${fragment}))?`;
65 const URI
·reference
= String
.raw
`(?:${URI})|(?:${relative·ref})`;
67 const iprivate
= String
.raw
68 `[\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]`;
69 const ucschar
= String
.raw
70 `[\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E0000}-\u{EFFFD}]`;
71 const iunreserved
= String
.raw
`[A-Za-z0-9\-\._~]|${ucschar}`;
72 const ipchar
= String
.raw
73 `${iunreserved}|${pct·encoded}|${sub·delims}|[:@]`;
74 const ifragment
= String
.raw
`(?:${ipchar}|[/?])*`;
75 const iquery
= String
.raw
`(?:${ipchar}|${iprivate}|[/?])*`;
76 const isegment
·nz
·nc
= String
.raw
77 `(?:${iunreserved}|${pct·encoded}|${sub·delims}|@)+`;
78 const isegment
·nz
= String
.raw
`(?:${ipchar})+`;
79 const isegment
= String
.raw
`(?:${ipchar})*`;
80 const ipath
·empty
= String
.raw
``;
81 const ipath
·rootless
= String
.raw
82 `(?:${isegment·nz})(?:/(?:${isegment}))*`;
83 const ipath
·noscheme
= String
.raw
84 `(?:${isegment·nz·nc})(?:/(?:${isegment}))*`;
85 const ipath
·absolute
= String
.raw
86 `/(?:(?:${isegment·nz})(?:/(?:${isegment}))*)?`;
87 const ipath
·abempty
= String
.raw
`(?:/(?:${isegment}))*`;
88 //deno-lint-ignore no-unused-vars
89 const ipath
= String
.raw
90 `${ipath·abempty}|${ipath·absolute}|${ipath·noscheme}|${ipath·rootless}|${ipath·empty}`;
91 const ireg
·name
= String
.raw
92 `(?:${iunreserved}|${pct·encoded}|${sub·delims})*`;
93 const ihost
= String
.raw
`${IP·literal}|${IPv4address}|${ireg·name}`;
94 const iuserinfo
= String
.raw
95 `(?:${iunreserved}|${pct·encoded}|${sub·delims}|:)*`;
96 const iauthority
= String
.raw
97 `(?:(?:${iuserinfo})@)?(?:${ihost})(?::(?:${port}))?`;
98 const irelative
·part
= String
.raw
99 `//(?:${iauthority})(?:${ipath·abempty})|(?:${ipath·absolute})|(?:${ipath·noscheme})|(?:${ipath·empty})`;
100 const irelative
·ref
= String
.raw
101 `(?:${irelative·part})(?:\?(?:${iquery}))?(?:#(?:${ifragment}))?`;
102 const ihier
·part
= String
.raw
103 `//(?:${iauthority})(?:${ipath·abempty})|(?:${ipath·absolute})|(?:${ipath·rootless})|(?:${ipath·empty})`;
104 const absolute
·IRI
= String
.raw
105 `(?:${scheme}):(?:${ihier·part})(?:\?(?:${iquery}))?`;
106 const IRI
= String
.raw
107 `(?:${scheme}):(?:${ihier·part})(?:\?(?:${iquery}))?(?:#(?:${ifragment}))?`;
108 const IRI
·reference
= String
.raw
`(?:${IRI})|(?:${irelative·ref})`;
111 isAbsoluteURI
, // U·R·I with no fragment
114 isAbsoluteIRI
, // I·R·I with no fragment
117 } = Object
.fromEntries(
119 isAbsoluteIRI
: absolute
·IRI
,
120 isAbsoluteURI
: absolute
·URI
,
122 isIRIReference
: IRI
·reference
,
124 isURIReference
: URI
·reference
,
125 }).map(([key
, value
]) => {
126 const regExp
= new RegExp(`^(?:${value})$`, "u");
129 Object
.defineProperties(
130 ($) => typeof $ == "string" && regExp
.test($),
132 name
: { value
: key
},
136 get: () => regExp
[Symbol
.match
].bind(regExp
),
146 * Recomposes an I·R·I reference from its component parts.
148 * See §5.3 of R·F·C 3986.
150 export const composeReference
= ($) => {
152 const { scheme
, authority
, path
, query
, fragment
} = $;
153 if (scheme
!= null) {
154 result
.push(scheme
, ":");
158 if (authority
!= null) {
159 result
.push("//", authority
);
163 result
.push(path
?? "");
165 result
.push("?", query
);
169 if (fragment
!= null) {
170 result
.push("#", fragment
);
174 return result
.join("");
178 * Converts an I·R·I to the corresponding U·R·I by percent‐encoding
179 * unsupported characters.
181 * This does not punycode the authority.
183 export const iri2uri
= ($) =>
185 const encoder
= new TextEncoder();
186 for (const character
of $) {
187 if (new RegExp(`${ucschar}|${iprivate}`, "u").test(character
)) {
188 for (const byte of encoder
.encode(character
)) {
189 yield `%${byte.toString(0x10).toUpperCase()}`;
198 * Merges a reference path with a base path.
200 * See §5.2.3 of R·F·C 3986.
202 export const mergePaths
= (base
, reference
) => {
203 const baseStr
= `${base}`;
205 baseStr.substring(0, baseStr.lastIndexOf("/") + 1)
210 * Returns the `scheme`, `authority`, `path`, `query`, and `fragment`
211 * of the provided I·R·I reference.
213 export const parseReference
= ($) => {
214 const regExp
= new RegExp(
216 `^(?:(?<absolute·scheme>${scheme}):(?://(?<absolute·authority>${iauthority})(?<absolute·patha>${ipath·abempty})|(?<absolute·pathb>(?:${ipath·absolute})|(?:${ipath·rootless})|(?:${ipath·empty})))(?:\?(?<absolute·query>${iquery}))?(?:#(?<absolute·fragment>${ifragment}))?|(?://(?<relative·authority>${iauthority})(?<relative·patha>${ipath·abempty})|(?<relative·pathb>(?:${ipath·absolute})|(?:${ipath·noscheme})|(?:${ipath·empty})))(?:\?(?<relative·query>${iquery}))?(?:#(?<relative·fragment>${ifragment}))?)$`,
231 } = regExp
.exec($)?.groups
?? {};
233 scheme
: absolute
·scheme
,
234 authority
: absolute
·authority
?? relative
·authority
,
235 path
: absolute
·patha
?? absolute
·pathb
?? relative
·patha
??
237 query
: absolute
·query
?? relative
·query
,
238 fragment
: absolute
·fragment
?? relative
·fragment
,
243 * Removes all dot segments ("." or "..") from the provided I·R·I.
245 * See §5.2.4 of R·F·C 3986.
247 export const removeDotSegments
= ($) => {
248 const input
= `${$}`;
250 const { length
} = input
;
252 while (index
< length
) {
253 if (input
.startsWith("../", index
)) {
254 // The input starts with a double leader; drop it. This can only
255 // occur at the beginning of the input.
257 } else if (input
.startsWith("./", index
)) {
258 // The input starts with a single leader; drop it. This can only
259 // occur at the beginning of the input.
261 } else if (input
.startsWith("/./", index
)) {
262 // The input starts with a slash, single leader, and another
263 // slash. Ignore it, and move the input to just before the second
266 } else if (input
.startsWith("/.", index
) && index
+ 2 == length
) {
267 // The input starts with a slash and single leader, and this
268 // exhausts the string. Push an empty segment and move the index
269 // to the end of the string.
272 } else if (input
.startsWith("/../", index
)) {
273 // The input starts with a slash, double leader, and another
274 // slash. Drop a segment from the output, and move the input to
275 // just before the second slash.
277 output
.splice(-1, 1);
278 } else if (input
.startsWith("/..", index
) && index
+ 3 == length
) {
279 // The input starts with a slash and single leader, and this
280 // exhausts the string. Drop a segment from the output, push an
281 // empty segment, and move the index to the end of the string.
282 output
.splice(-1, 1, "/");
285 input
.startsWith(".", index
) && index
+ 1 == length
||
286 input
.startsWith("..", index
) && index
+ 2 == length
288 // The input starts with a single or double leader, and this
289 // exhausts the string. Do nothing (this can only occur at the
290 // beginning of input) and move the index to the end of the
294 // The input does not start with a leader. Advance the index to
295 // the position before the next slash and push the segment
296 // between the old and new positions.
297 const nextIndex
= input
.indexOf("/", index
+ 1);
298 if (nextIndex
== -1) {
299 // No slash remains; set index to the end of the string.
300 output
.push(input
.substring(index
));
303 // There are further path segments.
304 output
.push(input
.substring(index
, nextIndex
));
309 return output
.join("");
313 * Resolves the provided reference relative to the provided base I·R·I.
315 * See §5.2 of R·F·C 3986.
317 export const resolveReference
= (R
, Base
= location
?? "") => {
320 authority
: Base
·authority
,
323 } = parseReference(Base
);
324 if (Base
·scheme
== null) {
326 `Piscēs: Base I·R·I did not have a scheme: ${Base}.`,
331 authority
: R
·authority
,
334 fragment
: R
·fragment
,
335 } = parseReference(R
);
336 return composeReference(
340 authority
: R
·authority
,
341 path
: removeDotSegments(R
·path
),
343 fragment
: R
·fragment
,
345 : R
·authority
!= null
348 authority
: R
·authority
,
349 path
: removeDotSegments(R
·path
),
351 fragment
: R
·fragment
,
356 authority
: Base
·authority
,
358 query
: R
·query
?? Base
·query
,
359 fragment
: R
·fragment
,
363 authority
: Base
·authority
,
364 path
: R
·path
[0] == "/"
365 ? removeDotSegments(R
·path
)
366 : removeDotSegments(mergePaths(Base
·path
|| "/", R
·path
)),
368 fragment
: R
·fragment
,
This page took 0.090342 seconds and 3 git commands to generate.