@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.
Files changed (47) hide show
  1. package/README.md +62 -63
  2. package/dist/codec.d.ts +9 -17
  3. package/dist/codec.d.ts.map +1 -1
  4. package/dist/codec.js +16 -25
  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 +64 -63
  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 +7 -6
  35. package/dist/xml-value.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/codec.ts +71 -91
  38. package/src/conventions.ts +7 -7
  39. package/src/entities/entity-decoder.ts +99 -98
  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 +35 -34
  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 +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. 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 keeps the built-in entities, including every numeric reference, from being
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 to 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–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 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.
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: 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. This is why the option is 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 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 does not distinguish them.
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. 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 intentionally, because a consumer's output already depends on them:
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: 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 to 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–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 {@link readHook}
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, meaning how much one round of expansion can add. Three inputs return before the scan and therefore never reach
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-xml';
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
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. 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.
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. 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.
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. 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. A range can therefore only make an entity stricter than the caller asked for, never more lenient.
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 to 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–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 relied on by anything matching the message text.
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 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`.
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 (`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 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
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, 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 `''`. That is why
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 `''`. That 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 `''`, 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. An empty array therefore disables limit accounting entirely, while an empty string falls back to `external`.
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 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.
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**: injected per document through {@link EntityDecoder.addInputEntities}
275
- * 2. **persistent external**: set through {@link EntityDecoder.setExternalEntities} and {@link EntityDecoder.addExternalEntity}, surviving
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**: the five XML predefined entities plus the constructor's `namedEntities` Both input and external resolve as the `external` tier for limit
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 intentionally, because a consumer's output already depends on them:
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: an empty string, a non-string, or a string with
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 to U+009F or for the U+FFFE/U+FFFF noncharacters, and the sweep for `&` leaves a name of
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 `&#41zz;` all
295
296
  * decode, and `&#0x41;` 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; &copy;'); // 'Acme v1.0 ©'
306
- * decoder.decode('&#x26;#38;'); // '&&', one pass, the output is never re-scanned
307
+ * decoder.decode('&#x26;#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. That lets the decode loop call it unconditionally on the path that actually
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}. That is the whole distinction from the input map.
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 rather
351
- * than a per-call one. Intentionally not reset by a thrown limit error, so the error message reports the over-limit count.
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". A caller who wrote it meant something the signature does not allow, and a decoder built from it would be
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 (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.
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, so {@link EntityDecoder.addExternalEntity} can call this unconditionally.
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, meaning how much one round of expansion can add. Three inputs return before the scan and therefore never reach
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-xml';
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. 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.
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: `ProhibitedCharacterReference`,
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`: 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
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: resolved against the input map, then the external map, then the base map.
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 (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.
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, 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.
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 keeps the ceiling a bound on growth rather than on document size.
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 (a name deleted before it was ever resolved
733
- * still carries the `external` tier), so there is no absent case to answer.
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: 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
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. It can only fail open.
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`, which the caller reads as "emit the
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 (the empty string meaning "delete") or `undefined` to leave the reference as written. Fails with
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) {