use miniserde::ser::{Fragment, Map as SerMap, Serialize};
use std::borrow::Cow;
fn escape_xml(s: &str) -> Cow<'_, str> {
quick_xml::escape::escape(s)
}
fn xml_type_attr_name(tn: &str) -> Cow<'_, str> {
match tn {
"string" | "boolean" | "byte" | "short" | "int" | "long" | "float" | "double"
| "dateTime" | "anyURI" | "base64Binary" => Cow::Owned(format!("xsd:{tn}")),
_ => Cow::Borrowed(tn),
}
}
fn array_element_name(array_type: &str) -> &str {
match array_type {
"ArrayOfString" => "string",
"ArrayOfBoolean" => "boolean",
"ArrayOfByte" => "byte",
"ArrayOfShort" => "short",
"ArrayOfInt" => "int",
"ArrayOfLong" => "long",
"ArrayOfFloat" => "float",
"ArrayOfDouble" => "double",
"ArrayOfDateTime" => "dateTime",
"ArrayOfAnyType" => "anyType",
"ArrayOfAnyURI" => "anyURI",
"ArrayOfBase64Binary" => "base64Binary",
other => other.strip_prefix("ArrayOf").unwrap_or("item"),
}
}
pub fn to_xml<T: Serialize>(value: &T, root_name: &str) -> String {
let mut out = String::new();
stream_xml(value, root_name, &mut out);
out
}
pub fn to_xml_doc<T: Serialize>(value: &T, root_name: &str) -> String {
let mut out = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
stream_xml(value, root_name, &mut out);
out
}
pub(crate) fn stream_xml(ser: &dyn Serialize, tag: &str, out: &mut String) {
match ser.begin() {
Fragment::Null => {
out.push('<');
out.push_str(tag);
out.push_str("/>");
}
Fragment::Bool(b) => write_leaf(tag, if b { "true" } else { "false" }, out),
Fragment::Str(s) => write_leaf(tag, &escape_xml(&s), out),
Fragment::U64(n) => write_leaf(tag, &n.to_string(), out),
Fragment::I64(n) => write_leaf(tag, &n.to_string(), out),
Fragment::F64(n) => write_leaf(tag, &n.to_string(), out),
Fragment::Seq(mut seq) => {
while let Some(item) = seq.next() {
stream_xml(item, tag, out);
}
}
Fragment::Map(mut map) => {
stream_xml_map(&mut *map, tag, out);
}
}
}
fn write_leaf(tag: &str, text: &str, out: &mut String) {
out.push('<');
out.push_str(tag);
out.push('>');
out.push_str(text);
out.push_str("</");
out.push_str(tag);
out.push('>');
}
fn write_scalar(ser: &dyn Serialize, out: &mut String) {
match ser.begin() {
Fragment::Null => {}
Fragment::Bool(b) => out.push_str(if b { "true" } else { "false" }),
Fragment::Str(s) => out.push_str(&escape_xml(&s)),
Fragment::U64(n) => out.push_str(&n.to_string()),
Fragment::I64(n) => out.push_str(&n.to_string()),
Fragment::F64(n) => out.push_str(&n.to_string()),
Fragment::Seq(_) | Fragment::Map(_) => {}
}
}
fn scalar_to_string(ser: &dyn Serialize) -> Option<String> {
match ser.begin() {
Fragment::Str(s) => Some(s.into_owned()),
_ => None,
}
}
fn stream_xml_map(map: &mut dyn SerMap, tag: &str, out: &mut String) {
let tag_start = out.len();
out.push('<');
out.push_str(tag);
let mut is_mor = false;
let mut mor_attr = String::new();
let mut mor_text = String::new();
let mut has_body = false;
let mut captured_type_name: Option<String> = None;
while let Some((key, value)) = map.next() {
if &*key == "_typeName" {
if let Some(tn) = scalar_to_string(value) {
if tn == "ManagedObjectReference" {
is_mor = true;
out.truncate(tag_start);
} else {
let attr_name = xml_type_attr_name(&tn);
out.push_str(" xsi:type=\"");
out.push_str(&escape_xml(&attr_name));
out.push('"');
captured_type_name = Some(tn);
}
}
} else if is_mor {
match &*key {
"type" => write_scalar(value, &mut mor_attr),
"value" => write_scalar(value, &mut mor_text),
_ => {}
}
} else if &*key == "_value" {
stream_value_content(value, &captured_type_name, tag, &mut has_body, out);
} else if let Some(attr_name) = key.strip_prefix('@') {
out.push(' ');
out.push_str(attr_name);
out.push_str("=\"");
write_scalar(value, out);
out.push('"');
} else if &*key == "#text" {
if !has_body {
out.push('>');
has_body = true;
}
write_scalar(value, out);
} else {
if !has_body {
out.push('>');
has_body = true;
}
stream_xml(value, &key, out);
}
}
if is_mor {
out.push('<');
out.push_str(tag);
if !mor_attr.is_empty() {
out.push_str(" type=\"");
out.push_str(&mor_attr);
out.push('"');
}
if !mor_text.is_empty() {
out.push('>');
out.push_str(&mor_text);
out.push_str("</");
out.push_str(tag);
out.push('>');
} else {
out.push_str("/>");
}
} else if has_body {
out.push_str("</");
out.push_str(tag);
out.push('>');
} else {
out.push_str("/>");
}
}
fn stream_value_content(
value: &dyn Serialize,
captured_type_name: &Option<String>,
tag: &str,
has_body: &mut bool,
out: &mut String,
) {
match value.begin() {
Fragment::Null => {}
Fragment::Bool(b) => {
if !*has_body {
out.push('>');
*has_body = true;
}
out.push_str(if b { "true" } else { "false" });
}
Fragment::Str(s) => {
if !*has_body {
out.push('>');
*has_body = true;
}
out.push_str(&escape_xml(&s));
}
Fragment::U64(n) => {
if !*has_body {
out.push('>');
*has_body = true;
}
out.push_str(&n.to_string());
}
Fragment::I64(n) => {
if !*has_body {
out.push('>');
*has_body = true;
}
out.push_str(&n.to_string());
}
Fragment::F64(n) => {
if !*has_body {
out.push('>');
*has_body = true;
}
out.push_str(&n.to_string());
}
Fragment::Seq(mut seq) => {
if !*has_body {
out.push('>');
*has_body = true;
}
let elem_name = captured_type_name
.as_deref()
.map(array_element_name)
.unwrap_or("item");
while let Some(item) = seq.next() {
stream_xml(item, elem_name, out);
}
}
Fragment::Map(mut map) => {
if !*has_body {
out.push('>');
*has_body = true;
}
let elem_name = captured_type_name
.as_deref()
.map(array_element_name)
.unwrap_or(tag);
stream_xml_map(&mut *map, elem_name, out);
}
}
}