Skip to main content

zparse/
convert.rs

1//! Format conversion utilities
2
3use crate::error::{Error, ErrorKind, Result, Span};
4use crate::json::{Config as JsonConfig, Parser as JsonParser};
5use crate::toml::Parser as TomlParser;
6use crate::value::{Object, TomlDatetime, Value};
7use crate::xml::model::{Content as XmlContent, Document as XmlDocument, Element as XmlElement};
8use crate::xml::parser::Parser as XmlParser;
9use crate::yaml::Parser as YamlParser;
10use indexmap::IndexMap;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum Format {
14    Json,
15    Toml,
16    Yaml,
17    Xml,
18}
19
20/// Conversion options per format
21#[derive(Clone, Debug, Default)]
22pub struct ConvertOptions {
23    pub json: JsonConfig,
24}
25
26/// Convert between supported formats
27pub fn convert(input: &str, from: Format, to: Format) -> Result<String> {
28    convert_with_options(input, from, to, &ConvertOptions::default())
29}
30
31/// Convert between supported formats with options
32pub fn convert_with_options(
33    input: &str,
34    from: Format,
35    to: Format,
36    options: &ConvertOptions,
37) -> Result<String> {
38    if from == to {
39        return Ok(input.to_string());
40    }
41
42    match (from, to) {
43        (Format::Xml, _) => {
44            let mut parser = XmlParser::new(input.as_bytes());
45            let doc = parser.parse()?;
46            let value = xml_to_value(&doc);
47            serialize_value(&value, to)
48        }
49        (_, Format::Xml) => {
50            let value = parse_value(input, from, options)?;
51            let doc = value_to_xml(&value);
52            Ok(serialize_xml(&doc))
53        }
54        _ => {
55            let value = parse_value(input, from, options)?;
56            serialize_value(&value, to)
57        }
58    }
59}
60
61fn parse_value(input: &str, format: Format, options: &ConvertOptions) -> Result<Value> {
62    match format {
63        Format::Json => {
64            let mut parser = JsonParser::with_config(input.as_bytes(), options.json);
65            parser.parse_value()
66        }
67        Format::Toml => {
68            let mut parser = TomlParser::new(input.as_bytes());
69            parser.parse()
70        }
71        Format::Yaml => {
72            let mut parser = YamlParser::new(input.as_bytes());
73            parser.parse()
74        }
75        Format::Xml => Err(Error::with_message(
76            ErrorKind::InvalidToken,
77            Span::empty(),
78            "xml requires xml parser".to_string(),
79        )),
80    }
81}
82
83fn serialize_value(value: &Value, format: Format) -> Result<String> {
84    match format {
85        Format::Json => Ok(serialize_json(value)),
86        Format::Toml => serialize_toml(value),
87        Format::Yaml => Ok(serialize_yaml(value, 0)),
88        Format::Xml => Err(Error::with_message(
89            ErrorKind::InvalidToken,
90            Span::empty(),
91            "xml requires xml serializer".to_string(),
92        )),
93    }
94}
95
96fn serialize_json(value: &Value) -> String {
97    match value {
98        Value::Null => "null".to_string(),
99        Value::Bool(b) => b.to_string(),
100        Value::Number(n) => {
101            if n.is_finite() {
102                n.to_string()
103            } else {
104                "null".to_string()
105            }
106        }
107        Value::String(s) => format!("\"{}\"", escape_json(s)),
108        Value::Array(arr) => {
109            let items: Vec<String> = arr.iter().map(serialize_json).collect();
110            format!("[{}]", items.join(","))
111        }
112        Value::Object(obj) => {
113            let pairs: Vec<String> = obj
114                .iter()
115                .map(|(k, v)| format!("\"{}\":{}", escape_json(k), serialize_json(v)))
116                .collect();
117            format!("{{{}}}", pairs.join(","))
118        }
119        Value::Datetime(dt) => format!("\"{}\"", format_datetime(dt)),
120    }
121}
122
123fn escape_json(input: &str) -> String {
124    input
125        .chars()
126        .flat_map(|ch| match ch {
127            '\\' => "\\\\".chars().collect::<Vec<_>>(),
128            '"' => "\\\"".chars().collect::<Vec<_>>(),
129            '\n' => "\\n".chars().collect::<Vec<_>>(),
130            '\r' => "\\r".chars().collect::<Vec<_>>(),
131            '\t' => "\\t".chars().collect::<Vec<_>>(),
132            _ => vec![ch],
133        })
134        .collect()
135}
136
137fn serialize_toml(value: &Value) -> Result<String> {
138    match value {
139        Value::Object(obj) => Ok(serialize_toml_object(obj)),
140        _ => Err(Error::with_message(
141            ErrorKind::InvalidToken,
142            Span::empty(),
143            "toml root must be object".to_string(),
144        )),
145    }
146}
147
148fn serialize_toml_object(obj: &Object) -> String {
149    let mut lines = Vec::new();
150    for (key, value) in obj.iter() {
151        lines.push(format!("{key} = {}", serialize_toml_value(value)));
152    }
153    lines.join("\n")
154}
155
156fn serialize_toml_value(value: &Value) -> String {
157    match value {
158        Value::Null => "null".to_string(),
159        Value::Bool(b) => b.to_string(),
160        Value::Number(n) => {
161            if n.is_finite() {
162                n.to_string()
163            } else {
164                "nan".to_string()
165            }
166        }
167        Value::String(s) => format!("\"{}\"", escape_toml(s)),
168        Value::Array(arr) => {
169            let items: Vec<String> = arr.iter().map(serialize_toml_value).collect();
170            format!("[{}]", items.join(", "))
171        }
172        Value::Object(obj) => {
173            let entries: Vec<String> = obj
174                .iter()
175                .map(|(k, v)| format!("{k} = {}", serialize_toml_value(v)))
176                .collect();
177            format!("{{{}}}", entries.join(", "))
178        }
179        Value::Datetime(dt) => format_datetime(dt),
180    }
181}
182
183fn escape_toml(input: &str) -> String {
184    input
185        .chars()
186        .flat_map(|ch| match ch {
187            '\\' => "\\\\".chars().collect::<Vec<_>>(),
188            '"' => "\\\"".chars().collect::<Vec<_>>(),
189            '\n' => "\\n".chars().collect::<Vec<_>>(),
190            '\r' => "\\r".chars().collect::<Vec<_>>(),
191            '\t' => "\\t".chars().collect::<Vec<_>>(),
192            _ => vec![ch],
193        })
194        .collect()
195}
196
197fn serialize_yaml(value: &Value, indent: usize) -> String {
198    let pad = " ".repeat(indent);
199    match value {
200        Value::Null => format!("{pad}null"),
201        Value::Bool(b) => format!("{pad}{b}"),
202        Value::Number(n) => format!("{pad}{n}"),
203        Value::String(s) => format!("{pad}\"{}\"", escape_yaml(s)),
204        Value::Datetime(dt) => format!("{pad}{}", format_datetime(dt)),
205        Value::Array(arr) => arr
206            .iter()
207            .map(|v| {
208                let item = serialize_yaml(v, indent + 2);
209                format!("{pad}- {}", item.trim_start())
210            })
211            .collect::<Vec<_>>()
212            .join("\n"),
213        Value::Object(obj) => obj
214            .iter()
215            .map(|(k, v)| {
216                let value = serialize_yaml(v, indent + 2);
217                if matches!(v, Value::Array(_) | Value::Object(_)) {
218                    format!("{pad}{k}:\n{value}")
219                } else {
220                    format!("{pad}{k}: {}", value.trim_start())
221                }
222            })
223            .collect::<Vec<_>>()
224            .join("\n"),
225    }
226}
227
228fn escape_yaml(input: &str) -> String {
229    input
230        .chars()
231        .flat_map(|ch| match ch {
232            '\\' => "\\\\".chars().collect::<Vec<_>>(),
233            '"' => "\\\"".chars().collect::<Vec<_>>(),
234            '\n' => "\\n".chars().collect::<Vec<_>>(),
235            '\r' => "\\r".chars().collect::<Vec<_>>(),
236            '\t' => "\\t".chars().collect::<Vec<_>>(),
237            _ => vec![ch],
238        })
239        .collect()
240}
241
242fn format_datetime(dt: &TomlDatetime) -> String {
243    use time::format_description::well_known::Rfc3339;
244    use time::macros::format_description;
245    match dt {
246        TomlDatetime::OffsetDateTime(value) => value
247            .format(&Rfc3339)
248            .unwrap_or_else(|_| "1979-05-27T07:32:00Z".to_string()),
249        TomlDatetime::LocalDateTime(value) => value
250            .format(&format_description!(
251                "[year]-[month]-[day]T[hour]:[minute]:[second]"
252            ))
253            .unwrap_or_else(|_| "1979-05-27T07:32:00".to_string()),
254        TomlDatetime::LocalDate(value) => value
255            .format(&format_description!("[year]-[month]-[day]"))
256            .unwrap_or_else(|_| "1979-05-27".to_string()),
257        TomlDatetime::LocalTime(value) => value
258            .format(&format_description!("[hour]:[minute]:[second]"))
259            .unwrap_or_else(|_| "07:32:00".to_string()),
260    }
261}
262
263fn xml_to_value(doc: &XmlDocument) -> Value {
264    let mut root = Object::new();
265    root.insert(&doc.root.name, element_to_value(&doc.root));
266    Value::Object(root)
267}
268
269fn element_to_value(element: &XmlElement) -> Value {
270    let mut obj = Object::new();
271
272    if !element.attributes.is_empty() {
273        let mut attrs = Object::new();
274        for (key, value) in element.attributes.iter() {
275            attrs.insert(key, value.clone());
276        }
277        obj.insert("@attributes", Value::Object(attrs));
278    }
279
280    let mut text = String::new();
281    for child in &element.children {
282        if let XmlContent::Text(value) = child {
283            text.push_str(value);
284        }
285    }
286    if !text.trim().is_empty() {
287        obj.insert("#text", Value::String(text));
288    }
289
290    for child in &element.children {
291        if let XmlContent::Element(child) = child {
292            let value = element_to_value(child);
293            match obj.get(&child.name) {
294                Some(Value::Array(arr)) => {
295                    let mut items = arr.clone();
296                    items.push(value);
297                    obj.insert(&child.name, Value::Array(items));
298                }
299                Some(existing) => {
300                    let items = vec![existing.clone(), value];
301                    obj.insert(&child.name, Value::Array(items.into()));
302                }
303                None => {
304                    obj.insert(&child.name, value);
305                }
306            }
307        }
308    }
309
310    if obj.is_empty() {
311        Value::Object(Object::new())
312    } else {
313        Value::Object(obj)
314    }
315}
316
317fn value_to_xml(value: &Value) -> XmlDocument {
318    let root = XmlElement {
319        name: "root".to_string(),
320        attributes: IndexMap::new(),
321        children: value_to_children(value),
322    };
323    XmlDocument { root }
324}
325
326fn value_to_children(value: &Value) -> Vec<XmlContent> {
327    match value {
328        Value::Object(obj) => obj
329            .iter()
330            .flat_map(|(key, value)| value_to_elements(key, value))
331            .map(XmlContent::Element)
332            .collect(),
333        Value::Array(arr) => arr.iter().flat_map(value_to_children).collect(),
334        Value::String(text) => vec![XmlContent::Text(text.clone())],
335        Value::Number(n) => vec![XmlContent::Text(n.to_string())],
336        Value::Bool(b) => vec![XmlContent::Text(b.to_string())],
337        Value::Null => Vec::new(),
338        Value::Datetime(dt) => vec![XmlContent::Text(format_datetime(dt))],
339    }
340}
341
342fn value_to_elements(name: &str, value: &Value) -> Vec<XmlElement> {
343    match value {
344        Value::Array(arr) => arr
345            .iter()
346            .flat_map(|value| value_to_elements(name, value))
347            .collect(),
348        Value::Object(obj) => {
349            let mut attributes = IndexMap::new();
350            let mut children = Vec::new();
351
352            if let Some(Value::Object(attrs)) = obj.get("@attributes") {
353                for (key, value) in attrs.iter() {
354                    if let Value::String(text) = value {
355                        attributes.insert(key.clone(), text.clone());
356                    } else {
357                        attributes.insert(key.clone(), serialize_json(value));
358                    }
359                }
360            }
361
362            if let Some(Value::String(text)) = obj.get("#text") {
363                children.push(XmlContent::Text(text.clone()));
364            }
365
366            for (key, value) in obj.iter() {
367                if key == "@attributes" || key == "#text" {
368                    continue;
369                }
370                for element in value_to_elements(key, value) {
371                    children.push(XmlContent::Element(element));
372                }
373            }
374
375            vec![XmlElement {
376                name: name.to_string(),
377                attributes,
378                children,
379            }]
380        }
381        _ => vec![XmlElement {
382            name: name.to_string(),
383            attributes: IndexMap::new(),
384            children: value_to_children(value),
385        }],
386    }
387}
388
389fn serialize_xml(doc: &XmlDocument) -> String {
390    let mut output = String::new();
391    serialize_element(&doc.root, &mut output);
392    output
393}
394
395fn serialize_element(element: &XmlElement, output: &mut String) {
396    output.push('<');
397    output.push_str(&element.name);
398
399    for (key, value) in element.attributes.iter() {
400        output.push(' ');
401        output.push_str(key);
402        output.push_str("=\"");
403        output.push_str(&escape_xml(value));
404        output.push('"');
405    }
406
407    if element.children.is_empty() {
408        output.push_str("/>");
409        return;
410    }
411
412    output.push('>');
413    for child in &element.children {
414        match child {
415            XmlContent::Element(child) => serialize_element(child, output),
416            XmlContent::Text(text) => output.push_str(&escape_xml(text)),
417        }
418    }
419    output.push_str("</");
420    output.push_str(&element.name);
421    output.push('>');
422}
423
424fn escape_xml(input: &str) -> String {
425    input
426        .replace('&', "&amp;")
427        .replace('<', "&lt;")
428        .replace('>', "&gt;")
429        .replace('"', "&quot;")
430        .replace('\'', "&apos;")
431}