use std::fs;
use std::io::Cursor;
use quick_xml::Writer;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use quick_xml::reader::Reader;
use serde_json::{Map, Value};
use crate::{Error, Job, Result};
pub fn xml_to_json(job: &Job) -> Result<()> {
let raw = fs::read_to_string(job.input)?;
let value = parse_xml(&raw)?;
let json = serde_json::to_string_pretty(&value)
.map_err(|e| Error::other(format!("json serialize: {e}")))?;
fs::write(job.output, json + "\n")?;
Ok(())
}
pub fn json_to_xml(job: &Job) -> Result<()> {
let raw = fs::read_to_string(job.input)?;
let value: Value = serde_json::from_str(&raw)
.map_err(|e| Error::other(format!("invalid JSON: {e}")))?;
let xml = render_xml(&value)?;
fs::write(job.output, xml)?;
Ok(())
}
fn parse_xml(raw: &str) -> Result<Value> {
let mut reader = Reader::from_str(raw);
reader.config_mut().trim_text(true);
let mut stack: Vec<(String, Value)> = Vec::new();
let mut root: Option<(String, Value)> = None;
let mut buf = Vec::new();
loop {
match reader
.read_event_into(&mut buf)
.map_err(|e| Error::other(format!("xml parse: {e}")))?
{
Event::Start(ref e) => {
let (name, node) = build_element(e)?;
stack.push((name, node));
}
Event::Empty(ref e) => {
let (name, node) = build_element(e)?;
let collapsed = collapse_node(node);
attach_child(&mut stack, &mut root, &name, collapsed);
}
Event::End(_) => {
let (name, node) = stack.pop().ok_or_else(|| {
Error::other("xml: unmatched closing tag".to_string())
})?;
let collapsed = collapse_node(node);
attach_child(&mut stack, &mut root, &name, collapsed);
}
Event::Text(t) => {
let text = String::from_utf8(t.into_inner().into_owned())
.map_err(|e| Error::other(format!("xml text utf8: {e}")))?;
let text = unescape_xml(&text);
if !text.is_empty()
&& let Some((_, node)) = stack.last_mut()
{
append_text(node, &text);
}
}
Event::CData(cdata) => {
let text = String::from_utf8(cdata.into_inner().into_owned())
.map_err(|e| Error::other(format!("xml cdata: {e}")))?;
if !text.is_empty()
&& let Some((_, node)) = stack.last_mut()
{
append_text(node, &text);
}
}
Event::Eof => break,
_ => {}
}
buf.clear();
}
let (name, value) =
root.ok_or_else(|| Error::other("xml: no root element".to_string()))?;
let mut wrapper = Map::new();
wrapper.insert(name, value);
Ok(Value::Object(wrapper))
}
fn build_element(e: &BytesStart) -> Result<(String, Value)> {
let name = std::str::from_utf8(e.name().as_ref())
.map_err(|err| Error::other(format!("xml tag utf8: {err}")))?
.to_string();
let mut obj = Map::new();
for attr in e.attributes() {
let attr = attr.map_err(|err| Error::other(format!("xml attr: {err}")))?;
let key = std::str::from_utf8(attr.key.as_ref())
.map_err(|err| Error::other(format!("xml attr name: {err}")))?
.to_string();
let value = attr
.unescape_value()
.map_err(|err| Error::other(format!("xml attr value: {err}")))?
.into_owned();
obj.insert(format!("@{key}"), Value::String(value));
}
Ok((name, Value::Object(obj)))
}
fn attach_child(
stack: &mut [(String, Value)],
root: &mut Option<(String, Value)>,
name: &str,
child: Value,
) {
if let Some((_, parent)) = stack.last_mut() {
let Value::Object(map) = parent else {
return;
};
if let Some(existing) = map.remove(name) {
let arr = match existing {
Value::Array(mut arr) => {
arr.push(child);
arr
}
other => vec![other, child],
};
map.insert(name.to_string(), Value::Array(arr));
} else {
map.insert(name.to_string(), child);
}
} else {
*root = Some((name.to_string(), child));
}
}
fn append_text(node: &mut Value, text: &str) {
let Value::Object(map) = node else {
return;
};
match map.get_mut("#text") {
Some(Value::String(existing)) => existing.push_str(text),
_ => {
map.insert("#text".to_string(), Value::String(text.to_string()));
}
}
}
fn unescape_xml(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut rest = s;
while let Some(i) = rest.find('&') {
out.push_str(&rest[..i]);
rest = &rest[i..];
if let Some(end) = rest.find(';') {
let entity = &rest[1..end];
match entity {
"amp" => out.push('&'),
"lt" => out.push('<'),
"gt" => out.push('>'),
"apos" => out.push('\''),
"quot" => out.push('"'),
e if e.starts_with("#x") || e.starts_with("#X") => {
if let Ok(n) = u32::from_str_radix(&e[2..], 16)
&& let Some(c) = char::from_u32(n)
{
out.push(c);
}
}
e if e.starts_with('#') => {
if let Ok(n) = e[1..].parse::<u32>()
&& let Some(c) = char::from_u32(n)
{
out.push(c);
}
}
_ => {
out.push('&');
out.push_str(entity);
out.push(';');
}
}
rest = &rest[end + 1..];
} else {
out.push_str(rest);
break;
}
}
out.push_str(rest);
out
}
fn collapse_node(value: Value) -> Value {
if let Value::Object(map) = &value
&& map.len() == 1
&& let Some(Value::String(text)) = map.get("#text")
{
return Value::String(text.clone());
}
value
}
fn render_xml(value: &Value) -> Result<String> {
let mut writer = Writer::new(Cursor::new(Vec::new()));
match value {
Value::Object(map) if map.len() == 1 => {
let (name, body) = map.iter().next().unwrap();
write_node(&mut writer, name, body)?;
}
_ => write_node(&mut writer, "root", value)?,
}
let bytes = writer.into_inner().into_inner();
String::from_utf8(bytes).map_err(|e| Error::other(format!("xml utf8: {e}")))
}
fn write_node(
writer: &mut Writer<Cursor<Vec<u8>>>,
tag: &str,
value: &Value,
) -> Result<()> {
match value {
Value::Null => {
writer
.write_event(Event::Empty(BytesStart::new(tag)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
}
Value::Array(items) => {
for item in items {
write_node(writer, tag, item)?;
}
}
Value::Object(map) => {
let mut start = BytesStart::new(tag);
let mut text: Option<&str> = None;
let mut children: Vec<(&String, &Value)> = Vec::new();
for (k, v) in map {
if let Some(attr_name) = k.strip_prefix('@')
&& let Value::String(s) = v
{
start.push_attribute((attr_name, s.as_str()));
continue;
}
if k == "#text"
&& let Value::String(s) = v
{
text = Some(s);
continue;
}
children.push((k, v));
}
if text.is_none() && children.is_empty() {
writer
.write_event(Event::Empty(start))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
return Ok(());
}
writer
.write_event(Event::Start(start))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
if let Some(t) = text {
writer
.write_event(Event::Text(BytesText::new(t)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
}
for (child_tag, child_val) in children {
write_node(writer, child_tag, child_val)?;
}
writer
.write_event(Event::End(BytesEnd::new(tag)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
}
Value::String(s) => {
writer
.write_event(Event::Start(BytesStart::new(tag)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
writer
.write_event(Event::Text(BytesText::new(s)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
writer
.write_event(Event::End(BytesEnd::new(tag)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
}
Value::Number(n) => {
let s = n.to_string();
writer
.write_event(Event::Start(BytesStart::new(tag)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
writer
.write_event(Event::Text(BytesText::new(&s)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
writer
.write_event(Event::End(BytesEnd::new(tag)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
}
Value::Bool(b) => {
let s = if *b { "true" } else { "false" };
writer
.write_event(Event::Start(BytesStart::new(tag)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
writer
.write_event(Event::Text(BytesText::new(s)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
writer
.write_event(Event::End(BytesEnd::new(tag)))
.map_err(|e| Error::other(format!("xml write: {e}")))?;
}
}
Ok(())
}