Skip to main content

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