Open-source project
RazrFalcon/roxmltree avatar
RazrFalcon/roxmltree

roxmltree parses XML into an immutable tree with zero dependencies

Represent an XML document as a read-only tree.

540 stars45 forksRustApache-2.0

At a glance

What is it?
roxmltree is an Apache-2.0 or MIT licensed Rust crate that represents an XML document as a read-only tree, with one dependency, memchr, and no unsafe code. It deliberately mimics the parsing behaviour of Python's lxml, and the trade is that it drops tree modification, writing, DTD support and HTML handling.
Who is it for?
Adopt roxmltree if you need to read and query XML documents in Rust and only read them, since it has one dependency, no unsafe blocks and keeps source positions for error reporting. Skip it if you need to modify a tree or write XML back out, because roxmltree supports neither, and skip it if you need DTD resolution or HTML parsing.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 31 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the crate actually parses

roxmltree represents an XML document as a read-only tree, and the whole crate fits in a single dependency and a handful of concepts. The README's example shows the typical use, which is finding one thing in a document you have already been handed.

rust
// Find element by id.
let doc = roxmltree::Document::parse("<rect id='rect1'/>")?;
let elem = doc.descendants().find(|n| n.attribute("id") == Some("rect1"))?;
assert!(elem.has_tag_name("rect"));

You parse a document from a string, walk its descendants, test attributes and check tag names. There is no builder API and no mutation API, because the tree is immutable. The reason for that is stated plainly under why read-only. In some cases all you need is to retrieve some data from an XML document, and for such cases a lot of optimizations become possible. That is the design rationale: the crate is aimed at extraction, and it gives up the ability to edit because editing is what forces the parser to keep a mutable, more general structure.

Adding the crate and the feature flags that change memory use

The dependency set is one crate.

toml
[dependencies]
memchr = { version = "2", default-features = false }

The Cargo.toml shows roxmltree at version 0.21.1 with a rust-version of 1.60 and edition 2021, and the feature list is where the interesting choice sits. The default features are std and positions, where std enables memchr's std mode and positions enables node and attribute positions in the original document. The comment on that feature is precise about the cost. It increases memory usage by a Range<usize> for each node, and by a Range<usize> plus a u16 and a u8 for each attribute. If your documents are large and you do not need to point back at the source, turning positions off is a straightforward memory saving, and the feature exists for exactly that reason. The crate is published under MIT OR Apache-2.0, with both LICENSE-MIT and LICENSE-APACHE files at the repository root, so you can pick either.

Parsing behaviour follows lxml, not just the specification

The README states the position on parsing directly. Sadly, XML can be parsed in many different ways, and roxmltree tries to mimic the behavior of Python's lxml. The details are pointed at docs/parsing.md in the repository. This is a deliberate choice with a practical consequence. A parser that follows the letter of the XML specification can differ from a parser that follows lxml in exactly the areas where the specification leaves room, and applications that consume XML written by other tools have usually been validated against whatever their reference implementation does. Matching lxml means a Rust service behaves like a Python service reading the same document, which matters in the mixed-language stacks where XML still shows up, and it means the crate's behaviour is predictable for anyone who has used lxml before. The cost is that the specification is not the only reference, and a document that a strictly conforming parser rejects may be accepted here.

The feature table, and what roxmltree gives up

The alternatives table is the most useful page in the repository, and it is a fair comparison rather than a marketing one. Against libxml2, xmltree and sxd-document, roxmltree supports element namespace resolving, attribute namespace resolving, entity references, character references, attribute-value normalization, comments, processing instructions, UTF-8 BOM handling and position preserving, and it uses no unsafe code and has zero dependencies. Where it differs is in what it omits. Non UTF-8 input is not supported. Complete DTD support is not supported. HTML support is not supported. And in the row that defines the crate, tree modification and writing are not supported, while libxml2, xmltree and sxd-document all support both. Entity references and attribute-value normalization, both of which are part of the XML specification, are missing from xmltree and sxd-document. The table also flags one asymmetry with sxd-document, which resolves attributes but not elements by default, and one partial case, where sxd-document has no default namespace propagation. The tested versions are listed as 0.20.0 for roxmltree, Apple-provided for libxml2, 0.10.3 for xmltree and 0.3.2 for sxd-document.

