Skip to main content

xml_disassembler/builders/
build_xml_string.rs

1//! Build XML string from XmlElement structure.
2
3use quick_xml::events::{BytesCData, BytesDecl, BytesEnd, BytesStart, BytesText, Event};
4use quick_xml::Writer;
5use serde_json::{Map, Value};
6
7use crate::types::XmlElement;
8
9fn value_to_string(v: &Value) -> String {
10    match v {
11        Value::String(s) => s.clone(),
12        Value::Number(n) => n.to_string(),
13        Value::Bool(b) => b.to_string(),
14        Value::Null => String::new(),
15        _ => serde_json::to_string(v).unwrap_or_default(),
16    }
17}
18
19fn write_element<W: std::io::Write>(
20    writer: &mut Writer<W>,
21    name: &str,
22    content: &Value,
23    indent_level: usize,
24) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
25    let indent = "    ".repeat(indent_level);
26    let child_indent = "    ".repeat(indent_level + 1);
27
28    match content {
29        Value::Object(obj) => {
30            let (attrs, children): (Vec<_>, Vec<_>) =
31                obj.iter().partition(|(k, _)| k.starts_with('@'));
32
33            let attr_name = |k: &str| k.trim_start_matches('@').to_string();
34
35            let mut text_content = String::new();
36            let mut cdata_content = String::new();
37            let child_elements: Vec<(&String, &Value)> = children
38                .iter()
39                .filter_map(|(k, v)| {
40                    if *k == "#text" {
41                        text_content = value_to_string(v);
42                        None
43                    } else if *k == "#cdata" {
44                        cdata_content = value_to_string(v);
45                        None
46                    } else {
47                        Some((*k, *v))
48                    }
49                })
50                .collect();
51
52            let has_children = child_elements.iter().any(|(_, v)| {
53                v.is_object()
54                    || (v.is_array() && v.as_array().map(|a| !a.is_empty()).unwrap_or(false))
55            });
56
57            let attrs: Vec<(String, String)> = attrs
58                .iter()
59                .map(|(k, v)| (attr_name(k), value_to_string(v)))
60                .collect();
61
62            let mut start = BytesStart::new(name);
63            for (k, v) in &attrs {
64                start.push_attribute((k.as_str(), v.as_str()));
65            }
66            writer.write_event(Event::Start(start))?;
67
68            if has_children || !child_elements.is_empty() {
69                writer.write_event(Event::Text(BytesText::new(
70                    format!("\n{}", child_indent).as_str(),
71                )))?;
72
73                let child_count = child_elements.len();
74                for (idx, (child_name, child_value)) in child_elements.iter().enumerate() {
75                    let is_last = idx == child_count - 1;
76                    match child_value {
77                        Value::Array(arr) => {
78                            let arr_len = arr.len();
79                            for (i, item) in arr.iter().enumerate() {
80                                let arr_last = i == arr_len - 1;
81                                write_element(writer, child_name, item, indent_level + 1)?;
82                                if !arr_last {
83                                    writer.write_event(Event::Text(BytesText::new(
84                                        format!("\n{}", child_indent).as_str(),
85                                    )))?;
86                                }
87                            }
88                            if !is_last {
89                                writer.write_event(Event::Text(BytesText::new(
90                                    format!("\n{}", child_indent).as_str(),
91                                )))?;
92                            }
93                        }
94                        Value::Object(_) => {
95                            write_element(writer, child_name, child_value, indent_level + 1)?;
96                            if !is_last {
97                                writer.write_event(Event::Text(BytesText::new(
98                                    format!("\n{}", child_indent).as_str(),
99                                )))?;
100                            }
101                        }
102                        _ => {
103                            writer
104                                .write_event(Event::Start(BytesStart::new(child_name.as_str())))?;
105                            // BytesText::new() expects unescaped content; the writer escapes when writing
106                            writer.write_event(Event::Text(BytesText::new(
107                                value_to_string(child_value).as_str(),
108                            )))?;
109                            writer.write_event(Event::End(BytesEnd::new(child_name.as_str())))?;
110                            if !is_last {
111                                writer.write_event(Event::Text(BytesText::new(
112                                    format!("\n{}", child_indent).as_str(),
113                                )))?;
114                            }
115                        }
116                    }
117                }
118
119                writer.write_event(Event::Text(BytesText::new(
120                    format!("\n{}", indent).as_str(),
121                )))?;
122            } else if !cdata_content.is_empty() || !text_content.is_empty() {
123                // Output #text first (e.g. whitespace before CDATA), then #cdata
124                if !text_content.is_empty() {
125                    writer.write_event(Event::Text(BytesText::new(text_content.as_str())))?;
126                }
127                if !cdata_content.is_empty() {
128                    writer.write_event(Event::CData(BytesCData::new(cdata_content.as_str())))?;
129                }
130            }
131
132            writer.write_event(Event::End(BytesEnd::new(name)))?;
133        }
134        Value::Array(arr) => {
135            for item in arr {
136                write_element(writer, name, item, indent_level)?;
137            }
138        }
139        _ => {
140            writer.write_event(Event::Start(BytesStart::new(name)))?;
141            // BytesText::new() expects unescaped content; the writer escapes when writing
142            writer.write_event(Event::Text(BytesText::new(
143                value_to_string(content).as_str(),
144            )))?;
145            writer.write_event(Event::End(BytesEnd::new(name)))?;
146        }
147    }
148
149    Ok(())
150}
151
152fn build_xml_from_object(
153    element: &Map<String, Value>,
154) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
155    let mut writer = Writer::new_with_indent(Vec::new(), b' ', 4);
156
157    let (declaration, root_key, root_value) = if let Some(decl) = element.get("?xml") {
158        let root_key = element
159            .keys()
160            .find(|k| *k != "?xml")
161            .cloned()
162            .unwrap_or_else(|| "root".to_string());
163        let root_value = element
164            .get(&root_key)
165            .cloned()
166            .unwrap_or_else(|| Value::Object(Map::new()));
167        (Some(decl), root_key, root_value)
168    } else {
169        let root_key = element
170            .keys()
171            .next()
172            .cloned()
173            .unwrap_or_else(|| "root".to_string());
174        let root_value = element
175            .get(&root_key)
176            .cloned()
177            .unwrap_or_else(|| Value::Object(Map::new()));
178        (None, root_key, root_value)
179    };
180
181    if declaration.is_some() {
182        if let Some(decl) = declaration {
183            if let Some(obj) = decl.as_object() {
184                let version = obj
185                    .get("@version")
186                    .and_then(|v| v.as_str())
187                    .unwrap_or("1.0");
188                let encoding = obj.get("@encoding").and_then(|v| v.as_str());
189                let standalone = obj.get("@standalone").and_then(|v| v.as_str());
190                writer.write_event(Event::Decl(BytesDecl::new(version, encoding, standalone)))?;
191            }
192        }
193    }
194
195    write_element(&mut writer, &root_key, &root_value, 0)?;
196
197    let result = String::from_utf8(writer.into_inner())?;
198    Ok(result.trim_end().to_string())
199}
200
201/// Build XML string from XmlElement.
202pub fn build_xml_string(element: &XmlElement) -> String {
203    match element {
204        Value::Object(obj) => build_xml_from_object(obj).unwrap_or_default(),
205        _ => String::new(),
206    }
207}