1use std::error::Error;
2use std::io::Cursor;
3
4#[derive(Debug, Clone, PartialEq)]
25pub enum CanonicalValue {
26 Null,
27 Bool(bool),
28 Number(serde_json::Number),
29 String(String),
30 Array(Vec<CanonicalValue>),
31 Object(Vec<(String, CanonicalValue)>),
32}
33
34type XmlStackFrame = (
35 String,
36 Vec<(String, String)>,
37 Vec<(String, CanonicalValue)>,
38 String,
39);
40
41impl CanonicalValue {
42 fn from_json_value(v: serde_json::Value) -> Self {
43 match v {
44 serde_json::Value::Null => Self::Null,
45 serde_json::Value::Bool(b) => Self::Bool(b),
46 serde_json::Value::Number(n) => Self::Number(n),
47 serde_json::Value::String(s) => Self::String(s),
48 serde_json::Value::Array(a) => {
49 Self::Array(a.into_iter().map(Self::from_json_value).collect())
50 }
51 serde_json::Value::Object(m) => Self::Object(
52 m.into_iter()
53 .map(|(k, v)| (k, Self::from_json_value(v)))
54 .collect(),
55 ),
56 }
57 }
58
59 fn to_json_value(&self) -> serde_json::Value {
60 match self {
61 Self::Null => serde_json::Value::Null,
62 Self::Bool(b) => serde_json::Value::Bool(*b),
63 Self::Number(n) => serde_json::Value::Number(n.clone()),
64 Self::String(s) => serde_json::Value::String(s.clone()),
65 Self::Array(a) => {
66 serde_json::Value::Array(a.iter().map(|v| v.to_json_value()).collect())
67 }
68 Self::Object(o) => {
69 let mut map = serde_json::Map::new();
70 for (k, v) in o {
71 map.insert(k.clone(), v.to_json_value());
72 }
73 serde_json::Value::Object(map)
74 }
75 }
76 }
77
78 pub fn from_json(input: &str) -> Result<Self, Box<dyn Error>> {
89 Ok(Self::from_json_value(serde_json::from_str(input)?))
90 }
91
92 pub fn to_json(&self) -> Result<String, Box<dyn Error>> {
101 Ok(serde_json::to_string(&self.to_json_value())?)
102 }
103
104 fn from_delimited(input: &str, delim: u8) -> Result<Self, Box<dyn Error>> {
105 let input = input.strip_prefix('\u{feff}').unwrap_or(input);
106 let mut rdr = csv::ReaderBuilder::new()
107 .delimiter(delim)
108 .from_reader(Cursor::new(input));
109 let headers = rdr.headers()?.clone();
110 let mut seen = std::collections::HashSet::new();
111 for h in headers.iter() {
112 if !seen.insert(h) {
113 return Err(format!("duplicate header: {h}").into());
114 }
115 }
116 let mut rows = Vec::new();
117 for result in rdr.records() {
118 let record = result?;
119 let mut obj = Vec::with_capacity(headers.len());
120 for (h, v) in headers.iter().zip(record.iter()) {
121 obj.push((h.to_string(), Self::String(v.to_string())));
122 }
123 rows.push(Self::Object(obj));
124 }
125 Ok(Self::Array(rows))
126 }
127
128 fn to_delimited(&self, delim: u8) -> Result<String, Box<dyn Error>> {
129 let rows = self.as_tabular()?;
130 if rows.is_empty() {
131 return Ok(String::new());
132 }
133 let mut headers: Vec<String> = Vec::new();
134 for row in &rows {
135 let obj = row.as_object()?;
136 for (k, _) in obj {
137 if !headers.contains(k) {
138 headers.push(k.clone());
139 }
140 }
141 }
142 let mut wtr = csv::WriterBuilder::new()
143 .delimiter(delim)
144 .from_writer(vec![]);
145 wtr.write_record(&headers)?;
146 for row in &rows {
147 let obj = row.as_object()?;
148 let mut record = Vec::with_capacity(headers.len());
149 for h in &headers {
150 let cell = match obj.iter().find(|(k, _)| k == h) {
151 Some((_, Self::Null)) | None => String::new(),
152 Some((_, Self::Bool(b))) => b.to_string(),
153 Some((_, Self::Number(n))) => n.to_string(),
154 Some((_, Self::String(s))) => s.clone(),
155 Some((_, Self::Array(_))) | Some((_, Self::Object(_))) => {
156 return Err(format!(
157 "nested value at key {h} cannot be represented in tabular output"
158 )
159 .into());
160 }
161 };
162 record.push(cell);
163 }
164 wtr.write_record(&record)?;
165 }
166 let data = wtr.into_inner()?;
167 Ok(String::from_utf8(data)?)
168 }
169
170 fn as_object(&self) -> Result<&Vec<(String, CanonicalValue)>, Box<dyn Error>> {
171 match self {
172 Self::Object(o) => Ok(o),
173 _ => Err("expected object".into()),
174 }
175 }
176
177 fn as_tabular(&self) -> Result<Vec<&CanonicalValue>, Box<dyn Error>> {
178 match self {
179 Self::Array(a) => Ok(a.iter().collect()),
180 Self::Object(o) if o.len() == 1 => match &o[0].1 {
181 Self::Array(a) => Ok(a.iter().collect()),
182 Self::Object(inner) if inner.len() == 1 => match &inner[0].1 {
183 Self::Array(a) => Ok(a.iter().collect()),
184 Self::Object(_) => Ok(vec![&inner[0].1]),
185 _ => Err("XML payload is not tabular".into()),
186 },
187 Self::Object(_) => Ok(vec![&o[0].1]),
188 _ => Err("XML payload is not tabular".into()),
189 },
190 _ => Err("root must be an array of objects or a single object".into()),
191 }
192 }
193
194 pub fn from_csv(input: &str) -> Result<Self, Box<dyn Error>> {
203 Self::from_delimited(input, b',')
204 }
205
206 pub fn to_csv(&self) -> Result<String, Box<dyn Error>> {
215 self.to_delimited(b',')
216 }
217
218 pub fn from_tsv(input: &str) -> Result<Self, Box<dyn Error>> {
227 Self::from_delimited(input, b'\t')
228 }
229
230 pub fn to_tsv(&self) -> Result<String, Box<dyn Error>> {
239 self.to_delimited(b'\t')
240 }
241
242 pub fn from_yaml(input: &str) -> Result<Self, Box<dyn Error>> {
251 let opts = serde_saphyr::options! {
252 strict_booleans: true,
253 ignore_binary_tag_for_string: true,
254 };
255 let v: serde_json::Value = serde_saphyr::from_str_with_options(input, opts)?;
256 Ok(Self::from_json_value(v))
257 }
258
259 pub fn to_yaml(&self) -> Result<String, Box<dyn Error>> {
268 Ok(serde_saphyr::to_string(&self.to_json_value())?)
269 }
270
271 pub fn from_xml(input: &str) -> Result<Self, Box<dyn Error>> {
284 use quick_xml::events::Event;
285 use quick_xml::Reader;
286
287 let mut reader = Reader::from_str(input);
288 reader.config_mut().trim_text(true);
289 let mut stack: Vec<XmlStackFrame> = Vec::new();
290 let mut root: Option<(String, CanonicalValue)> = None;
291
292 loop {
293 match reader.read_event() {
294 Ok(Event::Start(e)) => {
295 let name = String::from_utf8_lossy(e.name().as_ref()).to_string();
296 let mut attrs = Vec::new();
297 for a in e.attributes() {
298 let a = a?;
299 attrs.push((
300 String::from_utf8_lossy(a.key.as_ref()).to_string(),
301 a.unescape_value()?.to_string(),
302 ));
303 }
304 stack.push((name, attrs, Vec::new(), String::new()));
305 }
306 Ok(Event::Text(e)) => {
307 if let Some(top) = stack.last_mut() {
308 top.3.push_str(&e.unescape()?);
309 }
310 }
311 Ok(Event::End(_)) => {
312 let (name, attributes, children, text) = stack.pop().ok_or("unbalanced XML")?;
313 let trimmed = text.trim().to_string();
314 let value = if attributes.is_empty() && children.is_empty() {
315 Self::String(trimmed)
316 } else {
317 let mut obj: Vec<(String, CanonicalValue)> = Vec::new();
318 for (k, v) in attributes {
319 obj.push((format!("@{k}"), Self::String(v)));
320 }
321 if !trimmed.is_empty() {
322 obj.push(("#text".to_string(), Self::String(trimmed)));
323 }
324 for (cname, cval) in children {
325 if let Some(slot) = obj.iter_mut().find(|(k, _)| k == &cname) {
326 match &mut slot.1 {
327 Self::Array(a) => a.push(cval),
328 _ => {
329 let old = slot.1.clone();
330 slot.1 = Self::Array(vec![old, cval]);
331 }
332 }
333 } else {
334 obj.push((cname, cval));
335 }
336 }
337 Self::Object(obj)
338 };
339 if let Some(parent) = stack.last_mut() {
340 parent.2.push((name, value));
341 } else {
342 root = Some((name, value));
343 }
344 }
345 Ok(Event::Eof) => break,
346 Ok(_) => {}
347 Err(e) => return Err(e.into()),
348 }
349 }
350
351 let (root_name, root_value) = root.ok_or("empty XML")?;
352 Ok(Self::Object(vec![(root_name, root_value)]))
353 }
354
355 pub fn to_xml(&self) -> Result<String, Box<dyn Error>> {
366 let mut writer = quick_xml::Writer::new_with_indent(Vec::new(), b' ', 2);
367 match self {
368 Self::Object(o) if o.len() == 1 && !o[0].0.starts_with('@') && o[0].0 != "#text" => {
369 write_element(&mut writer, &o[0].0, &o[0].1)?;
370 }
371 Self::Array(a) => {
372 write_open(&mut writer, "root", &[])?;
373 for item in a {
374 write_element(&mut writer, "item", item)?;
375 }
376 write_close(&mut writer, "root")?;
377 }
378 _ => write_element(&mut writer, "root", self)?,
379 }
380 Ok(String::from_utf8(writer.into_inner())?)
381 }
382}
383
384fn write_open<W: std::io::Write>(
385 writer: &mut quick_xml::Writer<W>,
386 name: &str,
387 attrs: &[(String, String)],
388) -> Result<(), Box<dyn Error>> {
389 use quick_xml::events::{BytesStart, Event};
390 let mut start = BytesStart::new(name);
391 for (k, v) in attrs {
392 start.push_attribute((k.as_str(), v.as_str()));
393 }
394 writer.write_event(Event::Start(start))?;
395 Ok(())
396}
397
398fn write_close<W: std::io::Write>(
399 writer: &mut quick_xml::Writer<W>,
400 name: &str,
401) -> Result<(), Box<dyn Error>> {
402 use quick_xml::events::{BytesEnd, Event};
403 writer.write_event(Event::End(BytesEnd::new(name)))?;
404 Ok(())
405}
406
407fn write_element<W: std::io::Write>(
408 writer: &mut quick_xml::Writer<W>,
409 name: &str,
410 value: &CanonicalValue,
411) -> Result<(), Box<dyn Error>> {
412 use quick_xml::events::{BytesText, Event};
413
414 match value {
415 CanonicalValue::Array(items) => {
416 for item in items {
417 write_element(writer, name, item)?;
418 }
419 }
420 CanonicalValue::Object(o) => {
421 let attrs: Vec<(String, String)> = o
422 .iter()
423 .filter_map(|(k, v)| {
424 k.strip_prefix('@').and_then(|attr| match v {
425 CanonicalValue::String(s) => Some((attr.to_string(), s.clone())),
426 _ => None,
427 })
428 })
429 .collect();
430 write_open(writer, name, &attrs)?;
431 for (k, v) in o {
432 if k.starts_with('@') {
433 continue;
434 }
435 if k == "#text" {
436 if let CanonicalValue::String(s) = v {
437 writer.write_event(Event::Text(BytesText::new(s)))?;
438 }
439 continue;
440 }
441 write_element(writer, k, v)?;
442 }
443 write_close(writer, name)?;
444 }
445 CanonicalValue::String(s) => {
446 write_open(writer, name, &[])?;
447 writer.write_event(Event::Text(BytesText::new(s)))?;
448 write_close(writer, name)?;
449 }
450 CanonicalValue::Number(n) => {
451 write_open(writer, name, &[])?;
452 writer.write_event(Event::Text(BytesText::new(&n.to_string())))?;
453 write_close(writer, name)?;
454 }
455 CanonicalValue::Bool(b) => {
456 write_open(writer, name, &[])?;
457 writer.write_event(Event::Text(BytesText::new(&b.to_string())))?;
458 write_close(writer, name)?;
459 }
460 CanonicalValue::Null => {
461 write_open(writer, name, &[])?;
462 write_close(writer, name)?;
463 }
464 }
465 Ok(())
466}