parco_xml/dexml/
reader.rs

1use std::borrow::Cow;
2
3use crate::{
4    DeXml, DeXmlError,
5    de::AppendPath,
6    dexml::{
7        lex::{CloseAngle, Lexer, OpenAngle, Slash, Text},
8        parse::Action,
9    },
10};
11
12/// controls XML lexing and parsing
13#[derive(Clone, Debug)]
14pub struct Reader<'a> {
15    pub(super) lexer: Lexer<'a>,
16    pub(super) path: String,
17}
18
19impl<'a> Reader<'a> {
20    /// construct a new lexer from the xml input string and start an empty `path`
21    pub const fn new(xml: &'a str) -> Self {
22        Self {
23            lexer: Lexer::new(xml),
24            path: String::new(),
25        }
26    }
27
28    /// get the path for debugging where you are in the xml tree
29    pub const fn path(&self) -> &str {
30        self.path.as_str()
31    }
32
33    /// attempt to parse "\<\[TAG\]" where TAG is your input, for example `<soap:Envelope` can be parsed via `Reader::open_tag(..., "Envelope")` from the xml input string
34    pub fn open_tag(&mut self, tag: &str) -> Result<(), DeXmlError> {
35        self.append_path(AppendPath::Element(tag));
36
37        self.parse::<OpenAngle>(Action::OpenTag)?;
38        let tag_found = self.parse_tag(Action::OpenTag)?;
39
40        match tag_found.0 == tag {
41            true => Ok(()),
42            false => Err(self.err(Cow::Borrowed(DeXmlError::EXPECTED_ELEMENT))),
43        }
44    }
45
46    /// attempt to close a tag via "\<\/TAG\>" doesn't check if the tag is specific name
47    pub fn close_tag(&mut self) -> Result<(), DeXmlError> {
48        self.parse::<OpenAngle>(Action::CloseTag)?;
49        self.parse::<Slash>(Action::CloseTag)?;
50        self.parse_tag(Action::CloseTag)?;
51        self.parse::<CloseAngle>(Action::CloseTag)?;
52        self.exit_path();
53        Ok(())
54    }
55
56    /// attempt to grab a text node from the xml
57    pub fn text(&mut self) -> Result<&'a str, DeXmlError> {
58        self.append_path(AppendPath::Text);
59
60        match self.parse::<Text>(Action::Text) {
61            Ok(Text(text)) => {
62                self.exit_path();
63                Ok(text.trim())
64            }
65            Err(err) => Err(err),
66        }
67    }
68
69    /// return an error with the current path and xml input src. The message can be owned or static borrow
70    pub fn err(&mut self, message: Cow<'static, str>) -> DeXmlError {
71        DeXmlError {
72            message,
73            path: std::mem::take(&mut self.path),
74            xml: self.lexer.xml().into(),
75        }
76    }
77
78    /// `visit` a type that impls [`DeXml`] if you have a custom type this is where it is parsed and deserialized
79    ///
80    /// if you are needing to parse a sub document see [`Reader::dexml`]
81    pub fn visit<T: DeXml<'a>>(&mut self) -> Result<T, DeXmlError> {
82        T::dexml_reader(self)
83    }
84
85    /// similar to [`Reader::visit`] but used for attribute parsing for example
86    ///
87    /// when you call [`Reader::attr`] or [`Reader::attr_opt`] you will end up with the attribute value as a [str] slice
88    /// and you may want to parse it into a custom type
89    ///
90    /// of course if this parsing fails, you want some error logic to show the current path of the parser and where it is the xml tree
91    /// for debugging. this method does exactly that, provides a new lexer and reader and parses the [str] as if it were a different document
92    /// but moves over it's path, after parsing is complete takes back the path into the crrent reader
93    pub fn dexml<T: DeXml<'a>>(&mut self, arg: &'a str) -> Result<T, DeXmlError> {
94        let mut sub_reader = Self {
95            lexer: Lexer::new(arg),
96            path: ::std::mem::take(&mut self.path),
97        };
98        let res = T::dexml_reader(&mut sub_reader)?;
99        self.path = sub_reader.path;
100        Ok(res)
101    }
102}