sax

A tiny, dependency-free streaming SAX-style parser for XML and HTML in JavaScript.

Library
npm
v1.6.1
1,158stars
BlueOak-1.0.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
59/100Fair
Development Activity48
Maintenance16
Community84
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture74
Code Quality78
Innovation68
Learning Curve70

sax is an evented, streaming XML parser for Node.js and the browser. Rather than building a DOM tree in memory, it fires callbacks (onopentag, ontext, onattribute, onclosetag, and more) as it reads through a document, so arbitrarily large or malformed feeds can be processed with a small, constant memory footprint. It ships as a single dependency-free file that works equally well in strict, spec-compliant XML mode or a forgiving loose mode for real-world HTML and slightly-broken RSS/Atom feeds.

Because it exposes a Node.js Transform-stream wrapper (sax.createStream), it composes naturally with fs.createReadStream().pipe() pipelines, making it a common low-level building block underneath higher-level XML tooling (feed parsers, SVG processors, SOAP clients) rather than something most application code touches directly.

What You Get

  • An event-driven parser (onopentag, onclosetag, ontext, onattribute, oncdata, oncomment, ondoctype, onprocessinginstruction, onerror, and more) covering the full XML production grammar
  • A strict mode for spec-compliant XML and a loose/HTML mode that tolerates unclosed tags, unquoted attributes, and other real-world malformations
  • A Node.js stream wrapper (sax.createStream) that works directly with .pipe() for processing files or HTTP responses incrementally
  • Optional XML namespace (xmlns) support that tracks namespace bindings through a prototype chain per tag
  • Configurable safety limits (maxEntityCount, maxEntityDepth, MAX_BUFFER_LENGTH) that guard against entity-expansion (billion-laughs) attacks and unbounded buffer growth
  • Zero runtime dependencies and a single-file implementation (lib/sax.js) that runs unmodified in Node.js or a browser

Common Use Cases

  • Parsing large RSS/Atom feeds or XML exports without loading the entire document into memory
  • Building a custom SVG or SOAP client on top of low-level open/close/text events
  • Streaming-transforming XML/HTML through a Unix-pipe-style fs.createReadStream().pipe(saxStream).pipe(...) chain
  • Extracting a handful of fields from very large or slightly malformed XML documents where a full DOM parser would be overkill or too strict

Under The Hood

Architecture — sax is a single self-contained closure (lib/sax.js, ~1,900 lines) wrapped in a UMD-style IIFE ((function (sax) {...})(typeof exports === 'undefined' ? (this.sax = {}) : exports)) that attaches itself to module.exports in Node or window.sax in browsers. At its core is a hand-written character-by-character state machine (the S enum, e.g. S.BEGIN, S.TEXT, S.OPEN_TAG, S.ATTRIB_VALUE) driven by SAXParser.write(); each character transitions parser.state and appends into one of twelve reusable string buffers (comment, tagName, attribName, cdata, etc., listed in the buffers array) that are flushed and cleared as tokens complete, which is what keeps memory bounded regardless of input size. SAXStream (used via sax.createStream()) is a thin Node Stream subclass that decodes incoming buffers and forwards chunks into the same SAXParser.write(), and rebroadcasts the eighteen sax.EVENTS (from sax.EVENTS = ['text', 'opentag', 'closetag', ...]) as stream events, giving .pipe() compatibility for free.

Tech Stack — Runtime dependencies are zero; package.json lists only prettier and tap as devDependencies, and the module targets node >=11.0.0 while remaining plain ES5-compatible enough to run unmodified in a browser <script> tag. There is no build step: the published package is the same lib/sax.js file that lives in the repo, with files in package.json restricted to lib/sax.js, LICENSE, and README.md.

Code Quality — The project has strong behavioral test coverage: 68 files under test/, each a small fixture pairing an XML input string with an expected sequence of [eventName, payload] tuples (e.g. test/cdata.js asserts opentagstartopentagopencdatacdataclosecdataclosetag for a CDATA block), run via tap. Edge cases are explicitly covered, including billion-laughs.js (entity-expansion DoS), buffer-overrun.js, cdata-chunked.js/cdata-end-split.js (multi-write boundary handling), and Unicode/emoji/cyrillic input. Naming is terse but consistent (S. state constants, on<event> handler slots), and there is no TypeScript — types are implicit and undocumented beyond the README’s prose.

API Design — The public surface is deliberately minimal: construct a parser with sax.parser(strict, opt), assign on<event> callback properties (onopentag, ontext, onerror, …), then call .write(chunk).close(); the equivalent stream form is sax.createStream(strict, opt) piped like any Node stream. This callback-property style (rather than an EventEmitter) is unusual but well-documented in the README’s Events/Methods/Members sections, and getting started requires only a handful of lines with no configuration boilerplate — though consumers must build any DOM- or object-model construction themselves, as sax deliberately stops at raw events.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search