@endevops/effect-codec-xml 0.0.1 → 0.1.0-beta.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.
Files changed (47) hide show
  1. package/README.md +63 -62
  2. package/dist/codec.d.ts +17 -9
  3. package/dist/codec.d.ts.map +1 -1
  4. package/dist/codec.js +25 -16
  5. package/dist/codec.js.map +1 -1
  6. package/dist/conventions.d.ts +4 -4
  7. package/dist/conventions.js +7 -7
  8. package/dist/conventions.js.map +1 -1
  9. package/dist/entities/entity-decoder.d.ts +33 -33
  10. package/dist/entities/entity-decoder.d.ts.map +1 -1
  11. package/dist/entities/entity-decoder.js +63 -64
  12. package/dist/entities/entity-decoder.js.map +1 -1
  13. package/dist/errors.d.ts +2 -2
  14. package/dist/errors.js +2 -2
  15. package/dist/errors.js.map +1 -1
  16. package/dist/namespaces.js +2 -2
  17. package/dist/namespaces.js.map +1 -1
  18. package/dist/naming.d.ts +6 -6
  19. package/dist/naming.d.ts.map +1 -1
  20. package/dist/naming.js +3 -3
  21. package/dist/naming.js.map +1 -1
  22. package/dist/parse.d.ts +5 -5
  23. package/dist/parse.js +14 -14
  24. package/dist/parse.js.map +1 -1
  25. package/dist/render.d.ts +1 -1
  26. package/dist/render.d.ts.map +1 -1
  27. package/dist/render.js +28 -28
  28. package/dist/render.js.map +1 -1
  29. package/dist/xml-error.d.ts +9 -9
  30. package/dist/xml-error.js +18 -18
  31. package/dist/xml-error.js.map +1 -1
  32. package/dist/xml-value.d.ts +7 -7
  33. package/dist/xml-value.d.ts.map +1 -1
  34. package/dist/xml-value.js +6 -7
  35. package/dist/xml-value.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/codec.ts +91 -71
  38. package/src/conventions.ts +7 -7
  39. package/src/entities/entity-decoder.ts +98 -99
  40. package/src/errors.ts +3 -3
  41. package/src/index.ts +3 -3
  42. package/src/namespaces.ts +16 -16
  43. package/src/naming.ts +34 -35
  44. package/src/parse.ts +26 -26
  45. package/src/render.ts +44 -44
  46. package/src/xml-error.ts +18 -18
  47. package/src/xml-value.ts +10 -11
@@ -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 — 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.
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'` — 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.
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` — and any negative or non-numeric value — is unlimited, because the
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 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.
71
+ * @description Which tiers count against both limits. Defaults to `'external'`, which keeps the built-in entities, including every numeric reference, from being
72
+ * 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–U+001F other than tab, newline and carriage return;
84
+ * @description The XML version whose codepoint restrictions apply. `1.0` prohibits the C0 controls U+0001 to 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 — 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.
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 — 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.
110
+ * regx, val }` envelope is unwrapped to its `val`. Anything else (a number, `null`, a function, an envelope whose `val` is a function) is dropped,
111
+ * leaving the name unresolvable rather than failing the construction. Upstream's documentation says a value containing `&` is skipped here, to
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.
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 — an empty string, a
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 — _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
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. This is why the option is 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 is not what distinguishes them.
147
+ * subject to. Preserved as-is; the only in-code comment claims the charge is for unknown references, which does not distinguish 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 — see the class note on name validation.
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** — injected per document through {@link EntityDecoder.addInputEntities}
182
- * 2. **persistent external** — set through {@link EntityDecoder.setExternalEntities} and {@link EntityDecoder.addExternalEntity}, surviving
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** — the five XML predefined entities plus the constructor's `namedEntities` Both input and external resolve as the `external` tier for limit
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 deliberately, because a consumer's output already depends on them:
190
+ * Several quirks of the original are kept intentionally, 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 — an empty string, a non-string, or a string with
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–U+009F or for the U+FFFE/U+FFFF noncharacters, and the sweep for `&` leaves a name of
199
+ * - Nothing in XML 1.0 §2.2 is enforced for U+007F to 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 `&#41zz;` all
202
202
  * decode, and `&#0x41;` 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; &copy;'); // 'Acme v1.0 ©'
213
- * decoder.decode('&#x26;#38;'); // '&&' — one pass, the output is never re-scanned
213
+ * decoder.decode('&#x26;#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" — a caller who wrote it meant something the signature does not allow, and a decoder built from it would be
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 — 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.
239
+ * options whose wrong type disables them rather than failing the construction (the two hooks, the two name lists) are read through {@link readHook}
240
+ * 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 — how much one round of expansion can add. Three inputs return before the scan and therefore never reach
308
+ * pass, meaning 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-xml-codec';
315
+ * import { EntityDecoder } from '@endevops/effect-codec-xml';
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAmSL;;;;;;;;;;;;;;;;;;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 — 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.
12
+ * have passed since the `&`, so a longer run is not treated as one entity. It is copied through as literal text. The bound keeps a document with a
13
+ * 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, 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.
22
+ * @description Returned by `#classifyNCR` for a codepoint that carries no minimum action level. This value distinguishes "no restriction" from `NCR_LEVEL.allow`:
23
+ * 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 — a `Set` discards the
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, so a range can only ever make an entity stricter than the caller asked for — never more lenient.
49
+ * minimum a codepoint range imposes. A range can therefore only 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–U+001F is prohibited.
58
+ * @description The C0 control codes XML 1.0 §2.2 permits as literal characters. Every other code in U+0001 to 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 — it is load-bearing for anything matching on it.
89
+ * class this decoder does not have. It is relied on by anything matching the message text.
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 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`.
112
+ * object, not a `Map`. That is intentional. A `Map` iterates in pure insertion order, while `Object.keys` lifts array-index-like names to the front
113
+ * in numeric order, and the registration hooks observe that order. A name of `"2"` registered after `"brand"` reaches the hook first here and second
114
+ * in a `Map`.
115
115
  *
116
- * @param maps - The maps to merge. A falsy entry — `null`, `undefined`, `''`, `0` — contributes nothing rather than throwing.
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 — 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
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 as
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
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 — including no entry at all, which a table with a hole in 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 `''`, which is why
143
+ * @returns The replacement string, or `undefined` when the entry cannot be read. A name registered to the empty string yields `''`. That 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 `''`, which is why
158
+ * @returns The registered string, or `undefined` when the name is not an own key. A name registered to the empty string returns `''`. That is why
159
159
  * callers must compare against `undefined` rather than test for emptiness.
160
160
  */
161
161
  function ownEntity(map, key) {
@@ -168,8 +168,7 @@ 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, which is why an empty array disables limit accounting entirely while an empty string falls back to
172
- * `external`.
171
+ * limits. An array is taken as given. An empty array therefore disables limit accounting entirely, while an empty string falls back to `external`.
173
172
  */
174
173
  function parseLimitTiers(raw) {
175
174
  if (!raw || raw === LIMIT_TIER_EXTERNAL) return /* @__PURE__ */ new Set([LIMIT_TIER_EXTERNAL]);
@@ -229,7 +228,7 @@ function readPostCheck(raw) {
229
228
  *
230
229
  * @param raw - The configured hook, or nothing.
231
230
  *
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
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
233
232
  * unconditionally: a hook that is not there accepts.
234
233
  */
235
234
  function readHook(raw) {
@@ -238,9 +237,9 @@ function readHook(raw) {
238
237
  }
239
238
  /**
240
239
  * @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
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.
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 disables the
241
+ * list instead of taking the decoder down. Matching against a set also means a name in both lists is decided by the order {@link EntityDecoder.decode}
242
+ * consults them in, not by the order the caller wrote them in.
244
243
  *
245
244
  * @param raw - The configured list, or nothing.
246
245
  *
@@ -272,25 +271,25 @@ function scanTokenEnd(str, ampersand) {
272
271
  *
273
272
  * ### Entity lookup priority
274
273
  *
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
274
+ * 1. **input / runtime**: injected per document through {@link EntityDecoder.addInputEntities}
275
+ * 2. **persistent external**: set through {@link EntityDecoder.setExternalEntities} and {@link EntityDecoder.addExternalEntity}, surviving
277
276
  * {@link EntityDecoder.reset}
278
- * 3. **base** — the five XML predefined entities plus the constructor's `namedEntities` Both input and external resolve as the `external` tier for limit
277
+ * 3. **base**: the five XML predefined entities plus the constructor's `namedEntities` Both input and external resolve as the `external` tier for limit
279
278
  * purposes, because both are injected at runtime. Numeric references (`&#NNN;`, `&#xHH;`) resolve directly through `String.fromCodePoint` and are
280
279
  * always `base` tier: they cannot recurse, so a limit that counted them would only punish a document that spells its characters out.
281
280
  *
282
281
  * ### Upstream behaviour preserved
283
282
  *
284
- * Several quirks of the original are kept deliberately, because a consumer's output already depends on them:
283
+ * Several quirks of the original are kept intentionally, because a consumer's output already depends on them:
285
284
  *
286
285
  * - A value containing `&` is **not** filtered from `namedEntities` or `setExternalEntities`, contrary to the documentation. Only
287
286
  * {@link EntityDecoder.addExternalEntity} checks, and it drops the entry rather than storing it, so the same name registered either way can resolve
288
287
  * to nothing.
289
- * - {@link EntityDecoderOptions.postCheck} is skipped entirely for input that never reaches the scan — an empty string, a non-string, or a string with
288
+ * - {@link EntityDecoderOptions.postCheck} is skipped entirely for input that never reaches the scan: an empty string, a non-string, or a string with
290
289
  * no `&`.
291
290
  * - {@link EntityDecoder.decode} returns a non-string argument unchanged, despite being typed `string`.
292
291
  * - The expansion-limit errors are prefixed `EntityReplacer`, not `EntityDecoder`.
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
292
+ * - Nothing in XML 1.0 §2.2 is enforced for U+007F to U+009F or for the U+FFFE/U+FFFF noncharacters, and the sweep for `&` leaves a name of
294
293
  * {@link MAX_TOKEN_LENGTH} + 1 characters unresolvable.
295
294
  * - Numeric references are parsed with `parseInt`, so a leading space, sign, or trailing garbage is accepted: `&# 41;`, `&#x+41;` and `&#41zz;` all
296
295
  * decode, and `&#0x41;` parses as a null reference rather than `A`.
@@ -304,7 +303,7 @@ function scanTokenEnd(str, ampersand) {
304
303
  * decoder.addInputEntities({ version: '1.0' });
305
304
  *
306
305
  * decoder.decode('&brand; v&version; &copy;'); // 'Acme v1.0 ©'
307
- * decoder.decode('&#x26;#38;'); // '&&' — one pass, the output is never re-scanned
306
+ * decoder.decode('&#x26;#38;'); // '&&', one pass, the output is never re-scanned
308
307
  *
309
308
  * decoder.reset(); // drops the input entities and the counters, keeps the external ones
310
309
  * ```;
@@ -320,7 +319,7 @@ var EntityDecoder = class EntityDecoder {
320
319
  */
321
320
  #maxExpandedLength;
322
321
  /**
323
- * @description {@link EntityDecoderOptions.postCheck}, or the identity function — so the decode loop can call it unconditionally on the path that actually
322
+ * @description {@link EntityDecoderOptions.postCheck}, or the identity function. That lets the decode loop call it unconditionally on the path that actually
324
323
  * scanned, and never on the two fast paths that return early.
325
324
  */
326
325
  #postCheck;
@@ -339,7 +338,7 @@ var EntityDecoder = class EntityDecoder {
339
338
  #baseMap;
340
339
  /**
341
340
  * @description Persistent external entities, as a null-prototype object. Replaced wholesale by {@link EntityDecoder.setExternalEntities} and added to by
342
- * {@link EntityDecoder.addExternalEntity}, and never touched by {@link EntityDecoder.reset} — that is the whole distinction from the input map.
341
+ * {@link EntityDecoder.addExternalEntity}, and never touched by {@link EntityDecoder.reset}. That is the whole distinction from the input map.
343
342
  */
344
343
  #externalMap;
345
344
  /**
@@ -348,8 +347,8 @@ var EntityDecoder = class EntityDecoder {
348
347
  */
349
348
  #inputMap;
350
349
  /**
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.
350
+ * @description Tracked expansions since the last reset. Cumulative across {@link EntityDecoder.decode} calls, which makes a limit a per-document budget rather
351
+ * than a per-call one. Intentionally not reset by a thrown limit error, so the error message reports the over-limit count.
353
352
  */
354
353
  #totalExpansions;
355
354
  /**
@@ -391,7 +390,7 @@ var EntityDecoder = class EntityDecoder {
391
390
  #onInputEntity;
392
391
  /**
393
392
  * @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
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
393
+ * decoder with the defaults". A caller who wrote it meant something the signature does not allow, and a decoder built from it would be
395
394
  * indistinguishable from one built from `{}` while hiding the mistake. Saying so is worth a factory; `EntityDecoderOptions` is a plain object and
396
395
  * nothing else about construction can fail.
397
396
  *
@@ -414,8 +413,8 @@ var EntityDecoder = class EntityDecoder {
414
413
  })) : Effect.succeed(new EntityDecoder(options));
415
414
  /**
416
415
  * @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
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.
416
+ * options whose wrong type disables them rather than failing the construction (the two hooks, the two name lists) are read through {@link readHook}
417
+ * and {@link readNameList}, so that rule is written once instead of four times.
419
418
  *
420
419
  * @param resolved - Configuration, already checked. See {@link EntityDecoderOptions}.
421
420
  */
@@ -443,7 +442,7 @@ var EntityDecoder = class EntityDecoder {
443
442
  /**
444
443
  * @description Ask a registration hook about one name and value.
445
444
  *
446
- * @param hook - The hook, or `null`. A `null` hook accepts, which is what lets {@link EntityDecoder.addExternalEntity} call this unconditionally.
445
+ * @param hook - The hook, or `null`. A `null` hook accepts, so {@link EntityDecoder.addExternalEntity} can call this unconditionally.
447
446
  * @param name - The entity name, without `&` or `;`.
448
447
  * @param value - The resolved value, after any `{ regex, val }` envelope was unwrapped.
449
448
  * @param context - Which registration is in progress, for the error message.
@@ -552,14 +551,14 @@ var EntityDecoder = class EntityDecoder {
552
551
  /**
553
552
  * @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
554
553
  * value that itself contains reference text reaches the caller as that literal text, unexpanded. What the limits bound is the growth of this single
555
- * pass — how much one round of expansion can add. Three inputs return before the scan and therefore never reach
554
+ * pass, meaning how much one round of expansion can add. Three inputs return before the scan and therefore never reach
556
555
  * {@link EntityDecoderOptions.postCheck}: a non-string, the empty string, and any string with no `&` in it. The scan itself is `#expandAll`; what
557
556
  * this method adds is the three inputs that skip it and the single join of what it collected.
558
557
  *
559
558
  * @example
560
559
  * ```typescript
561
560
  * import { Effect } from 'effect';
562
- * import { EntityDecoder } from '@endevops/effect-xml-codec';
561
+ * import { EntityDecoder } from '@endevops/effect-codec-xml';
563
562
  *
564
563
  * const decoder = new EntityDecoder({ namedEntities: { copy: '©' } });
565
564
  * Effect.runSync(Effect.orElseSucceed(decoder.addExternalEntity('brand', 'Acme'), () => undefined));
@@ -584,15 +583,15 @@ var EntityDecoder = class EntityDecoder {
584
583
  /**
585
584
  * @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
586
585
  * `&` 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
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.
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. The pass
587
+ * stays single because of that, and a registered value containing `&` cannot expand a second level. What a reference becomes is `#resolveToken`'s
588
+ * to 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.
590
589
  *
591
590
  * @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
592
591
  * walk.
593
592
  *
594
593
  * @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
595
- * its own result". Fails with {@link XmlError} and the reason the offending reference carries — `ProhibitedCharacterReference`,
594
+ * its own result". Fails with {@link XmlError} and the reason the offending reference carries: `ProhibitedCharacterReference`,
596
595
  * `ExpansionLimitExceeded` or `ExpandedLengthLimitExceeded`.
597
596
  */
598
597
  #expandAll = Effect.fnUntraced(function* (str) {
@@ -628,19 +627,19 @@ var EntityDecoder = class EntityDecoder {
628
627
  /**
629
628
  * @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:
630
629
  *
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
630
+ * 1. `remove`: deleted outright, without the name ever being resolved, so the name need not exist.
631
+ * 2. `leave`: emitted as the original `&token;`, and charged to nothing.
632
+ * 3. A `#`-prefixed token: the numeric pipeline, which is the only one of the four that can fail. Classification runs before any decision about
634
633
  * `numericAllowed`, because the ranges that carry a minimum have to be caught whichever way that option is set.
635
- * 4. Anything else — resolved against the input map, then the external map, then the base map.
634
+ * 4. Anything else: resolved against the input map, then the external map, then the base map.
636
635
  *
637
636
  * @param token - The reference's token, e.g. `brand` or `#38`, with the `&` and the `;` already stripped. Never empty: the scanner drops `&;`
638
637
  * before calling.
639
638
  *
640
639
  * @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
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.
640
+ * nothing. `undefined` covers all three ways of leaving a reference alone (a listed `leave` name, a numeric reference that is out of range, and a
641
+ * name registered nowhere), and none of them is distinguishable from outside. Fails with {@link XmlError} and the `ProhibitedCharacterReference`
642
+ * reason when the numeric policy throws on the codepoint.
644
643
  */
645
644
  #resolveToken = Effect.fnUntraced(function* (token) {
646
645
  if (this.#removeSet.has(token)) return {
@@ -681,9 +680,9 @@ var EntityDecoder = class EntityDecoder {
681
680
  });
682
681
  /**
683
682
  * @description Add one expansion to the running total and compare it against {@link EntityDecoderLimitOptions.maxTotalExpansions}. The comparison is `>` rather
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.
683
+ * than `>=`, so a limit of `n` allows exactly `n` expansions and throws on the `n + 1`th. That is a contract, and the option's own documentation
684
+ * states it. It is also the kind of off-by-one a tidy-up changes by accident. The counter is intentionally not reset before failing: the over-limit
685
+ * total reports the over-limit total, and {@link EntityDecoder.reset} is the caller's way to start a new document.
687
686
  *
688
687
  * @returns An effect that fails with {@link XmlError} and the `ExpansionLimitExceeded` reason once the count is past the ceiling, and succeeds
689
688
  * otherwise. The `EntityReplacer` prefix in the message is preserved verbatim from the original throw, despite naming a class this decoder does
@@ -704,7 +703,7 @@ var EntityDecoder = class EntityDecoder {
704
703
  /**
705
704
  * @description Add one expansion's surplus to the running total and compare it against {@link EntityDecoderLimitOptions.maxExpandedLength}. Only the surplus
706
705
  * counts, and only upward: a reference whose replacement is no longer than the `&token;` it replaces contributes zero, and a shrinking one
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.
706
+ * contributes nothing and cannot trip the limit at all. That keeps the ceiling a bound on growth rather than on document size.
708
707
  *
709
708
  * @param token - The reference's token, with the `&` and `;` stripped. The two delimiters count towards what the expansion displaced.
710
709
  * @param replacement - What the reference expanded to, including `''` for a removal.
@@ -730,8 +729,8 @@ var EntityDecoder = class EntityDecoder {
730
729
  /**
731
730
  * @description Decide whether an entity of a given tier is charged against the limits.
732
731
  *
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.
732
+ * @param tier - The tier the replacement is charged to. Every expansion that reaches here carries one (a name deleted before it was ever resolved
733
+ * still carries the `external` tier), so there is no absent case to answer.
735
734
  *
736
735
  * @returns `true` when it counts. `'all'` short-circuits, so a filter naming every tier charges everything regardless of which map it came from.
737
736
  */
@@ -767,9 +766,9 @@ var EntityDecoder = class EntityDecoder {
767
766
  /**
768
767
  * @description Find the strictest action a codepoint's range requires. Checked in this order:
769
768
  *
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
769
+ * 1. U+0000: governed by `nullNCR`, already clamped to `remove` or stricter
770
+ * 2. U+D800 to U+DFFF: surrogates, always `remove`, under every policy and both XML versions
771
+ * 3. U+0001 to U+001F other than tab, newline, carriage return: XML 1.0 only, `remove` Nothing else is classified. U+007F to U+009F (C1) and the
773
772
  * 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
774
773
  * is only permitted under 1.1. Both gaps are upstream's and are kept.
775
774
  *
@@ -787,11 +786,11 @@ var EntityDecoder = class EntityDecoder {
787
786
  * @description Turn a resolved action level into a replacement.
788
787
  *
789
788
  * @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
790
- * produce a wrong string — it can only fail open.
789
+ * produce a wrong string. It can only fail open.
791
790
  * @param token - The raw token, e.g. `#38`, for the error message.
792
791
  * @param cp - The codepoint, for the error message.
793
792
  *
794
- * @returns An effect producing the character for `allow`, `''` for `remove`, and `undefined` for `leave` — which the caller reads as "emit the
793
+ * @returns An effect producing the character for `allow`, `''` for `remove`, and `undefined` for `leave`, which the caller reads as "emit the
795
794
  * original `&token;`". Fails with {@link XmlError} and the `ProhibitedCharacterReference` reason for `throw`, naming both the token and the
796
795
  * codepoint.
797
796
  */
@@ -820,7 +819,7 @@ var EntityDecoder = class EntityDecoder {
820
819
  *
821
820
  * @param token - The raw token without `&` and `;`, e.g. `#38`, `#x26`, `#X26`.
822
821
  *
823
- * @returns An effect producing the replacement — the empty string meaning "delete" — or `undefined` to leave the reference as written. Fails with
822
+ * @returns An effect producing the replacement (the empty string meaning "delete") or `undefined` to leave the reference as written. Fails with
824
823
  * {@link XmlError} and the `ProhibitedCharacterReference` reason when the effective action is `throw`.
825
824
  */
826
825
  #resolveNCR(token) {