oxml
🌐 EN , Change language
llms.txt

Migration Guide

Migrating from Python lxml to oxml

Upgrade your XML processing pipelines from Python's C-dependent libxml2 wrapper to pure-Rust memory safety, zero-copy streaming, and true multi-threaded concurrency.

Why Migrate?

Python's lxml has powered XML processing for two decades by wrapping the C libraries libxml2 and libxslt. However, production engineering demands have evolved:

  1. Memory Safety & Zero Vulnerabilities: libxml2 has suffered numerous CVEs (buffer overflows, use-after-free, memory leaks). oxml enforces #![forbid(unsafe_code)] with 100% pure, safe Rust.
  2. True Multi-Threading: lxml is constrained by Python's Global Interpreter Lock (GIL). oxml structures (Document, XPath) implement Send and Sync, allowing multi-threaded parsing across CPU cores with Rayon or Tokio.
  3. Painless Cross-Compilation: No C compiler toolchains, system package dependencies (libxml2-dev), or wheels mismatches across macOS, Linux (glibc/musl), and Windows.
  4. Edge & WebAssembly: oxml compiles directly to WebAssembly (oxml-wasm), enabling identical XML processing in browsers and Cloudflare Workers.

Concept & API Mapping

Python lxml.etree Rust oxml Ecosystem Key Advantage
etree.fromstring(xml) oxml::parse(xml) Fast generational arena allocation
etree.parse(file) oxml::stream::Reader::from_reader(f) Bounded 34 KB buffer memory footprint
tree.xpath("//book/text()") doc.select("//book/text()") Complete W3C XPath 1.0 with $O(1)$ integer fast-path
XPath(expr)(tree) oxml::XPath::compile(expr)? Precompiled expression reuse across threads
etree.iterparse(...) reader.next_borrowed() Zero-copy &str borrowing directly from buffer
etree.tostring(el) oxml::format_xml(doc, ...) Indentation, minification, and empty element style control
etree.XMLSchema(xsd).validate(doc) xmlschema::validate(&doc, &schema) Pure-Rust XSD validator with 95.2% W3C conformance
Custom LLM agent scripts oxml-mcp 5 native MCP tools for Claude, Cursor, and AI agents

Code Comparisons

1. Document Parsing & XPath Queries

In Python with lxml:

from lxml import etree

xml = """<catalog>
    <book id="bk101"><title>Dune</title>19.95</price></book>
    <book id="bk102"><title>Foundation</title>15.95</price></book>
</catalog>"""

root = etree.fromstring(xml.encode("utf-8"))
titles = root.xpath("//book[price > 18]/title/text()")
print(titles)  # ['Dune']

In Rust with oxml:

use oxml::{parse, XPath};

fn main() -> Result<(), Box<dyn std::error.Error>> {
    let xml = r#"<catalog>
        <book id="bk101"><title>Dune</title>19.95</price></book>
        <book id="bk102"><title>Foundation</title>15.95</price></book>
    </catalog>"#;

    let doc = parse(xml)?;
    let xpath = XPath::compile("//book[price > 18]/title/text()")?;
    let titles = xpath.evaluate(&doc);
    assert_eq!(titles.to_str(&doc), "Dune");
    Ok(())
}

2. High-Performance Streaming

In Python, iterparse requires careful manual node clearing (elem.clear()) to avoid unbounded memory expansion:

for event, elem in etree.iterparse(file_obj, tag="item"):
    process(elem)
    elem.clear()  # Required to free C memory

In Rust with oxml, memory is strictly bounded by design with zero-copy slice borrowing:

use oxml::stream::{BorrowedEvent, Reader};

let mut reader = Reader::from_reader(file)?;
while let Some(event) = reader.next_borrowed()? {
    match event {
        BorrowedEvent::StartElement { name, .. } if name.local == "item" => {
            // Borrowed directly from reader buffer with 0 heap allocations
        }
        _ => {}
    }
}

3. XML Schema (XSD) Validation

In Python with lxml:

schema = etree.XMLSchema(etree.parse("schema.xsd"))
doc = etree.parse("document.xml")
is_valid = schema.validate(doc)
if not is_valid:
    print(schema.error_log)

In Rust with xmlschema:

use xmlschema::{parse_schema, validate};

let schema = parse_schema(&std::fs::read_to_string("schema.xsd")?)?;
let doc = oxml::parse(&std::fs::read_to_string("document.xml")?)?;
let report = validate(&doc, &schema);

if report.is_valid() {
    println!("Document is strictly valid!");
} else {
    for violation in &report.violations {
        eprintln!("{violation}");
    }
}