Skip to main content

opcua_xml/encoding/
reader.rs

1use std::{
2    io::{BufReader, Read},
3    num::{ParseFloatError, ParseIntError},
4    str::FromStr,
5};
6
7use quick_xml::events::Event;
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11/// Error produced when reading XML.
12pub enum XmlReadError {
13    #[error("{0}")]
14    /// Failed to parse XML.
15    Xml(#[from] quick_xml::Error),
16    #[error("{0}")]
17    /// Failed to decode XML value.
18    Decode(#[from] quick_xml::encoding::EncodingError),
19    #[error("Unexpected EOF")]
20    /// Unexpected EOF.
21    UnexpectedEof,
22    #[error("Failed to parse integer: {0}")]
23    /// Failed to parse value as integer.
24    ParseInt(#[from] ParseIntError),
25    #[error("Failed to parse float: {0}")]
26    /// Failed to parse value as float.
27    ParseFloat(#[from] ParseFloatError),
28    #[error("Failed to parse value: {0}")]
29    /// Some other parse error.
30    Parse(String),
31}
32
33/// XML stream reader specialized for working with OPC-UA XML.
34pub struct XmlStreamReader<T> {
35    reader: quick_xml::Reader<BufReader<T>>,
36    buffer: Vec<u8>,
37}
38
39impl<T: Read> XmlStreamReader<T> {
40    /// Create a new stream reader with an internal buffer.
41    pub fn new(reader: T) -> Self {
42        Self {
43            reader: quick_xml::Reader::from_reader(BufReader::new(reader)),
44            buffer: Vec::new(),
45        }
46    }
47
48    /// Get the next event from the stream.
49    pub fn next_event(&mut self) -> Result<quick_xml::events::Event<'_>, XmlReadError> {
50        self.buffer.clear();
51        Ok(self.reader.read_event_into(&mut self.buffer)?)
52    }
53
54    /// Skip the current value. This should be called after encountering a
55    /// `Start` event, and will skip until the corresponding `End` event is consumed.
56    ///
57    /// Note that this does not check that the document is coherent, just that
58    /// an equal number of start and end events are consumed.
59    pub fn skip_value(&mut self) -> Result<(), XmlReadError> {
60        let mut depth = 1u32;
61        loop {
62            match self.next_event()? {
63                Event::Start(_) => depth += 1,
64                Event::End(_) => {
65                    depth -= 1;
66                    if depth == 0 {
67                        return Ok(());
68                    }
69                }
70                Event::Eof => {
71                    if depth == 1 {
72                        return Ok(());
73                    } else {
74                        return Err(XmlReadError::UnexpectedEof);
75                    }
76                }
77                _ => {}
78            }
79        }
80    }
81
82    /// Consume the current event, skipping any child elements and returning the combined text
83    /// content with leading and trailing whitespace removed.
84    /// Note that if there are multiple text elements they will be concatenated, but
85    /// whitespace between these will not be removed.
86    pub fn consume_as_text(&mut self) -> Result<String, XmlReadError> {
87        let mut text: Option<String> = None;
88        let mut depth = 1u32;
89        loop {
90            match self.next_event()? {
91                Event::Start(_) => depth += 1,
92                Event::End(_) => {
93                    depth -= 1;
94                    if depth == 0 {
95                        if let Some(mut text) = text {
96                            let trimmed = text.trim_ascii_end();
97                            text.truncate(trimmed.len());
98                            return Ok(text);
99                        } else {
100                            return Ok(String::new());
101                        }
102                    }
103                }
104                Event::Text(mut e) => {
105                    if depth != 1 {
106                        continue;
107                    }
108                    if let Some(text) = text.as_mut() {
109                        text.push_str(&e.decode()?);
110                    } else if e.inplace_trim_start() {
111                        continue;
112                    } else {
113                        text = Some(e.decode()?.into_owned());
114                    }
115                }
116
117                Event::Eof => {
118                    if depth == 1 {
119                        if let Some(mut text) = text {
120                            let trimmed = text.trim_ascii_end();
121                            text.truncate(trimmed.len());
122                            return Ok(text);
123                        } else {
124                            return Ok(String::new());
125                        }
126                    } else {
127                        return Err(XmlReadError::UnexpectedEof);
128                    }
129                }
130                _ => continue,
131            }
132        }
133    }
134
135    /// Consume the current element as a raw array of bytes.
136    pub fn consume_raw(&mut self) -> Result<Vec<u8>, XmlReadError> {
137        let mut out = Vec::new();
138        let mut depth = 1u32;
139        // quick-xml doesn't really have a way to do this, and in fact does not capture the full event,
140        // fortunately the way it does capture each event is quite predictable, so we can reconstruct
141        // the input.
142        // We do need the parser, since we only want to read the current element.
143        loop {
144            let evt = self.next_event()?;
145            match evt {
146                Event::Start(s) => {
147                    depth += 1;
148                    out.push(b'<');
149                    out.extend_from_slice(&s);
150                    out.push(b'>');
151                }
152                Event::End(s) => {
153                    depth -= 1;
154                    if depth == 0 {
155                        return Ok(out);
156                    }
157                    out.extend_from_slice(b"</");
158                    out.extend_from_slice(&s);
159                    out.push(b'>');
160                }
161                Event::CData(s) => {
162                    out.extend_from_slice(b"<![CDATA[");
163                    out.extend_from_slice(&s);
164                    out.extend_from_slice(b"]]>");
165                }
166                Event::Comment(s) => {
167                    out.extend_from_slice(b"<!--");
168                    out.extend_from_slice(&s);
169                    out.extend_from_slice(b"-->");
170                }
171                Event::Decl(s) => {
172                    out.extend_from_slice(b"<?");
173                    out.extend_from_slice(&s);
174                    out.extend_from_slice(b"?>");
175                }
176                Event::DocType(s) => {
177                    out.extend_from_slice(b"<!DOCTYPE");
178                    out.extend_from_slice(&s);
179                    out.push(b'>');
180                }
181                Event::Empty(s) => {
182                    out.push(b'<');
183                    out.extend_from_slice(&s);
184                    out.extend_from_slice(b"/>");
185                }
186                Event::PI(s) => {
187                    out.extend_from_slice(b"<?");
188                    out.extend_from_slice(&s);
189                    out.extend_from_slice(b"?>");
190                }
191                Event::Text(s) => {
192                    out.extend_from_slice(&s);
193                }
194                Event::Eof => {
195                    if depth == 1 {
196                        return Ok(out);
197                    } else {
198                        return Err(XmlReadError::UnexpectedEof);
199                    }
200                }
201                Event::GeneralRef(s) => {
202                    out.push(b'&');
203                    out.extend_from_slice(&s);
204                    out.push(b';');
205                }
206            }
207        }
208    }
209
210    /// Consume the current node as a text value and parse it as the given type.
211    pub fn consume_content<R: FromStr>(&mut self) -> Result<R, XmlReadError>
212    where
213        XmlReadError: From<<R as FromStr>::Err>,
214    {
215        let text = self.consume_as_text()?;
216        Ok(text.parse()?)
217    }
218}
219
220#[cfg(test)]
221mod test {
222    use std::io::Cursor;
223
224    use quick_xml::events::Event;
225
226    #[test]
227    fn test_xml_text_comments() {
228        let xml = r#"
229        <Foo>
230            Ho
231            <Bar>
232            Hello
233            </Bar>
234            Hello <!-- Comment --> there
235        </Foo>
236        "#;
237        let mut cursor = Cursor::new(xml.as_bytes());
238        let mut reader = super::XmlStreamReader::new(&mut cursor);
239        // You canend up with text everywhere. Any loading needs to account for this.
240        assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
241        assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
242        assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
243        assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
244        assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
245        assert!(matches!(reader.next_event().unwrap(), Event::End(_)));
246        assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
247        assert!(matches!(reader.next_event().unwrap(), Event::Comment(_)));
248        assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
249        assert!(matches!(reader.next_event().unwrap(), Event::End(_)));
250        assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
251        assert!(matches!(reader.next_event().unwrap(), Event::Eof));
252        assert!(matches!(reader.next_event().unwrap(), Event::Eof));
253    }
254
255    #[test]
256    fn test_consume_as_text() {
257        let xml = r#"<Foo>
258            <Bar>
259            Hello
260            </Bar>
261            Hello <!-- Comment -->there
262        </Foo>"#;
263
264        let mut cursor = Cursor::new(xml.as_bytes());
265        let mut reader = super::XmlStreamReader::new(&mut cursor);
266
267        assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
268        assert_eq!(reader.consume_as_text().unwrap(), "Hello there");
269    }
270
271    #[test]
272    fn test_consume_content() {
273        let xml = r#"<Foo>
274            12345
275        </Foo>"#;
276        let mut cursor = Cursor::new(xml.as_bytes());
277        let mut reader = super::XmlStreamReader::new(&mut cursor);
278
279        assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
280        assert_eq!(reader.consume_content::<u32>().unwrap(), 12345);
281    }
282
283    #[test]
284    fn test_consume_raw() {
285        let xml = r#"<Foo>
286<Bar>
287    Hello <!-- Comment here -->
288    More text
289</Bar>
290<Bar attr = "foo" />
291<? Mystery PI ?>
292</Foo>"#;
293        let mut cursor = Cursor::new(xml.as_bytes());
294        let mut reader = super::XmlStreamReader::new(&mut cursor);
295        assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
296        let raw = reader.consume_raw().unwrap();
297        println!("{}", String::from_utf8_lossy(&raw));
298        assert_eq!(&xml.as_bytes()[5..(xml.len() - 6)], &*raw);
299    }
300}