oxml/lib.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2026 oxml. All rights reserved.
3
4//! # oxml
5//!
6//! A pure Rust XML toolkit. Zero unsafe code. Parsing, an ergonomic
7//! tree, and `XPath` 1.0.
8//!
9//! ## Why this exists
10//!
11//! Rust's XML ecosystem is strong at one end and empty at the other.
12//! `quick-xml` and `roxmltree` parse quickly; nothing maintained
13//! offers what `lxml` gives Python. The only `XPath` crate,
14//! `sxd-xpath`, has not shipped a release since 2018, and XSLT and
15//! XSD validation have no pure-Rust implementation at all.
16//!
17//! oxml closes the query gap first, because that is the one people
18//! actually hit.
19//!
20//! ## Quick Start
21//!
22//! ```
23//! # #[cfg(all(feature = "xpath", feature = "std"))] {
24//! use oxml::{parse, XPath};
25//!
26//! let doc = parse(r#"
27//! <library>
28//! <book lang="en"><title>Dune</title></book>
29//! <book lang="fr"><title>Germinal</title></book>
30//! </library>
31//! "#).unwrap();
32//!
33//! let titles = XPath::compile("//book[@lang='en']/title").unwrap();
34//! let found = titles.evaluate(&doc);
35//!
36//! assert_eq!(found.to_str(&doc), "Dune");
37//! # }
38//! ```
39//!
40//! ## Walking the tree directly
41//!
42//! `XPath` is optional. The tree stands on its own:
43//!
44//! ```
45//! use oxml::parse;
46//!
47//! let doc = parse("<a><b id='1'>text</b></a>")?;
48//! let root = doc.root_element().expect("a root element");
49//!
50//! assert_eq!(doc.element_name(root).unwrap().local, "a");
51//!
52//! let b = doc.children(root)[0];
53//! assert_eq!(doc.attribute(b, "id"), Some("1"));
54//! assert_eq!(doc.text(b), "text");
55//! # Ok::<(), oxml::Error>(())
56//! ```
57//!
58//! ## Design
59//!
60//! - **Zero `unsafe`** — `#![forbid(unsafe_code)]`, enforced at
61//! compile time. The tree is an arena of index-addressed nodes, so
62//! parent links cost no `Rc`, no `RefCell`, and no raw pointers.
63//!
64//! - **No entity expansion** — only the five predefined entities and
65//! numeric character references are resolved. External and custom
66//! entities are not, which forecloses XXE and billion-laughs by
67//! construction rather than by configuration. A parser that cannot
68//! expand them cannot be talked into leaking a file.
69//!
70//! - **Namespace-correct** — names compare by URI and local part,
71//! never by prefix. An unprefixed *element* takes the default
72//! namespace; an unprefixed *attribute* is in no namespace. That
73//! asymmetry is the classic source of namespace bugs, so it is
74//! explicit in the parser rather than assumed.
75//!
76//! ## Feature flags
77//!
78//! - `std` *(default)* — standard library integration, including
79//! `std::error::Error`.
80//! - `xpath` *(default)* — the `XPath` engine. Turn it off if you only
81//! need to parse.
82
83#![forbid(unsafe_code)]
84#![deny(missing_docs)]
85#![cfg_attr(not(feature = "std"), no_std)]
86#![cfg_attr(docsrs, feature(doc_cfg))]
87
88extern crate alloc;
89
90#[cfg(feature = "std")]
91extern crate std;
92
93pub mod error;
94mod parser;
95pub mod tree;
96
97#[cfg(feature = "xpath")]
98#[cfg_attr(docsrs, doc(cfg(feature = "xpath")))]
99pub mod xpath;
100
101/// The README's examples, compiled as doctests.
102///
103/// `include_str!` rather than a copy: a snapshot of the README in a
104/// test file drifts from the README the moment either is edited, and a
105/// check that silently stops checking is worse than no check. This way
106/// every ```rust block in the README is compiled and run by
107/// `cargo test`, and a broken example fails the build.
108/// Gated on the features the README demonstrates: its examples use
109/// XPath, so under `--no-default-features` they would fail to compile
110/// for a reason that says nothing about the crate.
111#[cfg(all(doctest, feature = "xpath", feature = "std"))]
112#[doc = include_str!("../README.md")]
113pub struct ReadmeDoctests;
114
115/// The deepest element nesting the parser will accept.
116///
117/// Parsing descends one stack frame per open element, so an
118/// arbitrarily deep document would exhaust the stack — and a stack
119/// overflow aborts the process rather than unwinding, so no caller can
120/// catch it. Documents come from the network in every one of this
121/// crate's front ends, which makes that a denial of service rather
122/// than a curiosity.
123///
124/// The limit is well above any hand-written document and far below the
125/// depth that threatens the smallest stack a caller is likely to have
126/// (test harnesses commonly give threads 2 MiB).
127pub const MAX_DEPTH: usize = 256;
128
129mod dtd;
130pub mod encoding;
131pub mod external;
132mod limits;
133mod names4e;
134pub use limits::{Edition, Limits};
135
136pub use error::{Error, ErrorKind, Result};
137pub use parser::{
138 parse, parse_bytes, parse_bytes_with, parse_with, parse_with_external,
139};
140pub use tree::{Attribute, Document, ExpandedName, NodeId, NodeKind};
141
142#[cfg(feature = "xpath")]
143pub use xpath::{XPath, XPathError};