sax
A tiny, dependency-free streaming SAX-style parser for XML and HTML in JavaScript.
Repository Health
Technical Analysis
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 opentagstart → opentag → opencdata → cdata → closecdata → closetag 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.
Used by 4 apps in this directory
Omnivore
Knowledge Management · Bookmarks Archiving · Note Taking
Self-hosted read-it-later platform with highlights, newsletters, PDFs, and seamless Obsidian and Logseq integration.
Penpot
Design Tools
Open-source design platform with GPU-accelerated canvas, native design tokens, and MCP-powered AI workflows for teams that ship fast.
superglue
AI Agents · Data Engineering · Developer Tools
superglue is an AI-agent-driven integration engine that turns plain-English descriptions of enterprise systems into production-grade API tools, ERP/CRM connectors, and data pipelines — self-hosted or cloud, Y Combinator-backed (W25).
World Monitor
Monitoring · Analytics
Real-time global intelligence dashboard that fuses AI-synthesized news, geopolitical risk scoring, and infrastructure tracking into one open-source situational awareness platform.