Output Builders

June 26, 2026 · View on GitHub

A collection of customizable output builders for flexible-xml-parser. Each builder transforms parsed XML into a different JavaScript data structure.

Packages

PackageOutput shapeInstall
@nodable/base-output-builderBase class — extend to build custom formatsnpm i @nodable/base-output-builder
@nodable/compact-builderNested objects, minimal structurenpm i @nodable/compact-builder
@nodable/sequential-builderArray-per-tag, order preservednpm i @nodable/sequential-builder
@nodable/sequential-stream-builderSame as sequential, streamed to a Writablenpm i @nodable/sequential-stream-builder
@nodable/node-tree-builderFixed-shape tree (elementname / child / attributes)npm i @nodable/node-tree-builder

Quick start

import XMLParser from "@nodable/flexible-xml-parser";
import { CompactBuilderFactory } from "@nodable/compact-builder";

const parser = new XMLParser({
  OutputBuilder: new CompactBuilderFactory(),
});

const result = parser.parse(xmlString);

See each package's README for options and examples.


Architecture

XMLParser
  ├── Input Sources   — string, buffer, stream, feed
  ├── Core Parser     — tokenises and walks XML
  ├── Output Builders — transform nodes into any structures  ← this repo
  └── Value Parsers   — per-value transforms (number, boolean, entity …)

The parser calls a fixed set of methods on the builder as it walks the document. Builders are not tied to this parser — any parser that calls the same method contract can use them.

Parser options vs builder options

Parser options (skip, nameFor, attributes.groupBy/prefix/suffix, onStopNode, …) are passed to XMLParser and forwarded to the builder at parse time via getInstance(parserOptions, matcher). The builder reads them from this.parserOptions.

Builder options are passed to the factory constructor (new CompactBuilderFactory(builderOptions)). These are builder-specific concerns: value parser chains, forceArray, textInChild, onClose, etc. The builder reads them from this.builderOptions.

Do not put parser options inside builder options or vice versa.


Summary

Fields

FieldBaseOutputBuilderBaseOutputBuilderFactory
parserOptions
builderOptions
registry
sharedContext
tagsPipeline
attrsPipeline
matcher
_rootName
_pendingStopNode

Methods — which builder overrides which

A blank cell means the base no-op is used. A ✓ means the builder provides its own implementation.

MethodBaseCompactSequentialSequentialStreamNodeTree
addElement
closeElement
addValue
addAttribute
addComment
addLiteral
addInstruction
addRawValue
addInputEntities
addDeclaration
onExit
onStopNode
getOutput

onStopNode is called by the parser when a stop node closes. Each builder that supports it reads parserOptions.onStopNode and invokes it — it is a parser option, not a builder option.

Builder options

Options passed to the factory constructor. Parser-level options are excluded here — see parser docs.

OptionCompactSequentialSequentialStreamNodeTree
tags.valueParsers
attributes.valueParsers
textInChild
textJoint
forceArray
alwaysArray
forceTextNode
onClose
stream
onChunk
space

Value parsers

Registered by name on the factory. Pass names or instances in tags.valueParsers / attributes.valueParsers.

ParserRegistered nameOptions
EntitiesValueParser'entity'EntityDecoderOptions from @nodable/entities
NumberValueParser'number'strnum options — see base-output-builder typings
BooleanParser'boolean'trueList, falseList
WSNormalizer'ws'exclude
Trim'trim'

Custom builders

Extend BaseOutputBuilder and BaseOutputBuilderFactory:

import { BaseOutputBuilder, BaseOutputBuilderFactory } from '@nodable/base-output-builder';

class MyBuilder extends BaseOutputBuilder {
  constructor(parserOptions, builderOptions, readonlyMatcher, registry) {
    super(parserOptions, builderOptions, readonlyMatcher, registry);
    this.result = [];
  }

  addElement(tag) { /* … */ }
  closeElement()  { /* … */ }
  addValue(text)  { /* … */ }
  getOutput()     { return this.result; }
}

export default class MyBuilderFactory extends BaseOutputBuilderFactory {
  constructor(options = {}) {
    super();
    this.builderOptions = options;
  }

  getInstance(parserOptions, readonlyMatcher) {
    return new MyBuilder(parserOptions, this.builderOptions, readonlyMatcher, this.registry);
  }
}

Use this.tagsPipeline.run(val, context) and this.attrsPipeline.run(val, context) inside the builder to apply the value parser chain. Call factory.registerValueParser(name, instance) to add or replace a named parser.


Entity parsing

The default 'entity' parser uses @nodable/entities. To customise it, pass your own instance:

import { EntitiesValueParser } from '@nodable/base-output-builder';

const evp = new EntitiesValueParser({ ncr: { onNcr: 'allow' } });

const factory = new CompactBuilderFactory({
  attributes: { valueParsers: [evp] }
});
factory.registerValueParser('entity', evp);

Development

npm install        # install all workspace dependencies
npm test           # run tests for all packages

License

MIT © Amit Gupta