# xml-xsd-engine — Usage Guide

> Practical recipes for every feature in the library.  
> **v1.7.0**

---

## Table of Contents

1. [Installation & Setup](#1-installation--setup)
2. [Parsing XML](#2-parsing-xml)
3. [Navigating the DOM](#3-navigating-the-dom)
4. [SAX / Event Streaming](#4-sax--event-streaming)
5. [XPath Queries](#5-xpath-queries)
6. [Serializing XML](#6-serializing-xml)
7. [Parsing & Compiling XSD Schemas](#7-parsing--compiling-xsd-schemas)
8. [Validating XML Against a Schema](#8-validating-xml-against-a-schema)
9. [Formatting Validation Results](#9-formatting-validation-results)
10. [Async File I/O](#10-async-file-io)
11. [Stream Parsing Large Files](#11-stream-parsing-large-files)
12. [Plugins](#12-plugins)
13. [Parse Budget — Handling Untrusted Input](#13-parse-budget--handling-untrusted-input)
14. [CLI Tool](#14-cli-tool)
15. [Error Handling](#15-error-handling)
16. [TypeScript Tips](#16-typescript-tips)
17. [Running Benchmarks](#17-running-benchmarks)
18. [Building & Testing](#18-building--testing)
19. [XML ↔ JSON Transforms](#19-xml--json-transforms)
20. [XSLT Transformation](#20-xslt-transformation)
21. [Schema Cache](#21-schema-cache)
22. [Batch Validation](#22-batch-validation)
23. [Code Generation](#23-code-generation)
24. [Async Schema Loader](#24-async-schema-loader)
25. [Namespace Engine *(v1.4)*](#25-namespace-engine-v14)
26. [Schema Compiler *(v1.4)*](#26-schema-compiler-v14)
27. [Validation Pipeline *(v1.4)*](#27-validation-pipeline-v14)
28. [Strict / Lax / Recovery / Profiling *(v1.4)*](#28-strict--lax--recovery--profiling-v14)
29. [Compiled XPath *(v1.4)*](#29-compiled-xpath-v14)
30. [xml-format CLI *(v1.4)*](#30-xml-format-cli-v14)
31. [Structured Error Categories *(v1.4)*](#31-structured-error-categories-v14)
32. [SAX Instrumentation *(v1.5)*](#32-sax-instrumentation-v15)
33. [Schema Preflight *(v1.5)*](#33-schema-preflight-v15)
34. [Identity Constraint Engine *(v1.5)*](#34-identity-constraint-engine-v15)
35. [XPath 2.0 Functions *(v1.5)*](#35-xpath-20-functions-v15)
36. [SHA-256 Utilities *(v1.5)*](#36-sha-256-utilities-v15)
37. [`xml:id` / `xml:base` Processing *(v1.5)*](#37-xmlid--xmlbase-processing-v15)
38. [DTD Internal Entity Expansion *(v1.5)*](#38-dtd-internal-entity-expansion-v15)
39. [Platform Entry Points — Browser / Deno / Bun *(v1.5)*](#39-platform-entry-points--browser--deno--bun-v15)
40. [`xs:assert` Assertion Evaluator *(v1.5)*](#40-xsassert-assertion-evaluator-v15)
41. [`xs:list` / `xs:union` Hardening *(v1.5)*](#41-xslist--xsunion-hardening-v15)
42. [Source-Mapped Error Locations *(v1.6)*](#42-source-mapped-error-locations-v16)
43. [XML Diff *(v1.6)*](#43-xml-diff-v16)
44. [Schema Inference *(v1.6)*](#44-schema-inference-v16)
45. [Fragment & Subtree Validation *(v1.6)*](#45-fragment--subtree-validation-v16)
46. [Encoding Declaration *(v1.6)*](#46-encoding-declaration-v16)

---

## 1. Installation & Setup

```bash
npm install xml-xsd-engine
```

No additional runtime dependencies. Node.js ≥ 18 required.

### Build from source

```bash
git clone <repo>
cd xml-xsd-engine
npm install        # installs TypeScript dev dependency only
npm run build      # produces dist/cjs, dist/esm, dist/types
npm test           # run 2460 tests via ts-jest (no build required)
```

### Import styles

```ts
// ESM (recommended)
import { parseXml, parseXsd, validate } from 'xml-xsd-engine';

// CommonJS
const { parseXml, parseXsd, validate } = require('xml-xsd-engine');

// Sub-path imports
import { validateFiles }  from 'xml-xsd-engine/async';
import { parseXmlStream } from 'xml-xsd-engine/stream';
import { jsonFormatter }  from 'xml-xsd-engine/formatters';

// v1.5 — platform entry points
import { parseXml }       from 'xml-xsd-engine/browser';  // zero Node.js deps
import { denoReadXmlFile } from 'xml-xsd-engine/deno';    // Deno-native
import { bunReadXmlFile }  from 'xml-xsd-engine/bun';     // Bun-native
```

---

## 2. Parsing XML

### Basic parse

```ts
import { parseXml } from 'xml-xsd-engine';

const doc = parseXml(`
  <?xml version="1.0"?>
  <catalog>
    <book id="b1" lang="en">
      <title>Learning XML</title>
      <price>29.99</price>
    </book>
  </catalog>
`);

console.log(doc.root?.tagName);       // "catalog"
console.log(doc.version);             // "1.0"
console.log(doc.encoding);            // "UTF-8" (default)
```

### Parse options

```ts
const doc = parseXml(xmlString, {
  // Security limits (defaults are safe for most use cases)
  maxDepth:      200,        // max element nesting depth
  maxAttributes: 64,         // max attributes per element
  maxTextLength: 1_000_000,  // max text node character count
  maxNodeCount:  200_000,    // max total DOM nodes

  // Custom entity expansion
  entityMap: {
    company: 'ACME Corp',
    copy:    '©',
  },
});
```

### Handling parse errors

```ts
import { parseXml, XmlError } from 'xml-xsd-engine';

try {
  const doc = parseXml('<root><child></root>');  // mismatched tag
} catch (err) {
  if (err instanceof XmlError) {
    console.error(err.code);           // "PARSE_MISMATCHED_TAG"
    console.error(err.message);        // "Expected </child>, got </root>"
    console.error(err.location);       // { line: 1, col: 21 }
    console.error(err.isWellFormedness); // true
  }
}
```

---

## 3. Navigating the DOM

### Reading attributes and text

```ts
const doc  = parseXml('<book id="b1" lang="en"><title>XML Basics</title></book>');
const book = doc.root!;

book.tagName                    // "book"
book.localName                  // "book"  (no prefix)
book.getAttribute('id')         // "b1"
book.getAttribute('missing')    // null
book.hasAttribute('lang')       // true
book.textContent                // "XML Basics"  (all text nodes concatenated)
```

### Accessing child elements

```ts
const catalog = doc.root!;

catalog.childElements                // XmlElement[]  (skips text/comment nodes)
catalog.getChild('book')             // first <book> child or null
catalog.getChildren('book')          // all <book> children
```

### Path navigation

```ts
// Single-step path
const title = catalog.find('book/title');   // XmlElement | null
const titles = catalog.findAll('book/title'); // XmlElement[]

// Multi-level — slashes as separators
const author = catalog.find('section/book/author');
```

### Depth-first visitor

```ts
import { XmlNode, XmlElement, XmlText } from 'xml-xsd-engine';

doc.root!.walk((node: XmlNode) => {
  if (node.nodeType === 'element') {
    const el = node as XmlElement;
    console.log(el.tagName);
  }
  if (node.nodeType === 'text') {
    const txt = node as XmlText;
    console.log(txt.value);
  }
});
```

### Namespaces

```ts
const xml = `
  <root xmlns="http://default.example.com"
        xmlns:ns="http://ns.example.com">
    <ns:item id="1"/>
  </root>
`;
const doc = parseXml(xml);
const item = doc.root!.childElements[0];

item.tagName        // "ns:item"
item.localName      // "item"
item.prefix         // "ns"
item.namespaceURI   // "http://ns.example.com"

// The root element uses the default namespace
doc.root!.namespaceURI  // "http://default.example.com"
```

---

## 4. SAX / Event Streaming

Use SAX when documents are large, when you only need a subset of data,
or when you must not allocate a full DOM.

### Push API — register event handlers, then call parse()

```ts
import { SaxParser } from 'xml-xsd-engine';

const xml = '<catalog><book id="1"><title>XML</title></book></catalog>';

const titles: string[] = [];
let currentTitle = false;

new SaxParser(xml)
  .on('startElement', ({ event }) => {
    if (event.localName === 'title') currentTitle = true;
  })
  .on('text', ({ value }) => {
    if (currentTitle) titles.push(value);
  })
  .on('endElement', ({ event }) => {
    if (event.localName === 'title') currentTitle = false;
  })
  .on('error', ({ error }) => {
    console.error('Parse error:', error.code, error.message);
  })
  .parse();

console.log(titles); // ["XML"]
```

### Pull API — iterate the event generator

```ts
import { SaxParser } from 'xml-xsd-engine';

// The generator is lazy — events are produced one at a time
for (const ev of new SaxParser(xml).events()) {
  switch (ev.type) {
    case 'startElement':
      console.log('open:', ev.event.qName);
      break;
    case 'endElement':
      console.log('close:', ev.event.qName);
      break;
    case 'text':
      if (ev.value.trim()) console.log('text:', ev.value);
      break;
    case 'error':
      console.error(ev.error.code);
      break;
  }
}
```

### Convenience wrapper for simple callbacks

```ts
import { parseSax } from 'xml-xsd-engine';

parseSax('<root><a/><b/></root>', {
  startElement: ({ event }) => console.log('+', event.localName),
  endElement:   ({ event }) => console.log('-', event.localName),
});
// + root
// + a
// - a
// + b
// - b
// - root
```

### Counting elements without building a DOM

```ts
let count = 0;
new SaxParser(hugeXml).on('startElement', () => count++).parse();
console.log(`${count} elements`);
```

---

## 5. XPath Queries

```ts
import { parseXml, xpath } from 'xml-xsd-engine';

const doc = parseXml(`
  <catalog>
    <book id="b1" lang="en"><title>XML Basics</title><price>29.99</price></book>
    <book id="b2" lang="fr"><title>XML Avancé</title><price>39.99</price></book>
    <book id="b3" lang="en"><title>XSD Guide</title><price>24.99</price></book>
  </catalog>
`);
```

### Basic selection

```ts
xpath(doc, '//book')                         // all 3 books
xpath(doc, 'catalog/book')                   // all 3 books (from root)
xpath(doc.root!, 'book')                     // all 3 books (from catalog element)
xpath(doc, '//title')                        // all 3 title elements
```

### Attribute predicates

```ts
xpath(doc, '//book[@lang="en"]')             // 2 English books
xpath(doc, '//book[@lang!="en"]')            // 1 French book
xpath(doc, '//book[@id]')                    // 3 books (all have id)
```

### Positional predicates

```ts
xpath(doc, '//book[1]')                      // first book
xpath.first(doc, '//book')                   // same — returns XmlElement | null
xpath(doc, '//book[last()]')                 // third book
xpath(doc, '//book[2]')                      // second book
```

### String-matching predicates

```ts
xpath(doc, '//book[contains(@id, "b")]')     // all (all ids contain "b")
xpath(doc, '//book[starts-with(@id, "b1")]') // first book
xpath(doc, '//title[contains(text(), "XML")]') // first two titles
```

### NOT predicate

```ts
xpath(doc, '//book[not(@lang="en")]')        // non-English books
```

### Helpers

```ts
xpath.count(doc, '//book')                   // 3
xpath.string(doc, '//book[1]/title')         // "XML Basics"
xpath.first(doc, '//book[@lang="fr"]')?.getAttribute('id') // "b2"
```

### Working from an element context

```ts
const catalog = doc.root!;

// These are equivalent:
xpath(doc,     '//book')
xpath(catalog, 'book')
xpath(catalog, 'descendant-or-self::book')

// Self axis
xpath(catalog, 'self::node()')               // [catalog]  — the catalog element itself
xpath(catalog, '.')                          // [catalog]  — same

// Relative path from an element
const book = xpath.first(doc, '//book')!;
xpath(book, 'title')                         // [<title>XML Basics</title>]
xpath(book, '../book')                       // all sibling books (parent then children)
```

---

## 6. Serializing XML

### Round-trip (parse → modify → serialize)

```ts
import { parseXml, serializeXml } from 'xml-xsd-engine';

const doc = parseXml('<root><item id="1">hello</item></root>');
doc.root!.setAttribute('version', '2.0');

const xml = serializeXml(doc);
// <?xml version="1.0" encoding="UTF-8"?>
// <root version="2.0">
//   <item id="1">hello</item>
// </root>
```

### Serializer options

```ts
// Compact — no declaration, no indentation
serializeXml(doc, { xmlDeclaration: false, indent: 0 });
// <root version="2.0"><item id="1">hello</item></root>

// Keep explicit close tags (no self-closing)
serializeXml(doc, { selfClose: false });
// <root></root>  instead of  <root/>
```

### Mixed content is preserved

```ts
// This XML has text and child elements mixed — semantics would break if reformatted
const html = parseXml('<p>Hello <b>world</b>!</p>');
serializeXml(html, { indent: 2 });
// <p>Hello <b>world</b>!</p>   ← NOT split across lines
```

---

## 7. Parsing & Compiling XSD Schemas

### Simple inline schema

```ts
import { parseXsd } from 'xml-xsd-engine';

const schema = parseXsd(`
  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="catalog">
      <xs:complexType>
        <xs:sequence>
          <xs:element name="book" maxOccurs="unbounded">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="title"  type="xs:string"/>
                <xs:element name="price"  type="xs:decimal"/>
              </xs:sequence>
              <xs:attribute name="id" type="xs:string" use="required"/>
            </xs:complexType>
          </xs:element>
        </xs:sequence>
      </xs:complexType>
    </xs:element>
  </xs:schema>
`);

console.log(schema.elements.has('catalog')); // true
```

### Schema with xs:import (multi-file)

```ts
import { readFileSync } from 'fs';

const schema = parseXsd(
  readFileSync('main.xsd', 'utf8'),
  (schemaLocation) => {
    // Called once per xs:import / xs:include
    try {
      return readFileSync(schemaLocation, 'utf8');
    } catch {
      return null; // skip schemas that can't be loaded
    }
  }
);
```

### Async schema loader (manual two-step)

```ts
// Until async SchemaLoader is supported natively, load first then parse:
import { readFile } from 'fs/promises';

const text   = await readFile('schema.xsd', 'utf8');
const schema = parseXsd(text);
```

### Inspecting the compiled schema

```ts
// List all top-level element names
for (const [name, decl] of schema.elements) {
  console.log(name, decl.typeName);
}

// Look up a type
const bookType = schema.resolveComplexType('BookType');
if (bookType) {
  console.log(bookType.particle?.compositor); // "sequence"
}

// Check for circular type definitions
const cycles = schema.detectCircularTypes();
if (cycles.length > 0) {
  console.warn('Circular types:', cycles.map(c => c.typeName));
}
```

---

## 8. Validating XML Against a Schema

### Basic validation

```ts
import { parseXml, parseXsd, validate } from 'xml-xsd-engine';

const schema = parseXsd(xsdSource);
const doc    = parseXml(xmlSource);
const result = validate(doc, schema);

if (result.valid) {
  console.log('✔ Document is valid');
} else {
  for (const issue of result.errors) {
    console.error(`ERROR at ${issue.path}: ${issue.message}`);
  }
}
```

### Inspecting all issues

```ts
const result = validate(doc, schema);

console.log(result.summary);
// { errors: 2, warnings: 1 }

for (const issue of result.issues) {
  console.log(
    issue.severity,   // 'error' | 'warning'
    issue.path,       // '/catalog/book[1]/price'
    issue.message,    // '"abc" is not a valid xs:decimal'
    issue.line,       // source line (if available)
    issue.col,        // source column (if available)
  );
}
```

### Programmatic result use

```ts
if (!result.valid) {
  // Throw on first error
  const firstError = result.errors[0];
  throw new Error(`[${firstError.path}] ${firstError.message}`);
}

// Filter to a specific subtree
const priceErrors = result.errors.filter(e => e.path.includes('/price'));
```

### Running plugin validators after XSD validation

```ts
import { PluginRegistry } from 'xml-xsd-engine';

const registry = new PluginRegistry();
registry.registerSchemaValidator((doc, schema, result) => {
  // Custom business rule: root must have a "version" attribute
  if (!doc.root?.getAttribute('version')) {
    result.addError('root element must have a "version" attribute', '/root');
  }
});

const result = validate(doc, schema);
registry.runSchemaValidators(doc, schema, result);
```

---

## 9. Formatting Validation Results

### Text formatter (terminal output)

```ts
import { validate }       from 'xml-xsd-engine';
import { textFormatter }  from 'xml-xsd-engine/formatters';

const result = validate(doc, schema);
console.log(textFormatter(result, { fileName: 'document.xml', colors: true }));
```

Output:
```
✖ INVALID  document.xml
  ERROR  /catalog/book[1]/price   "abc" is not a valid xs:decimal
  WARN   /catalog/book[2]          Undeclared attribute "category"
──────────────────────────────────────
  1 error, 1 warning
```

### JSON formatter (API / webhook)

```ts
import { jsonFormatter } from 'xml-xsd-engine/formatters';

const json = jsonFormatter(result, { indent: 2 });
// {
//   "valid": false,
//   "errors": 1,
//   "warnings": 1,
//   "issues": [ ... ]
// }

// Minified for transmission
const minified = jsonFormatter(result, { indent: 0 });
```

### GitHub Actions formatter

```ts
import { githubActionsFormatter } from 'xml-xsd-engine/formatters';

// In a GitHub Actions workflow step:
process.stdout.write(githubActionsFormatter(result, 'document.xml'));
// ::error file=document.xml,line=0,col=0,title=xml-xsd-engine::/catalog/book[1]/price - "abc" is not a valid xs:decimal
```

### JUnit formatter (Jenkins / CircleCI / GitLab)

```ts
import { junitFormatter } from 'xml-xsd-engine/formatters';
import { writeFileSync }   from 'fs';

writeFileSync('test-results.xml', junitFormatter(result));
```

### Compact formatter (log files)

```ts
import { compactFormatter } from 'xml-xsd-engine/formatters';

const lines = compactFormatter(result);
// ERROR|/catalog/book[1]/price|"abc" is not a valid xs:decimal
// WARN|/catalog/book[2]|Undeclared attribute "category"
```

### Custom formatter

```ts
import { createFormatter } from 'xml-xsd-engine/formatters';

const csvFormatter = createFormatter({
  header: (r)  => `"valid","errors","warnings"\n"${r.valid}","${r.summary.errors}","${r.summary.warnings}"\n`,
  issue:  (i)  => `"${i.severity}","${i.path}","${i.message}"\n`,
  footer: ()   => undefined,   // no footer
});

writeFileSync('results.csv', csvFormatter(result));
```

---

## 10. Async File I/O

```ts
import {
  readXmlFile,
  readXsdFile,
  validateFiles,
  validateFilesWithLoader,
} from 'xml-xsd-engine/async';

// Read and parse a file
const doc    = await readXmlFile('document.xml');
const schema = await readXsdFile('schema.xsd');

// Validate two files
const result = await validateFiles('document.xml', 'schema.xsd');

// With a custom schema import loader
const result = await validateFilesWithLoader(
  'document.xml',
  'main.xsd',
  (schemaLocation) => {
    const { readFileSync } = require('fs');
    return readFileSync(schemaLocation, 'utf8');
  }
);

// Validate multiple documents concurrently against the same schema
const files  = ['a.xml', 'b.xml', 'c.xml'];
const results = await Promise.all(
  files.map(f => validateFiles(f, 'schema.xsd'))
);
results.forEach((r, i) => {
  console.log(files[i], r.valid ? '✔' : '✖');
});
```

---

## 11. Stream Parsing Large Files

Use streaming when documents are too large to fit comfortably in memory.

### Promise-based stream parse

```ts
import { parseXmlStream, parseXmlFileStream } from 'xml-xsd-engine/stream';
import { createReadStream } from 'fs';

// From a Node.js Readable stream
const doc = await parseXmlStream(createReadStream('large.xml'));

// Convenience: from a file path
const doc = await parseXmlFileStream('/data/exports/huge.xml');

console.log(doc.root?.tagName);
```

### Pipe to the Transform stream

```ts
import { XmlStreamParser } from 'xml-xsd-engine/stream';
import { createReadStream } from 'fs';

const parser = new XmlStreamParser();

parser.on('document', (doc) => {
  // Fires when the full document has been assembled
  console.log('Parsed:', doc.root?.tagName);
});

parser.on('error', (err) => {
  console.error('Stream parse error:', err.message);
});

createReadStream('/data/large.xml').pipe(parser);
```

### Combining streaming with SAX for truly large files

For multi-gigabyte files where even the DOM is too large, combine file streaming
with the SAX parser by reading chunks yourself:

```ts
import { createReadStream } from 'fs';
import { SaxParser }        from 'xml-xsd-engine';

async function countElements(path: string): Promise<number> {
  return new Promise((resolve, reject) => {
    let count  = 0;
    let buffer = '';

    const stream = createReadStream(path, { encoding: 'utf8' });
    stream.on('data', (chunk: string) => { buffer += chunk; });
    stream.on('end', () => {
      // Buffer assembled; now use SAX for O(depth) memory
      new SaxParser(buffer)
        .on('startElement', () => count++)
        .on('error', ({ error }) => reject(error))
        .parse();
      resolve(count);
    });
    stream.on('error', reject);
  });
}
```

---

## 12. Plugins

The plugin system lets you extend engine behaviour without modifying library code.

### Custom simple-type validator

```ts
import { PluginRegistry, parseXml, parseXsd, validate } from 'xml-xsd-engine';

const registry = new PluginRegistry();

// Register a validator for a custom type used in your XSD
// (the XSD would declare: <xs:simpleType name="app:Email" .../>)
registry.registerTypeValidator('app:Email', (value) => {
  const ok = /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/.test(value);
  return ok
    ? { valid: true }
    : { valid: false, message: `"${value}" is not a valid email address` };
});

// The validator is called automatically when the engine encounters app:Email
const schema = parseXsd(xsdSource);
const doc    = parseXml(xmlSource);
const result = validate(doc, schema);
```

### Custom entity resolver

```ts
registry.registerEntityResolver((name) => {
  const entities: Record<string, string> = {
    company:   'ACME Corporation',
    trademark: '™',
    copyright: '©',
  };
  return entities[name] ?? null;
});

// Pass the resolver map to the parser
const doc = parseXml(xmlSource, { entityMap: registry.buildEntityMap() });
// &company; in the XML is expanded to "ACME Corporation"
```

### Post-validation business rules

```ts
registry.registerSchemaValidator((doc, _schema, result) => {
  const root = doc.root;
  if (!root) return;

  // Rule 1: root must have a version attribute
  if (!root.getAttribute('version')) {
    result.addError('root element must declare a "version" attribute', '/root');
  }

  // Rule 2: all <price> elements must be positive
  const prices = root.findAll('book/price');
  for (const price of prices) {
    const val = parseFloat(price.textContent);
    if (val <= 0) {
      result.addError(`Price must be positive, got "${price.textContent}"`, '/root/book/price');
    }
  }
});

const result = validate(doc, schema);
registry.runSchemaValidators(doc, schema, result);
```

### Pre-serialization transforms

```ts
registry.registerSerializerTransform((doc) => {
  // Stamp every serialized document with a generated-at timestamp
  doc.root?.setAttribute('generated-at', new Date().toISOString());
  // Remove all comment nodes
  doc.root?.walk((node) => {
    if (node.nodeType === 'comment') node.remove?.();
  });
});

registry.applySerializerTransforms(doc);
const xml = serializeXml(doc);
```

### Composing registries

```ts
// core-rules.ts
export const coreRegistry = new PluginRegistry();
coreRegistry.registerTypeValidator('app:UUID', validateUUID);
coreRegistry.registerEntityResolver(builtinEntities);

// feature-x.ts
export const featureXRegistry = new PluginRegistry();
featureXRegistry.registerSchemaValidator(featureXRules);

// app.ts
const appRegistry = new PluginRegistry().merge(coreRegistry).merge(featureXRegistry);
console.log(appRegistry.summary());
// { typeValidators: 1, entityResolvers: 1, schemaValidators: 1, serializerTransforms: 0 }
```

---

## 13. Parse Budget — Handling Untrusted Input

Use `ParseBudget` any time your application receives XML from an untrusted source
(user uploads, external APIs, form submissions).

### Basic usage

```ts
import { ParseBudget } from 'xml-xsd-engine';

const budget = new ParseBudget({
  wallTimeMs:    1_000,      // hard 1-second timeout
  bytes:         2_000_000,  // reject anything over 2 MB before parsing
  nodes:         100_000,    // max DOM nodes
  depth:         50,         // max nesting depth
  maxAttributes: 32,         // max attributes per element
});

try {
  const doc = budget.parse(req.body);
  // ... continue processing
} catch (err) {
  // err is always an XmlError with a SEC_* code
  res.status(400).json({ error: err.message, code: err.code });
}
```

### Non-throwing cost report

```ts
const report = budget.report(untrustedInput);

if (!report.withinBudget) {
  console.warn('Budget violations:', report.violations);
  // ["byte limit exceeded (3145728 > 2000000)"]
}
console.log(`Parse took ${report.elapsedMs}ms for ${report.bytes} bytes`);
```

### Always-on protections (no configuration needed)

These are enforced by the engine regardless of `ParseBudget`:

| Threat | Default protection |
|---|---|
| Billion Laughs attack | Entity expansion capped at 1 000 000 chars |
| XXE (external entity injection) | `SYSTEM` / `PUBLIC` entities **never** fetched |
| Deep nesting (default depth limit) | Throws after 500 levels |
| Attribute flooding | Throws after 256 attributes per element |

---

## 14. CLI Tool

Install globally:

```bash
npm install -g xml-xsd-engine
```

### Usage modes

```bash
# Validate one XML file against a schema
xml-validate document.xml schema.xsd

# Validate multiple XML files against one schema
xml-validate a.xml b.xml c.xml schema.xsd

# Read XML from stdin (use - as the xml filename)
cat document.xml | xml-validate - schema.xsd

# Read XML from stdin using the explicit flag
cat document.xml | xml-validate --stdin schema.xsd

# Well-formedness only — no XSD required
xml-validate --well-formed document.xml

# Well-formedness check from stdin
cat document.xml | xml-validate --well-formed --stdin
```

### Output formats (`--format` / `-f`)

```bash
# Human-readable with ANSI colour (default)
xml-validate doc.xml schema.xsd --format text

# One line per issue — pipe-delimited (easy to parse in scripts)
xml-validate doc.xml schema.xsd --format compact

# JSON — pipe to jq or save to file
xml-validate doc.xml schema.xsd --format json
xml-validate doc.xml schema.xsd --format json | jq '.issues'
xml-validate doc.xml schema.xsd --format json --json-indent 0   # minified

# GitHub Actions ::error annotations
xml-validate doc.xml schema.xsd --format github

# JUnit XML — Jenkins, CircleCI, GitLab CI
xml-validate doc.xml schema.xsd --format junit
```

### Writing output to a file (`--output` / `-o`)

```bash
# Save as JSON instead of printing to stdout
xml-validate doc.xml schema.xsd --format json --output results.json

# Save JUnit results for CI artifact upload
xml-validate doc.xml schema.xsd --format junit --output test-results.xml

# Save compact log
xml-validate doc.xml schema.xsd --format compact --output errors.log
```

### Controlling what is displayed

```bash
# Hide warnings — only show errors
xml-validate doc.xml schema.xsd --no-warnings

# Show at most 5 errors
xml-validate doc.xml schema.xsd --max-errors 5

# Show at most 3 warnings
xml-validate doc.xml schema.xsd --max-warnings 3

# Hide the summary line at the bottom
xml-validate doc.xml schema.xsd --no-summary

# Disable ANSI colour (for log files, pipes)
xml-validate doc.xml schema.xsd --no-color

# Force ANSI colour on even when piping
xml-validate doc.xml schema.xsd --color
```

### Performance and diagnostics

```bash
# Show parse + validation time
xml-validate doc.xml schema.xsd --timing
# ✔ Valid – no issues found.
# ⏱  Completed in 12ms
```

### Shell scripting (`--quiet` / `-q`)

`--quiet` suppresses all output. Only the exit code matters.

```bash
# Silent check — use in shell scripts
xml-validate doc.xml schema.xsd --quiet && echo "OK" || echo "INVALID"

# Chain with other commands
xml-validate config.xml schema.xsd -q && deploy.sh
```

### Schema import resolution (`--schema-dir`)

When an XSD uses `xs:import` or `xs:include` with relative paths, the CLI
resolves them relative to the XSD file's directory by default.
Use `--schema-dir` to override:

```bash
xml-validate doc.xml schema.xsd --schema-dir /shared/schemas
```

### CI integration examples

**GitHub Actions:**

```yaml
- name: Validate XML
  run: xml-validate src/data/config.xml schemas/config.xsd --format github
  # Errors appear as GitHub PR review annotations automatically
```

**GitHub Actions — multiple files:**

```yaml
- name: Validate all XML data files
  run: xml-validate data/*.xml schemas/data.xsd --format github
```

**GitLab CI / Jenkins:**

```yaml
validate-xml:
  script:
    - xml-validate data.xml schema.xsd --format junit --output results.xml
  artifacts:
    reports:
      junit: results.xml
```

**Pre-commit hook:**

```bash
#!/bin/sh
# .git/hooks/pre-commit
xml-validate config/settings.xml schemas/settings.xsd --quiet
if [ $? -ne 0 ]; then
  echo "XML validation failed. Run: xml-validate config/settings.xml schemas/settings.xsd"
  exit 1
fi
```

### Complete flag reference

| Flag | Short | Default | Description |
|---|---|---|---|
| `--format <fmt>` | `-f` | `text` | Output format: `text` `compact` `json` `github` `junit` |
| `--output <file>` | `-o` | stdout | Write output to a file |
| `--well-formed` | `-w` | off | Check well-formedness only, no XSD needed |
| `--stdin` | | off | Read XML from stdin |
| `--quiet` | `-q` | off | No output; exit code only |
| `--no-warnings` | | off | Suppress warning lines |
| `--max-errors <n>` | | `0` (all) | Display at most n errors |
| `--max-warnings <n>` | | `0` (all) | Display at most n warnings |
| `--no-summary` | | off | Hide the summary line |
| `--json-indent <n>` | | `2` | Indent depth for `json` format |
| `--timing` | | off | Print elapsed time |
| `--schema-dir <path>` | | XSD dir | Base dir for xs:import / xs:include |
| `--color` | | auto | Force ANSI colour on |
| `--no-color` | | auto | Disable ANSI colour |
| `--exit-zero` | | off | Always exit 0 |
| `--version` | `-v` | | Show version |
| `--help` | `-h` | | Show help |

### Exit codes

| Code | Meaning |
|---|---|
| `0` | Document is valid (or `--exit-zero` was passed) |
| `1` | One or more validation errors found |
| `2` | Usage error or file not found |

---

## 15. Error Handling

### Always catch XmlError

```ts
import { parseXml, XmlError } from 'xml-xsd-engine';

function safeParseXml(input: string) {
  try {
    return parseXml(input);
  } catch (err) {
    if (err instanceof XmlError) {
      // Structured, safe to inspect
      return { error: { code: err.code, message: err.message, location: err.location } };
    }
    // Unexpected error — rethrow
    throw err;
  }
}
```

### Discriminate error types

```ts
if (err instanceof XmlError) {
  if (err.isSecurity) {
    // SEC_* — likely malicious input; return 400
    log.warn('Security limit hit', { code: err.code, ip: req.ip });
    return res.status(400).end();
  }
  if (err.isWellFormedness) {
    // PARSE_* — malformed XML; user error
    return res.status(422).json({ error: err.message, line: err.location?.line });
  }
  if (err.isValidation) {
    // VALID_* — schema mismatch; should not happen at parse time
  }
}
```

### Accessing validation issues without throwing

The validation engine **never throws** — it returns a `ValidationResult` with issues:

```ts
const result = validate(doc, schema);  // never throws

if (!result.valid) {
  // result.errors is always an array, even if empty
  const summary = result.errors.map(e => `${e.path}: ${e.message}`).join('\n');
  throw new Error(`Invalid document:\n${summary}`);
}
```

---

## 16. TypeScript Tips

### Narrowing node types

```ts
import { XmlNode, XmlElement, XmlText, XmlComment, XmlCData } from 'xml-xsd-engine';

function processNode(node: XmlNode) {
  switch (node.nodeType) {
    case 'element':
      const el = node as XmlElement;
      console.log(el.tagName, el.getAttribute('id'));
      break;
    case 'text':
      const txt = node as XmlText;
      console.log(txt.value);
      break;
    case 'comment':
      // skip
      break;
  }
}
```

### Typing the SAX event handler

```ts
import { SaxParser, SaxEventType, SaxHandler, StartElementEvent } from 'xml-xsd-engine';

const onStart: SaxHandler<'startElement'> = ({ event }) => {
  // event is fully typed as StartElementEvent
  const attrs = event.attributes;  // SaxAttribute[]
};

new SaxParser(xml).on('startElement', onStart).parse();
```

### Typing a custom formatter

```ts
import { Formatter, ValidationResult } from 'xml-xsd-engine';
import { createFormatter } from 'xml-xsd-engine/formatters';

const myFormatter: Formatter = createFormatter({
  header: (r: ValidationResult) => r.valid ? 'OK\n' : 'FAIL\n',
  issue:  (i) => `${i.path}: ${i.message}\n`,
});
```

---

## 17. Running Benchmarks

```bash
npm run build
node dist/cjs/utils/benchmark.js
```

Output:

```
══════════════════════════════════════════════════════════════
  BENCHMARKS
══════════════════════════════════════════════════════════════

  Input sizes: small=0.02MB  medium=0.17MB  large=0.85MB

  ── DOM Parse ──────────────────────────────────────────────
  parseXml  (5k  elements, 0.85 MB)                   35 ops/s   28.238ms/op

  ── SAX Parse ──────────────────────────────────────────────
  SaxParser (5k  elements, 0.85 MB)                   30 ops/s   33.725ms/op

  ── Serialize ──────────────────────────────────────────────
  serializeXml (1k  elements)                        548 ops/s    1.826ms/op

  ── XSD Compile ────────────────────────────────────────────
  parseXsd  (catalog schema)                       20453 ops/s    0.049ms/op

  ── Validate ───────────────────────────────────────────────
  validate  (1k  elements)                           590 ops/s    1.694ms/op

  ── XPath ──────────────────────────────────────────────────
  xpath //book (1k el)                              2252 ops/s    0.444ms/op
  xpath //book[@lang="en"] (1k)                     1619 ops/s    0.618ms/op
```

---

## 18. Building & Testing

### Build

```bash
npm run build           # CJS + ESM + types (runs automatically on npm publish)
npm run build:cjs       # CommonJS only
npm run build:esm       # ESM only (also runs fix-esm-extensions.mjs)
npm run build:types     # TypeScript declarations only
npm run build:watch     # Re-compile on file change
```

### Tests

```bash
npm test                        # run all Jest tests (no build needed)
npm run test:coverage           # with coverage report
npm run test:watch              # watch mode
npx jest src/tests/unit/xml-parser.test.ts   # single file
```

1282 tests across 45 focused files in `src/tests/unit/` — one file per module.

### Project layout

```
src/
  cache/            SchemaCache (LRU+TTL, content-hash keying v1.5)
  cli/              xml-validate + xml-format CLI entry points
  errors/           XmlError class and error codes (3 new v1.5)
  io/               Async file utils + stream parser
  namespace/        NamespaceEngine (v1.4 G35)
  parser/           XmlLexer, XmlParser, XmlNodes, XmlSerializer, SaxParser,
                    XPathEngine, SaxInstrumentation (v1.5), XPath2Functions (v1.5)
  pipeline/         SchemaCompilerLite (v1.4), ValidationPipeline (v1.4)
  plugins/          PluginRegistry
  schema/           SchemaModel (compiled XSD representation, ref? v1.5)
  tests/
    fixtures/       library.xml, library.xsd, library-invalid.xml, catalog.xml
    unit/           45 Jest unit test files — one per module
  transform/        XmlToJson, JsonToXml, XsltTransformer
  utils/            xmlUtils, StringReader, ParseBudget, sha256 (v1.5), benchmark
  validator/        ValidationEngine, TypeValidator, ValidationResult, BatchValidator,
                    formatters, AssertionEvaluator (v1.5), IdentityConstraintEngine (v1.5),
                    SchemaPreflight (v1.5), XsdTypeSystem
  xsd/              XsdParser
  browser.ts        Browser entry point (v1.5 G9)
  bun.ts            Bun entry point (v1.5 G25)
  deno.ts           Deno entry point (v1.5 G25)
  index.ts          Public API barrel export
dist/
  cjs/              Compiled CommonJS (+ package.json { "type":"commonjs" })
  esm/              Compiled ES Modules (+ package.json { "type":"module" })
  types/            TypeScript .d.ts declarations
docs/
  ARCHITECTURE.md   Internal design document
  ERRORS.md         Error code reference
  FEATURES.md       Complete feature reference
  GUIDE.md          This file
  MIGRATION.md      Upgrade guide
  ROADMAP.md        Future features and release plans
  TESTING.md        Test structure and how to write tests
```

### Adding a new test

Create or open `src/tests/unit/<module>.test.ts` and add a Jest `it()` block:

```ts
it('my feature – does the right thing', () => {
  const doc = parseXml('<root><item>hello</item></root>');
  const el  = xpath.first(doc, '//item');
  expect(el?.textContent).toBe('hello');
});
```

---

## 19. XML ↔ JSON Transforms

Convert between XML and plain JavaScript objects — no schema required.

### XML → JSON

```ts
import { parseXml, xmlToJson } from 'xml-xsd-engine';

const doc = parseXml(`
  <catalog version="2">
    <book id="1"><title>Dune</title><price>12.99</price></book>
    <book id="2"><title>1984</title><price>8.99</price></book>
  </catalog>
`);

const json = xmlToJson(doc, {
  attributePrefix: '@',    // attributes hoisted with this prefix (default '@')
  textKey:         '_text', // key for text content (default '_text')
  collapseText:    true,   // single text child becomes plain string
  coerceNumbers:   true,   // "12.99" → 12.99
  coerceBooleans:  true,   // "true" → true
});
// {
//   catalog: {
//     '@version': 2,
//     book: [
//       { '@id': 1, title: 'Dune', price: 12.99 },
//       { '@id': 2, title: '1984', price: 8.99 }
//     ]
//   }
// }
```

Repeated sibling elements are automatically grouped into arrays.

### JSON → XML

```ts
import { jsonToXml, jsonToXmlString } from 'xml-xsd-engine';
import { serializeXml } from 'xml-xsd-engine';

const doc = jsonToXml({
  catalog: {
    '@version': '2',        // @ prefix → XML attribute
    book: [                  // array → repeated sibling elements
      { '@id': '1', title: 'Dune',  price: '12.99' },
      { '@id': '2', title: '1984',  price: '8.99' },
    ],
  },
});

// Or get a string directly
const xml = jsonToXmlString({
  person: { '@id': '42', name: 'Alice', age: '30' }
});
// <person id="42"><name>Alice</name><age>30</age></person>
```

Special keys:
| Key pattern | Becomes |
|---|---|
| `@attr` | XML attribute |
| `_text` | text content |
| Array value | Repeated sibling elements |
| `null` value | Empty self-closing element |

---

## 20. XSLT Transformation

Transform XML with an XSLT 1.0 stylesheet — no external library needed.

### One-shot transform

```ts
import { transformXml } from 'xml-xsd-engine';

const sourceXml = `
  <catalog>
    <book><title>Dune</title><author>Herbert</author></book>
    <book><title>1984</title><author>Orwell</author></book>
  </catalog>
`;

const stylesheet = `
  <?xml version="1.0"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="/">
      <books>
        <xsl:apply-templates select="catalog/book"/>
      </books>
    </xsl:template>

    <xsl:template match="book">
      <item title="{title}">
        <xsl:value-of select="author"/>
      </item>
    </xsl:template>
  </xsl:stylesheet>
`;

const result = transformXml(sourceXml, stylesheet);
// <books><item title="Dune">Herbert</item><item title="1984">Orwell</item></books>
```

### Reusable transformer (compile once)

```ts
import { XsltTransformer } from 'xml-xsd-engine';

// Compile stylesheet once
const transformer = new XsltTransformer(stylesheet);

// Transform many documents
const out1 = transformer.transform(xml1);
const out2 = transformer.transform(xml2);
```

### Supported XSLT instructions

`xsl:template` · `xsl:apply-templates` · `xsl:value-of` · `xsl:copy-of` ·
`xsl:for-each` · `xsl:sort` · `xsl:if` · `xsl:choose` / `xsl:when` / `xsl:otherwise` ·
`xsl:element` · `xsl:attribute` · `xsl:text` · `xsl:param` · `xsl:variable` ·
`xsl:output` · Literal result elements with attribute value templates `{expr}`

---

## 21. Schema Cache

Parse an XSD once, reuse the compiled `SchemaModel` across thousands of validations.

### Global shared cache (simplest)

```ts
import { globalSchemaCache } from 'xml-xsd-engine';

// Async file-based — cached by absolute path
const schema = await globalSchemaCache.getOrLoad('./schemas/order.xsd');

// Validate — schema compiled only once
const result = await globalSchemaCache.validateFile('order-001.xml', './schemas/order.xsd');
console.log(result.valid);
```

### Custom cache instance

```ts
import { SchemaCache } from 'xml-xsd-engine';

const cache = new SchemaCache({
  maxSize: 50,            // LRU — evict oldest after 50 entries
  ttlMs:   60_000,        // TTL — expire after 60 seconds
});

// String-based (synchronous)
const schema = cache.getOrParse('order-v2', xsdString);

// Inspect
console.log(cache.size);    // number of cached entries
console.log(cache.keys());  // string[]

// Invalidate
cache.invalidate('order-v2');
cache.clear();
```

### Batch validate with caching built-in

```ts
// validateFiles() reuses the same compiled schema for all files
const results = await cache.validateFiles(
  ['a.xml', 'b.xml', 'c.xml'],
  './schemas/order.xsd',
);
```

---

## 22. Batch Validation

Validate many XML files or strings against one schema concurrently.

### Validate files

```ts
import { batchValidateFiles } from 'xml-xsd-engine';

const report = await batchValidateFiles(
  ['invoice-001.xml', 'invoice-002.xml', 'invoice-003.xml'],
  './schemas/invoice.xsd',
  {
    concurrency: 8,     // parallel workers (default 4)
    failFast:    false, // stop on first error? (default false)
    onProgress: (key, result) => {
      console.log(`${key}: ${result.valid ? '✔' : '✘'}`);
    },
  },
);

console.log(report.total);    // 3
console.log(report.passed);   // 2
console.log(report.failed);   // 1
console.log(report.allValid); // false
console.log(report.summary);  // "2 passed, 1 failed in 45ms"
```

### Validate strings

```ts
import { batchValidateStrings } from 'xml-xsd-engine';

const report = await batchValidateStrings(
  [
    { key: 'doc-1', xml: '<invoice><id>1</id></invoice>' },
    { key: 'doc-2', xml: '<invoice><id>2</id></invoice>' },
  ],
  xsdString,
);
```

### Class API (reuse schema across many calls)

```ts
import { BatchValidator } from 'xml-xsd-engine';

// Accepts schema as: file path | raw XSD string | pre-compiled SchemaModel
const bv = new BatchValidator('./schemas/invoice.xsd');

const report1 = await bv.validateFiles(['jan/*.xml']);
const report2 = await bv.validateFiles(['feb/*.xml']);
// Schema compiled only once and reused for both batches

// Single synchronous validation
const result = bv.validateString('<invoice><id>1</id></invoice>');
```

---

## 23. Code Generation

**Export path:** `xml-xsd-engine/codegen`

### XSD → TypeScript interfaces

Generate TypeScript `interface` / `type` declarations directly from a compiled `SchemaModel`.

```ts
import { parseXsd }           from 'xml-xsd-engine';
import { generateTypeScript } from 'xml-xsd-engine/codegen';

const xsdSource = `
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:complexType name="AddressType">
    <xs:sequence>
      <xs:element name="street" type="xs:string"/>
      <xs:element name="city"   type="xs:string"/>
      <xs:element name="zip"    type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

  <xs:element name="person">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="name"    type="xs:string"/>
        <xs:element name="address" type="AddressType" minOccurs="0"/>
      </xs:sequence>
      <xs:attribute name="id" type="xs:integer" use="required"/>
    </xs:complexType>
  </xs:element>
</xs:schema>`;

const schema = parseXsd(xsdSource);
const ts     = generateTypeScript(schema, {
  typePrefix: 'I',      // → IAddressType, IPerson
  exportAll:  true,
  jsdoc:      true,
});

console.log(ts);
// /** XSD complexType: AddressType */
// export interface IAddressType {
//   street: string;
//   city: string;
//   zip: string;
// }
// /** XSD element: person */
// export interface IPerson {
//   /** @attribute */
//   id: number;
//   name: string;
//   address?: IAddressType;
// }
```

#### Enumeration → literal union

```ts
// xs:simpleType with xs:enumeration facets
// → TypeScript literal union type
// export type IStatus = 'active' | 'inactive' | 'pending';
```

#### Write to a file

```ts
import { writeFileSync } from 'fs';

const ts = generateTypeScript(parseXsd(xsdSource), { typePrefix: 'I' });
writeFileSync('types/generated.ts', ts, 'utf8');
```

#### All options

```ts
generateTypeScript(schema, {
  header:       '// Auto-generated — do not edit\n',
  exportAll:    true,        // add `export` modifier
  typePrefix:   'I',         // e.g. IBook
  typeSuffix:   '',          // e.g. BookDto
  useTypeAlias: false,       // false = `interface`, true = `type =`
  jsdoc:        true,        // emit JSDoc comments
  indent:       2,           // spaces per indent level
  skipBuiltins: true,        // skip xs:string etc from output
  typeMap: {                 // custom overrides
    'xs:dateTime': 'Date',   // generate Date instead of string
  },
});
```

---

### XSD → JSON Schema (Draft 7)

Generate a JSON Schema compatible with AJV, `jsonschema`, and other validators.

```ts
import { parseXsd }          from 'xml-xsd-engine';
import { generateJsonSchema } from 'xml-xsd-engine/codegen';

const schema     = parseXsd(xsdSource);
const jsonSchema = generateJsonSchema(schema, {
  id:    'https://example.com/person.json',
  title: 'Person',
});

console.log(JSON.stringify(jsonSchema, null, 2));
// {
//   "$schema": "http://json-schema.org/draft-07/schema#",
//   "$id": "https://example.com/person.json",
//   "title": "Person",
//   "type": "object",
//   "properties": {
//     "@id": { "type": "integer" },
//     "name": { "type": "string" },
//     "address": { "$ref": "#/definitions/AddressType" }
//   },
//   "required": ["@id", "name"],
//   "definitions": {
//     "AddressType": { "type": "object", "properties": { … } }
//   }
// }
```

#### All options

```ts
generateJsonSchema(schema, {
  draft:           'http://json-schema.org/draft-07/schema#',
  id:              'https://example.com/book.json',
  title:           'Book',
  useDefinitions:  true,   // emit definitions + $ref
  allowAdditional: true,   // additionalProperties for xs:any
  typeMap: {               // custom overrides
    'xs:dateTime': { type: 'string', format: 'date-time' },
  },
});
```

---

### Schema introspection (`schema.toJSON()`)

Serialize a `SchemaModel` to a plain object for documentation generators, IDE plugins, or form builders.

```ts
import { parseXsd } from 'xml-xsd-engine';

const schema = parseXsd(xsdSource);
const json   = schema.toJSON();

// Safe to JSON.stringify — no circular references
const str = JSON.stringify(json, null, 2);
```

The returned object has the shape:

```json
{
  "targetNamespace": "http://example.com/ns",
  "elementFormDefault": "unqualified",
  "attributeFormDefault": "unqualified",
  "version": "",
  "elements": { "book": { "name": "book", "typeName": null, … } },
  "simpleTypes": { "StatusType": { "kind": "simple", "facets": […], … } },
  "complexTypes": { "BookType": { "kind": "complex", … } },
  "attributes": {},
  "substitutionGroups": {}
}
```

---

## 24. Async Schema Loader

By default `parseXsd()` accepts a synchronous `SchemaLoader` for `xs:import` / `xs:include` resolution. Use `parseXsdAsync()` when you need to resolve schemas asynchronously (e.g. via `fetch()` or `fs/promises`).

### Fetch remote schemas

```ts
import { parseXsdAsync, AsyncSchemaLoader } from 'xml-xsd-engine';

const loader: AsyncSchemaLoader = async (location, namespace) => {
  const url = `https://schemas.example.com/${location}`;
  try {
    const res = await fetch(url);
    return res.ok ? res.text() : null;
  } catch {
    return null; // returning null silently skips the import
  }
};

const schema = await parseXsdAsync(mainXsdSource, loader);
```

### Async file system loader

```ts
import { parseXsdAsync } from 'xml-xsd-engine';
import { readFile }       from 'fs/promises';
import { join, dirname }  from 'path';

async function loadSchemaFromFile(xsdPath: string) {
  const baseDir   = dirname(xsdPath);
  const xsdSource = await readFile(xsdPath, 'utf8');

  return parseXsdAsync(xsdSource, async (location) =>
    readFile(join(baseDir, location), 'utf8').catch(() => null)
  );
}

const schema = await loadSchemaFromFile('./schemas/main.xsd');
```

### Mix sync and async loaders

`parseXsdAsync` accepts either a synchronous `SchemaLoader` or an async `AsyncSchemaLoader`:

```ts
// Synchronous loader — works fine with parseXsdAsync
const syncLoader = (location: string) => {
  try { return readFileSync(join(baseDir, location), 'utf8'); }
  catch { return null; }
};

const schema = await parseXsdAsync(xsdSource, syncLoader);
```

### Import cycle protection

Both `parseXsd` and `parseXsdAsync` track visited `namespace+location` pairs and skip duplicate imports automatically — no infinite loops even with circular `xs:include`.

---

## 25. Namespace Engine *(v1.4 — G35)*

`NamespaceEngine` provides centralised, scoped namespace resolution. It is used internally by the parser and validation pipeline, and is also available as a public API.

```ts
import { NamespaceEngine, NS_XSD, NS_XSI, NS_XML } from 'xml-xsd-engine';

const ns = new NamespaceEngine();

// Push a new scope (one per element open)
ns.pushScope([
  { prefix: 'xs',  uri: NS_XSD },
  { prefix: 'xsi', uri: NS_XSI },
]);

// Resolve a QName
const r = ns.resolveQName('xs:string');
// → { namespaceURI: 'http://www.w3.org/2001/XMLSchema',
//     localName: 'string', prefix: 'xs', qname: 'xs:string' }

// Null-safe variant
const r2 = ns.tryResolveQName('unknown:foo');  // null

// Build / parse internal keys
const key = NamespaceEngine.buildKey('http://example.com', 'item');
// → '{http://example.com}item'
const { ns: uri, local } = NamespaceEngine.parseKey(key);

// Pop scope when element closes
ns.popScope();
```

### Common use: namespace-aware element comparison

```ts
// Instead of comparing tagName strings:
const isXsElement = el.localName === 'element' &&
                    el.namespaceURI === NS_XSD;
```

---

## 26. Schema Compiler *(v1.4 — G27-lite)*

```ts
import { compileSchema, parseXsd } from 'xml-xsd-engine';

const schema   = parseXsd(xsdSource);
const compiled = compileSchema(schema);

// compiled.elements    — ReadonlyMap<string, ElementDeclaration>
// compiled.complexTypes — ReadonlyMap<string, ComplexTypeDefinition>
// compiled.simpleTypes — ReadonlyMap<string, SimpleTypeDefinition>
// compiled.warnings    — string[] (pre-validation diagnostics)
// compiled.compiledAt  — timestamp

// Look up a resolved element declaration
const decl = compiled.elements.get('order')
          ?? compiled.elements.get('{http://example.com}order');
```

### When to use compileSchema directly

- Building a custom validation pipeline
- Pre-warming the schema on server start
- Inspecting the resolved type tree (e.g. for tooling)

For typical validation, `validate()` and `runPipeline()` call `compileSchema` automatically.

---

## 27. Validation Pipeline *(v1.4 — G34)*

The pipeline provides fine-grained observability and control over the 7-stage validation process.

```ts
import { runPipeline, ValidationPipeline } from 'xml-xsd-engine';

// One-shot convenience function
const result = runPipeline(xmlString, schema, {
  mode:         'lax',
  recover:      true,
  profile:      true,
  parseOptions: { maxDepth: 200 },
});

console.log(result.success);    // true / false
console.log(result.totalMs);    // total wall-clock time
console.log(result.validation.errors.length);

// Per-stage breakdown
result.stages.forEach(stage => {
  console.log(`${stage.stage}: ${stage.success ? 'ok' : 'FAIL'} (${stage.durationMs}ms)`);
  stage.issues.forEach(i => console.log(`  ${i.severity}: ${i.message}`));
});
```

### Reusable pipeline (server use)

```ts
const pipeline = new ValidationPipeline(schema, { mode: 'strict' });

// Compile schema once on startup
pipeline.precompileSchema();

// Reuse across many requests — schema not recompiled
app.post('/validate', async (req, res) => {
  const result = pipeline.run(req.body);
  res.json({ valid: result.success, errors: result.validation.errors });
});

// Or from a pre-parsed DOM
const doc    = parseXml(xmlString);
const result = pipeline.runDocument(doc);
```

### Pipeline stages

| Stage | What it does |
|---|---|
| `parse` | XML → DOM (well-formedness) |
| `namespace` | Attach namespace URIs |
| `schema-compile` | `SchemaModel` → `CompiledSchema` |
| `structure-validate` | Sequence / choice / occurrence |
| `type-validate` | Per-element + per-attribute types |
| `identity-check` | `xs:key` / `xs:unique` / `xs:keyref` |
| `post-process` | IDREF resolution |

---

## 28. Strict / Lax / Recovery / Profiling *(v1.4)*

### Strict vs Lax mode (G41)

```ts
import { validate } from 'xml-xsd-engine';

// strict (default) — unknown elements are errors
const r1 = validate(doc, schema, { mode: 'strict' });

// lax — unknown elements become warnings (VALID_UNKNOWN_ELEMENT)
const r2 = validate(doc, schema, { mode: 'lax' });

// Check for unknown-element warnings specifically
r2.byCategory('structure')
  .filter(i => i.code === 'VALID_UNKNOWN_ELEMENT')
  .forEach(i => console.log('Unknown element at', i.path));
```

### Error recovery mode (G43)

By default, validation stops after the first structural error in a sequence.
With `recover: true`, all stages run and all errors are collected:

```ts
const result = validate(doc, schema, {
  recover: true,  // collect everything
  mode:    'lax', // also tolerate unknown elements
});

console.log(`Found ${result.errors.length} errors and ${result.warnings.length} warnings`);
```

This is required for **IDE integrations** and **diagnostic tools** that need to show all problems at once.

### Profiling hooks (G44)

```ts
import { ValidationEngine } from 'xml-xsd-engine';

const events: ProfileEvent[] = [];

const engine = new ValidationEngine(schema, {
  profile:   true,
  onProfile: e => events.push(e),
});

engine.validate(doc);

// Sort by slowest element
events.sort((a, b) => b.durationMs - a.durationMs);
console.log('Slowest element:', events[0]);

// Or read from instance after validate()
console.log(engine.lastProfileEvents);
```

---

## 29. Compiled XPath *(v1.4 — G22)*

### Pre-compile for reuse

```ts
import { compileXPath, xpath, clearXPathCache, xpathCacheSize } from 'xml-xsd-engine';

// Compile once — reuse across many documents
const findEnglishBooks = compileXPath('//book[@lang="en"]');

for (const doc of documents) {
  const books = findEnglishBooks.evaluate(doc.root!);
  const first = findEnglishBooks.first(doc.root!);
  const count = findEnglishBooks.count(doc.root!);
  const title = findEnglishBooks.string(doc.root!);  // text of first match
}
```

### LRU cache management

```ts
// xpath() automatically caches expressions (512 entry LRU)
xpath(doc, '//book');  // compiled and cached
xpath(doc, '//book');  // served from cache

console.log(xpathCacheSize());  // 1

// Clear cache if needed (e.g. memory pressure)
clearXPathCache();
console.log(xpathCacheSize());  // 0
```

### text() node support (v1.4 fix)

`xpath()` now correctly returns `XmlNode[]` including text nodes:

```ts
// Returns XmlText nodes, not just elements
const textNodes = xpath(doc, '//p/text()');
```

---

## 30. xml-format CLI *(v1.4 — G18)*

```bash
# Basic pretty-print (2-space indent, to stdout)
xml-format file.xml

# 4-space indent, write to output file
xml-format file.xml --indent 4 --output formatted.xml

# Omit the XML declaration
xml-format file.xml --no-declaration

# Emit <tag></tag> instead of <tag/>
xml-format file.xml --no-self-close

# Sort attributes alphabetically (useful for diffs)
xml-format file.xml --sort-attributes

# CI check — exits 1 if file would change, no output
xml-format file.xml --check

# Pipe from stdin
cat unformatted.xml | xml-format -

# Format all XML files in a directory
for f in data/*.xml; do xml-format "$f" --output "$f"; done
```

### Check mode in CI

```yaml
# .github/workflows/ci.yml
- name: Check XML formatting
  run: xml-format docs/*.xml --check
```

---

## 31. Structured Error Categories *(v1.4 — G37)*

Every `XmlError` and every `ValidationIssue` now carries a stable `category` and `code`.

### Routing errors by category

```ts
import { XmlError, validate } from 'xml-xsd-engine';

const result = validate(doc, schema, { recover: true });

// Filter by category
const typeErrors      = result.byCategory('type');
const structureErrors = result.byCategory('structure');
const identityErrors  = result.byCategory('identity');

// Summary broken down by category
console.log(result.summary);
// {
//   errorCount: 3, warningCount: 1, infoCount: 0,
//   byCategory: { type: 2, structure: 1 }
// }
```

### Using ERROR_CATEGORY map

```ts
import { ERROR_CATEGORY } from 'xml-xsd-engine';

// Look up any code's category without an instance
const cat = ERROR_CATEGORY['VALID_PATTERN_MISMATCH'];  // 'type'
```

### Error code stability guarantee

```ts
// Safe to use error codes in switch statements — stable across minor/patch:
switch (issue.code) {
  case 'VALID_WRONG_TYPE':        handleTypeError(issue);     break;
  case 'VALID_MISSING_ELEMENT':   handleMissingElement(issue); break;
  case 'VALID_UNKNOWN_ELEMENT':   handleUnknown(issue);        break;
  case 'VALID_IDENTITY_CONSTRAINT': handleIdentity(issue);    break;
  default:                        handleOther(issue);
}
```

### Pipeline stage tagging

```ts
// Issues produced by runPipeline() carry a stage field
const pipelineResult = runPipeline(xmlString, schema);
pipelineResult.validation.issues.forEach(i => {
  console.log(`[${i.stage ?? 'unknown'}] ${i.code}: ${i.message}`);
  // e.g. "[type-validate] VALID_WRONG_TYPE: Expected xs:integer, got 'abc'"
});
```

---

## 32. SAX Instrumentation *(v1.5)*

The SAX instrumentation layer emits `SaxValidationHint` objects for every element boundary — without building a DOM. Use it as the foundation for streaming validators, linters, or indexers.

### Quick start — `collectSaxHints`

```ts
import { collectSaxHints } from 'xml-xsd-engine';

const hints = collectSaxHints(xml);
// hints is SaxValidationHint[]

for (const h of hints.filter(h => h.kind === 'open')) {
  console.log(`${h.path} (depth=${h.depth}) ns=${h.namespaceURI}`);
}
```

#
## PSVI — Typed Validation Output *(v1.7.0)*

`xml-xsd-engine` can annotate each validated element with its resolved XSD type, normalised value, and validity status (the *Post-Schema-Validation Infoset*).

```ts
import { parseXml, parseXsd, validate } from 'xml-xsd-engine';

const xml = `<order><qty>5</qty><price>19.99</price></order>`;
const xsd = `
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="order">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="qty"   type="xs:integer"/>
        <xs:element name="price" type="xs:decimal"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>`;

const doc    = parseXml(xml);
const schema = parseXsd(xsd);
const result = validate(doc, schema, { psvi: true });

const qtyEl   = doc.root!.children[0] as XmlElement;
const priceEl = doc.root!.children[1] as XmlElement;

console.log(result.psvi?.get(qtyEl)?.xsdType);          // 'xs:integer'
console.log(result.psvi?.get(qtyEl)?.normalizedValue);  // '5'
console.log(result.psvi?.get(priceEl)?.xsdType);        // 'xs:decimal'
```

PSVI is off by default (`psvi: false`). Enable it only when you need the type information — it adds a small allocation overhead per element.

---

## Canonical XML *(v1.7.0)*

Use `canonicalize()` to produce a byte-for-byte reproducible serialisation of a document or element subtree. Required for XML Digital Signatures.

```ts
import { parseXml, canonicalize } from 'xml-xsd-engine';

const doc  = parseXml('<root xmlns="http://example.com"><b z="2" a="1"/></root>');

// Inclusive C14N 1.0 (default)
const c14n = canonicalize(doc);
// -> <root xmlns="http://example.com"><b a="1" z="2"></b></root>

// Exclusive C14N
const exc = canonicalize(doc, { mode: 'exclusive' });

// Keep comments
const withCmt = canonicalize(doc, { withComments: true });

// Subtree (e.g. for ds:SignedInfo)
import { XmlElement } from 'xml-xsd-engine';
const subtree = canonicalize(doc.root!.children[0] as XmlElement);
```

---

## Streaming consumer

Implement `SaxEventConsumer` to process hints without buffering the full document:

```ts
import { createInstrumentedSax, SaxEventConsumer, SaxValidationHint }
  from 'xml-xsd-engine';

class DepthProfiler implements SaxEventConsumer {
  private counts: Record<number, number> = {};

  onHint(hint: SaxValidationHint): void {
    if (hint.kind === 'open') {
      this.counts[hint.depth] = (this.counts[hint.depth] ?? 0) + 1;
    }
  }

  onEnd(hadError: boolean): void {
    console.log('Profile:', this.counts, 'error:', hadError);
  }
}

createInstrumentedSax(xmlString, new DepthProfiler()).parse();
```

### Attaching to an existing SaxParser

```ts
import { SaxParser } from 'xml-xsd-engine';
import { instrumentSax } from 'xml-xsd-engine';

const parser = new SaxParser(xmlString);

// Attach other handlers first if needed
parser.on('text', e => processText(e.value));

// Attach instrumentation
instrumentSax(parser, {
  onHint: hint => console.log(hint.kind, hint.path),
  onEnd:  hadError => cleanup(hadError),
});

parser.parse();
```

### Path tracking

The `hint.path` field provides an XPath-like location:

| XML | `hint.path` |
|---|---|
| root element | `/root` |
| first `<book>` | `/root/book` |
| second `<book>` | `/root/book[2]` |
| `<title>` inside second book | `/root/book[2]/title` |

Sibling index is only appended when > 1, keeping paths readable.

### Namespace tracking

```ts
const hints = collectSaxHints('<ns:root xmlns:ns="http://example.com"><ns:child/></ns:root>');
const childHint = hints.find(h => h.kind === 'open' && h.localName === 'child')!;

console.log(childHint.namespaceURI);         // 'http://example.com'
console.log(childHint.namespaceStack);
// [{ prefix: 'ns', namespaceURI: 'http://example.com' }]
```

---

## 33. Schema Preflight *(v1.5)*

Catch XSD schema design errors **at schema load time** — before any document is validated.

### `validateSchema` — from XSD source

```ts
import { validateSchema } from 'xml-xsd-engine';

const xsd = `<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="person" type="UndefinedType"/>
</xs:schema>`;

const issues = validateSchema(xsd);
// issues = [{ severity: 'error', code: 'SCHEMA_UNDEFINED_REF',
//             message: '...UndefinedType...', component: 'person' }]
```

### `checkSchema` — from compiled model

```ts
import { parseXsd, checkSchema } from 'xml-xsd-engine';

const schema = parseXsd(xsdSource);
const issues = checkSchema(schema);
```

### Fail-fast on schema errors

```ts
import { validateSchema } from 'xml-xsd-engine';

function loadSchema(xsdSource: string) {
  const issues = validateSchema(xsdSource);
  const errors = issues.filter(i => i.severity === 'error');
  if (errors.length > 0) {
    throw new Error(
      `Schema has ${errors.length} error(s):\n` +
      errors.map(e => `  [${e.code}] ${e.message}`).join('\n')
    );
  }
  return parseXsd(xsdSource);
}
```

### Detect keyref linkage errors

```ts
import { checkSchema, SchemaModel } from 'xml-xsd-engine';

// If your schema has a keyref pointing to 'productKey' but that key
// doesn't exist yet (e.g. schema is under construction), checkSchema
// will tell you immediately:
const issues = checkSchema(model);
const keyrefErrors = issues.filter(i => i.code === 'SCHEMA_KEYREF_UNRESOLVED');
console.log(keyrefErrors.map(e => e.message));
```

---

## 34. Identity Constraint Engine *(v1.5)*

Evaluate `xs:key` and `xs:unique` constraints on a fully parsed document.

> The `ValidationEngine` calls this internally. Use the class directly only when you need lower-level control — e.g. running constraints on a document you already have parsed without re-running the full structural validation.

### Standalone usage

```ts
import { parseXml, parseXsd, validate,
         IdentityConstraintEngine, ValidationResult }
  from 'xml-xsd-engine';

const schema = parseXsd(`<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="catalog">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="product" maxOccurs="unbounded">
          <xs:complexType>
            <xs:attribute name="id" type="xs:string" use="required"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
    <xs:key name="productKey">
      <xs:selector xpath="product"/>
      <xs:field    xpath="@id"/>
    </xs:key>
  </xs:element>
</xs:schema>`);

const doc = parseXml(`<catalog>
  <product id="P001"/>
  <product id="P002"/>
  <product id="P001"/>
</catalog>`);

const result = new ValidationResult();
new IdentityConstraintEngine(schema).evaluate(doc, result);

console.log(result.valid);   // false
console.log(result.errors[0].code);     // 'VALID_IDENTITY_CONSTRAINT'
console.log(result.errors[0].category); // 'identity'
console.log(result.errors[0].message);
// 'xs:key "productKey" violation: duplicate value [P001] ...'
```

### Composite multi-field keys

```ts
// A key composed of two fields — only the combination must be unique
schema.identityConstraints.set('orders', [{
  kind:     'key',
  name:     'orderLineKey',
  selector: 'order',
  fields:   ['@orderId', '@lineId'],  // composite key
}]);
```

### Supported XPath selector patterns

```ts
// Direct children
{ selector: 'product',       fields: ['@id'] }

// Relative path
{ selector: './region/item', fields: ['@code'] }

// All descendants
{ selector: './/item',       fields: ['@id'] }

// Wildcard
{ selector: '*',             fields: ['.'] }

// Field: text content of child element
{ selector: 'item',          fields: ['name'] }

// Field: text content of self
{ selector: 'item',          fields: ['.'] }
```

---

## 35. XPath 2.0 Functions *(v1.5 — G6)*

All 22 XPath 2.0 functions are callable directly as pure TypeScript utilities — no XPath engine needed.

### String functions

```ts
import {
  upperCase, lowerCase, matchesXPath, replaceXPath, tokenize,
  normalizeUnicode, encodeForUri, iriToUri, substringBefore, substringAfter,
} from 'xml-xsd-engine';

upperCase('hello');                          // 'HELLO'
lowerCase('WORLD');                          // 'world'
matchesXPath('foo123', '\\d+');             // true
matchesXPath('FOO', 'foo', 'i');            // true  (case-insensitive flag)
replaceXPath('2024-04-15', '(\\d+)-(\\d+)-(\\d+)', '$3/$2/$1'); // '15/04/2024'
tokenize('a  b  c', '\\s+');               // ['a', 'b', 'c']
normalizeUnicode('caf\u0065\u0301');        // 'café' (NFC)
encodeForUri('hello world');               // 'hello%20world'
substringBefore('hello/world', '/');       // 'hello'
substringAfter('hello/world', '/');        // 'world'
```

### Sequence / aggregate functions

```ts
import {
  distinctValues, isEmpty, exists, stringJoin,
  sum, avg, minValue, maxValue,
  insertBefore, remove, subsequence, reverseSeq, indexOf,
} from 'xml-xsd-engine';

distinctValues([1, 2, 1, 3]);             // [1, 2, 3]
isEmpty([]);                               // true
exists([1]);                               // true
stringJoin(['a', 'b', 'c'], '-');         // 'a-b-c'
sum([1, 2, 3]);                            // 6
avg([1, 2, 3]);                            // 2
minValue([3, 1, 2]);                       // 1
maxValue([3, 1, 2]);                       // 3
insertBefore([1, 2, 3], 2, [10]);         // [1, 10, 2, 3] (1-based position)
remove([1, 2, 3], 2);                      // [1, 3] (1-based position)
subsequence([1, 2, 3, 4], 2, 2);         // [2, 3] (1-based, length 2)
reverseSeq([1, 2, 3]);                    // [3, 2, 1]
indexOf([10, 20, 10], 10);               // [1, 3] (1-based positions)
```

---

## 36. SHA-256 Utilities *(v1.5 — G40)*

Pure-TypeScript SHA-256 — zero runtime dependencies; works in Node.js, browsers, Deno, and Bun.

```ts
import { sha256Hex, sha256Short } from 'xml-xsd-engine';

sha256Hex('hello');
// → '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'  (64 chars)

sha256Short('hello');         // '2cf24dba5fb0a30e'  (first 16 chars, default)
sha256Short('hello', 8);      // '2cf24dba'           (first 8 chars)
```

### Using content-hash keying with SchemaCache

```ts
import { SchemaCache } from 'xml-xsd-engine';

const cache = new SchemaCache({ trackContentHash: true });  // default

// Schema is keyed as 'my-schema@<sha256short(xsdString)>'
const schema = cache.getOrParse('my-schema', xsdString);

// If the XSD file changes, old entry is automatically bypassed — new hash = new entry
const schema2 = cache.getOrParse('my-schema', updatedXsdString);

// Watch-mode: remove all stale entries for a path whose source has changed
await cache.invalidateByContent('./schemas/order.xsd', newXsdSource);

// Inspect the stored hash
const hash = cache.getContentHash('my-schema');   // '2cf24dba5fb0a30e' | null

// Get dependency map for a cache entry
const deps = cache.getDependencies('my-schema');  // Map<importPath, hash>

// Opt out: revert to legacy plain-key mode (no hash suffix)
const legacyCache = new SchemaCache({ trackContentHash: false });
```

---

## 37. `xml:id` / `xml:base` Processing *(v1.5 — G10)*

### `xml:id` — unique element IDs

```ts
import { parseXml } from 'xml-xsd-engine';

const doc = parseXml(`
  <catalog>
    <book xml:id="b1"><title>Dune</title></book>
    <book xml:id="b2"><title>1984</title></book>
  </catalog>
`);

// O(1) lookup by xml:id
const b1 = doc.xmlIds.get('b1');   // → the first <book> element
b1?.xmlId;                          // 'b1'

// Duplicate xml:id throws PARSE_DUPLICATE_ATTRIBUTE
// parseXml('<root><a xml:id="x"/><b xml:id="x"/></root>'); // throws!
```

### `xml:base` — effective base URI

```ts
const doc = parseXml(`
  <root xml:base="http://example.com/docs/">
    <section xml:base="chapter1/">
      <img src="fig.png"/>
    </section>
  </root>
`);

const section = doc.root!.childElements[0];
section.xmlBase;  // 'http://example.com/docs/chapter1/'
```

### `resolveXmlBase` — RFC 3986 URI resolution

```ts
import { resolveXmlBase } from 'xml-xsd-engine';

resolveXmlBase('http://example.com/docs/a.xml', '../images/logo.png');
// → 'http://example.com/images/logo.png'

resolveXmlBase('http://example.com/base/', 'page.html');
// → 'http://example.com/base/page.html'

// Falls back to string joining if URL constructor is unavailable
resolveXmlBase('/docs/', 'page.html');
// → '/docs/page.html'
```

---

## 38. DTD Internal Entity Expansion *(v1.5 — G11)*

General entities declared in an internal DTD subset are automatically parsed and expanded.

### Basic usage

```ts
import { parseXml } from 'xml-xsd-engine';

const xml = `<?xml version="1.0"?>
<!DOCTYPE config [
  <!ENTITY appName "MyApp">
  <!ENTITY version "2.0">
]>
<config>
  <name>&appName;</name>
  <ver>&version;</ver>
</config>`;

const doc = parseXml(xml);
doc.root!.find('name')?.textContent;   // 'MyApp'
doc.root!.find('ver')?.textContent;    // '2.0'
```

### Disabling DTD entity expansion

```ts
// When set to false, &greeting; remains un-expanded and causes LEX_INVALID_ENTITY
const doc = parseXml(xml, { expandDtdEntities: false });
```

### Caller entityMap takes precedence

```ts
// Override DTD-declared entities with caller-supplied values
const doc = parseXml(xml, {
  entityMap: { appName: 'OverriddenName' }  // caller wins
});
// doc.root.find('name').textContent → 'OverriddenName'
```

### Security guarantees

- External `SYSTEM`/`PUBLIC` entities are **silently skipped** (XXE prevention — unconditional)
- Parameter entities (`%`) are ignored
- Entity values exceeding `maxTextLength` are skipped
- Numeric character references (`&#xHH;`, `&#nnn;`) continue to work alongside DTD entities

---

## 39. Platform Entry Points — Browser / Deno / Bun *(v1.5 — G9/G25)*

### Browser (zero Node.js API dependencies)

```ts
import {
  parseXml, parseXsd, validate,
  xpath, serializeXml,
  sha256Hex, resolveXmlBase,
  AssertionEvaluator, IdentityConstraintEngine,
  collectSaxHints,
} from 'xml-xsd-engine/browser';

// Works in browsers, Cloudflare Workers, Vercel Edge, Deno, Bun
const doc    = parseXml('<root><item>hello</item></root>');
const schema = parseXsd(xsdString);
const result = validate(doc, schema);
```

Node-only functions (`readXmlFile`, `validateFiles`, `parseXmlStream`) throw descriptive errors when called in browser context:

```ts
import { readXmlFile } from 'xml-xsd-engine/browser';
readXmlFile('./x.xml');
// → Error: readXmlFile is not available in browser context. Use fetch() instead.
```

### Deno entry point

```ts
import {
  parseXml, parseXsd, validate,          // full API
  denoReadXmlFile, denoReadXsdFile,       // Deno-native file I/O
  denoSchemaLoader,                        // SchemaLoader backed by Deno.readTextFileSync
} from 'xml-xsd-engine/deno';

const doc    = await denoReadXmlFile('./document.xml');
const schema = await denoReadXsdFile('./schema.xsd');
const result = validate(doc, schema);
```

`deno.json` import-map is shipped with the package for path resolution in Deno workspaces.

### Bun entry point

```ts
import {
  parseXml, parseXsd, validate,
  bunReadXmlFile, bunReadXsdFile,  // Bun-native file I/O (Bun.file().text())
  bunSchemaLoader,                  // SchemaLoader backed by Node.js fs (Bun-compatible)
} from 'xml-xsd-engine/bun';

const doc    = await bunReadXmlFile('./document.xml');
const schema = await bunReadXsdFile('./schema.xsd');
const result = validate(doc, schema);
```

### Tree-shaking (`sideEffects: false`)

`"sideEffects": false` is set in `package.json`. All sub-path exports are verified to be side-effect-free, so bundlers (webpack, Rollup, esbuild, Vite) can eliminate unused exports for optimal bundle size.

---

## 40. `xs:assert` Assertion Evaluator *(v1.5 — G24)*

`xs:assert` inside `xs:complexType` is automatically parsed and evaluated during validation. Failures are reported as `VALID_ASSERTION_FAILED`.

### In XSD

```xml
<xs:complexType name="RangeType">
  <xs:sequence>
    <xs:element name="min" type="xs:integer"/>
    <xs:element name="max" type="xs:integer"/>
  </xs:sequence>
  <xs:assert test="@min &lt;= @max" message="min must not exceed max"/>
  <xs:assert test="string-length(@label) > 0" message="label must not be empty"/>
</xs:complexType>
```

Validation automatically evaluates these assertions:

```ts
const result = validate(doc, schema);
// If assertion fails: result.errors[0].code === 'VALID_ASSERTION_FAILED'
```

### Programmatic use (`AssertionEvaluator`)

```ts
import { AssertionEvaluator } from 'xml-xsd-engine';

const ev = new AssertionEvaluator(element);

// Attribute presence
ev.evaluate('@attr');                         // true if attr exists

// Attribute equality / comparison
ev.evaluate("@status = 'active'");           // attribute equals
ev.evaluate('@min &lt;= @max');              // numeric comparison (XML entities decoded)
ev.evaluate('@count > 0');                   // greater-than comparison
ev.evaluate('@val >= @min');                 // compound attribute comparison

// Logical operators
ev.evaluate('@a and @b');                    // both present
ev.evaluate('@a or @b');                     // either present
ev.evaluate('not(@forbidden)');              // negation

// String functions
ev.evaluate('string-length(@name) > 0');    // non-empty string
ev.evaluate("contains(@type, 'sub')");      // string contains
ev.evaluate("starts-with(@code, 'A')");     // string prefix
```

Unknown or arithmetic expressions pass through as `true` to avoid false-positive errors.

---

## 41. `xs:list` / `xs:union` Hardening *(v1.5 — G12)*

### `xs:list` edge cases

```ts
// Empty list — an empty string is a valid zero-item xs:list (was broken in v1.4)
validate(parseXml('<tags/>'), parseXsd(`
  <xs:schema ...>
    <xs:simpleType name="TagList">
      <xs:list itemType="xs:token"/>
    </xs:simpleType>
    <xs:element name="tags" type="TagList"/>
  </xs:schema>`)).valid;  // true — empty list

// Length facets now applied to ITEM COUNT (not string length)
// A list type requiring exactly 3 items:
// <xs:restriction base="TagList"><xs:length value="3"/></xs:restriction>
// validate('one two three') → valid
// validate('one two')       → invalid (2 items, expected 3)
```

### Anonymous `itemTypeDef`

```ts
// xs:list with an inline anonymous simpleType — stored on SimpleTypeDefinition.itemTypeDef
const xsd = `<xs:schema ...>
  <xs:simpleType name="CodeList">
    <xs:list>
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value="[A-Z]{2,4}"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:list>
  </xs:simpleType>
</xs:schema>`;

// Items 'US', 'DE', 'FR' are each validated against the inline pattern
```

### `xs:union` with anonymous `memberTypeDefs`

```ts
// xs:union with inline anonymous simpleType children — stored on SimpleTypeDefinition.memberTypeDefs
const xsd = `<xs:schema ...>
  <xs:simpleType name="ValueOrCode">
    <xs:union>
      <xs:simpleType>
        <xs:restriction base="xs:integer"/>
      </xs:simpleType>
      <xs:simpleType>
        <xs:restriction base="xs:string">
          <xs:pattern value="[A-Z]+"/>
        </xs:restriction>
      </xs:simpleType>
    </xs:union>
  </xs:simpleType>
</xs:schema>`;

// '42' → valid (matches first anonymous member: xs:integer)
// 'ABC' → valid (matches second anonymous member: uppercase string pattern)
// '123abc' → invalid (matches neither)
```

---

## 42. Source-Mapped Error Locations *(v1.6 — G2)*

In v1.6.0, every `ValidationIssue` produced by `ValidationEngine` now carries `line` and `col` fields, pointing to the offending element's opening tag in the source document.

```ts
import { validate, parseXml, parseXsd } from 'xml-xsd-engine';

const schema = parseXsd(`<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="name" type="xs:string"/>
        <xs:element name="age"  type="xs:integer"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>`);

const doc = parseXml(`<root>
  <name>Alice</name>
  <age>not-a-number</age>
</root>`);

const result = validate(doc, schema);
for (const err of result.errors) {
  // err.line and err.col are now always populated for all error kinds
  console.log(`[${err.line}:${err.col}] ${err.code}: ${err.message}`);
}
// → [3:3] VALID_WRONG_TYPE: "not-a-number" is not a valid xs:integer
```

This enables IDE integrations, code frames, and CI reports to pinpoint exact source locations without additional post-processing.

---

## 43. XML Diff *(v1.6 — G16)*

`diffXml` computes the structural difference between two parsed XML documents and returns an array of `XmlChange` objects in document order.

```ts
import { diffXml, parseXml } from 'xml-xsd-engine';

const v1 = parseXml(`<catalog>
  <book id="1"><title>Dune</title><author>Herbert</author></book>
  <book id="2"><title>Foundation</title></book>
</catalog>`);

const v2 = parseXml(`<catalog>
  <book id="1"><title>Dune Messiah</title><author>Herbert</author></book>
  <book id="3"><title>Neuromancer</title></book>
</catalog>`);

const changes = diffXml(v1, v2);
for (const c of changes) {
  console.log(c.type, c.path, c.from, '→', c.to);
}
// modified  /catalog/book/@id   "1" → "1"  (unchanged — no change)
// modified  /catalog/book/title  "Dune" → "Dune Messiah"
// modified  /catalog/book[2]/@id "2" → "3"
// modified  /catalog/book[2]/title "Foundation" → "Neuromancer"
```

### Change types

| Type | Meaning |
|---|---|
| `'added'` | Node present in `doc2` but not `doc1` |
| `'removed'` | Node present in `doc1` but not `doc2` |
| `'modified'` | Text content or attribute value changed |
| `'type-change'` | Element tag name changed (entire subtree replaced) |

### Options

```ts
const changes = diffXml(doc1, doc2, {
  ignoreWhitespace: true,   // default — ignore whitespace-only text
  ignoreComments:   true,   // default — ignore comment nodes
  ignorePI:         true,   // default — ignore processing instructions
});
```

### Use cases

- Audit trail for XML configuration changes
- Test assertions: `expect(diffXml(expected, actual)).toHaveLength(0)`
- Migration report: `diffXml(oldSchema.toXml(), newSchema.toXml())`

---

## 44. Schema Inference *(v1.6 — G17)*

`inferSchema` observes one or more `XmlDocument` instances and produces a draft `SchemaModel` plus a ready-to-use XSD string. Useful for bootstrapping schemas from existing XML data.

```ts
import { inferSchema, parseXml } from 'xml-xsd-engine';

// Feed sample documents
const samples = [
  parseXml('<products><item sku="A1" price="9.99"><name>Widget</name></item></products>'),
  parseXml('<products><item sku="B2" price="4.50"><name>Gadget</name><weight>0.3</weight></item></products>'),
  parseXml('<products><item sku="C3"><name>Dongle</name></item></products>'),
];

const inferred = inferSchema(samples, {
  targetNamespace:        'http://example.com/products',
  targetNamespacePrefix:  'prod',
  inferSimpleTypes:       true,  // detect xs:integer, xs:decimal, etc.
  inferRequired:          true,  // attrs present in every sample → required
});

// Get the XSD string
const xsdString = inferred.toXsdString();
console.log(xsdString);
/*
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="http://example.com/products"
           xmlns:prod="http://example.com/products">
  <xs:element name="products">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="item" minOccurs="0" maxOccurs="unbounded">
          ...
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
*/

// Use the inferred SchemaModel directly
const model = inferred.model;
console.log(model.elements.size);  // number of inferred top-level elements
```

### Type inference rules

| Value pattern | Inferred type |
|---|---|
| `"true"` / `"false"` | `xs:boolean` |
| Whole number string | `xs:integer` |
| Decimal number string | `xs:decimal` |
| ISO 8601 datetime | `xs:dateTime` |
| ISO 8601 date | `xs:date` |
| Anything else | `xs:string` |

### Occurrence inference

- `minOccurs` = minimum occurrences observed across all documents (0 if absent in any)
- `maxOccurs` = maximum occurrences observed (`unbounded` if > 1)

### Refining the inferred schema

The generated XSD is a starting point — always review and tighten it:

```ts
const xsd = inferred.toXsdString();
// 1. Add facets (patterns, enumerations, length constraints)
// 2. Tighten occurrence constraints
// 3. Add identity constraints (xs:key, xs:unique)
// 4. Run validateSchema(xsd) to catch any issues
```

---

## 45. Fragment & Subtree Validation *(v1.6 — G45)*

Two new functions allow validating XML fragments and subtrees without requiring a full document parse.

### `validateFragment`

Validates a raw XML string that may lack an XML declaration. Parse errors are returned as issues rather than thrown.

```ts
import { validateFragment, parseXsd } from 'xml-xsd-engine';

const schema = parseXsd(`<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:complexType name="AddressType">
    <xs:sequence>
      <xs:element name="street" type="xs:string"/>
      <xs:element name="city"   type="xs:string"/>
      <xs:element name="zip"    type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="address" type="AddressType"/>
</xs:schema>`);

// Validate a fragment — works even without XML declaration
const result = validateFragment(
  '<address><street>123 Main St</street><city>Springfield</city><zip>12345</zip></address>',
  schema,
);
console.log(result.valid);  // true

// Validate as a specific type using rootType
const result2 = validateFragment(
  '<contact><street>456 Oak Ave</street><city>Portland</city></contact>',
  schema,
  { rootType: 'AddressType' },  // validate as AddressType regardless of element name
);
```

### `validateSubtree`

Validates a pre-parsed `XmlElement` — useful when extracting parts of a larger document for per-record validation.

```ts
import { parseXml, validateSubtree, parseXsd } from 'xml-xsd-engine';

const bigDoc = parseXml(`<orders>
  <order id="1"><item>Widget</item><qty>3</qty></order>
  <order id="2"><item>Gadget</item><qty>-1</qty></order>
</orders>`);

const schema = parseXsd(orderSchema);

// Validate each order independently
for (const order of bigDoc.root!.childElements) {
  const result = validateSubtree(order, schema);
  if (!result.valid) {
    console.log(`Order ${order.getAttribute('id')} invalid:`, result.errors[0].message);
  }
}
```

### `ValidationEngine.validateSubtree` (instance method)

When validating many subtrees with the same schema/options, reuse the engine instance:

```ts
import { ValidationEngine } from 'xml-xsd-engine';

const engine = new ValidationEngine(schema, { mode: 'strict', recover: true });

for (const elem of doc.root!.childElements) {
  const result = engine.validateSubtree(elem);
  if (!result.valid) console.error(result.errors);
}
```

---

## 46. Encoding Declaration *(v1.6 — G19)*

When your application has already decoded bytes to a string (e.g. via `TextDecoder`) but still needs the original encoding name stored on the parsed document, use `ParseOptions.encoding`:

```ts
import { parseXml } from 'xml-xsd-engine';

// Suppose you decoded a Latin-1 file yourself
const rawBytes = fs.readFileSync('./legacy.xml');
const decoded = new TextDecoder('iso-8859-1').decode(rawBytes);

const doc = parseXml(decoded, { encoding: 'ISO-8859-1' });
console.log(doc.encoding);   // 'ISO-8859-1'
console.log(doc.version);    // '1.0'
```

Without `ParseOptions.encoding`, `doc.encoding` is set from the `encoding="..."` attribute in the XML declaration (or `''` if absent). With `ParseOptions.encoding`, the provided value always wins — useful for:

- Preserving encoding metadata in document round-trips
- Tooling that reports encoding alongside validation errors
- Migrating legacy ISO-8859-1 / Windows-1252 XML files

