toonify_core/
input.rs

1use std::io::Read;
2
3use csv::ReaderBuilder;
4use serde_json::{Map, Value};
5use xmltree::{Element, XMLNode};
6
7use crate::error::ToonifyError;
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum SourceFormat {
11    Json,
12    Yaml,
13    Xml,
14    Csv,
15}
16
17pub fn load_from_reader<R: Read>(
18    mut reader: R,
19    format: SourceFormat,
20) -> Result<Value, ToonifyError> {
21    let mut buf = String::new();
22    reader.read_to_string(&mut buf)?;
23    load_from_str(&buf, format)
24}
25
26pub fn load_from_str(input: &str, format: SourceFormat) -> Result<Value, ToonifyError> {
27    match format {
28        SourceFormat::Json => serde_json::from_str(input)
29            .map_err(|err| ToonifyError::parse_err(SourceFormat::Json, err)),
30        SourceFormat::Yaml => serde_yaml::from_str(input)
31            .map_err(|err| ToonifyError::parse_err(SourceFormat::Yaml, err)),
32        SourceFormat::Xml => parse_xml(input),
33        SourceFormat::Csv => parse_csv(input),
34    }
35}
36
37fn parse_csv(input: &str) -> Result<Value, ToonifyError> {
38    let mut reader = ReaderBuilder::new()
39        .has_headers(true)
40        .trim(csv::Trim::Fields)
41        .from_reader(input.as_bytes());
42
43    let headers = reader
44        .headers()
45        .map_err(|err| ToonifyError::parse_err(SourceFormat::Csv, err))?
46        .clone();
47
48    let mut rows = Vec::new();
49    for record in reader.records() {
50        let record = record.map_err(|err| ToonifyError::parse_err(SourceFormat::Csv, err))?;
51        let mut row = Map::with_capacity(headers.len());
52        for (idx, header) in headers.iter().enumerate() {
53            let cell = record.get(idx).unwrap_or_default();
54            row.insert(header.to_string(), parse_csv_cell(cell));
55        }
56        rows.push(Value::Object(row));
57    }
58
59    Ok(Value::Array(rows))
60}
61
62fn parse_csv_cell(cell: &str) -> Value {
63    if cell.is_empty() {
64        return Value::String(String::new());
65    }
66
67    if let Ok(Value::Bool(boolean)) = serde_json::from_str(cell) {
68        return Value::Bool(boolean);
69    }
70
71    if let Ok(Value::Number(number)) = serde_json::from_str(cell) {
72        return Value::Number(number);
73    }
74
75    if let Ok(Value::Null) = serde_json::from_str(cell) {
76        return Value::Null;
77    }
78
79    Value::String(cell.to_string())
80}
81
82fn parse_xml(input: &str) -> Result<Value, ToonifyError> {
83    let root = Element::parse(input.as_bytes())
84        .map_err(|err| ToonifyError::parse_err(SourceFormat::Xml, err))?;
85
86    let root_value = Value::Object({
87        let mut map = Map::new();
88        map.insert(root.name.clone(), element_to_value(&root));
89        map
90    });
91
92    Ok(root_value)
93}
94
95fn element_to_value(element: &Element) -> Value {
96    let mut object = Map::new();
97
98    for (attr, value) in &element.attributes {
99        object.insert(format!("@{}", attr), Value::String(value.clone()));
100    }
101
102    let mut child_groups: indexmap::IndexMap<String, Vec<Value>> = indexmap::IndexMap::new();
103    let mut text_content = Vec::new();
104
105    for child in &element.children {
106        match child {
107            XMLNode::Element(child_el) => {
108                child_groups
109                    .entry(child_el.name.clone())
110                    .or_default()
111                    .push(element_to_value(child_el));
112            }
113            XMLNode::Text(text) | XMLNode::CData(text) => {
114                let trimmed = text.trim();
115                if !trimmed.is_empty() {
116                    text_content.push(trimmed.to_string());
117                }
118            }
119            _ => {}
120        }
121    }
122
123    let combined_text = text_content.join(" ");
124    if child_groups.is_empty() && object.is_empty() {
125        if combined_text.is_empty() {
126            Value::Null
127        } else {
128            Value::String(combined_text)
129        }
130    } else {
131        if !combined_text.is_empty() {
132            object.insert("_text".into(), Value::String(combined_text));
133        }
134
135        for (name, values) in child_groups {
136            if values.len() == 1 {
137                object.insert(name, values.into_iter().next().unwrap());
138            } else {
139                object.insert(name, Value::Array(values));
140            }
141        }
142        Value::Object(object)
143    }
144}