xml2abx/
lib.rs

1//! # xml2abx
2//!
3//! A library for converting XML to Android Binary XML format.
4//!
5//! ## Example
6//!
7//! ```rust
8//! use xml2abx::XmlToAbxConverter;
9//! use std::io::Cursor;
10//!
11//! let xml = r#"<?xml version="1.0"?>
12//! <root>
13//!     <element attr="value">text content</element>
14//! </root>"#;
15//!
16//! let mut output = Vec::new();
17//! XmlToAbxConverter::convert_from_string(xml, &mut output).unwrap();
18//! ```
19
20use base64::Engine;
21use byteorder::{BigEndian, WriteBytesExt};
22use hex;
23use quick_xml::Reader;
24use quick_xml::events::Event;
25use std::collections::HashMap;
26use std::io::{self, BufRead, Write};
27use thiserror::Error;
28
29#[derive(Error, Debug)]
30pub enum ConversionError {
31    #[error("XML parsing failed: {0}")]
32    XmlParsing(#[from] quick_xml::Error),
33    #[error("IO error: {0}")]
34    Io(#[from] io::Error),
35    #[error("String too long: {0} bytes (max: {1})")]
36    StringTooLong(usize, usize),
37    #[error("Binary data too long: {0} bytes (max: {1})")]
38    BinaryDataTooLong(usize, usize),
39    #[error("Invalid hex string")]
40    InvalidHex,
41    #[error("Invalid base64 string")]
42    InvalidBase64,
43    #[error("UTF-8 conversion error: {0}")]
44    Utf8Error(#[from] std::str::Utf8Error),
45    #[error("Attribute error: {0}")]
46    AttrError(#[from] quick_xml::events::attributes::AttrError),
47}
48
49/// show a warning about unsupported features
50pub fn show_warning(feature: &str, details: Option<&str>) {
51    eprintln!("WARNING: {} is not supported and might be lost.", feature);
52    if let Some(details) = details {
53        eprintln!("  {}", details);
54    }
55}
56
57pub struct FastDataOutput<W: Write> {
58    writer: W,
59    string_pool: HashMap<String, u16>,
60    interned_strings: Vec<String>,
61}
62
63impl<W: Write> FastDataOutput<W> {
64    pub const MAX_UNSIGNED_SHORT: u16 = 65535;
65
66    pub fn new(writer: W) -> Self {
67        Self {
68            writer,
69            string_pool: HashMap::new(),
70            interned_strings: Vec::new(),
71        }
72    }
73
74    pub fn write_byte(&mut self, value: u8) -> Result<(), ConversionError> {
75        self.writer.write_u8(value)?;
76        Ok(())
77    }
78
79    pub fn write_short(&mut self, value: u16) -> Result<(), ConversionError> {
80        self.writer.write_u16::<BigEndian>(value)?;
81        Ok(())
82    }
83
84    pub fn write_int(&mut self, value: i32) -> Result<(), ConversionError> {
85        self.writer.write_i32::<BigEndian>(value)?;
86        Ok(())
87    }
88
89    pub fn write_long(&mut self, value: i64) -> Result<(), ConversionError> {
90        self.writer.write_i64::<BigEndian>(value)?;
91        Ok(())
92    }
93
94    pub fn write_float(&mut self, value: f32) -> Result<(), ConversionError> {
95        self.writer.write_f32::<BigEndian>(value)?;
96        Ok(())
97    }
98
99    pub fn write_double(&mut self, value: f64) -> Result<(), ConversionError> {
100        self.writer.write_f64::<BigEndian>(value)?;
101        Ok(())
102    }
103
104    pub fn write_utf(&mut self, s: &str) -> Result<(), ConversionError> {
105        let bytes = s.as_bytes();
106        if bytes.len() > Self::MAX_UNSIGNED_SHORT as usize {
107            return Err(ConversionError::StringTooLong(
108                bytes.len(),
109                Self::MAX_UNSIGNED_SHORT as usize,
110            ));
111        }
112        self.write_short(bytes.len() as u16)?;
113        self.writer.write_all(bytes)?;
114        Ok(())
115    }
116
117    pub fn write_interned_utf(&mut self, s: &str) -> Result<(), ConversionError> {
118        if let Some(&index) = self.string_pool.get(s) {
119            self.write_short(index)?;
120        } else {
121            self.write_short(0xFFFF)?;
122            self.write_utf(s)?;
123            let index = self.interned_strings.len() as u16;
124            self.string_pool.insert(s.to_string(), index);
125            self.interned_strings.push(s.to_string());
126        }
127        Ok(())
128    }
129
130    pub fn write_bytes(&mut self, data: &[u8]) -> Result<(), ConversionError> {
131        self.writer.write_all(data)?;
132        Ok(())
133    }
134
135    pub fn flush(&mut self) -> Result<(), ConversionError> {
136        self.writer.flush()?;
137        Ok(())
138    }
139}
140
141pub struct BinaryXmlSerializer<W: Write> {
142    output: FastDataOutput<W>,
143    tag_count: usize,
144    tag_names: Vec<String>,
145    preserve_whitespace: bool,
146}
147
148// Constants
149impl<W: Write> BinaryXmlSerializer<W> {
150    pub const PROTOCOL_MAGIC_VERSION_0: [u8; 4] = [0x41, 0x42, 0x58, 0x00];
151    pub const START_DOCUMENT: u8 = 0;
152    pub const END_DOCUMENT: u8 = 1;
153    pub const START_TAG: u8 = 2;
154    pub const END_TAG: u8 = 3;
155    pub const TEXT: u8 = 4;
156    pub const CDSECT: u8 = 5;
157    pub const ENTITY_REF: u8 = 6;
158    pub const IGNORABLE_WHITESPACE: u8 = 7;
159    pub const PROCESSING_INSTRUCTION: u8 = 8;
160    pub const COMMENT: u8 = 9;
161    pub const DOCDECL: u8 = 10;
162    pub const ATTRIBUTE: u8 = 15;
163
164    pub const TYPE_NULL: u8 = 1 << 4;
165    pub const TYPE_STRING: u8 = 2 << 4;
166    pub const TYPE_STRING_INTERNED: u8 = 3 << 4;
167    pub const TYPE_BYTES_HEX: u8 = 4 << 4;
168    pub const TYPE_BYTES_BASE64: u8 = 5 << 4;
169    pub const TYPE_INT: u8 = 6 << 4;
170    pub const TYPE_INT_HEX: u8 = 7 << 4;
171    pub const TYPE_LONG: u8 = 8 << 4;
172    pub const TYPE_LONG_HEX: u8 = 9 << 4;
173    pub const TYPE_FLOAT: u8 = 10 << 4;
174    pub const TYPE_DOUBLE: u8 = 11 << 4;
175    pub const TYPE_BOOLEAN_TRUE: u8 = 12 << 4;
176    pub const TYPE_BOOLEAN_FALSE: u8 = 13 << 4;
177
178    pub fn new(writer: W) -> Result<Self, ConversionError> {
179        Self::with_options(writer, true)
180    }
181
182    pub fn with_options(writer: W, preserve_whitespace: bool) -> Result<Self, ConversionError> {
183        let mut output = FastDataOutput::new(writer);
184        output.write_bytes(&Self::PROTOCOL_MAGIC_VERSION_0)?;
185
186        Ok(Self {
187            output,
188            tag_count: 0,
189            tag_names: Vec::with_capacity(8),
190            preserve_whitespace,
191        })
192    }
193
194    fn write_token(&mut self, token: u8, text: Option<&str>) -> Result<(), ConversionError> {
195        if let Some(text) = text {
196            self.output.write_byte(token | Self::TYPE_STRING)?;
197            self.output.write_utf(text)?;
198        } else {
199            self.output.write_byte(token | Self::TYPE_NULL)?;
200        }
201        Ok(())
202    }
203
204    pub fn start_document(&mut self) -> Result<(), ConversionError> {
205        self.output
206            .write_byte(Self::START_DOCUMENT | Self::TYPE_NULL)
207    }
208
209    pub fn end_document(&mut self) -> Result<(), ConversionError> {
210        self.output
211            .write_byte(Self::END_DOCUMENT | Self::TYPE_NULL)?;
212        self.output.flush()
213    }
214
215    pub fn start_tag(&mut self, name: &str) -> Result<(), ConversionError> {
216        if self.tag_count == self.tag_names.len() {
217            let new_size = self.tag_count + std::cmp::max(1, self.tag_count / 2);
218            self.tag_names.resize(new_size, String::new());
219        }
220        self.tag_names[self.tag_count] = name.to_string();
221        self.tag_count += 1;
222
223        self.output
224            .write_byte(Self::START_TAG | Self::TYPE_STRING_INTERNED)?;
225        self.output.write_interned_utf(name)
226    }
227
228    pub fn end_tag(&mut self, name: &str) -> Result<(), ConversionError> {
229        self.tag_count -= 1;
230        self.output
231            .write_byte(Self::END_TAG | Self::TYPE_STRING_INTERNED)?;
232        self.output.write_interned_utf(name)
233    }
234
235    pub fn attribute(&mut self, name: &str, value: &str) -> Result<(), ConversionError> {
236        self.output
237            .write_byte(Self::ATTRIBUTE | Self::TYPE_STRING)?;
238        self.output.write_interned_utf(name)?;
239        self.output.write_utf(value)
240    }
241
242    pub fn attribute_interned(&mut self, name: &str, value: &str) -> Result<(), ConversionError> {
243        self.output
244            .write_byte(Self::ATTRIBUTE | Self::TYPE_STRING_INTERNED)?;
245        self.output.write_interned_utf(name)?;
246        self.output.write_interned_utf(value)
247    }
248
249    pub fn attribute_bytes_hex(&mut self, name: &str, value: &[u8]) -> Result<(), ConversionError> {
250        if value.len() > FastDataOutput::<W>::MAX_UNSIGNED_SHORT as usize {
251            return Err(ConversionError::BinaryDataTooLong(
252                value.len(),
253                FastDataOutput::<W>::MAX_UNSIGNED_SHORT as usize,
254            ));
255        }
256        self.output
257            .write_byte(Self::ATTRIBUTE | Self::TYPE_BYTES_HEX)?;
258        self.output.write_interned_utf(name)?;
259        self.output.write_short(value.len() as u16)?;
260        self.output.write_bytes(value)
261    }
262
263    pub fn attribute_bytes_base64(
264        &mut self,
265        name: &str,
266        value: &[u8],
267    ) -> Result<(), ConversionError> {
268        if value.len() > FastDataOutput::<W>::MAX_UNSIGNED_SHORT as usize {
269            return Err(ConversionError::BinaryDataTooLong(
270                value.len(),
271                FastDataOutput::<W>::MAX_UNSIGNED_SHORT as usize,
272            ));
273        }
274        self.output
275            .write_byte(Self::ATTRIBUTE | Self::TYPE_BYTES_BASE64)?;
276        self.output.write_interned_utf(name)?;
277        self.output.write_short(value.len() as u16)?;
278        self.output.write_bytes(value)
279    }
280
281    pub fn attribute_int(&mut self, name: &str, value: i32) -> Result<(), ConversionError> {
282        self.output.write_byte(Self::ATTRIBUTE | Self::TYPE_INT)?;
283        self.output.write_interned_utf(name)?;
284        self.output.write_int(value)
285    }
286
287    pub fn attribute_int_hex(&mut self, name: &str, value: i32) -> Result<(), ConversionError> {
288        self.output
289            .write_byte(Self::ATTRIBUTE | Self::TYPE_INT_HEX)?;
290        self.output.write_interned_utf(name)?;
291        self.output.write_int(value)
292    }
293
294    pub fn attribute_long(&mut self, name: &str, value: i64) -> Result<(), ConversionError> {
295        self.output.write_byte(Self::ATTRIBUTE | Self::TYPE_LONG)?;
296        self.output.write_interned_utf(name)?;
297        self.output.write_long(value)
298    }
299
300    pub fn attribute_long_hex(&mut self, name: &str, value: i64) -> Result<(), ConversionError> {
301        self.output
302            .write_byte(Self::ATTRIBUTE | Self::TYPE_LONG_HEX)?;
303        self.output.write_interned_utf(name)?;
304        self.output.write_long(value)
305    }
306
307    pub fn attribute_float(&mut self, name: &str, value: f32) -> Result<(), ConversionError> {
308        self.output.write_byte(Self::ATTRIBUTE | Self::TYPE_FLOAT)?;
309        self.output.write_interned_utf(name)?;
310        self.output.write_float(value)
311    }
312
313    pub fn attribute_double(&mut self, name: &str, value: f64) -> Result<(), ConversionError> {
314        self.output
315            .write_byte(Self::ATTRIBUTE | Self::TYPE_DOUBLE)?;
316        self.output.write_interned_utf(name)?;
317        self.output.write_double(value)
318    }
319
320    pub fn attribute_boolean(&mut self, name: &str, value: bool) -> Result<(), ConversionError> {
321        let token = if value {
322            Self::ATTRIBUTE | Self::TYPE_BOOLEAN_TRUE
323        } else {
324            Self::ATTRIBUTE | Self::TYPE_BOOLEAN_FALSE
325        };
326        self.output.write_byte(token)?;
327        self.output.write_interned_utf(name)
328    }
329
330    pub fn text(&mut self, text: &str) -> Result<(), ConversionError> {
331        self.write_token(Self::TEXT, Some(text))
332    }
333
334    pub fn cdsect(&mut self, text: &str) -> Result<(), ConversionError> {
335        self.write_token(Self::CDSECT, Some(text))
336    }
337
338    pub fn comment(&mut self, text: &str) -> Result<(), ConversionError> {
339        self.write_token(Self::COMMENT, Some(text))
340    }
341
342    pub fn processing_instruction(
343        &mut self,
344        target: &str,
345        data: Option<&str>,
346    ) -> Result<(), ConversionError> {
347        let full_pi = if let Some(data) = data {
348            if data.is_empty() {
349                target.to_string()
350            } else {
351                format!("{} {}", target, data)
352            }
353        } else {
354            target.to_string()
355        };
356        self.write_token(Self::PROCESSING_INSTRUCTION, Some(&full_pi))
357    }
358
359    pub fn docdecl(&mut self, text: &str) -> Result<(), ConversionError> {
360        self.write_token(Self::DOCDECL, Some(text))
361    }
362
363    pub fn ignorable_whitespace(&mut self, text: &str) -> Result<(), ConversionError> {
364        self.write_token(Self::IGNORABLE_WHITESPACE, Some(text))
365    }
366
367    pub fn entity_ref(&mut self, text: &str) -> Result<(), ConversionError> {
368        self.write_token(Self::ENTITY_REF, Some(text))
369    }
370}
371
372mod type_detection {
373    pub fn is_numeric(s: &str) -> bool {
374        if s.is_empty() {
375            return false;
376        }
377        let start = if s.starts_with('-') { 1 } else { 0 };
378        s.chars().skip(start).all(|c| c.is_ascii_digit())
379    }
380
381    pub fn is_hex_number(s: &str) -> bool {
382        if s.len() < 3 {
383            return false;
384        }
385        let lower = s.to_lowercase();
386        if !lower.starts_with("0x") {
387            return false;
388        }
389        s.chars().skip(2).all(|c| c.is_ascii_hexdigit())
390    }
391
392    pub fn is_float(s: &str) -> bool {
393        if s.is_empty() {
394            return false;
395        }
396        let mut has_dot = false;
397        let start = if s.starts_with('-') { 1 } else { 0 };
398
399        for c in s.chars().skip(start) {
400            if c == '.' {
401                if has_dot {
402                    return false;
403                }
404                has_dot = true;
405            } else if !c.is_ascii_digit() {
406                return false;
407            }
408        }
409        has_dot
410    }
411
412    pub fn is_double(s: &str) -> bool {
413        if s.contains('e') || s.contains('E') {
414            return true;
415        }
416        if is_float(s) && s.len() > 10 {
417            return true;
418        }
419        false
420    }
421
422    pub fn is_boolean(s: &str) -> bool {
423        s == "true" || s == "false"
424    }
425
426    pub fn is_base64(s: &str) -> bool {
427        if s.is_empty() || s.len() % 4 != 0 {
428            return false;
429        }
430        s.chars()
431            .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '/' || c == '=')
432    }
433
434    pub fn is_hex_string(s: &str) -> bool {
435        if s.len() % 2 != 0 {
436            return false;
437        }
438        s.chars().all(|c| c.is_ascii_hexdigit())
439    }
440
441    pub fn is_whitespace_only(s: &str) -> bool {
442        s.chars().all(|c| c.is_whitespace())
443    }
444}
445
446pub struct XmlToAbxConverter;
447
448impl XmlToAbxConverter {
449    pub fn convert_from_string<W: Write>(xml: &str, writer: W) -> Result<(), ConversionError> {
450        Self::convert_from_string_with_options(xml, writer, true)
451    }
452
453    pub fn convert_from_string_with_options<W: Write>(
454        xml: &str,
455        writer: W,
456        preserve_whitespace: bool,
457    ) -> Result<(), ConversionError> {
458        let mut reader = Reader::from_str(xml);
459        reader.config_mut().trim_text(!preserve_whitespace);
460        Self::convert_reader_with_options(reader, writer, preserve_whitespace)
461    }
462
463    pub fn convert_from_file<W: Write>(input_path: &str, writer: W) -> Result<(), ConversionError> {
464        Self::convert_from_file_with_options(input_path, writer, true)
465    }
466
467    pub fn convert_from_file_with_options<W: Write>(
468        input_path: &str,
469        writer: W,
470        preserve_whitespace: bool,
471    ) -> Result<(), ConversionError> {
472        let mut reader = Reader::from_file(input_path)?;
473        reader.config_mut().trim_text(!preserve_whitespace);
474        Self::convert_reader_with_options(reader, writer, preserve_whitespace)
475    }
476
477    pub fn convert_from_reader<R: BufRead, W: Write>(
478        input: R,
479        writer: W,
480    ) -> Result<(), ConversionError> {
481        Self::convert_from_reader_with_options(input, writer, true)
482    }
483
484    pub fn convert_from_reader_with_options<R: BufRead, W: Write>(
485        input: R,
486        writer: W,
487        preserve_whitespace: bool,
488    ) -> Result<(), ConversionError> {
489        let mut reader = Reader::from_reader(input);
490        reader.config_mut().trim_text(!preserve_whitespace);
491        Self::convert_reader_with_options(reader, writer, preserve_whitespace)
492    }
493
494   /// fn convert_reader<R: BufRead, W: Write>(
495   ///     reader: Reader<R>,
496   ///     writer: W,
497   /// ) -> Result<(), ConversionError> {
498   ///      Self::convert_reader_with_options(reader, writer, true)
499   ///  }
500
501    fn convert_reader_with_options<R: BufRead, W: Write>(
502        mut reader: Reader<R>,
503        writer: W,
504        preserve_whitespace: bool,
505    ) -> Result<(), ConversionError> {
506        let mut serializer = BinaryXmlSerializer::with_options(writer, preserve_whitespace)?;
507        let mut buf = Vec::new();
508        let mut tag_stack = Vec::new();
509
510        serializer.start_document()?;
511
512        loop {
513            match reader.read_event_into(&mut buf)? {
514                Event::Start(e) => {
515                    let name_bytes = e.name();
516                    let name = std::str::from_utf8(name_bytes.as_ref())?;
517                    if name.contains(':') {
518                        show_warning(
519                            "Namespaces and prefixes",
520                            Some(&format!("Found prefixed element: {}", name)),
521                        );
522                    }
523
524                    serializer.start_tag(name)?;
525                    tag_stack.push(name.to_string());
526                    for attr in e.attributes() {
527                        let attr = attr?;
528                        let attr_name = std::str::from_utf8(attr.key.as_ref())?;
529                        let attr_value = std::str::from_utf8(&attr.value)?;
530                        if attr_name.starts_with("xmlns") || attr_name.contains(':') {
531                            show_warning(
532                                "Namespaces and prefixes",
533                                Some(&format!(
534                                    "Found namespace declaration or prefixed attribute: {}",
535                                    attr_name
536                                )),
537                            );
538                        }
539
540                        Self::write_attribute(&mut serializer, attr_name, attr_value)?;
541                    }
542                }
543                Event::End(e) => {
544                    let name_bytes = e.name();
545                    let name = std::str::from_utf8(name_bytes.as_ref())?;
546                    serializer.end_tag(name)?;
547                    tag_stack.pop();
548                }
549                Event::Empty(e) => {
550                    let name_bytes = e.name();
551                    let name = std::str::from_utf8(name_bytes.as_ref())?;
552                    if name.contains(':') {
553                        show_warning(
554                            "Namespaces and prefixes",
555                            Some(&format!("Found prefixed element: {}", name)),
556                        );
557                    }
558
559                    serializer.start_tag(name)?;
560                    for attr in e.attributes() {
561                        let attr = attr?;
562                        let attr_name = std::str::from_utf8(attr.key.as_ref())?;
563                        let attr_value = std::str::from_utf8(&attr.value)?;
564                        if attr_name.starts_with("xmlns") || attr_name.contains(':') {
565                            show_warning(
566                                "Namespaces and prefixes",
567                                Some(&format!(
568                                    "Found namespace declaration or prefixed attribute: {}",
569                                    attr_name
570                                )),
571                            );
572                        }
573
574                        Self::write_attribute(&mut serializer, attr_name, attr_value)?;
575                    }
576
577                    serializer.end_tag(name)?;
578                }
579                Event::Text(e) => {
580                    let text = std::str::from_utf8(&e)?;
581                    if type_detection::is_whitespace_only(text) {
582                        if serializer.preserve_whitespace {
583                            serializer.ignorable_whitespace(text)?;
584                        }
585                        // If preserve_whitespace is false, we skip whitespace-only text
586                    } else {
587                        serializer.text(text)?;
588                    }
589                }
590                Event::CData(e) => {
591                    let text = std::str::from_utf8(&e)?;
592                    serializer.cdsect(text)?;
593                }
594                Event::Comment(e) => {
595                    let text = std::str::from_utf8(&e)?;
596                    serializer.comment(text)?;
597                }
598                Event::PI(e) => {
599                    let target = std::str::from_utf8(e.target())?;
600                    let raw = e.content();
601                    let data = if raw.is_empty() {
602                        None
603                    } else {
604                        Some(std::str::from_utf8(raw)?)
605                    };
606
607                    if target == "xml" {
608                        if let Some(content) = data {
609                            if content.contains("encoding")
610                                && !content.to_lowercase().contains("utf-8")
611                            {
612                                show_warning(
613                                    "Non‑UTF‑8 encoding",
614                                    Some(&format!("Found in declaration: {}", content)),
615                                );
616                            }
617                        }
618                    }
619
620                    serializer.processing_instruction(target, data)?;
621                }
622                Event::Decl(decl) => {
623                    if let Some(enc_result) = decl.encoding() {
624                        let enc_bytes = enc_result?;
625                        let enc = std::str::from_utf8(enc_bytes.as_ref())?;
626                        if !enc.to_lowercase().contains("utf-8") {
627                            show_warning(
628                                "Non-UTF-8 encoding",
629                                Some(&format!("Found encoding: {}", enc)),
630                            );
631                        }
632                    }
633                }
634                Event::DocType(e) => {
635                    let text = std::str::from_utf8(&e)?;
636                    serializer.docdecl(text)?;
637                }
638                Event::GeneralRef(e) => {
639                    let text = std::str::from_utf8(&e)?;
640                    serializer.entity_ref(text)?;
641                }
642                Event::Eof => break,
643            }
644            buf.clear();
645        }
646
647        serializer.end_document()?;
648        Ok(())
649    }
650
651    fn write_attribute<W: Write>(
652        serializer: &mut BinaryXmlSerializer<W>,
653        name: &str,
654        value: &str,
655    ) -> Result<(), ConversionError> {
656        use type_detection::*;
657
658        if is_boolean(value) {
659            serializer.attribute_boolean(name, value == "true")?;
660        } else if is_hex_number(value) {
661            match Self::parse_hex_number(value) {
662                Ok((int_val, is_long)) => {
663                    if is_long {
664                        serializer.attribute_long_hex(name, int_val)?;
665                    } else {
666                        serializer.attribute_int_hex(name, int_val as i32)?;
667                    }
668                }
669                Err(_) => {
670                    serializer.attribute(name, value)?;
671                }
672            }
673        } else if is_numeric(value) {
674            match value.parse::<i32>() {
675                Ok(int_val) => {
676                    serializer.attribute_int(name, int_val)?;
677                }
678                Err(_) => match value.parse::<i64>() {
679                    Ok(long_val) => {
680                        serializer.attribute_long(name, long_val)?;
681                    }
682                    Err(_) => {
683                        serializer.attribute(name, value)?;
684                    }
685                },
686            }
687        } else if is_double(value) {
688            match value.parse::<f64>() {
689                Ok(double_val) => {
690                    serializer.attribute_double(name, double_val)?;
691                }
692                Err(_) => {
693                    serializer.attribute(name, value)?;
694                }
695            }
696        } else if is_float(value) {
697            match value.parse::<f32>() {
698                Ok(float_val) => {
699                    serializer.attribute_float(name, float_val)?;
700                }
701                Err(_) => {
702                    serializer.attribute(name, value)?;
703                }
704            }
705        } else if is_base64(value) && value.len() > 8 {
706            match base64::engine::general_purpose::STANDARD.decode(value) {
707                Ok(decoded) => {
708                    serializer.attribute_bytes_base64(name, &decoded)?;
709                }
710                Err(_) => {
711                    serializer.attribute(name, value)?;
712                }
713            }
714        } else if is_hex_string(value) && value.len() > 2 {
715            match hex::decode(value) {
716                Ok(decoded) => {
717                    serializer.attribute_bytes_hex(name, &decoded)?;
718                }
719                Err(_) => {
720                    serializer.attribute(name, value)?;
721                }
722            }
723        } else {
724            if value.len() < 50
725                && (!value.contains(' ')
726                    || value == "true"
727                    || value == "false"
728                    || value.contains('.'))
729            {
730                serializer.attribute_interned(name, value)?;
731            } else {
732                serializer.attribute(name, value)?;
733            }
734        }
735        Ok(())
736    }
737
738    fn parse_hex_number(s: &str) -> Result<(i64, bool), ConversionError> {
739        let hex_part = &s[2..]; // Skip "0x"
740        if hex_part.len() <= 8 {
741            i64::from_str_radix(hex_part, 16)
742                .map(|val| (val, false))
743                .map_err(|_| ConversionError::InvalidHex)
744        } else {
745            i64::from_str_radix(hex_part, 16)
746                .map(|val| (val, true))
747                .map_err(|_| ConversionError::InvalidHex)
748        }
749    }
750}