Skip to content

stacksjs/ts-xml

Social Card of this repo

npm version GitHub Actions Commitizen friendly

ts-xml

A fast, dependency-free XML parser, builder, and validator for TypeScript and Bun. Character-by-character parsing with charCodeAt comparisons for maximum performance.

Features

  • Zero Dependencies - only Bun as a runtime
  • XMLParser - parse XML strings to JavaScript objects
  • XMLBuilder - build XML strings from JavaScript objects
  • XMLValidator - validate XML structure with detailed error reporting
  • Entity Handling - XML, HTML, numeric, and hex entity decoding/encoding
  • Namespace Support - optional namespace prefix removal
  • Preserve Order - array-based output to maintain element ordering
  • Stop Nodes - skip parsing of specific tag contents
  • Unpaired Tags - support for HTML-style void elements (e.g., <br>, <hr>)
  • CDATA & Comments - optionally capture CDATA sections and comments as properties
  • Processing Instructions - capture <?xml?> declarations and custom PIs
  • Value Parsing - automatic number, boolean, hex, and scientific notation parsing
  • Custom Processors - tag value, attribute value, and tag name transformation callbacks
  • Fully Typed - complete TypeScript type definitions

Get Started

Installation

bun install ts-xml

Parsing XML

import { XMLParser } from 'ts-xml'

const parser = new XMLParser()
const result = parser.parse('<root><item>Hello</item></root>')
// { root: { item: 'Hello' } }

Parsing with Attributes

import { XMLParser } from 'ts-xml'

const parser = new XMLParser({ ignoreAttributes: false })
const result = parser.parse('<book isbn="978-0-123"><title>XML Guide</title></book>')
// { book: { '@_isbn': '978-0-123', title: 'XML Guide' } }

Building XML

import { XMLBuilder } from 'ts-xml'

const builder = new XMLBuilder({ format: true, indentBy: '  ' })
const xml = builder.build({
  root: {
    item: ['one', 'two', 'three'],
  },
})

Validating XML

import { XMLValidator } from 'ts-xml'

const result = XMLValidator('<root><child/></root>')
if (result === true) {
  console.log('Valid XML')
}
else {
  console.log(`Error: ${result.err.msg} at line ${result.err.line}`)
}

Preserve Element Order

import { XMLParser, XMLBuilder } from 'ts-xml'

const parser = new XMLParser({ preserveOrder: true })
const ordered = parser.parse('<root><a>1</a><b>2</b><a>3</a></root>')
// Maintains original element order as arrays

const builder = new XMLBuilder({ preserveOrder: true })
const xml = builder.build(ordered) // Round-trips correctly

Configuration

Parser Options

Option Type Default Description
attributeNamePrefix string "@_" Prefix for attribute names
attributesGroupName string | false false Group attributes under this key
textNodeName string "#text" Property name for text content
ignoreAttributes boolean true Skip attribute parsing
removeNSPrefix boolean false Strip namespace prefixes
allowBooleanAttributes boolean false Allow attributes without values
alwaysCreateTextNode boolean false Always create text node property
trimValues boolean true Trim whitespace from values
parseTagValue boolean true Parse numbers/booleans from text
parseAttributeValue boolean false Parse numbers/booleans from attributes
processEntities boolean true Decode XML entities
htmlEntities boolean false Decode HTML entities
commentPropName string | false false Property name for comments
cdataPropName string | false false Property name for CDATA sections
piPropName string | false false Property name for processing instructions
preserveOrder boolean false Maintain element ordering
stopNodes string[] [] Tags whose content is not parsed
unpairedTags string[] [] Tags that don't need closing
numberParseOptions NumberParseOptions { hex: true, leadingZeros: true, scientific: true } Number parsing behavior

Builder Options

Option Type Default Description
attributeNamePrefix string "@_" Prefix for attribute names
textNodeName string "#text" Property name for text content
ignoreAttributes boolean false Skip attributes when building
format boolean false Pretty-print output
indentBy string " " Indentation string
suppressEmptyNode boolean false Render empty nodes as self-closing
suppressBooleanAttributes boolean true Render boolean attributes without ="true"
processEntities boolean true Encode entities in output
preserveOrder boolean false Build from ordered format

Benchmarks

Benchmarked on Apple M3 Pro using mitata, comparing against fast-xml-parser, xml2js, and sax.

Parsing

Benchmark ts-xml fast-xml-parser xml2js sax
Simple XML 344 ns 1.64 µs (4.8x slower) 2.03 µs 885 ns
Medium (3 products + attrs) 6.67 µs 27.4 µs (4.1x slower) 18.0 µs 12.3 µs
Large (100 products) 321 µs 1.16 ms (3.6x slower) 2.04 ms 1.88 ms
Very Large (1000 products) 3.02 ms 13.0 ms (4.3x slower) 9.83 ms 4.90 ms
CDATA 831 ns 2.89 µs (3.5x slower) 5.62 µs 5.36 µs
Deep nesting (50 levels) 9.33 µs 59.1 µs (6.3x slower) 43.0 µs 27.5 µs
RSS Feed 8.33 µs 40.4 µs (4.9x slower) 53.9 µs 28.7 µs
Entities 2.28 µs 6.05 µs (2.7x slower) 9.42 µs 6.42 µs
Namespaces 3.42 µs 14.4 µs (4.2x slower) 11.6 µs 7.91 µs

Building

Benchmark ts-xml fast-xml-parser xml2js
Small object 1.61 µs 3.83 µs (2.4x slower) 8.60 µs
Large (100 products) 42.6 µs 118 µs (2.8x slower) 197 µs
Formatted output 1.67 µs 4.07 µs (2.4x slower) 8.05 µs

Validation

Benchmark ts-xml fast-xml-parser
Valid XML 3.30 µs 7.79 µs (2.4x slower)
Large valid (1000 products) 1.39 ms 3.37 ms (2.4x slower)
Invalid XML (early exit) 260 ns 591 ns (2.3x slower)

Round-trip

Benchmark ts-xml fast-xml-parser
Parse + Build (medium) 9.15 µs 34.7 µs (3.8x slower)

Run benchmarks yourself: bun run bench

Testing

bun test

724 tests across 6 test files covering parser, builder, validator, entities, and edge cases.

Changelog

Please see our releases page for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Community

For help, discussion about best practices, or any other conversation that would benefit from being searchable:

Discussions on GitHub

For casual chit-chat with others using this package:

Join the Stacks Discord Server

Postcardware

"Software that is free, but hopes for a postcard." We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.

Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎

Sponsors

We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.

License

The MIT License (MIT). Please see LICENSE for more information.

Made with 💙

About

A performant, dependency-free XML parser, builder, and validator.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors