Skip to main content

xml/reader/
events.rs

1//! Contains `XmlEvent` datatype, instances of which are emitted by the parser.
2
3use crate::attribute::OwnedAttribute;
4use crate::common::XmlVersion;
5use crate::name::OwnedName;
6use crate::namespace::Namespace;
7use std::fmt;
8
9/// An element of an XML input stream.
10///
11/// Items of this enum are emitted by `reader::EventReader`. They correspond to different
12/// elements of an XML document.
13#[derive(PartialEq, Clone)]
14pub enum XmlEvent {
15    /// Corresponds to XML document declaration.
16    ///
17    /// This event is always emitted before any other event. It is emitted
18    /// even if the actual declaration is not present in the document.
19    StartDocument {
20        /// XML version.
21        ///
22        /// If XML declaration is not present, defaults to `Version10`.
23        version: XmlVersion,
24
25        /// XML document encoding.
26        ///
27        /// If XML declaration is not present or does not contain `encoding` attribute,
28        /// defaults to `"UTF-8"`. This field is currently used for no other purpose than
29        /// informational.
30        encoding: String,
31
32        /// XML standalone declaration.
33        ///
34        /// If XML document is not present or does not contain `standalone` attribute,
35        /// defaults to `None`. This field is currently used for no other purpose than
36        /// informational.
37        standalone: Option<bool>,
38    },
39
40    /// Denotes to the end of the document stream.
41    ///
42    /// This event is always emitted after any other event (except `Error`). After it
43    /// is emitted for the first time, it will always be emitted on next event pull attempts.
44    EndDocument,
45
46    /// Denotes an XML processing instruction.
47    ///
48    /// This event contains a processing instruction target (`name`) and opaque `data`. It
49    /// is up to the application to process them.
50    ProcessingInstruction {
51        /// Processing instruction target.
52        name: String,
53
54        /// Processing instruction content.
55        data: Option<String>,
56    },
57
58    /// Denotes a beginning of an XML element.
59    ///
60    /// This event is emitted after parsing opening tags or after parsing bodiless tags. In the
61    /// latter case `EndElement` event immediately follows.
62    StartElement {
63        /// Qualified name of the element.
64        name: OwnedName,
65
66        /// A list of attributes associated with the element.
67        ///
68        /// Currently attributes are not checked for duplicates (TODO)
69        attributes: Vec<OwnedAttribute>,
70
71        /// Contents of the namespace mapping at this point of the document.
72        namespace: Namespace,
73    },
74
75    /// Denotes an end of an XML element.
76    ///
77    /// This event is emitted after parsing closing tags or after parsing bodiless tags. In the
78    /// latter case it is emitted immediately after corresponding `StartElement` event.
79    EndElement {
80        /// Qualified name of the element.
81        name: OwnedName,
82    },
83
84    /// Denotes CDATA content.
85    ///
86    /// This event contains unparsed data. No unescaping will be performed.
87    ///
88    /// It is possible to configure a parser to emit `Characters` event instead of `CData`. See
89    /// `pull::ParserConfiguration` structure for more information.
90    CData(String),
91
92    /// Denotes a comment.
93    ///
94    /// It is possible to configure a parser to ignore comments, so this event will never be emitted.
95    /// See `pull::ParserConfiguration` structure for more information.
96    Comment(String),
97
98    /// Denotes character data outside of tags.
99    ///
100    /// Contents of this event will always be unescaped, so no entities like `&lt;` or `&amp;` or `&#123;`
101    /// will appear in it.
102    ///
103    /// It is possible to configure a parser to trim leading and trailing whitespace for this event.
104    /// See `pull::ParserConfiguration` structure for more information.
105    Characters(String),
106
107    /// Denotes a chunk of whitespace outside of tags.
108    ///
109    /// It is possible to configure a parser to emit `Characters` event instead of `Whitespace`.
110    /// See `pull::ParserConfiguration` structure for more information. When combined with whitespace
111    /// trimming, it will eliminate standalone whitespace from the event stream completely.
112    Whitespace(String),
113    /// The whole DOCTYPE markup
114    Doctype {
115        /// Everything including `<` and `>`
116        syntax: String,
117    },
118}
119
120/// Supplement to the Doctype event (use the event if you want the full syntax)
121pub struct DoctypeRef<'tmp> {
122    pub(crate) syntax: &'tmp str,
123    /// Doctype name, following `<?DOCTYPE `…
124    pub(crate) name: &'tmp str,
125    /// [Public id](https://www.w3.org/TR/xml/#NT-ExternalID) of Doctype, if available.
126    pub(crate) public_id: Option<&'tmp str>,
127    /// [System id](https://www.w3.org/TR/xml/#NT-ExternalID) of Doctype, if available
128    pub(crate) system_id: Option<&'tmp str>,
129}
130
131impl DoctypeRef<'_> {
132    /// Doctype name, following <?DOCTYPE ...
133    #[must_use]
134    pub fn name(&self) -> &str {
135        self.name
136    }
137
138    /// [Public id](https://www.w3.org/TR/xml/#NT-ExternalID) of Doctype, if available.
139    #[must_use]
140    pub fn public_id(&self) -> Option<&str> {
141        self.public_id
142    }
143
144    /// [System id](https://www.w3.org/TR/xml/#NT-ExternalID) of Doctype, if available
145    #[must_use]
146    pub fn system_id(&self) -> Option<&str> {
147        self.system_id
148    }
149}
150
151impl std::ops::Deref for DoctypeRef<'_> {
152    type Target = str;
153
154    /// Don't use it. It's for back-compat with v0.8
155    fn deref(&self) -> &Self::Target {
156        self.syntax
157    }
158}
159
160impl fmt::Debug for XmlEvent {
161    #[cold]
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        match self {
164            Self::StartDocument { version, encoding, standalone } =>
165                write!(f, "StartDocument({}, {}, {:?})", version, *encoding, standalone),
166            Self::EndDocument =>
167                write!(f, "EndDocument"),
168            Self::ProcessingInstruction { name, data } =>
169                write!(f, "ProcessingInstruction({}{})", *name, match data {
170                    Some(data) => format!(", {data}"),
171                    None       => String::new()
172                }),
173            Self::StartElement { name, attributes, namespace: Namespace(namespace) } =>
174                write!(f, "StartElement({}, {:?}{})", name, namespace, if attributes.is_empty() {
175                    String::new()
176                } else {
177                    let attributes: Vec<String> = attributes.iter().map(
178                        |a| format!("{} -> {}", a.name, a.value)
179                    ).collect();
180                    format!(", [{}]", attributes.join(", "))
181                }),
182            Self::EndElement { name } =>
183                write!(f, "EndElement({name})"),
184            Self::Comment(data) =>
185                write!(f, "Comment({data})"),
186            Self::CData(data) =>
187                write!(f, "CData({data})"),
188            Self::Characters(data) =>
189                write!(f, "Characters({data})"),
190            Self::Whitespace(data) =>
191                write!(f, "Whitespace({data})"),
192            Self::Doctype { syntax } =>
193                write!(f, "Doctype({syntax})"),
194        }
195    }
196}
197
198impl XmlEvent {
199    /// Obtains a writer event from this reader event.
200    ///
201    /// This method is useful for streaming processing of XML documents where the output
202    /// is also an XML document. With this method it is possible to process some events
203    /// while passing other events through to the writer unchanged:
204    ///
205    /// ```rust
206    /// use std::str;
207    ///
208    /// use xml::reader::XmlEvent as ReaderEvent;
209    /// use xml::writer::XmlEvent as WriterEvent;
210    /// use xml::{EventReader, EventWriter};
211    ///
212    /// let mut input: &[u8] = b"<hello>world</hello>";
213    /// let mut output: Vec<u8> = Vec::new();
214    ///
215    /// {
216    ///     let mut reader = EventReader::new(&mut input);
217    ///     let mut writer = EventWriter::new(&mut output);
218    ///
219    ///     for e in reader {
220    ///         match e.unwrap() {
221    ///             ReaderEvent::Characters(s) => {
222    ///                 writer.write(WriterEvent::characters(&s.to_uppercase())).unwrap()
223    ///             },
224    ///             e => {
225    ///                 if let Some(e) = e.as_writer_event() {
226    ///                     writer.write(e).unwrap()
227    ///                 }
228    ///             },
229    ///         }
230    ///     }
231    /// }
232    ///
233    /// assert_eq!(
234    ///     str::from_utf8(&output).unwrap(),
235    ///     r#"<?xml version="1.0" encoding="UTF-8"?><hello>WORLD</hello>"#
236    /// );
237    /// ```
238    ///
239    /// Note that this API may change or get additions in future to improve its ergonomics.
240    #[must_use]
241    pub fn as_writer_event(&self) -> Option<crate::writer::events::XmlEvent<'_>> {
242        match self {
243            Self::StartDocument { version, encoding, standalone } =>
244                Some(crate::writer::events::XmlEvent::StartDocument {
245                    version: *version,
246                    encoding: Some(encoding),
247                    standalone: *standalone
248                }),
249            Self::ProcessingInstruction { name, data } =>
250                Some(crate::writer::events::XmlEvent::ProcessingInstruction {
251                    name,
252                    data: data.as_ref().map(|s| &**s)
253                }),
254            Self::StartElement { name, attributes, namespace } =>
255                Some(crate::writer::events::XmlEvent::StartElement {
256                    name: name.borrow(),
257                    attributes: attributes.iter().map(|a| a.borrow()).collect(),
258                    namespace: namespace.borrow(),
259                }),
260            Self::EndElement { name } =>
261                Some(crate::writer::events::XmlEvent::EndElement { name: Some(name.borrow()) }),
262            Self::Comment(data) => Some(crate::writer::events::XmlEvent::Comment(data)),
263            Self::CData(data) => Some(crate::writer::events::XmlEvent::CData(data)),
264            Self::Characters(data) |
265            Self::Whitespace(data) => Some(crate::writer::events::XmlEvent::Characters(data)),
266            Self::Doctype { syntax, .. } => Some(crate::writer::events::XmlEvent::Doctype(syntax)),
267            Self::EndDocument => None,
268        }
269    }
270}