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