Position preserving and the example that uses it

Position preserving is the feature that distinguishes roxmltree from a plain data-structure parser, and the README explains it in a note. roxmltree keeps all node and attribute positions in the original document, so you can easily retrieve it if you need it, and it points at examples/print_pos.rs for details. The other examples are ast.rs and stats.rs, which suggests the intended shape of the library: a tree you can walk, print with source locations, and interrogate for statistics. For a tool that reads configuration files, log formats or API responses, being able to say not just what the value was but where in the document it came from is the difference between an error message a user can act on and one they cannot. This is also why positions is a default feature rather than an afterthought, at the cost of the memory described above.

Benchmarks, and the caveat the README attaches to them

The README publishes benchmarks against other XML tree libraries, and then immediately undercuts them. The results are grouped by document size, with roxmltree, libxml2, sxd-document and xmltree compared on the huge, large, medium and tiny cases. For the huge document roxmltree records 2,997,887 ns per iteration against 6,850,666 for libxml2, 9,440,412 for sxd-document and 41,662,316 for xmltree, and the same ordering holds for the large, medium and tiny cases. The benchmarks were taken on an Apple M1 Pro, and you can run them yourself with cargo bench in the benches directory. The caveat is the important part: since all libraries have a different XML support, benchmarking is a bit pointless. libxml2 was benched through the rust-libxml wrapper crate. A second set compares against streaming parsers, where roxmltree is slightly slower than quick-xml but much faster than xmlrs, and the README notes that streaming parsers usually do not provide proper string unescaping, DTD resolving and namespace support. So the numbers describe a different capability set rather than a like-for-like race.

Where roxmltree is the wrong choice

The obvious case is anything that writes. If you need to build, modify or serialise an XML document, this crate does not do it, and the alternatives table lists libxml2, xmltree and sxd-document as the ones that do. The second case is DTD. Complete DTD support is absent, so a document relying on a document type definition for entity declarations will not parse the way you need. HTML is also out. The third case is non UTF-8 input, which libxml2 handles and roxmltree does not, and that matters if you are reading files produced by older systems. The fourth is ecosystem momentum. The README notes that there are also elementtree and treexml crates, but that they are abandoned for a long time, which tells you something about how this corner of Rust has been treated, and the fact that roxmltree itself has no GitHub releases and a Cargo version of 0.21.1 against a README table that still says the tested version is 0.20.0 tells you the release process is informal. For a library you intend to depend on for years, the crate's licence terms are permissive and its dependency count is zero, but the version discipline is informal, and that is worth weighing.

Editorial conclusion

Adopt roxmltree if you need to read and query XML documents in Rust and only read them, since it has one dependency, no unsafe blocks and keeps source positions for error reporting. Skip it if you need to modify a tree or write XML back out, because roxmltree supports neither, and skip it if you need DTD resolution or HTML parsing. Before you rely on its behaviour, read docs/parsing.md, since the crate states that XML can be parsed in many ways and roxmltree deliberately mimics lxml rather than the specification alone.

Frequently asked questions

What is roxmltree?

It is a Rust crate that represents an XML document as a read-only tree, published on crates.io with documentation on docs.rs. It requires Rust 1.60 or later, uses no unsafe code, and has a single dependency, memchr.

Does roxmltree support modifying or writing XML?

No. Tree modification and writing are both listed as not supported in the README's comparison table, while libxml2, xmltree and sxd-document support them. The crate is designed for reading data out of an XML document.

Which parser does roxmltree try to match?

It tries to mimic the behavior of Python's lxml, as the README states, because XML can be parsed in many different ways. The details are in docs/parsing.md in the repository.

What does the positions feature in roxmltree cost?

The Cargo.toml notes that it increases memory usage by a Range<usize> for each node and by a Range<usize> plus a u16 and a u8 for each attribute. It is enabled by default alongside std, and it is what lets you retrieve the original position of a node or attribute in the source document.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. RazrFalcon/roxmltree on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/razrfalcon-roxmltree.svg)](https://hysenlabs.com/projects/razrfalcon-roxmltree)

Was this useful?