wgtk/pxml/
mod.rs

1//! Packed XML codec. This codec is widely use in Wargaming games' files.
2//! 
3//! This is basically a binary compression of an XML file, excepting that
4//! some pattern can't be reproduced into. This is why a custom set of
5//! structures are introduced in this module to handle this case, such as
6//! [`Value`] and [`Element`].
7
8use glam::{Vec3A, Affine3A};
9use smallvec::SmallVec;
10
11mod de;
12mod ser;
13
14pub use de::{from_reader, from_bytes, DeError};
15pub use ser::to_writer;
16
17
18/// Magic of a packed XML file.
19pub const MAGIC: &[u8; 4] = b"\x45\x4E\xA1\x62";
20
21
22/// A packed XML untyped value.
23#[derive(Debug, Clone)]
24pub enum Value {
25    Element(Box<Element>),
26    String(String),
27    Integer(i64),
28    Boolean(bool),
29    Float(f32),
30    Vec3(Vec3A),
31    Affine3(Affine3A),
32}
33
34/// A packed element.
35#[derive(Debug, Clone)]
36pub struct Element {
37    /// Proper value of a element.
38    pub value: Value,
39    /// Children values, each value is mapped to a name that
40    /// is not guaranteed to be unique.
41    children: SmallVec<[(String, Value); 8]>,
42}
43
44impl Element {
45
46    pub fn new() -> Self {
47        Self { 
48            value: Value::Boolean(false), 
49            children: SmallVec::new(),
50        }
51    }
52
53    pub fn iter_children_all(&self) -> impl Iterator<Item = &'_ (String, Value)> {
54        self.children.iter()
55    }
56
57    pub fn add_children<S: Into<String>>(&mut self, key: S, value: Value) {
58        self.children.push((key.into(), value));
59    }
60
61    pub fn iter_children<'a, 'b: 'a>(&'a self, key: &'b str) -> impl Iterator<Item = &'a Value> + 'a {
62        self.children.iter().filter_map(move |(k, v)| (k == key).then_some(v))
63    }
64
65    pub fn iter_children_mut<'a, 'b: 'a>(&'a mut self, key: &'b str) -> impl Iterator<Item = &'a mut Value> + 'a {
66        self.children.iter_mut().filter_map(move |(k, v)| (k == key).then_some(v))
67    }
68
69    pub fn get_child<'a, 'b>(&'a self, key: &'b str) -> Option<&'a Value> {
70        self.children.iter().find_map(|(k, v)| (k == key).then_some(v))
71    }
72
73    pub fn get_child_mut<'a, 'b>(&'a mut self, key: &'b str) -> Option<&'a mut Value> {
74        self.children.iter_mut().find_map(|(k, v)| (k == key).then_some(v))
75    }
76
77}
78
79impl Value {
80
81    /// Try to get this value as an element if possible.
82    #[inline]
83    pub fn as_element(&self) -> Option<&Element> {
84        if let Self::Element(elt) = self { Some(&**elt) } else { None }
85    }
86
87    /// Try to get this value as a string if possible.
88    #[inline]
89    pub fn as_string(&self) -> Option<&String> {
90        if let Self::String(s) = self { Some(s) } else { None }
91    }
92
93    /// Try to get this value as an integer if possible.
94    #[inline]
95    pub fn as_integer(&self) -> Option<i64> {
96        if let Self::Integer(n) = *self { Some(n) } else { None }
97    }
98
99    /// Try to get this value as a boolean is possible.
100    #[inline]
101    pub fn as_boolean(&self) -> Option<bool> {
102        if let Self::Boolean(b) = *self { Some(b) } else { None }
103    }
104
105    /// Try to get this value as a float is possible.
106    #[inline]
107    pub fn as_float(&self) -> Option<f32> {
108        if let Self::Float(n) = *self { Some(n) } else { None }
109    }
110
111    /// Try to get this value as a vec3 is possible.
112    #[inline]
113    pub fn as_vec3(&self) -> Option<Vec3A> {
114        if let Self::Vec3(n) = *self { Some(n) } else { None }
115    }
116
117    /// Try to get this value as an affine3 is possible.
118    #[inline]
119    pub fn as_affine3(&self) -> Option<Affine3A> {
120        if let Self::Affine3(n) = *self { Some(n) } else { None }
121    }
122
123}
124
125
126/// Internally used data types for values.
127#[derive(Debug, Clone, Copy)]
128enum DataType {
129    Element = 0,
130    String = 1,
131    Integer = 2,
132    Float = 3,
133    Boolean = 4,
134    /// This special kind act like a compressed string.
135    /// This type is only used when the string to compress has a length
136    /// that is a multiple of 4 and composed of the base64 charset. In 
137    /// such case the string is base64-decoded, the resulting bytes
138    /// are used instead of the string. To get the original string
139    /// we need to encode the input.
140    CompressedString = 5,
141}
142
143impl DataType {
144
145    /// Return the data type from its raw 
146    fn from_raw(raw: u32) -> Option<Self> {
147        Some(match raw {
148            0 => Self::Element,
149            1 => Self::String,
150            2 => Self::Integer,
151            3 => Self::Float,
152            4 => Self::Boolean,
153            5 => Self::CompressedString,
154            _ => return None
155        })
156    }
157
158    fn to_raw(self) -> u32 {
159        match self {
160            DataType::Element => 0,
161            DataType::String => 1,
162            DataType::Integer => 2,
163            DataType::Float => 3,
164            DataType::Boolean => 4,
165            DataType::CompressedString => 5
166        }
167    }
168
169}