lib3mf_core/parser/
xml_parser.rs

1use crate::error::{Lib3mfError, Result};
2use lexical_core;
3use quick_xml::events::{BytesStart, Event};
4use quick_xml::reader::Reader;
5use std::borrow::Cow;
6use std::io::BufRead;
7
8pub struct XmlParser<R: BufRead> {
9    pub reader: Reader<R>,
10    pub buf: Vec<u8>,
11}
12
13impl<R: BufRead> XmlParser<R> {
14    pub fn new(reader: R) -> Self {
15        let mut reader = Reader::from_reader(reader);
16        reader.config_mut().trim_text(true);
17        reader.config_mut().expand_empty_elements = true;
18        Self {
19            reader,
20            buf: Vec::new(),
21        }
22    }
23
24    pub fn read_next_event(&mut self) -> Result<Event<'_>> {
25        self.buf.clear();
26        self.reader
27            .read_event_into(&mut self.buf)
28            .map_err(|e| Lib3mfError::Validation(e.to_string()))
29    }
30
31    pub fn read_text_content(&mut self) -> Result<String> {
32        let mut text = String::new();
33        let mut depth = 0;
34
35        loop {
36            match self.read_next_event()? {
37                Event::Text(e) => text.push_str(&String::from_utf8_lossy(e.as_ref())),
38                Event::CData(e) => text.push_str(&String::from_utf8_lossy(e.into_inner().as_ref())),
39                Event::Start(_) => depth += 1,
40                Event::End(_) => {
41                    if depth > 0 {
42                        depth -= 1;
43                    } else {
44                        return Ok(text);
45                    }
46                }
47                Event::Eof => {
48                    return Err(Lib3mfError::Validation(
49                        "Unexpected EOF in text content".to_string(),
50                    ));
51                }
52                _ => {}
53            }
54        }
55    }
56
57    pub fn read_to_end(&mut self, end: &[u8]) -> Result<()> {
58        // read_to_end_into expects QName
59        self.reader
60            .read_to_end_into(quick_xml::name::QName(end), &mut self.buf)
61            .map_err(|e| Lib3mfError::Validation(e.to_string()))?;
62        Ok(())
63    }
64}
65
66// Helper functions for attribute parsing
67pub fn get_attribute<'a>(e: &'a BytesStart, name: &[u8]) -> Option<Cow<'a, str>> {
68    e.try_get_attribute(name).ok().flatten().map(|a| {
69        a.unescape_value()
70            .unwrap_or_else(|_| String::from_utf8_lossy(&a.value).into_owned().into())
71    })
72}
73
74pub fn get_attribute_f32(e: &BytesStart, name: &[u8]) -> Result<f32> {
75    let attr = e.try_get_attribute(name).ok().flatten().ok_or_else(|| {
76        Lib3mfError::Validation(format!(
77            "Missing attribute: {}",
78            String::from_utf8_lossy(name)
79        ))
80    })?;
81    lexical_core::parse::<f32>(attr.value.as_ref()).map_err(|_| {
82        Lib3mfError::Validation(format!(
83            "Invalid float for attribute {}: {}",
84            String::from_utf8_lossy(name),
85            String::from_utf8_lossy(&attr.value)
86        ))
87    })
88}
89
90pub fn get_attribute_u32(e: &BytesStart, name: &[u8]) -> Result<u32> {
91    let attr = e.try_get_attribute(name).ok().flatten().ok_or_else(|| {
92        Lib3mfError::Validation(format!(
93            "Missing attribute: {}",
94            String::from_utf8_lossy(name)
95        ))
96    })?;
97    lexical_core::parse::<u32>(attr.value.as_ref()).map_err(|_| {
98        Lib3mfError::Validation(format!(
99            "Invalid integer for attribute {}: {}",
100            String::from_utf8_lossy(name),
101            String::from_utf8_lossy(&attr.value)
102        ))
103    })
104}
105
106pub fn get_attribute_u32_opt(e: &BytesStart, name: &[u8]) -> Result<Option<u32>> {
107    match e.try_get_attribute(name).ok().flatten() {
108        Some(attr) => lexical_core::parse::<u32>(attr.value.as_ref())
109            .map(Some)
110            .map_err(|_| {
111                Lib3mfError::Validation(format!(
112                    "Invalid integer: {}",
113                    String::from_utf8_lossy(&attr.value)
114                ))
115            }),
116        None => Ok(None),
117    }
118}
119
120pub fn get_attribute_uuid(e: &BytesStart) -> Result<Option<uuid::Uuid>> {
121    // Try "uuid" then "p:uuid"
122    let val = get_attribute(e, b"uuid").or_else(|| get_attribute(e, b"p:uuid"));
123
124    match val {
125        Some(s) => uuid::Uuid::parse_str(&s)
126            .map(Some)
127            .map_err(|_| Lib3mfError::Validation(format!("Invalid UUID: {}", s))),
128        None => Ok(None),
129    }
130}