From 3a21202820ca9e678494b01e04a642aeeedf52f5 Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Thu, 10 Nov 2022 19:49:03 -0800
Subject: [PATCH 01/32] [breaking] remove support for encoded directories
---
.changeset/smooth-years-speak.md | 5 ++++
.../docs/30-advanced/10-advanced-routing.md | 22 +-------------
.../sync/create_manifest_data/index.spec.js | 29 -------------------
packages/kit/src/utils/routing.js | 27 +++++++++--------
packages/kit/src/utils/routing.spec.js | 14 ++-------
.../routes/encoded/%24[ticker]/+page.svelte | 5 ----
.../basics/src/routes/encoded/+page.svelte | 1 -
.../+page.svelte | 0
packages/kit/test/apps/basics/test/test.js | 8 +----
9 files changed, 24 insertions(+), 87 deletions(-)
create mode 100644 .changeset/smooth-years-speak.md
delete mode 100644 packages/kit/test/apps/basics/src/routes/encoded/%24[ticker]/+page.svelte
rename packages/kit/test/apps/basics/src/routes/encoded/{%40[username] => @[username]}/+page.svelte (100%)
diff --git a/.changeset/smooth-years-speak.md b/.changeset/smooth-years-speak.md
new file mode 100644
index 000000000000..aa8db4c93de4
--- /dev/null
+++ b/.changeset/smooth-years-speak.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[breaking] remove support for encoded directories
diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md
index 021bfcd061a3..76a64242147e 100644
--- a/documentation/docs/30-advanced/10-advanced-routing.md
+++ b/documentation/docs/30-advanced/10-advanced-routing.md
@@ -123,27 +123,7 @@ src/routes/[...catchall]/+page.svelte
### Encoding
-Directory names are URI-decoded, meaning that (for example) a directory like `%40[username]` would match characters beginning with `@`:
-
-```js
-// @filename: ambient.d.ts
-declare global {
- const assert: {
- equal: (a: any, b: any) => boolean;
- };
-}
-
-export {};
-
-// @filename: index.js
-// ---cut---
-assert.equal(
- decodeURIComponent('%40[username]'),
- '@[username]'
-);
-```
-
-To express a `%` character, use `%25`, otherwise the result will be malformed.
+Directories should be named in URI-decoded form. E.g. name your directory `@[username]` and not `%40[username]`. There are some characters which may be illegal to create on a file system in decoded form. Such characters may be handled using [parameter matching](#matching).
### Advanced layouts
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
index a3ab91612d66..ca1a5f51dc30 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
@@ -181,35 +181,6 @@ test('succeeds when routes does not exist', () => {
]);
});
-// TODO some characters will need to be URL-encoded in the filename
-test('encodes invalid characters', () => {
- const { nodes, routes } = create('samples/encoding');
-
- // had to remove ? and " because windows
-
- // const quote = 'samples/encoding/".svelte';
- const hash = { component: 'samples/encoding/%23/+page.svelte' };
- // const question_mark = 'samples/encoding/?.svelte';
-
- assert.equal(nodes.map(simplify_node), [
- default_layout,
- default_error,
- // quote,
- hash
- // question_mark
- ]);
-
- assert.equal(
- routes.map((p) => p.pattern),
- [
- /^\/$/,
- // /^\/%22\/?$/,
- /^\/%23\/?$/
- // /^\/%3F\/?$/
- ]
- );
-});
-
test('sorts routes correctly', () => {
const expected = [
'/',
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index bd8d51dda079..7e968f29f25e 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -21,9 +21,18 @@ export function parse_route_id(id) {
: new RegExp(
`^${get_route_segments(id)
.map((segment, i, segments) => {
- const decoded_segment = decodeURIComponent(segment);
+ if (segment.includes('%')) {
+ throw new Error(`Routes ${id} must be provided in decoded form.`);
+ }
+ if (/[#\/\?]/.test(segment)) {
+ // #, ?, and / will not be decoded by decodeURI so cannot be matched
+ // '/' is illegal on all file systems so we don't bother warning about it
+ throw new Error(
+ `Route ${id} should not contain '#' or '?'. Please use a parameter.`
+ );
+ }
// special case — /[...rest]/ could contain zero segments
- const rest_match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(decoded_segment);
+ const rest_match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(segment);
if (rest_match) {
names.push(rest_match[1]);
types.push(rest_match[2]);
@@ -31,7 +40,7 @@ export function parse_route_id(id) {
return '(?:/(.*))?';
}
// special case — /[[optional]]/ could contain zero segments
- const optional_match = /^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(decoded_segment);
+ const optional_match = /^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(segment);
if (optional_match) {
names.push(optional_match[1]);
types.push(optional_match[2]);
@@ -41,11 +50,11 @@ export function parse_route_id(id) {
const is_last = i === segments.length - 1;
- if (!decoded_segment) {
+ if (!segment) {
return;
}
- const parts = decoded_segment.split(/\[(.+?)\](?!\])/);
+ const parts = segment.split(/\[(.+?)\](?!\])/);
const result = parts
.map((content, i) => {
if (i % 2) {
@@ -72,15 +81,9 @@ export function parse_route_id(id) {
return (
content // allow users to specify characters on the file system in an encoded manner
.normalize()
- // '#', '/', and '?' can only appear in URL path segments in an encoded manner.
- // They will not be touched by decodeURI so need to be encoded here, so
- // that we can match against them.
- // We skip '/' since you can't create a file with it on any OS
- .replace(/#/g, '%23')
- .replace(/\?/g, '%3F')
// escape characters that have special meaning in regex
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
- ); // TODO handle encoding
+ );
})
.join('');
diff --git a/packages/kit/src/utils/routing.spec.js b/packages/kit/src/utils/routing.spec.js
index 213f07b52577..c4b585ac5653 100644
--- a/packages/kit/src/utils/routing.spec.js
+++ b/packages/kit/src/utils/routing.spec.js
@@ -58,20 +58,10 @@ const tests = {
names: ['id'],
types: ['uuid']
},
- '/%23hash-encoded': {
- pattern: /^\/%23hash-encoded\/?$/,
- names: [],
- types: []
- },
- '/%40at-encoded/[id]': {
- pattern: /^\/@at-encoded\/([^/]+?)\/?$/,
+ '/@-symbol/[id]': {
+ pattern: /^\/@-symbol\/([^/]+?)\/?$/,
names: ['id'],
types: [undefined]
- },
- '/%255bdoubly-encoded': {
- pattern: /^\/%5bdoubly-encoded\/?$/,
- names: [],
- types: []
}
};
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/%24[ticker]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/%24[ticker]/+page.svelte
deleted file mode 100644
index a42e298cf3b3..000000000000
--- a/packages/kit/test/apps/basics/src/routes/encoded/%24[ticker]/+page.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
${$page.params.ticker}
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
index 3b96b33a5ef4..ee0a3ec52be9 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
@@ -3,7 +3,6 @@
反应
Redirect
@svelte
-$SVLT
test%20me
test%2fme
AC/DC
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/%40[username]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/@[username]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/%40[username]/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/@[username]/+page.svelte
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 5f9e5a2f7946..1ef0459809a1 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -323,13 +323,7 @@ test.describe('Encoded paths', () => {
});
});
- test('allows %-encoded characters in directory names', async ({ page, clicknav }) => {
- await page.goto('/encoded');
- await clicknav('[href="/encoded/$SVLT"]');
- expect(await page.textContent('h1')).toBe('$SVLT');
- });
-
- test('allows %-encoded characters in filenames', async ({ page, clicknav }) => {
+ test('allows non-ASCII character in parameterized route segment', async ({ page, clicknav }) => {
await page.goto('/encoded');
await clicknav('[href="/encoded/@svelte"]');
expect(await page.textContent('h1')).toBe('@svelte');
From 853901d76a68768d145932b92ca6abafbdd60313 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 15:50:48 -0500
Subject: [PATCH 02/32] readme-driven development
---
.../docs/30-advanced/10-advanced-routing.md | 20 ++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md
index 76a64242147e..abacbfa54a68 100644
--- a/documentation/docs/30-advanced/10-advanced-routing.md
+++ b/documentation/docs/30-advanced/10-advanced-routing.md
@@ -123,7 +123,25 @@ src/routes/[...catchall]/+page.svelte
### Encoding
-Directories should be named in URI-decoded form. E.g. name your directory `@[username]` and not `%40[username]`. There are some characters which may be illegal to create on a file system in decoded form. Such characters may be handled using [parameter matching](#matching).
+Some characters can't be used on the filesystem — `/` on Linux and Mac, `\ / : * ? " < > |` on Windows. Additionally, the `[ ] ( )` characters have special meaning to SvelteKit, so these also can't be used directly as part of your route.
+
+To use these characters in your routes, you can use HTML entities:
+
+- `\` — `\`
+- `/` — `/`
+- `:` — `:`
+- `*` — `*`
+- `?` — `?`
+- `"` — `"`
+- `<` — `<`
+- `>` — `>`
+- `|` — `|`
+- `[` — `[`
+- `]` — `]`
+- `(` — `(`
+- `)` — `)`
+
+For example, to create a `/:-)` route, you would create a `src/routes/:-)/+page.svelte` file.
### Advanced layouts
From 98947b980ce8eb1eddd30b8fca47050aca9d5a35 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 16:03:02 -0500
Subject: [PATCH 03/32] update
---
documentation/docs/30-advanced/10-advanced-routing.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md
index abacbfa54a68..caf3bd275ebd 100644
--- a/documentation/docs/30-advanced/10-advanced-routing.md
+++ b/documentation/docs/30-advanced/10-advanced-routing.md
@@ -123,7 +123,7 @@ src/routes/[...catchall]/+page.svelte
### Encoding
-Some characters can't be used on the filesystem — `/` on Linux and Mac, `\ / : * ? " < > |` on Windows. Additionally, the `[ ] ( )` characters have special meaning to SvelteKit, so these also can't be used directly as part of your route.
+Some characters can't be used on the filesystem — `/` on Linux and Mac, `\ / : * ? " < > |` on Windows. The `#` character has special meaning in URLs, and the `[ ] ( )` characters have special meaning to SvelteKit, so these also can't be used directly as part of your route.
To use these characters in your routes, you can use HTML entities:
@@ -136,6 +136,7 @@ To use these characters in your routes, you can use HTML entities:
- `<` — `<`
- `>` — `>`
- `|` — `|`
+- `#` — `#`
- `[` — `[`
- `]` — `]`
- `(` — `(`
From 1c2fd746c1bbac8b09350c0c31779ab24e01a9e4 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 16:04:16 -0500
Subject: [PATCH 04/32] update changeset
---
.changeset/smooth-years-speak.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.changeset/smooth-years-speak.md b/.changeset/smooth-years-speak.md
index aa8db4c93de4..198e613bb44f 100644
--- a/.changeset/smooth-years-speak.md
+++ b/.changeset/smooth-years-speak.md
@@ -2,4 +2,4 @@
'@sveltejs/kit': patch
---
-[breaking] remove support for encoded directories
+[breaking] require special characters to be HTML-encoded
From 9e4638a89bcae2a70263735f46f0f896b7a57e6a Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 16:14:13 -0500
Subject: [PATCH 05/32] reinstate and update test
---
.../sync/create_manifest_data/index.spec.js | 21 +++++++++++++++++++
.../encoding/{%23 => #}/+page.svelte | 0
.../samples/encoding/?/+page.svelte | 0
.../test/samples/encoding/"/+page.svelte | 0
4 files changed, 21 insertions(+)
rename packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/{%23 => #}/+page.svelte (100%)
create mode 100644 packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/?/+page.svelte
create mode 100644 packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/"/+page.svelte
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
index ca1a5f51dc30..57e1cdc53669 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
@@ -181,6 +181,27 @@ test('succeeds when routes does not exist', () => {
]);
});
+test('encodes invalid characters', () => {
+ const { nodes, routes } = create('samples/encoding');
+
+ const hash = { component: 'samples/encoding/#/+page.svelte' };
+ const question_mark = { component: 'samples/encoding/?/+page.svelte' };
+ const quote = { component: 'samples/encoding/"/+page.svelte' };
+
+ assert.equal(nodes.map(simplify_node), [
+ default_layout,
+ default_error,
+ hash,
+ question_mark,
+ quote
+ ]);
+
+ assert.equal(
+ routes.map((p) => p.pattern),
+ [/^\/$/, /^\/%23\/?$/, /^\/%3[Ff]\/?$/, /^\/"\/?$/]
+ );
+});
+
test('sorts routes correctly', () => {
const expected = [
'/',
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/%23/+page.svelte b/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/#/+page.svelte
similarity index 100%
rename from packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/%23/+page.svelte
rename to packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/#/+page.svelte
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/?/+page.svelte b/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/?/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/"/+page.svelte b/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/"/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
From 6835f30f8604fd6999825f41809fb4f646a5c211 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 16:18:35 -0500
Subject: [PATCH 06/32] change errors
---
.../kit/src/core/sync/create_manifest_data/index.js | 5 +++++
packages/kit/src/utils/routing.js | 10 ----------
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index 37b74741e1ff..2fe6367a353f 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -110,6 +110,11 @@ function create_routes_and_nodes(cwd, config, fallback) {
throw new Error(`Invalid route ${id} — brackets are unbalanced`);
}
+ if (/#/.test(segment)) {
+ // Vite will barf on files with # in them
+ throw new Error(`Route ${id} should be renamed ${id.replace(/#/g, '#')}`);
+ }
+
if (/\[\.\.\.\w+\]\/\[\[/.test(id)) {
throw new Error(
`Invalid route ${id} — an [[optional]] route segment cannot follow a [...rest] route segment`
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index 7e968f29f25e..f132cd095196 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -21,16 +21,6 @@ export function parse_route_id(id) {
: new RegExp(
`^${get_route_segments(id)
.map((segment, i, segments) => {
- if (segment.includes('%')) {
- throw new Error(`Routes ${id} must be provided in decoded form.`);
- }
- if (/[#\/\?]/.test(segment)) {
- // #, ?, and / will not be decoded by decodeURI so cannot be matched
- // '/' is illegal on all file systems so we don't bother warning about it
- throw new Error(
- `Route ${id} should not contain '#' or '?'. Please use a parameter.`
- );
- }
// special case — /[...rest]/ could contain zero segments
const rest_match = /^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(segment);
if (rest_match) {
From fb182929697fa3f4efb079ab6e2eb99e317dc9b0 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 16:23:38 -0500
Subject: [PATCH 07/32] add test
---
.../basics/src/routes/encoded/:-)/+page.svelte | 5 +++++
.../kit/test/apps/basics/src/routes/encoded/+page.svelte | 1 +
packages/kit/test/apps/basics/test/test.js | 6 ++++++
3 files changed, 12 insertions(+)
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
new file mode 100644
index 000000000000..c6e37c693223
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
@@ -0,0 +1,5 @@
+
+
+{$page.params.pathname.split('/').pop()}
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
index ee0a3ec52be9..b64f58737137 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
@@ -7,3 +7,4 @@
test%2fme
AC/DC
[
+:-)
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 1ef0459809a1..9d66229454e9 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -328,6 +328,12 @@ test.describe('Encoded paths', () => {
await clicknav('[href="/encoded/@svelte"]');
expect(await page.textContent('h1')).toBe('@svelte');
});
+
+ test('allows characters to be represented as HTML entities', async ({ page }) => {
+ await page.goto('/encoded');
+ await clicknav('[href="/encoded/:-)"]');
+ expect(await page.textContent('h1')).toBe(':-)');
+ });
});
test.describe('$env', () => {
From 4f11591ce0c9db465aa1e01fe615cd51ecb67eb0 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 16:41:23 -0500
Subject: [PATCH 08/32] fix tests
---
packages/kit/src/core/sync/create_manifest_data/index.spec.js | 4 ++--
packages/kit/test/apps/basics/test/test.js | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
index 57e1cdc53669..a1c72503d60f 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
@@ -197,8 +197,8 @@ test('encodes invalid characters', () => {
]);
assert.equal(
- routes.map((p) => p.pattern),
- [/^\/$/, /^\/%23\/?$/, /^\/%3[Ff]\/?$/, /^\/"\/?$/]
+ routes.map((p) => p.pattern.toString()),
+ [/^\/$/, /^\/%23\/?$/, /^\/%3[Ff]\/?$/, /^\/"\/?$/].map((pattern) => pattern.toString())
);
});
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 9d66229454e9..ce076872e775 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -329,7 +329,7 @@ test.describe('Encoded paths', () => {
expect(await page.textContent('h1')).toBe('@svelte');
});
- test('allows characters to be represented as HTML entities', async ({ page }) => {
+ test('allows characters to be represented as HTML entities', async ({ page, clicknav }) => {
await page.goto('/encoded');
await clicknav('[href="/encoded/:-)"]');
expect(await page.textContent('h1')).toBe(':-)');
From 1a7a9c80cd307214efd94c29cbe14c42da292356 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 17:42:44 -0500
Subject: [PATCH 09/32] get some stuff to work
---
.../core/sync/create_manifest_data/index.js | 3 +-
packages/kit/src/runtime/client/parse.js | 8 +-
packages/kit/src/utils/entities.js | 2259 +++++++++++++++++
packages/kit/src/utils/entities.spec.js | 13 +
packages/kit/src/utils/routing.js | 18 +-
packages/kit/src/utils/routing.spec.js | 12 +-
6 files changed, 2303 insertions(+), 10 deletions(-)
create mode 100644 packages/kit/src/utils/entities.js
create mode 100644 packages/kit/src/utils/entities.spec.js
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index 2fe6367a353f..31b903619b48 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -5,6 +5,7 @@ import { runtime_directory } from '../../utils.js';
import { posixify } from '../../../utils/filesystem.js';
import { parse_route_id } from '../../../utils/routing.js';
import { sort_routes } from './sort.js';
+import { decode_html_entities } from '../../../utils/entities.js';
/**
* Generates the manifest data used for the client-side manifest and types generation.
@@ -127,7 +128,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
);
}
- const { pattern, names, types, optional } = parse_route_id(id);
+ const { pattern, names, types, optional } = parse_route_id(id, decode_html_entities);
/** @type {import('types').RouteData} */
const route = {
diff --git a/packages/kit/src/runtime/client/parse.js b/packages/kit/src/runtime/client/parse.js
index 63909d0fcf8f..86feed8f1b2b 100644
--- a/packages/kit/src/runtime/client/parse.js
+++ b/packages/kit/src/runtime/client/parse.js
@@ -10,8 +10,14 @@ import { exec, parse_route_id } from '../../utils/routing.js';
export function parse(nodes, server_loads, dictionary, matchers) {
const layouts_with_server_load = new Set(server_loads);
+ const div = document.createElement('div');
+
return Object.entries(dictionary).map(([id, [leaf, layouts, errors]]) => {
- const { pattern, names, types, optional } = parse_route_id(id);
+ const { pattern, names, types, optional } = parse_route_id(id, (str) => {
+ if (!str.includes('&')) return str;
+ div.innerHTML = str;
+ return /** @type {string} */ (div.textContent);
+ });
const route = {
id,
diff --git a/packages/kit/src/utils/entities.js b/packages/kit/src/utils/entities.js
new file mode 100644
index 000000000000..e6b9997d692b
--- /dev/null
+++ b/packages/kit/src/utils/entities.js
@@ -0,0 +1,2259 @@
+/**
+ * Decode HTML entities
+ * @param {string} str
+ */
+export function decode_html_entities(str) {
+ if (!str.includes('&')) return str;
+
+ return str
+ .replace(/&(#)?(x)?(.+?);/g, (match, is_number, is_hex, value) => {
+ if (is_number) {
+ return String.fromCharCode(parseInt(value, is_hex ? 16 : 10));
+ }
+
+ return match in entities ? entities[match] : match;
+ })
+ .replace(ambiguous, (match) => entities[match]);
+}
+
+/** @type {Record} */
+const entities = {
+ '∳': '∳',
+ '∲': '∲',
+ '⟺': '⟺',
+ '⪢̸': '⪢̸',
+ '˝': '˝',
+ '⋣': '⋣',
+ '”': '”',
+ '∯': '∯',
+ '▪': '▪',
+ '​': '',
+ '⋠': '⋠',
+ '⋭': '⋭',
+ '⋡': '⋡',
+ 'ⅅ': 'ⅅ',
+ '⇔': '⇔',
+ '⟹': '⟹',
+ '▫': '▫',
+ '≫': '≫',
+ '∦': '∦',
+ '⩾̸': '⩾̸',
+ '⋬': '⋬',
+ '⋢': '⋢',
+ '“': '“',
+ '⥯': '⥯',
+ '⟸': '⟸',
+ '⥐': '⥐',
+ '⇆': '⇆',
+ '​': '',
+ '≧̸': '≧̸',
+ '⧐̸': '⧐̸',
+ '⇄': '⇄',
+ '⊒': '⊒',
+ '↭': '↭',
+ '⥟': '⥟',
+ '⥗': '⥗',
+ '⟷': '⟷',
+ '⟺': '⟺',
+ '​': '',
+ '⧏̸': '⧏̸',
+ '≼': '≼',
+ '⇋': '⇋',
+ '⟧': '⟧',
+ '⥝': '⥝',
+ '⥕': '⥕',
+ '⊵': '⊵',
+ '⊓': '⊓',
+ '≽': '≽',
+ '▸': '▸',
+ '⟷': '⟷',
+ '⇕': '⇕',
+ '∥': '∥',
+ '⥞': '⥞',
+ '⥖': '⥖',
+ '◼': '◼',
+ '⩾': '⩾',
+ '⟦': '⟦',
+ '⥡': '⥡',
+ '⥙': '⥙',
+ '⊴': '⊴',
+ '​': '',
+ '≫̸': '≫̸',
+ '⩽̸': '⩽̸',
+ '⪡̸': '⪡̸',
+ '∌': '∌',
+ '⊐̸': '⊐̸',
+ '≇': '≇',
+ '⟩': '⟩',
+ '⥏': '⥏',
+ '⊑': '⊑',
+ '❘': '❘',
+ '▾': '▾',
+ '◂': '◂',
+ '⇋': '⇋',
+ '⇌': '⇌',
+ '↠': '↠',
+ '´': '´',
+ '`': '`',
+ '˜': '˜',
+ '⇒': '⇒',
+ '⇵': '⇵',
+ '◻': '◻',
+ '⋛': '⋛',
+ '≧': '≧',
+ '⟨': '⟨',
+ '⥑': '⥑',
+ '⋚': '⋚',
+ ' ': ' ',
+ '⪯̸': '⪯̸',
+ '⋫': '⋫',
+ '⪰̸': '⪰̸',
+ '≿̸': '≿̸',
+ '⊉': '⊉',
+ '⧐': '⧐',
+ '⥜': '⥜',
+ '⥔': '⥔',
+ '⏝': '⏝',
+ '⇅': '⇅',
+ '↻': '↻',
+ '⇂': '⇂',
+ '⋭': '⋭',
+ '⇁': '⇁',
+ '⇉': '⇉',
+ '↞': '↞',
+ '⊳': '⊳',
+ '’': '’',
+ '∮': '∮',
+ '⇓': '⇓',
+ '⇐': '⇐',
+ '⇁': '⇁',
+ '⥎': '⥎',
+ '⧏': '⧏',
+ '⥠': '⥠',
+ '⥘': '⥘',
+ '↘': '↘',
+ '≱': '≱',
+ '≵': '≵',
+ '≎̸': '≎̸',
+ '⋪': '⋪',
+ '⊏̸': '⊏̸',
+ '⏜': '⏜',
+ '⇂': '⇂',
+ '→': '→',
+ '↗': '↗',
+ '▽': '▽',
+ '↺': '↺',
+ '↷': '↷',
+ '⇃': '⇃',
+ '↽': '↽',
+ '⇆': '⇆',
+ '⇎': '⇎',
+ '↮': '↮',
+ '⋬': '⋬',
+ '⇄': '⇄',
+ '↝': '↝',
+ '⋌': '⋌',
+ 'ϵ': 'ϵ',
+ '⊵': '⊵',
+ '⊲': '⊲',
+ '˙': '˙',
+ '⊨': '⊨',
+ '↽': '↽',
+ '⪢': '⪢',
+ '─': '─',
+ '⁣': '',
+ '⁢': '',
+ '⇃': '⇃',
+ '↔': '↔',
+ '⇔': '⇔',
+ '⩽': '⩽',
+ '⟶': '⟶',
+ '⟹': '⟹',
+ '↙': '↙',
+ '≪': '≪',
+ '≹': '≹',
+ '≸': '≸',
+ '⊈': '⊈',
+ '∤': '∤',
+ '‘': '‘',
+ '∋': '∋',
+ '⥛': '⥛',
+ '⥓': '⥓',
+ '↓': '↓',
+ '←': '←',
+ '⊐': '⊐',
+ '≅': '≅',
+ '↖': '↖',
+ '​': '',
+ '↶': '↶',
+ '⌆': '⌆',
+ '⇊': '⇊',
+ '↪': '↪',
+ '⇇': '⇇',
+ '↔': '↔',
+ '⋋': '⋋',
+ '⟶': '⟶',
+ '↬': '↬',
+ '∦': '∦',
+ '⋫': '⋫',
+ '↣': '↣',
+ '⇀': '⇀',
+ '⊴': '⊴',
+ '↾': '↾',
+ '⁡': '',
+ 'ⅆ': 'ⅆ',
+ '⫤': '⫤',
+ '⇑': '⇑',
+ '⥚': '⥚',
+ '⥒': '⥒',
+ '≦': '≦',
+ '⟵': '⟵',
+ '⟸': '⟸',
+ '≂̸': '≂̸',
+ '≄': '≄',
+ '≉': '≉',
+ 'ℌ': 'ℌ',
+ '⪯': '⪯',
+ '≾': '≾',
+ '⇥': '⇥',
+ '↦': '↦',
+ '⊳': '⊳',
+ '↾': '↾',
+ '⪰': '⪰',
+ '≿': '≿',
+ '⊇': '⊇',
+ '⥮': '⥮',
+ '≀': '≀',
+ ' ': ' ',
+ '△': '△',
+ '▴': '▴',
+ '⋇': '⋇',
+ '≒': '≒',
+ '↩': '↩',
+ '↢': '↢',
+ '↼': '↼',
+ '⟵': '⟵',
+ '↫': '↫',
+ '∡': '∡',
+ '⋪': '⋪',
+ '∥': '∥',
+ '∖': '∖',
+ '▹': '▹',
+ '↿': '↿',
+ '⫋︀': '⫋︀',
+ '⫌︀': '⫌︀',
+ '⤓': '⤓',
+ '↧': '↧',
+ 'ⅇ': 'ⅇ',
+ '≥': '≥',
+ '≳': '≳',
+ 'ℋ': 'ℋ',
+ '≎': '≎',
+ '⋂': '⋂',
+ '⇤': '⇤',
+ '↤': '↤',
+ '⊲': '⊲',
+ '↿': '↿',
+ '≢': '≢',
+ '≏̸': '≏̸',
+ '≰': '≰',
+ '≴': '≴',
+ '∝': '∝',
+ '⌉': '⌉',
+ '⥰': '⥰',
+ '↑': '↑',
+ '⊏': '⊏',
+ '⎵': '⎵',
+ '|': '|',
+ '⧫': '⧫',
+ 'ⅇ': 'ⅇ',
+ '≓': '≓',
+ '▿': '▿',
+ '◃': '◃',
+ '⊊︀': '⊊︀',
+ '⊋︀': '⊋︀',
+ '⊖': '⊖',
+ '⊗': '⊗',
+ '⇌': '⇌',
+ '≷': '≷',
+ '⌈': '⌈',
+ '≶': '≶',
+ ' ': ' ',
+ '≪̸': '≪̸',
+ '⊀': '⊀',
+ '⊁': '⊁',
+ '⊃⃒': '⊃⃒',
+ '⎴': '⎴',
+ '⇀': '⇀',
+ '⇛': '⇛',
+ '⧴': '⧴',
+ '∘': '∘',
+ '⊔': '⊔',
+ '⊆': '⊆',
+ '↕': '↕',
+ '⇕': '⇕',
+ '∣': '∣',
+ '϶': '϶',
+ '▪': '▪',
+ '⊚': '⊚',
+ '⊝': '⊝',
+ '⋞': '⋞',
+ '⋟': '⋟',
+ '♦': '♦',
+ '⪕': '⪕',
+ 'ℰ': 'ℰ',
+ '⇏': '⇏',
+ '↛': '↛',
+ '≼': '≼',
+ '⪹': '⪹',
+ 'ℍ': 'ℍ',
+ 'ϕ': 'ϕ',
+ '≽': '≽',
+ '⪺': '⪺',
+ '≈': '≈',
+ '↕': '↕',
+ 'ℬ': 'ℬ',
+ '⊕': '⊕',
+ '≂': '≂',
+ 'ℱ': 'ℱ',
+ 'ⅈ': 'ⅈ',
+ 'ℒ': 'ℒ',
+ '↼': '↼',
+ '⇚': '⇚',
+ '∉': '∉',
+ '≯': '≯',
+ '∷': '∷',
+ '→': '→',
+ '⌋': '⌋',
+ '⇒': '⇒',
+ '  ': ' ',
+ '≃': '≃',
+ '≈': '≈',
+ '⏟': '⏟',
+ '⤒': '⤒',
+ '↥': '↥',
+ '⊛': '⊛',
+ '∁': '∁',
+ '⋏': '⋏',
+ '⪖': '⪖',
+ '⪌': '⪌',
+ '⪅': '⪅',
+ '⪋': '⪋',
+ '⎰': '⎰',
+ '⟼': '⟼',
+ '↧': '↧',
+ '↤': '↤',
+ '⇍': '⇍',
+ '↚': '↚',
+ '⫅̸': '⫅̸',
+ '⫆̸': '⫆̸',
+ '⪷': '⪷',
+ '→': '→',
+ '⎱': '⎱',
+ '⊑': '⊑',
+ '⊒': '⊒',
+ '⫋': '⫋',
+ '⪸': '⪸',
+ '⫌': '⫌',
+ '⇈': '⇈',
+ 'ϵ': 'ϵ',
+ '∅': '∅',
+ '∖': '∖',
+ '·': '·',
+ '⊙': '⊙',
+ '≡': '≡',
+ '∐': '∐',
+ '¨': '¨',
+ '↓': '↓',
+ '̑': '̑',
+ '⇓': '⇓',
+ '≏': '≏',
+ '←': '←',
+ '⌊': '⌊',
+ '⇐': '⇐',
+ '≲': '≲',
+ 'ℳ': 'ℳ',
+ '∓': '∓',
+ '≭': '≭',
+ '∄': '∄',
+ '⊂⃒': '⊂⃒',
+ '⏞': '⏞',
+ '±': '±',
+ '∴': '∴',
+ ' ': ' ',
+ '⃛': '⃛',
+ '⊎': '⊎',
+ '‵': '‵',
+ '⋍': '⋍',
+ '⨂': '⨂',
+ '·': '·',
+ '✓': '✓',
+ 'ℂ': 'ℂ',
+ '⊡': '⊡',
+ '↓': '↓',
+ '⪆': '⪆',
+ '⋛': '⋛',
+ '≩︀': '≩︀',
+ '♥': '♥',
+ '←': '←',
+ '⋚': '⋚',
+ '≨︀': '≨︀',
+ '⩾̸': '⩾̸',
+ '⩽̸': '⩽̸',
+ '∦': '∦',
+ '∤': '∤',
+ '⊈': '⊈',
+ '⊉': '⊉',
+ '⋔': '⋔',
+ 'ℚ': 'ℚ',
+ '♠': '♠',
+ '⫅': '⫅',
+ '⊊': '⊊',
+ '⫆': '⫆',
+ '⊋': '⊋',
+ '∴': '∴',
+ '≜': '≜',
+ '∝': '∝',
+ '⤑': '⤑',
+ '≐': '≐',
+ '∫': '∫',
+ '⪡': '⪡',
+ '≠': '≠',
+ '≁': '≁',
+ '∂': '∂',
+ '≺': '≺',
+ '⊢': '⊢',
+ '≻': '≻',
+ '∋': '∋',
+ '⊃': '⊃',
+ '⥉': '⥉',
+ '_': '_',
+ '⩘': '⩘',
+ '⦨': '⦨',
+ '⦩': '⦩',
+ '⦪': '⦪',
+ '⦫': '⦫',
+ '⦬': '⦬',
+ '⦭': '⦭',
+ '⦮': '⦮',
+ '⦯': '⦯',
+ '⦝': '⦝',
+ '≊': '≊',
+ '∳': '∳',
+ '≌': '≌',
+ '⌅': '⌅',
+ '⎶': '⎶',
+ '⨁': '⨁',
+ '⨆': '⨆',
+ '⨄': '⨄',
+ '⋀': '⋀',
+ '⊟': '⊟',
+ '⊠': '⊠',
+ '⟈': '⟈',
+ '⩉': '⩉',
+ '®': '®',
+ 'Ⓢ': 'Ⓢ',
+ '⨐': '⨐',
+ '♣': '♣',
+ '⩈': '⩈',
+ '⋎': '⋎',
+ '∲': '∲',
+ '≑': '≑',
+ '∸': '∸',
+ '⤐': '⤐',
+ '⟿': '⟿',
+ '⏧': '⏧',
+ '∅': '∅',
+ '⧥': '⧥',
+ '⨍': '⨍',
+ '⩾': '⩾',
+ '⪄': '⪄',
+ '⪊': '⪊',
+ '⤥': '⤥',
+ '⤦': '⤦',
+ 'ℐ': 'ℐ',
+ 'ℑ': 'ℑ',
+ '⧝': '⧝',
+ 'ℤ': 'ℤ',
+ '⊺': '⊺',
+ '⨗': '⨗',
+ '⦴': '⦴',
+ '⥋': '⥋',
+ '⩽': '⩽',
+ '⪃': '⪃',
+ '⌞': '⌞',
+ '⪉': '⪉',
+ '⌟': '⌟',
+ '⥊': '⥊',
+ '↥': '↥',
+ '⊸': '⊸',
+ 'ℕ': 'ℕ',
+ '⩭̸': '⩭̸',
+ '⋵̸': '⋵̸',
+ '⨶': '⨶',
+ '∥': '∥',
+ '⨣': '⨣',
+ '⨕': '⨕',
+ '⪵': '⪵',
+ '⋨': '⋨',
+ '⌮': '⌮',
+ '⌒': '⌒',
+ '⌓': '⌓',
+ '⦳': '⦳',
+ 'ℜ': 'ℜ',
+ '⨒': '⨒',
+ '⧎': '⧎',
+ '⨓': '⨓',
+ '∖': '∖',
+ '∣': '∣',
+ '⧤': '⧤',
+ '⊏': '⊏',
+ '⊐': '⊐',
+ '⊆': '⊆',
+ '⪶': '⪶',
+ '⋩': '⋩',
+ '⊇': '⊇',
+ 'ϑ': 'ϑ',
+ '∼': '∼',
+ '⨱': '⨱',
+ '▵': '▵',
+ '⨺': '⨺',
+ '⏢': '⏢',
+ '⌜': '⌜',
+ '⌝': '⌝',
+ 'ϰ': 'ϰ',
+ 'ς': 'ς',
+ 'ϑ': 'ϑ',
+ '∵': '∵',
+ 'ℭ': 'ℭ',
+ '∰': '∰',
+ '¸': '¸',
+ '⋄': '⋄',
+ '⊤': '⊤',
+ '∈': '∈',
+ 'Ε': 'Ε',
+ '⇒': '⇒',
+ '⊣': '⊣',
+ '
': '\n',
+ '⁠': '',
+ '≮': '≮',
+ 'Ο': 'Ο',
+ '‾': '‾',
+ '∏': '∏',
+ '↑': '↑',
+ '⇑': '⇑',
+ 'Υ': 'Υ',
+ 'ℵ': 'ℵ',
+ '⊾': '⊾',
+ '⍼': '⍼',
+ '≍': '≍',
+ '∽': '∽',
+ '∵': '∵',
+ '⦰': '⦰',
+ '≬': '≬',
+ '◯': '◯',
+ '⨀': '⨀',
+ '★': '★',
+ '≡⃥': '≡⃥',
+ '⊞': '⊞',
+ '⩐': '⩐',
+ '⦲': '⦲',
+ '⧂': '⧂',
+ '≔': '≔',
+ '⩭': '⩭',
+ '⤸': '⤸',
+ '⤵': '⤵',
+ '⤽': '⤽',
+ '⤼': '⤼',
+ '⤏': '⤏',
+ '‡': '‡',
+ '⩷': '⩷',
+ '⦱': '⦱',
+ '⋄': '⋄',
+ 'ϝ': 'ϝ',
+ '∔': '∔',
+ '⦦': '⦦',
+ 'ε': 'ε',
+ '≕': '≕',
+ '⩸': '⩸',
+ '⪂': '⪂',
+ '⩼': '⩼',
+ '≷': '≷',
+ '⥈': '⥈',
+ '⨼': '⨼',
+ '⋵': '⋵',
+ '⤟': '⤟',
+ '⥳': '⥳',
+ '⦏': '⦏',
+ '⦍': '⦍',
+ '⥧': '⥧',
+ '⪁': '⪁',
+ '⋖': '⋖',
+ '≶': '≶',
+ '≲': '≲',
+ '⨴': '⨴',
+ '◊': '◊',
+ '⩻': '⩻',
+ '⥦': '⥦',
+ '✠': '✠',
+ '⨪': '⨪',
+ '≉': '≉',
+ '♮': '♮',
+ '↗': '↗',
+ '∄': '∄',
+ '∉': '∉',
+ '⋷': '⋷',
+ '⋶': '⋶',
+ '∌': '∌',
+ '⋾': '⋾',
+ '⋽': '⋽',
+ '⨔': '⨔',
+ '⪯̸': '⪯̸',
+ '⋢': '⋢',
+ '⋣': '⋣',
+ '⊂⃒': '⊂⃒',
+ '⪰̸': '⪰̸',
+ '⊃⃒': '⊃⃒',
+ '⧞': '⧞',
+ '⊴⃒': '⊴⃒',
+ '⊵⃒': '⊵⃒',
+ '↖': '↖',
+ '⦻': '⦻',
+ 'ο': 'ο',
+ 'ℴ': 'ℴ',
+ '⩗': '⩗',
+ '‱': '‱',
+ 'ℎ': 'ℎ',
+ '⨢': '⨢',
+ '⨦': '⨦',
+ '⨧': '⨧',
+ '≾': '≾',
+ '⨖': '⨖',
+ '≟': '≟',
+ '⤠': '⤠',
+ '⥴': '⥴',
+ '⦎': '⦎',
+ '⦐': '⦐',
+ '⥩': '⥩',
+ 'ℛ': 'ℛ',
+ '⨵': '⨵',
+ '⥨': '⥨',
+ '↘': '↘',
+ '⨤': '⨤',
+ '⥲': '⥲',
+ '⫃': '⫃',
+ '⫁': '⫁',
+ '⪿': '⪿',
+ '⥹': '⥹',
+ '≿': '≿',
+ '⫘': '⫘',
+ '⫄': '⫄',
+ '⟉': '⟉',
+ '⫗': '⫗',
+ '⥻': '⥻',
+ '⫂': '⫂',
+ '⫀': '⫀',
+ '↙': '↙',
+ '⫚': '⫚',
+ '⨹': '⨹',
+ '⨻': '⨻',
+ '↑': '↑',
+ 'υ': 'υ',
+ '⦧': '⦧',
+ '⦚': '⦚',
+ '⇝': '⇝',
+ 'Á': 'Á',
+ 'Ă': 'Ă',
+ 'À': 'À',
+ '≔': '≔',
+ 'Ã': 'Ã',
+ '⌆': '⌆',
+ '≎': '≎',
+ 'Ć': 'Ć',
+ 'Č': 'Č',
+ 'Ç': 'Ç',
+ '⩴': '⩴',
+ '∯': '∯',
+ '≍': '≍',
+ '‡': '‡',
+ 'Ď': 'Ď',
+ '⃜': '⃜',
+ 'Đ': 'Đ',
+ 'É': 'É',
+ 'Ě': 'Ě',
+ 'È': 'È',
+ '∃': '∃',
+ '∀': '∀',
+ 'Ϝ': 'Ϝ',
+ 'Ğ': 'Ğ',
+ 'Ģ': 'Ģ',
+ 'Ъ': 'Ъ',
+ 'Ħ': 'Ħ',
+ 'Í': 'Í',
+ 'Ì': 'Ì',
+ 'Ĩ': 'Ĩ',
+ 'Ј': 'Ј',
+ 'Ķ': 'Ķ',
+ 'Ĺ': 'Ĺ',
+ 'Λ': 'Λ',
+ 'Ľ': 'Ľ',
+ 'Ļ': 'Ļ',
+ 'Ŀ': 'Ŀ',
+ 'Ł': 'Ł',
+ 'Ń': 'Ń',
+ 'Ň': 'Ň',
+ 'Ņ': 'Ņ',
+ 'Ñ': 'Ñ',
+ 'Ó': 'Ó',
+ 'Ő': 'Ő',
+ 'Ò': 'Ò',
+ 'Ø': 'Ø',
+ 'Õ': 'Õ',
+ '⨷': '⨷',
+ 'Ŕ': 'Ŕ',
+ '⤖': '⤖',
+ 'Ř': 'Ř',
+ 'Ŗ': 'Ŗ',
+ 'Щ': 'Щ',
+ 'Ь': 'Ь',
+ 'Ś': 'Ś',
+ 'Š': 'Š',
+ 'Ş': 'Ş',
+ '□': '□',
+ '⋐': '⋐',
+ '⋑': '⋑',
+ 'Ť': 'Ť',
+ 'Ţ': 'Ţ',
+ 'Ŧ': 'Ŧ',
+ 'Ú': 'Ú',
+ 'Ŭ': 'Ŭ',
+ 'Ű': 'Ű',
+ 'Ù': 'Ù',
+ 'Ũ': 'Ũ',
+ '⫦': '⫦',
+ '‖': '‖',
+ '⊪': '⊪',
+ 'Ý': 'Ý',
+ 'Ź': 'Ź',
+ 'Ž': 'Ž',
+ 'á': 'á',
+ 'ă': 'ă',
+ 'à': 'à',
+ '⩕': '⩕',
+ '∡': '∡',
+ '∢': '∢',
+ '⩯': '⩯',
+ '≈': '≈',
+ 'ã': 'ã',
+ '⊽': '⊽',
+ '⌅': '⌅',
+ '∵': '∵',
+ 'ℬ': 'ℬ',
+ '⋂': '⋂',
+ '⋃': '⋃',
+ '⋁': '⋁',
+ '⤍': '⤍',
+ '⊥': '⊥',
+ '⋈': '⋈',
+ '⧉': '⧉',
+ '‵': '‵',
+ '¦': '¦',
+ '•': '•',
+ '≏': '≏',
+ 'ć': 'ć',
+ '⩄': '⩄',
+ '⩋': '⩋',
+ '⩇': '⩇',
+ '⩀': '⩀',
+ 'č': 'č',
+ 'ç': 'ç',
+ '≗': '≗',
+ '⫯': '⫯',
+ '≔': '≔',
+ '@': '@',
+ '∘': '∘',
+ '∮': '∮',
+ '∐': '∐',
+ '℗': '℗',
+ '↶': '↶',
+ '⩆': '⩆',
+ '⩊': '⩊',
+ '⊍': '⊍',
+ '↷': '↷',
+ '¤': '¤',
+ '⌭': '⌭',
+ '†': '†',
+ 'ℸ': 'ℸ',
+ 'ď': 'ď',
+ '⥿': '⥿',
+ '÷': '÷',
+ '⋇': '⋇',
+ '⌞': '⌞',
+ '⌍': '⌍',
+ '$': '$',
+ '⌟': '⌟',
+ '⌌': '⌌',
+ 'đ': 'đ',
+ 'é': 'é',
+ '⩮': '⩮',
+ 'ě': 'ě',
+ '≕': '≕',
+ 'è': 'è',
+ '⪘': '⪘',
+ '⪗': '⪗',
+ '∅': '∅',
+ ' ': ' ',
+ ' ': ' ',
+ '⧣': '⧣',
+ '≖': '≖',
+ '=': '=',
+ '≟': '≟',
+ '♀': '♀',
+ 'ffi': 'ffi',
+ 'ffl': 'ffl',
+ '∀': '∀',
+ '½': '½',
+ '⅓': '⅓',
+ '¼': '¼',
+ '⅕': '⅕',
+ '⅙': '⅙',
+ '⅛': '⅛',
+ '⅔': '⅔',
+ '⅖': '⅖',
+ '¾': '¾',
+ '⅗': '⅗',
+ '⅜': '⅜',
+ '⅘': '⅘',
+ '⅚': '⅚',
+ '⅝': '⅝',
+ '⅞': '⅞',
+ 'ǵ': 'ǵ',
+ 'ϝ': 'ϝ',
+ 'ğ': 'ğ',
+ '⪀': '⪀',
+ '⪔': '⪔',
+ '⦕': '⦕',
+ '⥸': '⥸',
+ '⋗': '⋗',
+ '≳': '≳',
+ ' ': ' ',
+ 'ℋ': 'ℋ',
+ 'ъ': 'ъ',
+ '♥': '♥',
+ '…': '…',
+ '⊹': '⊹',
+ '∻': '∻',
+ '―': '―',
+ 'ℏ': 'ℏ',
+ 'ħ': 'ħ',
+ '⁃': '⁃',
+ '‐': '‐',
+ 'í': 'í',
+ 'ì': 'ì',
+ '⨌': '⨌',
+ '⧜': '⧜',
+ '℅': '℅',
+ 'ı': 'ı',
+ '⊺': '⊺',
+ '¿': '¿',
+ '⋳': '⋳',
+ 'ĩ': 'ĩ',
+ 'ј': 'ј',
+ 'ϰ': 'ϰ',
+ 'ķ': 'ķ',
+ 'ĸ': 'ĸ',
+ '⤛': '⤛',
+ 'ĺ': 'ĺ',
+ 'ℒ': 'ℒ',
+ 'λ': 'λ',
+ '⟨': '⟨',
+ '⤝': '⤝',
+ '↩': '↩',
+ '↫': '↫',
+ '⤹': '⤹',
+ '↢': '↢',
+ '⤙': '⤙',
+ '{': '{',
+ '[': '[',
+ 'ľ': 'ľ',
+ 'ļ': 'ļ',
+ '„': '„',
+ '⩿': '⩿',
+ '⪓': '⪓',
+ '⥼': '⥼',
+ '⌊': '⌊',
+ '⥪': '⥪',
+ '⥫': '⥫',
+ 'ŀ': 'ŀ',
+ '⎰': '⎰',
+ '⨭': '⨭',
+ '∗': '∗',
+ '_': '_',
+ '⦓': '⦓',
+ '⥭': '⥭',
+ '‹': '‹',
+ '‚': '‚',
+ 'ł': 'ł',
+ '⋋': '⋋',
+ '⋉': '⋉',
+ '⥶': '⥶',
+ '⦖': '⦖',
+ '↦': '↦',
+ '▮': '▮',
+ '⨩': '⨩',
+ '*': '*',
+ '⫰': '⫰',
+ '·': '·',
+ '⊟': '⊟',
+ '∸': '∸',
+ '∓': '∓',
+ '⊧': '⊧',
+ '∾': '∾',
+ '⊯': '⊯',
+ '⊮': '⊮',
+ 'ń': 'ń',
+ '≏̸': '≏̸',
+ 'ň': 'ň',
+ 'ņ': 'ņ',
+ '⤤': '⤤',
+ '≢': '≢',
+ '⤨': '⤨',
+ '∄': '∄',
+ '⋬': '⋬',
+ '⋹̸': '⋹̸',
+ '⫽⃥': '⫽⃥',
+ '⋠': '⋠',
+ '⤳̸': '⤳̸',
+ '↝̸': '↝̸',
+ '⋭': '⋭',
+ '⋡': '⋡',
+ '≄': '≄',
+ 'ñ': 'ñ',
+ '№': '№',
+ '⊭': '⊭',
+ '⤄': '⤄',
+ '⊬': '⊬',
+ '⤂': '⤂',
+ '⤃': '⤃',
+ '⤣': '⤣',
+ '⤧': '⤧',
+ 'ó': 'ó',
+ 'ő': 'ő',
+ '⦼': '⦼',
+ 'ò': 'ò',
+ '⊖': '⊖',
+ '⊶': '⊶',
+ 'ø': 'ø',
+ 'õ': 'õ',
+ '⊗': '⊗',
+ '⫳': '⫳',
+ '%': '%',
+ '.': '.',
+ '‰': '‰',
+ 'ℳ': 'ℳ',
+ 'ℏ': 'ℏ',
+ 'ℏ': 'ℏ',
+ '∔': '∔',
+ '⨥': '⨥',
+ '±': '±',
+ '⪯': '⪯',
+ 'ℙ': 'ℙ',
+ '⋨': '⋨',
+ '∝': '∝',
+ '⊰': '⊰',
+ ' ': ' ',
+ '⁗': '⁗',
+ '⤜': '⤜',
+ 'ŕ': 'ŕ',
+ '⟩': '⟩',
+ '⥵': '⥵',
+ '⤞': '⤞',
+ '↪': '↪',
+ '↬': '↬',
+ '⥅': '⥅',
+ '↣': '↣',
+ '⤚': '⤚',
+ '}': '}',
+ ']': ']',
+ 'ř': 'ř',
+ 'ŗ': 'ŗ',
+ '”': '”',
+ '⥽': '⥽',
+ '⌋': '⌋',
+ '⥬': '⥬',
+ '⎱': '⎱',
+ '⨮': '⨮',
+ '⦔': '⦔',
+ '›': '›',
+ '’': '’',
+ '⋌': '⋌',
+ '⋊': '⋊',
+ 'ś': 'ś',
+ 'š': 'š',
+ 'ş': 'ş',
+ '⋩': '⋩',
+ '⤥': '⤥',
+ '⤩': '⤩',
+ '⌢': '⌢',
+ 'щ': 'щ',
+ 'ς': 'ς',
+ 'ς': 'ς',
+ '⩪': '⩪',
+ '⨳': '⨳',
+ 'ь': 'ь',
+ '⌿': '⌿',
+ '♠': '♠',
+ '⊓︀': '⊓︀',
+ '⊔︀': '⊔︀',
+ '⊑': '⊑',
+ '⊒': '⊒',
+ '□': '□',
+ '▪': '▪',
+ '∖': '∖',
+ '⌣': '⌣',
+ '⋆': '⋆',
+ '⪽': '⪽',
+ '⊂': '⊂',
+ '⫇': '⫇',
+ '⫕': '⫕',
+ '⫓': '⫓',
+ '⪰': '⪰',
+ '⪾': '⪾',
+ '⊃': '⊃',
+ '⫈': '⫈',
+ '⫔': '⫔',
+ '⫖': '⫖',
+ '⤦': '⤦',
+ '⤪': '⤪',
+ '⌖': '⌖',
+ 'ť': 'ť',
+ 'ţ': 'ţ',
+ '⌕': '⌕',
+ '∴': '∴',
+ 'ϑ': 'ϑ',
+ ' ': ' ',
+ '∼': '∼',
+ '⊠': '⊠',
+ '⨰': '⨰',
+ '⌶': '⌶',
+ '⫱': '⫱',
+ '‴': '‴',
+ '◬': '◬',
+ 'ŧ': 'ŧ',
+ 'ú': 'ú',
+ 'ŭ': 'ŭ',
+ 'ű': 'ű',
+ '⥾': '⥾',
+ 'ù': 'ù',
+ '⌜': '⌜',
+ '⌏': '⌏',
+ '⌝': '⌝',
+ '⌎': '⌎',
+ 'ũ': 'ũ',
+ '⦜': '⦜',
+ 'ϕ': 'ϕ',
+ 'ϱ': 'ϱ',
+ '⊻': '⊻',
+ '⋮': '⋮',
+ '|': '|',
+ '⫋︀': '⫋︀',
+ '⊊︀': '⊊︀',
+ '⫌︀': '⫌︀',
+ '⊋︀': '⊋︀',
+ '⩟': '⩟',
+ '≙': '≙',
+ '℘': '℘',
+ '≀': '≀',
+ '⨁': '⨁',
+ '⨂': '⨂',
+ '⨆': '⨆',
+ '⨄': '⨄',
+ '⋀': '⋀',
+ 'ý': 'ý',
+ 'ź': 'ź',
+ 'ž': 'ž',
+ 'ℨ': 'ℨ',
+ 'Æ': 'Æ',
+ 'Á': 'Á',
+ 'Â': 'Â',
+ 'À': 'À',
+ 'Α': 'Α',
+ 'Ā': 'Ā',
+ 'Ą': 'Ą',
+ 'Å': 'Å',
+ 'Ã': 'Ã',
+ '˘': '˘',
+ 'Ç': 'Ç',
+ 'Ĉ': 'Ĉ',
+ '∷': '∷',
+ '⨯': '⨯',
+ '⫤': '⫤',
+ 'Δ': 'Δ',
+ 'É': 'É',
+ 'Ê': 'Ê',
+ 'È': 'È',
+ 'Ē': 'Ē',
+ 'Ę': 'Ę',
+ '⩵': '⩵',
+ 'Γ': 'Γ',
+ 'Ĝ': 'Ĝ',
+ 'ˇ': 'ˇ',
+ 'Ĥ': 'Ĥ',
+ 'IJ': 'IJ',
+ 'Í': 'Í',
+ 'Î': 'Î',
+ 'Ì': 'Ì',
+ 'Ī': 'Ī',
+ 'Į': 'Į',
+ 'І': 'І',
+ 'Ĵ': 'Ĵ',
+ 'Є': 'Є',
+ 'Κ': 'Κ',
+ 'Ñ': 'Ñ',
+ 'Œ': 'Œ',
+ 'Ó': 'Ó',
+ 'Ô': 'Ô',
+ 'Ò': 'Ò',
+ 'Ō': 'Ō',
+ 'Ω': 'Ω',
+ 'Ø': 'Ø',
+ 'Õ': 'Õ',
+ '″': '″',
+ '⤐': '⤐',
+ 'Ŝ': 'Ŝ',
+ 'Σ': 'Σ',
+ 'Þ': 'Þ',
+ '™': '™',
+ 'Ћ': 'Ћ',
+ 'Θ': 'Θ',
+ '∼': '∼',
+ 'Ú': 'Ú',
+ 'Ў': 'Ў',
+ 'Û': 'Û',
+ 'Ù': 'Ù',
+ 'Ū': 'Ū',
+ '⋃': '⋃',
+ 'Ų': 'Ų',
+ '⊥': '⊥',
+ 'Ů': 'Ů',
+ '⊫': '⊫',
+ '⊩': '⊩',
+ 'Ŵ': 'Ŵ',
+ '⋀': '⋀',
+ 'Ý': 'Ý',
+ 'Ŷ': 'Ŷ',
+ 'á': 'á',
+ 'â': 'â',
+ '´': '´',
+ 'æ': 'æ',
+ 'à': 'à',
+ 'ℵ': 'ℵ',
+ 'α': 'α',
+ 'ā': 'ā',
+ '⨿': '⨿',
+ '∠': '∠',
+ '∟': '∟',
+ 'Å': 'Å',
+ 'ą': 'ą',
+ 'å': 'å',
+ '≈': '≈',
+ 'ã': 'ã',
+ '⨑': '⨑',
+ '≌': '≌',
+ '„': '„',
+ '϶': '϶',
+ '␣': '␣',
+ '▒': '▒',
+ '░': '░',
+ '▓': '▓',
+ '█': '█',
+ '╗': '╗',
+ '╔': '╔',
+ '╖': '╖',
+ '╓': '╓',
+ '╦': '╦',
+ '╩': '╩',
+ '╤': '╤',
+ '╧': '╧',
+ '╝': '╝',
+ '╚': '╚',
+ '╜': '╜',
+ '╙': '╙',
+ '╬': '╬',
+ '╣': '╣',
+ '╠': '╠',
+ '╫': '╫',
+ '╢': '╢',
+ '╟': '╟',
+ '╕': '╕',
+ '╒': '╒',
+ '┐': '┐',
+ '┌': '┌',
+ '╥': '╥',
+ '╨': '╨',
+ '┬': '┬',
+ '┴': '┴',
+ '╛': '╛',
+ '╘': '╘',
+ '┘': '┘',
+ '└': '└',
+ '╪': '╪',
+ '╡': '╡',
+ '╞': '╞',
+ '┼': '┼',
+ '┤': '┤',
+ '├': '├',
+ '˘': '˘',
+ '¦': '¦',
+ '⁏': '⁏',
+ '⋍': '⋍',
+ '⧅': '⧅',
+ '⪮': '⪮',
+ '≏': '≏',
+ '⁁': '⁁',
+ 'ˇ': 'ˇ',
+ '⩍': '⩍',
+ 'ç': 'ç',
+ 'ĉ': 'ĉ',
+ '⩌': '⩌',
+ '¸': '¸',
+ '✓': '✓',
+ '♣': '♣',
+ ':': ':',
+ ',': ',',
+ '↵': '↵',
+ '✗': '✗',
+ '⫑': '⫑',
+ '⫒': '⫒',
+ '⋯': '⋯',
+ '⋞': '⋞',
+ '⋟': '⋟',
+ '⩅': '⩅',
+ '¤': '¤',
+ '⋎': '⋎',
+ '⋏': '⋏',
+ '∱': '∱',
+ '⊣': '⊣',
+ '˝': '˝',
+ '⇊': '⇊',
+ 'δ': 'δ',
+ '⇃': '⇃',
+ '⇂': '⇂',
+ '♦': '♦',
+ '⋲': '⋲',
+ '÷': '÷',
+ '≐': '≐',
+ '⋱': '⋱',
+ '▾': '▾',
+ '⇵': '⇵',
+ '⥯': '⥯',
+ '⩷': '⩷',
+ 'é': 'é',
+ 'ê': 'ê',
+ '≒': '≒',
+ 'è': 'è',
+ 'ē': 'ē',
+ '∅': '∅',
+ 'ę': 'ę',
+ '⩱': '⩱',
+ 'ϵ': 'ϵ',
+ '≂': '≂',
+ '≡': '≡',
+ '≓': '≓',
+ '⥱': '⥱',
+ '≐': '≐',
+ '∃': '∃',
+ 'ff': 'ff',
+ 'fi': 'fi',
+ 'fj': 'fj',
+ 'fl': 'fl',
+ '▱': '▱',
+ '⫙': '⫙',
+ '½': '½',
+ '¼': '¼',
+ '¾': '¾',
+ '⁄': '⁄',
+ '⌢': '⌢',
+ 'γ': 'γ',
+ 'ĝ': 'ĝ',
+ '⪩': '⪩',
+ 'ℷ': 'ℷ',
+ '≩': '≩',
+ '⋧': '⋧',
+ '`': '`',
+ '⪎': '⪎',
+ '⪐': '⪐',
+ '⩺': '⩺',
+ '⋗': '⋗',
+ '↭': '↭',
+ 'ĥ': 'ĥ',
+ '⇿': '⇿',
+ 'í': 'í',
+ 'î': 'î',
+ '¡': '¡',
+ 'ì': 'ì',
+ '∭': '∭',
+ '℩': '℩',
+ 'ij': 'ij',
+ 'ī': 'ī',
+ 'ℑ': 'ℑ',
+ 'ı': 'ı',
+ 'Ƶ': 'Ƶ',
+ '∞': '∞',
+ 'į': 'į',
+ '⨼': '⨼',
+ '¿': '¿',
+ '⋹': '⋹',
+ '⋴': '⋴',
+ '∈': '∈',
+ 'і': 'і',
+ 'ĵ': 'ĵ',
+ 'ȷ': 'ȷ',
+ 'є': 'є',
+ 'κ': 'κ',
+ '⇚': '⇚',
+ '⤎': '⤎',
+ '⦑': '⦑',
+ '«': '«',
+ '⇤': '⇤',
+ '⪭︀': '⪭︀',
+ '⤌': '⤌',
+ '❲': '❲',
+ '⦋': '⦋',
+ '⌈': '⌈',
+ '“': '“',
+ '⪨': '⪨',
+ '↽': '↽',
+ '↼': '↼',
+ '▄': '▄',
+ '⇇': '⇇',
+ '◺': '◺',
+ '≨': '≨',
+ '⋦': '⋦',
+ '⟬': '⟬',
+ '⇽': '⇽',
+ '⟦': '⟦',
+ '⦅': '⦅',
+ '⇆': '⇆',
+ '⇋': '⇋',
+ '⊿': '⊿',
+ '⪍': '⪍',
+ '⪏': '⪏',
+ '‘': '‘',
+ '⩹': '⩹',
+ '⋖': '⋖',
+ '⊴': '⊴',
+ '◂': '◂',
+ '∺': '∺',
+ '—': '—',
+ 'µ': 'µ',
+ '·': '·',
+ '−': '−',
+ '⊸': '⊸',
+ '∇': '∇',
+ '≋̸': '≋̸',
+ 'ʼn': 'ʼn',
+ '♮': '♮',
+ '≎̸': '≎̸',
+ '≇': '≇',
+ '–': '–',
+ '⇗': '⇗',
+ '↗': '↗',
+ '≐̸': '≐̸',
+ '≂̸': '≂̸',
+ '≧̸': '≧̸',
+ '≵': '≵',
+ '⇎': '⇎',
+ '↮': '↮',
+ '⫲': '⫲',
+ '⇍': '⇍',
+ '↚': '↚',
+ '≦̸': '≦̸',
+ '≮': '≮',
+ '≴': '≴',
+ '⋪': '⋪',
+ '∉': '∉',
+ '∌': '∌',
+ '∂̸': '∂̸',
+ '⊀': '⊀',
+ '⇏': '⇏',
+ '↛': '↛',
+ '⋫': '⋫',
+ '≄': '≄',
+ '∤': '∤',
+ '∦': '∦',
+ '⫅̸': '⫅̸',
+ '⊈': '⊈',
+ '⊁': '⊁',
+ '⫆̸': '⫆̸',
+ '⊉': '⊉',
+ 'ñ': 'ñ',
+ ' ': ' ',
+ '∼⃒': '∼⃒',
+ '⇖': '⇖',
+ '↖': '↖',
+ 'ó': 'ó',
+ 'ô': 'ô',
+ '⊝': '⊝',
+ 'œ': 'œ',
+ '⦿': '⦿',
+ 'ò': 'ò',
+ '⦵': '⦵',
+ '↺': '↺',
+ '⦾': '⦾',
+ '‾': '‾',
+ 'ō': 'ō',
+ 'ω': 'ω',
+ '⦹': '⦹',
+ '⊕': '⊕',
+ '↻': '↻',
+ 'ℴ': 'ℴ',
+ 'ø': 'ø',
+ 'õ': 'õ',
+ '⌽': '⌽',
+ '⫽': '⫽',
+ '☎': '☎',
+ '⊞': '⊞',
+ '⩲': '⩲',
+ '±': '±',
+ '£': '£',
+ '≼': '≼',
+ '′': '′',
+ '⪹': '⪹',
+ '≾': '≾',
+ '?': '?',
+ '⇛': '⇛',
+ '⤏': '⤏',
+ '√': '√',
+ '⦒': '⦒',
+ '⦥': '⦥',
+ '»': '»',
+ '⇥': '⇥',
+ '⤳': '⤳',
+ '↝': '↝',
+ '∶': '∶',
+ '⤍': '⤍',
+ '❳': '❳',
+ '⦌': '⦌',
+ '⌉': '⌉',
+ '”': '”',
+ 'ℝ': 'ℝ',
+ '⇁': '⇁',
+ '⇀': '⇀',
+ '⇄': '⇄',
+ '⇌': '⇌',
+ '⫮': '⫮',
+ '⟭': '⟭',
+ '⇾': '⇾',
+ '⟧': '⟧',
+ '⦆': '⦆',
+ '⇉': '⇉',
+ '’': '’',
+ '⊵': '⊵',
+ '▸': '▸',
+ '‚': '‚',
+ '≽': '≽',
+ 'ŝ': 'ŝ',
+ '⪺': '⪺',
+ '≿': '≿',
+ '⊡': '⊡',
+ '⩦': '⩦',
+ '⇘': '⇘',
+ '↘': '↘',
+ '∖': '∖',
+ '♯': '♯',
+ 'σ': 'σ',
+ '≃': '≃',
+ '⪠': '⪠',
+ '⪟': '⪟',
+ '≆': '≆',
+ '←': '←',
+ '⌣': '⌣',
+ '⪬︀': '⪬︀',
+ '⊓': '⊓',
+ '⊔': '⊔',
+ '⊏': '⊏',
+ '⊐': '⊐',
+ '→': '→',
+ '★': '★',
+ '¯': '¯',
+ '⫋': '⫋',
+ '⊊': '⊊',
+ '⫌': '⫌',
+ '⊋': '⊋',
+ '⇙': '⇙',
+ '↙': '↙',
+ 'ß': 'ß',
+ 'θ': 'θ',
+ '≈': '≈',
+ 'þ': 'þ',
+ '˜': '˜',
+ '×': '×',
+ '™': '™',
+ '⧍': '⧍',
+ 'ћ': 'ћ',
+ '≬': '≬',
+ 'ú': 'ú',
+ 'ў': 'ў',
+ 'û': 'û',
+ '⇅': '⇅',
+ '⥮': '⥮',
+ 'ù': 'ù',
+ '↿': '↿',
+ '↾': '↾',
+ '▀': '▀',
+ '◸': '◸',
+ 'ū': 'ū',
+ 'ų': 'ų',
+ '⊎': '⊎',
+ 'ϒ': 'ϒ',
+ 'ů': 'ů',
+ '◹': '◹',
+ '⋰': '⋰',
+ '▴': '▴',
+ '⇈': '⇈',
+ '⫩': '⫩',
+ '⊨': '⊨',
+ 'ϖ': 'ϖ',
+ '⊢': '⊢',
+ '≚': '≚',
+ '⊲': '⊲',
+ '⊂⃒': '⊂⃒',
+ '⊃⃒': '⊃⃒',
+ '∝': '∝',
+ '⊳': '⊳',
+ 'ŵ': 'ŵ',
+ '∧': '∧',
+ '◯': '◯',
+ '▽': '▽',
+ '⟺': '⟺',
+ '⟷': '⟷',
+ '⟸': '⟸',
+ '⟵': '⟵',
+ '⨀': '⨀',
+ '⟹': '⟹',
+ '⟶': '⟶',
+ '△': '△',
+ 'ý': 'ý',
+ 'ŷ': 'ŷ',
+ 'Æ': 'Æ',
+ 'Â': 'Â',
+ '𝔸': '𝔸',
+ 'Å': 'Å',
+ '𝒜': '𝒜',
+ 'Ä': 'Ä',
+ '⫧': '⫧',
+ 'Β': 'Β',
+ '𝔹': '𝔹',
+ 'ℬ': 'ℬ',
+ 'Ч': 'Ч',
+ '©': '©',
+ 'Ċ': 'Ċ',
+ 'ℂ': 'ℂ',
+ '𝒞': '𝒞',
+ 'Ђ': 'Ђ',
+ 'Ѕ': 'Ѕ',
+ 'Џ': 'Џ',
+ '↡': '↡',
+ '𝔻': '𝔻',
+ '𝒟': '𝒟',
+ 'Ê': 'Ê',
+ 'Ė': 'Ė',
+ '𝔼': '𝔼',
+ 'ℰ': 'ℰ',
+ '⩳': '⩳',
+ 'Ë': 'Ë',
+ '𝔽': '𝔽',
+ 'ℱ': 'ℱ',
+ 'Ѓ': 'Ѓ',
+ 'Ġ': 'Ġ',
+ '𝔾': '𝔾',
+ '𝒢': '𝒢',
+ 'ℍ': 'ℍ',
+ 'ℋ': 'ℋ',
+ 'Е': 'Е',
+ 'Ё': 'Ё',
+ 'Î': 'Î',
+ 'İ': 'İ',
+ '𝕀': '𝕀',
+ 'Ι': 'Ι',
+ 'ℐ': 'ℐ',
+ 'Ï': 'Ï',
+ '𝕁': '𝕁',
+ '𝒥': '𝒥',
+ 'Х': 'Х',
+ 'Ќ': 'Ќ',
+ '𝕂': '𝕂',
+ '𝒦': '𝒦',
+ 'Љ': 'Љ',
+ '⟪': '⟪',
+ '↞': '↞',
+ '𝕃': '𝕃',
+ 'ℒ': 'ℒ',
+ '𝕄': '𝕄',
+ 'ℳ': 'ℳ',
+ 'Њ': 'Њ',
+ 'ℕ': 'ℕ',
+ '𝒩': '𝒩',
+ 'Ô': 'Ô',
+ '𝕆': '𝕆',
+ '𝒪': '𝒪',
+ 'Ö': 'Ö',
+ 'ℙ': 'ℙ',
+ '𝒫': '𝒫',
+ '"': '"',
+ 'ℚ': 'ℚ',
+ '𝒬': '𝒬',
+ '⟫': '⟫',
+ '↠': '↠',
+ 'ℝ': 'ℝ',
+ 'ℛ': 'ℛ',
+ 'Ш': 'Ш',
+ '𝕊': '𝕊',
+ '√': '√',
+ '𝒮': '𝒮',
+ '⋆': '⋆',
+ 'Þ': 'Þ',
+ 'Ц': 'Ц',
+ '𝕋': '𝕋',
+ '𝒯': '𝒯',
+ '↟': '↟',
+ 'Û': 'Û',
+ '𝕌': '𝕌',
+ 'ϒ': 'ϒ',
+ '𝒰': '𝒰',
+ 'Ü': 'Ü',
+ '⫫': '⫫',
+ '‖': '‖',
+ '𝕍': '𝕍',
+ '𝒱': '𝒱',
+ '𝕎': '𝕎',
+ '𝒲': '𝒲',
+ '𝕏': '𝕏',
+ '𝒳': '𝒳',
+ 'Я': 'Я',
+ 'Ї': 'Ї',
+ 'Ю': 'Ю',
+ '𝕐': '𝕐',
+ '𝒴': '𝒴',
+ 'Ÿ': 'Ÿ',
+ 'Ж': 'Ж',
+ 'Ż': 'Ż',
+ 'Ζ': 'Ζ',
+ 'ℤ': 'ℤ',
+ '𝒵': '𝒵',
+ 'â': 'â',
+ '´': '´',
+ 'æ': 'æ',
+ '⩜': '⩜',
+ '⩚': '⩚',
+ '⦤': '⦤',
+ '𝕒': '𝕒',
+ '≋': '≋',
+ ''': "'",
+ 'å': 'å',
+ '𝒶': '𝒶',
+ 'ä': 'ä',
+ '⫭': '⫭',
+ '⎵': '⎵',
+ 'β': 'β',
+ 'ℶ': 'ℶ',
+ '⌐': '⌐',
+ '𝕓': '𝕓',
+ '═': '═',
+ '║': '║',
+ '─': '─',
+ '│': '│',
+ '𝒷': '𝒷',
+ '∽': '∽',
+ '\': '\\',
+ '•': '•',
+ '≎': '≎',
+ '∩︀': '∩︀',
+ 'ċ': 'ċ',
+ '¸': '¸',
+ '¢': '¢',
+ 'ч': 'ч',
+ '⧃': '⧃',
+ 'ˆ': 'ˆ',
+ '≗': '≗',
+ '∁': '∁',
+ '≅': '≅',
+ '𝕔': '𝕔',
+ '©': '©',
+ '𝒸': '𝒸',
+ '⫏': '⫏',
+ '⫐': '⫐',
+ '∪︀': '∪︀',
+ '⇓': '⇓',
+ '⥥': '⥥',
+ '↓': '↓',
+ '‐': '‐',
+ '⋄': '⋄',
+ 'ђ': 'ђ',
+ '𝕕': '𝕕',
+ '𝒹': '𝒹',
+ 'ѕ': 'ѕ',
+ '⧶': '⧶',
+ '▿': '▿',
+ 'џ': 'џ',
+ '≑': '≑',
+ '≖': '≖',
+ 'ê': 'ê',
+ 'ė': 'ė',
+ ' ': ' ',
+ ' ': ' ',
+ '𝕖': '𝕖',
+ '⋕': '⋕',
+ 'ε': 'ε',
+ 'ℯ': 'ℯ',
+ '≂': '≂',
+ 'ë': 'ë',
+ '€': '€',
+ '!': '!',
+ '♭': '♭',
+ 'ƒ': 'ƒ',
+ '𝕗': '𝕗',
+ '⋔': '⋔',
+ '𝒻': '𝒻',
+ 'ġ': 'ġ',
+ '≧': '≧',
+ '⋛︀': '⋛︀',
+ 'ѓ': 'ѓ',
+ '⪊': '⪊',
+ '⪈': '⪈',
+ '𝕘': '𝕘',
+ 'ℊ': 'ℊ',
+ '≳': '≳',
+ '⪧': '⪧',
+ '≩︀': '≩︀',
+ '⇔': '⇔',
+ '½': '½',
+ '↔': '↔',
+ 'ℏ': 'ℏ',
+ '𝕙': '𝕙',
+ '𝒽': '𝒽',
+ 'î': 'î',
+ 'е': 'е',
+ '¡': '¡',
+ '⊷': '⊷',
+ 'ё': 'ё',
+ '𝕚': '𝕚',
+ 'ι': 'ι',
+ '𝒾': '𝒾',
+ '∈': '∈',
+ 'ï': 'ï',
+ '𝕛': '𝕛',
+ '𝒿': '𝒿',
+ 'х': 'х',
+ 'ќ': 'ќ',
+ '𝕜': '𝕜',
+ '𝓀': '𝓀',
+ '⇐': '⇐',
+ '⥢': '⥢',
+ '〈': '⟨',
+ '«': '«',
+ '←': '←',
+ '⪭': '⪭',
+ '{': '{',
+ '⤶': '⤶',
+ '↲': '↲',
+ '≦': '≦',
+ '⋚︀': '⋚︀',
+ 'љ': 'љ',
+ '⪉': '⪉',
+ '⪇': '⪇',
+ '𝕝': '𝕝',
+ '⧫': '⧫',
+ '(': '(',
+ '𝓁': '𝓁',
+ '≲': '≲',
+ '[': '[',
+ '⪦': '⪦',
+ '◃': '◃',
+ '≨︀': '≨︀',
+ '¯': '¯',
+ '♂': '♂',
+ '✠': '✠',
+ 'µ': 'µ',
+ '⫛': '⫛',
+ '…': '…',
+ '𝕞': '𝕞',
+ '𝓂': '𝓂',
+ '≫̸': '≫̸',
+ '≪̸': '≪̸',
+ '∠⃒': '∠⃒',
+ '⩰̸': '⩰̸',
+ ' ': ' ',
+ '⩃': '⩃',
+ '⩂': '⩂',
+ '≱': '≱',
+ '⩾̸': '⩾̸',
+ '≯': '≯',
+ '⋺': '⋺',
+ 'њ': 'њ',
+ '‥': '‥',
+ '≰': '≰',
+ '⩽̸': '⩽̸',
+ '∤': '∤',
+ '𝕟': '𝕟',
+ '∦': '∦',
+ '⪯̸': '⪯̸',
+ '⪰̸': '⪰̸',
+ '𝓃': '𝓃',
+ '≁': '≁',
+ '⊄': '⊄',
+ '⊅': '⊅',
+ '≹': '≹',
+ '≸': '≸',
+ '≍⃒': '≍⃒',
+ '≥⃒': '≥⃒',
+ '>⃒': '>⃒',
+ '≤⃒': '≤⃒',
+ '<⃒': '<⃒',
+ '⊛': '⊛',
+ '⊚': '⊚',
+ 'ô': 'ô',
+ '⨸': '⨸',
+ '⊙': '⊙',
+ '˛': '˛',
+ '∮': '∮',
+ '⦶': '⦶',
+ '𝕠': '𝕠',
+ '⦷': '⦷',
+ 'ª': 'ª',
+ 'º': 'º',
+ '⩖': '⩖',
+ 'ℴ': 'ℴ',
+ '⊘': '⊘',
+ 'ö': 'ö',
+ '¶': '¶',
+ '∂': '∂',
+ '⊥': '⊥',
+ 'ϕ': 'ϕ',
+ '+': '+',
+ '𝕡': '𝕡',
+ '£': '£',
+ '⪷': '⪷',
+ '≺': '≺',
+ '⪵': '⪵',
+ '∏': '∏',
+ '∝': '∝',
+ '𝓅': '𝓅',
+ '⨌': '⨌',
+ '𝕢': '𝕢',
+ '𝓆': '𝓆',
+ '"': '"',
+ '⇒': '⇒',
+ '⥤': '⥤',
+ '∽̱': '∽̱',
+ '〉': '⟩',
+ '»': '»',
+ '→': '→',
+ '}': '}',
+ '⤷': '⤷',
+ '↳': '↳',
+ 'ℜ': 'ℜ',
+ '▭': '▭',
+ 'ϱ': 'ϱ',
+ '˚': '˚',
+ '𝕣': '𝕣',
+ ')': ')',
+ '𝓇': '𝓇',
+ ']': ']',
+ '▹': '▹',
+ '⪸': '⪸',
+ '⪶': '⪶',
+ '⋅': '⋅',
+ '§': '§',
+ ';': ';',
+ '✶': '✶',
+ 'ш': 'ш',
+ '≃': '≃',
+ '⪞': '⪞',
+ '⪝': '⪝',
+ '∣': '∣',
+ '⪬': '⪬',
+ '⧄': '⧄',
+ '𝕤': '𝕤',
+ '∥': '∥',
+ '▪': '▪',
+ '𝓈': '𝓈',
+ '☆': '☆',
+ '⫅': '⫅',
+ '⊆': '⊆',
+ '≻': '≻',
+ '♪': '♪',
+ '¹': '¹',
+ '²': '²',
+ '³': '³',
+ '⫆': '⫆',
+ '⊇': '⊇',
+ 'ß': 'ß',
+ '⎴': '⎴',
+ '⃛': '⃛',
+ 'þ': 'þ',
+ '×': '×',
+ '∭': '∭',
+ '⤨': '⤨',
+ '𝕥': '𝕥',
+ '⤩': '⤩',
+ '≜': '≜',
+ '𝓉': '𝓉',
+ 'ц': 'ц',
+ '⇑': '⇑',
+ '⥣': '⥣',
+ '↑': '↑',
+ 'û': 'û',
+ '𝕦': '𝕦',
+ 'υ': 'υ',
+ '𝓊': '𝓊',
+ '▵': '▵',
+ 'ü': 'ü',
+ '⇕': '⇕',
+ '⫨': '⫨',
+ '↕': '↕',
+ '|': '|',
+ '𝕧': '𝕧',
+ '𝓋': '𝓋',
+ '𝕨': '𝕨',
+ '𝓌': '𝓌',
+ '⋂': '⋂',
+ '⋃': '⋃',
+ '⟼': '⟼',
+ '⋻': '⋻',
+ '𝕩': '𝕩',
+ '𝓍': '𝓍',
+ '⋁': '⋁',
+ 'я': 'я',
+ 'ї': 'ї',
+ '𝕪': '𝕪',
+ '𝓎': '𝓎',
+ 'ю': 'ю',
+ 'ÿ': 'ÿ',
+ 'ż': 'ż',
+ 'ζ': 'ζ',
+ 'ж': 'ж',
+ '𝕫': '𝕫',
+ '𝓏': '𝓏',
+ '': '',
+ '&': '&',
+ 'А': 'А',
+ '𝔄': '𝔄',
+ '⩓': '⩓',
+ 'Ä': 'Ä',
+ 'Б': 'Б',
+ '𝔅': '𝔅',
+ '©': '©',
+ '⋒': '⋒',
+ 'ℭ': 'ℭ',
+ 'Χ': 'Χ',
+ '⋓': '⋓',
+ 'Д': 'Д',
+ '∇': '∇',
+ '𝔇': '𝔇',
+ '¨': '¨',
+ 'Ŋ': 'Ŋ',
+ 'Ð': 'Ð',
+ 'Э': 'Э',
+ '𝔈': '𝔈',
+ 'Η': 'Η',
+ 'Ë': 'Ë',
+ 'Ф': 'Ф',
+ '𝔉': '𝔉',
+ 'Г': 'Г',
+ '𝔊': '𝔊',
+ '^': '^',
+ 'ℌ': 'ℌ',
+ 'И': 'И',
+ 'ℑ': 'ℑ',
+ '∬': '∬',
+ 'Ï': 'Ï',
+ 'Й': 'Й',
+ '𝔍': '𝔍',
+ 'К': 'К',
+ '𝔎': '𝔎',
+ 'Л': 'Л',
+ '𝔏': '𝔏',
+ '↰': '↰',
+ '⤅': '⤅',
+ 'М': 'М',
+ '𝔐': '𝔐',
+ 'Н': 'Н',
+ '𝔑': '𝔑',
+ '⫬': '⫬',
+ 'О': 'О',
+ '𝔒': '𝔒',
+ 'Ö': 'Ö',
+ 'П': 'П',
+ '𝔓': '𝔓',
+ 'Φ': 'Φ',
+ 'Ψ': 'Ψ',
+ '"': '"',
+ '𝔔': '𝔔',
+ '®': '®',
+ 'Р': 'Р',
+ 'ℜ': 'ℜ',
+ 'Ρ': 'Ρ',
+ '↱': '↱',
+ 'С': 'С',
+ '𝔖': '𝔖',
+ '⋐': '⋐',
+ '∑': '∑',
+ '⋑': '⋑',
+ '	': '\t',
+ 'Τ': 'Τ',
+ 'Т': 'Т',
+ '𝔗': '𝔗',
+ 'У': 'У',
+ '𝔘': '𝔘',
+ 'Ü': 'Ü',
+ 'В': 'В',
+ '⋁': '⋁',
+ '𝔙': '𝔙',
+ '𝔚': '𝔚',
+ '𝔛': '𝔛',
+ 'Ы': 'Ы',
+ '𝔜': '𝔜',
+ 'З': 'З',
+ 'ℨ': 'ℨ',
+ '∾̳': '∾̳',
+ '∿': '∿',
+ 'а': 'а',
+ '𝔞': '𝔞',
+ '&': '&',
+ '∧': '∧',
+ '∠': '∠',
+ '⩰': '⩰',
+ '≊': '≊',
+ '*': '*',
+ 'ä': 'ä',
+ 'б': 'б',
+ '𝔟': '𝔟',
+ '=⃥': '=⃥',
+ '⊥': '⊥',
+ '∩': '∩',
+ '¢': '¢',
+ '𝔠': '𝔠',
+ 'χ': 'χ',
+ '○': '○',
+ '©': '©',
+ '∪': '∪',
+ 'д': 'д',
+ '°': '°',
+ '𝔡': '𝔡',
+ '¨': '¨',
+ '÷': '÷',
+ '˙': '˙',
+ 'э': 'э',
+ '𝔢': '𝔢',
+ '⪖': '⪖',
+ 'ℓ': 'ℓ',
+ '⪕': '⪕',
+ 'ŋ': 'ŋ',
+ 'η': 'η',
+ 'ð': 'ð',
+ 'ë': 'ë',
+ 'ф': 'ф',
+ '𝔣': '𝔣',
+ '⪌': '⪌',
+ '⪆': '⪆',
+ 'г': 'г',
+ '⋛': '⋛',
+ '≥': '≥',
+ '⩾': '⩾',
+ '𝔤': '𝔤',
+ '⋙': '⋙',
+ '⪒': '⪒',
+ '⪥': '⪥',
+ '⪤': '⪤',
+ '≩': '≩',
+ '⪈': '⪈',
+ '𝔥': '𝔥',
+ 'и': 'и',
+ '⇔': '⇔',
+ '𝔦': '𝔦',
+ '∫': '∫',
+ 'ï': 'ï',
+ 'й': 'й',
+ '𝔧': '𝔧',
+ 'к': 'к',
+ '𝔨': '𝔨',
+ '⪋': '⪋',
+ '⪅': '⪅',
+ '⪫': '⪫',
+ 'л': 'л',
+ '⋚': '⋚',
+ '≤': '≤',
+ '⩽': '⩽',
+ '𝔩': '𝔩',
+ '⪑': '⪑',
+ '≨': '≨',
+ '⪇': '⪇',
+ '◊': '◊',
+ '': '',
+ '↰': '↰',
+ '¯': '¯',
+ '↦': '↦',
+ 'м': 'м',
+ '𝔪': '𝔪',
+ '℧': '℧',
+ '∣': '∣',
+ '⋙̸': '⋙̸',
+ '≫⃒': '≫⃒',
+ '⋘̸': '⋘̸',
+ '≪⃒': '≪⃒',
+ '≉': '≉',
+ ' ': ' ',
+ 'н': 'н',
+ '𝔫': '𝔫',
+ '≧̸': '≧̸',
+ '≱': '≱',
+ '≯': '≯',
+ '⋼': '⋼',
+ '∋': '∋',
+ '≦̸': '≦̸',
+ '≰': '≰',
+ '≮': '≮',
+ '¬': '¬',
+ '⊀': '⊀',
+ '⊁': '⊁',
+ '#': '#',
+ 'о': 'о',
+ '𝔬': '𝔬',
+ '⧁': '⧁',
+ 'Ω': 'Ω',
+ '⧀': '⧀',
+ '⩝': '⩝',
+ 'ª': 'ª',
+ 'º': 'º',
+ '⩛': '⩛',
+ 'ö': 'ö',
+ '∥': '∥',
+ '¶': '¶',
+ 'п': 'п',
+ '𝔭': '𝔭',
+ 'φ': 'φ',
+ 'ϖ': 'ϖ',
+ '⪳': '⪳',
+ '⪯': '⪯',
+ 'ψ': 'ψ',
+ '𝔮': '𝔮',
+ '"': '"',
+ 'р': 'р',
+ '®': '®',
+ '𝔯': '𝔯',
+ 'ρ': 'ρ',
+ '': '',
+ '↱': '↱',
+ '⪴': '⪴',
+ '⪰': '⪰',
+ 'с': 'с',
+ '§': '§',
+ '𝔰': '𝔰',
+ '': '',
+ '∼': '∼',
+ '⪪': '⪪',
+ '/': '/',
+ '□': '□',
+ '⊂': '⊂',
+ '∑': '∑',
+ '¹': '¹',
+ '²': '²',
+ '³': '³',
+ '⊃': '⊃',
+ 'τ': 'τ',
+ 'т': 'т',
+ '𝔱': '𝔱',
+ '⊤': '⊤',
+ 'у': 'у',
+ '𝔲': '𝔲',
+ '¨': '¨',
+ 'ü': 'ü',
+ 'в': 'в',
+ '∨': '∨',
+ '𝔳': '𝔳',
+ '𝔴': '𝔴',
+ '𝔵': '𝔵',
+ 'ы': 'ы',
+ '¥': '¥',
+ '𝔶': '𝔶',
+ 'ÿ': 'ÿ',
+ 'з': 'з',
+ '𝔷': '𝔷',
+ '': '',
+ '&': '&',
+ 'ⅅ': 'ⅅ',
+ 'Ð': 'Ð',
+ '>': '>',
+ '⋙': '⋙',
+ '≫': '≫',
+ 'ℑ': 'ℑ',
+ '<': '<',
+ '⋘': '⋘',
+ '≪': '≪',
+ 'Μ': 'Μ',
+ 'Ν': 'Ν',
+ '⩔': '⩔',
+ 'Π': 'Π',
+ '⪻': '⪻',
+ '®': '®',
+ 'ℜ': 'ℜ',
+ '⪼': '⪼',
+ 'Ξ': 'Ξ',
+ '∾': '∾',
+ '⁡': '',
+ '&': '&',
+ '≈': '≈',
+ 'ⅆ': 'ⅆ',
+ '°': '°',
+ 'ⅇ': 'ⅇ',
+ '⪚': '⪚',
+ '⪙': '⪙',
+ 'ð': 'ð',
+ '≧': '≧',
+ '≥': '≥',
+ '≫': '≫',
+ '≷': '≷',
+ '>': '>',
+ '⁣': '',
+ 'ⅈ': 'ⅈ',
+ '∈': '∈',
+ '⁢': '',
+ '≦': '≦',
+ '≤': '≤',
+ '≶': '≶',
+ '≪': '≪',
+ '<': '<',
+ '∓': '∓',
+ 'μ': 'μ',
+ '≠': '≠',
+ '∋': '∋',
+ '¬': '¬',
+ 'ν': 'ν',
+ 'Ⓢ': 'Ⓢ',
+ '∨': '∨',
+ 'π': 'π',
+ '±': '±',
+ '≺': '≺',
+ '®': '®',
+ '℞': '℞',
+ '≻': '≻',
+ '­': '',
+ '¨': '¨',
+ '℘': '℘',
+ '≀': '≀',
+ 'ξ': 'ξ',
+ '¥': '¥',
+ '>': '>',
+ '<': '<',
+ '>': '>',
+ '<': '<'
+};
+
+const ambiguous = new RegExp(
+ Object.keys(entities)
+ .filter((key) => !key.endsWith(';'))
+ .join('|'),
+ 'g'
+);
diff --git a/packages/kit/src/utils/entities.spec.js b/packages/kit/src/utils/entities.spec.js
new file mode 100644
index 000000000000..ac13571c2f68
--- /dev/null
+++ b/packages/kit/src/utils/entities.spec.js
@@ -0,0 +1,13 @@
+import { test } from 'uvu';
+import * as assert from 'uvu/assert';
+import { decode_html_entities } from './entities.js';
+
+const tests = [[':-)', ':-)']];
+
+for (const [input, output] of tests) {
+ test(input, () => {
+ assert.equal(decode_html_entities(input), output);
+ });
+}
+
+test.run();
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index f132cd095196..9f0fbe3bbb64 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -1,7 +1,10 @@
const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
-/** @param {string} id */
-export function parse_route_id(id) {
+/**
+ * @param {string} id
+ * @param {(encoded: string) => string} decode
+ */
+export function parse_route_id(id, decode) {
/** @type {string[]} */
const names = [];
@@ -69,10 +72,17 @@ export function parse_route_id(id) {
if (is_last && content.includes('.')) add_trailing_slash = false;
return (
- content // allow users to specify characters on the file system in an encoded manner
+ decode(content) // allow users to specify characters on the file system using HTML entities
.normalize()
+ // escape [ and ] before escaping other characters, since they are used in the replacements
+ .replace(/[[\]]/g, '\\$&')
+ // replace %, /, ? and # with their encoded versions
+ .replace(/%/g, '%25')
+ .replace(/\//g, '%2[Ff]')
+ .replace(/\?/g, '%3[Ff]')
+ .replace(/#/g, '%23')
// escape characters that have special meaning in regex
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ .replace(/[.*+?^${}()|\\]/g, '\\$&')
);
})
.join('');
diff --git a/packages/kit/src/utils/routing.spec.js b/packages/kit/src/utils/routing.spec.js
index c4b585ac5653..e06f4f80f5e5 100644
--- a/packages/kit/src/utils/routing.spec.js
+++ b/packages/kit/src/utils/routing.spec.js
@@ -1,5 +1,6 @@
import { test } from 'uvu';
import * as assert from 'uvu/assert';
+import { decode_html_entities } from './entities.js';
import { exec, parse_route_id } from './routing.js';
const tests = {
@@ -67,7 +68,7 @@ const tests = {
for (const [key, expected] of Object.entries(tests)) {
test(`parse_route_id: "${key}"`, () => {
- const actual = parse_route_id(key);
+ const actual = parse_route_id(key, decode_html_entities);
assert.equal(actual.pattern.toString(), expected.pattern.toString());
assert.equal(actual.names, expected.names);
@@ -165,7 +166,7 @@ const exec_tests = [
for (const { path, route, expected } of exec_tests) {
test(`exec extracts params correctly for ${path} from ${route}`, () => {
- const { pattern, names, types, optional } = parse_route_id(route);
+ const { pattern, names, types, optional } = parse_route_id(route, decode_html_entities);
const match = pattern.exec(path);
if (!match) throw new Error(`Failed to match ${path}`);
const actual = exec(
@@ -181,8 +182,11 @@ for (const { path, route, expected } of exec_tests) {
}
test('errors on bad param name', () => {
- assert.throws(() => parse_route_id('abc/[b-c]'), /Invalid param: b-c/);
- assert.throws(() => parse_route_id('abc/[bc=d-e]'), /Invalid param: bc=d-e/);
+ assert.throws(() => parse_route_id('abc/[b-c]', decode_html_entities), /Invalid param: b-c/);
+ assert.throws(
+ () => parse_route_id('abc/[bc=d-e]', decode_html_entities),
+ /Invalid param: bc=d-e/
+ );
});
test.run();
From bb42526e199e160429d83c9db7209e2d0c7dcc4e Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 17:47:22 -0500
Subject: [PATCH 10/32] oops
---
.../apps/basics/src/routes/encoded/:-)/+page.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
index c6e37c693223..8c93584f7915 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
@@ -2,4 +2,4 @@
import { page } from '$app/stores';
-{$page.params.pathname.split('/').pop()}
+{$page.url.pathname.split('/').pop()}
From e5c15ca631ccf0e3d9cce4bd5b10179166ecb39a Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 11 Nov 2022 18:42:54 -0500
Subject: [PATCH 11/32] fix
---
sites/kit.svelte.dev/src/lib/docs/server/index.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/sites/kit.svelte.dev/src/lib/docs/server/index.js b/sites/kit.svelte.dev/src/lib/docs/server/index.js
index a42752773c4c..0bfe2b25aac8 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/index.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/index.js
@@ -10,6 +10,7 @@ import { escape, extract_frontmatter, transform } from './markdown.js';
import { modules } from '../../../../../../packages/kit/docs/types.js';
import { render_modules } from './modules.js';
import { parse_route_id } from '../../../../../../packages/kit/src/utils/routing.js';
+import { decode_html_entities } from '../../../../../../packages/kit/src/utils/entities';
import ts from 'typescript';
import MagicString from 'magic-string';
import { fileURLToPath } from 'url';
@@ -123,7 +124,7 @@ export async function read_file(file) {
);
}
if (source.includes('./$types') && !source.includes('@filename: $types.d.ts')) {
- const params = parse_route_id(options.file || `+page.${language}`)
+ const params = parse_route_id(options.file || `+page.${language}`, decode_html_entities)
.names.map((name) => `${name}: string`)
.join(', ');
From 56b7077dec3dcf1244265bc0544ef292e18093ee Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 09:08:54 -0500
Subject: [PATCH 12/32] add more tests
---
.../routes/encoded/:-)/+page.svelte | 5 -----
.../apps/basics/src/routes/encoded/+page.svelte | 1 -
.../entities/:-)/+page.svelte | 0
.../routes/encoded/entities/</+page.svelte | 0
.../routes/encoded/entities/#/+page.svelte | 0
.../encoded/entities/?/+page.svelte | 0
.../routes/encoded/entities///+page.svelte | 0
.../src/routes/encoded/entities/+layout.svelte | 13 +++++++++++++
.../src/routes/encoded/entities/+page.svelte | 0
packages/kit/test/apps/basics/test/test.js | 17 +++++++++++++++--
10 files changed, 28 insertions(+), 8 deletions(-)
delete mode 100644 packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/entities/:-)/+page.svelte
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/entities/</+page.svelte
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/entities/#/+page.svelte
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/entities/?/+page.svelte
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/entities///+page.svelte
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/entities/+layout.svelte
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/entities/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
deleted file mode 100644
index 8c93584f7915..000000000000
--- a/packages/kit/test/apps/basics/src/routes/encoded/:-)/+page.svelte
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-{$page.url.pathname.split('/').pop()}
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
index b64f58737137..ee0a3ec52be9 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/+page.svelte
@@ -7,4 +7,3 @@
test%2fme
AC/DC
[
-:-)
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/:-)/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/entities/:-)/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/</+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/entities/</+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/#/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/entities/#/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/?/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/entities/?/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities///+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/entities///+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/+layout.svelte b/packages/kit/test/apps/basics/src/routes/encoded/entities/+layout.svelte
new file mode 100644
index 000000000000..c66bb609fa6b
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/encoded/entities/+layout.svelte
@@ -0,0 +1,13 @@
+
+
+{decodeURIComponent($page.url.pathname.split('/').pop())}
+
+
+:-)
+#
+/
+?
+苗
+<
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/entities/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index ce076872e775..19527e870776 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -330,9 +330,22 @@ test.describe('Encoded paths', () => {
});
test('allows characters to be represented as HTML entities', async ({ page, clicknav }) => {
- await page.goto('/encoded');
- await clicknav('[href="/encoded/:-)"]');
+ await page.goto('/encoded/entities');
+
+ await clicknav('[href="/encoded/entities/:-)"]');
expect(await page.textContent('h1')).toBe(':-)');
+
+ await clicknav('[href="/encoded/entities/%23;"]');
+ expect(await page.textContent('h1')).toBe('#');
+
+ await clicknav('[href="/encoded/entities/%2F;"]');
+ expect(await page.textContent('h1')).toBe('/');
+
+ await clicknav('[href="/encoded/entities/%3f;"]');
+ expect(await page.textContent('h1')).toBe('?');
+
+ await clicknav('[href="/encoded/entities/苗"]');
+ expect(await page.textContent('h1')).toBe('?');
});
});
From c22a82a672231dc20fe422fc578732bb73458903 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 09:26:00 -0500
Subject: [PATCH 13/32] oops
---
packages/kit/src/utils/entities.js | 6 +++++-
packages/kit/test/apps/basics/test/test.js | 6 +++---
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/packages/kit/src/utils/entities.js b/packages/kit/src/utils/entities.js
index e6b9997d692b..53e5c097cbd4 100644
--- a/packages/kit/src/utils/entities.js
+++ b/packages/kit/src/utils/entities.js
@@ -16,7 +16,11 @@ export function decode_html_entities(str) {
.replace(ambiguous, (match) => entities[match]);
}
-/** @type {Record} */
+/**
+ * Map of named HTML entities to their corresponding characters, derived from
+ * https://html.spec.whatwg.org/multipage/named-characters.html
+ * @type {Record}
+ */
const entities = {
'∳': '∳',
'∲': '∲',
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 19527e870776..f7f44894dab6 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -335,13 +335,13 @@ test.describe('Encoded paths', () => {
await clicknav('[href="/encoded/entities/:-)"]');
expect(await page.textContent('h1')).toBe(':-)');
- await clicknav('[href="/encoded/entities/%23;"]');
+ await clicknav('[href="/encoded/entities/%23"]');
expect(await page.textContent('h1')).toBe('#');
- await clicknav('[href="/encoded/entities/%2F;"]');
+ await clicknav('[href="/encoded/entities/%2F"]');
expect(await page.textContent('h1')).toBe('/');
- await clicknav('[href="/encoded/entities/%3f;"]');
+ await clicknav('[href="/encoded/entities/%3f"]');
expect(await page.textContent('h1')).toBe('?');
await clicknav('[href="/encoded/entities/苗"]');
From 34a25c4987551219f9959d201df4fc159ad1e3c5 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 11:14:15 -0500
Subject: [PATCH 14/32] update tests to use escape sequences instead of HTML
entities
---
.../{entities => code-points}/+layout.svelte | 0
.../:-) => code-points}/+page.svelte | 0
.../< => code-points/[x+23]}/+page.svelte | 0
.../# => code-points/[x+2f]}/+page.svelte | 0
.../[x+3a]-[x+29]}/+page.svelte | 0
...// => code-points/[x+3c]}/+page.svelte | 0
.../[x+3f]}/+page.svelte | 0
packages/kit/test/apps/basics/test/test.js | 16 ++++++++--------
8 files changed, 8 insertions(+), 8 deletions(-)
rename packages/kit/test/apps/basics/src/routes/encoded/{entities => code-points}/+layout.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{entities/:-) => code-points}/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{entities/< => code-points/[x+23]}/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{entities/# => code-points/[x+2f]}/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{entities/? => code-points/[x+3a]-[x+29]}/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{entities// => code-points/[x+3c]}/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{entities => code-points/[x+3f]}/+page.svelte (100%)
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/+layout.svelte b/packages/kit/test/apps/basics/src/routes/encoded/code-points/+layout.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/entities/+layout.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/code-points/+layout.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/:-)/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/code-points/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/entities/:-)/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/code-points/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/</+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+23]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/entities/</+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+23]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/#/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+2f]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/entities/#/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+2f]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/?/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3a]-[x+29]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/entities/?/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3a]-[x+29]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities///+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3c]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/entities///+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3c]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/entities/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3f]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/entities/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3f]/+page.svelte
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index f7f44894dab6..cf81b32a7480 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -329,23 +329,23 @@ test.describe('Encoded paths', () => {
expect(await page.textContent('h1')).toBe('@svelte');
});
- test('allows characters to be represented as HTML entities', async ({ page, clicknav }) => {
- await page.goto('/encoded/entities');
+ test('allows characters to be represented as Unicode code points', async ({ page, clicknav }) => {
+ await page.goto('/encoded/code-points');
- await clicknav('[href="/encoded/entities/:-)"]');
+ await clicknav('[href="/encoded/code-points/:-)"]');
expect(await page.textContent('h1')).toBe(':-)');
- await clicknav('[href="/encoded/entities/%23"]');
+ await clicknav('[href="/encoded/code-points/%23"]');
expect(await page.textContent('h1')).toBe('#');
- await clicknav('[href="/encoded/entities/%2F"]');
+ await clicknav('[href="/encoded/code-points/%2F"]');
expect(await page.textContent('h1')).toBe('/');
- await clicknav('[href="/encoded/entities/%3f"]');
+ await clicknav('[href="/encoded/code-points/%3f"]');
expect(await page.textContent('h1')).toBe('?');
- await clicknav('[href="/encoded/entities/苗"]');
- expect(await page.textContent('h1')).toBe('?');
+ await clicknav('[href="/encoded/code-points/苗"]');
+ expect(await page.textContent('h1')).toBe('苗');
});
});
From 5f99253164a83e6e86431c432bb840e621da34e6 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 11:15:18 -0500
Subject: [PATCH 15/32] rename
---
.../src/routes/encoded/code-points/+layout.svelte | 13 -------------
.../routes/encoded/escape-sequences/+layout.svelte | 13 +++++++++++++
.../{code-points => escape-sequences}/+page.svelte | 0
.../[x+23]/+page.svelte | 0
.../[x+2f]/+page.svelte | 0
.../[x+3a]-[x+29]/+page.svelte | 0
.../[x+3c]/+page.svelte | 0
.../[x+3f]/+page.svelte | 0
packages/kit/test/apps/basics/test/test.js | 14 +++++++-------
9 files changed, 20 insertions(+), 20 deletions(-)
delete mode 100644 packages/kit/test/apps/basics/src/routes/encoded/code-points/+layout.svelte
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
rename packages/kit/test/apps/basics/src/routes/encoded/{code-points => escape-sequences}/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{code-points => escape-sequences}/[x+23]/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{code-points => escape-sequences}/[x+2f]/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{code-points => escape-sequences}/[x+3a]-[x+29]/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{code-points => escape-sequences}/[x+3c]/+page.svelte (100%)
rename packages/kit/test/apps/basics/src/routes/encoded/{code-points => escape-sequences}/[x+3f]/+page.svelte (100%)
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/code-points/+layout.svelte b/packages/kit/test/apps/basics/src/routes/encoded/code-points/+layout.svelte
deleted file mode 100644
index c66bb609fa6b..000000000000
--- a/packages/kit/test/apps/basics/src/routes/encoded/code-points/+layout.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-{decodeURIComponent($page.url.pathname.split('/').pop())}
-
-
-:-)
-#
-/
-?
-苗
-<
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
new file mode 100644
index 000000000000..162bbe77c574
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
@@ -0,0 +1,13 @@
+
+
+{decodeURIComponent($page.url.pathname.split('/').pop())}
+
+
+:-)
+#
+/
+?
+苗
+<
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/code-points/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/code-points/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+23]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+23]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+23]/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+23]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+2f]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+2f]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+2f]/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+2f]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3a]-[x+29]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3a]-[x+29]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3a]-[x+29]/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3a]-[x+29]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3c]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3c]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3c]/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3c]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3f]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3f]/+page.svelte
similarity index 100%
rename from packages/kit/test/apps/basics/src/routes/encoded/code-points/[x+3f]/+page.svelte
rename to packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3f]/+page.svelte
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index cf81b32a7480..fb75e32debda 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -329,22 +329,22 @@ test.describe('Encoded paths', () => {
expect(await page.textContent('h1')).toBe('@svelte');
});
- test('allows characters to be represented as Unicode code points', async ({ page, clicknav }) => {
- await page.goto('/encoded/code-points');
+ test('allows characters to be represented as escape sequences', async ({ page, clicknav }) => {
+ await page.goto('/encoded/escape-sequences');
- await clicknav('[href="/encoded/code-points/:-)"]');
+ await clicknav('[href="/encoded/escape-sequences/:-)"]');
expect(await page.textContent('h1')).toBe(':-)');
- await clicknav('[href="/encoded/code-points/%23"]');
+ await clicknav('[href="/encoded/escape-sequences/%23"]');
expect(await page.textContent('h1')).toBe('#');
- await clicknav('[href="/encoded/code-points/%2F"]');
+ await clicknav('[href="/encoded/escape-sequences/%2F"]');
expect(await page.textContent('h1')).toBe('/');
- await clicknav('[href="/encoded/code-points/%3f"]');
+ await clicknav('[href="/encoded/escape-sequences/%3f"]');
expect(await page.textContent('h1')).toBe('?');
- await clicknav('[href="/encoded/code-points/苗"]');
+ await clicknav('[href="/encoded/escape-sequences/苗"]');
expect(await page.textContent('h1')).toBe('苗');
});
});
From 16edfc4896c0bd09709ae769f61efa95555ceb46 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 11:57:13 -0500
Subject: [PATCH 16/32] make it work
---
.../core/sync/create_manifest_data/index.js | 14 +-
packages/kit/src/runtime/client/parse.js | 8 +-
packages/kit/src/utils/entities.js | 2263 -----------------
packages/kit/src/utils/entities.spec.js | 13 -
packages/kit/src/utils/routing.js | 49 +-
packages/kit/src/utils/routing.spec.js | 12 +-
.../escape-sequences/[u+82d7]/+page.svelte | 0
.../src/lib/docs/server/index.js | 3 +-
8 files changed, 52 insertions(+), 2310 deletions(-)
delete mode 100644 packages/kit/src/utils/entities.js
delete mode 100644 packages/kit/src/utils/entities.spec.js
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+82d7]/+page.svelte
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index 31b903619b48..2f1329254054 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -5,7 +5,6 @@ import { runtime_directory } from '../../utils.js';
import { posixify } from '../../../utils/filesystem.js';
import { parse_route_id } from '../../../utils/routing.js';
import { sort_routes } from './sort.js';
-import { decode_html_entities } from '../../../utils/entities.js';
/**
* Generates the manifest data used for the client-side manifest and types generation.
@@ -128,7 +127,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
);
}
- const { pattern, names, types, optional } = parse_route_id(id, decode_html_entities);
+ const { pattern, names, types, optional } = parse_route_id(id);
/** @type {import('types').RouteData} */
const route = {
@@ -470,6 +469,17 @@ function normalize_route_id(id) {
// remove groups
.replace(/(?<=^|\/)\(.+?\)(?=$|\/)/g, '')
+ .replace(/\[x\+([0-9a-f]{2})\]/g, (_, x) =>
+ String.fromCharCode(parseInt(x, 16)).replace(/\//g, '%2f')
+ )
+
+ .replace(/\[u\+([0-9a-f]{4})(?:-([0-9a-f]{4}))?\]/g, (_, u1, u2) =>
+ (u2
+ ? String.fromCharCode(parseInt(u1, 16), parseInt(u2, 16))
+ : String.fromCharCode(parseInt(u1, 16))
+ ).replace(/\//g, '%2f')
+ )
+
// replace `[param]` with `<*>`, `[param=x]` with ``, and `[[param]]` with `*>`
.replace(
/\[(?:(\[)|(\.\.\.))?.+?(=.+?)?\]\]?/g,
diff --git a/packages/kit/src/runtime/client/parse.js b/packages/kit/src/runtime/client/parse.js
index 86feed8f1b2b..63909d0fcf8f 100644
--- a/packages/kit/src/runtime/client/parse.js
+++ b/packages/kit/src/runtime/client/parse.js
@@ -10,14 +10,8 @@ import { exec, parse_route_id } from '../../utils/routing.js';
export function parse(nodes, server_loads, dictionary, matchers) {
const layouts_with_server_load = new Set(server_loads);
- const div = document.createElement('div');
-
return Object.entries(dictionary).map(([id, [leaf, layouts, errors]]) => {
- const { pattern, names, types, optional } = parse_route_id(id, (str) => {
- if (!str.includes('&')) return str;
- div.innerHTML = str;
- return /** @type {string} */ (div.textContent);
- });
+ const { pattern, names, types, optional } = parse_route_id(id);
const route = {
id,
diff --git a/packages/kit/src/utils/entities.js b/packages/kit/src/utils/entities.js
deleted file mode 100644
index 53e5c097cbd4..000000000000
--- a/packages/kit/src/utils/entities.js
+++ /dev/null
@@ -1,2263 +0,0 @@
-/**
- * Decode HTML entities
- * @param {string} str
- */
-export function decode_html_entities(str) {
- if (!str.includes('&')) return str;
-
- return str
- .replace(/&(#)?(x)?(.+?);/g, (match, is_number, is_hex, value) => {
- if (is_number) {
- return String.fromCharCode(parseInt(value, is_hex ? 16 : 10));
- }
-
- return match in entities ? entities[match] : match;
- })
- .replace(ambiguous, (match) => entities[match]);
-}
-
-/**
- * Map of named HTML entities to their corresponding characters, derived from
- * https://html.spec.whatwg.org/multipage/named-characters.html
- * @type {Record}
- */
-const entities = {
- '∳': '∳',
- '∲': '∲',
- '⟺': '⟺',
- '⪢̸': '⪢̸',
- '˝': '˝',
- '⋣': '⋣',
- '”': '”',
- '∯': '∯',
- '▪': '▪',
- '​': '',
- '⋠': '⋠',
- '⋭': '⋭',
- '⋡': '⋡',
- 'ⅅ': 'ⅅ',
- '⇔': '⇔',
- '⟹': '⟹',
- '▫': '▫',
- '≫': '≫',
- '∦': '∦',
- '⩾̸': '⩾̸',
- '⋬': '⋬',
- '⋢': '⋢',
- '“': '“',
- '⥯': '⥯',
- '⟸': '⟸',
- '⥐': '⥐',
- '⇆': '⇆',
- '​': '',
- '≧̸': '≧̸',
- '⧐̸': '⧐̸',
- '⇄': '⇄',
- '⊒': '⊒',
- '↭': '↭',
- '⥟': '⥟',
- '⥗': '⥗',
- '⟷': '⟷',
- '⟺': '⟺',
- '​': '',
- '⧏̸': '⧏̸',
- '≼': '≼',
- '⇋': '⇋',
- '⟧': '⟧',
- '⥝': '⥝',
- '⥕': '⥕',
- '⊵': '⊵',
- '⊓': '⊓',
- '≽': '≽',
- '▸': '▸',
- '⟷': '⟷',
- '⇕': '⇕',
- '∥': '∥',
- '⥞': '⥞',
- '⥖': '⥖',
- '◼': '◼',
- '⩾': '⩾',
- '⟦': '⟦',
- '⥡': '⥡',
- '⥙': '⥙',
- '⊴': '⊴',
- '​': '',
- '≫̸': '≫̸',
- '⩽̸': '⩽̸',
- '⪡̸': '⪡̸',
- '∌': '∌',
- '⊐̸': '⊐̸',
- '≇': '≇',
- '⟩': '⟩',
- '⥏': '⥏',
- '⊑': '⊑',
- '❘': '❘',
- '▾': '▾',
- '◂': '◂',
- '⇋': '⇋',
- '⇌': '⇌',
- '↠': '↠',
- '´': '´',
- '`': '`',
- '˜': '˜',
- '⇒': '⇒',
- '⇵': '⇵',
- '◻': '◻',
- '⋛': '⋛',
- '≧': '≧',
- '⟨': '⟨',
- '⥑': '⥑',
- '⋚': '⋚',
- ' ': ' ',
- '⪯̸': '⪯̸',
- '⋫': '⋫',
- '⪰̸': '⪰̸',
- '≿̸': '≿̸',
- '⊉': '⊉',
- '⧐': '⧐',
- '⥜': '⥜',
- '⥔': '⥔',
- '⏝': '⏝',
- '⇅': '⇅',
- '↻': '↻',
- '⇂': '⇂',
- '⋭': '⋭',
- '⇁': '⇁',
- '⇉': '⇉',
- '↞': '↞',
- '⊳': '⊳',
- '’': '’',
- '∮': '∮',
- '⇓': '⇓',
- '⇐': '⇐',
- '⇁': '⇁',
- '⥎': '⥎',
- '⧏': '⧏',
- '⥠': '⥠',
- '⥘': '⥘',
- '↘': '↘',
- '≱': '≱',
- '≵': '≵',
- '≎̸': '≎̸',
- '⋪': '⋪',
- '⊏̸': '⊏̸',
- '⏜': '⏜',
- '⇂': '⇂',
- '→': '→',
- '↗': '↗',
- '▽': '▽',
- '↺': '↺',
- '↷': '↷',
- '⇃': '⇃',
- '↽': '↽',
- '⇆': '⇆',
- '⇎': '⇎',
- '↮': '↮',
- '⋬': '⋬',
- '⇄': '⇄',
- '↝': '↝',
- '⋌': '⋌',
- 'ϵ': 'ϵ',
- '⊵': '⊵',
- '⊲': '⊲',
- '˙': '˙',
- '⊨': '⊨',
- '↽': '↽',
- '⪢': '⪢',
- '─': '─',
- '⁣': '',
- '⁢': '',
- '⇃': '⇃',
- '↔': '↔',
- '⇔': '⇔',
- '⩽': '⩽',
- '⟶': '⟶',
- '⟹': '⟹',
- '↙': '↙',
- '≪': '≪',
- '≹': '≹',
- '≸': '≸',
- '⊈': '⊈',
- '∤': '∤',
- '‘': '‘',
- '∋': '∋',
- '⥛': '⥛',
- '⥓': '⥓',
- '↓': '↓',
- '←': '←',
- '⊐': '⊐',
- '≅': '≅',
- '↖': '↖',
- '​': '',
- '↶': '↶',
- '⌆': '⌆',
- '⇊': '⇊',
- '↪': '↪',
- '⇇': '⇇',
- '↔': '↔',
- '⋋': '⋋',
- '⟶': '⟶',
- '↬': '↬',
- '∦': '∦',
- '⋫': '⋫',
- '↣': '↣',
- '⇀': '⇀',
- '⊴': '⊴',
- '↾': '↾',
- '⁡': '',
- 'ⅆ': 'ⅆ',
- '⫤': '⫤',
- '⇑': '⇑',
- '⥚': '⥚',
- '⥒': '⥒',
- '≦': '≦',
- '⟵': '⟵',
- '⟸': '⟸',
- '≂̸': '≂̸',
- '≄': '≄',
- '≉': '≉',
- 'ℌ': 'ℌ',
- '⪯': '⪯',
- '≾': '≾',
- '⇥': '⇥',
- '↦': '↦',
- '⊳': '⊳',
- '↾': '↾',
- '⪰': '⪰',
- '≿': '≿',
- '⊇': '⊇',
- '⥮': '⥮',
- '≀': '≀',
- ' ': ' ',
- '△': '△',
- '▴': '▴',
- '⋇': '⋇',
- '≒': '≒',
- '↩': '↩',
- '↢': '↢',
- '↼': '↼',
- '⟵': '⟵',
- '↫': '↫',
- '∡': '∡',
- '⋪': '⋪',
- '∥': '∥',
- '∖': '∖',
- '▹': '▹',
- '↿': '↿',
- '⫋︀': '⫋︀',
- '⫌︀': '⫌︀',
- '⤓': '⤓',
- '↧': '↧',
- 'ⅇ': 'ⅇ',
- '≥': '≥',
- '≳': '≳',
- 'ℋ': 'ℋ',
- '≎': '≎',
- '⋂': '⋂',
- '⇤': '⇤',
- '↤': '↤',
- '⊲': '⊲',
- '↿': '↿',
- '≢': '≢',
- '≏̸': '≏̸',
- '≰': '≰',
- '≴': '≴',
- '∝': '∝',
- '⌉': '⌉',
- '⥰': '⥰',
- '↑': '↑',
- '⊏': '⊏',
- '⎵': '⎵',
- '|': '|',
- '⧫': '⧫',
- 'ⅇ': 'ⅇ',
- '≓': '≓',
- '▿': '▿',
- '◃': '◃',
- '⊊︀': '⊊︀',
- '⊋︀': '⊋︀',
- '⊖': '⊖',
- '⊗': '⊗',
- '⇌': '⇌',
- '≷': '≷',
- '⌈': '⌈',
- '≶': '≶',
- ' ': ' ',
- '≪̸': '≪̸',
- '⊀': '⊀',
- '⊁': '⊁',
- '⊃⃒': '⊃⃒',
- '⎴': '⎴',
- '⇀': '⇀',
- '⇛': '⇛',
- '⧴': '⧴',
- '∘': '∘',
- '⊔': '⊔',
- '⊆': '⊆',
- '↕': '↕',
- '⇕': '⇕',
- '∣': '∣',
- '϶': '϶',
- '▪': '▪',
- '⊚': '⊚',
- '⊝': '⊝',
- '⋞': '⋞',
- '⋟': '⋟',
- '♦': '♦',
- '⪕': '⪕',
- 'ℰ': 'ℰ',
- '⇏': '⇏',
- '↛': '↛',
- '≼': '≼',
- '⪹': '⪹',
- 'ℍ': 'ℍ',
- 'ϕ': 'ϕ',
- '≽': '≽',
- '⪺': '⪺',
- '≈': '≈',
- '↕': '↕',
- 'ℬ': 'ℬ',
- '⊕': '⊕',
- '≂': '≂',
- 'ℱ': 'ℱ',
- 'ⅈ': 'ⅈ',
- 'ℒ': 'ℒ',
- '↼': '↼',
- '⇚': '⇚',
- '∉': '∉',
- '≯': '≯',
- '∷': '∷',
- '→': '→',
- '⌋': '⌋',
- '⇒': '⇒',
- '  ': ' ',
- '≃': '≃',
- '≈': '≈',
- '⏟': '⏟',
- '⤒': '⤒',
- '↥': '↥',
- '⊛': '⊛',
- '∁': '∁',
- '⋏': '⋏',
- '⪖': '⪖',
- '⪌': '⪌',
- '⪅': '⪅',
- '⪋': '⪋',
- '⎰': '⎰',
- '⟼': '⟼',
- '↧': '↧',
- '↤': '↤',
- '⇍': '⇍',
- '↚': '↚',
- '⫅̸': '⫅̸',
- '⫆̸': '⫆̸',
- '⪷': '⪷',
- '→': '→',
- '⎱': '⎱',
- '⊑': '⊑',
- '⊒': '⊒',
- '⫋': '⫋',
- '⪸': '⪸',
- '⫌': '⫌',
- '⇈': '⇈',
- 'ϵ': 'ϵ',
- '∅': '∅',
- '∖': '∖',
- '·': '·',
- '⊙': '⊙',
- '≡': '≡',
- '∐': '∐',
- '¨': '¨',
- '↓': '↓',
- '̑': '̑',
- '⇓': '⇓',
- '≏': '≏',
- '←': '←',
- '⌊': '⌊',
- '⇐': '⇐',
- '≲': '≲',
- 'ℳ': 'ℳ',
- '∓': '∓',
- '≭': '≭',
- '∄': '∄',
- '⊂⃒': '⊂⃒',
- '⏞': '⏞',
- '±': '±',
- '∴': '∴',
- ' ': ' ',
- '⃛': '⃛',
- '⊎': '⊎',
- '‵': '‵',
- '⋍': '⋍',
- '⨂': '⨂',
- '·': '·',
- '✓': '✓',
- 'ℂ': 'ℂ',
- '⊡': '⊡',
- '↓': '↓',
- '⪆': '⪆',
- '⋛': '⋛',
- '≩︀': '≩︀',
- '♥': '♥',
- '←': '←',
- '⋚': '⋚',
- '≨︀': '≨︀',
- '⩾̸': '⩾̸',
- '⩽̸': '⩽̸',
- '∦': '∦',
- '∤': '∤',
- '⊈': '⊈',
- '⊉': '⊉',
- '⋔': '⋔',
- 'ℚ': 'ℚ',
- '♠': '♠',
- '⫅': '⫅',
- '⊊': '⊊',
- '⫆': '⫆',
- '⊋': '⊋',
- '∴': '∴',
- '≜': '≜',
- '∝': '∝',
- '⤑': '⤑',
- '≐': '≐',
- '∫': '∫',
- '⪡': '⪡',
- '≠': '≠',
- '≁': '≁',
- '∂': '∂',
- '≺': '≺',
- '⊢': '⊢',
- '≻': '≻',
- '∋': '∋',
- '⊃': '⊃',
- '⥉': '⥉',
- '_': '_',
- '⩘': '⩘',
- '⦨': '⦨',
- '⦩': '⦩',
- '⦪': '⦪',
- '⦫': '⦫',
- '⦬': '⦬',
- '⦭': '⦭',
- '⦮': '⦮',
- '⦯': '⦯',
- '⦝': '⦝',
- '≊': '≊',
- '∳': '∳',
- '≌': '≌',
- '⌅': '⌅',
- '⎶': '⎶',
- '⨁': '⨁',
- '⨆': '⨆',
- '⨄': '⨄',
- '⋀': '⋀',
- '⊟': '⊟',
- '⊠': '⊠',
- '⟈': '⟈',
- '⩉': '⩉',
- '®': '®',
- 'Ⓢ': 'Ⓢ',
- '⨐': '⨐',
- '♣': '♣',
- '⩈': '⩈',
- '⋎': '⋎',
- '∲': '∲',
- '≑': '≑',
- '∸': '∸',
- '⤐': '⤐',
- '⟿': '⟿',
- '⏧': '⏧',
- '∅': '∅',
- '⧥': '⧥',
- '⨍': '⨍',
- '⩾': '⩾',
- '⪄': '⪄',
- '⪊': '⪊',
- '⤥': '⤥',
- '⤦': '⤦',
- 'ℐ': 'ℐ',
- 'ℑ': 'ℑ',
- '⧝': '⧝',
- 'ℤ': 'ℤ',
- '⊺': '⊺',
- '⨗': '⨗',
- '⦴': '⦴',
- '⥋': '⥋',
- '⩽': '⩽',
- '⪃': '⪃',
- '⌞': '⌞',
- '⪉': '⪉',
- '⌟': '⌟',
- '⥊': '⥊',
- '↥': '↥',
- '⊸': '⊸',
- 'ℕ': 'ℕ',
- '⩭̸': '⩭̸',
- '⋵̸': '⋵̸',
- '⨶': '⨶',
- '∥': '∥',
- '⨣': '⨣',
- '⨕': '⨕',
- '⪵': '⪵',
- '⋨': '⋨',
- '⌮': '⌮',
- '⌒': '⌒',
- '⌓': '⌓',
- '⦳': '⦳',
- 'ℜ': 'ℜ',
- '⨒': '⨒',
- '⧎': '⧎',
- '⨓': '⨓',
- '∖': '∖',
- '∣': '∣',
- '⧤': '⧤',
- '⊏': '⊏',
- '⊐': '⊐',
- '⊆': '⊆',
- '⪶': '⪶',
- '⋩': '⋩',
- '⊇': '⊇',
- 'ϑ': 'ϑ',
- '∼': '∼',
- '⨱': '⨱',
- '▵': '▵',
- '⨺': '⨺',
- '⏢': '⏢',
- '⌜': '⌜',
- '⌝': '⌝',
- 'ϰ': 'ϰ',
- 'ς': 'ς',
- 'ϑ': 'ϑ',
- '∵': '∵',
- 'ℭ': 'ℭ',
- '∰': '∰',
- '¸': '¸',
- '⋄': '⋄',
- '⊤': '⊤',
- '∈': '∈',
- 'Ε': 'Ε',
- '⇒': '⇒',
- '⊣': '⊣',
- '
': '\n',
- '⁠': '',
- '≮': '≮',
- 'Ο': 'Ο',
- '‾': '‾',
- '∏': '∏',
- '↑': '↑',
- '⇑': '⇑',
- 'Υ': 'Υ',
- 'ℵ': 'ℵ',
- '⊾': '⊾',
- '⍼': '⍼',
- '≍': '≍',
- '∽': '∽',
- '∵': '∵',
- '⦰': '⦰',
- '≬': '≬',
- '◯': '◯',
- '⨀': '⨀',
- '★': '★',
- '≡⃥': '≡⃥',
- '⊞': '⊞',
- '⩐': '⩐',
- '⦲': '⦲',
- '⧂': '⧂',
- '≔': '≔',
- '⩭': '⩭',
- '⤸': '⤸',
- '⤵': '⤵',
- '⤽': '⤽',
- '⤼': '⤼',
- '⤏': '⤏',
- '‡': '‡',
- '⩷': '⩷',
- '⦱': '⦱',
- '⋄': '⋄',
- 'ϝ': 'ϝ',
- '∔': '∔',
- '⦦': '⦦',
- 'ε': 'ε',
- '≕': '≕',
- '⩸': '⩸',
- '⪂': '⪂',
- '⩼': '⩼',
- '≷': '≷',
- '⥈': '⥈',
- '⨼': '⨼',
- '⋵': '⋵',
- '⤟': '⤟',
- '⥳': '⥳',
- '⦏': '⦏',
- '⦍': '⦍',
- '⥧': '⥧',
- '⪁': '⪁',
- '⋖': '⋖',
- '≶': '≶',
- '≲': '≲',
- '⨴': '⨴',
- '◊': '◊',
- '⩻': '⩻',
- '⥦': '⥦',
- '✠': '✠',
- '⨪': '⨪',
- '≉': '≉',
- '♮': '♮',
- '↗': '↗',
- '∄': '∄',
- '∉': '∉',
- '⋷': '⋷',
- '⋶': '⋶',
- '∌': '∌',
- '⋾': '⋾',
- '⋽': '⋽',
- '⨔': '⨔',
- '⪯̸': '⪯̸',
- '⋢': '⋢',
- '⋣': '⋣',
- '⊂⃒': '⊂⃒',
- '⪰̸': '⪰̸',
- '⊃⃒': '⊃⃒',
- '⧞': '⧞',
- '⊴⃒': '⊴⃒',
- '⊵⃒': '⊵⃒',
- '↖': '↖',
- '⦻': '⦻',
- 'ο': 'ο',
- 'ℴ': 'ℴ',
- '⩗': '⩗',
- '‱': '‱',
- 'ℎ': 'ℎ',
- '⨢': '⨢',
- '⨦': '⨦',
- '⨧': '⨧',
- '≾': '≾',
- '⨖': '⨖',
- '≟': '≟',
- '⤠': '⤠',
- '⥴': '⥴',
- '⦎': '⦎',
- '⦐': '⦐',
- '⥩': '⥩',
- 'ℛ': 'ℛ',
- '⨵': '⨵',
- '⥨': '⥨',
- '↘': '↘',
- '⨤': '⨤',
- '⥲': '⥲',
- '⫃': '⫃',
- '⫁': '⫁',
- '⪿': '⪿',
- '⥹': '⥹',
- '≿': '≿',
- '⫘': '⫘',
- '⫄': '⫄',
- '⟉': '⟉',
- '⫗': '⫗',
- '⥻': '⥻',
- '⫂': '⫂',
- '⫀': '⫀',
- '↙': '↙',
- '⫚': '⫚',
- '⨹': '⨹',
- '⨻': '⨻',
- '↑': '↑',
- 'υ': 'υ',
- '⦧': '⦧',
- '⦚': '⦚',
- '⇝': '⇝',
- 'Á': 'Á',
- 'Ă': 'Ă',
- 'À': 'À',
- '≔': '≔',
- 'Ã': 'Ã',
- '⌆': '⌆',
- '≎': '≎',
- 'Ć': 'Ć',
- 'Č': 'Č',
- 'Ç': 'Ç',
- '⩴': '⩴',
- '∯': '∯',
- '≍': '≍',
- '‡': '‡',
- 'Ď': 'Ď',
- '⃜': '⃜',
- 'Đ': 'Đ',
- 'É': 'É',
- 'Ě': 'Ě',
- 'È': 'È',
- '∃': '∃',
- '∀': '∀',
- 'Ϝ': 'Ϝ',
- 'Ğ': 'Ğ',
- 'Ģ': 'Ģ',
- 'Ъ': 'Ъ',
- 'Ħ': 'Ħ',
- 'Í': 'Í',
- 'Ì': 'Ì',
- 'Ĩ': 'Ĩ',
- 'Ј': 'Ј',
- 'Ķ': 'Ķ',
- 'Ĺ': 'Ĺ',
- 'Λ': 'Λ',
- 'Ľ': 'Ľ',
- 'Ļ': 'Ļ',
- 'Ŀ': 'Ŀ',
- 'Ł': 'Ł',
- 'Ń': 'Ń',
- 'Ň': 'Ň',
- 'Ņ': 'Ņ',
- 'Ñ': 'Ñ',
- 'Ó': 'Ó',
- 'Ő': 'Ő',
- 'Ò': 'Ò',
- 'Ø': 'Ø',
- 'Õ': 'Õ',
- '⨷': '⨷',
- 'Ŕ': 'Ŕ',
- '⤖': '⤖',
- 'Ř': 'Ř',
- 'Ŗ': 'Ŗ',
- 'Щ': 'Щ',
- 'Ь': 'Ь',
- 'Ś': 'Ś',
- 'Š': 'Š',
- 'Ş': 'Ş',
- '□': '□',
- '⋐': '⋐',
- '⋑': '⋑',
- 'Ť': 'Ť',
- 'Ţ': 'Ţ',
- 'Ŧ': 'Ŧ',
- 'Ú': 'Ú',
- 'Ŭ': 'Ŭ',
- 'Ű': 'Ű',
- 'Ù': 'Ù',
- 'Ũ': 'Ũ',
- '⫦': '⫦',
- '‖': '‖',
- '⊪': '⊪',
- 'Ý': 'Ý',
- 'Ź': 'Ź',
- 'Ž': 'Ž',
- 'á': 'á',
- 'ă': 'ă',
- 'à': 'à',
- '⩕': '⩕',
- '∡': '∡',
- '∢': '∢',
- '⩯': '⩯',
- '≈': '≈',
- 'ã': 'ã',
- '⊽': '⊽',
- '⌅': '⌅',
- '∵': '∵',
- 'ℬ': 'ℬ',
- '⋂': '⋂',
- '⋃': '⋃',
- '⋁': '⋁',
- '⤍': '⤍',
- '⊥': '⊥',
- '⋈': '⋈',
- '⧉': '⧉',
- '‵': '‵',
- '¦': '¦',
- '•': '•',
- '≏': '≏',
- 'ć': 'ć',
- '⩄': '⩄',
- '⩋': '⩋',
- '⩇': '⩇',
- '⩀': '⩀',
- 'č': 'č',
- 'ç': 'ç',
- '≗': '≗',
- '⫯': '⫯',
- '≔': '≔',
- '@': '@',
- '∘': '∘',
- '∮': '∮',
- '∐': '∐',
- '℗': '℗',
- '↶': '↶',
- '⩆': '⩆',
- '⩊': '⩊',
- '⊍': '⊍',
- '↷': '↷',
- '¤': '¤',
- '⌭': '⌭',
- '†': '†',
- 'ℸ': 'ℸ',
- 'ď': 'ď',
- '⥿': '⥿',
- '÷': '÷',
- '⋇': '⋇',
- '⌞': '⌞',
- '⌍': '⌍',
- '$': '$',
- '⌟': '⌟',
- '⌌': '⌌',
- 'đ': 'đ',
- 'é': 'é',
- '⩮': '⩮',
- 'ě': 'ě',
- '≕': '≕',
- 'è': 'è',
- '⪘': '⪘',
- '⪗': '⪗',
- '∅': '∅',
- ' ': ' ',
- ' ': ' ',
- '⧣': '⧣',
- '≖': '≖',
- '=': '=',
- '≟': '≟',
- '♀': '♀',
- 'ffi': 'ffi',
- 'ffl': 'ffl',
- '∀': '∀',
- '½': '½',
- '⅓': '⅓',
- '¼': '¼',
- '⅕': '⅕',
- '⅙': '⅙',
- '⅛': '⅛',
- '⅔': '⅔',
- '⅖': '⅖',
- '¾': '¾',
- '⅗': '⅗',
- '⅜': '⅜',
- '⅘': '⅘',
- '⅚': '⅚',
- '⅝': '⅝',
- '⅞': '⅞',
- 'ǵ': 'ǵ',
- 'ϝ': 'ϝ',
- 'ğ': 'ğ',
- '⪀': '⪀',
- '⪔': '⪔',
- '⦕': '⦕',
- '⥸': '⥸',
- '⋗': '⋗',
- '≳': '≳',
- ' ': ' ',
- 'ℋ': 'ℋ',
- 'ъ': 'ъ',
- '♥': '♥',
- '…': '…',
- '⊹': '⊹',
- '∻': '∻',
- '―': '―',
- 'ℏ': 'ℏ',
- 'ħ': 'ħ',
- '⁃': '⁃',
- '‐': '‐',
- 'í': 'í',
- 'ì': 'ì',
- '⨌': '⨌',
- '⧜': '⧜',
- '℅': '℅',
- 'ı': 'ı',
- '⊺': '⊺',
- '¿': '¿',
- '⋳': '⋳',
- 'ĩ': 'ĩ',
- 'ј': 'ј',
- 'ϰ': 'ϰ',
- 'ķ': 'ķ',
- 'ĸ': 'ĸ',
- '⤛': '⤛',
- 'ĺ': 'ĺ',
- 'ℒ': 'ℒ',
- 'λ': 'λ',
- '⟨': '⟨',
- '⤝': '⤝',
- '↩': '↩',
- '↫': '↫',
- '⤹': '⤹',
- '↢': '↢',
- '⤙': '⤙',
- '{': '{',
- '[': '[',
- 'ľ': 'ľ',
- 'ļ': 'ļ',
- '„': '„',
- '⩿': '⩿',
- '⪓': '⪓',
- '⥼': '⥼',
- '⌊': '⌊',
- '⥪': '⥪',
- '⥫': '⥫',
- 'ŀ': 'ŀ',
- '⎰': '⎰',
- '⨭': '⨭',
- '∗': '∗',
- '_': '_',
- '⦓': '⦓',
- '⥭': '⥭',
- '‹': '‹',
- '‚': '‚',
- 'ł': 'ł',
- '⋋': '⋋',
- '⋉': '⋉',
- '⥶': '⥶',
- '⦖': '⦖',
- '↦': '↦',
- '▮': '▮',
- '⨩': '⨩',
- '*': '*',
- '⫰': '⫰',
- '·': '·',
- '⊟': '⊟',
- '∸': '∸',
- '∓': '∓',
- '⊧': '⊧',
- '∾': '∾',
- '⊯': '⊯',
- '⊮': '⊮',
- 'ń': 'ń',
- '≏̸': '≏̸',
- 'ň': 'ň',
- 'ņ': 'ņ',
- '⤤': '⤤',
- '≢': '≢',
- '⤨': '⤨',
- '∄': '∄',
- '⋬': '⋬',
- '⋹̸': '⋹̸',
- '⫽⃥': '⫽⃥',
- '⋠': '⋠',
- '⤳̸': '⤳̸',
- '↝̸': '↝̸',
- '⋭': '⋭',
- '⋡': '⋡',
- '≄': '≄',
- 'ñ': 'ñ',
- '№': '№',
- '⊭': '⊭',
- '⤄': '⤄',
- '⊬': '⊬',
- '⤂': '⤂',
- '⤃': '⤃',
- '⤣': '⤣',
- '⤧': '⤧',
- 'ó': 'ó',
- 'ő': 'ő',
- '⦼': '⦼',
- 'ò': 'ò',
- '⊖': '⊖',
- '⊶': '⊶',
- 'ø': 'ø',
- 'õ': 'õ',
- '⊗': '⊗',
- '⫳': '⫳',
- '%': '%',
- '.': '.',
- '‰': '‰',
- 'ℳ': 'ℳ',
- 'ℏ': 'ℏ',
- 'ℏ': 'ℏ',
- '∔': '∔',
- '⨥': '⨥',
- '±': '±',
- '⪯': '⪯',
- 'ℙ': 'ℙ',
- '⋨': '⋨',
- '∝': '∝',
- '⊰': '⊰',
- ' ': ' ',
- '⁗': '⁗',
- '⤜': '⤜',
- 'ŕ': 'ŕ',
- '⟩': '⟩',
- '⥵': '⥵',
- '⤞': '⤞',
- '↪': '↪',
- '↬': '↬',
- '⥅': '⥅',
- '↣': '↣',
- '⤚': '⤚',
- '}': '}',
- ']': ']',
- 'ř': 'ř',
- 'ŗ': 'ŗ',
- '”': '”',
- '⥽': '⥽',
- '⌋': '⌋',
- '⥬': '⥬',
- '⎱': '⎱',
- '⨮': '⨮',
- '⦔': '⦔',
- '›': '›',
- '’': '’',
- '⋌': '⋌',
- '⋊': '⋊',
- 'ś': 'ś',
- 'š': 'š',
- 'ş': 'ş',
- '⋩': '⋩',
- '⤥': '⤥',
- '⤩': '⤩',
- '⌢': '⌢',
- 'щ': 'щ',
- 'ς': 'ς',
- 'ς': 'ς',
- '⩪': '⩪',
- '⨳': '⨳',
- 'ь': 'ь',
- '⌿': '⌿',
- '♠': '♠',
- '⊓︀': '⊓︀',
- '⊔︀': '⊔︀',
- '⊑': '⊑',
- '⊒': '⊒',
- '□': '□',
- '▪': '▪',
- '∖': '∖',
- '⌣': '⌣',
- '⋆': '⋆',
- '⪽': '⪽',
- '⊂': '⊂',
- '⫇': '⫇',
- '⫕': '⫕',
- '⫓': '⫓',
- '⪰': '⪰',
- '⪾': '⪾',
- '⊃': '⊃',
- '⫈': '⫈',
- '⫔': '⫔',
- '⫖': '⫖',
- '⤦': '⤦',
- '⤪': '⤪',
- '⌖': '⌖',
- 'ť': 'ť',
- 'ţ': 'ţ',
- '⌕': '⌕',
- '∴': '∴',
- 'ϑ': 'ϑ',
- ' ': ' ',
- '∼': '∼',
- '⊠': '⊠',
- '⨰': '⨰',
- '⌶': '⌶',
- '⫱': '⫱',
- '‴': '‴',
- '◬': '◬',
- 'ŧ': 'ŧ',
- 'ú': 'ú',
- 'ŭ': 'ŭ',
- 'ű': 'ű',
- '⥾': '⥾',
- 'ù': 'ù',
- '⌜': '⌜',
- '⌏': '⌏',
- '⌝': '⌝',
- '⌎': '⌎',
- 'ũ': 'ũ',
- '⦜': '⦜',
- 'ϕ': 'ϕ',
- 'ϱ': 'ϱ',
- '⊻': '⊻',
- '⋮': '⋮',
- '|': '|',
- '⫋︀': '⫋︀',
- '⊊︀': '⊊︀',
- '⫌︀': '⫌︀',
- '⊋︀': '⊋︀',
- '⩟': '⩟',
- '≙': '≙',
- '℘': '℘',
- '≀': '≀',
- '⨁': '⨁',
- '⨂': '⨂',
- '⨆': '⨆',
- '⨄': '⨄',
- '⋀': '⋀',
- 'ý': 'ý',
- 'ź': 'ź',
- 'ž': 'ž',
- 'ℨ': 'ℨ',
- 'Æ': 'Æ',
- 'Á': 'Á',
- 'Â': 'Â',
- 'À': 'À',
- 'Α': 'Α',
- 'Ā': 'Ā',
- 'Ą': 'Ą',
- 'Å': 'Å',
- 'Ã': 'Ã',
- '˘': '˘',
- 'Ç': 'Ç',
- 'Ĉ': 'Ĉ',
- '∷': '∷',
- '⨯': '⨯',
- '⫤': '⫤',
- 'Δ': 'Δ',
- 'É': 'É',
- 'Ê': 'Ê',
- 'È': 'È',
- 'Ē': 'Ē',
- 'Ę': 'Ę',
- '⩵': '⩵',
- 'Γ': 'Γ',
- 'Ĝ': 'Ĝ',
- 'ˇ': 'ˇ',
- 'Ĥ': 'Ĥ',
- 'IJ': 'IJ',
- 'Í': 'Í',
- 'Î': 'Î',
- 'Ì': 'Ì',
- 'Ī': 'Ī',
- 'Į': 'Į',
- 'І': 'І',
- 'Ĵ': 'Ĵ',
- 'Є': 'Є',
- 'Κ': 'Κ',
- 'Ñ': 'Ñ',
- 'Œ': 'Œ',
- 'Ó': 'Ó',
- 'Ô': 'Ô',
- 'Ò': 'Ò',
- 'Ō': 'Ō',
- 'Ω': 'Ω',
- 'Ø': 'Ø',
- 'Õ': 'Õ',
- '″': '″',
- '⤐': '⤐',
- 'Ŝ': 'Ŝ',
- 'Σ': 'Σ',
- 'Þ': 'Þ',
- '™': '™',
- 'Ћ': 'Ћ',
- 'Θ': 'Θ',
- '∼': '∼',
- 'Ú': 'Ú',
- 'Ў': 'Ў',
- 'Û': 'Û',
- 'Ù': 'Ù',
- 'Ū': 'Ū',
- '⋃': '⋃',
- 'Ų': 'Ų',
- '⊥': '⊥',
- 'Ů': 'Ů',
- '⊫': '⊫',
- '⊩': '⊩',
- 'Ŵ': 'Ŵ',
- '⋀': '⋀',
- 'Ý': 'Ý',
- 'Ŷ': 'Ŷ',
- 'á': 'á',
- 'â': 'â',
- '´': '´',
- 'æ': 'æ',
- 'à': 'à',
- 'ℵ': 'ℵ',
- 'α': 'α',
- 'ā': 'ā',
- '⨿': '⨿',
- '∠': '∠',
- '∟': '∟',
- 'Å': 'Å',
- 'ą': 'ą',
- 'å': 'å',
- '≈': '≈',
- 'ã': 'ã',
- '⨑': '⨑',
- '≌': '≌',
- '„': '„',
- '϶': '϶',
- '␣': '␣',
- '▒': '▒',
- '░': '░',
- '▓': '▓',
- '█': '█',
- '╗': '╗',
- '╔': '╔',
- '╖': '╖',
- '╓': '╓',
- '╦': '╦',
- '╩': '╩',
- '╤': '╤',
- '╧': '╧',
- '╝': '╝',
- '╚': '╚',
- '╜': '╜',
- '╙': '╙',
- '╬': '╬',
- '╣': '╣',
- '╠': '╠',
- '╫': '╫',
- '╢': '╢',
- '╟': '╟',
- '╕': '╕',
- '╒': '╒',
- '┐': '┐',
- '┌': '┌',
- '╥': '╥',
- '╨': '╨',
- '┬': '┬',
- '┴': '┴',
- '╛': '╛',
- '╘': '╘',
- '┘': '┘',
- '└': '└',
- '╪': '╪',
- '╡': '╡',
- '╞': '╞',
- '┼': '┼',
- '┤': '┤',
- '├': '├',
- '˘': '˘',
- '¦': '¦',
- '⁏': '⁏',
- '⋍': '⋍',
- '⧅': '⧅',
- '⪮': '⪮',
- '≏': '≏',
- '⁁': '⁁',
- 'ˇ': 'ˇ',
- '⩍': '⩍',
- 'ç': 'ç',
- 'ĉ': 'ĉ',
- '⩌': '⩌',
- '¸': '¸',
- '✓': '✓',
- '♣': '♣',
- ':': ':',
- ',': ',',
- '↵': '↵',
- '✗': '✗',
- '⫑': '⫑',
- '⫒': '⫒',
- '⋯': '⋯',
- '⋞': '⋞',
- '⋟': '⋟',
- '⩅': '⩅',
- '¤': '¤',
- '⋎': '⋎',
- '⋏': '⋏',
- '∱': '∱',
- '⊣': '⊣',
- '˝': '˝',
- '⇊': '⇊',
- 'δ': 'δ',
- '⇃': '⇃',
- '⇂': '⇂',
- '♦': '♦',
- '⋲': '⋲',
- '÷': '÷',
- '≐': '≐',
- '⋱': '⋱',
- '▾': '▾',
- '⇵': '⇵',
- '⥯': '⥯',
- '⩷': '⩷',
- 'é': 'é',
- 'ê': 'ê',
- '≒': '≒',
- 'è': 'è',
- 'ē': 'ē',
- '∅': '∅',
- 'ę': 'ę',
- '⩱': '⩱',
- 'ϵ': 'ϵ',
- '≂': '≂',
- '≡': '≡',
- '≓': '≓',
- '⥱': '⥱',
- '≐': '≐',
- '∃': '∃',
- 'ff': 'ff',
- 'fi': 'fi',
- 'fj': 'fj',
- 'fl': 'fl',
- '▱': '▱',
- '⫙': '⫙',
- '½': '½',
- '¼': '¼',
- '¾': '¾',
- '⁄': '⁄',
- '⌢': '⌢',
- 'γ': 'γ',
- 'ĝ': 'ĝ',
- '⪩': '⪩',
- 'ℷ': 'ℷ',
- '≩': '≩',
- '⋧': '⋧',
- '`': '`',
- '⪎': '⪎',
- '⪐': '⪐',
- '⩺': '⩺',
- '⋗': '⋗',
- '↭': '↭',
- 'ĥ': 'ĥ',
- '⇿': '⇿',
- 'í': 'í',
- 'î': 'î',
- '¡': '¡',
- 'ì': 'ì',
- '∭': '∭',
- '℩': '℩',
- 'ij': 'ij',
- 'ī': 'ī',
- 'ℑ': 'ℑ',
- 'ı': 'ı',
- 'Ƶ': 'Ƶ',
- '∞': '∞',
- 'į': 'į',
- '⨼': '⨼',
- '¿': '¿',
- '⋹': '⋹',
- '⋴': '⋴',
- '∈': '∈',
- 'і': 'і',
- 'ĵ': 'ĵ',
- 'ȷ': 'ȷ',
- 'є': 'є',
- 'κ': 'κ',
- '⇚': '⇚',
- '⤎': '⤎',
- '⦑': '⦑',
- '«': '«',
- '⇤': '⇤',
- '⪭︀': '⪭︀',
- '⤌': '⤌',
- '❲': '❲',
- '⦋': '⦋',
- '⌈': '⌈',
- '“': '“',
- '⪨': '⪨',
- '↽': '↽',
- '↼': '↼',
- '▄': '▄',
- '⇇': '⇇',
- '◺': '◺',
- '≨': '≨',
- '⋦': '⋦',
- '⟬': '⟬',
- '⇽': '⇽',
- '⟦': '⟦',
- '⦅': '⦅',
- '⇆': '⇆',
- '⇋': '⇋',
- '⊿': '⊿',
- '⪍': '⪍',
- '⪏': '⪏',
- '‘': '‘',
- '⩹': '⩹',
- '⋖': '⋖',
- '⊴': '⊴',
- '◂': '◂',
- '∺': '∺',
- '—': '—',
- 'µ': 'µ',
- '·': '·',
- '−': '−',
- '⊸': '⊸',
- '∇': '∇',
- '≋̸': '≋̸',
- 'ʼn': 'ʼn',
- '♮': '♮',
- '≎̸': '≎̸',
- '≇': '≇',
- '–': '–',
- '⇗': '⇗',
- '↗': '↗',
- '≐̸': '≐̸',
- '≂̸': '≂̸',
- '≧̸': '≧̸',
- '≵': '≵',
- '⇎': '⇎',
- '↮': '↮',
- '⫲': '⫲',
- '⇍': '⇍',
- '↚': '↚',
- '≦̸': '≦̸',
- '≮': '≮',
- '≴': '≴',
- '⋪': '⋪',
- '∉': '∉',
- '∌': '∌',
- '∂̸': '∂̸',
- '⊀': '⊀',
- '⇏': '⇏',
- '↛': '↛',
- '⋫': '⋫',
- '≄': '≄',
- '∤': '∤',
- '∦': '∦',
- '⫅̸': '⫅̸',
- '⊈': '⊈',
- '⊁': '⊁',
- '⫆̸': '⫆̸',
- '⊉': '⊉',
- 'ñ': 'ñ',
- ' ': ' ',
- '∼⃒': '∼⃒',
- '⇖': '⇖',
- '↖': '↖',
- 'ó': 'ó',
- 'ô': 'ô',
- '⊝': '⊝',
- 'œ': 'œ',
- '⦿': '⦿',
- 'ò': 'ò',
- '⦵': '⦵',
- '↺': '↺',
- '⦾': '⦾',
- '‾': '‾',
- 'ō': 'ō',
- 'ω': 'ω',
- '⦹': '⦹',
- '⊕': '⊕',
- '↻': '↻',
- 'ℴ': 'ℴ',
- 'ø': 'ø',
- 'õ': 'õ',
- '⌽': '⌽',
- '⫽': '⫽',
- '☎': '☎',
- '⊞': '⊞',
- '⩲': '⩲',
- '±': '±',
- '£': '£',
- '≼': '≼',
- '′': '′',
- '⪹': '⪹',
- '≾': '≾',
- '?': '?',
- '⇛': '⇛',
- '⤏': '⤏',
- '√': '√',
- '⦒': '⦒',
- '⦥': '⦥',
- '»': '»',
- '⇥': '⇥',
- '⤳': '⤳',
- '↝': '↝',
- '∶': '∶',
- '⤍': '⤍',
- '❳': '❳',
- '⦌': '⦌',
- '⌉': '⌉',
- '”': '”',
- 'ℝ': 'ℝ',
- '⇁': '⇁',
- '⇀': '⇀',
- '⇄': '⇄',
- '⇌': '⇌',
- '⫮': '⫮',
- '⟭': '⟭',
- '⇾': '⇾',
- '⟧': '⟧',
- '⦆': '⦆',
- '⇉': '⇉',
- '’': '’',
- '⊵': '⊵',
- '▸': '▸',
- '‚': '‚',
- '≽': '≽',
- 'ŝ': 'ŝ',
- '⪺': '⪺',
- '≿': '≿',
- '⊡': '⊡',
- '⩦': '⩦',
- '⇘': '⇘',
- '↘': '↘',
- '∖': '∖',
- '♯': '♯',
- 'σ': 'σ',
- '≃': '≃',
- '⪠': '⪠',
- '⪟': '⪟',
- '≆': '≆',
- '←': '←',
- '⌣': '⌣',
- '⪬︀': '⪬︀',
- '⊓': '⊓',
- '⊔': '⊔',
- '⊏': '⊏',
- '⊐': '⊐',
- '→': '→',
- '★': '★',
- '¯': '¯',
- '⫋': '⫋',
- '⊊': '⊊',
- '⫌': '⫌',
- '⊋': '⊋',
- '⇙': '⇙',
- '↙': '↙',
- 'ß': 'ß',
- 'θ': 'θ',
- '≈': '≈',
- 'þ': 'þ',
- '˜': '˜',
- '×': '×',
- '™': '™',
- '⧍': '⧍',
- 'ћ': 'ћ',
- '≬': '≬',
- 'ú': 'ú',
- 'ў': 'ў',
- 'û': 'û',
- '⇅': '⇅',
- '⥮': '⥮',
- 'ù': 'ù',
- '↿': '↿',
- '↾': '↾',
- '▀': '▀',
- '◸': '◸',
- 'ū': 'ū',
- 'ų': 'ų',
- '⊎': '⊎',
- 'ϒ': 'ϒ',
- 'ů': 'ů',
- '◹': '◹',
- '⋰': '⋰',
- '▴': '▴',
- '⇈': '⇈',
- '⫩': '⫩',
- '⊨': '⊨',
- 'ϖ': 'ϖ',
- '⊢': '⊢',
- '≚': '≚',
- '⊲': '⊲',
- '⊂⃒': '⊂⃒',
- '⊃⃒': '⊃⃒',
- '∝': '∝',
- '⊳': '⊳',
- 'ŵ': 'ŵ',
- '∧': '∧',
- '◯': '◯',
- '▽': '▽',
- '⟺': '⟺',
- '⟷': '⟷',
- '⟸': '⟸',
- '⟵': '⟵',
- '⨀': '⨀',
- '⟹': '⟹',
- '⟶': '⟶',
- '△': '△',
- 'ý': 'ý',
- 'ŷ': 'ŷ',
- 'Æ': 'Æ',
- 'Â': 'Â',
- '𝔸': '𝔸',
- 'Å': 'Å',
- '𝒜': '𝒜',
- 'Ä': 'Ä',
- '⫧': '⫧',
- 'Β': 'Β',
- '𝔹': '𝔹',
- 'ℬ': 'ℬ',
- 'Ч': 'Ч',
- '©': '©',
- 'Ċ': 'Ċ',
- 'ℂ': 'ℂ',
- '𝒞': '𝒞',
- 'Ђ': 'Ђ',
- 'Ѕ': 'Ѕ',
- 'Џ': 'Џ',
- '↡': '↡',
- '𝔻': '𝔻',
- '𝒟': '𝒟',
- 'Ê': 'Ê',
- 'Ė': 'Ė',
- '𝔼': '𝔼',
- 'ℰ': 'ℰ',
- '⩳': '⩳',
- 'Ë': 'Ë',
- '𝔽': '𝔽',
- 'ℱ': 'ℱ',
- 'Ѓ': 'Ѓ',
- 'Ġ': 'Ġ',
- '𝔾': '𝔾',
- '𝒢': '𝒢',
- 'ℍ': 'ℍ',
- 'ℋ': 'ℋ',
- 'Е': 'Е',
- 'Ё': 'Ё',
- 'Î': 'Î',
- 'İ': 'İ',
- '𝕀': '𝕀',
- 'Ι': 'Ι',
- 'ℐ': 'ℐ',
- 'Ï': 'Ï',
- '𝕁': '𝕁',
- '𝒥': '𝒥',
- 'Х': 'Х',
- 'Ќ': 'Ќ',
- '𝕂': '𝕂',
- '𝒦': '𝒦',
- 'Љ': 'Љ',
- '⟪': '⟪',
- '↞': '↞',
- '𝕃': '𝕃',
- 'ℒ': 'ℒ',
- '𝕄': '𝕄',
- 'ℳ': 'ℳ',
- 'Њ': 'Њ',
- 'ℕ': 'ℕ',
- '𝒩': '𝒩',
- 'Ô': 'Ô',
- '𝕆': '𝕆',
- '𝒪': '𝒪',
- 'Ö': 'Ö',
- 'ℙ': 'ℙ',
- '𝒫': '𝒫',
- '"': '"',
- 'ℚ': 'ℚ',
- '𝒬': '𝒬',
- '⟫': '⟫',
- '↠': '↠',
- 'ℝ': 'ℝ',
- 'ℛ': 'ℛ',
- 'Ш': 'Ш',
- '𝕊': '𝕊',
- '√': '√',
- '𝒮': '𝒮',
- '⋆': '⋆',
- 'Þ': 'Þ',
- 'Ц': 'Ц',
- '𝕋': '𝕋',
- '𝒯': '𝒯',
- '↟': '↟',
- 'Û': 'Û',
- '𝕌': '𝕌',
- 'ϒ': 'ϒ',
- '𝒰': '𝒰',
- 'Ü': 'Ü',
- '⫫': '⫫',
- '‖': '‖',
- '𝕍': '𝕍',
- '𝒱': '𝒱',
- '𝕎': '𝕎',
- '𝒲': '𝒲',
- '𝕏': '𝕏',
- '𝒳': '𝒳',
- 'Я': 'Я',
- 'Ї': 'Ї',
- 'Ю': 'Ю',
- '𝕐': '𝕐',
- '𝒴': '𝒴',
- 'Ÿ': 'Ÿ',
- 'Ж': 'Ж',
- 'Ż': 'Ż',
- 'Ζ': 'Ζ',
- 'ℤ': 'ℤ',
- '𝒵': '𝒵',
- 'â': 'â',
- '´': '´',
- 'æ': 'æ',
- '⩜': '⩜',
- '⩚': '⩚',
- '⦤': '⦤',
- '𝕒': '𝕒',
- '≋': '≋',
- ''': "'",
- 'å': 'å',
- '𝒶': '𝒶',
- 'ä': 'ä',
- '⫭': '⫭',
- '⎵': '⎵',
- 'β': 'β',
- 'ℶ': 'ℶ',
- '⌐': '⌐',
- '𝕓': '𝕓',
- '═': '═',
- '║': '║',
- '─': '─',
- '│': '│',
- '𝒷': '𝒷',
- '∽': '∽',
- '\': '\\',
- '•': '•',
- '≎': '≎',
- '∩︀': '∩︀',
- 'ċ': 'ċ',
- '¸': '¸',
- '¢': '¢',
- 'ч': 'ч',
- '⧃': '⧃',
- 'ˆ': 'ˆ',
- '≗': '≗',
- '∁': '∁',
- '≅': '≅',
- '𝕔': '𝕔',
- '©': '©',
- '𝒸': '𝒸',
- '⫏': '⫏',
- '⫐': '⫐',
- '∪︀': '∪︀',
- '⇓': '⇓',
- '⥥': '⥥',
- '↓': '↓',
- '‐': '‐',
- '⋄': '⋄',
- 'ђ': 'ђ',
- '𝕕': '𝕕',
- '𝒹': '𝒹',
- 'ѕ': 'ѕ',
- '⧶': '⧶',
- '▿': '▿',
- 'џ': 'џ',
- '≑': '≑',
- '≖': '≖',
- 'ê': 'ê',
- 'ė': 'ė',
- ' ': ' ',
- ' ': ' ',
- '𝕖': '𝕖',
- '⋕': '⋕',
- 'ε': 'ε',
- 'ℯ': 'ℯ',
- '≂': '≂',
- 'ë': 'ë',
- '€': '€',
- '!': '!',
- '♭': '♭',
- 'ƒ': 'ƒ',
- '𝕗': '𝕗',
- '⋔': '⋔',
- '𝒻': '𝒻',
- 'ġ': 'ġ',
- '≧': '≧',
- '⋛︀': '⋛︀',
- 'ѓ': 'ѓ',
- '⪊': '⪊',
- '⪈': '⪈',
- '𝕘': '𝕘',
- 'ℊ': 'ℊ',
- '≳': '≳',
- '⪧': '⪧',
- '≩︀': '≩︀',
- '⇔': '⇔',
- '½': '½',
- '↔': '↔',
- 'ℏ': 'ℏ',
- '𝕙': '𝕙',
- '𝒽': '𝒽',
- 'î': 'î',
- 'е': 'е',
- '¡': '¡',
- '⊷': '⊷',
- 'ё': 'ё',
- '𝕚': '𝕚',
- 'ι': 'ι',
- '𝒾': '𝒾',
- '∈': '∈',
- 'ï': 'ï',
- '𝕛': '𝕛',
- '𝒿': '𝒿',
- 'х': 'х',
- 'ќ': 'ќ',
- '𝕜': '𝕜',
- '𝓀': '𝓀',
- '⇐': '⇐',
- '⥢': '⥢',
- '〈': '⟨',
- '«': '«',
- '←': '←',
- '⪭': '⪭',
- '{': '{',
- '⤶': '⤶',
- '↲': '↲',
- '≦': '≦',
- '⋚︀': '⋚︀',
- 'љ': 'љ',
- '⪉': '⪉',
- '⪇': '⪇',
- '𝕝': '𝕝',
- '⧫': '⧫',
- '(': '(',
- '𝓁': '𝓁',
- '≲': '≲',
- '[': '[',
- '⪦': '⪦',
- '◃': '◃',
- '≨︀': '≨︀',
- '¯': '¯',
- '♂': '♂',
- '✠': '✠',
- 'µ': 'µ',
- '⫛': '⫛',
- '…': '…',
- '𝕞': '𝕞',
- '𝓂': '𝓂',
- '≫̸': '≫̸',
- '≪̸': '≪̸',
- '∠⃒': '∠⃒',
- '⩰̸': '⩰̸',
- ' ': ' ',
- '⩃': '⩃',
- '⩂': '⩂',
- '≱': '≱',
- '⩾̸': '⩾̸',
- '≯': '≯',
- '⋺': '⋺',
- 'њ': 'њ',
- '‥': '‥',
- '≰': '≰',
- '⩽̸': '⩽̸',
- '∤': '∤',
- '𝕟': '𝕟',
- '∦': '∦',
- '⪯̸': '⪯̸',
- '⪰̸': '⪰̸',
- '𝓃': '𝓃',
- '≁': '≁',
- '⊄': '⊄',
- '⊅': '⊅',
- '≹': '≹',
- '≸': '≸',
- '≍⃒': '≍⃒',
- '≥⃒': '≥⃒',
- '>⃒': '>⃒',
- '≤⃒': '≤⃒',
- '<⃒': '<⃒',
- '⊛': '⊛',
- '⊚': '⊚',
- 'ô': 'ô',
- '⨸': '⨸',
- '⊙': '⊙',
- '˛': '˛',
- '∮': '∮',
- '⦶': '⦶',
- '𝕠': '𝕠',
- '⦷': '⦷',
- 'ª': 'ª',
- 'º': 'º',
- '⩖': '⩖',
- 'ℴ': 'ℴ',
- '⊘': '⊘',
- 'ö': 'ö',
- '¶': '¶',
- '∂': '∂',
- '⊥': '⊥',
- 'ϕ': 'ϕ',
- '+': '+',
- '𝕡': '𝕡',
- '£': '£',
- '⪷': '⪷',
- '≺': '≺',
- '⪵': '⪵',
- '∏': '∏',
- '∝': '∝',
- '𝓅': '𝓅',
- '⨌': '⨌',
- '𝕢': '𝕢',
- '𝓆': '𝓆',
- '"': '"',
- '⇒': '⇒',
- '⥤': '⥤',
- '∽̱': '∽̱',
- '〉': '⟩',
- '»': '»',
- '→': '→',
- '}': '}',
- '⤷': '⤷',
- '↳': '↳',
- 'ℜ': 'ℜ',
- '▭': '▭',
- 'ϱ': 'ϱ',
- '˚': '˚',
- '𝕣': '𝕣',
- ')': ')',
- '𝓇': '𝓇',
- ']': ']',
- '▹': '▹',
- '⪸': '⪸',
- '⪶': '⪶',
- '⋅': '⋅',
- '§': '§',
- ';': ';',
- '✶': '✶',
- 'ш': 'ш',
- '≃': '≃',
- '⪞': '⪞',
- '⪝': '⪝',
- '∣': '∣',
- '⪬': '⪬',
- '⧄': '⧄',
- '𝕤': '𝕤',
- '∥': '∥',
- '▪': '▪',
- '𝓈': '𝓈',
- '☆': '☆',
- '⫅': '⫅',
- '⊆': '⊆',
- '≻': '≻',
- '♪': '♪',
- '¹': '¹',
- '²': '²',
- '³': '³',
- '⫆': '⫆',
- '⊇': '⊇',
- 'ß': 'ß',
- '⎴': '⎴',
- '⃛': '⃛',
- 'þ': 'þ',
- '×': '×',
- '∭': '∭',
- '⤨': '⤨',
- '𝕥': '𝕥',
- '⤩': '⤩',
- '≜': '≜',
- '𝓉': '𝓉',
- 'ц': 'ц',
- '⇑': '⇑',
- '⥣': '⥣',
- '↑': '↑',
- 'û': 'û',
- '𝕦': '𝕦',
- 'υ': 'υ',
- '𝓊': '𝓊',
- '▵': '▵',
- 'ü': 'ü',
- '⇕': '⇕',
- '⫨': '⫨',
- '↕': '↕',
- '|': '|',
- '𝕧': '𝕧',
- '𝓋': '𝓋',
- '𝕨': '𝕨',
- '𝓌': '𝓌',
- '⋂': '⋂',
- '⋃': '⋃',
- '⟼': '⟼',
- '⋻': '⋻',
- '𝕩': '𝕩',
- '𝓍': '𝓍',
- '⋁': '⋁',
- 'я': 'я',
- 'ї': 'ї',
- '𝕪': '𝕪',
- '𝓎': '𝓎',
- 'ю': 'ю',
- 'ÿ': 'ÿ',
- 'ż': 'ż',
- 'ζ': 'ζ',
- 'ж': 'ж',
- '𝕫': '𝕫',
- '𝓏': '𝓏',
- '': '',
- '&': '&',
- 'А': 'А',
- '𝔄': '𝔄',
- '⩓': '⩓',
- 'Ä': 'Ä',
- 'Б': 'Б',
- '𝔅': '𝔅',
- '©': '©',
- '⋒': '⋒',
- 'ℭ': 'ℭ',
- 'Χ': 'Χ',
- '⋓': '⋓',
- 'Д': 'Д',
- '∇': '∇',
- '𝔇': '𝔇',
- '¨': '¨',
- 'Ŋ': 'Ŋ',
- 'Ð': 'Ð',
- 'Э': 'Э',
- '𝔈': '𝔈',
- 'Η': 'Η',
- 'Ë': 'Ë',
- 'Ф': 'Ф',
- '𝔉': '𝔉',
- 'Г': 'Г',
- '𝔊': '𝔊',
- '^': '^',
- 'ℌ': 'ℌ',
- 'И': 'И',
- 'ℑ': 'ℑ',
- '∬': '∬',
- 'Ï': 'Ï',
- 'Й': 'Й',
- '𝔍': '𝔍',
- 'К': 'К',
- '𝔎': '𝔎',
- 'Л': 'Л',
- '𝔏': '𝔏',
- '↰': '↰',
- '⤅': '⤅',
- 'М': 'М',
- '𝔐': '𝔐',
- 'Н': 'Н',
- '𝔑': '𝔑',
- '⫬': '⫬',
- 'О': 'О',
- '𝔒': '𝔒',
- 'Ö': 'Ö',
- 'П': 'П',
- '𝔓': '𝔓',
- 'Φ': 'Φ',
- 'Ψ': 'Ψ',
- '"': '"',
- '𝔔': '𝔔',
- '®': '®',
- 'Р': 'Р',
- 'ℜ': 'ℜ',
- 'Ρ': 'Ρ',
- '↱': '↱',
- 'С': 'С',
- '𝔖': '𝔖',
- '⋐': '⋐',
- '∑': '∑',
- '⋑': '⋑',
- '	': '\t',
- 'Τ': 'Τ',
- 'Т': 'Т',
- '𝔗': '𝔗',
- 'У': 'У',
- '𝔘': '𝔘',
- 'Ü': 'Ü',
- 'В': 'В',
- '⋁': '⋁',
- '𝔙': '𝔙',
- '𝔚': '𝔚',
- '𝔛': '𝔛',
- 'Ы': 'Ы',
- '𝔜': '𝔜',
- 'З': 'З',
- 'ℨ': 'ℨ',
- '∾̳': '∾̳',
- '∿': '∿',
- 'а': 'а',
- '𝔞': '𝔞',
- '&': '&',
- '∧': '∧',
- '∠': '∠',
- '⩰': '⩰',
- '≊': '≊',
- '*': '*',
- 'ä': 'ä',
- 'б': 'б',
- '𝔟': '𝔟',
- '=⃥': '=⃥',
- '⊥': '⊥',
- '∩': '∩',
- '¢': '¢',
- '𝔠': '𝔠',
- 'χ': 'χ',
- '○': '○',
- '©': '©',
- '∪': '∪',
- 'д': 'д',
- '°': '°',
- '𝔡': '𝔡',
- '¨': '¨',
- '÷': '÷',
- '˙': '˙',
- 'э': 'э',
- '𝔢': '𝔢',
- '⪖': '⪖',
- 'ℓ': 'ℓ',
- '⪕': '⪕',
- 'ŋ': 'ŋ',
- 'η': 'η',
- 'ð': 'ð',
- 'ë': 'ë',
- 'ф': 'ф',
- '𝔣': '𝔣',
- '⪌': '⪌',
- '⪆': '⪆',
- 'г': 'г',
- '⋛': '⋛',
- '≥': '≥',
- '⩾': '⩾',
- '𝔤': '𝔤',
- '⋙': '⋙',
- '⪒': '⪒',
- '⪥': '⪥',
- '⪤': '⪤',
- '≩': '≩',
- '⪈': '⪈',
- '𝔥': '𝔥',
- 'и': 'и',
- '⇔': '⇔',
- '𝔦': '𝔦',
- '∫': '∫',
- 'ï': 'ï',
- 'й': 'й',
- '𝔧': '𝔧',
- 'к': 'к',
- '𝔨': '𝔨',
- '⪋': '⪋',
- '⪅': '⪅',
- '⪫': '⪫',
- 'л': 'л',
- '⋚': '⋚',
- '≤': '≤',
- '⩽': '⩽',
- '𝔩': '𝔩',
- '⪑': '⪑',
- '≨': '≨',
- '⪇': '⪇',
- '◊': '◊',
- '': '',
- '↰': '↰',
- '¯': '¯',
- '↦': '↦',
- 'м': 'м',
- '𝔪': '𝔪',
- '℧': '℧',
- '∣': '∣',
- '⋙̸': '⋙̸',
- '≫⃒': '≫⃒',
- '⋘̸': '⋘̸',
- '≪⃒': '≪⃒',
- '≉': '≉',
- ' ': ' ',
- 'н': 'н',
- '𝔫': '𝔫',
- '≧̸': '≧̸',
- '≱': '≱',
- '≯': '≯',
- '⋼': '⋼',
- '∋': '∋',
- '≦̸': '≦̸',
- '≰': '≰',
- '≮': '≮',
- '¬': '¬',
- '⊀': '⊀',
- '⊁': '⊁',
- '#': '#',
- 'о': 'о',
- '𝔬': '𝔬',
- '⧁': '⧁',
- 'Ω': 'Ω',
- '⧀': '⧀',
- '⩝': '⩝',
- 'ª': 'ª',
- 'º': 'º',
- '⩛': '⩛',
- 'ö': 'ö',
- '∥': '∥',
- '¶': '¶',
- 'п': 'п',
- '𝔭': '𝔭',
- 'φ': 'φ',
- 'ϖ': 'ϖ',
- '⪳': '⪳',
- '⪯': '⪯',
- 'ψ': 'ψ',
- '𝔮': '𝔮',
- '"': '"',
- 'р': 'р',
- '®': '®',
- '𝔯': '𝔯',
- 'ρ': 'ρ',
- '': '',
- '↱': '↱',
- '⪴': '⪴',
- '⪰': '⪰',
- 'с': 'с',
- '§': '§',
- '𝔰': '𝔰',
- '': '',
- '∼': '∼',
- '⪪': '⪪',
- '/': '/',
- '□': '□',
- '⊂': '⊂',
- '∑': '∑',
- '¹': '¹',
- '²': '²',
- '³': '³',
- '⊃': '⊃',
- 'τ': 'τ',
- 'т': 'т',
- '𝔱': '𝔱',
- '⊤': '⊤',
- 'у': 'у',
- '𝔲': '𝔲',
- '¨': '¨',
- 'ü': 'ü',
- 'в': 'в',
- '∨': '∨',
- '𝔳': '𝔳',
- '𝔴': '𝔴',
- '𝔵': '𝔵',
- 'ы': 'ы',
- '¥': '¥',
- '𝔶': '𝔶',
- 'ÿ': 'ÿ',
- 'з': 'з',
- '𝔷': '𝔷',
- '': '',
- '&': '&',
- 'ⅅ': 'ⅅ',
- 'Ð': 'Ð',
- '>': '>',
- '⋙': '⋙',
- '≫': '≫',
- 'ℑ': 'ℑ',
- '<': '<',
- '⋘': '⋘',
- '≪': '≪',
- 'Μ': 'Μ',
- 'Ν': 'Ν',
- '⩔': '⩔',
- 'Π': 'Π',
- '⪻': '⪻',
- '®': '®',
- 'ℜ': 'ℜ',
- '⪼': '⪼',
- 'Ξ': 'Ξ',
- '∾': '∾',
- '⁡': '',
- '&': '&',
- '≈': '≈',
- 'ⅆ': 'ⅆ',
- '°': '°',
- 'ⅇ': 'ⅇ',
- '⪚': '⪚',
- '⪙': '⪙',
- 'ð': 'ð',
- '≧': '≧',
- '≥': '≥',
- '≫': '≫',
- '≷': '≷',
- '>': '>',
- '⁣': '',
- 'ⅈ': 'ⅈ',
- '∈': '∈',
- '⁢': '',
- '≦': '≦',
- '≤': '≤',
- '≶': '≶',
- '≪': '≪',
- '<': '<',
- '∓': '∓',
- 'μ': 'μ',
- '≠': '≠',
- '∋': '∋',
- '¬': '¬',
- 'ν': 'ν',
- 'Ⓢ': 'Ⓢ',
- '∨': '∨',
- 'π': 'π',
- '±': '±',
- '≺': '≺',
- '®': '®',
- '℞': '℞',
- '≻': '≻',
- '­': '',
- '¨': '¨',
- '℘': '℘',
- '≀': '≀',
- 'ξ': 'ξ',
- '¥': '¥',
- '>': '>',
- '<': '<',
- '>': '>',
- '<': '<'
-};
-
-const ambiguous = new RegExp(
- Object.keys(entities)
- .filter((key) => !key.endsWith(';'))
- .join('|'),
- 'g'
-);
diff --git a/packages/kit/src/utils/entities.spec.js b/packages/kit/src/utils/entities.spec.js
deleted file mode 100644
index ac13571c2f68..000000000000
--- a/packages/kit/src/utils/entities.spec.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { test } from 'uvu';
-import * as assert from 'uvu/assert';
-import { decode_html_entities } from './entities.js';
-
-const tests = [[':-)', ':-)']];
-
-for (const [input, output] of tests) {
- test(input, () => {
- assert.equal(decode_html_entities(input), output);
- });
-}
-
-test.run();
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index 9f0fbe3bbb64..36c29450a549 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -2,9 +2,8 @@ const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
/**
* @param {string} id
- * @param {(encoded: string) => string} decode
*/
-export function parse_route_id(id, decode) {
+export function parse_route_id(id) {
/** @type {string[]} */
const names = [];
@@ -51,6 +50,21 @@ export function parse_route_id(id, decode) {
const result = parts
.map((content, i) => {
if (i % 2) {
+ if (content.startsWith('x+')) {
+ return escape(String.fromCharCode(parseInt(content.slice(2), 16)));
+ }
+
+ if (content.startsWith('u+')) {
+ return escape(
+ String.fromCharCode(
+ ...content
+ .slice(2)
+ .split('-')
+ .map((code) => parseInt(code, 16))
+ )
+ );
+ }
+
const match = param_pattern.exec(content);
if (!match) {
throw new Error(
@@ -71,19 +85,7 @@ export function parse_route_id(id, decode) {
if (is_last && content.includes('.')) add_trailing_slash = false;
- return (
- decode(content) // allow users to specify characters on the file system using HTML entities
- .normalize()
- // escape [ and ] before escaping other characters, since they are used in the replacements
- .replace(/[[\]]/g, '\\$&')
- // replace %, /, ? and # with their encoded versions
- .replace(/%/g, '%25')
- .replace(/\//g, '%2[Ff]')
- .replace(/\?/g, '%3[Ff]')
- .replace(/#/g, '%23')
- // escape characters that have special meaning in regex
- .replace(/[.*+?^${}()|\\]/g, '\\$&')
- );
+ return escape(content);
})
.join('');
@@ -146,3 +148,20 @@ export function exec(match, { names, types, optional }, matchers) {
return params;
}
+
+/** @param {string} str */
+function escape(str) {
+ return (
+ str
+ .normalize()
+ // escape [ and ] before escaping other characters, since they are used in the replacements
+ .replace(/[[\]]/g, '\\$&')
+ // replace %, /, ? and # with their encoded versions
+ .replace(/%/g, '%25')
+ .replace(/\//g, '%2[Ff]')
+ .replace(/\?/g, '%3[Ff]')
+ .replace(/#/g, '%23')
+ // escape characters that have special meaning in regex
+ .replace(/[.*+?^${}()|\\]/g, '\\$&')
+ );
+}
diff --git a/packages/kit/src/utils/routing.spec.js b/packages/kit/src/utils/routing.spec.js
index e06f4f80f5e5..c4b585ac5653 100644
--- a/packages/kit/src/utils/routing.spec.js
+++ b/packages/kit/src/utils/routing.spec.js
@@ -1,6 +1,5 @@
import { test } from 'uvu';
import * as assert from 'uvu/assert';
-import { decode_html_entities } from './entities.js';
import { exec, parse_route_id } from './routing.js';
const tests = {
@@ -68,7 +67,7 @@ const tests = {
for (const [key, expected] of Object.entries(tests)) {
test(`parse_route_id: "${key}"`, () => {
- const actual = parse_route_id(key, decode_html_entities);
+ const actual = parse_route_id(key);
assert.equal(actual.pattern.toString(), expected.pattern.toString());
assert.equal(actual.names, expected.names);
@@ -166,7 +165,7 @@ const exec_tests = [
for (const { path, route, expected } of exec_tests) {
test(`exec extracts params correctly for ${path} from ${route}`, () => {
- const { pattern, names, types, optional } = parse_route_id(route, decode_html_entities);
+ const { pattern, names, types, optional } = parse_route_id(route);
const match = pattern.exec(path);
if (!match) throw new Error(`Failed to match ${path}`);
const actual = exec(
@@ -182,11 +181,8 @@ for (const { path, route, expected } of exec_tests) {
}
test('errors on bad param name', () => {
- assert.throws(() => parse_route_id('abc/[b-c]', decode_html_entities), /Invalid param: b-c/);
- assert.throws(
- () => parse_route_id('abc/[bc=d-e]', decode_html_entities),
- /Invalid param: bc=d-e/
- );
+ assert.throws(() => parse_route_id('abc/[b-c]'), /Invalid param: b-c/);
+ assert.throws(() => parse_route_id('abc/[bc=d-e]'), /Invalid param: bc=d-e/);
});
test.run();
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+82d7]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+82d7]/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/sites/kit.svelte.dev/src/lib/docs/server/index.js b/sites/kit.svelte.dev/src/lib/docs/server/index.js
index 0bfe2b25aac8..a42752773c4c 100644
--- a/sites/kit.svelte.dev/src/lib/docs/server/index.js
+++ b/sites/kit.svelte.dev/src/lib/docs/server/index.js
@@ -10,7 +10,6 @@ import { escape, extract_frontmatter, transform } from './markdown.js';
import { modules } from '../../../../../../packages/kit/docs/types.js';
import { render_modules } from './modules.js';
import { parse_route_id } from '../../../../../../packages/kit/src/utils/routing.js';
-import { decode_html_entities } from '../../../../../../packages/kit/src/utils/entities';
import ts from 'typescript';
import MagicString from 'magic-string';
import { fileURLToPath } from 'url';
@@ -124,7 +123,7 @@ export async function read_file(file) {
);
}
if (source.includes('./$types') && !source.includes('@filename: $types.d.ts')) {
- const params = parse_route_id(options.file || `+page.${language}`, decode_html_entities)
+ const params = parse_route_id(options.file || `+page.${language}`)
.names.map((name) => `${name}: string`)
.join(', ');
From d23bb53c32692785aba086e4dac463205d681053 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 12:33:23 -0500
Subject: [PATCH 17/32] various
---
.../core/sync/create_manifest_data/index.js | 37 ++++++++++++++-----
.../encoded/escape-sequences/+layout.svelte | 1 +
.../[x][x+3c][y]/+page.svelte | 0
packages/kit/test/apps/basics/test/test.js | 6 +++
4 files changed, 35 insertions(+), 9 deletions(-)
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index 2f1329254054..0c8c08c37161 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -102,7 +102,33 @@ function create_routes_and_nodes(cwd, config, fallback) {
* @param {import('types').RouteData | null} parent
*/
const walk = (depth, id, segment, parent) => {
- if (/\]\[/.test(id)) {
+ const unescaped = id.replace(/\[([ux])\+([^\]]+)\]/i, (match, type, code) => {
+ if (match !== match.toLowerCase()) {
+ throw new Error(`Character escape sequence in ${id} should be lowercase`);
+ }
+
+ if (!/[0-9a-f]+/.test(code)) {
+ throw new Error(`Invalid character escape sequence in ${id}`);
+ }
+
+ if (type === 'x') {
+ if (code.length !== 2) {
+ throw new Error(`Hexadecimal escape sequence in ${id} should be two characters`);
+ }
+
+ return String.fromCharCode(parseInt(code, 16));
+ } else {
+ if (code.length < 4 || code.length > 6) {
+ throw new Error(
+ `Unicode escape sequence in ${id} should be between four and six characters`
+ );
+ }
+
+ return String.fromCharCode(parseInt(code, 16));
+ }
+ });
+
+ if (/\]\[/.test(unescaped)) {
throw new Error(`Invalid route ${id} — parameters must be separated`);
}
@@ -469,17 +495,10 @@ function normalize_route_id(id) {
// remove groups
.replace(/(?<=^|\/)\(.+?\)(?=$|\/)/g, '')
- .replace(/\[x\+([0-9a-f]{2})\]/g, (_, x) =>
+ .replace(/\[[ux]\+([0-9a-f]+)\]/g, (_, x) =>
String.fromCharCode(parseInt(x, 16)).replace(/\//g, '%2f')
)
- .replace(/\[u\+([0-9a-f]{4})(?:-([0-9a-f]{4}))?\]/g, (_, u1, u2) =>
- (u2
- ? String.fromCharCode(parseInt(u1, 16), parseInt(u2, 16))
- : String.fromCharCode(parseInt(u1, 16))
- ).replace(/\//g, '%2f')
- )
-
// replace `[param]` with `<*>`, `[param=x]` with ``, and `[[param]]` with `*>`
.replace(
/\[(?:(\[)|(\.\.\.))?.+?(=.+?)?\]\]?/g,
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
index 162bbe77c574..d6853e4baa6f 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
@@ -11,3 +11,4 @@
?
苗
<
+1<2
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index fb75e32debda..655a1d8dcca1 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -344,6 +344,12 @@ test.describe('Encoded paths', () => {
await clicknav('[href="/encoded/escape-sequences/%3f"]');
expect(await page.textContent('h1')).toBe('?');
+ await clicknav('[href="/encoded/escape-sequences/<"]');
+ expect(await page.textContent('h1')).toBe('<');
+
+ await clicknav('[href="/encoded/escape-sequences/1<2"]');
+ expect(await page.textContent('h1')).toBe('1<2');
+
await clicknav('[href="/encoded/escape-sequences/苗"]');
expect(await page.textContent('h1')).toBe('苗');
});
From 96b5b985adb7c3eede121c94368e405149f0fa2d Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 12:38:13 -0500
Subject: [PATCH 18/32] fix unit test
---
.../src/core/sync/create_manifest_data/index.spec.js | 12 ++++++------
.../samples/encoding/{# => [x+22]}/+page.svelte | 0
.../encoding/{? => [x+23]}/+page.svelte | 0
.../samples/encoding/{" => [x+3f]}/+page.svelte | 0
4 files changed, 6 insertions(+), 6 deletions(-)
rename packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/{# => [x+22]}/+page.svelte (100%)
rename packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/{? => [x+23]}/+page.svelte (100%)
rename packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/{" => [x+3f]}/+page.svelte (100%)
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.spec.js b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
index a1c72503d60f..20ac5bb582cb 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.spec.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.spec.js
@@ -184,21 +184,21 @@ test('succeeds when routes does not exist', () => {
test('encodes invalid characters', () => {
const { nodes, routes } = create('samples/encoding');
- const hash = { component: 'samples/encoding/#/+page.svelte' };
- const question_mark = { component: 'samples/encoding/?/+page.svelte' };
- const quote = { component: 'samples/encoding/"/+page.svelte' };
+ const quote = { component: 'samples/encoding/[x+22]/+page.svelte' };
+ const hash = { component: 'samples/encoding/[x+23]/+page.svelte' };
+ const question_mark = { component: 'samples/encoding/[x+3f]/+page.svelte' };
assert.equal(nodes.map(simplify_node), [
default_layout,
default_error,
+ quote,
hash,
- question_mark,
- quote
+ question_mark
]);
assert.equal(
routes.map((p) => p.pattern.toString()),
- [/^\/$/, /^\/%23\/?$/, /^\/%3[Ff]\/?$/, /^\/"\/?$/].map((pattern) => pattern.toString())
+ [/^\/$/, /^\/%3[Ff]\/?$/, /^\/%23\/?$/, /^\/"\/?$/].map((pattern) => pattern.toString())
);
});
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/#/+page.svelte b/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/[x+22]/+page.svelte
similarity index 100%
rename from packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/#/+page.svelte
rename to packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/[x+22]/+page.svelte
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/?/+page.svelte b/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/[x+23]/+page.svelte
similarity index 100%
rename from packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/?/+page.svelte
rename to packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/[x+23]/+page.svelte
diff --git a/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/"/+page.svelte b/packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/[x+3f]/+page.svelte
similarity index 100%
rename from packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/"/+page.svelte
rename to packages/kit/src/core/sync/create_manifest_data/test/samples/encoding/[x+3f]/+page.svelte
From 596ac509880dc9f97d98e11281fe97ef65b14a6a Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 12:42:35 -0500
Subject: [PATCH 19/32] update changeset
---
.changeset/smooth-years-speak.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.changeset/smooth-years-speak.md b/.changeset/smooth-years-speak.md
index 198e613bb44f..12acf52b757d 100644
--- a/.changeset/smooth-years-speak.md
+++ b/.changeset/smooth-years-speak.md
@@ -2,4 +2,4 @@
'@sveltejs/kit': patch
---
-[breaking] require special characters to be HTML-encoded
+[breaking] allow hex/unicode escape sequences in routes
From 2225cd3d233392fa7412a18df3656faba5fabfe7 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 14:12:08 -0500
Subject: [PATCH 20/32] docs
---
.../docs/30-advanced/10-advanced-routing.md | 43 +++++++++++--------
.../encoded/escape-sequences/+layout.svelte | 1 +
.../[u+d83e][u+dd2a]/+page.svelte | 0
packages/kit/test/apps/basics/test/test.js | 3 ++
4 files changed, 29 insertions(+), 18 deletions(-)
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+d83e][u+dd2a]/+page.svelte
diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md
index caf3bd275ebd..79a4d49c7be0 100644
--- a/documentation/docs/30-advanced/10-advanced-routing.md
+++ b/documentation/docs/30-advanced/10-advanced-routing.md
@@ -125,24 +125,31 @@ src/routes/[...catchall]/+page.svelte
Some characters can't be used on the filesystem — `/` on Linux and Mac, `\ / : * ? " < > |` on Windows. The `#` character has special meaning in URLs, and the `[ ] ( )` characters have special meaning to SvelteKit, so these also can't be used directly as part of your route.
-To use these characters in your routes, you can use HTML entities:
-
-- `\` — `\`
-- `/` — `/`
-- `:` — `:`
-- `*` — `*`
-- `?` — `?`
-- `"` — `"`
-- `<` — `<`
-- `>` — `>`
-- `|` — `|`
-- `#` — `#`
-- `[` — `[`
-- `]` — `]`
-- `(` — `(`
-- `)` — `)`
-
-For example, to create a `/:-)` route, you would create a `src/routes/:-)/+page.svelte` file.
+To use these characters in your routes, you can use hexadecimal escape sequences:
+
+- `\` — `[x+5c]`
+- `/` — `[x+2f]`
+- `:` — `[x+3a]`
+- `*` — `[x+2a]`
+- `?` — `[x+3f]`
+- `"` — `[x+22]`
+- `<` — `[x+3c]`
+- `>` — `[x+3e]`
+- `|` — `[x+7c]`
+- `#` — `[x+23]`
+- `[` — `[x+5b]`
+- `]` — `[x+5d]`
+- `(` — `[x+28]`
+- `)` — `[x+29]`
+
+For example, to create a `/:-)` route, you would create a `src/routes/[x+3a]-[x+29]/+page.svelte` file.
+
+You can also use unicode escape sequences. Generally you won't need to as you can use the unencoded character directly, but if — for some reason — you can't use emoji in filenames, for example, then you can use the escaped characters. In other words, these are equivalent:
+
+```
+src/routes/[u+d83e][u+dd2a]/+page.svelte
+src/routes/🤪/+page.svelte
+```
### Advanced layouts
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
index d6853e4baa6f..d52c7ad98a3a 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
@@ -12,3 +12,4 @@
苗
<
1<2
+🤪
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+d83e][u+dd2a]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+d83e][u+dd2a]/+page.svelte
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 655a1d8dcca1..b478de83753f 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -352,6 +352,9 @@ test.describe('Encoded paths', () => {
await clicknav('[href="/encoded/escape-sequences/苗"]');
expect(await page.textContent('h1')).toBe('苗');
+
+ await clicknav('[href="/encoded/escape-sequences/🤪"]');
+ expect(await page.textContent('h1')).toBe('🤪');
});
});
From ff649fea985826b4bb8452f8bf1d3d9e755092a9 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 14:13:08 -0500
Subject: [PATCH 21/32] fix error
---
packages/kit/src/core/sync/create_manifest_data/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index 0c8c08c37161..6f633bb35af7 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -138,7 +138,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
if (/#/.test(segment)) {
// Vite will barf on files with # in them
- throw new Error(`Route ${id} should be renamed ${id.replace(/#/g, '#')}`);
+ throw new Error(`Route ${id} should be renamed ${id.replace(/#/g, '[x+23]')}`);
}
if (/\[\.\.\.\w+\]\/\[\[/.test(id)) {
From 4ce0aadb709d228080455d2bafa32ccc17c72ce2 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 15:12:05 -0500
Subject: [PATCH 22/32] Apply suggestions from code review
Co-authored-by: Dominik G.
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
---
.changeset/smooth-years-speak.md | 2 +-
packages/kit/src/core/sync/create_manifest_data/index.js | 4 ++--
packages/kit/src/utils/routing.js | 1 +
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/.changeset/smooth-years-speak.md b/.changeset/smooth-years-speak.md
index 12acf52b757d..57073177272a 100644
--- a/.changeset/smooth-years-speak.md
+++ b/.changeset/smooth-years-speak.md
@@ -2,4 +2,4 @@
'@sveltejs/kit': patch
---
-[breaking] allow hex/unicode escape sequences in routes
+[breaking] use hex/unicode escape sequences for encoding special characters in route directory names
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index 6f633bb35af7..1c66557869f7 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -102,7 +102,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
* @param {import('types').RouteData | null} parent
*/
const walk = (depth, id, segment, parent) => {
- const unescaped = id.replace(/\[([ux])\+([^\]]+)\]/i, (match, type, code) => {
+ const unescaped = id.replace(/\[([ux])\+([^\]]+)\]/gi, (match, type, code) => {
if (match !== match.toLowerCase()) {
throw new Error(`Character escape sequence in ${id} should be lowercase`);
}
@@ -138,7 +138,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
if (/#/.test(segment)) {
// Vite will barf on files with # in them
- throw new Error(`Route ${id} should be renamed ${id.replace(/#/g, '[x+23]')}`);
+ throw new Error(`Route ${id} should be renamed to ${id.replace(/#/g, '[x+23]')}`);
}
if (/\[\.\.\.\w+\]\/\[\[/.test(id)) {
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index 36c29450a549..b739c26e68f2 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -1,6 +1,7 @@
const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
/**
+ * Creates the regex pattern, extracts parameter names, and generates types for a route
* @param {string} id
*/
export function parse_route_id(id) {
From f757ebd54bee1623f7de03e8ba7dd42bcfacbf6f Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 15:25:54 -0500
Subject: [PATCH 23/32] flesh out docs
---
.../docs/30-advanced/10-advanced-routing.md | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md
index 79a4d49c7be0..2c7d2be9ae38 100644
--- a/documentation/docs/30-advanced/10-advanced-routing.md
+++ b/documentation/docs/30-advanced/10-advanced-routing.md
@@ -125,7 +125,7 @@ src/routes/[...catchall]/+page.svelte
Some characters can't be used on the filesystem — `/` on Linux and Mac, `\ / : * ? " < > |` on Windows. The `#` character has special meaning in URLs, and the `[ ] ( )` characters have special meaning to SvelteKit, so these also can't be used directly as part of your route.
-To use these characters in your routes, you can use hexadecimal escape sequences:
+To use these characters in your routes, you can use hexadecimal escape sequences, which have the format `[x+nn]` where `nn` is a hexadecimal character code:
- `\` — `[x+5c]`
- `/` — `[x+2f]`
@@ -142,15 +142,23 @@ To use these characters in your routes, you can use hexadecimal escape sequences
- `(` — `[x+28]`
- `)` — `[x+29]`
-For example, to create a `/:-)` route, you would create a `src/routes/[x+3a]-[x+29]/+page.svelte` file.
+For example, to create a `/smileys/:-)` route, you would create a `src/routes/smileys/[x+3a]-[x+29]/+page.svelte` file.
-You can also use unicode escape sequences. Generally you won't need to as you can use the unencoded character directly, but if — for some reason — you can't use emoji in filenames, for example, then you can use the escaped characters. In other words, these are equivalent:
+You can determine the hexadecimal code for a character with JavaScript:
+
+```js
+':'.charCodeAt(0).toString(16); // '3a', hence '[x+3a]'
+```
+
+You can also use Unicode escape sequences. Generally you won't need to as you can use the unencoded character directly, but if — for some reason — you can't use emoji in filenames, for example, then you can use the escaped characters. In other words, these are equivalent:
```
src/routes/[u+d83e][u+dd2a]/+page.svelte
src/routes/🤪/+page.svelte
```
+The format for a Unicode escape sequence is `[u+nnnn]` where `nnnn` is a valid value between `0000` and `10ffff`. (Unlike JavaScript string escaping, there's no need to use surrogate pairs to represent code points above `ffff`.) To learn more about Unicode encodings, consult [Programming with Unicode](https://unicodebook.readthedocs.io/unicode_encodings.html).
+
### Advanced layouts
By default, the _layout hierarchy_ mirrors the _route hierarchy_. In some cases, that might not be what you want.
From 727d61f2190ca35538463a5fa8f821f811e3acb6 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 15:29:05 -0500
Subject: [PATCH 24/32] add characters to pages
---
.../src/routes/encoded/escape-sequences/[u+82d7]/+page.svelte | 1 +
.../encoded/escape-sequences/[u+d83e][u+dd2a]/+page.svelte | 1 +
.../src/routes/encoded/escape-sequences/[x+23]/+page.svelte | 1 +
.../src/routes/encoded/escape-sequences/[x+2f]/+page.svelte | 1 +
.../routes/encoded/escape-sequences/[x+3a]-[x+29]/+page.svelte | 1 +
.../src/routes/encoded/escape-sequences/[x+3c]/+page.svelte | 1 +
.../src/routes/encoded/escape-sequences/[x+3f]/+page.svelte | 1 +
.../routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte | 1 +
8 files changed, 8 insertions(+)
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+82d7]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+82d7]/+page.svelte
index e69de29bb2d1..2ca7e14eeac1 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+82d7]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+82d7]/+page.svelte
@@ -0,0 +1 @@
+苗
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+d83e][u+dd2a]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+d83e][u+dd2a]/+page.svelte
index e69de29bb2d1..fe2055e2a85e 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+d83e][u+dd2a]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[u+d83e][u+dd2a]/+page.svelte
@@ -0,0 +1 @@
+🤪
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+23]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+23]/+page.svelte
index e69de29bb2d1..792d6005489e 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+23]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+23]/+page.svelte
@@ -0,0 +1 @@
+#
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+2f]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+2f]/+page.svelte
index e69de29bb2d1..b498fd495d6c 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+2f]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+2f]/+page.svelte
@@ -0,0 +1 @@
+/
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3a]-[x+29]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3a]-[x+29]/+page.svelte
index e69de29bb2d1..4671195863f2 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3a]-[x+29]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3a]-[x+29]/+page.svelte
@@ -0,0 +1 @@
+:-)
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3c]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3c]/+page.svelte
index e69de29bb2d1..dd3d9a7f0440 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3c]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3c]/+page.svelte
@@ -0,0 +1 @@
+<
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3f]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3f]/+page.svelte
index e69de29bb2d1..a1e2647d215e 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3f]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+3f]/+page.svelte
@@ -0,0 +1 @@
+?
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte
index e69de29bb2d1..3a66ce3b28d3 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte
@@ -0,0 +1 @@
+1<2
\ No newline at end of file
From 49687670e4f6b0f5f5c9fc580f757561e3fc26bc Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 15:47:14 -0500
Subject: [PATCH 25/32] ffs
---
.../routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte
index 3a66ce3b28d3..974d0d231145 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x][x+3c][y]/+page.svelte
@@ -1 +1 @@
-1<2
\ No newline at end of file
+1<2
From 85eeabe3829f6de461ec6ce65ec0666353714847 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 17:20:05 -0500
Subject: [PATCH 26/32] change should to must
---
packages/kit/src/core/sync/create_manifest_data/index.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/kit/src/core/sync/create_manifest_data/index.js b/packages/kit/src/core/sync/create_manifest_data/index.js
index 1c66557869f7..0da27b59f0d1 100644
--- a/packages/kit/src/core/sync/create_manifest_data/index.js
+++ b/packages/kit/src/core/sync/create_manifest_data/index.js
@@ -104,7 +104,7 @@ function create_routes_and_nodes(cwd, config, fallback) {
const walk = (depth, id, segment, parent) => {
const unescaped = id.replace(/\[([ux])\+([^\]]+)\]/gi, (match, type, code) => {
if (match !== match.toLowerCase()) {
- throw new Error(`Character escape sequence in ${id} should be lowercase`);
+ throw new Error(`Character escape sequence in ${id} must be lowercase`);
}
if (!/[0-9a-f]+/.test(code)) {
@@ -113,14 +113,14 @@ function create_routes_and_nodes(cwd, config, fallback) {
if (type === 'x') {
if (code.length !== 2) {
- throw new Error(`Hexadecimal escape sequence in ${id} should be two characters`);
+ throw new Error(`Hexadecimal escape sequence in ${id} must be two characters`);
}
return String.fromCharCode(parseInt(code, 16));
} else {
if (code.length < 4 || code.length > 6) {
throw new Error(
- `Unicode escape sequence in ${id} should be between four and six characters`
+ `Unicode escape sequence in ${id} must be between four and six characters`
);
}
From 460b53f35276a16fe9e7d0c7bfb5b48bdd7dbadc Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 17:28:07 -0500
Subject: [PATCH 27/32] add % test and docs
---
documentation/docs/30-advanced/10-advanced-routing.md | 3 ++-
.../src/routes/encoded/escape-sequences/[x+25]/+page.svelte | 1 +
packages/kit/test/apps/basics/test/test.js | 3 +++
3 files changed, 6 insertions(+), 1 deletion(-)
create mode 100644 packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+25]/+page.svelte
diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md
index 2c7d2be9ae38..20b52742fa63 100644
--- a/documentation/docs/30-advanced/10-advanced-routing.md
+++ b/documentation/docs/30-advanced/10-advanced-routing.md
@@ -123,7 +123,7 @@ src/routes/[...catchall]/+page.svelte
### Encoding
-Some characters can't be used on the filesystem — `/` on Linux and Mac, `\ / : * ? " < > |` on Windows. The `#` character has special meaning in URLs, and the `[ ] ( )` characters have special meaning to SvelteKit, so these also can't be used directly as part of your route.
+Some characters can't be used on the filesystem — `/` on Linux and Mac, `\ / : * ? " < > |` on Windows. The `#` and `%` characters have special meaning in URLs, and the `[ ] ( )` characters have special meaning to SvelteKit, so these also can't be used directly as part of your route.
To use these characters in your routes, you can use hexadecimal escape sequences, which have the format `[x+nn]` where `nn` is a hexadecimal character code:
@@ -137,6 +137,7 @@ To use these characters in your routes, you can use hexadecimal escape sequences
- `>` — `[x+3e]`
- `|` — `[x+7c]`
- `#` — `[x+23]`
+- `%` — `[x+25]`
- `[` — `[x+5b]`
- `]` — `[x+5d]`
- `(` — `[x+28]`
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+25]/+page.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+25]/+page.svelte
new file mode 100644
index 000000000000..314d73bf4f57
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/[x+25]/+page.svelte
@@ -0,0 +1 @@
+%
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index b478de83753f..188e9ac45779 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -344,6 +344,9 @@ test.describe('Encoded paths', () => {
await clicknav('[href="/encoded/escape-sequences/%3f"]');
expect(await page.textContent('h1')).toBe('?');
+ await clicknav('[href="/encoded/escape-sequences/%"]');
+ expect(await page.textContent('h1')).toBe('%');
+
await clicknav('[href="/encoded/escape-sequences/<"]');
expect(await page.textContent('h1')).toBe('<');
From 60ca550797487a2a2b18675f27ee4e8b87f63fe6 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 17:42:08 -0500
Subject: [PATCH 28/32] fixes
---
documentation/docs/30-advanced/10-advanced-routing.md | 2 +-
.../basics/src/routes/encoded/escape-sequences/+layout.svelte | 1 +
packages/kit/test/apps/basics/test/test.js | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md
index 20b52742fa63..d60302bb0365 100644
--- a/documentation/docs/30-advanced/10-advanced-routing.md
+++ b/documentation/docs/30-advanced/10-advanced-routing.md
@@ -151,7 +151,7 @@ You can determine the hexadecimal code for a character with JavaScript:
':'.charCodeAt(0).toString(16); // '3a', hence '[x+3a]'
```
-You can also use Unicode escape sequences. Generally you won't need to as you can use the unencoded character directly, but if — for some reason — you can't use emoji in filenames, for example, then you can use the escaped characters. In other words, these are equivalent:
+You can also use Unicode escape sequences. Generally you won't need to as you can use the unencoded character directly, but if — for some reason — you can't have a filename with an emoji in it, for example, then you can use the escaped characters. In other words, these are equivalent:
```
src/routes/[u+d83e][u+dd2a]/+page.svelte
diff --git a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
index d52c7ad98a3a..47f4385e87b1 100644
--- a/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
+++ b/packages/kit/test/apps/basics/src/routes/encoded/escape-sequences/+layout.svelte
@@ -13,3 +13,4 @@
<
1<2
🤪
+%
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 188e9ac45779..46a003d51f65 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -344,7 +344,7 @@ test.describe('Encoded paths', () => {
await clicknav('[href="/encoded/escape-sequences/%3f"]');
expect(await page.textContent('h1')).toBe('?');
- await clicknav('[href="/encoded/escape-sequences/%"]');
+ await clicknav('[href="/encoded/escape-sequences/%25"]');
expect(await page.textContent('h1')).toBe('%');
await clicknav('[href="/encoded/escape-sequences/<"]');
From 367fed0f76c22ce3a9b003afdaa802394b558a45 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 17:54:23 -0500
Subject: [PATCH 29/32] fix weird completely unrelated typechecking errors, wtf
---
.../basics/src/routes/load/cors/server-only/+page.server.js | 2 +-
.../apps/basics/src/routes/redirect/missing-status/b/+page.js | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.server.js b/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.server.js
index 9fbd10aed9bf..7a412012c0e6 100644
--- a/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.server.js
+++ b/packages/kit/test/apps/basics/src/routes/load/cors/server-only/+page.server.js
@@ -1,4 +1,4 @@
-/** @type {import('./$types').PageLoad} */
+/** @type {import('./$types').PageServerLoad} */
export async function load({ fetch, url }) {
const res = await fetch(`http://localhost:${url.searchParams.get('port')}`);
diff --git a/packages/kit/test/apps/basics/src/routes/redirect/missing-status/b/+page.js b/packages/kit/test/apps/basics/src/routes/redirect/missing-status/b/+page.js
index 5aba1d550606..c269fa152b86 100644
--- a/packages/kit/test/apps/basics/src/routes/redirect/missing-status/b/+page.js
+++ b/packages/kit/test/apps/basics/src/routes/redirect/missing-status/b/+page.js
@@ -1,5 +1,6 @@
import { redirect } from '@sveltejs/kit';
export function load() {
+ // @ts-ignore
throw redirect(555, './a');
}
From bc8b26c2de6c5ab06b8a65165ed290361867625a Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 18:02:35 -0500
Subject: [PATCH 30/32] pretty sure we can get rid of decode_pathname
---
packages/kit/src/runtime/client/client.js | 10 ++--------
packages/kit/src/runtime/server/index.js | 3 +--
packages/kit/src/utils/routing.js | 2 +-
packages/kit/src/utils/url.js | 8 --------
4 files changed, 4 insertions(+), 19 deletions(-)
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 583457a93106..fbae953f5efe 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -1,11 +1,5 @@
import { onMount, tick } from 'svelte';
-import {
- make_trackable,
- decode_pathname,
- decode_params,
- normalize_path,
- add_data_suffix
-} from '../../utils/url.js';
+import { make_trackable, decode_params, normalize_path, add_data_suffix } from '../../utils/url.js';
import { find_anchor, get_base_uri, scroll_state } from './utils.js';
import {
lock_fetch,
@@ -993,7 +987,7 @@ export function create_client({ target, base, trailing_slash }) {
function get_navigation_intent(url, invalidating) {
if (is_external_url(url)) return;
- const path = decode_pathname(url.pathname.slice(base.length) || '/');
+ const path = decodeURI(url.pathname.slice(base.length) || '/');
for (const route of routes) {
const params = route.exec(path);
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index d0e9ab2dced0..6e395ee64bd5 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -5,7 +5,6 @@ import { respond_with_error } from './page/respond_with_error.js';
import { is_form_content_type } from '../../utils/http.js';
import { GENERIC_ERROR, handle_fatal_error, redirect_response } from './utils.js';
import {
- decode_pathname,
decode_params,
disable_search,
has_data_suffix,
@@ -44,7 +43,7 @@ export async function respond(request, options, state) {
let decoded;
try {
- decoded = decode_pathname(url.pathname);
+ decoded = decodeURI(url.pathname);
} catch {
return new Response('Malformed URI', { status: 400 });
}
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index b739c26e68f2..6d69deb92462 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -158,7 +158,7 @@ function escape(str) {
// escape [ and ] before escaping other characters, since they are used in the replacements
.replace(/[[\]]/g, '\\$&')
// replace %, /, ? and # with their encoded versions
- .replace(/%/g, '%25')
+ // .replace(/%/g, '%25')
.replace(/\//g, '%2[Ff]')
.replace(/\?/g, '%3[Ff]')
.replace(/#/g, '%23')
diff --git a/packages/kit/src/utils/url.js b/packages/kit/src/utils/url.js
index db4e6d882137..e167b5a316c9 100644
--- a/packages/kit/src/utils/url.js
+++ b/packages/kit/src/utils/url.js
@@ -54,14 +54,6 @@ export function normalize_path(path, trailing_slash) {
return path;
}
-/**
- * Decode pathname excluding %25 to prevent further double decoding of params
- * @param {string} pathname
- */
-export function decode_pathname(pathname) {
- return pathname.split('%25').map(decodeURI).join('%25');
-}
-
/** @param {Record} params */
export function decode_params(params) {
for (const key in params) {
From d3586b562a545a52d4d3378a7ff8faa385c6a059 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 18:09:42 -0500
Subject: [PATCH 31/32] fucked around, find out. revert revert revert
This reverts commit bc8b26c2de6c5ab06b8a65165ed290361867625a.
---
packages/kit/src/runtime/client/client.js | 10 ++++++++--
packages/kit/src/runtime/server/index.js | 3 ++-
packages/kit/src/utils/routing.js | 2 +-
packages/kit/src/utils/url.js | 8 ++++++++
4 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index fbae953f5efe..583457a93106 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -1,5 +1,11 @@
import { onMount, tick } from 'svelte';
-import { make_trackable, decode_params, normalize_path, add_data_suffix } from '../../utils/url.js';
+import {
+ make_trackable,
+ decode_pathname,
+ decode_params,
+ normalize_path,
+ add_data_suffix
+} from '../../utils/url.js';
import { find_anchor, get_base_uri, scroll_state } from './utils.js';
import {
lock_fetch,
@@ -987,7 +993,7 @@ export function create_client({ target, base, trailing_slash }) {
function get_navigation_intent(url, invalidating) {
if (is_external_url(url)) return;
- const path = decodeURI(url.pathname.slice(base.length) || '/');
+ const path = decode_pathname(url.pathname.slice(base.length) || '/');
for (const route of routes) {
const params = route.exec(path);
diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js
index 6e395ee64bd5..d0e9ab2dced0 100644
--- a/packages/kit/src/runtime/server/index.js
+++ b/packages/kit/src/runtime/server/index.js
@@ -5,6 +5,7 @@ import { respond_with_error } from './page/respond_with_error.js';
import { is_form_content_type } from '../../utils/http.js';
import { GENERIC_ERROR, handle_fatal_error, redirect_response } from './utils.js';
import {
+ decode_pathname,
decode_params,
disable_search,
has_data_suffix,
@@ -43,7 +44,7 @@ export async function respond(request, options, state) {
let decoded;
try {
- decoded = decodeURI(url.pathname);
+ decoded = decode_pathname(url.pathname);
} catch {
return new Response('Malformed URI', { status: 400 });
}
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index 6d69deb92462..b739c26e68f2 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -158,7 +158,7 @@ function escape(str) {
// escape [ and ] before escaping other characters, since they are used in the replacements
.replace(/[[\]]/g, '\\$&')
// replace %, /, ? and # with their encoded versions
- // .replace(/%/g, '%25')
+ .replace(/%/g, '%25')
.replace(/\//g, '%2[Ff]')
.replace(/\?/g, '%3[Ff]')
.replace(/#/g, '%23')
diff --git a/packages/kit/src/utils/url.js b/packages/kit/src/utils/url.js
index e167b5a316c9..db4e6d882137 100644
--- a/packages/kit/src/utils/url.js
+++ b/packages/kit/src/utils/url.js
@@ -54,6 +54,14 @@ export function normalize_path(path, trailing_slash) {
return path;
}
+/**
+ * Decode pathname excluding %25 to prevent further double decoding of params
+ * @param {string} pathname
+ */
+export function decode_pathname(pathname) {
+ return pathname.split('%25').map(decodeURI).join('%25');
+}
+
/** @param {Record} params */
export function decode_params(params) {
for (const key in params) {
From c697df70c2d3755ec6ac97f788b5060c40b4d4c8 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Mon, 14 Nov 2022 18:36:42 -0500
Subject: [PATCH 32/32] Update packages/kit/src/utils/routing.js
Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
---
packages/kit/src/utils/routing.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kit/src/utils/routing.js b/packages/kit/src/utils/routing.js
index b739c26e68f2..7991499df119 100644
--- a/packages/kit/src/utils/routing.js
+++ b/packages/kit/src/utils/routing.js
@@ -157,7 +157,7 @@ function escape(str) {
.normalize()
// escape [ and ] before escaping other characters, since they are used in the replacements
.replace(/[[\]]/g, '\\$&')
- // replace %, /, ? and # with their encoded versions
+ // replace %, /, ? and # with their encoded versions because decode_pathname leaves them untouched
.replace(/%/g, '%25')
.replace(/\//g, '%2[Ff]')
.replace(/\?/g, '%3[Ff]')