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 pub fn from_toml(input: &str) -> Result<Self, Box<dyn Error>> {
171 let value: toml::Value = toml::from_str(input)?;
172 Ok(Self::from_toml_value(value))
173 }
174
175 fn from_toml_value(v: toml::Value) -> Self {
176 match v {
177 toml::Value::String(s) => Self::String(s),
178 toml::Value::Integer(i) => Self::Number(serde_json::Number::from(i)),
179 toml::Value::Float(f) => match serde_json::Number::from_f64(f) {
180 Some(n) => Self::Number(n),
181 None => Self::String(f.to_string()),
182 },
183 toml::Value::Boolean(b) => Self::Bool(b),
184 toml::Value::Datetime(dt) => Self::String(dt.to_string()),
185 toml::Value::Array(a) => {
186 Self::Array(a.into_iter().map(Self::from_toml_value).collect())
187 }
188 toml::Value::Table(t) => Self::Object(
189 t.into_iter()
190 .map(|(k, v)| (k, Self::from_toml_value(v)))
191 .collect(),
192 ),
193 }
194 }
195
196 pub fn to_toml(&self) -> Result<String, Box<dyn Error>> {
197 let value = match self {
198 Self::Array(_) => {
199 let mut table = toml::map::Map::new();
200 table.insert("item".to_string(), self.to_toml_value()?);
201 toml::Value::Table(table)
202 }
203 Self::Object(_) => self.to_toml_value()?,
204 _ => {
205 let mut table = toml::map::Map::new();
206 table.insert("value".to_string(), self.to_toml_value()?);
207 toml::Value::Table(table)
208 }
209 };
210 Ok(toml::to_string_pretty(&value)?)
211 }
212
213 fn to_toml_value(&self) -> Result<toml::Value, Box<dyn Error>> {
214 Ok(match self {
215 Self::Null => return Err("TOML has no null type".into()),
216 Self::Bool(b) => toml::Value::Boolean(*b),
217 Self::Number(n) => {
218 if let Some(i) = n.as_i64() {
219 toml::Value::Integer(i)
220 } else if let Some(u) = n.as_u64() {
221 if u <= i64::MAX as u64 {
222 toml::Value::Integer(u as i64)
223 } else {
224 return Err(format!("TOML integer out of range: {u}").into());
225 }
226 } else if let Some(f) = n.as_f64() {
227 toml::Value::Float(f)
228 } else {
229 return Err(format!("cannot represent number {n} in TOML").into());
230 }
231 }
232 Self::String(s) => toml::Value::String(s.clone()),
233 Self::Array(a) => {
234 let mut items = Vec::with_capacity(a.len());
235 for item in a {
236 if let Self::Null = item {
237 return Err("TOML arrays cannot contain null".into());
238 }
239 items.push(item.to_toml_value()?);
240 }
241 toml::Value::Array(items)
242 }
243 Self::Object(o) => {
244 let mut table = toml::map::Map::new();
245 for (k, v) in o {
246 if let Self::Null = v {
247 continue;
248 }
249 table.insert(k.clone(), v.to_toml_value()?);
250 }
251 toml::Value::Table(table)
252 }
253 })
254 }
255
256 fn as_object(&self) -> Result<&Vec<(String, CanonicalValue)>, Box<dyn Error>> {
257 match self {
258 Self::Object(o) => Ok(o),
259 _ => Err("expected object".into()),
260 }
261 }
262
263 fn as_tabular(&self) -> Result<Vec<&CanonicalValue>, Box<dyn Error>> {
264 if let Some(arr) = find_first_object_array(self) {
265 return Ok(arr.iter().collect());
266 }
267 let mut current = self;
268 while let Self::Object(o) = current {
269 if o.len() != 1 {
270 break;
271 }
272 match &o[0].1 {
273 Self::Object(_) => current = &o[0].1,
274 _ => break,
275 }
276 }
277 if let Self::Object(o) = current {
278 if o.iter().all(|(_, v)| is_scalar(v)) {
279 return Ok(vec![current]);
280 }
281 }
282 Err("cannot determine tabular structure".into())
283 }
284
285 pub fn from_csv(input: &str) -> Result<Self, Box<dyn Error>> {
294 Self::from_delimited(input, b',')
295 }
296
297 pub fn to_csv(&self) -> Result<String, Box<dyn Error>> {
306 self.to_delimited(b',')
307 }
308
309 pub fn from_tsv(input: &str) -> Result<Self, Box<dyn Error>> {
318 Self::from_delimited(input, b'\t')
319 }
320
321 pub fn to_tsv(&self) -> Result<String, Box<dyn Error>> {
330 self.to_delimited(b'\t')
331 }
332
333 pub fn from_yaml(input: &str) -> Result<Self, Box<dyn Error>> {
342 let opts = serde_saphyr::options! {
343 strict_booleans: true,
344 ignore_binary_tag_for_string: true,
345 };
346 let v: serde_json::Value = serde_saphyr::from_str_with_options(input, opts)?;
347 Ok(Self::from_json_value(v))
348 }
349
350 pub fn to_yaml(&self) -> Result<String, Box<dyn Error>> {
359 Ok(serde_saphyr::to_string(&self.to_json_value())?)
360 }
361
362 pub fn from_xml(input: &str) -> Result<Self, Box<dyn Error>> {
375 use quick_xml::events::Event;
376 use quick_xml::Reader;
377
378 let mut reader = Reader::from_str(input);
379 reader.config_mut().trim_text(true);
380 let mut stack: Vec<XmlStackFrame> = Vec::new();
381 let mut root: Option<(String, CanonicalValue)> = None;
382
383 loop {
384 match reader.read_event() {
385 Ok(Event::Start(e)) => {
386 let name = String::from_utf8_lossy(e.name().as_ref()).to_string();
387 let mut attrs = Vec::new();
388 for a in e.attributes() {
389 let a = a?;
390 attrs.push((
391 String::from_utf8_lossy(a.key.as_ref()).to_string(),
392 a.unescape_value()?.to_string(),
393 ));
394 }
395 stack.push((name, attrs, Vec::new(), String::new()));
396 }
397 Ok(Event::Text(e)) => {
398 if let Some(top) = stack.last_mut() {
399 top.3.push_str(&e.unescape()?);
400 }
401 }
402 Ok(Event::End(_)) => {
403 let (name, attributes, children, text) = stack.pop().ok_or("unbalanced XML")?;
404 let trimmed = text.trim().to_string();
405 let value = if attributes.is_empty() && children.is_empty() {
406 Self::String(trimmed)
407 } else {
408 let mut obj: Vec<(String, CanonicalValue)> = Vec::new();
409 for (k, v) in attributes {
410 obj.push((format!("@{k}"), Self::String(v)));
411 }
412 if !trimmed.is_empty() {
413 obj.push(("#text".to_string(), Self::String(trimmed)));
414 }
415 for (cname, cval) in children {
416 if let Some(slot) = obj.iter_mut().find(|(k, _)| k == &cname) {
417 match &mut slot.1 {
418 Self::Array(a) => a.push(cval),
419 _ => {
420 let old = slot.1.clone();
421 slot.1 = Self::Array(vec![old, cval]);
422 }
423 }
424 } else {
425 obj.push((cname, cval));
426 }
427 }
428 Self::Object(obj)
429 };
430 if let Some(parent) = stack.last_mut() {
431 parent.2.push((name, value));
432 } else {
433 root = Some((name, value));
434 }
435 }
436 Ok(Event::Eof) => break,
437 Ok(_) => {}
438 Err(e) => return Err(e.into()),
439 }
440 }
441
442 let (root_name, root_value) = root.ok_or("empty XML")?;
443 Ok(Self::Object(vec![(root_name, root_value)]))
444 }
445
446 pub fn to_xml(&self) -> Result<String, Box<dyn Error>> {
457 let mut writer = quick_xml::Writer::new_with_indent(Vec::new(), b' ', 2);
458 match self {
459 Self::Object(o) if o.len() == 1 && !o[0].0.starts_with('@') && o[0].0 != "#text" => {
460 write_element(&mut writer, &o[0].0, &o[0].1)?;
461 }
462 Self::Array(a) => {
463 write_open(&mut writer, "root", &[])?;
464 for item in a {
465 write_element(&mut writer, "item", item)?;
466 }
467 write_close(&mut writer, "root")?;
468 }
469 _ => write_element(&mut writer, "root", self)?,
470 }
471 Ok(String::from_utf8(writer.into_inner())?)
472 }
473}
474
475fn write_open<W: std::io::Write>(
476 writer: &mut quick_xml::Writer<W>,
477 name: &str,
478 attrs: &[(String, String)],
479) -> Result<(), Box<dyn Error>> {
480 use quick_xml::events::{BytesStart, Event};
481 let mut start = BytesStart::new(name);
482 for (k, v) in attrs {
483 start.push_attribute((k.as_str(), v.as_str()));
484 }
485 writer.write_event(Event::Start(start))?;
486 Ok(())
487}
488
489fn write_close<W: std::io::Write>(
490 writer: &mut quick_xml::Writer<W>,
491 name: &str,
492) -> Result<(), Box<dyn Error>> {
493 use quick_xml::events::{BytesEnd, Event};
494 writer.write_event(Event::End(BytesEnd::new(name)))?;
495 Ok(())
496}
497
498fn write_element<W: std::io::Write>(
499 writer: &mut quick_xml::Writer<W>,
500 name: &str,
501 value: &CanonicalValue,
502) -> Result<(), Box<dyn Error>> {
503 use quick_xml::events::{BytesText, Event};
504
505 match value {
506 CanonicalValue::Array(items) => {
507 for item in items {
508 write_element(writer, name, item)?;
509 }
510 }
511 CanonicalValue::Object(o) => {
512 let attrs: Vec<(String, String)> = o
513 .iter()
514 .filter_map(|(k, v)| {
515 k.strip_prefix('@').and_then(|attr| match v {
516 CanonicalValue::String(s) => Some((attr.to_string(), s.clone())),
517 _ => None,
518 })
519 })
520 .collect();
521 write_open(writer, name, &attrs)?;
522 for (k, v) in o {
523 if k.starts_with('@') {
524 continue;
525 }
526 if k == "#text" {
527 if let CanonicalValue::String(s) = v {
528 writer.write_event(Event::Text(BytesText::new(s)))?;
529 }
530 continue;
531 }
532 write_element(writer, k, v)?;
533 }
534 write_close(writer, name)?;
535 }
536 CanonicalValue::String(s) => {
537 write_open(writer, name, &[])?;
538 writer.write_event(Event::Text(BytesText::new(s)))?;
539 write_close(writer, name)?;
540 }
541 CanonicalValue::Number(n) => {
542 write_open(writer, name, &[])?;
543 writer.write_event(Event::Text(BytesText::new(&n.to_string())))?;
544 write_close(writer, name)?;
545 }
546 CanonicalValue::Bool(b) => {
547 write_open(writer, name, &[])?;
548 writer.write_event(Event::Text(BytesText::new(&b.to_string())))?;
549 write_close(writer, name)?;
550 }
551 CanonicalValue::Null => {
552 write_open(writer, name, &[])?;
553 write_close(writer, name)?;
554 }
555 }
556 Ok(())
557}
558
559fn is_scalar(v: &CanonicalValue) -> bool {
560 matches!(
561 v,
562 CanonicalValue::Null
563 | CanonicalValue::Bool(_)
564 | CanonicalValue::Number(_)
565 | CanonicalValue::String(_)
566 )
567}
568
569fn find_first_object_array(v: &CanonicalValue) -> Option<&Vec<CanonicalValue>> {
570 match v {
571 CanonicalValue::Array(a)
572 if !a.is_empty() && a.iter().all(|x| matches!(x, CanonicalValue::Object(_))) =>
573 {
574 Some(a)
575 }
576 CanonicalValue::Object(o) => {
577 for (_, v) in o {
578 if let Some(arr) = find_first_object_array(v) {
579 return Some(arr);
580 }
581 }
582 None
583 }
584 _ => None,
585 }
586}