aboutsummaryrefslogtreecommitdiff
path: root/src/attribute.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2023-04-15 18:26:29 +0200
committerHampusM <hampus@hampusmat.com>2023-05-09 19:51:02 +0200
commite762babd9e69400ccd178ba8946168640093eb63 (patch)
treee07a56940f0c4a3551c87afad80bb949969335c7 /src/attribute.rs
parentda509c366972ac6d423f95732cd3d319a2265841 (diff)
feat: add deserialization
Diffstat (limited to 'src/attribute.rs')
-rw-r--r--src/attribute.rs170
1 files changed, 170 insertions, 0 deletions
diff --git a/src/attribute.rs b/src/attribute.rs
new file mode 100644
index 0000000..8fb4778
--- /dev/null
+++ b/src/attribute.rs
@@ -0,0 +1,170 @@
+//! Attribute.
+
+use quick_xml::events::attributes::{
+ AttrError,
+ Attribute as QuickXMLAttribute,
+ Attributes,
+};
+
+/// Represent a XML attribute.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Attribute<'a>
+{
+ inner: QuickXMLAttribute<'a>,
+}
+
+impl<'a> Attribute<'a>
+{
+ /// Attribute key.
+ #[must_use]
+ pub fn key(&self) -> &[u8]
+ {
+ self.inner.key.as_ref()
+ }
+
+ /// Attribute value.
+ #[must_use]
+ pub fn value(&self) -> &[u8]
+ {
+ &self.inner.value
+ }
+}
+
+// Crate-local functions
+impl<'a> Attribute<'a>
+{
+ pub(crate) fn from_inner(inner: QuickXMLAttribute<'a>) -> Self
+ {
+ Self { inner }
+ }
+}
+
+/// Errors that can be raised when parsing [`Attribute`]s.
+///
+/// Recovery position in examples shows the position from which parsing of the
+/// next attribute will be attempted.
+#[derive(Debug, thiserror::Error)]
+#[non_exhaustive]
+pub enum Error
+{
+ /// Attribute key was not followed by `=`, position relative to the start of
+ /// the owning tag is provided.
+ ///
+ /// Example of input that raises this error:
+ /// ```xml
+ /// <tag key another="attribute"/>
+ /// <!-- ^~~ error position, recovery position (8) -->
+ /// ```
+ #[error("Position {0}: attribute key must be directly followed by `=` or space")]
+ ExpectedEq(usize),
+
+ /// Attribute value was not found after `=`, position relative to the start
+ /// of the owning tag is provided.
+ ///
+ /// Example of input that raises this error:
+ /// ```xml
+ /// <tag key = />
+ /// <!-- ^~~ error position, recovery position (10) -->
+ /// ```
+ ///
+ /// This error can be returned only for the last attribute in the list,
+ /// because otherwise any content after `=` will be threated as a value.
+ /// The XML
+ /// ```xml
+ /// <tag key = another-key = "value"/>
+ /// <!-- ^ ^- recovery position (24) -->
+ /// <!-- '~~ error position (22) -->
+ /// ```
+ ///
+ /// will be treated as `Attribute { key = b"key", value = b"another-key" }`
+ /// and or [`Attribute`] is returned, or [`Error::UnquotedValue`] is raised,
+ /// depending on the parsing mode.
+ #[error("Position {0}: `=` must be followed by an attribute value")]
+ ExpectedValue(usize),
+
+ /// Attribute value is not quoted, position relative to the start of the
+ /// owning tag is provided.
+ ///
+ /// Example of input that raises this error:
+ /// ```xml
+ /// <tag key = value />
+ /// <!-- ^ ^~~ recovery position (15) -->
+ /// <!-- '~~ error position (10) -->
+ /// ```
+ #[error("Position {0}: attribute value must be enclosed in `\"` or `'`")]
+ UnquotedValue(usize),
+
+ /// Attribute value was not finished with a matching quote, position relative
+ /// to the start of owning tag and a quote is provided. That position is always
+ /// a last character in the tag content.
+ ///
+ /// Example of input that raises this error:
+ /// ```xml
+ /// <tag key = "value />
+ /// <tag key = 'value />
+ /// <!-- ^~~ error position, recovery position (18) -->
+ /// ```
+ ///
+ /// This error can be returned only for the last attribute in the list,
+ /// because all input was consumed during scanning for a quote.
+ #[error("Position {0}: missing closing quote `{1}` in attribute value")]
+ ExpectedQuote(usize, u8),
+
+ /// An attribute with the same name was already encountered. Two parameters
+ /// define (1) the error position relative to the start of the owning tag
+ /// for a new attribute and (2) the start position of a previously encountered
+ /// attribute with the same name.
+ ///
+ /// Example of input that raises this error:
+ /// ```xml
+ /// <tag key = 'value' key="value2" attr3='value3' />
+ /// <!-- ^ ^ ^~~ recovery position (32) -->
+ /// <!-- | '~~ error position (19) -->
+ /// <!-- '~~ previous position (4) -->
+ /// ```
+ #[error("Position {0}: duplicated attribute, previous declaration at position {1}")]
+ Duplicated(usize, usize),
+}
+
+impl From<AttrError> for Error
+{
+ fn from(attr_err: AttrError) -> Self
+ {
+ match attr_err {
+ AttrError::ExpectedEq(pos) => Self::ExpectedEq(pos),
+ AttrError::ExpectedValue(pos) => Self::ExpectedValue(pos),
+ AttrError::UnquotedValue(pos) => Self::UnquotedValue(pos),
+ AttrError::ExpectedQuote(pos, quote) => Self::ExpectedQuote(pos, quote),
+ AttrError::Duplicated(pos, same_attr_pos) => {
+ Self::Duplicated(pos, same_attr_pos)
+ }
+ }
+ }
+}
+
+/// Iterates through [`Attribute`]s.
+#[derive(Debug)]
+pub struct Iter<'a>
+{
+ attrs: Attributes<'a>,
+}
+
+impl<'a> Iter<'a>
+{
+ pub(crate) fn new(attrs: Attributes<'a>) -> Self
+ {
+ Self { attrs }
+ }
+}
+
+impl<'a> Iterator for Iter<'a>
+{
+ type Item = Result<Attribute<'a>, Error>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let attr = self.attrs.next()?;
+
+ Some(attr.map(Attribute::from_inner).map_err(Into::into))
+ }
+}