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