mod decode;
mod encode;
mod text;
use crate::android::{self, AttrDef, AttrFormat};
use crate::res::{ChunkError, ResourceId, ResourceRef, ResourceRefError, StringPoolError, Value};
pub use crate::android::NAMESPACE as ANDROID_NAMESPACE;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum XmlError {
#[error(transparent)]
Chunk(#[from] ChunkError),
#[error(transparent)]
StringPool(#[from] StringPoolError),
#[error("binary XML has no string pool before its first node")]
MissingStringPool,
#[error("string index {index} is outside the string pool")]
StringOutOfRange { index: u32 },
#[error("{kind} node at offset {offset:#x} is truncated")]
TruncatedNode { kind: NodeKind, offset: usize },
#[error("closing tag </{found}> does not match open element <{expected}>")]
MismatchedEnd { expected: String, found: String },
#[error("closing tag namespace {found:?} does not match open namespace {expected:?}")]
MismatchedNamespace { expected: Option<String>, found: Option<String> },
#[error("closing tag </{found}> has no open element")]
UnexpectedEnd { found: String },
#[error("{count} element(s) are never closed")]
Unclosed { count: usize },
#[error("document has no root element")]
NoRoot,
#[error("document has more than one root element")]
MultipleRoots,
#[error("elements nest deeper than {max} levels", max = MAX_DEPTH)]
TooDeep,
#[error("android:{name} is not a public framework attribute")]
UnknownAttribute { name: String },
#[error(transparent)]
Reference(#[from] ResourceRefError),
#[error("reference {reference} cannot be resolved")]
UnresolvedReference { reference: ResourceRef },
#[error("value {value:?} is not a valid {format:?} for android:{attribute}")]
InvalidAttributeValue { attribute: &'static str, value: String, format: AttrFormat },
#[error("android:{attribute} accepts {format:?}, not {value:?}")]
MistypedValue { attribute: &'static str, value: Value, format: AttrFormat },
#[error("{name} in {namespace} is an app-defined attribute, which this crate cannot compile")]
AppAttribute { namespace: String, name: String },
#[error("namespace prefix `{prefix}` is not declared")]
UndeclaredPrefix { prefix: String },
#[error("entity &{name}; is not defined (only the predefined XML entities are)")]
UnknownEntity { name: String },
#[error("XML syntax error: {0}")]
Syntax(#[from] quick_xml::Error),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum NodeKind {
Namespace,
StartElement,
Attribute,
EndElement,
Text,
}
impl std::fmt::Display for NodeKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Namespace => "namespace",
Self::StartElement => "element start",
Self::Attribute => "attribute",
Self::EndElement => "element end",
Self::Text => "text",
})
}
}
pub const MAX_DEPTH: usize = 512;
#[derive(Clone, Debug, PartialEq)]
pub struct Document {
pub root: Element,
}
impl Document {
pub fn new(root: Element) -> Self {
Self { root }
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Namespace {
pub prefix: Option<String>,
pub uri: String,
}
impl Namespace {
pub fn android() -> Self {
Self { prefix: Some("android".into()), uri: ANDROID_NAMESPACE.into() }
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Node {
Element(Element),
Text(String),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Element {
pub name: String,
pub namespace: Option<String>,
pub namespaces: Vec<Namespace>,
pub attributes: Vec<Attribute>,
pub children: Vec<Node>,
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AttrName {
pub namespace: Option<String>,
pub name: String,
pub id: Option<ResourceId>,
}
impl AttrName {
pub fn plain(name: impl Into<String>) -> Self {
Self { namespace: None, name: name.into(), id: None }
}
pub fn android(def: &AttrDef) -> Self {
Self { namespace: Some(ANDROID_NAMESPACE.into()), name: def.name().into(), id: Some(def.id()) }
}
pub fn android_def(&self) -> Option<&'static AttrDef> {
if self.namespace.as_deref() != Some(ANDROID_NAMESPACE) {
return None;
}
match self.id {
Some(id) => android::attr_by_id(id),
None => android::find_attr(&self.name),
}
}
pub fn resource_id(&self) -> Option<ResourceId> {
self.id.or_else(|| self.android_def().map(AttrDef::id))
}
pub(crate) fn sort_key(&self) -> (bool, Option<ResourceId>, Option<&str>, &str) {
let id = self.resource_id();
(id.is_none(), id, self.namespace.as_deref(), &self.name)
}
fn matches(&self, namespace: Option<&str>, name: &str) -> bool {
self.namespace.as_deref() == namespace && self.name == name
}
fn same(&self, other: &Self) -> bool {
match (self.resource_id(), other.resource_id()) {
(Some(a), Some(b)) => a == b,
_ => self.matches(other.namespace.as_deref(), &other.name),
}
}
}
impl From<&AttrDef> for AttrName {
fn from(def: &AttrDef) -> Self {
Self::android(def)
}
}
impl From<&str> for AttrName {
fn from(name: &str) -> Self {
Self::plain(name)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Attribute {
pub name: AttrName,
pub value: Value,
}
impl Element {
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into(), ..Self::default() }
}
pub fn with_android_namespace(mut self) -> Self {
self.namespaces.push(Namespace::android());
self
}
pub fn with_attr(mut self, name: impl Into<AttrName>, value: impl Into<Value>) -> Self {
self.set_attr(name, value);
self
}
pub fn with_opt_attr<V: Into<Value>>(mut self, name: impl Into<AttrName>, value: Option<V>) -> Self {
if let Some(value) = value {
self.set_attr(name, value);
}
self
}
pub fn with_child(mut self, child: Element) -> Self {
self.children.push(Node::Element(child));
self
}
pub fn with_children(mut self, children: impl IntoIterator<Item = Element>) -> Self {
self.children.extend(children.into_iter().map(Node::Element));
self
}
pub fn set_attr(&mut self, name: impl Into<AttrName>, value: impl Into<Value>) -> &mut Self {
let name = name.into();
let value = value.into();
match self.attributes.iter_mut().find(|a| a.name.same(&name)) {
Some(existing) => existing.value = value,
None => {
let key = name.sort_key();
let index = self.attributes.partition_point(|a| a.name.sort_key() <= key);
self.attributes.insert(index, Attribute { name, value });
}
}
self
}
pub fn push_child(&mut self, child: Element) -> &mut Self {
self.children.push(Node::Element(child));
self
}
pub fn attr(&self, namespace: Option<&str>, name: &str) -> Option<&Value> {
self.attributes.iter().find(|a| a.name.matches(namespace, name)).map(|a| &a.value)
}
pub fn android_attr(&self, def: &AttrDef) -> Option<&Value> {
self.attributes
.iter()
.find(|a| a.name.id == Some(def.id()) || a.name.matches(Some(ANDROID_NAMESPACE), def.name()))
.map(|a| &a.value)
}
pub fn elements(&self) -> impl Iterator<Item = &Element> {
self.children.iter().filter_map(|node| match node {
Node::Element(element) => Some(element),
Node::Text(_) => None,
})
}
pub fn elements_named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Element> + 'a {
self.elements().filter(move |e| e.name == name)
}
pub fn element(&self, name: &str) -> Option<&Element> {
self.elements().find(|e| e.name == name)
}
pub fn elements_mut(&mut self) -> impl Iterator<Item = &mut Element> {
self.children.iter_mut().filter_map(|node| match node {
Node::Element(element) => Some(element),
Node::Text(_) => None,
})
}
pub fn element_mut(&mut self, name: &str) -> Option<&mut Element> {
self.elements_mut().find(|e| e.name == name)
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Step<'a> {
Open(&'a Element),
Text(&'a str),
Close(&'a Element),
}
pub(crate) fn walk<'a>(root: &'a Element) -> impl Iterator<Item = (usize, Step<'a>)> {
let mut open: Vec<(&'a Element, usize)> = Vec::new();
let mut start = Some(root);
std::iter::from_fn(move || {
if let Some(root) = start.take() {
open.push((root, 0));
return Some((0, Step::Open(root)));
}
let depth = open.len().checked_sub(1)?;
let (element, next) = open.last_mut()?;
let element = *element;
let child = element.children.get(*next);
*next += 1;
Some(match child {
Some(Node::Element(child)) => {
open.push((child, 0));
(depth + 1, Step::Open(child))
}
Some(Node::Text(text)) => (depth, Step::Text(text)),
None => {
open.pop();
(depth, Step::Close(element))
}
})
})
}