libxml-rs
Phase 11: Historical matrix — semantic epochs for libxml2/libxslt behavior.
Custodial native-Rust reimplementation of the libxml2 + libxslt ecosystem: a forensic reconstruction of observable behavior, implemented in native Rust, with C ABI compatibility for drop-in replacement.
This is not an XML crate. This is not an XSLT crate. This is not a wrapper. This is a custodial forensic archive and native-Rust reimplementation of the complete observable behavior of libxml2 and libxslt across their historical lifetimes.
Current Status: Phase 11 — Historical matrix (§85)
Phase 11 delivers the cross-version archaeology of §41 (historical oracle matrix), §42 (version fingerprints), §51 (multi-version triangulation). Per §85:
Deliverable: The project can explain how current behavior came to exist.
A configurable oracle matrix (oracle/historical/) builds 12 historical libxml2
releases (2.7.8 → 2.15.0) plus 5 libxslt releases (1.1.26 → 1.1.42) from the archaeology
git clones and runs a 28-case behavioral corpus against every oracle and the system
2.15.3/1.1.45 binaries, capturing byte-exact stdout/stderr/exit and per-version sha256
fingerprints:
oracle/historical/build.sh— era-tolerant oracle builder (era tag spellings, autotools modernizations,--without-threadsfor modern glibc)oracle/historical/run_matrix.sh— matrix runner + per-case epoch grouping;results/matrix.jsonholds version→case→sha256atlas/SEMANTIC_EPOCHS.md— the epoch map: 10 behavioral epochs (E-001…E-008 + exit-code epochs) correlated with the exact upstream commits/NEWS that created themcourts/suites/historical/HIST-EPOCH-*.json— §43 casefiles;courts/receipts/historical-matrix-2026-08-29.json— §44 receipt
Headline findings (all correlated to upstream commits in the atlas):
xmllint --xpathnode-set output gained newline separators in 2.9.10 (commitda35eeae, an upstream-documented breaking change)- The 2.9.10 parser regression (
EndTag: '</' not found) was fixed in 2.9.11 (de5b624f); the second parse-error diagnostic was dropped entirely in 2.12 - A 2.13.0 cluster: parser/validation exit codes reworked (1→4, 4→3), entity-in-attribute errors reported twice, entity-decl debug nodes became
TEXT compact(8d04f0ee) - 2.15.0:
--htmldumps became single-line (newline writes removed fromHTMLtree.c);--validwith no DTD returns exit 0 - libxslt core transform output is a stable epoch — byte-identical from 1.1.26 (2009) to 1.1.45
Phase 10 (historical)
Phase 10 delivered the xmllint and xmlcatalog command-line tools
(§36):
Both tools are faithful native-Rust ports of the upstream programs, verified byte-for-byte against the system libxml2 2.15.3 binaries:
xmllintCLI —--debug,--copy,--format,--valid/--postvalid,--dtdvalid,--xpath/--xpath0,--xinclude,--html/--xmlout,--noent,--no-compact,--encode,--recover,--dropdtd,--pedantic,--noout,--quiet,--nonet,--huge, … with upstream exit codes (0/1/3/4) and the upstreamfile:line: parser error : MSG+ source-line/caret diagnosticsxmlcatalogCLI —--create,--add,--del,--resolve(system/public/URI),--noout,--shell(interactiveresolve/system/public/add/del/dump/help/exit), SGML→XML catalog conversion, upstream exit codes- Compact text nodes — the parser reproduces libxml2's
XML_PARSE_COMPACTinline text storage (≤15 bytes in the node struct), so--debugdumps showTEXT compactexactly like the oracle; merged/entity-interrupted text is correctly non-compact - DTD validation diagnostics —
--validoutput (messages, caret placement, exit codes) matches the oracle for both no-DTD and declaration errors - Entity expansion —
--noentre-parses declared-entity content through the input stack (nested references and markup entities included), matching upstream trees - HTML serialization — meta-charset insertion, upstream formatting rules (p/pre/param never formatted, single-child and inline elements inline), HTML document headers
- 1110 passing tests:
cargo test --lib— 0 failures - Differential oracle parity: a 44-case CLI suite (
target/difftest_summary.sh) is byte-identical to the system tools (stdout + stderr + exit codes), plus a 30+ case edge corpus (entities, DTDs, HTML, compact/no-compact, debug dumps, XPath)
Phase 9 (historical)
Phase 9 delivered the complete EXSLT module set (§35) and the
xsltproc command-line tool (§36):
All seven EXSLT modules are implemented natively and registered through the
process-wide EXSLT registry (exsltRegisterAll, mirroring upstream):
exsl:Common —exsl:node-set(with real result-tree-fragment support),exsl:object-typemath:—math:max,math:min,math:sin,math:cos,math:tan,math:constant,math:power,math:sqrt,math:log,math:random, …set:—set:difference,set:intersection,set:distinct,set:has-same-node,set:leading,set:trailingstr:—str:tokenize,str:padding,str:concat,str:split,str:replace,str:align,str:encode-uri,str:decode-uridyn:—dyn:evaluate,dyn:element,dyn:attribute,dyn:map,dyn:call, …date:—date:date-time,date:date,date:time,date:year,date:month-in-year,date:day-in-month,date:day-of-week-in-month,date:format-date,date:add,date:difference,date:seconds,date:day-name, …func:—func:function,func:result(extension-function declarations)exsltRegisterAllC ABI export — mirrors upstream;xsltproccalls it at startupxsltprocCLI — full option surface (--param,--stringparam,--output,--noout,--html,--encoding,--xinclude,--profile,--maxdepth,--maxvars,--nonet,--nowrite, …) with upstream exit codes (1–11)- RTF support — variables with inline content become context-owned result tree fragments;
exsl:node-set($var)/pathnavigation works - 1110 passing tests:
cargo test --lib— 0 failures - Differential oracle parity: a 12-case
xsltproccorpus (basic transform,count()/AVTs,exsl:node-set/math:/set:/str:, predicates, attribute string-values,xsl:if/xsl:when, numbering, descendingxsl:sort,key(),call-templatewith params,method="html") is byte-identical to the system libxslt 1.1.45xsltproc(stdout + exit codes)
Underlying subsystem fixes landed during Phase 9
| Fix | Surface | Detail |
|---|---|---|
| XPath core functions in XSLT | xslt | The transform context now registers the XPath 1.0 core function library — before, every XPath function call (count(), substring(), …) failed as unknown |
| AVT evaluation | xslt | {expr} attribute value templates evaluated in literal attributes and xsl:element/xsl:attribute/xsl:processing-instruction names (XSLT 1.0 §7.6.2) |
| RTF variable ownership | xslt | Inline variable content is deep-copied into a context-owned RVT (freed exactly once at context teardown) — fixes a double-free and enables exsl:node-set |
| Node string-value | tree | node_get_content concatenates all descendant text (XPath 1.0 string-value), not just direct text children |
| Caller parameter format | xslt | xsltApplyStylesheet params parsed as upstream (name, value) pairs with {uri}name namespace form |
date: no-arg default |
exslt | date:date()/date:time() and the component functions default to the current date-time (EXSLT spec) |
Phase 8 (historical)
Phase 8 implemented the complete native-Rust XSLT 1.0 engine: stylesheet compilation, template matching, pattern compilation, variable/parameter binding, keys, sorting, numbering, imports/includes, extensions, security, serialization, and the transform runtime (§31–§34). The XSLT engine operates exclusively on the Rust libxml implementation — no upstream libxml2/libxslt is loaded, linked, or shelled out to (§31):
Rust CLI → Rust libxslt compatibility layer → Rust XSLT engine
→ Rust XPath implementation → Rust libxml tree/parser/serializer
Subsystem fixes landed during Phase 8:
| Fix | Surface | Detail |
|---|---|---|
| SAX2 namespace resolution | parser | startElementNs receives split prefix/localname + resolved URIs; element and attribute namespaces now attached by the tree builder |
| Absolute path evaluation | XPath | /root/item evaluates from the document node, not the root element (XPath 1.0 / semantics) |
| XPath C ABI helpers | ABI | xmlXPathObjectCopy, xmlXPathCastToString, xmlXPathCastStringToNumber, xmlXPathCmpNodes, xmlXPathNodeSetCreate |
| Node content getter | tree | node_get_content (upstream xmlNodeGetContent semantics) |
| Template-content ownership | xslt | Template content belongs to the stylesheet document; xsltFreeTemplate no longer double-frees it |
node()/text() patterns |
xslt | Bare node-test calls translate to steps with correct §5.5 priorities |
match="/" semantics |
xslt | Matches only the document node, not the root element |
All six Phase 8 residuals are documented in atlas/RESIDUAL_LEDGER.md (R-000101–R-000106).
Build
# Test C consumer compilation against our headers:
# Build and run oracle container:
Published artifacts
- crates.io:
libxml-rs0.1.0-alpha.11 - GitHub: https://github.com/infinityabundance/libxml-rs
Oracle verification
The oracle Docker container builds libxml2 2.12.0 and libxslt 1.1.39 from source.
The candidate (libxml-rs) does not link against system libxml2/libxslt — verified
by the oracle contamination court.
Test coverage by subsystem
| Subsystem | Tests | Subsystem | Tests | Subsystem | Tests |
|---|---|---|---|---|---|
| XPath 1.0 | 128 | URI | 69 | Encoding | 65 |
| XML Schema (XSD) | 62 | DTD validation | 56 | RELAX NG | 56 |
| XML parser + SAX | 50 | Regex | 44 | I/O | 44 |
| Schematron | 40 | DTD | 35 | XML Reader | 35 |
| Entities | 31 | HTML | 31 | C14N | 29 |
| Tree/ownership | 28 | XInclude | 23 | XML Writer | 20 |
| Automata | 16 | Catalog | 16 | XPointer | 15 |
| Debug | 11 | Hash | 9 | List | 8 |
| Dictionary | 7 | Globals | 7 | String | 6 |
| Errors | 5 | Memory | 2 | Threads | 2 |
| ABI allocator | 8 | ||||
| XSLT patterns | 46 | XSLT transform | 26 | XSLT security | 7 |
| XSLT numbering | 5 | XSLT stylesheet | 4 | XSLT variables/params | 9 |
| XSLT compiler | 3 | XSLT sorting | 3 | XSLT misc (keys/space/serial/ns/imports/ext) | 13 |
| EXSLT dates | 9 | EXSLT strings | 6 | EXSLT math | 5 |
| EXSLT common | 4 | EXSLT sets | 4 | EXSLT dynamic | 2 |
| EXSLT functions | 1 | EXSLT registry | 3 | XSLT params | 5 |
| Total (1110 passing, 1 ignored) |
Project Structure
libxml-rs/
├── Cargo.toml # Single Cargo package (no workspace)
├── src/
│ ├── lib.rs # Library entry point
│ ├── abi/ # C ABI compatibility layer (§4, §14)
│ ├── xml/ # libxml2 implementation (§1, §3, §31)
│ ├── xslt/ # libxslt implementation (§31–§34)
│ ├── exslt/ # EXSLT modules (§35)
│ ├── compatibility/ # Historical profiles, quirks, platform (§68, §69)
│ └── bin/ # CLI tools: xmllint, xmlcatalog, xsltproc (§36)
├── include/ # Compatible C headers (§15)
├── atlas/ # Forensic archive (§7–§12)
│ ├── releases/ # Release manifests per version
│ ├── api/ # Public API inventories
│ ├── abi/ # ABI snapshots
│ ├── symbols/ # Symbol table comparisons
│ ├── config/ # Build configuration profiles
│ ├── standards/ # Standards mapping
│ ├── HISTORY.md # Complete release history
│ ├── LORE.md # Undocumented behavior archive
│ ├── QUIRKS.md # Confirmed compatibility quirks
│ ├── PARITY_MATRIX.md # Current parity status
│ ├── RESIDUAL_LEDGER.md # Unexplained differences
│ └── SECURITY_HISTORY.md # Vulnerability custody
├── oracle/ # Reproducible upstream build environment (§39)
├── courts/ # Differential testing framework (§40–§50)
│ ├── schema.json # Casefile schema
│ ├── suites/ # Court case suites
│ ├── receipts/ # Execution receipts
│ └── tools/ # Court runner
├── tools/ # Archaeology and analysis tooling
│ ├── archaeology/ # manifest.py, apiatlas.py, symbols.py, delta.py, profileconfig.py
│ └── courts/ # Court runner
├── docker/ # Reproducible Docker oracle images
├── docs/ # Technical documentation
└── archaeology/ # Upstream git clones (immutable, offline)
Build
The crate builds as cdylib + staticlib + rlib; the build script also
emits libxml-2.0.pc/libxslt.pc pkg-config files, xml2-config/xslt-config
scripts, and SONAME symlinks into the target directory.
License
Licensed under either of:
- MIT license (LICENSE-MIT)
- Apache License, Version 2.0 (LICENSE-APACHE)
at your option.
Project Status
| Dimension | Status |
|---|---|
| API completeness | 🟢 libxml2 surfaces complete; libxslt ABI (33 symbols) exported |
| ABI compatibility | 🟢 ABI courts passing (struct, symbol, enum) |
| Tree/ownership | 🟢 28 tree tests passing |
| XML parser + SAX | 🟢 50 parser tests; SAX2 namespace resolution |
| XPath 1.0 | 🟢 128 tests, absolute paths, node sets, conversions |
| Validation family | 🟢 DTD (56) + XSD (62) + RELAX NG (56) + Schematron (40) |
| XSLT 1.0 engine | 🟢 103 tests; full instruction surface; end-to-end transforms |
| C headers | 🟢 45+19 headers, gcc & clang, zero warnings |
| CLI parity | 🟢 xsltproc (Phase 9), xmllint + xmlcatalog (Phase 10) complete with upstream exit codes and differential oracle parity |
| EXSLT | 🟢 All seven modules registered (34 tests); exsl:node-set on RTFs, math/set/str/dyn/date/func verified end-to-end |
| Historical atlas | 🟢 Semantic epochs (E-001…E-008) + release manifests + API/ABI snapshots; atlas/SEMANTIC_EPOCHS.md correlates every behavior change with its upstream commit |
| Oracle infrastructure | 🟢 Docker oracle built and verified; 12 historical libxml2 + 5 libxslt oracles built from archaeology git with era-tolerant build tooling (oracle/historical/) |
| Court coverage | 🟢 ABI courts passing; differential suites staged |
| Downstream testing | 🔴 Not started (Phase 12) |
See atlas/PARITY_MATRIX.md for the detailed,
evidence-bounded parity matrix and atlas/RESIDUAL_LEDGER.md
for the residual ledger.