Skip to content
StAX-XML

EventReader - Asynchronous XML Parsing

EventReader is the asynchronous stable-event API. It accepts bytes from a ReadableStream<Uint8Array> or AsyncIterable<Uint8Array> and reads another input chunk only when the consumer asks for an event that is not already buffered.

import { EventReader, XmlEventType } from 'stax-xml';
for await (const event of new EventReader(response.body!)) {
if (event.type === XmlEventType.START_ELEMENT) {
const id = event.attributes.get('id')?.value;
console.log(event.name, id);
} else if (event.type === XmlEventType.CHARACTERS) {
console.log(event.value);
}
}

Every yielded event and attribute is a stable JavaScript object. Advancing the reader does not mutate previously yielded values.

Starting with v1.1, these events can be forwarded directly to Writer.writeEvent(), with selected events replaced in between. See XML Transformation Pipelines for complete sync and async examples.

type StreamReaderSource =
| ReadableStream<Uint8Array>
| AsyncIterable<Uint8Array>;
interface EventReaderOptions {
documentMode?: 'document' | 'fragment';
namespaceAware?: boolean; // default: true
autoDecodeEntities?: boolean; // default: true
addEntities?: { entity: string; value: string }[];
encoding?: string; // default: 'utf-8'
}

Byte input is decoded incrementally by a fatal TextDecoder. encoding defaults to utf-8 and accepts labels supported by the host decoder. The label is not inferred from the XML declaration. Invalid byte sequences and malformed XML reject next() and return the underlying source. The reader does not interpret DTD declarations, resolve external entities, or perform external I/O.

autoDecodeEntities defaults to true and performs single-pass decoding of the five predefined XML entities, numeric character references, and trusted definitions supplied through addEntities. Set it to false to preserve reference spelling in returned text and attributes while retaining XML reference validation. CDATA is always literal. Custom definitions are non-recursive and cannot override predefined entities.

Node.js Readable streams can be passed directly because they are async iterables and their Buffer chunks are Uint8Array values.

import { createReadStream } from 'node:fs';
import { EventReader } from 'stax-xml';
const reader = new EventReader(createReadStream('large.xml'));

EventReader implements both AsyncIterable<AnyXmlEvent> and AsyncIterator<AnyXmlEvent>. It emits START_DOCUMENT first and END_DOCUMENT last. Breaking from for await invokes return() and cancels or returns the source. In a manual loop, call await reader.return() when stopping early. Concurrent next() calls are rejected.

The first next() may consume input before yielding START_DOCUMENT. BOM, XML declaration, and DTD preamble information must be inspected before that event can be materialized, so source errors or cancellation can also occur on the first call.

Use StreamReader when reducing event-object allocation matters. It accepts the same sources and returns the event type while accessors read the current token. Attribute values can be read by index, qualified name, or the (namespaceURI, localName) pair.

import { StreamReader, XmlEventType } from 'stax-xml';
const reader = new StreamReader(source);
try {
while (await reader.next() !== null) {
if (reader.eventType() === XmlEventType.START_ELEMENT) {
console.log(reader.name(), reader.attributeValue('id'));
}
}
} finally {
await reader.close();
}

Current-token accessors are valid until the next successful next() call.

AnyXmlEvent covers document, start/end element, characters, CDATA, comment, processing-instruction, and DTD events. Start-element attributes are an EventAttributes is a read-only Map keyed by qualified name. Values retain name, localName, prefix, namespaceURI, and value; iteration follows source order. JSON.stringify() emits a JSON object rather than {}. In namespace-aware mode, namespace declarations are included in source order as attributes in the XMLNS namespace. This lets writeEvent() reproduce the bindings required by qualified names. attributes is undefined when the element has none. Start-document events also carry XML declaration metadata, and start-element events carry selfClosing. Use the exported type guards such as isStartElement() and isCharacters() for TypeScript narrowing.

The package does not provide async string input. When the complete XML is already a JavaScript string, use EventReaderSync or StreamReaderSync so the string can be scanned directly.