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;
14mod tree;
15#[cfg(feature = "xml-backend-xmloxide")]
16mod xmloxide;
17
18use std::fmt;
19
20use self::preflight::LexicalPreflight;
21
22pub use tree::{
23 Ancestors, Attribute, Attributes, Children, Descendants, Document, ExpandedName, Namespace,
24 Namespaces, Node, NodeId, NodeType, PI, XmlIdError,
25};
26
27#[derive(Clone, Copy, Debug)]
29pub struct ParsingOptions {
30 pub allow_dtd: bool,
32 pub nodes_limit: u32,
34}
35
36impl Default for ParsingOptions {
37 fn default() -> Self {
38 Self {
39 allow_dtd: false,
40 nodes_limit: u32::MAX,
41 }
42 }
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
51pub enum XmlBackend {
52 Xmloxide,
54 Roxmltree,
56 Differential,
58}
59
60impl XmlBackend {
61 pub(crate) const fn build_default() -> Self {
62 if cfg!(feature = "xml-backend-differential") {
63 Self::Differential
64 } else if cfg!(feature = "xml-backend-xmloxide") {
65 Self::Xmloxide
66 } else {
67 Self::Roxmltree
68 }
69 }
70
71 #[must_use]
73 pub const fn is_available(self) -> bool {
74 match self {
75 Self::Xmloxide => cfg!(feature = "xml-backend-xmloxide"),
76 Self::Roxmltree => cfg!(feature = "xml-backend-roxmltree"),
77 Self::Differential => cfg!(all(
78 feature = "xml-backend-xmloxide",
79 feature = "xml-backend-roxmltree"
80 )),
81 }
82 }
83
84 pub fn available() -> impl Iterator<Item = Self> {
86 [Self::Xmloxide, Self::Roxmltree, Self::Differential]
87 .into_iter()
88 .filter(|backend| backend.is_available())
89 }
90
91 fn parse<'input>(
92 self,
93 input: &'input str,
94 options: ParsingOptions,
95 preflight: &LexicalPreflight,
96 ) -> Result<Document<'input>, ParseError> {
97 match self {
98 Self::Xmloxide => parse_with_xmloxide(input, options, preflight),
99 Self::Roxmltree => parse_with_roxmltree(input, options, preflight),
100 Self::Differential => parse_differentially(input, options, preflight),
101 }
102 }
103}
104
105fn parse_with_xmloxide<'input>(
106 input: &'input str,
107 options: ParsingOptions,
108 preflight: &LexicalPreflight,
109) -> Result<Document<'input>, ParseError> {
110 #[cfg(feature = "xml-backend-xmloxide")]
111 return xmloxide::XmloxideBackend::parse(input, options, preflight);
112 #[cfg(not(feature = "xml-backend-xmloxide"))]
113 {
114 let _ = (input, options, preflight);
115 Err(ParseError::BackendUnavailable {
116 backend: XmlBackend::Xmloxide,
117 })
118 }
119}
120
121fn parse_with_roxmltree<'input>(
122 input: &'input str,
123 options: ParsingOptions,
124 preflight: &LexicalPreflight,
125) -> Result<Document<'input>, ParseError> {
126 #[cfg(feature = "xml-backend-roxmltree")]
127 return roxmltree::RoxmltreeBackend::parse(input, options, preflight);
128 #[cfg(not(feature = "xml-backend-roxmltree"))]
129 {
130 let _ = (input, options, preflight);
131 Err(ParseError::BackendUnavailable {
132 backend: XmlBackend::Roxmltree,
133 })
134 }
135}
136
137fn parse_differentially<'input>(
138 input: &'input str,
139 options: ParsingOptions,
140 preflight: &LexicalPreflight,
141) -> Result<Document<'input>, ParseError> {
142 #[cfg(all(feature = "xml-backend-xmloxide", feature = "xml-backend-roxmltree"))]
143 return differential::DifferentialBackend::parse(input, options, preflight);
144 #[cfg(not(all(feature = "xml-backend-xmloxide", feature = "xml-backend-roxmltree")))]
145 {
146 let _ = (input, options, preflight);
147 Err(ParseError::BackendUnavailable {
148 backend: XmlBackend::Differential,
149 })
150 }
151}
152
153impl Default for XmlBackend {
154 fn default() -> Self {
155 Self::build_default()
156 }
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
161pub enum ParseError {
162 BackendUnavailable {
164 backend: XmlBackend,
166 },
167 ByteLimitReached {
169 maximum: usize,
171 actual: usize,
173 },
174 DtdDetected,
176 NodesLimitReached,
178 EntityExpansionLimitReached {
180 maximum: u32,
182 actual: u32,
184 },
185 EntityExpansionWorkLimitReached {
187 maximum: usize,
189 actual: usize,
191 },
192 SourcePositionLimitReached {
194 maximum: usize,
196 actual: usize,
198 },
199 DepthLimitReached {
201 maximum: usize,
203 actual: usize,
205 },
206 NamespaceBindingLimitReached {
208 maximum: usize,
210 actual: usize,
212 },
213 Backend {
215 backend: &'static str,
217 message: String,
219 },
220 BackendDivergence {
222 reason: String,
224 },
225}
226
227impl fmt::Display for ParseError {
228 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
229 match self {
230 Self::BackendUnavailable { backend } => {
231 write!(
232 formatter,
233 "XML backend {backend:?} is not compiled into this build"
234 )
235 }
236 Self::ByteLimitReached { maximum, actual } => {
237 write!(
238 formatter,
239 "XML byte limit reached: maximum {maximum}, actual {actual}"
240 )
241 }
242 Self::DtdDetected => formatter.write_str("DTD detected"),
243 Self::NodesLimitReached => formatter.write_str("nodes limit reached"),
244 Self::EntityExpansionLimitReached { maximum, actual } => write!(
245 formatter,
246 "XML entity expansion limit {maximum} exceeded at expansion {actual}"
247 ),
248 Self::EntityExpansionWorkLimitReached { maximum, actual } => write!(
249 formatter,
250 "XML entity expansion-work limit {maximum} bytes exceeded at {actual} bytes"
251 ),
252 Self::SourcePositionLimitReached { maximum, actual } => write!(
253 formatter,
254 "XML source-position limit {maximum} exceeded at position {actual}"
255 ),
256 Self::DepthLimitReached { maximum, actual } => {
257 write!(
258 formatter,
259 "XML depth limit {maximum} exceeded at depth {actual}"
260 )
261 }
262 Self::NamespaceBindingLimitReached { maximum, actual } => write!(
263 formatter,
264 "XML namespace binding limit {maximum} exceeded at {actual} active bindings"
265 ),
266 Self::Backend { backend, message } => {
267 write!(formatter, "{backend} rejected XML: {message}")
268 }
269 Self::BackendDivergence { reason } => {
270 write!(formatter, "XML backend semantic divergence: {reason}")
271 }
272 }
273 }
274}
275
276impl std::error::Error for ParseError {}
277
278trait XmlBackendImplementation {
279 fn parse<'input>(
280 input: &'input str,
281 options: ParsingOptions,
282 preflight: &LexicalPreflight,
283 ) -> Result<Document<'input>, ParseError>;
284}
285
286#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
288pub(crate) struct SemanticNodeId(u32);
289
290impl SemanticNodeId {
291 pub(super) const fn new(raw: u32) -> Self {
292 Self(raw)
293 }
294 pub(super) const fn raw(self) -> u32 {
295 self.0
296 }
297}
298
299pub(crate) trait SemanticDocument {
301 type Node<'a>: Copy
302 where
303 Self: 'a;
304 fn node(&self, id: SemanticNodeId) -> Option<Self::Node<'_>>;
305 fn node_id<'a>(&'a self, node: Self::Node<'a>) -> SemanticNodeId;
306}
307
308impl SemanticDocument for Document<'_> {
309 type Node<'a>
310 = Node<'a, 'a>
311 where
312 Self: 'a;
313
314 fn node(&self, id: SemanticNodeId) -> Option<Self::Node<'_>> {
315 self.get_node(NodeId::from(id.raw()))
316 }
317
318 fn node_id<'a>(&'a self, node: Self::Node<'a>) -> SemanticNodeId {
319 SemanticNodeId::new(node.id().get())
320 }
321}