07

April 10, 2026 · View on GitHub

By default the parser throws on any structural problem. autoClose lets you recover gracefully from malformed or incomplete XML — useful for parsing real-world HTML fragments.


Two Failure Modes

1. Unclosed tags at EOF — document ends with open tags still on the stack:

<root><a><b>hello</b>

2. Mismatched closing tag — close tag doesn't match the currently open tag:

<root><outer><inner>text</outer></root>

Options

new XMLParser({
  autoClose: {
    onEof:         'throw',   // 'throw' | 'closeAll'
    onMismatch:    'throw',   // 'throw' | 'recover' | 'discard'
    collectErrors: false,
  }
});

All three sub-options are independent and default to the strictest value.

onEof

ValueBehaviour
'throw'Throw an error (default)
'closeAll'Silently close all remaining open tags, innermost first
const parser = new XMLParser({ autoClose: { onEof: 'closeAll' } });
parser.parse('<root><a><b>hello</b>');
// → { root: { a: { b: 'hello' } } }

onMismatch

ValueBehaviour
'throw'Throw an error (default)
'recover'Scan up the stack for a matching opener; close intermediate tags implicitly
'discard'Silently ignore the bad closing tag
const parser = new XMLParser({ autoClose: { onMismatch: 'recover' } });
parser.parse('<root><outer><inner>text</outer></root>');
// → { root: { outer: { inner: 'text' } } }

A closing tag with no matching opener anywhere in the stack is called a phantom close. With 'recover' or 'discard' it is dropped and, if collectErrors: true, logged as a phantom-close error.

collectErrors

When true, structural problems are recorded rather than silently dropped. After parsing, retrieve the list with parser.getParseErrors():

const parser = new XMLParser({
  autoClose: { onEof: 'closeAll', collectErrors: true }
});
parser.parse('<root><a><b>hi</b>');

parser.getParseErrors();
// [{ type: 'unclosed-eof', tag: 'a', expected: null, line: 1, col: 8, index: 7 }]

Error record fields

FieldDescription
type'unclosed-eof', 'mismatched-close', or 'phantom-close'
tagName of the tag that caused the problem
expectedWhat the parser expected (null for unclosed-eof)
line, col, indexPosition of the opening tag

HTML Preset

The 'html' shorthand enables all three relaxed behaviours and registers standard HTML void elements (br, img, input, meta, etc.) in tags.unpaired:

const parser = new XMLParser({ autoClose: 'html' });

Equivalent to:

new XMLParser({
  autoClose: {
    onEof:         'closeAll',
    onMismatch:    'discard',
    collectErrors: true,
  },
  tags: {
    unpaired: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
               'link', 'meta', 'param', 'source', 'track', 'wbr'],
  },
});

Any tags.unpaired values you add yourself are merged with the HTML void elements, not replaced.

const parser = new XMLParser({ autoClose: 'html', skip: { attributes: false } });
parser.parse('<html><head><meta charset="UTF-8"></head><body><p>Line one<br>Line two</body></html>');
// Parses successfully

Common Combinations

// Stream/truncation recovery only
{ autoClose: { onEof: 'closeAll' } }

// Lenient about mismatches, strict at EOF
{ autoClose: { onMismatch: 'recover' } }

// Fully lenient with error log
{ autoClose: { onEof: 'closeAll', onMismatch: 'recover', collectErrors: true } }

Works with all input modes

autoClose works identically with parse(), parseStream(), and feed()/end(). Errors are attached to the result returned by end().

parser.feed('<root><a>');
parser.feed('<b>hello</b>');
const result = parser.end();
// result.root.a.b === 'hello'
// parser.getParseErrors() → [{ type: 'unclosed-eof', tag: 'a', ... }]

➡ Next: 08 — Security