android-abx 0.4.0

Android Binary XML (ABX) parser using winnow
Documentation
use quick_xml::XmlVersion;
use quick_xml::events::{BytesStart, Event as XmlEvent};
use quick_xml::reader::Reader;

use super::AbxWriter;
use crate::{AbxError, Attribute, AttributeValue, Event, InternedStr, Result};

fn utf8(bytes: &[u8]) -> Result<&str> {
    std::str::from_utf8(bytes).map_err(|_| AbxError::InvalidUtf8)
}

fn xml_err(e: impl std::fmt::Display) -> AbxError {
    AbxError::Xml(e.to_string())
}

fn start_tag_attributes(start: &BytesStart) -> Result<Vec<Attribute>> {
    let mut attributes = Vec::new();
    for attr in start.attributes() {
        let attr = attr.map_err(xml_err)?;
        let name = utf8(attr.key.as_ref())?;
        let value = attr
            .normalized_value(XmlVersion::Implicit1_0)
            .map_err(xml_err)?;
        attributes.push(Attribute {
            name: InternedStr::from(name),
            value: AttributeValue::String(value.into_owned()),
        });
    }
    Ok(attributes)
}

/// Encodes XML text as ABX bytes.
///
/// - Every attribute value is written as [`AttributeValue::String`]: no type is
///   inferred, so `version="3"` stays a string.
/// - The `<?xml ...?>` declaration is dropped, and
///   [`StartDocument`](Event::StartDocument)/[`EndDocument`](Event::EndDocument)
///   are added.
/// - Entity references in text become [`Event::EntityReference`]; in attribute
///   values they are decoded.
/// - Namespaces are not processed: `android:name` is a plain name.
///
/// # Errors
///
/// Returns [`AbxError::Xml`] if the XML is malformed, or
/// [`AbxError::ValueTooLong`] if a string is longer than 65,535 bytes.
///
/// # Examples
///
/// ```
/// use android_abx::{AbxParser, AttributeValue, xml_to_abx};
///
/// let data = xml_to_abx(r#"<pkg name="com.example" version="3"/>"#)?;
/// let version = AbxParser::new(&data)?.find_attribute("pkg", "version")?;
/// assert_eq!(version, Some(AttributeValue::String("3".into())));
/// # Ok::<(), android_abx::AbxError>(())
/// ```
pub fn xml_to_abx(xml: &str) -> Result<Vec<u8>> {
    let mut reader = Reader::from_str(xml);
    reader.config_mut().trim_text(false);

    let mut w = AbxWriter::new(Vec::new())?;
    w.write_event(&Event::StartDocument)?;

    loop {
        match reader.read_event().map_err(xml_err)? {
            XmlEvent::Start(start) => {
                let name = utf8(start.name().as_ref())?.to_string();
                let attributes = start_tag_attributes(&start)?;
                w.write_event(&Event::StartTag {
                    name: name.into(),
                    attributes,
                })?;
            }
            XmlEvent::Empty(start) => {
                let name: InternedStr = utf8(start.name().as_ref())?.into();
                let attributes = start_tag_attributes(&start)?;
                w.write_event(&Event::StartTag {
                    name: name.clone(),
                    attributes,
                })?;
                w.write_event(&Event::EndTag { name })?;
            }
            XmlEvent::End(end) => {
                let name = utf8(end.name().as_ref())?.to_string();
                w.write_event(&Event::EndTag { name: name.into() })?;
            }
            XmlEvent::Text(text) => {
                let s = text.decode().map_err(xml_err)?;
                w.write_event(&Event::Text(s.into_owned()))?;
            }
            XmlEvent::GeneralRef(bytes_ref) => {
                let s = bytes_ref.decode().map_err(xml_err)?;
                w.write_event(&Event::EntityReference(s.into_owned()))?;
            }
            XmlEvent::CData(cdata) => {
                let s = utf8(cdata.as_ref())?.to_string();
                w.write_event(&Event::CdataSection(s))?;
            }
            XmlEvent::Comment(comment) => {
                let s = utf8(comment.as_ref())?.to_string();
                w.write_event(&Event::Comment(s))?;
            }
            XmlEvent::PI(pi) => {
                let s = utf8(pi.as_ref())?.to_string();
                w.write_event(&Event::ProcessingInstruction(s))?;
            }
            XmlEvent::DocType(doctype) => {
                let s = utf8(doctype.as_ref())?.to_string();
                w.write_event(&Event::DocDecl(s))?;
            }
            XmlEvent::Decl(_) => {}
            XmlEvent::Eof => break,
        }
    }

    w.write_event(&Event::EndDocument)?;
    Ok(w.into_inner())
}