@endevops/effect-codec-xml 0.1.0-beta.1 → 0.1.0-effect.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -63
- package/dist/codec.d.ts +9 -17
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +16 -25
- package/dist/codec.js.map +1 -1
- package/dist/conventions.d.ts +4 -4
- package/dist/conventions.js +7 -7
- package/dist/conventions.js.map +1 -1
- package/dist/entities/entity-decoder.d.ts +33 -33
- package/dist/entities/entity-decoder.d.ts.map +1 -1
- package/dist/entities/entity-decoder.js +64 -63
- package/dist/entities/entity-decoder.js.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/namespaces.js +2 -2
- package/dist/namespaces.js.map +1 -1
- package/dist/naming.d.ts +6 -6
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +3 -3
- package/dist/naming.js.map +1 -1
- package/dist/parse.d.ts +5 -5
- package/dist/parse.js +14 -14
- package/dist/parse.js.map +1 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +28 -28
- package/dist/render.js.map +1 -1
- package/dist/xml-error.d.ts +9 -9
- package/dist/xml-error.js +18 -18
- package/dist/xml-error.js.map +1 -1
- package/dist/xml-value.d.ts +7 -7
- package/dist/xml-value.d.ts.map +1 -1
- package/dist/xml-value.js +7 -6
- package/dist/xml-value.js.map +1 -1
- package/package.json +1 -1
- package/src/codec.ts +71 -91
- package/src/conventions.ts +7 -7
- package/src/entities/entity-decoder.ts +99 -98
- package/src/errors.ts +3 -3
- package/src/index.ts +3 -3
- package/src/namespaces.ts +16 -16
- package/src/naming.ts +35 -34
- package/src/parse.ts +26 -26
- package/src/render.ts +44 -44
- package/src/xml-error.ts +18 -18
- package/src/xml-value.ts +11 -10
|
@@ -7,8 +7,8 @@ import { Effect } from "effect";
|
|
|
7
7
|
*/
|
|
8
8
|
export type EntityHookAction = 'allow' | 'block' | 'throw';
|
|
9
9
|
/**
|
|
10
|
-
* @description A function-valued entity replacement: the `val` of the legacy `{ regex, val }` form when it is not a string. This decoder cannot use one
|
|
11
|
-
* function has no meaning without the regex it was meant to be matched against
|
|
10
|
+
* @description A function-valued entity replacement: the `val` of the legacy `{ regex, val }` form when it is not a string. This decoder cannot use one — a
|
|
11
|
+
* function has no meaning without the regex it was meant to be matched against — so such an entry is dropped at registration rather than expanded.
|
|
12
12
|
*/
|
|
13
13
|
export type EntityValFn = (match: string, captured: string, ...rest: Array<unknown>) => string;
|
|
14
14
|
/**
|
|
@@ -41,15 +41,15 @@ export declare const ENTITY_ACTION: Readonly<{
|
|
|
41
41
|
/**
|
|
42
42
|
* @description Which entity categories count toward the expansion limits.
|
|
43
43
|
*
|
|
44
|
-
* - `'external'
|
|
45
|
-
* - `'base'
|
|
46
|
-
* - `'all'
|
|
47
|
-
* - `Array<'external' | 'base'
|
|
44
|
+
* - `'external'` — only input/runtime + persistent external entities. The default, and the only one that ignores the built-in XML entities.
|
|
45
|
+
* - `'base'` — only the built-in XML entities, the caller's `namedEntities`, and numeric references.
|
|
46
|
+
* - `'all'` — every entity regardless of tier.
|
|
47
|
+
* - `Array<'external' | 'base'>` — an explicit combination. An empty array is honoured literally: nothing counts, so the limits can never trip.
|
|
48
48
|
*/
|
|
49
49
|
export type ApplyLimitsTo = 'external' | 'base' | 'all' | Array<'external' | 'base'>;
|
|
50
50
|
/**
|
|
51
51
|
* @description Ceilings on what a single document's entity references may cost. Both are cumulative across {@link EntityDecoder.decode} calls until
|
|
52
|
-
* {@link EntityDecoder.reset}, and both default to `0`, meaning unlimited. `0
|
|
52
|
+
* {@link EntityDecoder.reset}, and both default to `0`, meaning unlimited. `0` — and any negative or non-numeric value — is unlimited, because the
|
|
53
53
|
* runtime tests `> 0` rather than truthiness of the configured number.
|
|
54
54
|
*/
|
|
55
55
|
export interface EntityDecoderLimitOptions {
|
|
@@ -68,8 +68,8 @@ export interface EntityDecoderLimitOptions {
|
|
|
68
68
|
*/
|
|
69
69
|
maxExpandedLength?: number;
|
|
70
70
|
/**
|
|
71
|
-
* @description Which tiers count against both limits. Defaults to `'external'`, which keeps the built-in entities
|
|
72
|
-
* able to trip a limit on a document the caller already trusts.
|
|
71
|
+
* @description Which tiers count against both limits. Defaults to `'external'`, which is what keeps the built-in entities — including every numeric reference —
|
|
72
|
+
* from being able to trip a limit on a document the caller already trusts.
|
|
73
73
|
*
|
|
74
74
|
* @default 'external'
|
|
75
75
|
*/
|
|
@@ -81,15 +81,15 @@ export interface EntityDecoderLimitOptions {
|
|
|
81
81
|
*/
|
|
82
82
|
export interface EntityDecoderNCROptions {
|
|
83
83
|
/**
|
|
84
|
-
* @description The XML version whose codepoint restrictions apply. `1.0` prohibits the C0 controls U+0001
|
|
84
|
+
* @description The XML version whose codepoint restrictions apply. `1.0` prohibits the C0 controls U+0001–U+001F other than tab, newline and carriage return;
|
|
85
85
|
* `1.1` does not, since it permits them when written as references. Any value other than `1.1` is read as `1.0`.
|
|
86
86
|
*
|
|
87
87
|
* @default 1.0
|
|
88
88
|
*/
|
|
89
89
|
xmlVersion?: 1.0 | 1.1;
|
|
90
90
|
/**
|
|
91
|
-
* @description The base action for every numeric reference. Codepoint ranges that carry a minimum
|
|
92
|
-
* null under `nullNCR`
|
|
91
|
+
* @description The base action for every numeric reference. Codepoint ranges that carry a minimum — surrogates always, the XML 1.0 C0 controls under `1.0`, and
|
|
92
|
+
* null under `nullNCR` — take the stricter of the two, so this is a floor and not an override.
|
|
93
93
|
*
|
|
94
94
|
* @default 'allow'
|
|
95
95
|
*/
|
|
@@ -107,10 +107,10 @@ export interface EntityDecoderNCROptions {
|
|
|
107
107
|
export interface EntityDecoderOptions {
|
|
108
108
|
/**
|
|
109
109
|
* @description Extra named entities merged into the `base` map alongside the five XML predefined ones. A string value is used directly; a `{ regex, val }` or `{
|
|
110
|
-
* regx, val }` envelope is unwrapped to its `val`. Anything else
|
|
111
|
-
* leaving the name unresolvable rather than failing the construction. Upstream's documentation says a value containing `&` is skipped
|
|
112
|
-
* prevent recursive expansion. It is not: the code stores the value unchanged, and only {@link EntityDecoder.addExternalEntity} checks for
|
|
113
|
-
* Preserved as-is; see the note on the class.
|
|
110
|
+
* regx, val }` envelope is unwrapped to its `val`. Anything else — a number, `null`, a function, an envelope whose `val` is a function — is
|
|
111
|
+
* dropped, leaving the name unresolvable rather than failing the construction. Upstream's documentation says a value containing `&` is skipped
|
|
112
|
+
* here, to prevent recursive expansion. It is not: the code stores the value unchanged, and only {@link EntityDecoder.addExternalEntity} checks for
|
|
113
|
+
* `&`. Preserved as-is; see the note on the class.
|
|
114
114
|
*
|
|
115
115
|
* @default null
|
|
116
116
|
*/
|
|
@@ -120,7 +120,7 @@ export interface EntityDecoderOptions {
|
|
|
120
120
|
}> | null;
|
|
121
121
|
/**
|
|
122
122
|
* @description Called once on the finished string. Receives `(resolved, original)` and must return a string; return `original` to reject the expansion outright,
|
|
123
|
-
* or a sanitised form of `resolved` to clean it. It is _not_ called for a string that never reaches the scanning loop
|
|
123
|
+
* or a sanitised form of `resolved` to clean it. It is _not_ called for a string that never reaches the scanning loop — an empty string, a
|
|
124
124
|
* non-string, or any string with no `&` in it. A caller relying on `postCheck` to sanitise therefore has to know that a string with no ampersand is
|
|
125
125
|
* never inspected.
|
|
126
126
|
*
|
|
@@ -128,8 +128,8 @@ export interface EntityDecoderOptions {
|
|
|
128
128
|
*/
|
|
129
129
|
postCheck?: ((resolved: string, original: string) => string) | null;
|
|
130
130
|
/**
|
|
131
|
-
* @description Whether numeric references expand at all. Turning it off leaves every one of them in the output verbatim
|
|
132
|
-
* minimum action of `remove` or stricter, which are still handled, because that classification runs first
|
|
131
|
+
* @description Whether numeric references expand at all. Turning it off leaves every one of them in the output verbatim — _except_ the codepoints that carry a
|
|
132
|
+
* minimum action of `remove` or stricter, which are still handled, because that classification runs first and is what makes the option safe to rely
|
|
133
133
|
* on.
|
|
134
134
|
*
|
|
135
135
|
* @default true
|
|
@@ -144,7 +144,7 @@ export interface EntityDecoderOptions {
|
|
|
144
144
|
/**
|
|
145
145
|
* @description Names to delete outright, matched the same way as {@link EntityDecoderOptions.leave}. A removed reference is charged to the `external` tier even
|
|
146
146
|
* when the name is a built-in one, so a document full of removed built-ins can trip an `applyLimitsTo: 'external'` limit it would not otherwise be
|
|
147
|
-
* subject to. Preserved as-is; the only in-code comment claims the charge is for unknown references, which
|
|
147
|
+
* subject to. Preserved as-is; the only in-code comment claims the charge is for unknown references, which is not what distinguishes them.
|
|
148
148
|
*
|
|
149
149
|
* @default [ ]
|
|
150
150
|
*/
|
|
@@ -167,7 +167,7 @@ export interface EntityDecoderOptions {
|
|
|
167
167
|
onExternalEntity?: EntityRegistrationHook | null;
|
|
168
168
|
/**
|
|
169
169
|
* @description Called once per entity as it is registered through {@link EntityDecoder.addInputEntities}. Same contract as
|
|
170
|
-
* {@link EntityDecoderOptions.onExternalEntity}, and unlike it the hook is not the only filter
|
|
170
|
+
* {@link EntityDecoderOptions.onExternalEntity}, and unlike it the hook is not the only filter — see the class note on name validation.
|
|
171
171
|
*
|
|
172
172
|
* @default null
|
|
173
173
|
*/
|
|
@@ -178,25 +178,25 @@ export interface EntityDecoderOptions {
|
|
|
178
178
|
*
|
|
179
179
|
* ### Entity lookup priority
|
|
180
180
|
*
|
|
181
|
-
* 1. **input / runtime
|
|
182
|
-
* 2. **persistent external
|
|
181
|
+
* 1. **input / runtime** — injected per document through {@link EntityDecoder.addInputEntities}
|
|
182
|
+
* 2. **persistent external** — set through {@link EntityDecoder.setExternalEntities} and {@link EntityDecoder.addExternalEntity}, surviving
|
|
183
183
|
* {@link EntityDecoder.reset}
|
|
184
|
-
* 3. **base
|
|
184
|
+
* 3. **base** — the five XML predefined entities plus the constructor's `namedEntities` Both input and external resolve as the `external` tier for limit
|
|
185
185
|
* purposes, because both are injected at runtime. Numeric references (`&#NNN;`, `&#xHH;`) resolve directly through `String.fromCodePoint` and are
|
|
186
186
|
* always `base` tier: they cannot recurse, so a limit that counted them would only punish a document that spells its characters out.
|
|
187
187
|
*
|
|
188
188
|
* ### Upstream behaviour preserved
|
|
189
189
|
*
|
|
190
|
-
* Several quirks of the original are kept
|
|
190
|
+
* Several quirks of the original are kept deliberately, because a consumer's output already depends on them:
|
|
191
191
|
*
|
|
192
192
|
* - A value containing `&` is **not** filtered from `namedEntities` or `setExternalEntities`, contrary to the documentation. Only
|
|
193
193
|
* {@link EntityDecoder.addExternalEntity} checks, and it drops the entry rather than storing it, so the same name registered either way can resolve
|
|
194
194
|
* to nothing.
|
|
195
|
-
* - {@link EntityDecoderOptions.postCheck} is skipped entirely for input that never reaches the scan
|
|
195
|
+
* - {@link EntityDecoderOptions.postCheck} is skipped entirely for input that never reaches the scan — an empty string, a non-string, or a string with
|
|
196
196
|
* no `&`.
|
|
197
197
|
* - {@link EntityDecoder.decode} returns a non-string argument unchanged, despite being typed `string`.
|
|
198
198
|
* - The expansion-limit errors are prefixed `EntityReplacer`, not `EntityDecoder`.
|
|
199
|
-
* - Nothing in XML 1.0 §2.2 is enforced for U+007F
|
|
199
|
+
* - Nothing in XML 1.0 §2.2 is enforced for U+007F–U+009F or for the U+FFFE/U+FFFF noncharacters, and the sweep for `&` leaves a name of
|
|
200
200
|
* {@link MAX_TOKEN_LENGTH} + 1 characters unresolvable.
|
|
201
201
|
* - Numeric references are parsed with `parseInt`, so a leading space, sign, or trailing garbage is accepted: `&# 41;`, `&#x+41;` and `)zz;` all
|
|
202
202
|
* decode, and `�x41;` parses as a null reference rather than `A`.
|
|
@@ -210,7 +210,7 @@ export interface EntityDecoderOptions {
|
|
|
210
210
|
* decoder.addInputEntities({ version: '1.0' });
|
|
211
211
|
*
|
|
212
212
|
* decoder.decode('&brand; v&version; ©'); // 'Acme v1.0 ©'
|
|
213
|
-
* decoder.decode('&#38;'); // '&&'
|
|
213
|
+
* decoder.decode('&#38;'); // '&&' — one pass, the output is never re-scanned
|
|
214
214
|
*
|
|
215
215
|
* decoder.reset(); // drops the input entities and the counters, keeps the external ones
|
|
216
216
|
* ```;
|
|
@@ -219,7 +219,7 @@ export declare class EntityDecoder {
|
|
|
219
219
|
#private;
|
|
220
220
|
/**
|
|
221
221
|
* @description Create a decoder. A factory rather than a constructor, because it refuses a `null` options object. Every field is optional, so `null` is not "a
|
|
222
|
-
* decoder with the defaults"
|
|
222
|
+
* decoder with the defaults" — a caller who wrote it meant something the signature does not allow, and a decoder built from it would be
|
|
223
223
|
* indistinguishable from one built from `{}` while hiding the mistake. Saying so is worth a factory; `EntityDecoderOptions` is a plain object and
|
|
224
224
|
* nothing else about construction can fail.
|
|
225
225
|
*
|
|
@@ -236,8 +236,8 @@ export declare class EntityDecoder {
|
|
|
236
236
|
static make: (options?: EntityDecoderOptions) => Effect.Effect<EntityDecoder, XmlError>;
|
|
237
237
|
/**
|
|
238
238
|
* @description Create a decoder. Every option is resolved here into the flat fields the decode loop reads, so nothing per-reference has to re-derive it. The
|
|
239
|
-
* options whose wrong type disables them rather than failing the construction
|
|
240
|
-
* and {@link readNameList}, so that rule is written once instead of four times.
|
|
239
|
+
* options whose wrong type disables them rather than failing the construction — the two hooks, the two name lists — are read through
|
|
240
|
+
* {@link readHook} and {@link readNameList}, so that rule is written once instead of four times.
|
|
241
241
|
*
|
|
242
242
|
* @param resolved - Configuration, already checked. See {@link EntityDecoderOptions}.
|
|
243
243
|
*/
|
|
@@ -305,14 +305,14 @@ export declare class EntityDecoder {
|
|
|
305
305
|
/**
|
|
306
306
|
* @description Expand every entity reference in a string, in one pass. The output is never re-scanned, so no expansion can produce a _second_ one: a registered
|
|
307
307
|
* value that itself contains reference text reaches the caller as that literal text, unexpanded. What the limits bound is the growth of this single
|
|
308
|
-
* pass
|
|
308
|
+
* pass — how much one round of expansion can add. Three inputs return before the scan and therefore never reach
|
|
309
309
|
* {@link EntityDecoderOptions.postCheck}: a non-string, the empty string, and any string with no `&` in it. The scan itself is `#expandAll`; what
|
|
310
310
|
* this method adds is the three inputs that skip it and the single join of what it collected.
|
|
311
311
|
*
|
|
312
312
|
* @example
|
|
313
313
|
* ```typescript
|
|
314
314
|
* import { Effect } from 'effect';
|
|
315
|
-
* import { EntityDecoder } from '@endevops/effect-codec
|
|
315
|
+
* import { EntityDecoder } from '@endevops/effect-xml-codec';
|
|
316
316
|
*
|
|
317
317
|
* const decoder = new EntityDecoder({ namedEntities: { copy: '©' } });
|
|
318
318
|
* Effect.runSync(Effect.orElseSucceed(decoder.addExternalEntity('brand', 'Acme'), () => undefined));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-decoder.d.ts","names":[],"sources":["../../src/entities/entity-decoder.ts"],"mappings":";;;;;;;YA6HY;;;;;YAMA,eAAe,eAAe,qBAAqB,MAAM;;;;;;;;;;;YAYzD,0BAA0B,cAAc,kBAAkB;;;;;;;;;;;;qBAazD,eAAe;EAAW;EAAgB;EAAgB;;;;;;;;;;YAkB3D,8CAA8C;;;;;;iBAOzC;;;;;;;EAOf;;;;;;;EAQA;;;;;;;EAQA,gBAAgB;;;;;;iBAOD;;;;;;;EAOf;;;;;;;EAQA;;;;;;EAOA;;;;;iBAMe;;;;;;;;;;EAUf,gBAAgB;IAA0B,OAAO;IAAQ,cAAc;;;;;;;;;;EAUvE,cAAc,kBAAkB;;;;;;;;EAShC;;;;;;EAOA,QAAQ;;;;;;;;EASR,SAAS;;;;EAKT,QAAQ;;;;EAKR,MAAM;;;;;;;;EASN,mBAAmB;;;;;;;EAQnB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"entity-decoder.d.ts","names":[],"sources":["../../src/entities/entity-decoder.ts"],"mappings":";;;;;;;YA6HY;;;;;YAMA,eAAe,eAAe,qBAAqB,MAAM;;;;;;;;;;;YAYzD,0BAA0B,cAAc,kBAAkB;;;;;;;;;;;;qBAazD,eAAe;EAAW;EAAgB;EAAgB;;;;;;;;;;YAkB3D,8CAA8C;;;;;;iBAOzC;;;;;;;EAOf;;;;;;;EAQA;;;;;;;EAQA,gBAAgB;;;;;;iBAOD;;;;;;;EAOf;;;;;;;EAQA;;;;;;EAOA;;;;;iBAMe;;;;;;;;;;EAUf,gBAAgB;IAA0B,OAAO;IAAQ,cAAc;;;;;;;;;;EAUvE,cAAc,kBAAkB;;;;;;;;EAShC;;;;;;EAOA,QAAQ;;;;;;;;EASR,SAAS;;;;EAKT,QAAQ;;;;EAKR,MAAM;;;;;;;;EASN,mBAAmB;;;;;;;EAQnB,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAoSL;;;;;;;;;;;;;;;;;;SAiHJ,OAAI,UAAa,yBAA4B,OAAO,OAAO,eAAe;;;;;;;;UAiB1E;;;;;;;;;;;EAkEP,sBAAmB,MAAA,eAAA,KAAA;IAEqB,OAAA;IAAa,cAAS;SAoB3D,OAAA,aAAA;;;;;;;;;;;;;EAcH,oBAAiB,MAAA,eAAA,aAAA,kBAAA,OAAA,aAAA;;;;;;;;;;;EAqBjB,mBAAgB,MAAA,eAAA,KAAA;IAEuB,MAAA;IAAa,cAAS;;IAAyB,OAAA;IAAa,cAAS;SAkBzG,OAAA,aAAA;;;;;;;EAQH;;;;;;;;;EAeA,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;EA6Bd,SAAM,MAAA,eAAA,gBAAA,OAAA,eAAA"}
|
|
@@ -9,8 +9,8 @@ const CODE_LOWER_X = 120;
|
|
|
9
9
|
const CODE_UPPER_X = 88;
|
|
10
10
|
/**
|
|
11
11
|
* @description The widest entity name {@link EntityDecoder.decode} will look for, in characters. The forward scan for `;` stops once more than this many characters
|
|
12
|
-
* have passed since the `&`, so a longer run is not treated as one entity
|
|
13
|
-
* megabyte of non-entity text between two ampersands from being sliced.
|
|
12
|
+
* have passed since the `&`, so a longer run is not treated as one entity — it is copied through as literal text. The bound is what keeps a document
|
|
13
|
+
* with a megabyte of non-entity text between two ampersands from being sliced.
|
|
14
14
|
*/
|
|
15
15
|
const MAX_TOKEN_LENGTH = 32;
|
|
16
16
|
/**
|
|
@@ -19,14 +19,14 @@ const MAX_TOKEN_LENGTH = 32;
|
|
|
19
19
|
*/
|
|
20
20
|
const MAX_CODE_POINT = 1114111;
|
|
21
21
|
/**
|
|
22
|
-
* @description Returned by `#classifyNCR` for a codepoint that carries no minimum action level
|
|
23
|
-
* both end up expanding, but only the first lets `numericAllowed: false` short-circuit the whole pipeline.
|
|
22
|
+
* @description Returned by `#classifyNCR` for a codepoint that carries no minimum action level, which is what distinguishes "no restriction" from
|
|
23
|
+
* `NCR_LEVEL.allow` — both end up expanding, but only the first lets `numericAllowed: false` short-circuit the whole pipeline.
|
|
24
24
|
*/
|
|
25
25
|
const NO_MINIMUM_LEVEL = -1;
|
|
26
26
|
/**
|
|
27
27
|
* @description Characters that may not appear in an entity name registered through {@link EntityDecoder.setExternalEntities} or
|
|
28
28
|
* {@link EntityDecoder.addExternalEntity}. A name carrying one of these cannot be written as `&name;` at all, so registration refuses it rather than
|
|
29
|
-
* storing a name no document could ever reference. The set is the upstream string verbatim, including its duplicated backslash
|
|
29
|
+
* storing a name no document could ever reference. The set is the upstream string verbatim, including its duplicated backslash — a `Set` discards the
|
|
30
30
|
* duplicate, so the effective set is the eighteen characters below.
|
|
31
31
|
*/
|
|
32
32
|
const SPECIAL_CHARS = /* @__PURE__ */ new Set("!?\\/[]$%{}^&*()<>|+");
|
|
@@ -46,7 +46,7 @@ const LIMIT_TIER_BASE = "base";
|
|
|
46
46
|
const LIMIT_TIER_ALL = "all";
|
|
47
47
|
/**
|
|
48
48
|
* @description The NCR action levels, in severity order. A higher number is a stricter action, and the resolver takes the maximum of the configured level and the
|
|
49
|
-
* minimum a codepoint range imposes
|
|
49
|
+
* minimum a codepoint range imposes, so a range can only ever make an entity stricter than the caller asked for — never more lenient.
|
|
50
50
|
*/
|
|
51
51
|
const NCR_LEVEL = Object.freeze({
|
|
52
52
|
allow: 0,
|
|
@@ -55,7 +55,7 @@ const NCR_LEVEL = Object.freeze({
|
|
|
55
55
|
throw: 3
|
|
56
56
|
});
|
|
57
57
|
/**
|
|
58
|
-
* @description The C0 control codes XML 1.0 §2.2 permits as literal characters. Every other code in U+0001
|
|
58
|
+
* @description The C0 control codes XML 1.0 §2.2 permits as literal characters. Every other code in U+0001–U+001F is prohibited.
|
|
59
59
|
*/
|
|
60
60
|
const XML10_ALLOWED_C0 = /* @__PURE__ */ new Set([
|
|
61
61
|
9,
|
|
@@ -86,7 +86,7 @@ const ENTITY_ACTION = Object.freeze({
|
|
|
86
86
|
*
|
|
87
87
|
* @returns An effect producing the name, unchanged, so the call can be inlined. Fails with {@link XmlError} and the `InvalidEntityName` reason,
|
|
88
88
|
* carrying the offending character. The `[EntityReplacer]` prefix in the message is preserved verbatim from the original throw, despite naming a
|
|
89
|
-
* class this decoder does not have
|
|
89
|
+
* class this decoder does not have — it is load-bearing for anything matching on it.
|
|
90
90
|
*/
|
|
91
91
|
const checkEntityName = (name) => {
|
|
92
92
|
if (name.charCodeAt(0) === CODE_HASH) return Effect.fail(new XmlError({
|
|
@@ -109,11 +109,11 @@ const checkEntityName = (name) => {
|
|
|
109
109
|
};
|
|
110
110
|
/**
|
|
111
111
|
* @description Flatten registration maps into one name to string map, later maps winning over earlier ones for the same name. The result is a null-prototype
|
|
112
|
-
* object, not a `Map`. That is
|
|
113
|
-
* in numeric order, and the registration hooks observe that order. A name of `"2"` registered after `"brand"` reaches the hook first here and
|
|
114
|
-
* in a `Map`.
|
|
112
|
+
* object, not a `Map`. That is not incidental: a `Map` iterates in pure insertion order, while `Object.keys` lifts array-index-like names to the
|
|
113
|
+
* front in numeric order, and the registration hooks observe that order. A name of `"2"` registered after `"brand"` reaches the hook first here and
|
|
114
|
+
* second in a `Map`.
|
|
115
115
|
*
|
|
116
|
-
* @param maps - The maps to merge. A falsy entry
|
|
116
|
+
* @param maps - The maps to merge. A falsy entry — `null`, `undefined`, `''`, `0` — contributes nothing rather than throwing.
|
|
117
117
|
*
|
|
118
118
|
* @returns A null-prototype object of own string-valued entries. Nothing from `Object.prototype` can be read out of it, so a document naming
|
|
119
119
|
* `constructor` or `toString` finds nothing. Each entry is read through {@link flattenEntityValue}, so an entry that cannot be reduced to a string
|
|
@@ -132,15 +132,15 @@ function mergeEntityMaps(...maps) {
|
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
134
|
* @description Reduce one registration entry to the string a reference to it expands to, or to nothing when the entry is a form the scanner has no use for. Three
|
|
135
|
-
* shapes survive: the string itself, and a `{ regex | regx, val }` envelope whose `val` is a string. Everything else
|
|
136
|
-
* a bare function, an envelope whose `val` is a function
|
|
137
|
-
* the text it was written as. Dropping is silent on purpose: the runtime inspects whatever it is handed, and failing the construction over one
|
|
135
|
+
* shapes survive: the string itself, and a `{ regex | regx, val }` envelope whose `val` is a string. Everything else — a number, `null`, `undefined`,
|
|
136
|
+
* a bare function, an envelope whose `val` is a function — has no string to substitute, so the name is dropped and a reference to it comes back out
|
|
137
|
+
* as the text it was written as. Dropping is silent on purpose: the runtime inspects whatever it is handed, and failing the construction over one
|
|
138
138
|
* unreadable entry would take every other entity in the table down with it.
|
|
139
139
|
*
|
|
140
|
-
* @param raw - The entry as it arrived, in whatever shape the caller supplied it
|
|
140
|
+
* @param raw - The entry as it arrived, in whatever shape the caller supplied it — including no entry at all, which a table with a hole in it
|
|
141
141
|
* produces.
|
|
142
142
|
*
|
|
143
|
-
* @returns The replacement string, or `undefined` when the entry cannot be read. A name registered to the empty string yields `''
|
|
143
|
+
* @returns The replacement string, or `undefined` when the entry cannot be read. A name registered to the empty string yields `''`, which is why
|
|
144
144
|
* callers compare against `undefined` rather than testing for emptiness.
|
|
145
145
|
*/
|
|
146
146
|
function flattenEntityValue(raw) {
|
|
@@ -155,7 +155,7 @@ function flattenEntityValue(raw) {
|
|
|
155
155
|
* @param map - The map to read.
|
|
156
156
|
* @param key - The entity name.
|
|
157
157
|
*
|
|
158
|
-
* @returns The registered string, or `undefined` when the name is not an own key. A name registered to the empty string returns `''
|
|
158
|
+
* @returns The registered string, or `undefined` when the name is not an own key. A name registered to the empty string returns `''`, which is why
|
|
159
159
|
* callers must compare against `undefined` rather than test for emptiness.
|
|
160
160
|
*/
|
|
161
161
|
function ownEntity(map, key) {
|
|
@@ -168,7 +168,8 @@ function ownEntity(map, key) {
|
|
|
168
168
|
* @param raw - The configured value.
|
|
169
169
|
*
|
|
170
170
|
* @returns The tier set. An unrecognised string falls back to `external` rather than to no filtering at all, so a typo cannot silently disable the
|
|
171
|
-
* limits. An array is taken as given
|
|
171
|
+
* limits. An array is taken as given, which is why an empty array disables limit accounting entirely while an empty string falls back to
|
|
172
|
+
* `external`.
|
|
172
173
|
*/
|
|
173
174
|
function parseLimitTiers(raw) {
|
|
174
175
|
if (!raw || raw === LIMIT_TIER_EXTERNAL) return /* @__PURE__ */ new Set([LIMIT_TIER_EXTERNAL]);
|
|
@@ -228,7 +229,7 @@ function readPostCheck(raw) {
|
|
|
228
229
|
*
|
|
229
230
|
* @param raw - The configured hook, or nothing.
|
|
230
231
|
*
|
|
231
|
-
* @returns The hook itself, or `null` for an absent option and for a value that is not a function. `null` lets every registration path ask
|
|
232
|
+
* @returns The hook itself, or `null` for an absent option and for a value that is not a function. `null` is what lets every registration path ask
|
|
232
233
|
* unconditionally: a hook that is not there accepts.
|
|
233
234
|
*/
|
|
234
235
|
function readHook(raw) {
|
|
@@ -237,9 +238,9 @@ function readHook(raw) {
|
|
|
237
238
|
}
|
|
238
239
|
/**
|
|
239
240
|
* @description Read one of the two entity-name lists as a set, under the same missing-value rule as the other options: absent is empty, not an error. The
|
|
240
|
-
* `Array.isArray` test rather than a truthiness one keeps a mistyped list from reaching `new Set` and throwing there, so a caller's typo
|
|
241
|
-
* list instead of taking the decoder down. Matching against a set also
|
|
242
|
-
* consults them in, not by the order the caller wrote them in.
|
|
241
|
+
* `Array.isArray` test rather than a truthiness one is what keeps a mistyped list from reaching `new Set` and throwing there, so a caller's typo
|
|
242
|
+
* disables the list instead of taking the decoder down. Matching against a set is also why a name in both lists is decided by the order
|
|
243
|
+
* {@link EntityDecoder.decode} consults them in, not by the order the caller wrote them in.
|
|
243
244
|
*
|
|
244
245
|
* @param raw - The configured list, or nothing.
|
|
245
246
|
*
|
|
@@ -271,25 +272,25 @@ function scanTokenEnd(str, ampersand) {
|
|
|
271
272
|
*
|
|
272
273
|
* ### Entity lookup priority
|
|
273
274
|
*
|
|
274
|
-
* 1. **input / runtime
|
|
275
|
-
* 2. **persistent external
|
|
275
|
+
* 1. **input / runtime** — injected per document through {@link EntityDecoder.addInputEntities}
|
|
276
|
+
* 2. **persistent external** — set through {@link EntityDecoder.setExternalEntities} and {@link EntityDecoder.addExternalEntity}, surviving
|
|
276
277
|
* {@link EntityDecoder.reset}
|
|
277
|
-
* 3. **base
|
|
278
|
+
* 3. **base** — the five XML predefined entities plus the constructor's `namedEntities` Both input and external resolve as the `external` tier for limit
|
|
278
279
|
* purposes, because both are injected at runtime. Numeric references (`&#NNN;`, `&#xHH;`) resolve directly through `String.fromCodePoint` and are
|
|
279
280
|
* always `base` tier: they cannot recurse, so a limit that counted them would only punish a document that spells its characters out.
|
|
280
281
|
*
|
|
281
282
|
* ### Upstream behaviour preserved
|
|
282
283
|
*
|
|
283
|
-
* Several quirks of the original are kept
|
|
284
|
+
* Several quirks of the original are kept deliberately, because a consumer's output already depends on them:
|
|
284
285
|
*
|
|
285
286
|
* - A value containing `&` is **not** filtered from `namedEntities` or `setExternalEntities`, contrary to the documentation. Only
|
|
286
287
|
* {@link EntityDecoder.addExternalEntity} checks, and it drops the entry rather than storing it, so the same name registered either way can resolve
|
|
287
288
|
* to nothing.
|
|
288
|
-
* - {@link EntityDecoderOptions.postCheck} is skipped entirely for input that never reaches the scan
|
|
289
|
+
* - {@link EntityDecoderOptions.postCheck} is skipped entirely for input that never reaches the scan — an empty string, a non-string, or a string with
|
|
289
290
|
* no `&`.
|
|
290
291
|
* - {@link EntityDecoder.decode} returns a non-string argument unchanged, despite being typed `string`.
|
|
291
292
|
* - The expansion-limit errors are prefixed `EntityReplacer`, not `EntityDecoder`.
|
|
292
|
-
* - Nothing in XML 1.0 §2.2 is enforced for U+007F
|
|
293
|
+
* - Nothing in XML 1.0 §2.2 is enforced for U+007F–U+009F or for the U+FFFE/U+FFFF noncharacters, and the sweep for `&` leaves a name of
|
|
293
294
|
* {@link MAX_TOKEN_LENGTH} + 1 characters unresolvable.
|
|
294
295
|
* - Numeric references are parsed with `parseInt`, so a leading space, sign, or trailing garbage is accepted: `&# 41;`, `&#x+41;` and `)zz;` all
|
|
295
296
|
* decode, and `�x41;` parses as a null reference rather than `A`.
|
|
@@ -303,7 +304,7 @@ function scanTokenEnd(str, ampersand) {
|
|
|
303
304
|
* decoder.addInputEntities({ version: '1.0' });
|
|
304
305
|
*
|
|
305
306
|
* decoder.decode('&brand; v&version; ©'); // 'Acme v1.0 ©'
|
|
306
|
-
* decoder.decode('&#38;'); // '&&'
|
|
307
|
+
* decoder.decode('&#38;'); // '&&' — one pass, the output is never re-scanned
|
|
307
308
|
*
|
|
308
309
|
* decoder.reset(); // drops the input entities and the counters, keeps the external ones
|
|
309
310
|
* ```;
|
|
@@ -319,7 +320,7 @@ var EntityDecoder = class EntityDecoder {
|
|
|
319
320
|
*/
|
|
320
321
|
#maxExpandedLength;
|
|
321
322
|
/**
|
|
322
|
-
* @description {@link EntityDecoderOptions.postCheck}, or the identity function
|
|
323
|
+
* @description {@link EntityDecoderOptions.postCheck}, or the identity function — so the decode loop can call it unconditionally on the path that actually
|
|
323
324
|
* scanned, and never on the two fast paths that return early.
|
|
324
325
|
*/
|
|
325
326
|
#postCheck;
|
|
@@ -338,7 +339,7 @@ var EntityDecoder = class EntityDecoder {
|
|
|
338
339
|
#baseMap;
|
|
339
340
|
/**
|
|
340
341
|
* @description Persistent external entities, as a null-prototype object. Replaced wholesale by {@link EntityDecoder.setExternalEntities} and added to by
|
|
341
|
-
* {@link EntityDecoder.addExternalEntity}, and never touched by {@link EntityDecoder.reset}
|
|
342
|
+
* {@link EntityDecoder.addExternalEntity}, and never touched by {@link EntityDecoder.reset} — that is the whole distinction from the input map.
|
|
342
343
|
*/
|
|
343
344
|
#externalMap;
|
|
344
345
|
/**
|
|
@@ -347,8 +348,8 @@ var EntityDecoder = class EntityDecoder {
|
|
|
347
348
|
*/
|
|
348
349
|
#inputMap;
|
|
349
350
|
/**
|
|
350
|
-
* @description Tracked expansions since the last reset. Cumulative across {@link EntityDecoder.decode} calls, which makes a limit a per-document budget
|
|
351
|
-
* than a per-call one.
|
|
351
|
+
* @description Tracked expansions since the last reset. Cumulative across {@link EntityDecoder.decode} calls, which is what makes a limit a per-document budget
|
|
352
|
+
* rather than a per-call one. Deliberately not reset by a thrown limit error, so the over-limit count is what the error message reports.
|
|
352
353
|
*/
|
|
353
354
|
#totalExpansions;
|
|
354
355
|
/**
|
|
@@ -390,7 +391,7 @@ var EntityDecoder = class EntityDecoder {
|
|
|
390
391
|
#onInputEntity;
|
|
391
392
|
/**
|
|
392
393
|
* @description Create a decoder. A factory rather than a constructor, because it refuses a `null` options object. Every field is optional, so `null` is not "a
|
|
393
|
-
* decoder with the defaults"
|
|
394
|
+
* decoder with the defaults" — a caller who wrote it meant something the signature does not allow, and a decoder built from it would be
|
|
394
395
|
* indistinguishable from one built from `{}` while hiding the mistake. Saying so is worth a factory; `EntityDecoderOptions` is a plain object and
|
|
395
396
|
* nothing else about construction can fail.
|
|
396
397
|
*
|
|
@@ -413,8 +414,8 @@ var EntityDecoder = class EntityDecoder {
|
|
|
413
414
|
})) : Effect.succeed(new EntityDecoder(options));
|
|
414
415
|
/**
|
|
415
416
|
* @description Create a decoder. Every option is resolved here into the flat fields the decode loop reads, so nothing per-reference has to re-derive it. The
|
|
416
|
-
* options whose wrong type disables them rather than failing the construction
|
|
417
|
-
* and {@link readNameList}, so that rule is written once instead of four times.
|
|
417
|
+
* options whose wrong type disables them rather than failing the construction — the two hooks, the two name lists — are read through
|
|
418
|
+
* {@link readHook} and {@link readNameList}, so that rule is written once instead of four times.
|
|
418
419
|
*
|
|
419
420
|
* @param resolved - Configuration, already checked. See {@link EntityDecoderOptions}.
|
|
420
421
|
*/
|
|
@@ -442,7 +443,7 @@ var EntityDecoder = class EntityDecoder {
|
|
|
442
443
|
/**
|
|
443
444
|
* @description Ask a registration hook about one name and value.
|
|
444
445
|
*
|
|
445
|
-
* @param hook - The hook, or `null`. A `null` hook accepts,
|
|
446
|
+
* @param hook - The hook, or `null`. A `null` hook accepts, which is what lets {@link EntityDecoder.addExternalEntity} call this unconditionally.
|
|
446
447
|
* @param name - The entity name, without `&` or `;`.
|
|
447
448
|
* @param value - The resolved value, after any `{ regex, val }` envelope was unwrapped.
|
|
448
449
|
* @param context - Which registration is in progress, for the error message.
|
|
@@ -551,14 +552,14 @@ var EntityDecoder = class EntityDecoder {
|
|
|
551
552
|
/**
|
|
552
553
|
* @description Expand every entity reference in a string, in one pass. The output is never re-scanned, so no expansion can produce a _second_ one: a registered
|
|
553
554
|
* value that itself contains reference text reaches the caller as that literal text, unexpanded. What the limits bound is the growth of this single
|
|
554
|
-
* pass
|
|
555
|
+
* pass — how much one round of expansion can add. Three inputs return before the scan and therefore never reach
|
|
555
556
|
* {@link EntityDecoderOptions.postCheck}: a non-string, the empty string, and any string with no `&` in it. The scan itself is `#expandAll`; what
|
|
556
557
|
* this method adds is the three inputs that skip it and the single join of what it collected.
|
|
557
558
|
*
|
|
558
559
|
* @example
|
|
559
560
|
* ```typescript
|
|
560
561
|
* import { Effect } from 'effect';
|
|
561
|
-
* import { EntityDecoder } from '@endevops/effect-codec
|
|
562
|
+
* import { EntityDecoder } from '@endevops/effect-xml-codec';
|
|
562
563
|
*
|
|
563
564
|
* const decoder = new EntityDecoder({ namedEntities: { copy: '©' } });
|
|
564
565
|
* Effect.runSync(Effect.orElseSucceed(decoder.addExternalEntity('brand', 'Acme'), () => undefined));
|
|
@@ -583,15 +584,15 @@ var EntityDecoder = class EntityDecoder {
|
|
|
583
584
|
/**
|
|
584
585
|
* @description Walk the string once and collect the pieces of every reference that resolved. Two advance rules make the walk terminate and keep it correct: an
|
|
585
586
|
* `&` that turns out to open nothing moves the cursor by one character rather than to the end of its run, so a second `&` in the same text is still
|
|
586
|
-
* found; and a reference that did resolve moves it to just past the `;`, so the text that was substituted for it is never looked at again
|
|
587
|
-
*
|
|
588
|
-
*
|
|
587
|
+
* found; and a reference that did resolve moves it to just past the `;`, so the text that was substituted for it is never looked at again — that is
|
|
588
|
+
* what makes the pass single, and a registered value containing `&` cannot expand a second level. What a reference becomes is `#resolveToken`'s to
|
|
589
|
+
* decide and what it costs is `#chargeExpansion`'s to apply, which leaves the scanning here as the only thing with a rule of its own.
|
|
589
590
|
*
|
|
590
591
|
* @param str - The string to expand. It always holds at least one `&` and is never empty, or the caller would have returned before reaching the
|
|
591
592
|
* walk.
|
|
592
593
|
*
|
|
593
594
|
* @returns An effect producing the pieces in order. The array is empty exactly when nothing was replaced, which the caller reads as "the input is
|
|
594
|
-
* its own result". Fails with {@link XmlError} and the reason the offending reference carries
|
|
595
|
+
* its own result". Fails with {@link XmlError} and the reason the offending reference carries — `ProhibitedCharacterReference`,
|
|
595
596
|
* `ExpansionLimitExceeded` or `ExpandedLengthLimitExceeded`.
|
|
596
597
|
*/
|
|
597
598
|
#expandAll = Effect.fnUntraced(function* (str) {
|
|
@@ -627,19 +628,19 @@ var EntityDecoder = class EntityDecoder {
|
|
|
627
628
|
/**
|
|
628
629
|
* @description Decide what one reference expands to. The lists and maps are consulted in the one order the runtime uses, and the first that matches wins:
|
|
629
630
|
*
|
|
630
|
-
* 1. `remove
|
|
631
|
-
* 2. `leave
|
|
632
|
-
* 3. A `#`-prefixed token
|
|
631
|
+
* 1. `remove` — deleted outright, without the name ever being resolved, so the name need not exist.
|
|
632
|
+
* 2. `leave` — emitted as the original `&token;`, and charged to nothing.
|
|
633
|
+
* 3. A `#`-prefixed token — the numeric pipeline, which is the only one of the four that can fail. Classification runs before any decision about
|
|
633
634
|
* `numericAllowed`, because the ranges that carry a minimum have to be caught whichever way that option is set.
|
|
634
|
-
* 4. Anything else
|
|
635
|
+
* 4. Anything else — resolved against the input map, then the external map, then the base map.
|
|
635
636
|
*
|
|
636
637
|
* @param token - The reference's token, e.g. `brand` or `#38`, with the `&` and the `;` already stripped. Never empty: the scanner drops `&;`
|
|
637
638
|
* before calling.
|
|
638
639
|
*
|
|
639
640
|
* @returns An effect producing what the reference expands to and the tier to charge it to, or `undefined` to leave it as written and charge it
|
|
640
|
-
* nothing. `undefined` covers all three ways of leaving a reference alone
|
|
641
|
-
* name registered nowhere
|
|
642
|
-
* reason when the numeric policy throws on the codepoint.
|
|
641
|
+
* nothing. `undefined` covers all three ways of leaving a reference alone — a listed `leave` name, a numeric reference that is out of range, and
|
|
642
|
+
* a name registered nowhere — and none of them is distinguishable from outside. Fails with {@link XmlError} and the
|
|
643
|
+
* `ProhibitedCharacterReference` reason when the numeric policy throws on the codepoint.
|
|
643
644
|
*/
|
|
644
645
|
#resolveToken = Effect.fnUntraced(function* (token) {
|
|
645
646
|
if (this.#removeSet.has(token)) return {
|
|
@@ -680,9 +681,9 @@ var EntityDecoder = class EntityDecoder {
|
|
|
680
681
|
});
|
|
681
682
|
/**
|
|
682
683
|
* @description Add one expansion to the running total and compare it against {@link EntityDecoderLimitOptions.maxTotalExpansions}. The comparison is `>` rather
|
|
683
|
-
* than `>=`, so a limit of `n` allows exactly `n` expansions and throws on the `n + 1`th. That is a contract
|
|
684
|
-
* states it
|
|
685
|
-
*
|
|
684
|
+
* than `>=`, so a limit of `n` allows exactly `n` expansions and throws on the `n + 1`th. That is a contract — the option's own documentation
|
|
685
|
+
* states it — and the kind of off-by-one a tidy-up changes by accident. The counter is deliberately not reset before failing: the over-limit total
|
|
686
|
+
* is what the error message reports, and {@link EntityDecoder.reset} is the caller's way to start a new document.
|
|
686
687
|
*
|
|
687
688
|
* @returns An effect that fails with {@link XmlError} and the `ExpansionLimitExceeded` reason once the count is past the ceiling, and succeeds
|
|
688
689
|
* otherwise. The `EntityReplacer` prefix in the message is preserved verbatim from the original throw, despite naming a class this decoder does
|
|
@@ -703,7 +704,7 @@ var EntityDecoder = class EntityDecoder {
|
|
|
703
704
|
/**
|
|
704
705
|
* @description Add one expansion's surplus to the running total and compare it against {@link EntityDecoderLimitOptions.maxExpandedLength}. Only the surplus
|
|
705
706
|
* counts, and only upward: a reference whose replacement is no longer than the `&token;` it replaces contributes zero, and a shrinking one
|
|
706
|
-
* contributes nothing and cannot trip the limit at all. That
|
|
707
|
+
* contributes nothing and cannot trip the limit at all. That is what makes the ceiling a bound on growth rather than on document size.
|
|
707
708
|
*
|
|
708
709
|
* @param token - The reference's token, with the `&` and `;` stripped. The two delimiters count towards what the expansion displaced.
|
|
709
710
|
* @param replacement - What the reference expanded to, including `''` for a removal.
|
|
@@ -729,8 +730,8 @@ var EntityDecoder = class EntityDecoder {
|
|
|
729
730
|
/**
|
|
730
731
|
* @description Decide whether an entity of a given tier is charged against the limits.
|
|
731
732
|
*
|
|
732
|
-
* @param tier - The tier the replacement is charged to. Every expansion that reaches here carries one
|
|
733
|
-
* still carries the `external` tier
|
|
733
|
+
* @param tier - The tier the replacement is charged to. Every expansion that reaches here carries one — a name deleted before it was ever resolved
|
|
734
|
+
* still carries the `external` tier — so there is no absent case to answer.
|
|
734
735
|
*
|
|
735
736
|
* @returns `true` when it counts. `'all'` short-circuits, so a filter naming every tier charges everything regardless of which map it came from.
|
|
736
737
|
*/
|
|
@@ -766,9 +767,9 @@ var EntityDecoder = class EntityDecoder {
|
|
|
766
767
|
/**
|
|
767
768
|
* @description Find the strictest action a codepoint's range requires. Checked in this order:
|
|
768
769
|
*
|
|
769
|
-
* 1. U+0000
|
|
770
|
-
* 2. U+D800
|
|
771
|
-
* 3. U+0001
|
|
770
|
+
* 1. U+0000 — governed by `nullNCR`, already clamped to `remove` or stricter
|
|
771
|
+
* 2. U+D800–U+DFFF — surrogates, always `remove`, under every policy and both XML versions
|
|
772
|
+
* 3. U+0001–U+001F other than tab, newline, carriage return — XML 1.0 only, `remove` Nothing else is classified. U+007F–U+009F (C1) and the
|
|
772
773
|
* U+FFFE/U+FFFF noncharacters are not checked, even though XML 1.0 §2.2 prohibits them and the `xmlVersion` option's own documentation claims C1
|
|
773
774
|
* is only permitted under 1.1. Both gaps are upstream's and are kept.
|
|
774
775
|
*
|
|
@@ -786,11 +787,11 @@ var EntityDecoder = class EntityDecoder {
|
|
|
786
787
|
* @description Turn a resolved action level into a replacement.
|
|
787
788
|
*
|
|
788
789
|
* @param action - A level from {@link NCR_LEVEL}. A level outside the four known ones falls through to the allow behaviour, so a bad level cannot
|
|
789
|
-
* produce a wrong string
|
|
790
|
+
* produce a wrong string — it can only fail open.
|
|
790
791
|
* @param token - The raw token, e.g. `#38`, for the error message.
|
|
791
792
|
* @param cp - The codepoint, for the error message.
|
|
792
793
|
*
|
|
793
|
-
* @returns An effect producing the character for `allow`, `''` for `remove`, and `undefined` for `leave
|
|
794
|
+
* @returns An effect producing the character for `allow`, `''` for `remove`, and `undefined` for `leave` — which the caller reads as "emit the
|
|
794
795
|
* original `&token;`". Fails with {@link XmlError} and the `ProhibitedCharacterReference` reason for `throw`, naming both the token and the
|
|
795
796
|
* codepoint.
|
|
796
797
|
*/
|
|
@@ -819,7 +820,7 @@ var EntityDecoder = class EntityDecoder {
|
|
|
819
820
|
*
|
|
820
821
|
* @param token - The raw token without `&` and `;`, e.g. `#38`, `#x26`, `#X26`.
|
|
821
822
|
*
|
|
822
|
-
* @returns An effect producing the replacement
|
|
823
|
+
* @returns An effect producing the replacement — the empty string meaning "delete" — or `undefined` to leave the reference as written. Fails with
|
|
823
824
|
* {@link XmlError} and the `ProhibitedCharacterReference` reason when the effective action is `throw`.
|
|
824
825
|
*/
|
|
825
826
|
#resolveNCR(token) {
|