🚀. Socket Launch Week Day 2:Introducing Manifest Alerts.Learn more
Sign In

@nodable/entities

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nodable/entities - npm Package Compare versions

Comparing version
1.0.1
to
1.1.0
+1
-1
package.json
{
"name": "@nodable/entities",
"version": "1.0.1",
"version": "1.1.0",
"description": "Replace XML, HTML, External entites with security controls",

@@ -5,0 +5,0 @@ "main": "./src/index.js",

+20
-63

@@ -7,3 +7,3 @@ # `@nodable/entities`

- **Persistent vs. input entity separation** — no state leaks between documents
- **`getInstance()`** — clean per-document reset without cloning
- **`reset()`** — clean per-document reset without cloning
- **Composable named entity groups** (HTML, currency, math, arrows, numeric refs)

@@ -61,3 +61,3 @@ - **Security limits** — cap total expansions and expanded length per document

Entities set at configuration time that survive across all documents. Never wiped by `getInstance()`. Set via `setExternalEntities()` or `addExternalEntity()` / `addEntity()`.
Entities set at configuration time that survive across all documents. Never wiped by `reset()`. Set via `setExternalEntities()` or `addExternalEntity()` / `addEntity()`.

@@ -73,3 +73,3 @@ ```js

Entities injected by the parser from the document's DOCTYPE block. Stored separately from persistent entities and **wiped on every `getInstance()` call** so they cannot leak between documents.
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.

@@ -161,3 +161,3 @@ Set via `addInputEntities()`. Never call this manually — `BaseOutputBuilder` calls it automatically.

Replace the full set of **persistent** external entities. These survive across all documents and are not cleared by `getInstance()`.
Replace the full set of **persistent** external entities. These survive across all documents and are not cleared by `reset()`.

@@ -181,3 +181,3 @@ ```js

Inject **input/runtime** (DOCTYPE) entities for the current document. These are stored separately from persistent entities and wiped on the next `getInstance()` call. Also resets per-document expansion counters.
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.

@@ -191,3 +191,3 @@ ```js

### `getInstance()`
### `reset()`

@@ -209,5 +209,5 @@ Reset all per-document state and return `this`.

// In a builder factory:
getInstance() {
reset() {
const builder = new MyBuilder(this.config);
builder.entityParser = this.entityVP.getInstance();
builder.entityParser = this.entityVP.reset();
return builder;

@@ -225,3 +225,3 @@ }

Document 1 parse:
factory.getInstance() → evp.getInstance() [clears input, resets counters]
factory.reset() → evp.reset() [clears input, resets counters]
builder sees DOCTYPE → evp.addInputEntities({ version: '1.0' })

@@ -231,3 +231,3 @@ builder processes values → evp.parse('&brand; v&version;') → 'Acme v1.0'

Document 2 parse (no DOCTYPE):
factory.getInstance() → evp.getInstance() [clears &version;, resets counters]
factory.reset() → evp.reset() [clears &version;, resets counters]
no DOCTYPE → addInputEntities() not called

@@ -312,12 +312,10 @@ builder processes values → evp.parse('&brand; v&version;') → 'Acme v&version;'

## `EntitiesValueParser` — flex-xml-parser adapter
## Integration with — flex-xml-parser adapter
`EntitiesValueParser` wraps `EntityReplacer` and implements the `ValueParser` interface used by `@nodable/flexible-xml-parser`.
### Setup
```js
import { EntitiesValueParser, COMMON_HTML } from '@nodable/entities';
import EntityReplacer, { COMMON_HTML } from '@nodable/entities';
const evp = new EntitiesValueParser({
const evp = new EntityReplacer({
system: COMMON_HTML,

@@ -342,3 +340,3 @@ maxTotalExpansions: 500,

```js
new EntitiesValueParser({
new EntityReplacer({
// All EntityReplacer options...

@@ -355,32 +353,12 @@ default: true,

### `setExternalEntities(map)`
### `reset()` — called by builder factory
Replace the full persistent entity map. These entities survive across all documents.
```js
evp.setExternalEntities({ brand: 'Acme', copy: '©' });
```
### `addEntity(key, value)`
Append a single persistent external entity. Previously registered entities are preserved.
```js
evp.addEntity('copy', '©');
evp.addEntity('trade', '™');
evp.addEntity('year', '2024');
```
Throws if `key` contains `&` or `;`, or if `value` contains `&`.
### `getInstance()` — called by builder factory
Reset per-document state (input entities + counters) and return `this`. The builder factory calls this each time it creates a new builder instance.
```js
// In your CompactObjBuilderFactory.getInstance():
getInstance() {
// In your CompactObjBuilderFactory.reset():
reset() {
const builder = new CompactObjBuilder(this._config);
// Reset EVP for the new document:
builder.entityParser = this._entityVP.getInstance();
builder.entityParser = this._entityVP.reset();
return builder;

@@ -390,10 +368,2 @@ }

### `addInputEntities(entities)` — called automatically
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`.
### `parse(val, context?)`
Implements the `ValueParser` interface. `context` is accepted but ignored. Returns non-string input unchanged.
---

@@ -437,3 +407,3 @@

| Persistent vs. input entity separation | ❌ | ✅ |
| Per-document reset via `getInstance()` | ❌ | ✅ |
| Per-document reset via `reset()` | ❌ | ✅ |
| Expansion count limit | ❌ | ✅ |

@@ -454,7 +424,5 @@ | Expanded length limit | ❌ | ✅ |

import EntityReplacer, {
EntitiesValueParser,
COMMON_HTML,
EntityTable,
EntityReplacerOptions,
EntitiesValueParserOptions,
} from '@nodable/entities';

@@ -472,15 +440,4 @@

replacer.setExternalEntities({ brand: 'Acme' });
replacer.getInstance(); // reset for new document
replacer.reset(); // reset for new document
replacer.addInputEntities({ version: '1.0' }); // from DOCTYPE
// EntitiesValueParser
const evpOpts: EntitiesValueParserOptions = {
system: COMMON_HTML,
entities: { brand: 'Acme' },
};
const evp = new EntitiesValueParser(evpOpts);
evp.addEntity('copy', '©');
evp.getInstance(); // called by builder factory
evp.addInputEntities({ company: 'Nodable' }); // called by BaseOutputBuilder
const result: string = evp.parse('<©&brand;');
```

@@ -487,0 +444,0 @@

@@ -233,9 +233,7 @@ // ---------------------------------------------------------------------------

*
* @returns {EntityReplacer} `this`, after reset
*/
getInstance() {
reset() {
this._inputEntries = [];
this._totalExpansions = 0;
this._expandedLength = 0;
return this;
}

@@ -299,2 +297,11 @@

/**
*
* @param {string} val
* @returns
*/
parse(val) {
return this.replace(val);
}
// -------------------------------------------------------------------------

@@ -301,0 +308,0 @@ // Private helpers

@@ -215,5 +215,4 @@ // ---------------------------------------------------------------------------

*
* @returns `this` — for convenient chaining in factory code
*/
getInstance(): this;
reset(): this;

@@ -229,2 +228,7 @@ // -------------------------------------------------------------------------

replace(str: string): string;
/**
* wrapper on replace()
*/
parse(str: string): string;
}

@@ -236,17 +240,4 @@

/**
* Options accepted by `EntitiesValueParser` — a superset of `EntityReplacerOptions`.
*/
export interface EntitiesValueParserOptions extends EntityReplacerOptions {
/**
* Initial persistent external entity map loaded at construction time.
* Values must not contain `&` (to prevent recursive expansion).
* Equivalent to calling `setExternalEntities()` after construction.
*
* @example
* new EntitiesValueParser({ entities: { copy: '©', trade: '™' } })
*/
entities?: Record<string, string>;
}
/**

@@ -276,108 +267,2 @@ * Raw DOCTYPE entity map shape as produced by `DocTypeReader`.

/**
* `EntitiesValueParser` — value-parser adapter that wraps `EntityReplacer`
* for use with `@nodable/flexible-xml-parser`.
*
* ## Setup
*
* ```ts
* import { EntitiesValueParser, COMMON_HTML } from '@nodable/entities';
*
* const evp = new EntitiesValueParser({ system: COMMON_HTML });
*
* // Persistent entities — never wiped between documents:
* evp.setExternalEntities({ brand: 'Acme', product: 'Widget' });
*
* // Register with the builder factory:
* builder.registerValueParser('entity', evp);
*
* const parser = new XMLParser({ OutputBuilder: builder });
* parser.parse(xml);
* ```
*
* ## Lifecycle (called automatically by the builder / parser)
*
* | Caller | Method | When |
* |-----------------|----------------------|-------------------------------------------|
* | Builder factory | `getInstance()` | Before each `parse()` call |
* | Builder | `addInputEntities()` | After DOCTYPE is read (if present) |
* | Builder | `parse(val)` | For each text / attribute value |
*/
export class EntitiesValueParser {
constructor(options?: EntitiesValueParserOptions);
// -------------------------------------------------------------------------
// Persistent external entity registration
// -------------------------------------------------------------------------
/**
* Replace the full set of persistent external entities.
*
* These survive across all documents and are **not** cleared by
* `getInstance()`. Call this once after construction (or at any time to
* swap the entire persistent entity map).
*
* @throws if any value contains `&`
*/
setExternalEntities(map: Record<string, string>): void;
/**
* Append a single persistent external entity.
*
* Provide the bare name without `&` and `;` — e.g. `'copy'` for `&copy;`.
* Existing persistent entities are preserved.
*
* @throws if `key` contains `&` or `;`
* @throws if `value` is not a string or contains `&`
*/
addEntity(key: string, value: string): void;
// -------------------------------------------------------------------------
// Builder factory integration
// -------------------------------------------------------------------------
/**
* Reset per-document state and return `this`.
*
* Clears input/runtime entities (DOCTYPE) and resets expansion counters.
* Does **not** clear persistent external entities.
*
* The builder factory calls this when creating a new builder instance.
*
* @returns `this`
*/
getInstance(): this;
// -------------------------------------------------------------------------
// DOCTYPE integration — called automatically by BaseOutputBuilder
// -------------------------------------------------------------------------
/**
* Receive DOCTYPE entities for the current document.
*
* Called automatically by `BaseOutputBuilder`. Stores entities separately
* from persistent entities so they are wiped on the next `getInstance()`.
* Also resets per-document expansion counters.
*
* Accepts both plain string values and `{ regx, val }` / `{ regex, val }`
* objects as produced by `DocTypeReader`.
*/
addInputEntities(entities: DocTypeEntityMap): void;
// -------------------------------------------------------------------------
// ValueParser interface
// -------------------------------------------------------------------------
/**
* Replace entity references in `val`.
*
* Implements the `ValueParser` interface. The `context` argument is
* accepted but ignored — replacement is applied uniformly to all values.
*
* Returns non-string input unchanged.
*/
parse(val: string, context?: ValueParserContext): string;
parse(val: unknown, context?: ValueParserContext): unknown;
}
// ---------------------------------------------------------------------------

@@ -384,0 +269,0 @@ // Named entity group exports

@@ -20,3 +20,2 @@ /**

export { DEFAULT_XML_ENTITIES, AMP_ENTITY } from './EntityReplacer.js';
export { default as EntitiesValueParser } from './EntitiesValueParser.js';
export {

@@ -23,0 +22,0 @@ COMMON_HTML,

import EntityReplacer from './EntityReplacer.js';
/**
* EntitiesValueParser — value-parser adapter that wraps `EntityReplacer`.
*
* Register an instance under the key `'entity'` on a `@nodable/flexible-xml-parser`
* output builder factory to enable entity expansion for all parsed text values.
*
* ## Lifecycle
*
* 1. **Construction** — supply configuration and optional persistent entities.
* 2. **`setExternalEntities(map)`** — (re)set the full persistent entity map.
* Or use `addEntity(key, value)` to add one at a time.
* 3. **`getInstance()`** — builder factory calls this when creating a new builder
* instance. Resets input entities and per-document counters. Returns `this`.
* 4. **`addInputEntities(map)`** — builder calls this if the document has a
* DOCTYPE block. Stores entities for *this document only*.
* 5. **`parse(val)`** — called by the builder for each text value.
*
* ```js
* const evp = new EntitiesValueParser({ system: COMMON_HTML });
* evp.setExternalEntities({ brand: 'Acme' });
* builder.registerValueParser('entity', evp);
* ```
*
* -------------------------------------------------------------------------
* Constructor options (all optional)
* -------------------------------------------------------------------------
*
* `default` — `true` (default) | `false`/`null` | custom EntityTable
* `system` — `false` (default) | `true` for COMMON_HTML | EntityTable
* `amp` — `true` (default) | `false`/`null`
* `maxTotalExpansions` — max entity refs expanded per document (0 = unlimited)
* `maxExpandedLength` — max characters added by expansion per document (0 = unlimited)
* `applyLimitsTo` — which categories count toward limits (default: `'external'`)
* `postCheck` — `(resolved, original) => string` hook
* `entities` — initial persistent entity map, e.g. `{ copy: '©' }`
*/
export default class EntitiesValueParser {
constructor(options = {}) {
this._replacer = new EntityReplacer(options);
// Load any entities provided inline at construction time as persistent entities
if (options.entities && typeof options.entities === 'object') {
const init = {};
for (const [key, val] of Object.entries(options.entities)) {
this._validateEntityArgs(key, val);
init[key] = val;
}
this._replacer.setExternalEntities(init);
}
}
// -------------------------------------------------------------------------
// Persistent external entity registration
// -------------------------------------------------------------------------
/**
* Replace the full set of persistent external entities.
* These survive across documents and are never wiped by `getInstance()`.
*
* @param {Record<string, string>} map — e.g. `{ copy: '©', brand: 'Acme' }`
*/
setExternalEntities(map) {
for (const [key, val] of Object.entries(map)) {
this._validateEntityArgs(key, val);
}
this._replacer.setExternalEntities(map);
}
/**
* Add (or replace) a single persistent external entity.
* Existing persistent entities are preserved.
*
* @param {string} key — bare name without `&` / `;`, e.g. `'copy'`
* @param {string} value — replacement string, e.g. `'©'`
*/
addEntity(key, value) {
this._validateEntityArgs(key, value);
this._replacer.addExternalEntity(key, value);
}
// -------------------------------------------------------------------------
// Builder factory integration
// -------------------------------------------------------------------------
/**
* Reset per-document state (input entities + expansion counters) and return `this`.
*
* The builder factory calls this when creating a new builder instance so that
* DOCTYPE entities from a previous document are never carried over.
*
* @returns {EntitiesValueParser} `this`
*/
getInstance() {
this._replacer.getInstance();
return this;
}
// -------------------------------------------------------------------------
// DOCTYPE integration — called by BaseOutputBuilder
// -------------------------------------------------------------------------
/**
* Receive DOCTYPE entities from the output builder.
*
* These are stored separately from persistent entities and wiped on the next
* `getInstance()` call. Resets per-document expansion counters.
*
* @param {Record<string, string | { regx: RegExp, val: string | Function }>} entities
* Raw entity map from `DocTypeReader` — values may be plain strings or
* `{ regx, val }` objects (note: `regx`, not `regex`, matching the reader's output).
*/
addInputEntities(entities) {
this._replacer.addInputEntities(entities);
}
// -------------------------------------------------------------------------
// ValueParser interface
// -------------------------------------------------------------------------
/**
* Replace entity references in `val`.
*
* @param {string} val
* @param {object} [_context]
* @returns {string}
*/
parse(val, _context) {
if (typeof val !== 'string') return val;
return this._replacer.replace(val);
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
_validateEntityArgs(key, value) {
if (typeof key !== 'string' || key.includes('&') || key.includes(';')) {
throw new Error(
`[EntitiesValueParser] Entity key must not contain '&' or ';'. ` +
`Use 'copy' for '&copy;', got: ${JSON.stringify(key)}`
);
}
if (typeof value !== 'string' || value.includes('&')) {
throw new Error(
`[EntitiesValueParser] Entity value must be a plain string that does not ` +
`contain '&', got: ${JSON.stringify(value)}`
);
}
}
}