🚀. Socket Launch Week Day 3:Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions.Learn more
Sign In

@nodable/flexible-xml-parser

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nodable/flexible-xml-parser - npm Package Compare versions

Comparing version
1.0.0
to
1.0.1
+4
-4
lib/fxp.d.cts

@@ -13,3 +13,3 @@ /**

* ```ts
* import { xmlEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -73,3 +73,3 @@ * const parser = new XMLParser({

* @example
* import { xmlEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -136,3 +136,3 @@ * skip: {

* ```ts
* import { xmlEnclosures, quoteEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -183,3 +183,3 @@ * const parser = new XMLParser({

* @example
* import { xmlEnclosures, quoteEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -186,0 +186,0 @@ * stopNodes: [

{
"name": "@nodable/flexible-xml-parser",
"version": "1.0.0",
"version": "1.0.1",
"description": "Fastest XML parser in pure JS with fully customizable ouput",

@@ -49,3 +49,3 @@ "main": "./lib/fxp.cjs",

"@nodable/compact-builder": "^1.0.2",
"path-expression-matcher": "^1.4.0",
"path-expression-matcher": "^1.5.0",
"strnum": "^2.2.2"

@@ -52,0 +52,0 @@ },

+126
-207

@@ -1,29 +0,56 @@

# Flexible XML Parser
# @nodable/flexible-xml-parser
A flexible, high-performance XML parser for Node.js with pluggable output builders, a composable value parser chain, and multiple input modes.
A high-performance, flexible XML parser in pure javascript for Node.js and browsers with pluggable output builders, composable value parsers, and multiple input modes.
## Features
> From the creater of fast-xml-parser
- **Multiple input modes** — string, Buffer, Uint8Array, Node.js streams, and incremental feed/end API. More can be created easily.
- **Pluggable output builders** — swap `CompactObjBuilder` for `NodeTreeBuilder`, `OrderedKeyValueBuilder`, or your own subclass of `BaseOutputBuilder`
- **Composable value parser chain** — built-in parsers for entities, numbers, booleans, trim, and currency; custom parsers receive full context
- **Path-expression stop nodes** — capture raw content inside matched tags (e.g. `<script>`, `<style>`) without further XML parsing; configurable enclosure skipping for nested quotes and comments
- **Entity expansion control** — built-in XML entities, optional HTML entities, external/registered entities, DocType-declared entities; all with DoS-prevention limits
- **Auto-close for lenient HTML parsing** — configurable recovery from unclosed tags and mismatched close tags; collect parse errors without throwing
- **DoS protection** — configurable limits on nesting depth, attributes per tag, entity count, entity size, and total expansion length
- **Security** — prototype-pollution prevention; reserved names throw; dangerous names are sanitised by default
- **TypeScript definitions** — complete dual-mode types (`fxp.d.ts` for ESM, `fxp.d.cts` for CJS)
- **ES Modules + CommonJS** — `"type": "module"` source with a bundled CJS output
## Benefits over fast-xml-parser?
| Feature | fast-xml-parser | flexible-xml-parser |
|---|---|---|
| Output format | Fixed JS object | Pluggable (compact, sequential, node-tree, custom) |
| Value parsing | Inline options | Separate, composable pipeline per output builder |
| Value parsers for tags vs attrs | Single config | Independent chains |
| Input modes | String / Buffer | String, Buffer, Uint8Array, Stream, Feed/End |
| Stop node enclosures | Limited | Per-node `skipEnclosures` control |
| Exit | After complete processing | Allow partial parsing |
| Lenient HTML mode | No | `autoClose` with error collection |
| Custom output | No | Extend `BaseOutputBuilder` |
The core parser is intentionally minimal. Options like `transformTagName`, `alwaysArray`, `forceTextNode`, and value parser configuration live in the **output builder**, not in `XMLParser`. This keeps the parser lean and lets you mix builders without changing your parsing code.
### Performance
fast-xml-parser doesn't support streams, while flexible-xml-parser does. This makes flexible-xml-parser more memory efficient for large XML files.
Additionally, flexible-xml-parser is considerably faster than fast-xml-parser. Checkout [benchmarks](https://github.com/nodable/flexible-xml-parser) for more details.
## Package Ecosystem
`@nodable/flexible-xml-parser` is the core parser. Output builders are published separately so you only install what you need:
| Package | Description |
|---|---|
| `@nodable/flexible-xml-parser` | Core parser (this package) |
| `@nodable/base-output-builder` | Base class + value parsers (`ElementType`, entity parsers) |
| `@nodable/compact-builder` | Default JS-object output (like fast-xml-parser) |
| `@nodable/sequential-builder` | Ordered key-value array output |
| `@nodable/sequential-stream-builder` | Sequential builder with streaming output |
| `@nodable/node-tree-builder` | Uniform AST-style node tree |
## Installation
```bash
npm install flexible-xml-parser
npm install @nodable/flexible-xml-parser @nodable/compact-builder
```
Install additional builders only as needed.
## Quick Start
```javascript
import XMLParser from 'flexible-xml-parser';
import XMLParser from '@nodable/flexible-xml-parser';
import { CompactBuilderFactory } from '@nodable/compact-builder';
// Default output (uses CompactBuilder internally)
const parser = new XMLParser();

@@ -33,3 +60,3 @@ const result = parser.parse('<root><count>3</count><active>true</active></root>');

// Enable attributes
// With attributes
const parser2 = new XMLParser({ skip: { attributes: false } });

@@ -40,34 +67,17 @@ parser2.parse('<item id="1">hello</item>');

## Input modes
## Options
```javascript
// String or Buffer
parser.parse('<root/>');
parser.parse(Buffer.from('<root/>'));
All options are optional. Pass only what you need.
// Typed array
parser.parseBytesArr(new Uint8Array([...]));
// Node.js Readable stream — memory stays proportional to the largest token,
// not the total document size
const result = await parser.parseStream(fs.createReadStream('large.xml'));
// Incremental feed — useful for WebSocket / chunked HTTP
parser.feed('<root>');
parser.feed('<item>1</item>');
const result = parser.end();
```
## Options
```javascript
new XMLParser({
// What to exclude from output
// What to skip
skip: {
attributes: true, // set false to parse attributes (default: true)
nsPrefix: false, // strip ns:tag → tag (default: false)
declaration: false,
pi: false,
cdata: false,
comment: false,
attributes: true, // Skip all attributes
declaration: false, // Skip <?xml ...?> declaration
pi: false, // Skip <?...?> processing instructions
cdata: false, // Exclude CDATA from output entirely
comment: false, // Exclude comments from output entirely
nsPrefix: false, // Strip namespace prefixes (ns:tag → tag)
tags: [], // Tag paths to drop silently from output
},

@@ -78,3 +88,3 @@

text: '#text', // mixed-content text property
cdata: '', // '' = merge CDATA into text; '#cdata' = separate key
cdata: '', // '' = merge into text; '#cdata' = separate key
comment: '', // '' = omit; '#comment' = capture

@@ -85,204 +95,113 @@ },

attributes: {
prefix: '@_',
suffix: '',
groupBy: '', // group all attributes under this key; '' = inline
booleanType: false, // allow valueless attributes (treated as true)
valueParsers: ['entity', 'number', 'boolean'],
prefix: '@_',
suffix: '',
groupBy: '', // group all attributes under this key; '' = inline
booleanType: false, // allow valueless attributes (treated as true)
},
// Tag value options
// Tag options
tags: {
unpaired: [], // self-closing tags without / (e.g. ['br', 'img'])
stopNodes: [], // paths whose content is captured raw (see below)
valueParsers: ['entity', 'number', 'boolean'],
unpaired: [], // self-closing tags without / (e.g. ['br', 'img'])
stopNodes: [], // paths whose content is captured raw (see docs/04-stop-nodes.md)
},
numberParseOptions: { hex: true, leadingZeros: true, eNotation: true },
// Entity sources and security limits
entityParseOptions: {
default: true, // built-in XML entities (lt, gt, amp, …)
html: false, // HTML named entities (&nbsp;, &copy;, …)
external: true, // entities added via parser.addEntity()
docType: false, // entities declared in DOCTYPE internal subset
maxEntityCount: 100,
maxEntitySize: 10000,
maxTotalExpansions: 1000,
maxExpandedLength: 100000,
},
// DoS prevention
limits: {
maxNestedTags: null, // max tag nesting depth
maxAttributesPerTag: null, // max attributes on a single tag
maxNestedTags: null,
maxAttributesPerTag: null,
},
// Lenient HTML-mode recovery
autoClose: null, // null = strict; 'html' = recover from unclosed/mismatched tags
// DOCTYPE entity expansion
doctypeOptions: {
enabled: false,
maxEntityCount: 100,
maxEntitySize: 10000,
},
// Pluggable output builder (default: CompactObjBuilder)
OutputBuilder: null,
});
```
// Security
strictReservedNames: false,
onDangerousProperty: defaultOnDangerousProperty,
## Value parsers
// Stop parsing early based on a condition
exitIf: null,
Built-in chain names: `'entity'`, `'number'`, `'boolean'`, `'trim'`, `'currency'`.
// Buffer settings for feed/stream modes
feedable: {
maxBufferSize: 10 * 1024 * 1024,
autoFlush: true,
flushThreshold: 1024,
},
```javascript
// Disable entity expansion
new XMLParser({ tags: { valueParsers: ['number', 'boolean'] } });
// Lenient HTML-mode recovery
autoClose: null, // null = strict; 'html' = recover from unclosed/mismatched tags
// HTML entities + trim whitespace
new XMLParser({
tags: { valueParsers: ['entity', 'trim', 'number', 'boolean'] },
entityParseOptions: { html: true },
// Pluggable output builder
OutputBuilder: null, // default: CompactBuilder
});
// All values as raw strings
new XMLParser({ tags: { valueParsers: [] }, attributes: { valueParsers: [] } });
```
Custom parsers receive `(val, context)` where context carries `{ elementName, elementValue, elementType, matcher, isLeafNode }`:
## Value Parsers
```javascript
class PriceParser {
parse(val, context) {
return context.elementName === 'price' ? parseFloat(val) : val;
}
}
Value parsers are configured on the **output builder**, not on `XMLParser`. This lets you set independent pipelines for tag text and attribute values.
new XMLParser({
tags: { valueParsers: ['entity', new PriceParser(), 'boolean'] },
});
```
Built-in parsers: `'entity'`, `'number'`, `'boolean'`, `'trim'`, `'currency'`.
Register a reusable custom parser by name via `CompactObjBuilder`:
```javascript
import { CompactObjBuilder } from 'flexible-xml-parser';
import { CompactBuilderFactory } from '@nodable/compact-builder';
const builder = new CompactObjBuilder();
builder.registerValueParser('price', new PriceParser());
new XMLParser({
tags: { valueParsers: ['entity', 'price', 'boolean'] },
OutputBuilder: builder,
const builder = new CompactBuilderFactory({
tags: { valueParsers: ['entity', 'boolean', 'number'] },
attributes: { valueParsers: ['entity', 'number', 'boolean'] },
});
```
## Stop nodes
Stop nodes capture raw content without further XML parsing — useful for `<script>`, `<style>`, or embedded HTML fragments.
```javascript
import { xmlEnclosures, quoteEnclosures } from 'flexible-xml-parser';
new XMLParser({
tags: {
stopNodes: [
'..script', // plain — first </script> ends collection
{ expression: 'body..pre', skipEnclosures: [...xmlEnclosures] },
{ expression: 'head..style', skipEnclosures: [...xmlEnclosures, ...quoteEnclosures] },
],
},
onStopNode(tagDetail, rawContent, matcher) {
console.log(tagDetail.name, rawContent);
},
});
const parser = new XMLParser({ OutputBuilder: builder });
```
`xmlEnclosures` covers XML comments and CDATA; `quoteEnclosures` covers single-quote, double-quote, and template literals.
See [`docs/03-value-parsers.md`](./docs/03-value-parsers.md) for the full pipeline reference and custom parser guide.
## Pluggable output builders
## Input Modes
```javascript
import XMLParser, { CompactObjBuilder, BaseOutputBuilder, ElementType } from 'flexible-xml-parser';
// String or Buffer
parser.parse('<root/>');
parser.parse(Buffer.from('<root/>'));
// CompactObjBuilder — default JS object output with extra options
const builder = new CompactObjBuilder({
alwaysArray: ['item'], // tag names or path expressions always wrapped in []
forceArray: (matcher) => ..., // function-based array forcing
forceTextNode: false, // always emit nameFor.text even for text-only tags
textJoint: '', // join string when text spans multiple text nodes
});
// Typed array
parser.parseBytesArr(new Uint8Array([...]));
new XMLParser({ OutputBuilder: builder });
// Node.js Readable stream
const result = await parser.parseStream(fs.createReadStream('large.xml'));
// Custom builder by extending BaseOutputBuilder
class MyBuilder extends BaseOutputBuilder {
addElement(tag, matcher) { /* … */ }
closeElement(matcher) { /* … */ }
addValue(text, matcher) { /* … */ }
getOutput() { return this.result; }
}
// Incremental feed (WebSocket, chunked HTTP, etc.)
parser.feed('<root>');
parser.feed('<item>1</item>');
const result = parser.end();
```
## Auto-close (lenient HTML parsing)
## Possible Usage
```javascript
// 'html' preset: recover from unclosed tags and mismatched close tags
const parser = new XMLParser({ autoClose: 'html' });
const result = parser.parse('<div><p>text<br></div>');
- Parse XML config files, SOAP responses, RSS/Atom feeds
- Stream-parse large XML files with bounded memory
- Build custom AST-style output with `NodeTreeBuilder`
- Lenient HTML-fragment parsing with `autoClose`
- Stop-node capture for `<script>`, `<style>`, embedded HTML
- Extend `BaseOutputBuilder` to write parsed data directly to a database
const errors = parser.getParseErrors();
// [{ type: 'unclosed-eof', tag: 'p', line: 1, col: … }, …]
```
## Documentation
Fine-grained control:
| File | Topic |
|---|---|
| [`docs/01-getting-started.md`](./docs/01-getting-started.md) | Installation, quick start, common patterns |
| [`docs/02-options.md`](./docs/02-options.md) | Full options reference |
| [`docs/03-value-parsers.md`](./docs/03-value-parsers.md) | Value parser pipeline, built-ins, custom parsers |
| [`docs/04-stop-nodes.md`](./docs/04-stop-nodes.md) | Stop nodes and skip tags |
| [`docs/05-output-builders.md`](./docs/05-output-builders.md) | Built-in and custom output builders |
| [`docs/06-streaming.md`](./docs/06-streaming.md) | Stream, feed/end, and memory characteristics |
| [`docs/07-auto-close.md`](./docs/07-auto-close.md) | Lenient HTML parsing and error collection |
| [`docs/08-security.md`](./docs/08-security.md) | Security, DoS limits, prototype pollution |
| [`docs/09-path-expressions.md`](./docs/09-path-expressions.md) | Path expression syntax for stop nodes, skip, exitIf |
| [`docs/10-typescript.md`](./docs/10-typescript.md) | TypeScript usage and type definitions |
```javascript
new XMLParser({
autoClose: {
onEof: 'closeAll', // 'throw' | 'closeAll'
onMismatch: 'recover', // 'throw' | 'recover' | 'discard'
collectErrors: true,
},
});
```
## Error handling
```javascript
import XMLParser, { ParseError, ErrorCode } from 'flexible-xml-parser';
try {
parser.parse(xml);
} catch (e) {
if (e instanceof ParseError) {
console.error(e.code, e.line, e.col, e.message);
// e.g. 'MISMATCHED_CLOSE_TAG' 4 12 'Expected </div>, got </span>'
} else {
throw e;
}
}
```
All error codes are available on the `ErrorCode` constant for exhaustive matching without string literals.
## Custom entities
```javascript
parser.addEntity('copy', '©');
parser.addEntity('trade', '™');
// requires entityParseOptions.external: true (default)
```
## TypeScript
```typescript
import XMLParser, { X2jOptions, CompactObjBuilder, BaseOutputBuilder, ElementType } from 'flexible-xml-parser';
const options: X2jOptions = {
skip: { attributes: false, nsPrefix: true },
nameFor: { cdata: '#cdata' },
tags: { valueParsers: ['entity', 'trim', 'number', 'boolean'] },
limits: { maxNestedTags: 100 },
};
const parser = new XMLParser(options);
```
## License
MIT — [Amit Gupta](https://nodable.com)
MIT — [Amit Gupta](https://solothought.com)

@@ -12,3 +12,3 @@ import { BaseOutputBuilderFactory } from "@nodable/base-output-builder"

* ```ts
* import { xmlEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -72,3 +72,3 @@ * const parser = new XMLParser({

* @example
* import { xmlEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -135,3 +135,3 @@ * skip: {

* ```ts
* import { xmlEnclosures, quoteEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -182,3 +182,3 @@ * const parser = new XMLParser({

* @example
* import { xmlEnclosures, quoteEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -185,0 +185,0 @@ * stopNodes: [

/**
* ParseError — structured error class for flex-xml-parser.
* ParseError — structured error class for flexible-xml-parser.
*

@@ -27,4 +27,4 @@ * All errors thrown by the parser are instances of ParseError so callers can

this.line = position.line ?? undefined;
this.col = position.col ?? undefined;
this.line = position.line ?? undefined;
this.col = position.col ?? undefined;
this.index = position.index ?? undefined;

@@ -53,41 +53,41 @@ }

// Input type errors
INVALID_INPUT: 'INVALID_INPUT',
INVALID_STREAM: 'INVALID_STREAM',
INVALID_INPUT: 'INVALID_INPUT',
INVALID_STREAM: 'INVALID_STREAM',
// Streaming / feed API
ALREADY_STREAMING: 'ALREADY_STREAMING',
NOT_STREAMING: 'NOT_STREAMING',
DATA_MUST_BE_STRING: 'DATA_MUST_BE_STRING',
ALREADY_STREAMING: 'ALREADY_STREAMING',
NOT_STREAMING: 'NOT_STREAMING',
DATA_MUST_BE_STRING: 'DATA_MUST_BE_STRING',
// Tag structure
UNEXPECTED_END: 'UNEXPECTED_END',
UNEXPECTED_CLOSE_TAG: 'UNEXPECTED_CLOSE_TAG',
MISMATCHED_CLOSE_TAG: 'MISMATCHED_CLOSE_TAG',
UNEXPECTED_TRAILING_DATA: 'UNEXPECTED_TRAILING_DATA',
INVALID_TAG: 'INVALID_TAG',
UNCLOSED_QUOTE: 'UNCLOSED_QUOTE',
UNEXPECTED_END: 'UNEXPECTED_END',
UNEXPECTED_CLOSE_TAG: 'UNEXPECTED_CLOSE_TAG',
MISMATCHED_CLOSE_TAG: 'MISMATCHED_CLOSE_TAG',
UNEXPECTED_TRAILING_DATA: 'UNEXPECTED_TRAILING_DATA',
INVALID_TAG: 'INVALID_TAG',
UNCLOSED_QUOTE: 'UNCLOSED_QUOTE',
// Namespace
MULTIPLE_NAMESPACES: 'MULTIPLE_NAMESPACES',
MULTIPLE_NAMESPACES: 'MULTIPLE_NAMESPACES',
// Security
SECURITY_PROTOTYPE_POLLUTION: 'SECURITY_PROTOTYPE_POLLUTION',
SECURITY_RESERVED_OPTION: 'SECURITY_RESERVED_OPTION',
SECURITY_RESTRICTED_NAME: 'SECURITY_RESTRICTED_NAME',
SECURITY_RESERVED_OPTION: 'SECURITY_RESERVED_OPTION',
SECURITY_RESTRICTED_NAME: 'SECURITY_RESTRICTED_NAME',
// Limits (DoS prevention)
LIMIT_MAX_NESTED_TAGS: 'LIMIT_MAX_NESTED_TAGS',
LIMIT_MAX_ATTRIBUTES: 'LIMIT_MAX_ATTRIBUTES',
LIMIT_MAX_NESTED_TAGS: 'LIMIT_MAX_NESTED_TAGS',
LIMIT_MAX_ATTRIBUTES: 'LIMIT_MAX_ATTRIBUTES',
// Entity limits
ENTITY_MAX_COUNT: 'ENTITY_MAX_COUNT',
ENTITY_MAX_SIZE: 'ENTITY_MAX_SIZE',
ENTITY_MAX_EXPANSIONS: 'ENTITY_MAX_EXPANSIONS',
ENTITY_MAX_COUNT: 'ENTITY_MAX_COUNT',
ENTITY_MAX_SIZE: 'ENTITY_MAX_SIZE',
ENTITY_MAX_EXPANSIONS: 'ENTITY_MAX_EXPANSIONS',
ENTITY_MAX_EXPANDED_LENGTH: 'ENTITY_MAX_EXPANDED_LENGTH',
// Entity registration
ENTITY_INVALID_KEY: 'ENTITY_INVALID_KEY',
ENTITY_INVALID_VALUE: 'ENTITY_INVALID_VALUE',
ENTITY_INVALID_KEY: 'ENTITY_INVALID_KEY',
ENTITY_INVALID_VALUE: 'ENTITY_INVALID_VALUE',
});
export default ParseError;

@@ -8,3 +8,3 @@ import { ParseError, ErrorCode } from './ParseError.js';

*
* import { xmlEnclosures, quoteEnclosures } from 'flex-xml-parser';
* import { xmlEnclosures, quoteEnclosures } from '@nodable/flexible-xml-parser';
*

@@ -11,0 +11,0 @@ * stopNodes: [