
Product
Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.
@nodable/entities
Advanced tools
@nodable/entitiesStandalone, zero-dependency XML/HTML entity replacement with:
getInstance() — clean per-document reset without cloningpostCheck hook — inspect or sanitize the fully resolved stringnpm install @nodable/entities
import EntityReplacer from '@nodable/entities';
const replacer = new EntityReplacer({ default: true });
replacer.replace('5 < 10 && x > 0');
// → '5 < 10 && x > 0'
With named entity groups:
import EntityReplacer, { COMMON_HTML, CURRENCY_ENTITIES } from '@nodable/entities';
const replacer = new EntityReplacer({
default: true,
system: { ...COMMON_HTML, ...CURRENCY_ENTITIES },
});
replacer.replace('© 2024 — Price: £9.99');
// → '© 2024 — Price: £9.99'
Entities are processed in this fixed order — not configurable:
persistent input/runtime → external → system → default → amp
persistent external — Caller-supplied configuration entitiesEntities set at configuration time that survive across all documents. Never wiped by getInstance(). Set via setExternalEntities() or addExternalEntity() / addEntity().
const replacer = new EntityReplacer({ default: true });
replacer.setExternalEntities({ brand: 'Acme Corp', product: 'Widget Pro' });
replacer.replace('&brand; makes &product;');
// → 'Acme Corp makes Widget Pro'
input / runtime — Per-document DOCTYPE entitiesEntities 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.
Set via addInputEntities(). Never call this manually — BaseOutputBuilder calls it automatically.
system — Named entity groupsOpt-in. Trusted programmer-supplied groups. Compose freely:
import {
COMMON_HTML,
CURRENCY_ENTITIES,
MATH_ENTITIES,
ARROW_ENTITIES,
NUMERIC_ENTITIES,
} from '@nodable/entities';
const replacer = new EntityReplacer({
system: { ...COMMON_HTML, ...MATH_ENTITIES },
});
| Group | Contents |
|---|---|
COMMON_HTML | © ® ™ — – … « » ‘ ’ “ ” • ¶ § ° ½ ¼ ¾ |
CURRENCY_ENTITIES | ¢ £ ¥ € &inr; ¤ ƒ |
MATH_ENTITIES | × ÷ ± − ² ³ ‰ ∞ ∑ ∏ √ ≠ ≤ ≥ |
ARROW_ENTITIES | ← ↑ → ↓ ↔ ⇐ ⇑ ⇒ ⇓ ⇔ |
NUMERIC_ENTITIES | &#NNN; decimal and &#xHH; hex refs — any valid Unicode code point |
default — Built-in XML entitiesAlways on unless explicitly disabled.
| Entity | Output |
|---|---|
< | < |
> | > |
" | " |
' | ' |
amp — Final pass& → &
Processed after all other categories to prevent double-expansion:
&lt; → < ✓ (not <)&amp; → & ✓ (not &)const replacer = new EntityReplacer({
// Category toggles
default: true, // true (default) | false | custom EntityTable object
amp: true, // true (default) | false | null
system: false, // false (default) | true for COMMON_HTML | EntityTable object
// Security limits — 0 = unlimited
maxTotalExpansions: 0,
maxExpandedLength: 0,
// Which categories count against the limits
applyLimitsTo: 'external', // 'external' (default) | 'all' | ['external', 'system'] | ...
// Post-processing hook — fires once on the fully resolved string
postCheck: resolved => resolved, // (resolved: string, original: string) => string
});
replace(str)Replace all entity references in str. Returns str unchanged (same reference) if no & is present — fast path.
replacer.replace('Tom & Jerry <cartoons>');
// → 'Tom & Jerry <cartoons>'
setExternalEntities(map)Replace the full set of persistent external entities. These survive across all documents and are not cleared by getInstance().
replacer.setExternalEntities({ brand: 'Acme', year: '2025' });
Calling this a second time replaces the entire persistent map. Values containing & are silently skipped.
addExternalEntity(key, value)Append a single persistent external entity without disturbing the rest.
replacer.addExternalEntity('brand', 'Acme');
replacer.addExternalEntity('year', '2025');
addInputEntities(map)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.
// Called automatically by BaseOutputBuilder — no manual wiring needed.
replacer.addInputEntities(doctypeEntityMap);
Values containing & are silently skipped. Accepts pre-built { regex, val } or { regx, val } objects as produced by DocTypeReader.
getInstance()Reset all per-document state and return this.
Clears:
_totalExpansions counter_expandedLength counterPreserves:
setExternalEntities() / addExternalEntity()The builder factory calls this when creating a new builder instance, ensuring each document starts clean whether or not it has a DOCTYPE.
// In a builder factory:
getInstance() {
const builder = new MyBuilder(this.config);
builder.entityParser = this.entityVP.getInstance();
return builder;
}
A key design goal is that entities from one document never bleed into the next. Here's how the two categories work together:
Document 1 parse:
factory.getInstance() → evp.getInstance() [clears input, resets counters]
builder sees DOCTYPE → evp.addInputEntities({ version: '1.0' })
builder processes values → evp.parse('&brand; v&version;') → 'Acme v1.0'
Document 2 parse (no DOCTYPE):
factory.getInstance() → evp.getInstance() [clears &version;, resets counters]
no DOCTYPE → addInputEntities() not called
builder processes values → evp.parse('&brand; v&version;') → 'Acme v&version;'
↑ persistent &brand; works
↑ &version; is gone — correct
Caps the number of entity references that may be expanded per document.
const replacer = new EntityReplacer({ maxTotalExpansions: 1000 });
Throws Error if exceeded:
[EntityReplacer] Entity expansion count limit exceeded: 1001 > 1000
Caps the total number of characters added by entity expansion per document.
const replacer = new EntityReplacer({ maxExpandedLength: 65536 });
Throws Error if exceeded:
[EntityReplacer] Expanded content length limit exceeded: 65537 > 65536
applyLimitsToControls which categories count against the limits.
// Default — only untrusted injected entities (safest)
applyLimitsTo: 'external'
// All categories
applyLimitsTo: 'all'
// Specific combination
applyLimitsTo: ['external', 'system']
applyLimitsTo: ['external', 'default']
postCheck HookFires once on the fully resolved string, after all categories have been processed. Not called if the string is unchanged (no & present or no matches found).
// Signature
postCheck: (resolved: string, original: string) => string
resolved — string after all entity replacementsoriginal — the original input string before any replacementreturn originalresolvedExamples:
// Reject if expansion produces any HTML tags
postCheck: (resolved, original) =>
/<[a-z]/i.test(resolved) ? original : resolved
// Strip all tag-like content from the result
postCheck: (resolved) =>
resolved.replace(/<[^>]*>/g, '')
EntitiesValueParser — flex-xml-parser adapterEntitiesValueParser wraps EntityReplacer and implements the ValueParser interface used by @nodable/flexible-xml-parser.
import { EntitiesValueParser, COMMON_HTML } from '@nodable/entities';
const evp = new EntitiesValueParser({
system: COMMON_HTML,
maxTotalExpansions: 500,
});
// Persistent entities — survive across all documents:
evp.setExternalEntities({ brand: 'Acme', product: 'Widget' });
// Register with the builder factory:
myBuilder.registerValueParser('entity', evp);
const parser = new XMLParser({ OutputBuilder: myBuilder });
parser.parse(xml);
All EntityReplacerOptions are accepted, plus one extra:
new EntitiesValueParser({
// All EntityReplacer options...
default: true,
system: COMMON_HTML,
maxTotalExpansions: 1000,
postCheck: (resolved, original) => resolved,
// Extra: initial persistent entity map (same as calling setExternalEntities after construction)
entities: { copy: '©', trade: '™', brand: 'Acme Corp' },
})
setExternalEntities(map)Replace the full persistent entity map. These entities survive across all documents.
evp.setExternalEntities({ brand: 'Acme', copy: '©' });
addEntity(key, value)Append a single persistent external entity. Previously registered entities are preserved.
evp.addEntity('copy', '©');
evp.addEntity('trade', '™');
evp.addEntity('year', '2024');
Throws if key contains & or ;, or if value contains &.
getInstance() — called by builder factoryReset per-document state (input entities + counters) and return this. The builder factory calls this each time it creates a new builder instance.
// In your CompactObjBuilderFactory.getInstance():
getInstance() {
const builder = new CompactObjBuilder(this._config);
// Reset EVP for the new document:
builder.entityParser = this._entityVP.getInstance();
return builder;
}
addInputEntities(entities) — called automaticallyReceives 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.
Pass any plain object as default or system to replace the built-in set:
const myEntities = {
br: { regex: /&br;/g, val: '\n' },
tab: { regex: /&tab;/g, val: '\t' },
};
const replacer = new EntityReplacer({ default: myEntities });
replacer.replace('line1&br;line2&tab;indented');
// → 'line1\nline2\tindented'
Extend the built-in tables via spreading:
import { DEFAULT_XML_ENTITIES } from '@nodable/entities';
const replacer = new EntityReplacer({
default: { ...DEFAULT_XML_ENTITIES, br: { regex: /&br;/g, val: '\n' } },
});
entities npm package| Feature | entities pkg | @nodable/entities |
|---|---|---|
| XML entity decoding | ✅ | ✅ |
| HTML entity decoding | ✅ full ~2000 | ✅ grouped, composable |
| Numeric refs with leading zeros | ✅ | ✅ |
| DOCTYPE / external entity injection | ❌ | ✅ |
| Persistent vs. input entity separation | ❌ | ✅ |
Per-document reset via getInstance() | ❌ | ✅ |
| Expansion count limit | ❌ | ✅ |
| Expanded length limit | ❌ | ✅ |
applyLimitsTo granularity | ❌ | ✅ |
postCheck hook | ❌ | ✅ |
| Encoding / HTML escaping | ✅ | ❌ out of scope |
| Zero dependencies | ✅ | ✅ |
Full TypeScript declarations are included via index.d.ts. No @types/ package needed.
import EntityReplacer, {
EntitiesValueParser,
COMMON_HTML,
EntityTable,
EntityReplacerOptions,
EntitiesValueParserOptions,
} from '@nodable/entities';
// EntityReplacer
const opts: EntityReplacerOptions = {
default: true,
system: COMMON_HTML,
maxTotalExpansions: 500,
postCheck: (resolved, original) =>
/<script/i.test(resolved) ? original : resolved,
};
const replacer = new EntityReplacer(opts);
replacer.setExternalEntities({ brand: 'Acme' });
replacer.getInstance(); // 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;');
This library silently skip numeric entities which are out range. For example � is skipped.
MIT
FAQs
Entity parser for XML, HTML, External entites with security and NCR control
The npm package @nodable/entities receives a total of 25,127,309 weekly downloads. As such, @nodable/entities popularity was classified as popular.
We found that @nodable/entities demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.

Research
More than 140 Mastra npm packages were compromised in a supply chain attack that used a typosquatted dependency to deliver a cross-platform infostealer during installation.

Research
/Security News
A new npm package tests AI malware scanners with prompt injection, safety-triggering comments, context flooding, and obfuscated JavaScript.