oxixml_xml/de/event.rs
1//! Simplified events consumed by the deserializer and the intermediate reader
2//! that produces them.
3
4use std::borrow::Cow;
5use std::mem::replace;
6use std::ops::{Deref, Range};
7
8use crate::de::resolver::{EntityResolver, PredefinedEntityResolver};
9use crate::de::xml_read::XmlRead;
10use crate::encoding::Decoder;
11use crate::errors::serialize::DeError;
12use crate::escape::{EscapeError, parse_number};
13use crate::events::{BytesCData, BytesEnd, BytesRef, BytesStart, BytesText};
14use crate::name::QName;
15
16/// A function to check whether the character is a whitespace (blank, new line, carriage return or tab).
17#[inline]
18const fn is_non_whitespace(ch: char) -> bool {
19 !matches!(ch, ' ' | '\r' | '\n' | '\t')
20}
21
22/// Decoded and concatenated content of consequent [`Text`] and [`CData`]
23/// events. _Consequent_ means that events should follow each other or be
24/// delimited only by (any count of) [`Comment`] or [`PI`] events.
25///
26/// Internally text is stored in `Cow<str>`. Cloning of text is cheap while it
27/// is borrowed and makes copies of data when it is owned.
28///
29/// [`Text`]: crate::events::Event::Text
30/// [`CData`]: crate::events::Event::CData
31/// [`Comment`]: crate::events::Event::Comment
32/// [`PI`]: crate::events::Event::PI
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Text<'a> {
35 /// Untrimmed text after concatenating content of all
36 /// [`Text`] and [`CData`] events
37 ///
38 /// [`Text`]: crate::events::Event::Text
39 /// [`CData`]: crate::events::Event::CData
40 pub(crate) text: Cow<'a, str>,
41 /// A range into `text` which contains data after trimming
42 content: Range<usize>,
43}
44
45impl<'a> Text<'a> {
46 pub(crate) fn new(text: Cow<'a, str>) -> Self {
47 let start = text.find(is_non_whitespace).unwrap_or(0);
48 let end = text.rfind(is_non_whitespace).map_or(0, |i| i + 1);
49
50 let content = if start >= end { 0..0 } else { start..end };
51
52 Self { text, content }
53 }
54
55 /// Returns text without leading and trailing whitespaces as [defined] by XML specification.
56 ///
57 /// If you want to only check if text contains only whitespaces, use [`is_blank`](Self::is_blank),
58 /// which will not allocate.
59 ///
60 /// # Example
61 ///
62 /// ```
63 /// # use oxixml_xml::de::Text;
64 /// #
65 /// let text = Text::from("");
66 /// assert_eq!(text.trimmed(), "");
67 ///
68 /// let text = Text::from(" \r\n\t ");
69 /// assert_eq!(text.trimmed(), "");
70 ///
71 /// let text = Text::from(" some useful text ");
72 /// assert_eq!(text.trimmed(), "some useful text");
73 /// ```
74 ///
75 /// [defined]: https://www.w3.org/TR/xml11/#NT-S
76 pub fn trimmed(&self) -> Cow<'a, str> {
77 match self.text {
78 Cow::Borrowed(text) => Cow::Borrowed(&text[self.content.clone()]),
79 Cow::Owned(ref text) => Cow::Owned(text[self.content.clone()].to_string()),
80 }
81 }
82
83 /// Returns `true` if text is empty or contains only whitespaces as [defined] by XML specification.
84 ///
85 /// # Example
86 ///
87 /// ```
88 /// # use oxixml_xml::de::Text;
89 /// #
90 /// let text = Text::from("");
91 /// assert_eq!(text.is_blank(), true);
92 ///
93 /// let text = Text::from(" \r\n\t ");
94 /// assert_eq!(text.is_blank(), true);
95 ///
96 /// let text = Text::from(" some useful text ");
97 /// assert_eq!(text.is_blank(), false);
98 /// ```
99 ///
100 /// [defined]: https://www.w3.org/TR/xml11/#NT-S
101 pub fn is_blank(&self) -> bool {
102 self.content.is_empty()
103 }
104}
105
106impl<'a> Deref for Text<'a> {
107 type Target = str;
108
109 #[inline]
110 fn deref(&self) -> &Self::Target {
111 self.text.deref()
112 }
113}
114
115impl<'a> From<&'a str> for Text<'a> {
116 #[inline]
117 fn from(text: &'a str) -> Self {
118 Self::new(Cow::Borrowed(text))
119 }
120}
121
122impl<'a> From<String> for Text<'a> {
123 #[inline]
124 fn from(text: String) -> Self {
125 Self::new(Cow::Owned(text))
126 }
127}
128
129impl<'a> From<Cow<'a, str>> for Text<'a> {
130 #[inline]
131 fn from(text: Cow<'a, str>) -> Self {
132 Self::new(text)
133 }
134}
135
136////////////////////////////////////////////////////////////////////////////////////////////////////
137
138/// Simplified event which contains only these variants that used by deserializer
139#[derive(Clone, Debug, PartialEq, Eq)]
140pub enum DeEvent<'a> {
141 /// Start tag (with attributes) `<tag attr="value">`.
142 Start(BytesStart<'a>),
143 /// End tag `</tag>`.
144 End(BytesEnd<'a>),
145 /// Decoded and concatenated content of consequent [`Text`] and [`CData`]
146 /// events. _Consequent_ means that events should follow each other or be
147 /// delimited only by (any count of) [`Comment`] or [`PI`] events.
148 ///
149 /// [`Text`]: crate::events::Event::Text
150 /// [`CData`]: crate::events::Event::CData
151 /// [`Comment`]: crate::events::Event::Comment
152 /// [`PI`]: crate::events::Event::PI
153 Text(Text<'a>),
154 /// End of XML document.
155 Eof,
156}
157
158////////////////////////////////////////////////////////////////////////////////////////////////////
159
160/// Simplified event which contains only these variants that used by deserializer,
161/// but [`Text`] events not yet fully processed.
162///
163/// [`Text`] events should be trimmed if they does not surrounded by the other
164/// [`Text`] or [`CData`] events. This event contains intermediate state of [`Text`]
165/// event, where they are trimmed from the start, but not from the end. To trim
166/// end spaces we should lookahead by one deserializer event (i. e. skip all
167/// comments and processing instructions).
168///
169/// [`Text`]: crate::events::Event::Text
170/// [`CData`]: crate::events::Event::CData
171#[derive(Clone, Debug, PartialEq, Eq)]
172pub enum PayloadEvent<'a> {
173 /// Start tag (with attributes) `<tag attr="value">`.
174 Start(BytesStart<'a>),
175 /// End tag `</tag>`.
176 End(BytesEnd<'a>),
177 /// Escaped character data between tags.
178 Text(BytesText<'a>),
179 /// Unescaped character data stored in `<![CDATA[...]]>`.
180 CData(BytesCData<'a>),
181 /// Document type definition data (DTD) stored in `<!DOCTYPE ...>`.
182 DocType(BytesText<'a>),
183 /// Reference `&ref;` in the textual data.
184 GeneralRef(BytesRef<'a>),
185 /// End of XML document.
186 Eof,
187}
188
189impl<'a> PayloadEvent<'a> {
190 /// Ensures that all data is owned to extend the object's lifetime if necessary.
191 #[inline]
192 pub(crate) fn into_owned(self) -> PayloadEvent<'static> {
193 match self {
194 PayloadEvent::Start(e) => PayloadEvent::Start(e.into_owned()),
195 PayloadEvent::End(e) => PayloadEvent::End(e.into_owned()),
196 PayloadEvent::Text(e) => PayloadEvent::Text(e.into_owned()),
197 PayloadEvent::CData(e) => PayloadEvent::CData(e.into_owned()),
198 PayloadEvent::DocType(e) => PayloadEvent::DocType(e.into_owned()),
199 PayloadEvent::GeneralRef(e) => PayloadEvent::GeneralRef(e.into_owned()),
200 PayloadEvent::Eof => PayloadEvent::Eof,
201 }
202 }
203}
204
205/// An intermediate reader that consumes [`PayloadEvent`]s and produces final [`DeEvent`]s.
206/// [`PayloadEvent::Text`] events, that followed by any event except
207/// [`PayloadEvent::Text`] or [`PayloadEvent::CData`], are trimmed from the end.
208pub(crate) struct XmlReader<'i, R: XmlRead<'i>, E: EntityResolver = PredefinedEntityResolver> {
209 /// A source of low-level XML events
210 pub(crate) reader: R,
211 /// Intermediate event, that could be returned by the next call to `next()`.
212 /// If that is the `Text` event then leading spaces already trimmed, but
213 /// trailing spaces is not. Before the event will be returned, trimming of
214 /// the spaces could be necessary
215 lookahead: Result<PayloadEvent<'i>, DeError>,
216
217 /// Used to resolve unknown entities that would otherwise cause the parser
218 /// to return an [`EscapeError::UnrecognizedEntity`] error.
219 ///
220 /// [`EscapeError::UnrecognizedEntity`]: crate::escape::EscapeError::UnrecognizedEntity
221 entity_resolver: E,
222}
223
224impl<'i, R: XmlRead<'i>, E: EntityResolver> XmlReader<'i, R, E> {
225 pub(crate) fn new(mut reader: R, entity_resolver: E) -> Self {
226 // Lookahead by one event immediately, so we do not need to check in the
227 // loop if we need lookahead or not
228 let lookahead = reader.next();
229
230 Self {
231 reader,
232 lookahead,
233 entity_resolver,
234 }
235 }
236
237 /// Returns `true` if all events was consumed
238 pub(crate) const fn is_empty(&self) -> bool {
239 matches!(self.lookahead, Ok(PayloadEvent::Eof))
240 }
241
242 /// Read next event and put it in lookahead, return the current lookahead
243 #[inline(always)]
244 fn next_impl(&mut self) -> Result<PayloadEvent<'i>, DeError> {
245 replace(&mut self.lookahead, self.reader.next())
246 }
247
248 /// Returns `true` when next event is not a text event in any form.
249 #[inline(always)]
250 const fn current_event_is_last_text(&self) -> bool {
251 // If next event is a text-like event or a DocType (which is
252 // metadata and invisible to the data model), we should not
253 // trim trailing spaces — there is more content to drain, and
254 // any DocType between us and the next text run needs to be
255 // absorbed so `read_text` does not later see two consecutive
256 // `DeEvent::Text`. Without DocType here, an input like
257 // `<a>x<!DOCTYPE y>z</a>` produces two text events for `x`
258 // and `z`, tripping `unreachable!()` in `read_text`.
259 !matches!(
260 self.lookahead,
261 Ok(PayloadEvent::Text(_)
262 | PayloadEvent::CData(_)
263 | PayloadEvent::GeneralRef(_)
264 | PayloadEvent::DocType(_))
265 )
266 }
267
268 /// Read all consequent [`Text`] and [`CData`] events until non-text event
269 /// occurs. Content of all events would be appended to `result` and returned
270 /// as [`DeEvent::Text`].
271 ///
272 /// DocType events that fall between text events are absorbed by the
273 /// entity resolver and do not break the run — see
274 /// [`Self::current_event_is_last_text`] for the rationale.
275 ///
276 /// [`Text`]: PayloadEvent::Text
277 /// [`CData`]: PayloadEvent::CData
278 fn drain_text(&mut self, mut result: Cow<'i, str>) -> Result<DeEvent<'i>, DeError> {
279 loop {
280 if self.current_event_is_last_text() {
281 break;
282 }
283
284 match self.next_impl()? {
285 PayloadEvent::Text(e) => result
286 .to_mut()
287 .push_str(&e.xml_content(self.reader.xml_version())?),
288 PayloadEvent::CData(e) => result
289 .to_mut()
290 .push_str(&e.xml_content(self.reader.xml_version())?),
291 PayloadEvent::GeneralRef(e) => self.resolve_reference(result.to_mut(), e)?,
292 PayloadEvent::DocType(e) => {
293 self.entity_resolver
294 .capture(e)
295 .map_err(|err| DeError::Custom(format!("cannot parse DTD: {}", err)))?;
296 }
297
298 // SAFETY: current_event_is_last_text checks that event is Text, CData, GeneralRef, or DocType
299 _ => unreachable!(
300 "Only `Text`, `CData`, `GeneralRef` or `DocType` events can come here"
301 ),
302 }
303 }
304 Ok(DeEvent::Text(Text::new(result)))
305 }
306
307 /// Return an input-borrowing event.
308 pub(crate) fn next(&mut self) -> Result<DeEvent<'i>, DeError> {
309 loop {
310 return match self.next_impl()? {
311 PayloadEvent::Start(e) => Ok(DeEvent::Start(e)),
312 PayloadEvent::End(e) => Ok(DeEvent::End(e)),
313 PayloadEvent::Text(e) => self.drain_text(e.xml_content(self.reader.xml_version())?),
314 PayloadEvent::CData(e) => {
315 self.drain_text(e.xml_content(self.reader.xml_version())?)
316 }
317 PayloadEvent::DocType(e) => {
318 self.entity_resolver
319 .capture(e)
320 .map_err(|err| DeError::Custom(format!("cannot parse DTD: {}", err)))?;
321 continue;
322 }
323 PayloadEvent::GeneralRef(e) => {
324 let mut text = String::new();
325 self.resolve_reference(&mut text, e)?;
326 self.drain_text(text.into())
327 }
328 PayloadEvent::Eof => Ok(DeEvent::Eof),
329 };
330 }
331 }
332
333 fn resolve_reference(&mut self, result: &mut String, event: BytesRef) -> Result<(), DeError> {
334 let len = event.len();
335 let reference = self.decoder().decode(&event)?;
336
337 if let Some(num) = reference.strip_prefix('#') {
338 let codepoint = parse_number(num).map_err(EscapeError::InvalidCharRef)?;
339 result.push_str(codepoint.encode_utf8(&mut [0u8; 4]));
340 return Ok(());
341 }
342 if let Some(value) = self.entity_resolver.resolve(reference.as_ref()) {
343 result.push_str(value);
344 return Ok(());
345 }
346 Err(EscapeError::UnrecognizedEntity(0..len, reference.to_string()).into())
347 }
348
349 #[inline]
350 pub(crate) fn read_to_end(&mut self, name: QName) -> Result<(), DeError> {
351 match self.lookahead {
352 // We pre-read event with the same name that is required to be skipped.
353 // First call of `read_to_end` will end out pre-read event, the second
354 // will consume other events
355 Ok(PayloadEvent::Start(ref e)) if e.name() == name => {
356 let result1 = self.reader.read_to_end(name);
357 let result2 = self.reader.read_to_end(name);
358
359 // In case of error `next_impl` returns `Eof`
360 let _ = self.next_impl();
361 result1?;
362 result2?;
363 }
364 // We pre-read event with the same name that is required to be skipped.
365 // Because this is end event, we already consume the whole tree, so
366 // nothing to do, just update lookahead
367 Ok(PayloadEvent::End(ref e)) if e.name() == name => {
368 let _ = self.next_impl();
369 }
370 Ok(_) => {
371 let result = self.reader.read_to_end(name);
372
373 // In case of error `next_impl` returns `Eof`
374 let _ = self.next_impl();
375 result?;
376 }
377 // Read next lookahead event, unpack error from the current lookahead
378 Err(_) => {
379 self.next_impl()?;
380 }
381 }
382 Ok(())
383 }
384
385 #[inline]
386 pub(crate) fn decoder(&self) -> Decoder {
387 self.reader.decoder()
388 }
389}