A ground-up Rust implementation of the TinyXML2 C++ API.
Crate Architecture:
tinyxml2(core library) ·tinyxml2-capi(C FFI bindings) ·tinyxml2-bench(benchmarks & comparisons)
This roadmap defines eight sequential phases for building a complete, production-grade Rust replacement for TinyXML2. The implementation uses a generational arena for memory-safe tree storage and a recursive-descent parser for XML processing. Each phase builds on the previous one and is designed to produce a shippable, testable increment.
| Phase | Title | Complexity | Status |
|---|---|---|---|
| 1 | Foundation | Medium | ✅ Completed |
| 2 | DOM Core | High | ✅ Completed |
| 3 | XML Parser | High | ✅ Completed |
| 4 | Writer / Serializer | Medium | ✅ Completed |
| 5 | Visitor Pattern & Ergonomics | Medium | ✅ Completed |
| 6 | C API (tinyxml2-capi) |
Medium | ✅ Completed |
| 7 | Testing & Benchmarks | Medium | ✅ Completed |
| 8 | Documentation & Release | Low | ✅ Completed |
| 9 | WASM & no_std Support |
Medium | ✅ Completed |
| 10 | XPath & Serde Integration | High | 🔲 Planned |
| 11 | Advanced Perf & SIMD | High | 🔲 Planned |
Establish the core infrastructure, error handling, and build pipeline that every subsequent phase depends on.
Estimated Complexity: Medium
- Error types —
XmlErrorenum covering parse errors, I/O errors, missing attributes, value conversion failures, and arena allocation faults. Maps to TinyXML2'sXMLErrorcodes. - Entity handling — Encode/decode the five predefined XML entities (
&,<,>,",') plus numeric character references (&#xHHHH;,&#DDDD;). - Generational arena allocator — Arena-based node storage using generational indices
(
NodeId) for O(1) access with dangling-reference detection. Supports allocation, deallocation, and safe index reuse. - Configuration system —
ParserConfigstruct with builder pattern for whitespace mode, entity processing toggle, maximum parse depth, and encoding settings. - CI pipeline — GitHub Actions workflows for
cargo check,cargo test,cargo clippy,cargo fmt --check, and MSRV verification. - Documentation infrastructure — Crate-level docs, module-level docs, and
#[doc(hidden)]annotations for internal APIs. - Test suite — 87 unit tests covering error construction, entity round-tripping, arena operations, and configuration validation. 15 doc tests embedded in public API documentation.
| Decision | Choice | Rationale |
|---|---|---|
| Node storage | Generational arena (Vec<Entry<T>>) |
Cache-friendly, no Rc/RefCell, safe reuse |
| Index type | NodeId(u32 index, u32 generation) |
Compact, detects use-after-free |
| Error strategy | Result<T, XmlError> everywhere |
Idiomatic Rust, no panics in library code |
| String storage | Owned String per node |
Simplicity first; interning deferred to Phase 8 |
| Configuration | Builder pattern on ParserConfig |
Ergonomic, extensible, #[non_exhaustive] |
Implement the full XML Document Object Model tree with all node types, tree manipulation operations, and deep cloning.
Estimated Complexity: High — this is the largest single phase by line count.
- Node type enum —
XmlNodevariants:Document,Element,Text,Comment,Declaration,ProcessingInstruction,Unknown. Each variant holds type-specific data. -
Documentroot — The top-level container. Owns the arena. Providesnew(),load_file(),save_file(), and theroot_element()accessor. -
Elementoperations —name(),set_name(), attribute CRUD (set_attribute,find_attribute,delete_attribute), child element queries (first_child_element,next_sibling_elementwith optional name filter). -
Attributestorage — Linked-list of attributes per element, supporting typed getters (int_value,float_value,bool_value) with fallback defaults, mirroring the C++ API. - Tree operations —
insert_first_child,insert_end_child,insert_after_node,delete_child,delete_children. All operations maintain parent/child/sibling pointer consistency within the arena. - Deep cloning —
deep_clone(node, target_document)that recursively copies a subtree, including across documents (re-allocating into the target arena). - Memory management —
Document::clear()to reset the arena. Individual node deletion with generation bump to invalidate staleNodeIdhandles.
Document (arena owner)
└─ Element "root"
├─ Attribute "version" = "1.0"
├─ Element "child1"
│ └─ Text "hello"
├─ Comment "<!-- note -->"
└─ Element "child2"
└─ Element "nested"
All parent ↔ child ↔ sibling links are stored as Option<NodeId> inside each node's metadata
struct, enabling O(1) traversal without pointer chasing outside the arena.
Build the recursive-descent XML parser that converts raw XML text into the DOM tree from Phase 2.
Estimated Complexity: High — correctness-critical with many edge cases.
- Recursive-descent parser — Hand-written parser (no parser combinators or generated code)
operating on
&[u8]input. Matches TinyXML2's parsing strategy for behavioral compatibility. - Entity resolution — Inline expansion of the five predefined entities and numeric character references during text and attribute value parsing (leveraging Phase 1 entity module).
- Whitespace handling — Two modes mirroring TinyXML2:
PRESERVE_WHITESPACE— Keep all whitespace as-is.COLLAPSE_WHITESPACE— Collapse runs of whitespace to a single space, trim leading/trailing.
- BOM detection — Auto-detect and skip UTF-8 BOM (
0xEF 0xBB 0xBF) at the start of input. Reject non-UTF-8 BOMs with a clear error. - Depth limits — Configurable maximum nesting depth (default: 100) to prevent stack
overflow on malicious input. Returns
XmlError::ElementDepthExceededon violation. - Error recovery — Rich error reporting with byte offset, line number, and column number. No panic paths.
- Parse entry points —
Document::parse(xml: &str),Document::load_file(path),Document::parse_bytes(bytes: &[u8]).
parse_document()
├─ skip_bom()
├─ parse_declaration()? // <?xml ... ?>
└─ parse_element() // recursive
├─ parse_attributes()
├─ parse_children()
│ ├─ parse_element() // recurse
│ ├─ parse_text()
│ ├─ parse_comment() // <!-- ... -->
│ ├─ parse_cdata() // <![CDATA[ ... ]]>
│ └─ parse_pi() // <?target ... ?>
└─ parse_close_tag()
Implement XML output with pretty-printing and compact modes, equivalent to TinyXML2's
XMLPrinter.
Estimated Complexity: Medium
-
XmlPrinterstruct — Stateful writer that traverses the DOM and emits well-formed XML. Implements theXmlVisitortrait (Phase 5) internally for traversal. - Pretty-print mode — Indented output with configurable indent string (default: 4 spaces).
Newlines between elements. Mirrors
XMLPrinter(FILE*, true). - Compact mode — Minimal whitespace output for network/storage efficiency. Mirrors
XMLPrinter(FILE*, false). - Write targets —
to_string() -> String— In-memory serialization.to_writer(impl Write)— Streaming output to anystd::io::Writesink.to_file(path)— Convenience wrapper for file output.
- Streaming API — Push-based API for building XML without a DOM tree:
open_element("tag"),push_attribute("key", "value"),push_text("content"),close_element(). Useful for high-performance serialization. - Entity escaping — Automatic escaping of
<,>,&,",'in text content and attribute values during output. - Declaration output — Optional XML declaration (
<?xml version="1.0" encoding="UTF-8"?>) controlled by configuration.
Add the Visitor pattern for DOM traversal and ergonomic wrapper types for a Rust-idiomatic API surface.
Estimated Complexity: Medium
-
XmlVisitortrait — Mirrors TinyXML2'sXMLVisitorwithvisit_enter/visit_exitmethods for each node type. Returnsboolto control traversal continuation. -
Document::accept(visitor)— Depth-first traversal that drives the visitor. - Handle types —
Handle<'a>,HandleMut<'a>— null-safe navigation wrappers aroundNodeIdfor fluent DOM traversal chains with automaticNonepropagation. -
NodeRef<'a>/ElementRef<'a>— Typed, lifetime-bounded reference wrappers for safe, ergonomic access. Prevents iterator invalidation. - Iterators —
Children,ChildElements,Siblings,Attributes,Descendants— standard Rust iterators withDoubleEndedIteratorandFusedIteratorsupport. - Convenience methods —
Document::children(),child_elements(),siblings(),descendants(),attributes(),handle(),handle_mut(),node_ref(),element_ref().
Expose the Rust library through a C-compatible FFI for drop-in replacement in C/C++ projects.
Estimated Complexity: Medium — mechanically intensive but architecturally straightforward.
-
extern "C"function exports — ~56 C functions covering document lifecycle, DOM factory, tree mutation, navigation, element/attribute access, typed attribute queries, streaming printer, and node inspection. Naming convention:tx_document_new,tx_element_name, etc. - Opaque handle types —
TxDocument(boxedDocument) andTxPrinter(boxedXmlPrinter) wrapped in opaque structs.TxNodeIdas#[repr(C)]value type. - Error codes — C-compatible
TxErrorenum with integer values matching TinyXML2's error enum for maximum compatibility. - Header generation — Auto-generated
tinyxml2.hheader viacbindgen. Configured incbindgen.tomlwith C-style output. - Build outputs —
- Static library (
libtinyxml2_capi.a) - Shared library (
libtinyxml2_capi.dylib/.so/.dll) - Configured via
crate-type = ["staticlib", "cdylib"]
- Static library (
- Memory safety contract — All FFI functions validate non-null pointers. Null inputs
return error codes or no-op. No panics across FFI boundary (
catch_unwindguards). - Lifetime management —
tx_document_free(),tx_printer_free()for heap-allocated returns. Clear ownership documentation in header comments.
Comprehensive compatibility testing against TinyXML2 C++ and performance benchmarking.
Estimated Complexity: Medium
- Compatibility test suite — Port TinyXML2's own test cases (
xmltest.cpp) to Rust. Verify identical parse results, error behavior, and output formatting for ~300 test vectors. - Round-trip tests — Parse → serialize → re-parse cycle for a corpus of real-world XML files. Assert structural equality.
- Fuzz testing —
cargo-fuzzharnesses for:fuzz_parse— Random byte sequences → parser (must not panic/crash).fuzz_roundtrip— Valid XML → parse → serialize → parse → assert equality.fuzz_capi— Random FFI call sequences → C API (must not segfault).
- Criterion benchmarks — Comparative benchmarks against the original TinyXML2 C++ library
(linked via
cccrate):Benchmark Measures parse_smallParse a 1 KB XML document parse_mediumParse a 100 KB XML document parse_largeParse a 10 MB XML document serialize_prettyPretty-print a parsed DOM serialize_compactCompact-print a parsed DOM dom_traversalWalk all nodes depth-first attribute_lookupQuery attributes by name (100K lookups) arena_alloc_deallocAllocate/deallocate 100K nodes - Performance targets — Within 1.5× of TinyXML2 C++ for parsing, within 1.0× for
serialization (Rust I/O should match or beat C++
fprintf). - CI integration — Benchmark results tracked via
criterion's JSON output. Regression alerts on >10% slowdown.
Polish documentation, write migration guides, and publish the initial release.
Estimated Complexity: Low
- Migration guide —
MIGRATION.mdmapping every TinyXML2 C++ class and method to its Rust equivalent. Organized by class (XMLDocument→Document,XMLElement→Element, etc.) with code examples for each. - Example programs —
examples/directory with:parse_file.rs— Parse an XML file and print element names.build_dom.rs— Programmatically construct a DOM and serialize.visitor.rs— Implement a custom visitor to extract data.streaming_writer.rs— Use the push-based API for large output.c_interop.rs— Demonstrate calling the C API from Rust.
- API documentation — 100% documentation coverage on public items. Rich examples in doc
comments. Cross-linked with
#[doc]attributes. -
README.mdoverhaul — Feature matrix, quick start, performance comparison table, MSRV policy, and contribution guidelines. -
CHANGELOG.md— Initial changelog following Keep a Changelog format. - Crate metadata —
Cargo.tomlkeywords, categories, repository link, license (MIT/Apache-2.0 dual license), andrust-versionfield. - 1.0.0 release — Publish
tinyxml2andtinyxml2-capito crates.io. Tagv1.0.0in git.
Enable tinyxml2-rs to run in resource-constrained embedded environments and web browsers.
- no_std compatibility — Feature-gate standard library dependencies and use the
alloccrate for all dynamic memory allocations (Vec,String,Box). Target:#![no_std]withextern crate alloc. - WASM target support — Validate the core crate on
wasm32-unknown-unknownandwasm32-wasip1. JavaScript bindings are intentionally left to the host application boundary; seedocs/architecture/wasm.md. - Abstract I/O — Gate file/stream writers behind the
stdfeature and keep the core parser, DOM, and string serialization available withoutstd::io::Write.
- C/C++ FFI WASM Support — Support compiling and linking the FFI bindings (
tinyxml2-capi) onwasm32-unknown-unknownandwasm32-wasip1targets for C/C++ WebAssembly integration.
Improve the ergonomics of data extraction and structured serialization.
- XPath Subset — Implement a lightweight, fast, and spec-compliant subset of XPath 1.0 (e.g., path selection, tag filtering) to query DOM nodes dynamically. Expose these querying interfaces to C/C++ via FFI functions in
tinyxml2-capi. - Serde Serialization — Introduce a separate
tinyxml2-serdecrate allowing automatic Rust struct serialization/deserialization into XML structures. - Attributes vs. Elements Annotations — Custom serde macros (e.g.,
#[xml(attribute)],#[xml(element)]) for direct structure control.
Unlock high-performance processing for large-scale XML workloads.
- SIMD Character Scanning — Use SIMD vector instructions (SSE2/AVX2/NEON) via
memchror custom intrinsics to accelerate whitespace scanning and token delimiter matching. - String Interning — Intern tag and attribute names in the Document arena to reduce memory usage and improve lookup speed.
- Parallel Traversal — Add Rayon-based parallel visitor traversal for multi-threaded read-only traversals of very large documents.
- XML Namespace Support — Namespace-qualified element/attribute lookup and context propagation. Expose these features via the FFI layer (
tinyxml2-capi) to give C and C++ users native namespace capabilities that the original C++ TinyXML2 lacks.
Contributions are welcome at any phase. Please see the issue tracker for phase-specific tasks
labeled phase-N. Each phase has a tracking issue with a checklist of deliverables.
Priority for contributors:
- Phase 2 (DOM Core) — Most impactful, enables all downstream phases.
- Phase 3 (Parser) — Second most impactful, enables real-world usage.
- Phase 7 (Testing) — Can begin in parallel once Phase 3 is complete.
Last updated: 2026-07-01