Skip to main content

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
54        Ok(())
55    }
56
57    /// attempt to grab a text node from the xml
58    pub fn text(&mut self) -> Result<&'a str, DeXmlError> {
59        self.append_path(AppendPath::Text);
60
61        match self.parse::<Text>(Action::Text) {
62            Ok(Text(text)) => {
63                self.exit_path();
64                Ok(text.trim())
65            }
66            Err(err) => Err(err),
67        }
68    }
69
70    /// return an error with the current path and xml input src. The message can be owned or static borrow
71    pub fn err(&mut self, message: Cow<'static, str>) -> DeXmlError {
72        DeXmlError {
73            message,
74            path: std::mem::take(&mut self.path),
75            xml: self.lexer.xml().into(),
76        }
77    }
78
79    /// `visit` a type that impls [`DeXml`] if you have a custom type this is where it is parsed and deserialized
80    ///
81    /// if you are needing to parse a sub document see [`Reader::dexml`]
82    pub fn visit<T: DeXml<'a>>(&mut self) -> Result<T, DeXmlError> {
83        T::dexml_reader(self)
84    }
85
86    /// similar to [`Reader::visit`] but used for attribute parsing for example
87    ///
88    /// when you call [`Reader::attr`] or [`Reader::attr_opt`] you will end up with the attribute value as a [str] slice
89    /// and you may want to parse it into a custom type
90    ///
91    /// 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
92    /// for debugging. this method does exactly that, provides a new lexer and reader and parses the [str] as if it were a different document
93    /// but moves over it's path, after parsing is complete takes back the path into the crrent reader
94    pub fn dexml<T: DeXml<'a>>(&mut self, arg: &'a str) -> Result<T, DeXmlError> {
95        let mut sub_reader = Self {
96            lexer: Lexer::new(arg),
97            path: ::std::mem::take(&mut self.path),
98        };
99        let res = T::dexml_reader(&mut sub_reader)?;
100        self.path = sub_reader.path;
101        Ok(res)
102    }
103}