xml-stinks 0.1.0

Powerful & easy manual XML deserialization using quick-xml
Documentation
//! Buffered XML deserializer.
use std::convert::Infallible;
use std::io::BufRead;

use quick_xml::events::Event;
use quick_xml::Reader;

use crate::deserializer::{Deserializer, Error, IgnoreEnd, MaybeStatic};
use crate::event::EventExt;
use crate::tagged::TagStart;
use crate::util::trait_alias;
use crate::DeserializeTagged;

trait_alias! {
    /// A XML deserializer source with an internal buffer.
    pub Source: BufRead + MaybeStatic;
}

/// XML deserializer using a source which has an internal buffer.
pub struct Buffered<TSource: Source>
{
    reader: Reader<TSource>,
    leftover_event: Option<Event<'static>>,
    buf: Vec<u8>,
}

impl<TSource: Source> Buffered<TSource>
{
    /// Returns a new [`Buffered`].
    pub fn new(source: TSource) -> Self
    {
        let mut reader = Reader::from_reader(source);

        reader.trim_text(true);
        reader.expand_empty_elements(true);

        Self {
            reader,
            leftover_event: None,
            buf: Vec::new(),
        }
    }
}

impl<TSource: Source> Deserializer for Buffered<TSource>
{
    fn de_tag<De: DeserializeTagged>(
        &mut self,
        tag_name: &str,
        ignore_end: IgnoreEnd,
    ) -> Result<De, Error<De::Error>>
    {
        self.de_tag_with(tag_name, ignore_end, De::deserialize)
    }

    fn de_tag_with<Output, Err, DeserializeFn>(
        &mut self,
        tag_name: &str,
        ignore_end: IgnoreEnd,
        deserialize: DeserializeFn,
    ) -> Result<Output, Error<Err>>
    where
        Err: std::error::Error + Send + Sync + 'static,
        DeserializeFn: FnOnce(&TagStart, &mut Self) -> Result<Output, Err>,
    {
        let deserialized = match self.read_event()? {
            Event::Start(start) if start.name().as_ref() == tag_name.as_bytes() => {
                deserialize(&TagStart::from_inner(start), self)
                    .map_err(Error::DeserializeFailed)?
            }
            event => {
                self.leftover_event = Some(event.clone().into_owned());

                return Err(Error::UnexpectedEvent {
                    expected_event_name: format!("start({tag_name})"),
                    found_event: event.describe().unwrap(),
                });
            }
        };

        if let IgnoreEnd::No = ignore_end {
            self.read_end_event(tag_name.as_bytes())
                .map_err(Error::into_with_de_error)?;
        }

        Ok(deserialized)
    }

    fn de_tag_list<De: DeserializeTagged, Name: AsRef<str>>(
        &mut self,
        tag_name: Option<Name>,
    ) -> Result<Vec<De>, Error<De::Error>>
    {
        let mut deserialized_items = Vec::new();

        loop {
            let start = match self.read_event()? {
                Event::Start(start)
                    if tag_name.as_ref().map_or_else(
                        || true,
                        |expected_tag_name| {
                            start.name().as_ref() == expected_tag_name.as_ref().as_bytes()
                        },
                    ) =>
                {
                    TagStart::from_inner(start)
                }
                Event::Comment(_) => {
                    continue;
                }
                event => {
                    self.leftover_event = Some(event.into_owned());
                    break;
                }
            };

            let deserialized =
                De::deserialize(&start, self).map_err(Error::DeserializeFailed)?;

            self.read_end_event(start.name_bytes())
                .map_err(Error::into_with_de_error)?;

            deserialized_items.push(deserialized);
        }

        Ok(deserialized_items)
    }

    fn de_text(&mut self) -> Result<String, Error<Infallible>>
    {
        let text = match self.read_event::<Infallible>()? {
            Event::Text(text) => Ok(text),
            event => {
                self.leftover_event = Some(event.clone().into_owned());

                Err(Error::<Infallible>::UnexpectedEvent {
                    expected_event_name: "text".to_string(),
                    found_event: event.describe().unwrap(),
                })
            }
        }?
        .unescape()
        .map_err(|err| Error::<Infallible>::XMLError(err.into()))?;

        Ok(text.to_string())
    }

    fn skip_to_tag_start(&mut self, tag_name: &str) -> Result<(), Error<Infallible>>
    {
        loop {
            match self.read_event::<Infallible>()? {
                Event::Start(start) if start.name().as_ref() == tag_name.as_bytes() => {
                    self.leftover_event = Some(Event::Start(start).into_owned());

                    break;
                }
                _ => {}
            }
        }

        Ok(())
    }

    fn skip_to_tag_end(&mut self, tag_name: &str) -> Result<(), Error<Infallible>>
    {
        loop {
            match self.read_event::<Infallible>()? {
                Event::End(end) if end.name().as_ref() == tag_name.as_bytes() => {
                    self.leftover_event = Some(Event::End(end).into_owned());

                    return Ok(());
                }
                _ => {}
            }
        }
    }
}

impl<TSource: Source> Buffered<TSource>
{
    fn read_end_event(&mut self, tag_name: &[u8]) -> Result<(), Error<Infallible>>
    {
        let event = self.read_event::<Infallible>()?;

        if matches!(&event, Event::End(end) if end.name().as_ref() == tag_name) {
            return Ok(());
        }

        Err(Error::UnexpectedEvent {
            expected_event_name: "end".to_string(),
            found_event: event.describe().unwrap(),
        })
    }

    fn read_event<DeError>(&mut self) -> Result<Event<'static>, Error<DeError>>
    {
        let event = if let Some(leftover_event) = self.leftover_event.take() {
            leftover_event
        } else {
            self.reader
                .read_event_into(&mut self.buf)
                .map_err(|err| Error::<DeError>::XMLError(err.into()))?
                .into_owned()
        };

        if let Event::Eof = &event {
            return Err(Error::UnexpectedEndOfFile);
        }

        Ok(event)
    }
}