Skip to main content

android_abx/
event.rs

1//! The shared event/data model — [`Event`], [`Attribute`], [`AttributeValue`],
2//! [`InternedStr`] — and the XML-rendering logic built on them.
3//! `to_xml`/`write_xml` on both parsers call into [`render_event`].
4
5use base64::Engine as _;
6
7/// Renders a hex-typed attribute value like AOSP's `Integer.toString(v, 16)`/
8/// `Long.toString(v, 16)`: `v` is signed, so a negative value renders as
9/// `-` + hex(magnitude), not the raw bit pattern (e.g. `0xCAFEBABE` is
10/// `"-35014542"`, not `"cafebabe"`).
11fn format_signed_hex(v: i64) -> String {
12    if v < 0 {
13        format!("-{:x}", v.unsigned_abs())
14    } else {
15        format!("{v:x}")
16    }
17}
18
19/// The typed payload of an XML attribute.
20#[derive(Debug, Clone, PartialEq)]
21pub enum AttributeValue {
22    /// No value (AOSP's `TYPE_NULL`).
23    Null,
24    /// A plain UTF-8 string.
25    String(String),
26    /// Bytes whose canonical text form is lowercase hex.
27    BytesHex(Vec<u8>),
28    /// Bytes whose canonical text form is Base64.
29    BytesBase64(Vec<u8>),
30    /// A 32-bit signed integer, rendered in decimal.
31    Int(i32),
32    /// A 32-bit value rendered in hex — see [`AttributeValue::as_str`] for
33    /// the signed-magnitude rendering rule this follows.
34    IntHex(u32),
35    /// A 64-bit signed integer, rendered in decimal.
36    Long(i64),
37    /// A 64-bit value rendered in hex — see [`AttributeValue::as_str`] for
38    /// the signed-magnitude rendering rule this follows.
39    LongHex(u64),
40    /// A 32-bit floating-point value.
41    Float(f32),
42    /// A 64-bit floating-point value.
43    Double(f64),
44    /// A boolean, rendered as `"true"`/`"false"`.
45    Boolean(bool),
46}
47
48impl AttributeValue {
49    /// Render the value as a human-readable string, mirroring the original
50    /// Java serializer's output.
51    pub fn as_str(&self) -> std::borrow::Cow<'_, str> {
52        use std::borrow::Cow;
53        match self {
54            AttributeValue::Null => Cow::Borrowed(""),
55            AttributeValue::String(s) => Cow::Borrowed(s.as_str()),
56            AttributeValue::BytesHex(b) => Cow::Owned(faster_hex::hex_string(b)),
57            AttributeValue::BytesBase64(b) => {
58                Cow::Owned(base64::engine::general_purpose::STANDARD.encode(b))
59            }
60            AttributeValue::Int(v) => Cow::Owned(v.to_string()),
61            AttributeValue::IntHex(v) => Cow::Owned(format_signed_hex(*v as i32 as i64)),
62            AttributeValue::Long(v) => Cow::Owned(v.to_string()),
63            AttributeValue::LongHex(v) => Cow::Owned(format_signed_hex(*v as i64)),
64            AttributeValue::Float(v) => {
65                if v.fract() == 0.0 && v.is_finite() {
66                    Cow::Owned(format!("{:.1}", v))
67                } else {
68                    Cow::Owned(v.to_string())
69                }
70            }
71            AttributeValue::Double(v) => {
72                if v.fract() == 0.0 && v.is_finite() {
73                    Cow::Owned(format!("{:.1}", v))
74                } else {
75                    Cow::Owned(v.to_string())
76                }
77            }
78            AttributeValue::Boolean(b) => {
79                if *b {
80                    Cow::Borrowed("true")
81                } else {
82                    Cow::Borrowed("false")
83                }
84            }
85        }
86    }
87
88    /// Returns the inner string if this is [`AttributeValue::String`], else `None`.
89    pub fn as_string(&self) -> Option<&str> {
90        if let AttributeValue::String(s) = self {
91            Some(s)
92        } else {
93            None
94        }
95    }
96    /// Returns the inner value if this is [`AttributeValue::Int`], else `None`.
97    pub fn as_int(&self) -> Option<i32> {
98        if let AttributeValue::Int(v) = self {
99            Some(*v)
100        } else {
101            None
102        }
103    }
104    /// Returns the inner value if this is [`AttributeValue::IntHex`], else `None`.
105    pub fn as_int_hex(&self) -> Option<u32> {
106        if let AttributeValue::IntHex(v) = self {
107            Some(*v)
108        } else {
109            None
110        }
111    }
112    /// Returns the inner value if this is [`AttributeValue::Long`], else `None`.
113    pub fn as_long(&self) -> Option<i64> {
114        if let AttributeValue::Long(v) = self {
115            Some(*v)
116        } else {
117            None
118        }
119    }
120    /// Returns the inner value if this is [`AttributeValue::LongHex`], else `None`.
121    pub fn as_long_hex(&self) -> Option<u64> {
122        if let AttributeValue::LongHex(v) = self {
123            Some(*v)
124        } else {
125            None
126        }
127    }
128    /// Returns the inner value if this is [`AttributeValue::Float`], else `None`.
129    pub fn as_float(&self) -> Option<f32> {
130        if let AttributeValue::Float(v) = self {
131            Some(*v)
132        } else {
133            None
134        }
135    }
136    /// Returns the inner value if this is [`AttributeValue::Double`], else `None`.
137    pub fn as_double(&self) -> Option<f64> {
138        if let AttributeValue::Double(v) = self {
139            Some(*v)
140        } else {
141            None
142        }
143    }
144    /// Returns the inner value if this is [`AttributeValue::Boolean`], else `None`.
145    pub fn as_bool(&self) -> Option<bool> {
146        if let AttributeValue::Boolean(b) = self {
147            Some(*b)
148        } else {
149            None
150        }
151    }
152    /// Returns the inner bytes if this is [`AttributeValue::BytesHex`] or
153    /// [`AttributeValue::BytesBase64`], else `None`.
154    pub fn as_bytes(&self) -> Option<&[u8]> {
155        match self {
156            AttributeValue::BytesHex(b) | AttributeValue::BytesBase64(b) => Some(b),
157            _ => None,
158        }
159    }
160}
161
162/// A tag or attribute name read from the wire format's interned-string
163/// pool. The same handful of names (`pkg`, `name`, `version`, ...) repeat
164/// across every element in a typical document, so back-reference clones
165/// need to be cheap.
166///
167/// `InternedStr` is [`smol_str::SmolStr`]: strings up to 23 bytes are
168/// stored inline (clone is a stack copy), longer ones fall back to a
169/// reference-counted `Arc<str>` (clone is a refcount bump) — either way, no
170/// allocation on clone.
171pub type InternedStr = smol_str::SmolStr;
172
173/// One XML attribute: a name plus its typed value.
174#[derive(Debug, Clone, PartialEq)]
175pub struct Attribute {
176    /// The attribute's name.
177    pub name: InternedStr,
178    /// The attribute's typed value.
179    pub value: AttributeValue,
180}
181
182impl Attribute {
183    /// Render [`Attribute::value`] as a human-readable string; see
184    /// [`AttributeValue::as_str`].
185    pub fn as_str(&self) -> std::borrow::Cow<'_, str> {
186        self.value.as_str()
187    }
188}
189
190/// One `XmlPullParser`-style parse event, as read from (or written to) an
191/// ABX stream.
192#[derive(Debug, Clone, PartialEq)]
193pub enum Event {
194    /// The start of the document — always the first event.
195    StartDocument,
196    /// The end of the document — always the last event.
197    EndDocument,
198    /// The opening tag of an element, with its attributes.
199    StartTag {
200        /// The element's tag name.
201        name: InternedStr,
202        /// The element's attributes, in document order.
203        attributes: Vec<Attribute>,
204    },
205    /// The closing tag of an element.
206    EndTag {
207        /// The element's tag name, matching the corresponding [`Event::StartTag`].
208        name: InternedStr,
209    },
210    /// Plain character data between tags.
211    Text(String),
212    /// A `<![CDATA[...]]>` section's raw content.
213    CdataSection(String),
214    /// A `<!--...-->` comment's raw content.
215    Comment(String),
216    /// A `<?...?>` processing instruction's raw content.
217    ProcessingInstruction(String),
218    /// An entity reference's raw name (e.g. `"amp"` for `&amp;`), not the
219    /// resolved character — see the crate docs for why.
220    EntityReference(String),
221    /// Whitespace-only character data that a validating parser would treat
222    /// as ignorable.
223    IgnorableWhitespace(String),
224    /// A `<!DOCTYPE ...>` declaration's raw content.
225    DocDecl(String),
226}
227
228pub(crate) fn xml_escape(s: &str) -> std::borrow::Cow<'_, str> {
229    match s.find(['<', '>', '&', '"', '\'']) {
230        None => std::borrow::Cow::Borrowed(s),
231        Some(first) => {
232            let mut out = String::with_capacity(s.len() + 8);
233            out.push_str(&s[..first]);
234            for c in s[first..].chars() {
235                match c {
236                    '<' => out.push_str("&lt;"),
237                    '>' => out.push_str("&gt;"),
238                    '&' => out.push_str("&amp;"),
239                    '"' => out.push_str("&quot;"),
240                    '\'' => out.push_str("&apos;"),
241                    other => out.push(other),
242                }
243            }
244            std::borrow::Cow::Owned(out)
245        }
246    }
247}
248
249/// Write `value`'s XML-attribute text form directly into `buf`, without
250/// allocating an intermediate `String`. Numeric/bool/bytes output can never
251/// contain an XML-special character, so those variants also skip the
252/// escaping scan entirely.
253fn push_attr_value(buf: &mut String, value: &AttributeValue) {
254    use std::fmt::Write as _;
255    match value {
256        AttributeValue::Null => {}
257        AttributeValue::String(s) => buf.push_str(&xml_escape(s)),
258        AttributeValue::BytesHex(b) => buf.push_str(&faster_hex::hex_string(b)),
259        AttributeValue::BytesBase64(b) => {
260            base64::engine::general_purpose::STANDARD.encode_string(b, buf);
261        }
262        AttributeValue::Int(v) => {
263            let _ = write!(buf, "{v}");
264        }
265        AttributeValue::IntHex(v) => {
266            let _ = write!(buf, "{}", format_signed_hex(*v as i32 as i64));
267        }
268        AttributeValue::Long(v) => {
269            let _ = write!(buf, "{v}");
270        }
271        AttributeValue::LongHex(v) => {
272            let _ = write!(buf, "{}", format_signed_hex(*v as i64));
273        }
274        AttributeValue::Float(v) => {
275            if v.fract() == 0.0 && v.is_finite() {
276                let _ = write!(buf, "{v:.1}");
277            } else {
278                let _ = write!(buf, "{v}");
279            }
280        }
281        AttributeValue::Double(v) => {
282            if v.fract() == 0.0 && v.is_finite() {
283                let _ = write!(buf, "{v:.1}");
284            } else {
285                let _ = write!(buf, "{v}");
286            }
287        }
288        AttributeValue::Boolean(b) => buf.push_str(if *b { "true" } else { "false" }),
289    }
290}
291
292/// Shared render-to-XML logic used by both parsers.
293pub(crate) fn render_event(ev: &Event, buf: &mut String) {
294    match ev {
295        Event::StartDocument | Event::EndDocument => {}
296        Event::StartTag { name, attributes } => {
297            buf.push('<');
298            buf.push_str(name);
299            for attr in attributes {
300                buf.push(' ');
301                buf.push_str(&attr.name);
302                buf.push_str("=\"");
303                push_attr_value(buf, &attr.value);
304                buf.push('"');
305            }
306            buf.push('>');
307        }
308        Event::EndTag { name } => {
309            buf.push_str("</");
310            buf.push_str(name);
311            buf.push('>');
312        }
313        Event::Text(t) if !t.is_empty() => buf.push_str(&xml_escape(t)),
314        Event::CdataSection(t) => {
315            buf.push_str("<![CDATA[");
316            buf.push_str(t);
317            buf.push_str("]]>");
318        }
319        Event::Comment(t) => {
320            buf.push_str("<!--");
321            buf.push_str(t);
322            buf.push_str("-->");
323        }
324        Event::ProcessingInstruction(t) => {
325            buf.push_str("<?");
326            buf.push_str(t);
327            buf.push_str("?>");
328        }
329        Event::EntityReference(t) => {
330            buf.push('&');
331            buf.push_str(t);
332            buf.push(';');
333        }
334        Event::IgnorableWhitespace(t) => buf.push_str(t),
335        Event::DocDecl(t) => {
336            buf.push_str("<!DOCTYPE ");
337            buf.push_str(t);
338            buf.push('>');
339        }
340        _ => {}
341    }
342}