#![allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct XmlElement {
pub tag: String,
pub attributes: Vec<(String, String)>,
pub children: Vec<XmlElement>,
pub text: Option<String>,
}
impl XmlElement {
pub fn new(tag: &str) -> Self {
Self { tag: tag.into(), ..Default::default() }
}
pub fn set_attr(&mut self, key: &str, val: &str) {
self.attributes.push((key.into(), val.into()));
}
pub fn add_child(&mut self, child: XmlElement) {
self.children.push(child);
}
}
#[derive(Debug, Clone, Default)]
pub struct XmlDocument {
pub root: Option<XmlElement>,
pub version: String,
pub encoding: String,
}
pub fn new_xml_document(root_tag: &str) -> XmlDocument {
XmlDocument {
root: Some(XmlElement::new(root_tag)),
version: "1.0".into(),
encoding: "UTF-8".into(),
}
}
pub fn add_root_attr(doc: &mut XmlDocument, key: &str, val: &str) {
if let Some(ref mut root) = doc.root {
root.set_attr(key, val);
}
}
pub fn add_root_child(doc: &mut XmlDocument, child: XmlElement) {
if let Some(ref mut root) = doc.root {
root.add_child(child);
}
}
pub fn element_to_string(el: &XmlElement, indent: usize) -> String {
let pad = " ".repeat(indent);
let attrs: String = el.attributes.iter().map(|(k, v)| format!(" {}=\"{}\"", xml_escape(k), xml_escape(v))).collect();
if el.children.is_empty() && el.text.is_none() {
return format!("{}<{}{}/>\n", pad, el.tag, attrs);
}
let mut s = format!("{}<{}{}>\n", pad, el.tag, attrs);
if let Some(ref t) = el.text {
s.push_str(&format!("{}{}\n", " ".repeat(indent + 2), xml_escape(t)));
}
for child in &el.children {
s.push_str(&element_to_string(child, indent + 2));
}
s.push_str(&format!("{}</{}>\n", pad, el.tag));
s
}
pub fn export_xml(doc: &XmlDocument) -> String {
let mut s = format!("<?xml version=\"{}\" encoding=\"{}\"?>\n", doc.version, doc.encoding);
if let Some(ref root) = doc.root {
s.push_str(&element_to_string(root, 0));
}
s
}
pub fn xml_escape(s: &str) -> String {
s.replace('&', "&").replace('<', "<").replace('>', ">").replace('"', """)
}
pub fn root_child_count(doc: &XmlDocument) -> usize {
doc.root.as_ref().map_or(0, |r| r.children.len())
}
pub fn mesh_to_xml(positions: &[[f32; 3]], indices: &[u32]) -> XmlDocument {
let mut doc = new_xml_document("mesh");
add_root_attr(&mut doc, "vertices", &positions.len().to_string());
add_root_attr(&mut doc, "triangles", &(indices.len() / 3).to_string());
let mut verts_el = XmlElement::new("vertices");
for (i, p) in positions.iter().enumerate() {
let mut v = XmlElement::new("v");
v.set_attr("id", &i.to_string());
v.set_attr("x", &format!("{:.6}", p[0]));
v.set_attr("y", &format!("{:.6}", p[1]));
v.set_attr("z", &format!("{:.6}", p[2]));
verts_el.add_child(v);
}
add_root_child(&mut doc, verts_el);
doc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_xml_document_has_root() {
let doc = new_xml_document("root");
assert!(doc.root.is_some());
}
#[test]
fn test_export_xml_has_declaration() {
let doc = new_xml_document("data");
let s = export_xml(&doc);
assert!(s.starts_with("<?xml"));
}
#[test]
fn test_add_root_attr() {
let mut doc = new_xml_document("mesh");
add_root_attr(&mut doc, "units", "mm");
let s = export_xml(&doc);
assert!(s.contains("units"));
}
#[test]
fn test_add_root_child() {
let mut doc = new_xml_document("root");
add_root_child(&mut doc, XmlElement::new("child"));
assert_eq!(root_child_count(&doc), 1);
}
#[test]
fn test_xml_escape_ampersand() {
assert_eq!(xml_escape("a&b"), "a&b");
}
#[test]
fn test_xml_escape_less_than() {
assert_eq!(xml_escape("a<b"), "a<b");
}
#[test]
fn test_element_self_closing() {
let el = XmlElement::new("empty");
let s = element_to_string(&el, 0);
assert!(s.contains("/>"));
}
#[test]
fn test_mesh_to_xml_vertex_count() {
let p = vec![[0.0,0.0,0.0],[1.0,0.0,0.0],[0.5,1.0,0.0]];
let i = vec![0u32,1,2];
let doc = mesh_to_xml(&p, &i);
let s = export_xml(&doc);
assert!(s.contains("vertices=\"3\""));
}
#[test]
fn test_root_child_count_zero() {
let doc = new_xml_document("x");
assert_eq!(root_child_count(&doc), 0);
}
#[test]
fn test_export_xml_contains_root_tag() {
let doc = new_xml_document("scene");
let s = export_xml(&doc);
assert!(s.contains("<scene"));
}
}