android-manifest 0.4.0

Android Manifest serializer and deserializer for Rust
Documentation
//! Internal Android manifest XML deserialization.

use super::XmlDeserialize;
use std::io::Read;
use xml::name::OwnedName;
use xml::reader::{EventReader, ParserConfig, XmlEvent};

pub fn from_str<T: XmlDeserialize>(s: &str) -> Result<T, String> {
    from_reader(s.as_bytes())
}

pub fn from_reader<R: Read, T: XmlDeserialize>(reader: R) -> Result<T, String> {
    <T as XmlDeserialize>::deserialize(&mut Deserializer::new_from_reader(reader))
}

pub struct Deserializer<R: Read> {
    depth: usize,
    reader: EventReader<R>,
    peeked: Option<XmlEvent>,
}

impl<R: Read> Deserializer<R> {
    pub fn new(reader: EventReader<R>) -> Self {
        Deserializer {
            depth: 0,
            reader,
            peeked: None,
        }
    }

    pub fn new_from_reader(reader: R) -> Self {
        let config = ParserConfig::new()
            .trim_whitespace(true)
            .whitespace_to_characters(true)
            .cdata_to_characters(true)
            .ignore_comments(true)
            .coalesce_characters(true);

        Self::new(EventReader::new_with_config(reader, config))
    }

    pub fn peek(&mut self) -> Result<&XmlEvent, String> {
        if self.peeked.is_none() {
            self.peeked = Some(self.inner_next()?);
        }

        if let Some(ref next) = self.peeked {
            Ok(next)
        } else {
            Err("unable to peek next item".into())
        }
    }

    pub fn inner_next(&mut self) -> Result<XmlEvent, String> {
        loop {
            match self.reader.next() {
                Ok(next) => {
                    match next {
                        XmlEvent::StartDocument { .. }
                        | XmlEvent::ProcessingInstruction { .. }
                        | XmlEvent::Comment(_) => { /* skip */ }
                        other => return Ok(other),
                    }
                }
                Err(msg) => {
                    return Err(msg.msg().to_string());
                }
            }
        }
    }

    pub fn next_event(&mut self) -> Result<XmlEvent, String> {
        let next_event = if let Some(peeked) = self.peeked.take() {
            peeked
        } else {
            self.inner_next()?
        };
        match next_event {
            XmlEvent::StartElement { .. } => {
                self.depth += 1;
            }
            XmlEvent::EndElement { .. } => {
                self.depth = self
                    .depth
                    .checked_sub(1)
                    .ok_or_else(|| "unexpected closing element".to_string())?;
            }
            _ => {}
        }
        log::debug!("Fetched {:?}, new depth {}", next_event, self.depth);
        Ok(next_event)
    }

    pub fn skip_element(&mut self, mut cb: impl FnMut(&XmlEvent)) -> Result<(), String> {
        let depth = self.depth;

        while self.depth >= depth {
            cb(&self.next_event()?);
        }

        Ok(())
    }

    pub fn depth(&self) -> usize {
        self.depth
    }

    #[allow(dead_code)] // Used by generated code for nested value forms.
    pub fn read_inner_value<T, F: FnOnce(&mut Self) -> Result<T, String>>(
        &mut self,
        f: F,
    ) -> Result<T, String> {
        if let XmlEvent::StartElement { name, .. } = self.next_event()? {
            let result = f(self)?;
            self.expect_end_element(&name)?;
            Ok(result)
        } else {
            Err("expected a start element".to_string())
        }
    }

    #[allow(dead_code)] // Used by generated code for nested value forms.
    pub fn expect_end_element(&mut self, start_name: &OwnedName) -> Result<(), String> {
        match self.next_event()? {
            XmlEvent::EndElement { name } if name == *start_name => Ok(()),
            XmlEvent::EndElement { name } => Err(format!(
                "end tag </{}> did not match start tag <{}>",
                name.local_name, start_name.local_name
            )),
            _ => Err(format!("expected end tag </{}>", start_name.local_name)),
        }
    }
}