use std::{
borrow::Cow,
collections::HashMap,
fmt::Display,
io::{Cursor, Read, Seek, Write},
};
use crate::{
defs::{HeaderSizeStatic, ResChunk, ResTableRef, ResType, ResTypeValue, ResourceMap},
res_value::{ResValue, ResValueType},
stream::{
NewResultCtx, Readable, ReadableNoOptions, StreamError, StreamResult, VecReadable,
VecWritable, Writeable, WriteableNoOptions,
},
string_pool::{ResStringPoolRef, StringPool, StringPoolHandler},
};
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct ResXMLTreeNode {
pub line_number: u32,
pub comment: ResStringPoolRef,
}
impl Default for ResXMLTreeNode {
fn default() -> Self {
Self {
line_number: 1,
comment: ResStringPoolRef::null(),
}
}
}
impl ResXMLTreeNode {
pub fn new() -> Self {
Self::default()
}
pub fn with_line_number(mut self, line_number: u32) -> Self {
self.line_number = line_number;
self
}
pub fn set_line_number(&mut self, line_number: u32) {
self.line_number = line_number;
}
pub fn with_comment_ref(mut self, comment: ResStringPoolRef) -> Self {
self.comment = comment;
self
}
pub fn set_comment_ref(&mut self, comment: ResStringPoolRef) {
self.comment = comment;
}
pub fn with_comment_str(
mut self,
comment: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.comment = string_pool.allocate(comment);
self
}
pub fn set_comment_str(&mut self, comment: Cow<'_, str>, string_pool: &mut StringPoolHandler) {
self.comment = string_pool.allocate(comment);
}
}
impl Readable for ResXMLTreeNode {
type Args = ();
fn read<R: Read + Seek>(reader: &mut R, _args: Self::Args) -> StreamResult<Self> {
Ok(Self {
line_number: u32::read_no_opts(reader)
.add_context(|| "read line_number for ResXMLTreeNode")?,
comment: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read comment for ResXMLTreeNode")?,
})
}
}
impl Writeable for ResXMLTreeNode {
type Args = ();
fn write<W: Write + Seek>(self, writer: &mut W, _args: Self::Args) -> StreamResult<()> {
self.line_number
.write_no_opts(writer)
.add_context(|| "write line_number for ResXMLTreeNode")?;
self.comment
.write_no_opts(writer)
.add_context(|| "write comment for ResXMLTreeNode")?;
Ok(())
}
}
impl HeaderSizeStatic for ResXMLTreeNode {
fn header_size() -> usize {
8
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct ResXMLTreeCDataExt {
pub node: ResXMLTreeNode,
pub data: ResStringPoolRef,
pub typed_data: ResValue,
}
impl Readable for ResXMLTreeCDataExt {
type Args = ();
fn read<R: Read + Seek>(reader: &mut R, _args: Self::Args) -> StreamResult<Self> {
Ok(Self {
node: ResXMLTreeNode::read_no_opts(reader)
.add_context(|| "read node for ResXMLTreeCDataExt")?,
data: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read data for ResXMLTreeCDataExt")?,
typed_data: ResValue::read_no_opts(reader)
.add_context(|| "read typed_data for ResXMLTreeCDataExt")?,
})
}
}
impl Writeable for ResXMLTreeCDataExt {
type Args = ();
fn write<W: Write + Seek>(self, writer: &mut W, _args: Self::Args) -> StreamResult<()> {
self.node
.write_no_opts(writer)
.add_context(|| "write node for ResXMLTreeCDataExt")?;
self.data
.write_no_opts(writer)
.add_context(|| "write data for ResXMLTreeCDataExt")?;
self.typed_data
.write_no_opts(writer)
.add_context(|| "write typed_data for ResXMLTreeCDataExt")?;
Ok(())
}
}
impl HeaderSizeStatic for ResXMLTreeCDataExt {
fn header_size() -> usize {
ResXMLTreeNode::header_size()
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct ResXMLTreeNameSpaceExt {
pub node: ResXMLTreeNode,
pub prefix: ResStringPoolRef,
pub uri: ResStringPoolRef,
}
impl Readable for ResXMLTreeNameSpaceExt {
type Args = ();
fn read<R: Read + Seek>(reader: &mut R, _args: Self::Args) -> StreamResult<Self> {
Ok(Self {
node: ResXMLTreeNode::read_no_opts(reader)
.add_context(|| "read node for ResXMLTreeNameSpaceExt")?,
prefix: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read prefix for ResXMLTreeNameSpaceExt")?,
uri: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read uri for ResXMLTreeNameSpaceExt")?,
})
}
}
impl Writeable for ResXMLTreeNameSpaceExt {
type Args = ();
fn write<W: Write + Seek>(self, writer: &mut W, _args: Self::Args) -> StreamResult<()> {
self.node
.write_no_opts(writer)
.add_context(|| "write node for ResXMLTreeNameSpaceExt")?;
self.prefix
.write_no_opts(writer)
.add_context(|| "write prefix for ResXMLTreeNameSpaceExt")?;
self.uri
.write_no_opts(writer)
.add_context(|| "write uri ResXMLTreeNameSpaceExt")?;
Ok(())
}
}
impl HeaderSizeStatic for ResXMLTreeNameSpaceExt {
fn header_size() -> usize {
ResXMLTreeNode::header_size()
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct ResXMLTreeEndElementExt {
pub node: ResXMLTreeNode,
pub ns: ResStringPoolRef,
pub name: ResStringPoolRef,
}
impl Readable for ResXMLTreeEndElementExt {
type Args = ();
fn read<R: Read + Seek>(reader: &mut R, _args: Self::Args) -> StreamResult<Self> {
Ok(Self {
node: ResXMLTreeNode::read_no_opts(reader)
.add_context(|| "read node for ResXMLTreeEndElementExt")?,
ns: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read ns for ResXMLTreeEndElementExt")?,
name: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read name for ResXMLTreeEndElementExt")?,
})
}
}
impl Writeable for ResXMLTreeEndElementExt {
type Args = ();
fn write<W: Write + Seek>(self, writer: &mut W, _args: Self::Args) -> StreamResult<()> {
self.node
.write_no_opts(writer)
.add_context(|| "write node for ResXMLTreeEndElementExt")?;
self.ns
.write_no_opts(writer)
.add_context(|| "write ns for ResXMLTreeEndElementExt")?;
self.name
.write_no_opts(writer)
.add_context(|| "write name for ResXMLTreeEndElementExt")?;
Ok(())
}
}
impl HeaderSizeStatic for ResXMLTreeEndElementExt {
fn header_size() -> usize {
ResXMLTreeNode::header_size()
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct ResXMLTreeAttrExt {
pub node: ResXMLTreeNode,
pub ns: ResStringPoolRef,
pub name: ResStringPoolRef,
pub id_index: u16,
pub class_index: u16,
pub style_index: u16,
pub attributes: Vec<ResXMLTreeAttribute>,
}
impl ResXMLTreeAttrExt {
pub fn new() -> Self {
Self::default()
}
pub fn add_attribute(&mut self, attr: ResXMLTreeAttribute) {
self.attributes.push(attr);
}
pub fn with_attribute(mut self, attr: ResXMLTreeAttribute) -> Self {
self.add_attribute(attr);
self
}
pub fn with_namespace_ref(mut self, namespace: ResStringPoolRef) -> Self {
self.set_namespace_ref(namespace);
self
}
pub fn set_namespace_ref(&mut self, namespace: ResStringPoolRef) {
self.ns = namespace;
}
pub fn with_namespace_str(
mut self,
namespace: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_namespace_str(namespace, string_pool);
self
}
pub fn set_namespace_str(
&mut self,
namespace: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) {
self.ns = string_pool.allocate(namespace);
}
pub fn with_name_ref(mut self, name: ResStringPoolRef) -> Self {
self.set_name_ref(name);
self
}
pub fn set_name_ref(&mut self, name: ResStringPoolRef) {
self.name = name;
}
pub fn with_name_str(
mut self,
name: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_name_str(name, string_pool);
self
}
pub fn set_name_str(&mut self, name: Cow<'_, str>, string_pool: &mut StringPoolHandler) {
self.name = string_pool.allocate(name);
}
pub fn with_line_number(mut self, line_number: u32) -> Self {
self.set_line_number(line_number);
self
}
pub fn set_line_number(&mut self, line_number: u32) {
self.node.set_line_number(line_number);
}
pub fn with_comment_ref(mut self, comment: ResStringPoolRef) -> Self {
self.set_comment_ref(comment);
self
}
pub fn set_comment_ref(&mut self, comment: ResStringPoolRef) {
self.node.set_comment_ref(comment);
}
pub fn with_comment_str(
mut self,
comment: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_comment_str(comment, string_pool);
self
}
pub fn set_comment_str(&mut self, comment: Cow<'_, str>, string_pool: &mut StringPoolHandler) {
self.node.set_comment_str(comment, string_pool);
}
pub fn equivalent(
&self,
other: &Self,
string_pool: &StringPoolHandler,
other_string_pool: &StringPoolHandler,
strict_attr_check: bool,
) -> NodeDifference {
if self.name.resolve(string_pool) != other.name.resolve(other_string_pool) {
return NodeDifference::NameElement(
self.name.resolve(string_pool).map(|v| v.to_string()),
other.name.resolve(other_string_pool).map(|v| v.to_string()),
);
}
if self.ns.resolve(string_pool) != other.ns.resolve(other_string_pool) {
return NodeDifference::NsElement(
self.name.resolve(string_pool).map(|v| v.to_string()),
self.ns.resolve(string_pool).map(|v| v.to_string()),
other.ns.resolve(other_string_pool).map(|v| v.to_string()),
);
}
if self.attributes.len() != other.attributes.len() {
return NodeDifference::AttrCount(self.attributes.len(), other.attributes.len());
}
if self.id_index != other.id_index {
return NodeDifference::IdIndex(
self.name.resolve(string_pool).map(|v| v.to_string()),
self.id_index,
other.id_index,
);
}
if self.class_index != other.class_index {
return NodeDifference::ClassIndex(
self.name.resolve(string_pool).map(|v| v.to_string()),
self.class_index,
other.class_index,
);
}
if self.style_index != other.style_index {
return NodeDifference::StyleIndex(
self.name.resolve(string_pool).map(|v| v.to_string()),
self.style_index,
other.style_index,
);
}
if strict_attr_check {
for (sattr, oatter) in self.attributes.iter().zip(other.attributes.iter()) {
if sattr.name.resolve(string_pool) != oatter.name.resolve(other_string_pool) {
return NodeDifference::NameAttr(
self.name.resolve(string_pool).map(|v| v.to_string()),
sattr.name.resolve(string_pool).map(|v| v.to_string()),
oatter
.name
.resolve(other_string_pool)
.map(|v| v.to_string()),
);
}
if sattr.ns.resolve(string_pool) != oatter.ns.resolve(other_string_pool) {
return NodeDifference::NsAttr(
sattr.ns.resolve(string_pool).map(|v| v.to_string()),
oatter.ns.resolve(other_string_pool).map(|v| v.to_string()),
);
}
if sattr.raw_value.resolve(string_pool)
!= oatter.raw_value.resolve(other_string_pool)
{
return NodeDifference::AttrRawValue(
sattr.raw_value.resolve(string_pool).map(|v| v.to_string()),
oatter
.raw_value
.resolve(other_string_pool)
.map(|v| v.to_string()),
);
}
let val_eq = sattr.typed_value.equivalent(
oatter.typed_value,
string_pool,
other_string_pool,
);
if val_eq != NodeDifference::None {
return val_eq;
}
}
}
let mut found_attrs = vec![false; other.attributes.len()];
for sattr in &self.attributes {
let sattr_name = sattr.name.resolve(string_pool);
if let Some(sattr_name) = sattr_name {
let mut found = None;
for (i, oattr) in other.attributes.iter().enumerate() {
let oattr_name = oattr.name.resolve(other_string_pool);
if let Some(oattr_name) = oattr_name {
if sattr_name == oattr_name {
found = Some((i, oattr));
break;
}
}
}
if let Some((i, found)) = found {
found_attrs[i] = true;
if sattr.ns.resolve(string_pool) != found.ns.resolve(other_string_pool) {
return NodeDifference::NsAttr(
sattr.ns.resolve(string_pool).map(|v| v.to_string()),
found.ns.resolve(other_string_pool).map(|v| v.to_string()),
);
}
if sattr.raw_value.resolve(string_pool)
!= found.raw_value.resolve(other_string_pool)
{
return NodeDifference::AttrRawValue(
sattr.raw_value.resolve(string_pool).map(|v| v.to_string()),
found
.raw_value
.resolve(other_string_pool)
.map(|v| v.to_string()),
);
}
let val_eq = sattr.typed_value.equivalent(
found.typed_value,
string_pool,
other_string_pool,
);
if val_eq != NodeDifference::None {
return val_eq;
}
} else {
return NodeDifference::NoAttr(
sattr.name.resolve(string_pool).map(|v| v.to_string()),
);
}
}
}
for (i, attr) in other.attributes.iter().enumerate() {
if !found_attrs[i] {
return NodeDifference::ExtraAttr(
attr.name.resolve(other_string_pool).map(|v| v.to_string()),
);
}
}
NodeDifference::None
}
}
#[derive(Debug, PartialEq)]
pub enum NodeDifference {
None,
NsElement(Option<String>, Option<String>, Option<String>),
NameElement(Option<String>, Option<String>),
AttrCount(usize, usize),
NsAttr(Option<String>, Option<String>),
NoAttr(Option<String>),
AttrRawValue(Option<String>, Option<String>),
ResValue(String, String),
ResValueString(Option<String>, Option<String>),
WrongResValueType(ResValueType, ResValueType),
OtherNoNamespace(Option<String>, XMLNameSpace),
ExpectedNoNamespace(Option<String>, XMLNameSpace),
NodeNsPrefix(Option<String>, Option<String>),
ChildCount(Option<String>, usize, usize),
TotalChildCount(Option<String>, usize, usize),
ExtraAttr(Option<String>),
NameAttr(Option<String>, Option<String>, Option<String>),
ResMapLen(usize, usize),
ResMapValMissing(String, ResTableRef, Option<ResTableRef>),
ResMapValExtra(String, ResTableRef, Option<ResTableRef>),
IdIndex(Option<String>, u16, u16),
ClassIndex(Option<String>, u16, u16),
StyleIndex(Option<String>, u16, u16),
}
impl Writeable for ResXMLTreeAttrExt {
type Args = ();
fn write<W: Write + Seek>(self, writer: &mut W, _args: Self::Args) -> StreamResult<()> {
self.node
.write_no_opts(writer)
.add_context(|| "write node for ResXMLTreeAttrExt")?;
self.ns
.write_no_opts(writer)
.add_context(|| "write ns for ResXMLTreeAttrExt")?;
self.name
.write_no_opts(writer)
.add_context(|| "write name for ResXMLTreeAttrExt")?;
let attribute_start: u16 = 20;
attribute_start
.write_no_opts(writer)
.add_context(|| "write attribute_start for ResXMLTreeAttrExt")?;
let attribute_size: u16 = 20;
attribute_size
.write_no_opts(writer)
.add_context(|| "write attribute_size for ResXMLTreeAttrExt")?;
let attribute_count: u16 = self.attributes.len() as u16;
attribute_count
.write_no_opts(writer)
.add_context(|| "write attribute_count for ResXMLTreeAttrExt")?;
self.id_index
.write_no_opts(writer)
.add_context(|| "write id_index for ResXMLTeeAttrExt")?;
self.class_index
.write_no_opts(writer)
.add_context(|| "write class_index for ResXMLTreeAttrExt")?;
self.style_index
.write_no_opts(writer)
.add_context(|| "write style_index for ResXMLTreeAttrExt")?;
self.attributes
.write_vec(writer)
.add_context(|| "write attributes for ResXMLTreeAttrExt")?;
Ok(())
}
}
impl Readable for ResXMLTreeAttrExt {
type Args = ();
fn read<R: Read + Seek>(reader: &mut R, _args: Self::Args) -> StreamResult<Self> {
let node = ResXMLTreeNode::read_no_opts(reader)
.add_context(|| "read node for ResXMLTreeAttrExt")?;
let start_pos = reader.stream_position()?;
let ns = ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read ns for ResXMLTreeAttrExt")?;
let name = ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read name for ResXMLTreeAttrExt")?;
let attribute_start = u16::read_no_opts(reader)
.add_context(|| "read attribute_start for ResXMLTreeAttrExt")?;
let attribute_size = u16::read_no_opts(reader)
.add_context(|| "read attribute_size for ResXMLTreeAttrExt")?;
if attribute_size != 20 {
return Err(StreamError::new_string_context(
format!("invalid attribute_size: {attribute_size}, expected 20"),
reader.stream_position()?,
"validate attribute size for ResXMLTreeAttrExt",
));
}
let attribute_count = u16::read_no_opts(reader)
.add_context(|| "read attribute_count for ResXMLTreeAttrExt")?;
let id_index =
u16::read_no_opts(reader).add_context(|| "read id_index for ResXMLTreeAttrExt")?;
let class_index =
u16::read_no_opts(reader).add_context(|| "read class_index for ResXMLTreeAttrExt")?;
let style_index =
u16::read_no_opts(reader).add_context(|| "read style_index for ResXMLTreeAttrExt")?;
reader.seek(std::io::SeekFrom::Start(start_pos + attribute_start as u64))?;
let attributes = <Vec<ResXMLTreeAttribute>>::read_vec(reader, attribute_count as usize)
.add_context(|| "read attributes for ResXMLTreeAttrExt")?;
Ok(Self {
node,
ns,
name,
id_index,
class_index,
style_index,
attributes,
})
}
}
impl HeaderSizeStatic for ResXMLTreeAttrExt {
fn header_size() -> usize {
ResXMLTreeNode::header_size()
}
}
#[derive(Debug)]
pub enum NodeToElementError {
NoAttrName { index: u32 },
NoAttrValue { index: u32 },
NoNodeName { index: u32 },
}
#[derive(Debug, PartialEq, Copy, Clone, Default)]
pub struct ResXMLTreeAttribute {
pub ns: ResStringPoolRef,
pub name: ResStringPoolRef,
pub raw_value: ResStringPoolRef,
pub typed_value: ResValue,
}
impl ResXMLTreeAttribute {
pub fn with_namespace_ref(mut self, namespace: ResStringPoolRef) -> Self {
self.set_namespace_ref(namespace);
self
}
pub fn set_namespace_ref(&mut self, namespace: ResStringPoolRef) {
self.ns = namespace;
}
pub fn with_namespace_str(
mut self,
namespace: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_namespace_str(namespace, string_pool);
self
}
pub fn set_namespace_str(
&mut self,
namespace: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) {
self.ns = string_pool.allocate(namespace);
}
pub fn with_name_ref(mut self, name: ResStringPoolRef) -> Self {
self.set_name_ref(name);
self
}
pub fn set_name_ref(&mut self, name: ResStringPoolRef) {
self.name = name;
}
pub fn with_name_str(
mut self,
name: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_name_str(name, string_pool);
self
}
pub fn set_name_str(&mut self, name: Cow<'_, str>, string_pool: &mut StringPoolHandler) {
self.name = string_pool.allocate(name);
}
pub fn with_value(mut self, value: ResValue) -> Self {
self.set_value(value);
self
}
pub fn set_name_ref_res_map(
&mut self,
name: ResStringPoolRef,
table_ref: ResTableRef,
res_map: &mut ResourceMap,
) {
self.set_name_ref(name);
res_map.insert(name, table_ref);
}
pub fn with_name_ref_res_map(
mut self,
name: ResStringPoolRef,
table_ref: ResTableRef,
res_map: &mut ResourceMap,
) -> Self {
self.set_name_ref_res_map(name, table_ref, res_map);
self
}
pub fn set_name_str_res_map(
&mut self,
name: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
table_ref: ResTableRef,
res_map: &mut ResourceMap,
) {
self.set_name_ref_res_map(string_pool.allocate(name), table_ref, res_map);
}
pub fn with_name_str_res_map(
mut self,
name: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
table_ref: ResTableRef,
res_map: &mut ResourceMap,
) -> Self {
self.set_name_str_res_map(name, string_pool, table_ref, res_map);
self
}
}
impl Readable for ResXMLTreeAttribute {
type Args = ();
fn read<R: Read + Seek>(reader: &mut R, _args: Self::Args) -> StreamResult<Self> {
Ok(Self {
ns: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read ns for ResXMLTreeAttribute")?,
name: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read name for ResXMLTreeAttribute")?,
raw_value: ResStringPoolRef::read_no_opts(reader)
.add_context(|| "read raw_value for ResXMLTreeAttribute")?,
typed_value: ResValue::read_no_opts(reader)
.add_context(|| "read typed_value for ResXMLTreeAttribute")?,
})
}
}
impl Writeable for ResXMLTreeAttribute {
type Args = ();
fn write<W: Write + Seek>(self, writer: &mut W, _args: Self::Args) -> StreamResult<()> {
self.ns
.write_no_opts(writer)
.add_context(|| "write ns for ResXMLTreeAttribute")?;
self.name
.write_no_opts(writer)
.add_context(|| "write name for ResXMLTreeAttribute")?;
self.raw_value
.write_no_opts(writer)
.add_context(|| "write raw_value for ResXMLTreeAttribute")?;
self.typed_value
.write_no_opts(writer)
.add_context(|| "write typed_value for ResXMLTreeAttribute")
}
}
impl ResXMLTreeAttribute {
pub fn write_string(&mut self, string: Cow<'_, str>, strings: &mut StringPoolHandler) {
let string_pool_ref = strings.allocate(string);
self.set_value(ResValue::new(ResValueType::String(string_pool_ref)));
}
pub fn write_bool(&mut self, value: bool) {
self.set_value(ResValue::new(ResValueType::IntBoolean(value.into())));
}
pub fn set_value(&mut self, value: ResValue) {
self.typed_value = value;
match value.data {
ResValueType::String(string_ref) => self.raw_value = string_ref,
_ => self.raw_value = ResStringPoolRef::null(),
};
}
pub fn new() -> Self {
Self::default()
}
pub fn new_alloc(
ns: ResStringPoolRef,
name: Cow<'_, str>,
value: ResValue,
strings: &mut StringPoolHandler,
) -> Self {
let raw_value = if let ResValueType::String(string_ref) = value.data {
string_ref
} else {
ResStringPoolRef::null()
};
Self {
ns,
name: strings.allocate(name),
raw_value,
typed_value: value,
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct RawXMLTree {
pub chunks: Vec<ResChunk>,
}
impl Readable for RawXMLTree {
type Args = u64;
fn read<R: Read + Seek>(reader: &mut R, args: Self::Args) -> StreamResult<Self> {
Ok(Self {
chunks: <Vec<ResChunk>>::read(reader, args)
.add_context(|| "read chunks for RawXMLTree")?,
})
}
}
impl Writeable for RawXMLTree {
type Args = ();
fn write<W: Write + Seek>(self, writer: &mut W, _args: Self::Args) -> StreamResult<()> {
self.chunks
.write_vec(writer)
.add_context(|| "write chunks for RawXMLTree")
}
}
impl HeaderSizeStatic for RawXMLTree {
fn header_size() -> usize {
0
}
}
impl RawXMLTree {
pub fn read_full<R: Seek + Read>(reader: &mut R) -> StreamResult<RawXMLTree> {
let pos = reader.stream_position()?;
let header = ResChunk::read_no_opts(reader).add_context(|| "read chunk for RawXMLTree")?;
if let ResTypeValue::XML(xml) = header.data {
return Ok(xml);
}
let res_type: ResType = (&header.data).into();
Err(StreamError::new_string_context(
format!("invalid res_type: {res_type}, expected XML"),
pos,
"validate read chunk for RawXMLTree",
))
}
pub fn write_full<W: Seek + Write>(self, writer: &mut W) -> StreamResult<()> {
let header = ResChunk {
data: ResTypeValue::XML(self),
};
header
.write_no_opts(writer)
.add_context(|| "write chunk for RawXMLTree")
}
}
#[derive(Debug)]
pub enum ReadAXMLError {
ReadError(std::io::Error),
InvalidType(ResType),
}
impl Display for ReadAXMLError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ReadError(e) => write!(f, "failed to read res xml tree: {e}"),
Self::InvalidType(t) => write!(f, "invalid type: {t} expected XML Tree"),
}
}
}
impl TryFrom<RawXMLTree> for Vec<u8> {
type Error = StreamError;
fn try_from(value: RawXMLTree) -> Result<Self, Self::Error> {
let mut stream = Cursor::new(Vec::new());
value.write_full(&mut stream)?;
Ok(stream.into_inner())
}
}
#[derive(Debug)]
pub enum TreeToElementError {
ReadError(std::io::Error),
InvalidType(Box<ResTypeValue>),
NoElements,
InvalidNameSpace,
NoStringPool,
UnbalancedElements,
NoRootElement,
NodeToElementError(NodeToElementError),
}
impl From<NodeToElementError> for TreeToElementError {
fn from(value: NodeToElementError) -> Self {
Self::NodeToElementError(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct XMLNameSpace {
pub prefix: ResStringPoolRef,
pub uri: ResStringPoolRef,
}
impl XMLNameSpace {
pub fn new(prefix: ResStringPoolRef, uri: ResStringPoolRef) -> Self {
Self { prefix, uri }
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct XMLTreeNode {
pub element: ResXMLTreeAttrExt,
pub children: Vec<XMLTreeNode>,
pub namespace: Option<XMLNameSpace>,
}
impl XMLTreeNode {
pub fn new() -> Self {
Self::default()
}
pub fn add_child(&mut self, child: XMLTreeNode) {
self.children.push(child);
}
pub fn with_child(mut self, child: XMLTreeNode) -> Self {
self.add_child(child);
self
}
pub fn with_element(mut self, element: ResXMLTreeAttrExt) -> Self {
self.set_element(element);
self
}
pub fn set_element(&mut self, element: ResXMLTreeAttrExt) {
self.element = element;
}
pub fn add_attribute(&mut self, attr: ResXMLTreeAttribute) {
self.element.add_attribute(attr);
}
pub fn with_attribute(mut self, attr: ResXMLTreeAttribute) -> Self {
self.add_attribute(attr);
self
}
pub fn with_namespace_ref(mut self, namespace: ResStringPoolRef) -> Self {
self.set_namespace_ref(namespace);
self
}
pub fn set_namespace_ref(&mut self, namespace: ResStringPoolRef) {
self.element.set_namespace_ref(namespace);
}
pub fn set_namespace_prefix_ref(&mut self, prefix: ResStringPoolRef) {
if let Some(ref mut ns) = self.namespace {
ns.prefix = prefix;
} else {
self.namespace = Some(XMLNameSpace::new(prefix, ResStringPoolRef::null()))
}
}
pub fn with_namespace_prefix_ref(mut self, prefix: ResStringPoolRef) -> Self {
self.set_namespace_prefix_ref(prefix);
self
}
pub fn set_namespace_uri_ref(&mut self, uri: ResStringPoolRef) {
if let Some(ref mut ns) = self.namespace {
ns.uri = uri;
} else {
self.namespace = Some(XMLNameSpace::new(ResStringPoolRef::null(), uri))
}
}
pub fn with_namespace_uri_ref(mut self, uri: ResStringPoolRef) -> Self {
self.set_namespace_uri_ref(uri);
self
}
pub fn set_namespace_prefix_str(
&mut self,
prefix: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) {
self.set_namespace_prefix_ref(string_pool.allocate(prefix));
}
pub fn with_namespace_prefix_str(
mut self,
prefix: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_namespace_prefix_str(prefix, string_pool);
self
}
pub fn set_namespace_uri_str(
&mut self,
uri: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) {
self.set_namespace_uri_ref(string_pool.allocate(uri));
}
pub fn with_namespace_uri_str(
mut self,
uri: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_namespace_uri_str(uri, string_pool);
self
}
pub fn set_namespace_full_str(&mut self, namespace: &str, string_pool: &mut StringPoolHandler) {
if let Some((prefix, uri)) = namespace.split_once(":") {
self.set_namespace_prefix_str(prefix.into(), string_pool);
self.set_namespace_uri_str(uri.into(), string_pool);
}
}
pub fn with_namespace_full_str(
mut self,
namespace: &str,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_namespace_full_str(namespace, string_pool);
self
}
pub fn with_namespace_str(
mut self,
namespace: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_namespace_str(namespace, string_pool);
self
}
pub fn set_namespace_str(
&mut self,
namespace: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) {
self.element.set_namespace_str(namespace, string_pool);
}
pub fn with_name_ref(mut self, name: ResStringPoolRef) -> Self {
self.set_name_ref(name);
self
}
pub fn set_name_ref(&mut self, name: ResStringPoolRef) {
self.element.set_name_ref(name);
}
pub fn with_name_str(
mut self,
name: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_name_str(name, string_pool);
self
}
pub fn set_name_str(&mut self, name: Cow<'_, str>, string_pool: &mut StringPoolHandler) {
self.element.set_name_str(name, string_pool);
}
pub fn with_line_number(mut self, line_number: u32) -> Self {
self.set_line_number(line_number);
self
}
pub fn set_line_number(&mut self, line_number: u32) {
self.element.set_line_number(line_number);
}
pub fn with_comment_ref(mut self, comment: ResStringPoolRef) -> Self {
self.set_comment_ref(comment);
self
}
pub fn set_comment_ref(&mut self, comment: ResStringPoolRef) {
self.element.set_comment_ref(comment);
}
pub fn with_comment_str(
mut self,
comment: Cow<'_, str>,
string_pool: &mut StringPoolHandler,
) -> Self {
self.set_comment_str(comment, string_pool);
self
}
pub fn set_comment_str(&mut self, comment: Cow<'_, str>, string_pool: &mut StringPoolHandler) {
self.element.set_comment_str(comment, string_pool);
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct XMLTree {
pub string_pool: StringPoolHandler,
pub resource_map: Option<ResourceMap>,
pub root: XMLTreeNode,
}
impl XMLTree {
pub fn equivalent(&self, other: &Self, strict_attr_check: bool) -> NodeDifference {
let mut self_res_map_strs = HashMap::new();
let mut other_res_map_strs = HashMap::new();
if let Some(ref res_map) = self.resource_map {
for (sp_index, table_ref) in res_map.mapping.iter().enumerate() {
if table_ref.is_null() {
continue;
}
let str = self.string_pool.resolve(ResStringPoolRef {
index: sp_index as u32,
});
if let Some(str) = str {
self_res_map_strs.insert(str, table_ref);
}
}
}
if let Some(ref res_map) = other.resource_map {
for (sp_index, table_ref) in res_map.mapping.iter().enumerate() {
if table_ref.is_null() {
continue;
}
let str = other.string_pool.resolve(ResStringPoolRef {
index: sp_index as u32,
});
if let Some(str) = str {
other_res_map_strs.insert(str, table_ref);
}
}
}
for (str, val) in &self_res_map_strs {
let val2 = other_res_map_strs.get(str);
if Some(val) != val2 {
return NodeDifference::ResMapValMissing(str.to_string(), **val, val2.map(|v| **v));
}
}
for (str, val) in &other_res_map_strs {
let val2 = self_res_map_strs.get(str);
if Some(val) != val2 {
return NodeDifference::ResMapValExtra(str.to_string(), **val, val2.map(|v| **v));
}
}
if self_res_map_strs.len() != other_res_map_strs.len() {
return NodeDifference::ResMapLen(self_res_map_strs.len(), other_res_map_strs.len());
}
return self.root.equivalent(
&other.root,
&self.string_pool,
&other.string_pool,
strict_attr_check,
);
}
}
impl XMLTreeNode {
pub fn equivalent(
&self,
other: &Self,
string_pool: &StringPoolHandler,
other_string_pool: &StringPoolHandler,
strict_attr_check: bool,
) -> NodeDifference {
let el_dif = self.element.equivalent(
&other.element,
string_pool,
other_string_pool,
strict_attr_check,
);
if el_dif != NodeDifference::None {
return el_dif;
}
if let Some(sns) = self.namespace {
if let Some(ons) = other.namespace {
if sns.prefix.resolve(string_pool) != ons.prefix.resolve(other_string_pool) {
return NodeDifference::NodeNsPrefix(
sns.prefix.resolve(string_pool).map(|v| v.to_string()),
ons.prefix.resolve(other_string_pool).map(|v| v.to_string()),
);
}
} else {
return NodeDifference::OtherNoNamespace(
self.element
.name
.resolve(string_pool)
.map(|v| v.to_string()),
sns,
);
}
} else {
if let Some(ons) = other.namespace {
return NodeDifference::ExpectedNoNamespace(
self.element
.name
.resolve(string_pool)
.map(|v| v.to_string()),
ons,
);
}
}
if self.children.len() != other.children.len() {
return NodeDifference::TotalChildCount(
self.element
.name
.resolve(string_pool)
.map(|v| v.to_string()),
self.children.len(),
other.children.len(),
);
}
let mut grouped_children: HashMap<String, (Vec<&Self>, Vec<&Self>)> = HashMap::new();
for (schild, ochild) in self.children.iter().zip(other.children.iter()) {
let schild_name = schild.element.name.resolve(string_pool);
if let Some(schild_name) = schild_name {
grouped_children
.entry(schild_name.to_string())
.or_default()
.0
.push(schild);
}
let ochild_name = ochild.element.name.resolve(other_string_pool);
if let Some(ochild_name) = ochild_name {
grouped_children
.entry(ochild_name.to_string())
.or_default()
.1
.push(ochild);
}
}
for (child_name, (schildren, ochildren)) in grouped_children {
if schildren.len() != ochildren.len() {
return NodeDifference::ChildCount(
Some(child_name),
schildren.len(),
ochildren.len(),
);
}
for (schild, ochild) in schildren.iter().zip(ochildren.iter()) {
let child_eq =
schild.equivalent(ochild, string_pool, other_string_pool, strict_attr_check);
if child_eq != NodeDifference::None {
return child_eq;
}
}
}
return NodeDifference::None;
}
}
#[derive(Debug)]
pub enum ReadXMLTreeError {
ReadData(StreamError),
ReadFile(std::path::PathBuf, std::io::Error),
ConvertRaw(XMLTreeParseError),
}
impl Display for ReadXMLTreeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ReadData(r) => r.fmt(f),
Self::ConvertRaw(c) => c.fmt(f),
Self::ReadFile(path, error) => write!(
f,
"{}",
format!(
"failed to read file at: {} due to {error}",
path.to_string_lossy()
),
),
}
}
}
impl XMLTree {
pub fn read<R: Read + Seek>(reader: &mut R) -> Result<XMLTree, ReadXMLTreeError> {
let raw_xml: RawXMLTree =
RawXMLTree::read_full(reader).map_err(ReadXMLTreeError::ReadData)?;
raw_xml.try_into().map_err(ReadXMLTreeError::ConvertRaw)
}
pub fn from_path(path: &std::path::Path) -> Result<XMLTree, ReadXMLTreeError> {
Self::read(
&mut std::fs::File::open(path)
.map_err(|e| ReadXMLTreeError::ReadFile(path.to_path_buf(), e))?,
)
}
pub fn write<W: Write + Seek>(self, writer: &mut W) -> Result<(), StreamError> {
let raw_xml: RawXMLTree = self.into();
raw_xml.write_full(writer)?;
Ok(())
}
}
impl TryFrom<&[u8]> for XMLTree {
type Error = ReadXMLTreeError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let mut reader = Cursor::new(value);
XMLTree::read(&mut reader)
}
}
impl TryFrom<XMLTree> for Vec<u8> {
type Error = StreamError;
fn try_from(value: XMLTree) -> Result<Self, Self::Error> {
let mut writer = Cursor::new(Vec::new());
value.write(&mut writer)?;
Ok(writer.into_inner())
}
}
#[derive(Debug)]
pub enum XMLTreeParseError {
NoStringPool,
NoRoot,
TooManyEndNamespaces,
TooFewEndNamespaces,
TooManyEndElements,
TooFewEndElements,
UnrecognisedChunk(ResType),
MultipleRootNodes,
}
impl Display for XMLTreeParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Self::NoStringPool => "failed to find string pool in xml tree".to_string(),
Self::NoRoot => "no root element found in xml tree".to_string(),
Self::TooManyEndNamespaces => "too many end namespace chunks in xml tree".to_string(),
Self::TooFewEndNamespaces => "too few end namespace chunks in xml tree".to_string(),
Self::TooManyEndElements => "too many end element chunks in xml tree".to_string(),
Self::TooFewEndElements => "too few end element chunks in xml tree".to_string(),
Self::UnrecognisedChunk(t) => format!("unrecognised chunk type: {t} in xml tree"),
Self::MultipleRootNodes => "multiple root nodes found in xml tree".to_string(),
};
write!(f, "{str}")
}
}
impl XMLTreeNode {
pub fn write_chunks(self, chunks: &mut Vec<ResChunk>) {
let node = self.element.node;
let ns = self.element.ns;
let name = self.element.name;
chunks.push(ResChunk {
data: ResTypeValue::XMLStartElement(self.element),
});
for child in self.children {
child.write_chunks(chunks);
}
chunks.push(ResChunk {
data: ResTypeValue::XMLEndElement(ResXMLTreeEndElementExt { node, ns, name }),
});
}
}
impl From<XMLTree> for RawXMLTree {
fn from(value: XMLTree) -> Self {
let mut chunks: Vec<ResChunk> = Vec::new();
chunks.push(value.string_pool.string_pool.into());
if let Some(rn) = value.resource_map {
chunks.push(rn.into())
}
let ns_ext = if let Some(ns) = value.root.namespace {
Some(ResXMLTreeNameSpaceExt {
node: value.root.element.node,
prefix: ns.prefix,
uri: ns.uri,
})
} else {
None
};
if let Some(ns) = ns_ext {
chunks.push(ResChunk {
data: ResTypeValue::XMLStartNameSpace(ns),
});
}
value.root.write_chunks(&mut chunks);
if let Some(ns) = ns_ext {
chunks.push(ResChunk {
data: ResTypeValue::XMLEndNameSpace(ns),
});
}
Self { chunks }
}
}
impl XMLTreeNode {
pub fn find_element<'a>(
&'a self,
name: &str,
strings: &StringPoolHandler,
) -> Option<&'a XMLTreeNode> {
if self.element.name.resolve(strings) == Some(name) {
return Some(self);
}
for child in &self.children {
if let Some(el) = child.find_element(name, strings) {
return Some(el);
}
}
None
}
pub fn get_child<'a>(
&'a self,
name: &str,
strings: &StringPoolHandler,
) -> Option<&'a XMLTreeNode> {
self.children
.iter()
.find(|c| c.element.name.resolve(strings) == Some(name))
}
pub fn get_elements_mut<'a>(
&'a mut self,
path: &[&str],
strings: &StringPoolHandler,
) -> Vec<&'a mut XMLTreeNode> {
let mut elements: Vec<&mut XMLTreeNode> = Vec::new();
let mut stack: Vec<(usize, &mut XMLTreeNode)> = Vec::new();
stack.push((0, self));
while let Some((index, element)) = stack.pop() {
let item = path.get(index);
if item.is_none() {
continue;
}
if item == element.element.name.resolve(strings).as_ref() {
if index == path.len() - 1 {
elements.push(element);
continue;
}
for child in element.children.iter_mut() {
stack.push((index + 1, child));
}
}
}
elements
}
pub fn get_element_mut<'a>(
&'a mut self,
path: &[&str],
strings: &StringPoolHandler,
) -> Option<&'a mut XMLTreeNode> {
let mut stack: Vec<(usize, &mut XMLTreeNode)> = Vec::new();
stack.push((0, self));
while let Some((index, element)) = stack.pop() {
let item = path.get(index);
if item.is_none() {
continue;
}
if item == element.element.name.resolve(strings).as_ref() {
if index == path.len() - 1 {
return Some(element);
}
for child in element.children.iter_mut() {
stack.push((index + 1, child));
}
}
}
None
}
pub fn get_elements<'a>(
&'a self,
path: &[&str],
strings: &StringPoolHandler,
) -> Vec<&'a XMLTreeNode> {
let mut elements: Vec<&XMLTreeNode> = Vec::new();
let mut stack: Vec<(usize, &XMLTreeNode)> = Vec::new();
stack.push((0, self));
while let Some((index, element)) = stack.pop() {
let item = path.get(index);
if item.is_none() {
continue;
}
if item == element.element.name.resolve(strings).as_ref() {
if index == path.len() - 1 {
elements.push(element);
continue;
}
for child in &element.children {
stack.push((index + 1, child));
}
}
}
elements
}
pub fn get_element<'a>(
&'a self,
path: &[&str],
strings: &StringPoolHandler,
) -> Option<&'a XMLTreeNode> {
let mut stack: Vec<(usize, &XMLTreeNode)> = Vec::new();
stack.push((0, self));
while let Some((index, element)) = stack.pop() {
let item = path.get(index);
if item.is_none() {
continue;
}
if item == element.element.name.resolve(strings).as_ref() {
if index == path.len() - 1 {
return Some(element);
}
for child in &element.children {
stack.push((index + 1, child));
}
}
}
None
}
pub fn get_attribute<'a>(
&'a self,
name: &str,
strings: &StringPoolHandler,
) -> Option<&'a ResXMLTreeAttribute> {
self.element
.attributes
.iter()
.find(|attr| attr.name.resolve(strings) == Some(name))
}
pub fn get_attribute_names<'a>(&self, strings: &'a StringPoolHandler) -> Vec<&'a str> {
self.element
.attributes
.iter()
.flat_map(|a| a.name.resolve(strings))
.collect()
}
pub fn get_child_names<'a>(&self, strings: &'a StringPoolHandler) -> Vec<&'a str> {
self.children
.iter()
.flat_map(|c| c.element.name.resolve(strings))
.collect()
}
pub fn get_attribute_mut<'a>(
&'a mut self,
name: &str,
strings: &StringPoolHandler,
) -> Option<&'a mut ResXMLTreeAttribute> {
self.element
.attributes
.iter_mut()
.find(|attr| attr.name.resolve(strings) == Some(name))
}
pub fn insert_attribute(
&mut self,
name: Cow<'_, str>,
value: ResValue,
strings: &mut StringPoolHandler,
mut res_map: Option<&mut ResourceMap>,
resource_id: Option<ResTableRef>,
) -> &mut ResXMLTreeAttribute {
let attr_ind = self
.element
.attributes
.iter()
.position(|attr| attr.name.resolve(strings) == Some(name.as_ref()));
let name_index = strings.allocate(name);
if let Some(ref mut res_map) = res_map
&& let Some(resource_id) = resource_id
{
res_map.insert(name_index, resource_id);
}
if let Some(attr_ind) = attr_ind {
let attr = self
.element
.attributes
.get_mut(attr_ind)
.expect("attribute index is valid");
attr.set_value(value);
attr
} else {
let attr = ResXMLTreeAttribute::new()
.with_namespace_ref(self.element.ns)
.with_name_ref(name_index)
.with_value(value);
let position = if let Some(res_map) = res_map
&& let Some(resource_id) = resource_id
{
self.element.attributes.iter().position(|attr| {
res_map
.get(attr.name)
.is_some_and(|attr_ref| *attr_ref >= resource_id)
})
} else {
None
};
if let Some(position) = position {
self.element.attributes.insert(position, attr);
self.element
.attributes
.get_mut(position)
.expect("attribute inserted correctly")
} else {
self.element.attributes.push(attr);
self.element
.attributes
.last_mut()
.expect("attributes was not empty")
}
}
}
pub fn set_attribute(
&mut self,
name: &str,
value: ResValue,
strings: &StringPoolHandler,
) -> Option<&mut ResXMLTreeAttribute> {
let attr = self
.element
.attributes
.iter_mut()
.find(|attr| attr.name.resolve(strings) == Some(name));
if let Some(attr) = attr {
attr.set_value(value);
return Some(attr);
}
None
}
}
impl TryFrom<RawXMLTree> for XMLTree {
type Error = XMLTreeParseError;
fn try_from(value: RawXMLTree) -> Result<Self, Self::Error> {
let mut string_pool: Option<StringPool> = None;
let mut resource_map: Option<ResourceMap> = None;
let mut root: Option<XMLTreeNode> = None;
let mut stack: Vec<XMLTreeNode> = Vec::new();
let mut namespace_stack: Vec<XMLNameSpace> = Vec::new();
for chunk in value.chunks {
match chunk.data {
ResTypeValue::StringPool(sp) => string_pool = Some(sp),
ResTypeValue::XMLStartElement(start_element) => {
let el = XMLTreeNode {
namespace: namespace_stack.last().copied(),
element: start_element,
children: Vec::new(),
};
stack.push(el);
}
ResTypeValue::XMLEndElement(_) => {
let el = stack.pop().ok_or(XMLTreeParseError::TooManyEndElements)?;
if let Some(parent) = stack.last_mut() {
parent.children.push(el)
} else {
if root.is_some() {
return Err(XMLTreeParseError::MultipleRootNodes);
}
root = Some(el);
}
}
ResTypeValue::XMLStartNameSpace(start_namespace) => {
namespace_stack.push(XMLNameSpace {
prefix: start_namespace.prefix,
uri: start_namespace.uri,
})
}
ResTypeValue::XMLEndNameSpace(_) => {
_ = namespace_stack
.pop()
.ok_or(XMLTreeParseError::TooManyEndNamespaces)?
}
ResTypeValue::ResourceMap(rm) => resource_map = Some(rm),
v => return Err(XMLTreeParseError::UnrecognisedChunk((&v).into())),
}
}
if !stack.is_empty() {
return Err(XMLTreeParseError::TooFewEndElements);
}
if !namespace_stack.is_empty() {
return Err(XMLTreeParseError::TooFewEndNamespaces);
}
Ok(Self {
string_pool: string_pool.ok_or(XMLTreeParseError::NoStringPool)?.into(),
resource_map,
root: root.ok_or(XMLTreeParseError::NoRoot)?,
})
}
}