1#[cfg(feature = "benchmark-internals")]
8pub(crate) mod benchmark;
9#[cfg(all(feature = "xml-backend-roxmltree", feature = "xml-backend-xmloxide"))]
10mod differential;
11mod preflight;
12#[cfg(feature = "xml-backend-roxmltree")]
13mod roxmltree;
14#[doc(hidden)]
15pub mod storage;
16mod tree;
17#[cfg(feature = "xml-backend-xmloxide")]
18mod xmloxide;
19
20use std::fmt;
21
22use self::preflight::LexicalPreflight;
23
24pub use tree::{
25 Ancestors, Attribute, Attributes, Children, Descendants, Document, ExpandedName, Namespace,
26 Namespaces, Node, NodeId, NodeType, PI, XmlIdError,
27};
28
29#[derive(Clone, Copy, Debug)]
31pub struct ParsingOptions {
32 pub allow_dtd: bool,
34 pub nodes_limit: u32,
36}
37
38impl Default for ParsingOptions {
39 fn default() -> Self {
40 Self {
41 allow_dtd: false,
42 nodes_limit: u32::MAX,
43 }
44 }
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
53pub enum XmlBackend {
54 Xmloxide,
56 Roxmltree,
58 Differential,
60}
61
62impl XmlBackend {
63 pub(crate) const fn build_default() -> Self {
64 if cfg!(feature = "xml-backend-differential") {
65 Self::Differential
66 } else if cfg!(feature = "xml-backend-xmloxide") {
67 Self::Xmloxide
68 } else {
69 Self::Roxmltree
70 }
71 }
72
73 #[must_use]
75 pub const fn is_available(self) -> bool {
76 match self {
77 Self::Xmloxide => cfg!(feature = "xml-backend-xmloxide"),
78 Self::Roxmltree => cfg!(feature = "xml-backend-roxmltree"),
79 Self::Differential => cfg!(all(
80 feature = "xml-backend-xmloxide",
81 feature = "xml-backend-roxmltree"
82 )),
83 }
84 }
85
86 pub fn available() -> impl Iterator<Item = Self> {
88 [Self::Xmloxide, Self::Roxmltree, Self::Differential]
89 .into_iter()
90 .filter(|backend| backend.is_available())
91 }
92
93 fn parse<'input>(
94 self,
95 input: &'input str,
96 options: ParsingOptions,
97 preflight: &LexicalPreflight,
98 ) -> Result<Document<'input>, ParseError> {
99 match self {
100 Self::Xmloxide => parse_with_xmloxide(input, options, preflight),
101 Self::Roxmltree => parse_with_roxmltree(input, options, preflight),
102 Self::Differential => parse_differentially(input, options, preflight),
103 }
104 }
105
106 #[cfg(feature = "xmldsig")]
107 fn parse_in<'input, 'w, W: structured_asn1::memory::Work>(
108 self,
109 input: &'input str,
110 options: ParsingOptions,
111 preflight: &LexicalPreflight<'input, storage::Accounted<'w, W>>,
112 storage: storage::Accounted<'w, W>,
113 ) -> Result<Document<'input, storage::Accounted<'w, W>>, AccountedParseError> {
114 match self {
115 Self::Xmloxide => {
116 #[cfg(feature = "xml-backend-xmloxide")]
117 return xmloxide::XmloxideBackend::parse_in(input, options, preflight, storage);
118 #[cfg(not(feature = "xml-backend-xmloxide"))]
119 Err(ParseError::BackendUnavailable { backend: self }.into())
120 }
121 Self::Roxmltree => {
122 #[cfg(feature = "xml-backend-roxmltree")]
123 return roxmltree::RoxmltreeBackend::parse_in(input, options, preflight, storage);
124 #[cfg(not(feature = "xml-backend-roxmltree"))]
125 Err(ParseError::BackendUnavailable { backend: self }.into())
126 }
127 Self::Differential => {
128 #[cfg(all(feature = "xml-backend-xmloxide", feature = "xml-backend-roxmltree"))]
129 return differential::parse_in(input, options, preflight, storage);
130 #[cfg(not(all(
131 feature = "xml-backend-xmloxide",
132 feature = "xml-backend-roxmltree"
133 )))]
134 Err(ParseError::BackendUnavailable { backend: self }.into())
135 }
136 }
137 }
138}
139
140fn parse_with_xmloxide<'input>(
141 input: &'input str,
142 options: ParsingOptions,
143 preflight: &LexicalPreflight,
144) -> Result<Document<'input>, ParseError> {
145 #[cfg(feature = "xml-backend-xmloxide")]
146 return xmloxide::XmloxideBackend::parse(input, options, preflight);
147 #[cfg(not(feature = "xml-backend-xmloxide"))]
148 {
149 let _ = (input, options, preflight);
150 Err(ParseError::BackendUnavailable {
151 backend: XmlBackend::Xmloxide,
152 })
153 }
154}
155
156fn parse_with_roxmltree<'input>(
157 input: &'input str,
158 options: ParsingOptions,
159 preflight: &LexicalPreflight,
160) -> Result<Document<'input>, ParseError> {
161 #[cfg(feature = "xml-backend-roxmltree")]
162 return roxmltree::RoxmltreeBackend::parse(input, options, preflight);
163 #[cfg(not(feature = "xml-backend-roxmltree"))]
164 {
165 let _ = (input, options, preflight);
166 Err(ParseError::BackendUnavailable {
167 backend: XmlBackend::Roxmltree,
168 })
169 }
170}
171
172fn parse_differentially<'input>(
173 input: &'input str,
174 options: ParsingOptions,
175 preflight: &LexicalPreflight,
176) -> Result<Document<'input>, ParseError> {
177 #[cfg(all(feature = "xml-backend-xmloxide", feature = "xml-backend-roxmltree"))]
178 return differential::DifferentialBackend::parse(input, options, preflight);
179 #[cfg(not(all(feature = "xml-backend-xmloxide", feature = "xml-backend-roxmltree")))]
180 {
181 let _ = (input, options, preflight);
182 Err(ParseError::BackendUnavailable {
183 backend: XmlBackend::Differential,
184 })
185 }
186}
187
188impl Default for XmlBackend {
189 fn default() -> Self {
190 Self::build_default()
191 }
192}
193
194#[cfg(feature = "xmldsig")]
198#[derive(Debug, thiserror::Error)]
199pub enum AccountedParseError {
200 #[error("{0}")]
202 Semantic(#[from] ParseError),
203 #[error("{0}")]
205 Lexical(
206 #[source]
207 crate::xml_input::lexical::Error<
208 structured_asn1::memory::OwnedText,
209 structured_asn1::memory::MemoryError,
210 >,
211 ),
212 #[cfg(feature = "xml-backend-roxmltree")]
214 #[error("{0}")]
215 Roxmltree(#[source] crate::parser_roxmltree::Error<structured_asn1::memory::OwnedText>),
216 #[cfg(feature = "xml-backend-xmloxide")]
218 #[error("{0}")]
219 Xmloxide(
220 #[source]
221 crate::parser_xmloxide::error::ParseError<
222 structured_asn1::memory::OwnedText,
223 structured_asn1::memory::OwnedVec<
224 crate::parser_xmloxide::error::ParseDiagnostic<structured_asn1::memory::OwnedText>,
225 >,
226 >,
227 ),
228 #[error("XML backend divergence: {0}")]
230 Divergence(structured_asn1::memory::OwnedText),
231}
232
233#[derive(Clone, Debug, PartialEq, Eq)]
235pub enum ParseError {
236 AllocationFailed,
238 #[cfg(feature = "xmldsig")]
240 SharedExecution(structured_asn1::memory::MemoryError),
241 BackendUnavailable {
243 backend: XmlBackend,
245 },
246 ByteLimitReached {
248 maximum: usize,
250 actual: usize,
252 },
253 DtdDetected,
255 NodesLimitReached,
257 EntityExpansionLimitReached {
259 maximum: u32,
261 actual: u32,
263 },
264 EntityExpansionWorkLimitReached {
266 maximum: usize,
268 actual: usize,
270 },
271 SourcePositionLimitReached {
273 maximum: usize,
275 actual: usize,
277 },
278 DepthLimitReached {
280 maximum: usize,
282 actual: usize,
284 },
285 NamespaceBindingLimitReached {
287 maximum: usize,
289 actual: usize,
291 },
292 SourceMapMismatch {
294 expected: &'static str,
296 found: &'static str,
298 },
299 Backend {
301 backend: &'static str,
303 message: String,
305 },
306 BackendDivergence {
308 reason: String,
310 },
311}
312
313#[cfg(feature = "xmldsig")]
314impl From<structured_asn1::memory::MemoryError> for ParseError {
315 fn from(error: structured_asn1::memory::MemoryError) -> Self {
316 Self::SharedExecution(error)
317 }
318}
319
320impl fmt::Display for ParseError {
321 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
322 match self {
323 Self::AllocationFailed => formatter.write_str("XML tree allocation failed"),
324 #[cfg(feature = "xmldsig")]
325 Self::SharedExecution(error) => write!(formatter, "XML tree execution: {error}"),
326 Self::BackendUnavailable { backend } => {
327 write!(
328 formatter,
329 "XML backend {backend:?} is not compiled into this build"
330 )
331 }
332 Self::ByteLimitReached { maximum, actual } => {
333 write!(
334 formatter,
335 "XML byte limit reached: maximum {maximum}, actual {actual}"
336 )
337 }
338 Self::DtdDetected => formatter.write_str("DTD detected"),
339 Self::NodesLimitReached => formatter.write_str("nodes limit reached"),
340 Self::EntityExpansionLimitReached { maximum, actual } => write!(
341 formatter,
342 "XML entity expansion limit {maximum} exceeded at expansion {actual}"
343 ),
344 Self::EntityExpansionWorkLimitReached { maximum, actual } => write!(
345 formatter,
346 "XML entity expansion-work limit {maximum} bytes exceeded at {actual} bytes"
347 ),
348 Self::SourcePositionLimitReached { maximum, actual } => write!(
349 formatter,
350 "XML source-position limit {maximum} exceeded at position {actual}"
351 ),
352 Self::DepthLimitReached { maximum, actual } => {
353 write!(
354 formatter,
355 "XML depth limit {maximum} exceeded at depth {actual}"
356 )
357 }
358 Self::NamespaceBindingLimitReached { maximum, actual } => write!(
359 formatter,
360 "XML namespace binding limit {maximum} exceeded at {actual} active bindings"
361 ),
362 Self::SourceMapMismatch { expected, found } => write!(
363 formatter,
364 "xmloxide-source-map rejected XML: expected {expected}, found {found}"
365 ),
366 Self::Backend { backend, message } => {
367 write!(formatter, "{backend} rejected XML: {message}")
368 }
369 Self::BackendDivergence { reason } => {
370 write!(formatter, "XML backend semantic divergence: {reason}")
371 }
372 }
373 }
374}
375
376impl std::error::Error for ParseError {}
377
378trait XmlBackendImplementation {
379 fn parse<'input>(
380 input: &'input str,
381 options: ParsingOptions,
382 preflight: &LexicalPreflight,
383 ) -> Result<Document<'input>, ParseError>;
384}
385
386#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
388pub(crate) struct SemanticNodeId(u32);
389
390impl SemanticNodeId {
391 pub(super) const fn new(raw: u32) -> Self {
392 Self(raw)
393 }
394 pub(super) const fn raw(self) -> u32 {
395 self.0
396 }
397}
398
399pub(crate) trait SemanticDocument {
401 type Node<'a>: Copy
402 where
403 Self: 'a;
404 fn node(&self, id: SemanticNodeId) -> Option<Self::Node<'_>>;
405 fn node_id<'a>(&'a self, node: Self::Node<'a>) -> SemanticNodeId;
406}
407
408impl SemanticDocument for Document<'_> {
409 type Node<'a>
410 = Node<'a, 'a>
411 where
412 Self: 'a;
413
414 fn node(&self, id: SemanticNodeId) -> Option<Self::Node<'_>> {
415 self.get_node(NodeId::from(id.raw()))
416 }
417
418 fn node_id<'a>(&'a self, node: Self::Node<'a>) -> SemanticNodeId {
419 SemanticNodeId::new(node.id().get())
420 }
421}