@nodable/entities 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -63
- package/package.json +1 -1
- package/src/EntityReplacer.js +10 -3
- package/src/index.d.ts +7 -122
- package/src/index.js +0 -1
- package/src/EntitiesValueParser.js +0 -152
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Standalone, zero-dependency XML/HTML entity replacement with:
|
|
|
4
4
|
|
|
5
5
|
- **5 entity categories** processed in a fixed, predictable order
|
|
6
6
|
- **Persistent vs. input entity separation** — no state leaks between documents
|
|
7
|
-
- **`
|
|
7
|
+
- **`reset()`** — clean per-document reset without cloning
|
|
8
8
|
- **Composable named entity groups** (HTML, currency, math, arrows, numeric refs)
|
|
9
9
|
- **Security limits** — cap total expansions and expanded length per document
|
|
10
10
|
- **Granular limit targeting** — apply limits to any subset of categories
|
|
@@ -57,7 +57,7 @@ persistent input/runtime → external → system → default → amp
|
|
|
57
57
|
|
|
58
58
|
### `persistent external` — Caller-supplied configuration entities
|
|
59
59
|
|
|
60
|
-
Entities set at configuration time that survive across all documents. Never wiped by `
|
|
60
|
+
Entities set at configuration time that survive across all documents. Never wiped by `reset()`. Set via `setExternalEntities()` or `addExternalEntity()` / `addEntity()`.
|
|
61
61
|
|
|
62
62
|
```js
|
|
63
63
|
const replacer = new EntityReplacer({ default: true });
|
|
@@ -68,7 +68,7 @@ replacer.replace('&brand; makes &product;');
|
|
|
68
68
|
|
|
69
69
|
### `input / runtime` — Per-document DOCTYPE entities
|
|
70
70
|
|
|
71
|
-
Entities injected by the parser from the document's DOCTYPE block. Stored separately from persistent entities and **wiped on every `
|
|
71
|
+
Entities injected by the parser from the document's DOCTYPE block. Stored separately from persistent entities and **wiped on every `reset()` call** so they cannot leak between documents.
|
|
72
72
|
|
|
73
73
|
Set via `addInputEntities()`. Never call this manually — `BaseOutputBuilder` calls it automatically.
|
|
74
74
|
|
|
@@ -155,7 +155,7 @@ replacer.replace('Tom & Jerry <cartoons>');
|
|
|
155
155
|
|
|
156
156
|
### `setExternalEntities(map)`
|
|
157
157
|
|
|
158
|
-
Replace the full set of **persistent** external entities. These survive across all documents and are not cleared by `
|
|
158
|
+
Replace the full set of **persistent** external entities. These survive across all documents and are not cleared by `reset()`.
|
|
159
159
|
|
|
160
160
|
```js
|
|
161
161
|
replacer.setExternalEntities({ brand: 'Acme', year: '2025' });
|
|
@@ -174,7 +174,7 @@ replacer.addExternalEntity('year', '2025');
|
|
|
174
174
|
|
|
175
175
|
### `addInputEntities(map)`
|
|
176
176
|
|
|
177
|
-
Inject **input/runtime** (DOCTYPE) entities for the current document. These are stored separately from persistent entities and wiped on the next `
|
|
177
|
+
Inject **input/runtime** (DOCTYPE) entities for the current document. These are stored separately from persistent entities and wiped on the next `reset()` call. Also resets per-document expansion counters.
|
|
178
178
|
|
|
179
179
|
```js
|
|
180
180
|
// Called automatically by BaseOutputBuilder — no manual wiring needed.
|
|
@@ -183,7 +183,7 @@ replacer.addInputEntities(doctypeEntityMap);
|
|
|
183
183
|
|
|
184
184
|
Values containing `&` are silently skipped. Accepts pre-built `{ regex, val }` or `{ regx, val }` objects as produced by `DocTypeReader`.
|
|
185
185
|
|
|
186
|
-
### `
|
|
186
|
+
### `reset()`
|
|
187
187
|
|
|
188
188
|
Reset all per-document state and return `this`.
|
|
189
189
|
|
|
@@ -200,9 +200,9 @@ The builder factory calls this when creating a new builder instance, ensuring ea
|
|
|
200
200
|
|
|
201
201
|
```js
|
|
202
202
|
// In a builder factory:
|
|
203
|
-
|
|
203
|
+
reset() {
|
|
204
204
|
const builder = new MyBuilder(this.config);
|
|
205
|
-
builder.entityParser = this.entityVP.
|
|
205
|
+
builder.entityParser = this.entityVP.reset();
|
|
206
206
|
return builder;
|
|
207
207
|
}
|
|
208
208
|
```
|
|
@@ -215,12 +215,12 @@ A key design goal is that entities from one document never bleed into the next.
|
|
|
215
215
|
|
|
216
216
|
```
|
|
217
217
|
Document 1 parse:
|
|
218
|
-
factory.
|
|
218
|
+
factory.reset() → evp.reset() [clears input, resets counters]
|
|
219
219
|
builder sees DOCTYPE → evp.addInputEntities({ version: '1.0' })
|
|
220
220
|
builder processes values → evp.parse('&brand; v&version;') → 'Acme v1.0'
|
|
221
221
|
|
|
222
222
|
Document 2 parse (no DOCTYPE):
|
|
223
|
-
factory.
|
|
223
|
+
factory.reset() → evp.reset() [clears &version;, resets counters]
|
|
224
224
|
no DOCTYPE → addInputEntities() not called
|
|
225
225
|
builder processes values → evp.parse('&brand; v&version;') → 'Acme v&version;'
|
|
226
226
|
↑ persistent &brand; works
|
|
@@ -300,16 +300,14 @@ postCheck: (resolved) =>
|
|
|
300
300
|
|
|
301
301
|
---
|
|
302
302
|
|
|
303
|
-
##
|
|
304
|
-
|
|
305
|
-
`EntitiesValueParser` wraps `EntityReplacer` and implements the `ValueParser` interface used by `@nodable/flexible-xml-parser`.
|
|
303
|
+
## Integration with — flex-xml-parser adapter
|
|
306
304
|
|
|
307
305
|
### Setup
|
|
308
306
|
|
|
309
307
|
```js
|
|
310
|
-
import {
|
|
308
|
+
import EntityReplacer, { COMMON_HTML } from '@nodable/entities';
|
|
311
309
|
|
|
312
|
-
const evp = new
|
|
310
|
+
const evp = new EntityReplacer({
|
|
313
311
|
system: COMMON_HTML,
|
|
314
312
|
maxTotalExpansions: 500,
|
|
315
313
|
});
|
|
@@ -329,7 +327,7 @@ parser.parse(xml);
|
|
|
329
327
|
All `EntityReplacerOptions` are accepted, plus one extra:
|
|
330
328
|
|
|
331
329
|
```js
|
|
332
|
-
new
|
|
330
|
+
new EntityReplacer({
|
|
333
331
|
// All EntityReplacer options...
|
|
334
332
|
default: true,
|
|
335
333
|
system: COMMON_HTML,
|
|
@@ -341,48 +339,20 @@ new EntitiesValueParser({
|
|
|
341
339
|
})
|
|
342
340
|
```
|
|
343
341
|
|
|
344
|
-
### `
|
|
345
|
-
|
|
346
|
-
Replace the full persistent entity map. These entities survive across all documents.
|
|
347
|
-
|
|
348
|
-
```js
|
|
349
|
-
evp.setExternalEntities({ brand: 'Acme', copy: '©' });
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
### `addEntity(key, value)`
|
|
353
|
-
|
|
354
|
-
Append a single persistent external entity. Previously registered entities are preserved.
|
|
355
|
-
|
|
356
|
-
```js
|
|
357
|
-
evp.addEntity('copy', '©');
|
|
358
|
-
evp.addEntity('trade', '™');
|
|
359
|
-
evp.addEntity('year', '2024');
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
Throws if `key` contains `&` or `;`, or if `value` contains `&`.
|
|
363
|
-
|
|
364
|
-
### `getInstance()` — called by builder factory
|
|
342
|
+
### `reset()` — called by builder factory
|
|
365
343
|
|
|
366
344
|
Reset per-document state (input entities + counters) and return `this`. The builder factory calls this each time it creates a new builder instance.
|
|
367
345
|
|
|
368
346
|
```js
|
|
369
|
-
// In your CompactObjBuilderFactory.
|
|
370
|
-
|
|
347
|
+
// In your CompactObjBuilderFactory.reset():
|
|
348
|
+
reset() {
|
|
371
349
|
const builder = new CompactObjBuilder(this._config);
|
|
372
350
|
// Reset EVP for the new document:
|
|
373
|
-
builder.entityParser = this._entityVP.
|
|
351
|
+
builder.entityParser = this._entityVP.reset();
|
|
374
352
|
return builder;
|
|
375
353
|
}
|
|
376
354
|
```
|
|
377
355
|
|
|
378
|
-
### `addInputEntities(entities)` — called automatically
|
|
379
|
-
|
|
380
|
-
Receives the DOCTYPE entity map from `BaseOutputBuilder` once per parse. Resets per-document expansion counters. Accepts both plain string values and `{ regx, val }` objects from `DocTypeReader`.
|
|
381
|
-
|
|
382
|
-
### `parse(val, context?)`
|
|
383
|
-
|
|
384
|
-
Implements the `ValueParser` interface. `context` is accepted but ignored. Returns non-string input unchanged.
|
|
385
|
-
|
|
386
356
|
---
|
|
387
357
|
|
|
388
358
|
## Custom Entity Tables
|
|
@@ -421,7 +391,7 @@ const replacer = new EntityReplacer({
|
|
|
421
391
|
| Numeric refs with leading zeros | ✅ | ✅ |
|
|
422
392
|
| DOCTYPE / external entity injection | ❌ | ✅ |
|
|
423
393
|
| Persistent vs. input entity separation | ❌ | ✅ |
|
|
424
|
-
| Per-document reset via `
|
|
394
|
+
| Per-document reset via `reset()` | ❌ | ✅ |
|
|
425
395
|
| Expansion count limit | ❌ | ✅ |
|
|
426
396
|
| Expanded length limit | ❌ | ✅ |
|
|
427
397
|
| `applyLimitsTo` granularity | ❌ | ✅ |
|
|
@@ -437,11 +407,9 @@ Full TypeScript declarations are included via `index.d.ts`. No `@types/` package
|
|
|
437
407
|
|
|
438
408
|
```ts
|
|
439
409
|
import EntityReplacer, {
|
|
440
|
-
EntitiesValueParser,
|
|
441
410
|
COMMON_HTML,
|
|
442
411
|
EntityTable,
|
|
443
412
|
EntityReplacerOptions,
|
|
444
|
-
EntitiesValueParserOptions,
|
|
445
413
|
} from '@nodable/entities';
|
|
446
414
|
|
|
447
415
|
// EntityReplacer
|
|
@@ -454,19 +422,8 @@ const opts: EntityReplacerOptions = {
|
|
|
454
422
|
};
|
|
455
423
|
const replacer = new EntityReplacer(opts);
|
|
456
424
|
replacer.setExternalEntities({ brand: 'Acme' });
|
|
457
|
-
replacer.
|
|
425
|
+
replacer.reset(); // reset for new document
|
|
458
426
|
replacer.addInputEntities({ version: '1.0' }); // from DOCTYPE
|
|
459
|
-
|
|
460
|
-
// EntitiesValueParser
|
|
461
|
-
const evpOpts: EntitiesValueParserOptions = {
|
|
462
|
-
system: COMMON_HTML,
|
|
463
|
-
entities: { brand: 'Acme' },
|
|
464
|
-
};
|
|
465
|
-
const evp = new EntitiesValueParser(evpOpts);
|
|
466
|
-
evp.addEntity('copy', '©');
|
|
467
|
-
evp.getInstance(); // called by builder factory
|
|
468
|
-
evp.addInputEntities({ company: 'Nodable' }); // called by BaseOutputBuilder
|
|
469
|
-
const result: string = evp.parse('<©&brand;');
|
|
470
427
|
```
|
|
471
428
|
|
|
472
429
|
## Note
|
package/package.json
CHANGED
package/src/EntityReplacer.js
CHANGED
|
@@ -230,13 +230,11 @@ export default class EntityReplacer {
|
|
|
230
230
|
* The builder factory calls this each time it creates a new builder instance
|
|
231
231
|
* so DOCTYPE entities from a previous document are never carried over.
|
|
232
232
|
*
|
|
233
|
-
* @returns {EntityReplacer} `this`, after reset
|
|
234
233
|
*/
|
|
235
|
-
|
|
234
|
+
reset() {
|
|
236
235
|
this._inputEntries = [];
|
|
237
236
|
this._totalExpansions = 0;
|
|
238
237
|
this._expandedLength = 0;
|
|
239
|
-
return this;
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
// -------------------------------------------------------------------------
|
|
@@ -295,6 +293,15 @@ export default class EntityReplacer {
|
|
|
295
293
|
return str;
|
|
296
294
|
}
|
|
297
295
|
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
*
|
|
299
|
+
* @param {string} val
|
|
300
|
+
* @returns
|
|
301
|
+
*/
|
|
302
|
+
parse(val) {
|
|
303
|
+
return this.replace(val);
|
|
304
|
+
}
|
|
298
305
|
// -------------------------------------------------------------------------
|
|
299
306
|
// Private helpers
|
|
300
307
|
// -------------------------------------------------------------------------
|
package/src/index.d.ts
CHANGED
|
@@ -212,9 +212,8 @@ export default class EntityReplacer {
|
|
|
212
212
|
* The builder factory calls this when creating a new builder instance,
|
|
213
213
|
* ensuring each document starts clean regardless of whether it has a DOCTYPE.
|
|
214
214
|
*
|
|
215
|
-
* @returns `this` — for convenient chaining in factory code
|
|
216
215
|
*/
|
|
217
|
-
|
|
216
|
+
reset(): this;
|
|
218
217
|
|
|
219
218
|
// -------------------------------------------------------------------------
|
|
220
219
|
// Primary API
|
|
@@ -225,26 +224,18 @@ export default class EntityReplacer {
|
|
|
225
224
|
* Returns `str` unchanged if it contains no `&` character (fast path).
|
|
226
225
|
*/
|
|
227
226
|
replace(str: string): string;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* wrapper on replace()
|
|
230
|
+
*/
|
|
231
|
+
parse(str: string): string;
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
// ---------------------------------------------------------------------------
|
|
231
235
|
// EntitiesValueParser
|
|
232
236
|
// ---------------------------------------------------------------------------
|
|
233
237
|
|
|
234
|
-
|
|
235
|
-
* Options accepted by `EntitiesValueParser` — a superset of `EntityReplacerOptions`.
|
|
236
|
-
*/
|
|
237
|
-
export interface EntitiesValueParserOptions extends EntityReplacerOptions {
|
|
238
|
-
/**
|
|
239
|
-
* Initial persistent external entity map loaded at construction time.
|
|
240
|
-
* Values must not contain `&` (to prevent recursive expansion).
|
|
241
|
-
* Equivalent to calling `setExternalEntities()` after construction.
|
|
242
|
-
*
|
|
243
|
-
* @example
|
|
244
|
-
* new EntitiesValueParser({ entities: { copy: '©', trade: '™' } })
|
|
245
|
-
*/
|
|
246
|
-
entities?: Record<string, string>;
|
|
247
|
-
}
|
|
238
|
+
|
|
248
239
|
|
|
249
240
|
/**
|
|
250
241
|
* Raw DOCTYPE entity map shape as produced by `DocTypeReader`.
|
|
@@ -270,112 +261,6 @@ export interface ValueParserContext {
|
|
|
270
261
|
isLeafNode?: boolean;
|
|
271
262
|
}
|
|
272
263
|
|
|
273
|
-
/**
|
|
274
|
-
* `EntitiesValueParser` — value-parser adapter that wraps `EntityReplacer`
|
|
275
|
-
* for use with `@nodable/flexible-xml-parser`.
|
|
276
|
-
*
|
|
277
|
-
* ## Setup
|
|
278
|
-
*
|
|
279
|
-
* ```ts
|
|
280
|
-
* import { EntitiesValueParser, COMMON_HTML } from '@nodable/entities';
|
|
281
|
-
*
|
|
282
|
-
* const evp = new EntitiesValueParser({ system: COMMON_HTML });
|
|
283
|
-
*
|
|
284
|
-
* // Persistent entities — never wiped between documents:
|
|
285
|
-
* evp.setExternalEntities({ brand: 'Acme', product: 'Widget' });
|
|
286
|
-
*
|
|
287
|
-
* // Register with the builder factory:
|
|
288
|
-
* builder.registerValueParser('entity', evp);
|
|
289
|
-
*
|
|
290
|
-
* const parser = new XMLParser({ OutputBuilder: builder });
|
|
291
|
-
* parser.parse(xml);
|
|
292
|
-
* ```
|
|
293
|
-
*
|
|
294
|
-
* ## Lifecycle (called automatically by the builder / parser)
|
|
295
|
-
*
|
|
296
|
-
* | Caller | Method | When |
|
|
297
|
-
* |-----------------|----------------------|-------------------------------------------|
|
|
298
|
-
* | Builder factory | `getInstance()` | Before each `parse()` call |
|
|
299
|
-
* | Builder | `addInputEntities()` | After DOCTYPE is read (if present) |
|
|
300
|
-
* | Builder | `parse(val)` | For each text / attribute value |
|
|
301
|
-
*/
|
|
302
|
-
export class EntitiesValueParser {
|
|
303
|
-
constructor(options?: EntitiesValueParserOptions);
|
|
304
|
-
|
|
305
|
-
// -------------------------------------------------------------------------
|
|
306
|
-
// Persistent external entity registration
|
|
307
|
-
// -------------------------------------------------------------------------
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Replace the full set of persistent external entities.
|
|
311
|
-
*
|
|
312
|
-
* These survive across all documents and are **not** cleared by
|
|
313
|
-
* `getInstance()`. Call this once after construction (or at any time to
|
|
314
|
-
* swap the entire persistent entity map).
|
|
315
|
-
*
|
|
316
|
-
* @throws if any value contains `&`
|
|
317
|
-
*/
|
|
318
|
-
setExternalEntities(map: Record<string, string>): void;
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Append a single persistent external entity.
|
|
322
|
-
*
|
|
323
|
-
* Provide the bare name without `&` and `;` — e.g. `'copy'` for `©`.
|
|
324
|
-
* Existing persistent entities are preserved.
|
|
325
|
-
*
|
|
326
|
-
* @throws if `key` contains `&` or `;`
|
|
327
|
-
* @throws if `value` is not a string or contains `&`
|
|
328
|
-
*/
|
|
329
|
-
addEntity(key: string, value: string): void;
|
|
330
|
-
|
|
331
|
-
// -------------------------------------------------------------------------
|
|
332
|
-
// Builder factory integration
|
|
333
|
-
// -------------------------------------------------------------------------
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Reset per-document state and return `this`.
|
|
337
|
-
*
|
|
338
|
-
* Clears input/runtime entities (DOCTYPE) and resets expansion counters.
|
|
339
|
-
* Does **not** clear persistent external entities.
|
|
340
|
-
*
|
|
341
|
-
* The builder factory calls this when creating a new builder instance.
|
|
342
|
-
*
|
|
343
|
-
* @returns `this`
|
|
344
|
-
*/
|
|
345
|
-
getInstance(): this;
|
|
346
|
-
|
|
347
|
-
// -------------------------------------------------------------------------
|
|
348
|
-
// DOCTYPE integration — called automatically by BaseOutputBuilder
|
|
349
|
-
// -------------------------------------------------------------------------
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Receive DOCTYPE entities for the current document.
|
|
353
|
-
*
|
|
354
|
-
* Called automatically by `BaseOutputBuilder`. Stores entities separately
|
|
355
|
-
* from persistent entities so they are wiped on the next `getInstance()`.
|
|
356
|
-
* Also resets per-document expansion counters.
|
|
357
|
-
*
|
|
358
|
-
* Accepts both plain string values and `{ regx, val }` / `{ regex, val }`
|
|
359
|
-
* objects as produced by `DocTypeReader`.
|
|
360
|
-
*/
|
|
361
|
-
addInputEntities(entities: DocTypeEntityMap): void;
|
|
362
|
-
|
|
363
|
-
// -------------------------------------------------------------------------
|
|
364
|
-
// ValueParser interface
|
|
365
|
-
// -------------------------------------------------------------------------
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Replace entity references in `val`.
|
|
369
|
-
*
|
|
370
|
-
* Implements the `ValueParser` interface. The `context` argument is
|
|
371
|
-
* accepted but ignored — replacement is applied uniformly to all values.
|
|
372
|
-
*
|
|
373
|
-
* Returns non-string input unchanged.
|
|
374
|
-
*/
|
|
375
|
-
parse(val: string, context?: ValueParserContext): string;
|
|
376
|
-
parse(val: unknown, context?: ValueParserContext): unknown;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
264
|
// ---------------------------------------------------------------------------
|
|
380
265
|
// Named entity group exports
|
|
381
266
|
// ---------------------------------------------------------------------------
|
package/src/index.js
CHANGED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import EntityReplacer from './EntityReplacer.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* EntitiesValueParser — value-parser adapter that wraps `EntityReplacer`.
|
|
5
|
-
*
|
|
6
|
-
* Register an instance under the key `'entity'` on a `@nodable/flexible-xml-parser`
|
|
7
|
-
* output builder factory to enable entity expansion for all parsed text values.
|
|
8
|
-
*
|
|
9
|
-
* ## Lifecycle
|
|
10
|
-
*
|
|
11
|
-
* 1. **Construction** — supply configuration and optional persistent entities.
|
|
12
|
-
* 2. **`setExternalEntities(map)`** — (re)set the full persistent entity map.
|
|
13
|
-
* Or use `addEntity(key, value)` to add one at a time.
|
|
14
|
-
* 3. **`getInstance()`** — builder factory calls this when creating a new builder
|
|
15
|
-
* instance. Resets input entities and per-document counters. Returns `this`.
|
|
16
|
-
* 4. **`addInputEntities(map)`** — builder calls this if the document has a
|
|
17
|
-
* DOCTYPE block. Stores entities for *this document only*.
|
|
18
|
-
* 5. **`parse(val)`** — called by the builder for each text value.
|
|
19
|
-
*
|
|
20
|
-
* ```js
|
|
21
|
-
* const evp = new EntitiesValueParser({ system: COMMON_HTML });
|
|
22
|
-
* evp.setExternalEntities({ brand: 'Acme' });
|
|
23
|
-
* builder.registerValueParser('entity', evp);
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* -------------------------------------------------------------------------
|
|
27
|
-
* Constructor options (all optional)
|
|
28
|
-
* -------------------------------------------------------------------------
|
|
29
|
-
*
|
|
30
|
-
* `default` — `true` (default) | `false`/`null` | custom EntityTable
|
|
31
|
-
* `system` — `false` (default) | `true` for COMMON_HTML | EntityTable
|
|
32
|
-
* `amp` — `true` (default) | `false`/`null`
|
|
33
|
-
* `maxTotalExpansions` — max entity refs expanded per document (0 = unlimited)
|
|
34
|
-
* `maxExpandedLength` — max characters added by expansion per document (0 = unlimited)
|
|
35
|
-
* `applyLimitsTo` — which categories count toward limits (default: `'external'`)
|
|
36
|
-
* `postCheck` — `(resolved, original) => string` hook
|
|
37
|
-
* `entities` — initial persistent entity map, e.g. `{ copy: '©' }`
|
|
38
|
-
*/
|
|
39
|
-
export default class EntitiesValueParser {
|
|
40
|
-
constructor(options = {}) {
|
|
41
|
-
this._replacer = new EntityReplacer(options);
|
|
42
|
-
|
|
43
|
-
// Load any entities provided inline at construction time as persistent entities
|
|
44
|
-
if (options.entities && typeof options.entities === 'object') {
|
|
45
|
-
const init = {};
|
|
46
|
-
for (const [key, val] of Object.entries(options.entities)) {
|
|
47
|
-
this._validateEntityArgs(key, val);
|
|
48
|
-
init[key] = val;
|
|
49
|
-
}
|
|
50
|
-
this._replacer.setExternalEntities(init);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// -------------------------------------------------------------------------
|
|
55
|
-
// Persistent external entity registration
|
|
56
|
-
// -------------------------------------------------------------------------
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Replace the full set of persistent external entities.
|
|
60
|
-
* These survive across documents and are never wiped by `getInstance()`.
|
|
61
|
-
*
|
|
62
|
-
* @param {Record<string, string>} map — e.g. `{ copy: '©', brand: 'Acme' }`
|
|
63
|
-
*/
|
|
64
|
-
setExternalEntities(map) {
|
|
65
|
-
for (const [key, val] of Object.entries(map)) {
|
|
66
|
-
this._validateEntityArgs(key, val);
|
|
67
|
-
}
|
|
68
|
-
this._replacer.setExternalEntities(map);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Add (or replace) a single persistent external entity.
|
|
73
|
-
* Existing persistent entities are preserved.
|
|
74
|
-
*
|
|
75
|
-
* @param {string} key — bare name without `&` / `;`, e.g. `'copy'`
|
|
76
|
-
* @param {string} value — replacement string, e.g. `'©'`
|
|
77
|
-
*/
|
|
78
|
-
addEntity(key, value) {
|
|
79
|
-
this._validateEntityArgs(key, value);
|
|
80
|
-
this._replacer.addExternalEntity(key, value);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// -------------------------------------------------------------------------
|
|
84
|
-
// Builder factory integration
|
|
85
|
-
// -------------------------------------------------------------------------
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Reset per-document state (input entities + expansion counters) and return `this`.
|
|
89
|
-
*
|
|
90
|
-
* The builder factory calls this when creating a new builder instance so that
|
|
91
|
-
* DOCTYPE entities from a previous document are never carried over.
|
|
92
|
-
*
|
|
93
|
-
* @returns {EntitiesValueParser} `this`
|
|
94
|
-
*/
|
|
95
|
-
getInstance() {
|
|
96
|
-
this._replacer.getInstance();
|
|
97
|
-
return this;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// -------------------------------------------------------------------------
|
|
101
|
-
// DOCTYPE integration — called by BaseOutputBuilder
|
|
102
|
-
// -------------------------------------------------------------------------
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Receive DOCTYPE entities from the output builder.
|
|
106
|
-
*
|
|
107
|
-
* These are stored separately from persistent entities and wiped on the next
|
|
108
|
-
* `getInstance()` call. Resets per-document expansion counters.
|
|
109
|
-
*
|
|
110
|
-
* @param {Record<string, string | { regx: RegExp, val: string | Function }>} entities
|
|
111
|
-
* Raw entity map from `DocTypeReader` — values may be plain strings or
|
|
112
|
-
* `{ regx, val }` objects (note: `regx`, not `regex`, matching the reader's output).
|
|
113
|
-
*/
|
|
114
|
-
addInputEntities(entities) {
|
|
115
|
-
this._replacer.addInputEntities(entities);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// -------------------------------------------------------------------------
|
|
119
|
-
// ValueParser interface
|
|
120
|
-
// -------------------------------------------------------------------------
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Replace entity references in `val`.
|
|
124
|
-
*
|
|
125
|
-
* @param {string} val
|
|
126
|
-
* @param {object} [_context]
|
|
127
|
-
* @returns {string}
|
|
128
|
-
*/
|
|
129
|
-
parse(val, _context) {
|
|
130
|
-
if (typeof val !== 'string') return val;
|
|
131
|
-
return this._replacer.replace(val);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// -------------------------------------------------------------------------
|
|
135
|
-
// Private helpers
|
|
136
|
-
// -------------------------------------------------------------------------
|
|
137
|
-
|
|
138
|
-
_validateEntityArgs(key, value) {
|
|
139
|
-
if (typeof key !== 'string' || key.includes('&') || key.includes(';')) {
|
|
140
|
-
throw new Error(
|
|
141
|
-
`[EntitiesValueParser] Entity key must not contain '&' or ';'. ` +
|
|
142
|
-
`Use 'copy' for '©', got: ${JSON.stringify(key)}`
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
if (typeof value !== 'string' || value.includes('&')) {
|
|
146
|
-
throw new Error(
|
|
147
|
-
`[EntitiesValueParser] Entity value must be a plain string that does not ` +
|
|
148
|
-
`contain '&', got: ${JSON.stringify(value)}`
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|