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