WriterSync - Synchronous XML Generation
WriterSync - Synchronous XML Generation
Section titled “WriterSync - Synchronous XML Generation”StAX-XML includes a synchronous XML writer that generates XML documents programmatically. WriterSync builds a complete XML string in memory. WriterSyncSink uses the same synchronous writer model but writes incrementally to a sink, so large output can keep high throughput without retaining the whole XML string.
In v1.1, writeEvent() accepts events from EventReaderSync, enabling a
fixture → reader → modify → writer pipeline without manual event dispatch. See
XML Transformation Pipelines.
For large file output, prefer the sink path. The 1GiB writer benchmark shows WriterSyncSink has the best write throughput while peak RSS stays in the same range as async writing.
🔧 Quick Start
Section titled “🔧 Quick Start”Writing to Local File
Section titled “Writing to Local File”import { WriterSync } from 'stax-xml';import { writeFileSync } from 'fs';
// For Node.js - write to a local file synchronouslyfunction createLocalXmlFile() { const writer = new WriterSync({ prettyPrint: true, indentString: ' ' });
// Write XML document writer.writeStartDocument('1.0', 'utf-8');
writer.writeStartElement('catalog', { attributes: { version: '1.0' } });
writer.writeStartElement('product', { attributes: { id: '001' } });
writer.writeStartElement('name'); writer.writeCharacters('Laptop Computer'); writer.writeEndElement();
writer.writeStartElement('price', { attributes: { currency: 'USD' } }); writer.writeCharacters('999.99'); writer.writeEndElement();
writer.writeEndElement(); // product writer.writeEndElement(); // catalog
writer.writeEndDocument();
// Get the final XML string and write to file writeFileSync('./output.xml', writer.getXmlString()); console.log('XML file created successfully!');}
createLocalXmlFile();Express.js Middleware - XML Response
Section titled “Express.js Middleware - XML Response”import express from 'express';import { WriterSync } from 'stax-xml';
const app = express();
// Middleware to create XML responseapp.get('/api/users', (req, res) => { try { // Sample data const users = [ { id: 1, name: 'John Doe', email: 'john@example.com' }, { id: 2, name: 'Jane Smith', email: 'jane@example.com' } ];
const writer = new WriterSync({ prettyPrint: true, indentString: ' ' });
// Set appropriate headers res.setHeader('Content-Type', 'application/xml; charset=utf-8'); res.setHeader('Cache-Control', 'no-cache');
// Write XML writer.writeStartDocument('1.0', 'utf-8'); writer.writeStartElement('users');
for (const user of users) { writer.writeStartElement('user', { attributes: { id: user.id.toString() } });
writer.writeStartElement('name'); writer.writeCharacters(user.name); writer.writeEndElement();
writer.writeStartElement('email'); writer.writeCharacters(user.email); writer.writeEndElement();
writer.writeEndElement(); // user }
writer.writeEndElement(); // users writer.writeEndDocument();
// Send the final XML string res.send(writer.getXmlString());
} catch (error) { res.status(500).json({ error: 'Failed to generate XML' }); }});
app.listen(3000, () => { console.log('Server running on port 3000');});Hono Framework - XML Response
Section titled “Hono Framework - XML Response”import { Hono } from 'hono';import { WriterSync } from 'stax-xml';
const app = new Hono();
app.get('/api/products', (c) => { // Sample product data const products = [ { id: 'P001', name: 'Smartphone', price: 699.99, category: 'Electronics' }, { id: 'P002', name: 'Headphones', price: 199.99, category: 'Electronics' }, { id: 'P003', name: 'Coffee Maker', price: 149.99, category: 'Appliances' } ];
const writer = new WriterSync({ prettyPrint: true, indentString: ' ' });
try { // Generate XML writer.writeStartDocument('1.0', 'utf-8'); writer.writeStartElement('products', { attributes: { count: products.length.toString(), generated: new Date().toISOString() } });
for (const product of products) { writer.writeStartElement('product', { attributes: { id: product.id, category: product.category } });
writer.writeStartElement('name'); writer.writeCharacters(product.name); writer.writeEndElement();
writer.writeStartElement('price', { attributes: { currency: 'USD' } }); writer.writeCharacters(product.price.toString()); writer.writeEndElement();
writer.writeEndElement(); // product }
writer.writeEndElement(); // products writer.writeEndDocument();
// Return the generated XML string as a Response return c.text(writer.getXmlString(), 200, { 'Content-Type': 'application/xml; charset=utf-8', 'Cache-Control': 'no-cache', });
} catch (error) { return c.text('Failed to generate XML', 500); }});
export default app;Sink-Based Incremental Writing
Section titled “Sink-Based Incremental Writing”WriterSyncSink writes to any object implementing the small SyncTextSink
interface. Use the runtime standard library to build the target you need.
For external encoders, set the sink’s encoding metadata and encode each text
chunk in the sink. The XML declaration must match this value. Stateful encoders
must flush their final bytes from close().
import iconv from 'iconv-lite';import { closeSync, openSync, writeSync } from 'node:fs';import { WriterSyncSink } from 'stax-xml';
const fd = openSync('./catalog-euc-kr.xml', 'w');const encoder = iconv.getEncoder('euc-kr');const writer = new WriterSyncSink({ encoding: 'EUC-KR', write(chunk) { writeSync(fd, encoder.write(chunk)); }, close() { const tail = encoder.end(); if (tail) writeSync(fd, tail); closeSync(fd); },});
writer.writeStartDocument();writer.writeStartElement('catalog').writeCharacters('한국어').writeEndElement();writer.close();import { closeSync, openSync, writeSync } from 'node:fs';import { WriterSyncSink } from 'stax-xml';
const fd = openSync('./catalog.xml', 'w');const writer = new WriterSyncSink( { write(chunk) { writeSync(fd, chunk); }, close() { closeSync(fd); } }, { bufferSize: 4096, enableAutoFlush: true, flushThreshold: 0.7 });
writer.writeStartDocument('1.0', 'utf-8');writer.writeStartElement('catalog', { attributes: { version: '1.0' } });writer.writeStartElement('product', { attributes: { id: '001' } });writer.writeStartElement('name');writer.writeCharacters('Laptop Computer');writer.writeEndElement();writer.writeEndElement(); // productwriter.writeEndElement(); // catalogwriter.close();writer.flush() drains buffered chunks and calls sink.flush() when available.
writer.close() finalizes the document if needed, optionally calls sink.flush(), and closes the underlying target.
Advanced Writer Features
Section titled “Advanced Writer Features”import { WriterSync } from 'stax-xml';
// Create in-memory XML with custom entities and namespacesfunction createAdvancedXml() { const writer = new WriterSync({ prettyPrint: true, indentString: ' ', addEntities: [ { entity: 'company', value: 'Acme Corporation' }, { entity: 'copyright', value: '© 2024' } ], autoEncodeEntities: true });
// Write XML with namespaces and custom entities writer.writeStartDocument('1.0', 'utf-8');
writer.writeStartElement('document', { prefix: 'doc', uri: 'http://example.com/document', attributes: { version: '2.0' } }); writer.writeNamespace('meta', 'http://example.com/metadata');
writer.writeStartElement('header', { prefix: 'meta' }); writer.writeStartElement('title'); writer.writeCharacters('Product Catalog'); writer.writeEndElement();
writer.writeStartElement('company'); writer.writeCharacters('&company;'); // Will be encoded automatically writer.writeEndElement(); writer.writeEndElement(); // header
writer.writeStartElement('content'); writer.writeStartElement('item', { attributes: { type: 'featured' } });
// Self-closing element writer.writeStartElement('thumbnail', { attributes: { src: 'image.jpg', alt: 'Product Image' }, selfClosing: true });
writer.writeStartElement('description'); writer.writeCData('<p>This is <b>HTML</b> content in CDATA</p>'); writer.writeEndElement();
writer.writeEndElement(); // item writer.writeEndElement(); // content writer.writeEndElement(); // document
writer.writeEndDocument();
return writer.getXmlString();}
// Usageconsole.log('Generated XML:', createAdvancedXml());Unified WriteElementOptions API
Section titled “Unified WriteElementOptions API”WriterSync supports a unified API that simplifies element creation by consolidating all options into a single WriteElementOptions object:
import { WriterSync, WriteElementOptions } from 'stax-xml';
function createXmlWithNewAPI() { const writer = new WriterSync({ prettyPrint: true });
writer.writeStartDocument();
// Basic element with attributes writer.writeStartElement('catalog', { attributes: { version: '2.0', xmlns: 'http://example.com/catalog' } });
// Element with namespace and attributes writer.writeStartElement('product', { prefix: 'cat', uri: 'http://example.com/catalog', attributes: { id: '001', featured: 'true' } });
writer.writeStartElement('name'); writer.writeCharacters('Premium Laptop'); writer.writeEndElement();
// Self-closing element with attributes writer.writeStartElement('thumbnail', { attributes: { src: 'image.jpg', alt: 'Product Image', width: '200' }, selfClosing: true // No need to call writeEndElement() });
// Simple self-closing element writer.writeStartElement('br', { selfClosing: true });
writer.writeEndElement(); // product writer.writeEndElement(); // catalog
writer.writeEndDocument(); return writer.getXmlString();}
// Output:// <?xml version="1.0" encoding="UTF-8"?>// <catalog version="2.0" xmlns="http://example.com/catalog">// <cat:product id="001" featured="true" xmlns:cat="http://example.com/catalog">// <name>Premium Laptop</name>// <thumbnail src="image.jpg" alt="Product Image" width="200"/>// <br/>// </cat:product>// </catalog>Key Benefits of the Unified API:
- Unified Parameters: All element options (attributes, namespace, self-closing) are consolidated into a single options object
- Self-Closing Support: Set
selfClosing: trueto automatically close elements without callingwriteEndElement() - Cleaner Syntax: More intuitive and readable code structure
- Type Safety: Full TypeScript support with comprehensive type definitions
Usage Examples:
// Simple element with attributeswriter.writeStartElement('img', { attributes: { src: 'image.jpg', alt: 'Image' }, selfClosing: true});
// Element with namespacewriter.writeStartElement('title', { prefix: 'html', uri: 'http://www.w3.org/1999/xhtml', attributes: { lang: 'en' }});📚 API Reference
Section titled “📚 API Reference”class WriterSync { constructor( options?: WriterSyncOptions )
// Document Level Methods writeStartDocument(version?: '1.0', encoding?: string, standalone?: boolean): this // UTF-8 only writeEndDocument(): void
// Element Writing Methods writeStartElement(localName: string, options?: WriteElementOptions): this writeEndElement(): this
// Attribute and Namespace Methods writeAttribute(localName: string, value: string, prefix?: string): this writeNamespace(prefix: string, uri: string): this
// Content Writing Methods writeCharacters(text: string): this writeCData(cdata: string): this writeComment(comment: string): this writeProcessingInstruction(target: string, data?: string): this writeDTD(value: string): this writeEvent(event: AnyXmlEvent): this writeRaw(xml: string): this
// Utility Methods setPrettyPrint(enabled: boolean): this setIndentString(indentString: string): this isPrettyPrintEnabled(): boolean getIndentString(): string getXmlString(): string}
interface WriterSyncOptions { encoding?: string; // WriterSync: UTF-8; WriterSyncSink: must match sink.encoding prettyPrint?: boolean; // Default: false indentString?: string; // Default: ' ' addEntities?: { entity: string, value: string }[]; autoEncodeEntities?: boolean; // Default: true}
interface SyncTextSink { readonly encoding?: string; // Encoding produced by the external sink; default: UTF-8 write(chunk: string): void; flush?(): void; close?(): void;}
interface WriterSyncSinkOptions extends WriterSyncOptions { bufferSize?: number; // default: 16 * 1024 enableAutoFlush?: boolean; // default: true flushThreshold?: number; // default: 0.8 or absolute char count flushOnClose?: boolean; // default: false}
class WriterSyncSink { constructor( sink: SyncTextSink, options?: WriterSyncSinkOptions )
// Document Level Methods flush(): void close(): void}WriterSync and WriterSyncSink produce JavaScript text rather than encoded
bytes. WriterSync is restricted to UTF-8 declaration metadata. A
WriterSyncSink may declare another encoding through sink.encoding; the sink
must perform that external encoding, and options.encoding must match it.
🚀 Key Features
Section titled “🚀 Key Features”- Synchronous Operation: Use
WriterSyncwhen you need the complete XML string immediately - Sink-Based Large Output: Use
WriterSyncSinkwhen performance and memory both matter - Pretty Printing: Configurable indentation and formatting
- Namespace Support: Full XML namespace handling with prefix management
- Entity Encoding: Automatic or custom entity encoding
- Self-Closing Elements: Built-in support for self-closing tags
- Type Safety: Complete TypeScript support with detailed type definitions
- Memory Efficient: Direct string building without streaming overhead
💡 When to Use WriterSync
Section titled “💡 When to Use WriterSync”Use WriterSync / WriterSyncSink when:
- You need the complete XML document in memory immediately
- You want direct synchronous writes to a custom sink through
WriterSyncSink - Building XML responses for web APIs
- Generating configuration files or data exports
- Working in synchronous workflows where blocking is acceptable
- Memory usage is not a primary concern
For Web WritableStream response workflows, use the async Writer. For large synchronous file or response writes, use WriterSyncSink.