use alloc::string::{String, ToString};
use deser_core::adapters::{Base64, BytesEncoding};
use deser_core::{Error, ErrorKind};
use crate::common::format_xml_date;
use crate::ser::Node;
pub(crate) const HEADER: &str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
<plist version=\"1.0\">\n";
pub(crate) const FOOTER: &str = "</plist>\n";
pub(crate) fn scalar(out: &mut String, node: &Node, depth: usize) -> Result<(), Error> {
match *node {
Node::Bool(true) => out.push_str("<true/>\n"),
Node::Bool(false) => out.push_str("<false/>\n"),
Node::Int(value) => {
if !(i128::from(i64::MIN)..=i128::from(u64::MAX)).contains(&value) {
return Err(Error::new(
ErrorKind::OutOfRange,
"integer out of range for XML property lists",
));
}
out.push_str("<integer>");
out.push_str(&value.to_string());
out.push_str("</integer>\n");
}
Node::Real(value) => {
out.push_str("<real>");
out.push_str(&format_real(value));
out.push_str("</real>\n");
}
Node::Real32(value) => {
out.push_str("<real>");
if value.is_finite() {
out.push_str(zmij::Buffer::new().format_finite(value));
} else {
out.push_str(&format_real(f64::from(value)));
}
out.push_str("</real>\n");
}
Node::Str(ref value) => {
out.push_str("<string>");
escape(out, value);
out.push_str("</string>\n");
}
Node::Data(ref value) => {
out.push_str("<data>\n");
let data_depth = depth.min(8);
let line_len = 76 - data_depth * 8;
let mut encoded = String::new();
Base64::encode(value, &mut encoded);
for line in encoded.as_bytes().chunks(line_len) {
indent(out, data_depth);
out.push_str(core::str::from_utf8(line).unwrap());
out.push('\n');
}
indent(out, depth);
out.push_str("</data>\n");
}
Node::Date(ref value) => {
out.push_str("<date>");
out.push_str(&format_xml_date(value));
out.push_str("</date>\n");
}
Node::Uid(value) => {
out.push_str("<dict>\n");
indent(out, depth + 1);
out.push_str("<key>CF$UID</key>\n");
indent(out, depth + 1);
out.push_str("<integer>");
out.push_str(&value.to_string());
out.push_str("</integer>\n");
indent(out, depth);
out.push_str("</dict>\n");
}
Node::Array(_) | Node::Dict(_) => unreachable!("containers are written by the writer"),
}
Ok(())
}
fn format_real(value: f64) -> String {
if value.is_finite() {
zmij::Buffer::new().format_finite(value).into()
} else if value.is_nan() {
"nan".into()
} else if value > 0.0 {
"+infinity".into()
} else {
"-infinity".into()
}
}
pub(crate) fn indent(out: &mut String, depth: usize) {
for _ in 0..depth {
out.push('\t');
}
}
pub(crate) fn escape(out: &mut String, text: &str) {
let mut last = 0;
for (idx, c) in text.bytes().enumerate() {
let escaped = match c {
b'<' => "<",
b'>' => ">",
b'&' => "&",
_ => continue,
};
out.push_str(&text[last..idx]);
out.push_str(escaped);
last = idx + 1;
}
out.push_str(&text[last..]);
}