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 `&`), 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("<"),
258 '>' => out.push_str(">"),
259 '&' => out.push_str("&"),
260 '"' => out.push_str("""),
261 '\'' => out.push_str("'"),
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}