sup_xml/lib.rs
1//! SupXML — a memory-safe, libxml2-compatible XML library for Rust.
2//!
3//! # Quick start
4//!
5//! ```
6//! use sup_xml::{parse_str, serialize_to_string, xpath_count, ParseOptions};
7//!
8//! let doc = parse_str(r#"
9//! <catalog>
10//! <book id="1"><title>Dune</title></book>
11//! <book id="2"><title>Foundation</title></book>
12//! </catalog>
13//! "#, &ParseOptions::default()).unwrap();
14//!
15//! // Navigate with XPath.
16//! assert_eq!(xpath_count(&doc, "/catalog/book").unwrap(), 2);
17//!
18//! // Roundtrip back to XML text.
19//! let xml = serialize_to_string(&doc);
20//! assert!(xml.contains("<title>Dune</title>"));
21//! ```
22//!
23//! # Feature overview
24//!
25//! | Area | Functions |
26//! |---|---|
27//! | Parsing | [`parse_str`], [`parse_bytes`] (both take `&ParseOptions`), [`parse_str_with_recovered`], [`parse_bytes_with_recovered`] |
28//! | Namespace resolution | [`parse_ns_str`] |
29//! | Serialization | [`serialize_to_string`], [`serialize_formatted`], [`serialize_with`] |
30//! | XPath 1.0 | [`XPathContext`] (reusable), [`xpath_eval`], [`xpath_str`], [`xpath_bool`], [`xpath_num`], [`xpath_count`] |
31//! | Typed serde deserialization | [`de::from_str`], [`de::from_bytes`] (feature `serde`) |
32//! | Security | [`ParseOptions`] — entity budget, depth limit, external-entity toggle |
33//!
34//! # Thread safety
35//!
36//! A [`Document`] owns its entire arena, so it is **`Send`**: you can parse
37//! on one thread and hand the whole document to another for XPath, walking,
38//! or serialization. Parsing itself is thread-independent — every call to
39//! [`parse_str`] / [`parse_bytes`] builds a self-contained document with no
40//! shared mutable state, so any number of threads can parse concurrently.
41//!
42//! ```
43//! use sup_xml::{parse_str, xpath_count, ParseOptions};
44//! let doc = parse_str("<r><a/></r>", &ParseOptions::default()).unwrap();
45//! // Moving the whole document into a thread is fine — `Document: Send`.
46//! let n = std::thread::spawn(move || xpath_count(&doc, "//a").unwrap())
47//! .join()
48//! .unwrap();
49//! assert_eq!(n, 1);
50//! ```
51//!
52//! A `Document` is **not `Sync`**, however: its nodes thread themselves
53//! together with interior-mutable pointers, so a shared `&Document` cannot
54//! be touched from two threads at once. Share work by giving each thread its
55//! own document, not a borrow of one.
56//!
57//! ```compile_fail
58//! use sup_xml::{parse_str, xpath_count, ParseOptions};
59//! let doc = parse_str("<r><a/></r>", &ParseOptions::default()).unwrap();
60//! // `&Document` is not `Sync`, so it cannot be shared across scoped
61//! // threads — this fails to compile.
62//! std::thread::scope(|s| {
63//! s.spawn(|| xpath_count(&doc, "//a").unwrap());
64//! s.spawn(|| xpath_count(&doc, "//a").unwrap());
65//! });
66//! ```
67//!
68//! # Security
69//!
70//! SupXML is built to parse **untrusted input** safely, and the default
71//! [`ParseOptions`] are the hardened stance — you opt *out* of
72//! protections, never into them.
73//!
74//! - **No external fetches (XXE / SSRF).** External DTD loading is off
75//! ([`ParseOptions::load_external_dtd`] defaults to `false`) and no
76//! external entity or DTD is ever retrieved from the network or
77//! filesystem unless you explicitly install an
78//! [`ParseOptions::external_resolver`]. An unresolved external entity
79//! reference is rejected, not silently expanded.
80//! - **Bounded entity expansion (billion laughs).**
81//! [`ParseOptions::max_entity_expansion_bytes`] (default 1 MB) caps
82//! total expanded entity text, defeating exponential/quadratic blowup.
83//! - **Bounded nesting (stack-exhaustion DoS).**
84//! [`ParseOptions::max_element_depth`] (default 256) caps element
85//! nesting; DTD content models and regular expressions (XSD `pattern`
86//! facets, XPath `matches`/`replace`/`tokenize`) are independently
87//! depth-bounded so malicious schemas and patterns cannot overflow the
88//! parser stack.
89//! - **Memory safety by construction.** This crate is
90//! `#![forbid(unsafe_code)]`; the parser cannot produce a buffer
91//! overflow or use-after-free regardless of input.
92//!
93//! See the Security reference in the SupXML documentation for the full
94//! threat model.
95
96#![forbid(unsafe_code)] // see CONTRIBUTING.md § "Unsafe policy"
97
98// Errors
99pub use sup_xml_core::{ErrorDomain, ErrorLevel, XmlError};
100pub use sup_xml_core::error::Result;
101
102// Licensing — parsing requires a valid license certificate (verified
103// once per process and cached). `verify_license()` checks eagerly for
104// fail-fast startup; otherwise the first parse triggers the check.
105pub use sup_xml_core::verify_license;
106
107// Encoding detection / transcoding (Tier 1: UTF-8, ASCII, Latin-1, Windows-1252)
108pub use sup_xml_core::encoding;
109
110// ─── Parsing ───────────────────────────────────────────────────────────────
111//
112// `parse_bytes` / `parse_str` and their `*_opts` / `*_unchecked` siblings
113// return [`Document`] — the bump-allocated DOM in `sup_xml_tree::dom`.
114// All nodes, attributes, and strings produced by the parser live in a
115// single shared allocation, freed wholesale when the `Document` drops.
116pub use sup_xml_core::parse_bytes as parse_bytes;
117pub use sup_xml_core::parse_bytes_unchecked as parse_bytes_unchecked;
118pub use sup_xml_core::parse_bytes_in_place as parse_bytes_in_place;
119pub use sup_xml_core::parse_str as parse_str;
120pub use sup_xml_core::parse_ns_str as parse_ns_str;
121pub use sup_xml_core::parse_ns_bytes as parse_ns_bytes;
122pub use sup_xml_core::parse_str_with_recovered as parse_str_with_recovered;
123pub use sup_xml_core::parse_bytes_with_recovered as parse_bytes_with_recovered;
124pub use sup_xml_core::XmlDeclInfo;
125pub use sup_xml_core::StreamParser;
126pub use sup_xml_core::{XmlByteStreamReader, DEFAULT_BUFFER_SIZE, HUGE_BUFFER_SIZE};
127pub use sup_xml_core::ParseOptions;
128
129// Streaming SAX-style readers (unchanged — these never built a DOM).
130pub use sup_xml_core::{Attr, Attrs, Event, EventInto, XmlReader, unescape};
131pub use sup_xml_core::iterparse::{IterEvent, Iterparse};
132pub use sup_xml_core::{ParseSelectorError, Selector};
133pub use sup_xml_core::{BytesAttr, BytesAttrs, BytesEvent, BytesEventInto, XmlBytesReader, unescape_bytes};
134
135// Serialization.
136pub use sup_xml_core::{
137 serialize_formatted, serialize_to_bytes, serialize_to_string, serialize_with,
138};
139
140// XML Catalogs — OASIS-format public/system identifier resolution.
141// See COMPARISON.md § "XML Catalogs" for the rationale and what's
142// supported.
143pub use sup_xml_core::{Catalog, discover_catalog_paths, load_default_catalog};
144
145// External entity / DTD loading — opt in by setting
146// `ParseOptions::external_resolver` to one of these. Without a
147// resolver configured, the parser refuses every external
148// reference (XXE prevention). See COMPARISON.md § "External
149// entity / DTD loading".
150pub use sup_xml_core::{
151 ChainedResolver, EntityResolver, FilesystemResolver, InMemoryResolver, ResolveError,
152};
153#[cfg(feature = "network-resolver")]
154pub use sup_xml_core::entity_resolver::NetworkResolver;
155
156// HTML5 round-trip serializer.
157pub use sup_xml_core::serialize_html_to_string as serialize_html_to_string;
158pub use sup_xml_core::SerializeOptions;
159pub use sup_xml_core::OutputCharset;
160
161// Canonical XML (W3C C14N 1.0 + Exclusive C14N 1.0).
162pub use sup_xml_core::canonical::{
163 canonicalize_node_to_bytes as canonicalize_node_to_bytes,
164 canonicalize_node_with as canonicalize_node_with,
165 canonicalize_to_bytes as canonicalize_to_bytes,
166 canonicalize_with as canonicalize_with,
167 include_all as canonicalize_include_all,
168 VisitTarget as CanonicalizeVisitTarget,
169};
170pub use sup_xml_core::{C14nMode, CanonicalizeOptions};
171
172// XPath 1.0.
173pub use sup_xml_core::{
174 xpath_bool as xpath_bool,
175 xpath_count as xpath_count,
176 xpath_eval as xpath_eval,
177 xpath_num as xpath_num,
178 xpath_str as xpath_str,
179 xpath_strings as xpath_strings,
180 XPathContext as XPathContext,
181 parse_xpath, parse_xpath_with, XPathOptions, XPathValue,
182 XPathBindingsBuilder,
183};
184
185/// XPath 1.0 + a small subset used for fast streaming-style node matching.
186///
187/// See [`xpath::pattern::Pattern`] for the libxml2-flavour pattern matcher.
188pub mod xpath {
189 pub use sup_xml_core::xpath::pattern;
190 pub use sup_xml_core::xpath::pattern::Pattern;
191}
192
193// Tree types — the DOM shape returned by every parsing entry point.
194pub use sup_xml_tree::dom::{
195 Attribute, Document, Namespace, Node, NodeKind,
196 ChildIter, AttrIter, NsDeclIter, DocumentBuilder,
197};
198pub use sup_xml_tree::{HtmlDoctype, HtmlMeta, QuirksMode};
199
200// XInclude — `<xi:include href="..."/>` processing.
201pub use sup_xml_core::xinclude::process_xincludes as process_xincludes;
202pub use sup_xml_core::{XIncludeOptions, XINCLUDE_NS};
203
204// Lenient HTML5 parser (feature `html`). Driven by html5ever; see
205// `sup_xml_core::html` for the full module. Top-level `parse_html_*`
206// functions return [`Document`].
207#[cfg(feature = "html")]
208pub use sup_xml_core::html::{
209 parse_html_str, parse_html_str_opts, parse_html_str_with_recovered,
210 parse_html_bytes, parse_html_bytes_opts, parse_html_bytes_with_recovered,
211 HtmlAttribute, HtmlAttrs, HtmlBytesReader,
212 HtmlEvent, HtmlParseOptions, HtmlReader, HtmlSaxHandler, HtmlSaxParser,
213};
214
215// Serde-driven typed deserialization (feature-gated).
216#[cfg(feature = "serde")]
217pub mod de;
218
219// Async I/O wrappers — `tokio` feature. Slurp via async read,
220// then dispatch to the synchronous parser. See module docs.
221#[cfg(feature = "tokio")]
222pub mod async_io;
223
224// XML Schema 1.0 validation (feature-gated).
225#[cfg(feature = "xsd")]
226pub mod xsd {
227 //! XML Schema 1.0 — schema compiler and instance validator.
228 //!
229 //! Re-exports the public surface of [`sup_xml_core::xsd`]. See that
230 //! module for the conventions.
231 //!
232 //! # Quick start
233 //!
234 //! ```no_run
235 //! # fn main() -> Result<(), Box<dyn std::error::Error>> {
236 //! use sup_xml::xsd::Schema;
237 //!
238 //! let schema = Schema::compile_str(r#"
239 //! <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
240 //! targetNamespace="urn:demo" xmlns="urn:demo">
241 //! <xs:element name="port" type="xs:int"/>
242 //! </xs:schema>"#)?;
243 //!
244 //! schema.validate_str(r#"<port xmlns="urn:demo">8080</port>"#)?;
245 //! # Ok(())
246 //! # }
247 //! ```
248 pub use sup_xml_core::xsd::{
249 BuiltinType, FsResolver, InMemoryResolver, NoResolver, Schema, SchemaCompileError,
250 SchemaOptions, SchemaResolver, SchemaVersion, ValidationError, ValidationIssue,
251 ValidationKind, ValidationOptions, QName, TypeRef,
252 };
253}
254
255// XSLT 1.0 transforms (feature-gated).
256#[cfg(feature = "xslt")]
257pub mod xslt {
258 //! XSLT 1.0 transform engine.
259 //!
260 //! Re-exports the public surface of [`sup_xml_xslt`].
261 //!
262 //! # Quick start
263 //!
264 //! ```no_run
265 //! # fn main() -> Result<(), Box<dyn std::error::Error>> {
266 //! use sup_xml::{parse_str, ParseOptions};
267 //! use sup_xml::xslt::Stylesheet;
268 //!
269 //! let xsl = r#"<xsl:stylesheet version="1.0"
270 //! xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
271 //! <xsl:template match="/"><out><xsl:value-of select="."/></out></xsl:template>
272 //! </xsl:stylesheet>"#;
273 //! let style = Stylesheet::compile_str(xsl)?;
274 //!
275 //! let opts = ParseOptions { namespace_aware: true, ..Default::default() };
276 //! let doc = parse_str("<r>hello</r>", &opts)?;
277 //! let result = style.apply(&doc)?;
278 //! println!("{}", result.to_string()?);
279 //! # Ok(())
280 //! # }
281 //! ```
282 pub use sup_xml_xslt::{
283 Stylesheet, XsltError,
284 loader::{Loader, FilesystemLoader, InMemoryLoader, NullLoader},
285 extensions::{ExtensionFunctions, Extensions},
286 result_tree::{ResultTree, ResultNode},
287 };
288
289 /// Schematron — ISO/IEC 19757-3 rule-based validation. Built on
290 /// top of the XSLT/XPath engine.
291 pub mod schematron {
292 pub use sup_xml_xslt::schematron::{
293 Schematron, ValidationReport, Finding, FindingKind,
294 };
295 }
296}