-const leiri·iprivate = String.raw
- `[\u{E000}-\u{F8FF}\u{E0000}-\u{E0FFF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]`;
-const leiri·ucschar = String.raw
- `[ <>"{}|\\^${"`"}\u{0}-\u{1F}\u{7F}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}]`;
-const leiri·iunreserved = String.raw
- `[A-Za-z0-9\-\._~]|${leiri·ucschar}`;
-const leiri·ipchar = String.raw
- `${leiri·iunreserved}|${pct·encoded}|${sub·delims}|[:@]`;
-const leiri·ifragment = String.raw`(?:${leiri·ipchar}|[/?])*`;
-const leiri·iquery = String.raw
- `(?:${leiri·ipchar}|${leiri·iprivate}|[/?])*`;
-const leiri·isegment·nz·nc = String.raw
- `(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims}|@)+`;
-const leiri·isegment·nz = String.raw`(?:${leiri·ipchar})+`;
-const leiri·isegment = String.raw`(?:${leiri·ipchar})*`;
-const leiri·ipath·empty = String.raw``;
-const leiri·ipath·rootless = String.raw
- `(?:${leiri·isegment·nz})(?:/(?:${leiri·isegment}))*`;
-const leiri·ipath·noscheme = String.raw
- `(?:${leiri·isegment·nz·nc})(?:/(?:${leiri·isegment}))*`;
-const leiri·ipath·absolute = String.raw
- `/(?:(?:${leiri·isegment·nz})(?:/(?:${leiri·isegment}))*)?`;
-const leiri·ipath·abempty = String.raw`(?:/(?:${leiri·isegment}))*`;
-const leiri·ipath = String.raw
- `${leiri·ipath·abempty}|${leiri·ipath·absolute}|${leiri·ipath·noscheme}|${leiri·ipath·rootless}|${leiri·ipath·empty}`;
-const leiri·ireg·name = String.raw
- `(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims})*`;
-const leiri·ihost = String.raw
- `${IP·literal}|${IPv4address}|${leiri·ireg·name}`;
-const leiri·iuserinfo = String.raw
- `(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims}|:)*`;
-const leiri·iauthority = String.raw
- `(?:(?:${leiri·iuserinfo})@)?(?:${leiri·ihost})(?::(?:${port}))?`;
-const leiri·irelative·part = String.raw
- `//(?:${leiri·iauthority})(?:${leiri·ipath·abempty})|(?:${leiri·ipath·absolute})|(?:${leiri·ipath·noscheme})|(?:${leiri·ipath·empty})`;
-const leiri·irelative·ref = String.raw
- `(?:${leiri·irelative·part})(?:\?(?:${leiri·iquery}))?(?:#(?:${leiri·ifragment}))?`;
-const leiri·ihier·part = String.raw
- `//(?:${leiri·iauthority})(?:${leiri·ipath·abempty})|(?:${leiri·ipath·absolute})|(?:${leiri·ipath·rootless})|(?:${leiri·ipath·empty})`;
-const absolute·LEIRI = String.raw
- `(?:${scheme}):(?:${leiri·ihier·part})(?:\?(?:${leiri·iquery}))?`;
-const LEIRI = String.raw
- `(?:${scheme}):(?:${leiri·ihier·part})(?:\?(?:${leiri·iquery}))?(?:#(?:${leiri·ifragment}))?`;
-const LEIRI·reference = String.raw
- `(?:${LEIRI})|(?:${leiri·irelative·ref})`;
+const leiri·iprivate =
+ rawString`[\u{E000}-\u{F8FF}\u{E0000}-\u{E0FFF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]`;
+const leiri·ucschar =
+ rawString`[ <>"{}|\\^${"\x60"}\u{0}-\u{1F}\u{7F}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}]`;
+const leiri·iunreserved =
+ rawString`[A-Za-z0-9\-\._~]|${leiri·ucschar}`;
+const leiri·ipchar =
+ rawString`${leiri·iunreserved}|${pct·encoded}|${sub·delims}|[:@]`;
+const leiri·ifragment = rawString`(?:${leiri·ipchar}|[/?])*`;
+const leiri·iquery =
+ rawString`(?:${leiri·ipchar}|${leiri·iprivate}|[/?])*`;
+const leiri·isegment·nz·nc =
+ rawString`(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims}|@)+`;
+const leiri·isegment·nz = rawString`(?:${leiri·ipchar})+`;
+const leiri·isegment = rawString`(?:${leiri·ipchar})*`;
+const leiri·ipath·empty = rawString``;
+const leiri·ipath·rootless =
+ rawString`(?:${leiri·isegment·nz})(?:/(?:${leiri·isegment}))*`;
+const leiri·ipath·noscheme =
+ rawString`(?:${leiri·isegment·nz·nc})(?:/(?:${leiri·isegment}))*`;
+const leiri·ipath·absolute =
+ rawString`/(?:(?:${leiri·isegment·nz})(?:/(?:${leiri·isegment}))*)?`;
+const leiri·ipath·abempty = rawString`(?:/(?:${leiri·isegment}))*`;
+const leiri·ipath =
+ rawString`${leiri·ipath·abempty}|${leiri·ipath·absolute}|${leiri·ipath·noscheme}|${leiri·ipath·rootless}|${leiri·ipath·empty}`;
+const leiri·ireg·name =
+ rawString`(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims})*`;
+const leiri·ihost =
+ rawString`${IP·literal}|${IPv4address}|${leiri·ireg·name}`;
+const leiri·iuserinfo =
+ rawString`(?:${leiri·iunreserved}|${pct·encoded}|${sub·delims}|:)*`;
+const leiri·iauthority =
+ rawString`(?:(?:${leiri·iuserinfo})@)?(?:${leiri·ihost})(?::(?:${port}))?`;
+const leiri·irelative·part =
+ rawString`//(?:${leiri·iauthority})(?:${leiri·ipath·abempty})|(?:${leiri·ipath·absolute})|(?:${leiri·ipath·noscheme})|(?:${leiri·ipath·empty})`;
+const leiri·irelative·ref =
+ rawString`(?:${leiri·irelative·part})(?:\?(?:${leiri·iquery}))?(?:#(?:${leiri·ifragment}))?`;
+const leiri·ihier·part =
+ rawString`//(?:${leiri·iauthority})(?:${leiri·ipath·abempty})|(?:${leiri·ipath·absolute})|(?:${leiri·ipath·rootless})|(?:${leiri·ipath·empty})`;
+const absolute·LEIRI =
+ rawString`(?:${scheme}):(?:${leiri·ihier·part})(?:\?(?:${leiri·iquery}))?`;
+const LEIRI =
+ rawString`(?:${scheme}):(?:${leiri·ihier·part})(?:\?(?:${leiri·iquery}))?(?:#(?:${leiri·ifragment}))?`;
+const LEIRI·reference =
+ rawString`(?:${LEIRI})|(?:${leiri·irelative·ref})`;
+
+export const {
+ /**
+ * Recomposes an (L·E·)I·R·I reference from its component parts.
+ *
+ * See §5.3 of R·F·C 3986.
+ */
+ composeReference,
+
+ /**
+ * Converts an L·E·I·R·I to the corresponding I·R·I by
+ * percent‐encoding unsupported characters.
+ *
+ * This function is somewhat complex because the I·R·I syntax allows
+ * private·use characters *only* in the query.
+ */
+ escapeForIRI,
+
+ /**
+ * Converts an (L·E·)I·R·I to the corresponding U·R·I by
+ * percent‐encoding unsupported characters.
+ *
+ * This does not punycode the authority.
+ */
+ escapeForURI,
+
+ /**
+ * Removes all dot segments ("." or "..") from the provided
+ * (L·E·)I·R·I.
+ *
+ * See §5.2.4 of R·F·C 3986.
+ */
+ removeDotSegments,
+} = (() => {
+ const TE = TextEncoder;
+ const { iterator: iteratorSymbol } = Symbol;
+ const { toString: numberToString } = Number.prototype;
+ const { encode: teEncode } = TE.prototype;
+
+ const { [iteratorSymbol]: arrayIterator } = Array.prototype;
+ const {
+ next: arrayIteratorNext,
+ } = Object.getPrototypeOf([][iteratorSymbol]());
+ const {
+ next: generatorIteratorNext,
+ } = Object.getPrototypeOf(function* () {}.prototype);
+ const { [iteratorSymbol]: stringIterator } = String.prototype;
+ const {
+ next: stringIteratorNext,
+ } = Object.getPrototypeOf(""[iteratorSymbol]());
+
+ const iriCharacterIterablePrototype = {
+ [iteratorSymbol]() {
+ return {
+ next: bind(
+ stringIteratorNext,
+ call(stringIterator, this.source, []),
+ [],
+ ),
+ };
+ },
+ };
+ const iriGeneratorIterablePrototype = {
+ [iteratorSymbol]() {
+ return {
+ next: bind(generatorIteratorNext, this.generator(), []),
+ };
+ },
+ };
+ const iriSegmentIterablePrototype = {
+ [iteratorSymbol]() {
+ return {
+ next: bind(
+ arrayIteratorNext,
+ call(arrayIterator, this.segments, []),
+ [],
+ ),
+ };
+ },
+ };
+
+ return {
+ composeReference: ($) =>
+ join(
+ objectCreate(
+ iriGeneratorIterablePrototype,
+ {
+ generator: {
+ value: function* () {
+ const { scheme, authority, path, query, fragment } = $;
+ if (scheme != null) {
+ // A scheme is present.
+ yield scheme;
+ yield ":";
+ } else {
+ // No scheme is present.
+ /* do nothing */
+ }
+ if (authority != null) {
+ // An authority is present.
+ yield "//";
+ yield authority;
+ } else {
+ // No authority is present.
+ /* do nothing */
+ }
+ yield path ?? "";
+ if (query != null) {
+ // A query is present.
+ yield "?";
+ yield query;
+ } else {
+ // No query is present.
+ /* do nothing */
+ }
+ if (fragment != null) {
+ // A fragment is present.
+ yield "#";
+ yield fragment;
+ } else {
+ // No fragment is present.
+ /* do nothing */
+ }
+ },
+ },
+ },
+ ),
+ "",
+ ),
+ escapeForIRI: ($) => {
+ const components = parseReference($);
+
+ // The path will always be present (although perhaps empty) on a
+ // successful parse. If it isn’t (and parsing failed), treat the
+ // entire input as the path.
+ components.path ??= `${$}`;
+
+ // Escape disallowed codepoints in each component and compose an
+ // I·R·I from the result.
+ const reference = objectCreate(null);
+ for (const componentName in components) {
+ const componentValue = components[componentName];
+ reference[componentName] = componentValue == null
+ ? undefined
+ : join(
+ objectCreate(
+ iriGeneratorIterablePrototype,
+ {
+ generator: {
+ value: function* () {
+ const encoder = new TE();
+ for (
+ const character of objectCreate(
+ iriCharacterIterablePrototype,
+ { source: { value: componentValue } },
+ )
+ ) {
+ if (
+ new Matcher(
+ `${leiri·ucschar}|${leiri·iprivate}`,
+ )(character) &&
+ !new Matcher(
+ `${ucschar}${
+ componentName == "query"
+ ? `|${iprivate}`
+ : ""
+ }`,
+ )(character)
+ ) {
+ // This codepoint needs to be escaped.
+ const encoded = call(teEncode, encoder, [
+ character,
+ ]);
+ for (
+ let index = 0;
+ index < encoded.length;
+ ++index
+ ) {
+ const byte = encoded[index];
+ yield `%${byte < 0x10 ? "0" : ""}${
+ asciiUppercase(
+ call(numberToString, byte, [0x10]),
+ )
+ }`;
+ }
+ } else {
+ // This codepoint does not need escaping.
+ yield character;
+ }
+ }
+ },
+ },
+ },
+ ),
+ "",
+ );
+ }
+ return composeReference(reference);
+ },
+ escapeForURI: ($) =>
+ join(
+ objectCreate(
+ iriGeneratorIterablePrototype,
+ {
+ generator: {
+ value: function* () {
+ const encoder = new TE();
+ for (
+ const character of objectCreate(
+ iriCharacterIterablePrototype,
+ { source: { value: `${$}` } },
+ )
+ ) {
+ if (
+ new Matcher(
+ `${leiri·ucschar}|${leiri·iprivate}`,
+ )(character)
+ ) {
+ // This codepoint needs to be escaped.
+ const encoded = call(teEncode, encoder, [
+ character,
+ ]);
+ for (
+ let index = 0;
+ index < encoded.length;
+ ++index
+ ) {
+ const byte = encoded[index];
+ yield `%${byte < 0x10 ? "0" : ""}${
+ asciiUppercase(
+ call(numberToString, byte, [0x10]),
+ )
+ }`;
+ }
+ } else {
+ // This codepoint does not need escaping.
+ yield character;
+ }
+ }
+ },
+ },
+ },
+ ),
+ "",
+ ),
+ removeDotSegments: ($) => {
+ const input = `${$}`;
+ const output = [];
+ const { length } = input;
+ let index = 0;
+ while (index < length) {
+ if (stringStartsWith(input, "../", index)) {
+ // The input starts with a double leader; drop it. This can
+ // only occur at the beginning of the input.
+ index += 3;
+ } else if (stringStartsWith(input, "./", index)) {
+ // The input starts with a single leader; drop it. This can
+ // only occur at the beginning of the input.
+ index += 2;
+ } else if (stringStartsWith(input, "/./", index)) {
+ // The input starts with a slash, single leader, and another
+ // slash. Ignore it, and move the input to just before the
+ // second slash.
+ index += 2;
+ } else if (
+ stringStartsWith(input, "/.", index) && index + 2 == length
+ ) {
+ // The input starts with a slash and single leader, and this
+ // exhausts the string. Push an empty segment and move the
+ // index to the end of the string.
+ push(output, "/");
+ index = length;
+ } else if (stringStartsWith(input, "/../", index)) {
+ // The input starts with a slash, double leader, and another
+ // slash. Drop a segment from the output, and move the input
+ // to just before the second slash.
+ index += 3;
+ splice(output, -1, 1);
+ } else if (
+ stringStartsWith(input, "/..", index) && index + 3 == length
+ ) {
+ // The input starts with a slash and single leader, and this
+ // exhausts the string. Drop a segment from the output, push
+ // an empty segment, and move the index to the end of the
+ // string.
+ splice(output, -1, 1, "/");
+ index = length;
+ } else if (
+ stringStartsWith(input, ".", index) && index + 1 == length ||
+ stringStartsWith(input, "..", index) && index + 2 == length
+ ) {
+ // The input starts with a single or double leader, and this
+ // exhausts the string. Do nothing (this can only occur at
+ // the beginning of input) and move the index to the end of
+ // the string.
+ index = length;
+ } else {
+ // The input does not start with a leader. Advance the index
+ // to the position before the next slash and push the segment
+ // between the old and new positions.
+ const nextIndex = getFirstSubstringIndex(
+ input,
+ "/",
+ index + 1,
+ );
+ if (nextIndex == -1) {
+ // No slash remains; set index to the end of the string.
+ push(output, substring(input, index));
+ index = length;
+ } else {
+ // There are further path segments.
+ push(output, substring(input, index, nextIndex));
+ index = nextIndex;
+ }
+ }
+ }
+ return join(
+ objectCreate(
+ iriSegmentIterablePrototype,
+ { segments: { value: output } },
+ ),
+ "",
+ );
+ },
+ };
+})();