Skip to main content

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    //! ```ignore
235    //! use sup_xml::xsd::Schema;
236    //!
237    //! let schema = Schema::compile_str(r#"
238    //!     <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
239    //!                targetNamespace="urn:demo" xmlns="urn:demo">
240    //!       <xs:element name="port" type="xs:int"/>
241    //!     </xs:schema>"#)?;
242    //!
243    //! schema.validate_str(r#"<port xmlns="urn:demo">8080</port>"#)?;
244    //! ```
245    pub use sup_xml_core::xsd::{
246        BuiltinType, FsResolver, InMemoryResolver, NoResolver, Schema, SchemaCompileError,
247        SchemaOptions, SchemaResolver, SchemaVersion, ValidationError, ValidationIssue,
248        ValidationKind, ValidationOptions, QName, TypeRef,
249    };
250}
251
252// XSLT 1.0 transforms (feature-gated).
253#[cfg(feature = "xslt")]
254pub mod xslt {
255    //! XSLT 1.0 transform engine.
256    //!
257    //! Re-exports the public surface of [`sup_xml_xslt`].
258    //!
259    //! # Quick start
260    //!
261    //! ```ignore
262    //! use sup_xml::{parse_str, ParseOptions};
263    //! use sup_xml::xslt::Stylesheet;
264    //!
265    //! let xsl = r#"<xsl:stylesheet version="1.0"
266    //!     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
267    //!   <xsl:template match="/"><out><xsl:value-of select="."/></out></xsl:template>
268    //! </xsl:stylesheet>"#;
269    //! let style = Stylesheet::compile_str(xsl)?;
270    //!
271    //! let opts = ParseOptions { namespace_aware: true, ..Default::default() };
272    //! let doc  = parse_str("<r>hello</r>", &opts)?;
273    //! let result = style.apply(&doc)?;
274    //! println!("{}", result.to_string()?);
275    //! ```
276    pub use sup_xml_xslt::{
277        Stylesheet, XsltError,
278        loader::{Loader, FilesystemLoader, InMemoryLoader, NullLoader},
279        extensions::{ExtensionFunctions, Extensions},
280        result_tree::{ResultTree, ResultNode},
281    };
282
283    /// Schematron — ISO/IEC 19757-3 rule-based validation.  Built on
284    /// top of the XSLT/XPath engine.
285    pub mod schematron {
286        pub use sup_xml_xslt::schematron::{
287            Schematron, ValidationReport, Finding, FindingKind,
288        };
289    }
290}