use std::io::Write;
use crate::document::XmlDocument;
use crate::error::Result;
use crate::node::{NodeType, XmlNode, XmlRoNode};
#[derive(Debug, Clone)]
pub struct SerializeOptions {
pub indent: bool,
pub indent_str: String,
pub xml_declaration: bool,
pub encoding: String,
}
impl Default for SerializeOptions {
fn default() -> Self {
Self {
indent: false,
indent_str: " ".to_string(),
xml_declaration: false,
encoding: "UTF-8".to_string(),
}
}
}
impl SerializeOptions {
pub fn pretty() -> Self {
Self {
indent: true,
..Default::default()
}
}
}
pub fn node_to_xml_string(doc: &XmlDocument, node: &XmlNode) -> Result<String> {
node_to_xml_string_with_options(doc, node, &SerializeOptions::default())
}
pub fn readonly_node_to_xml_string(doc: &XmlDocument, node: &XmlRoNode) -> Result<String> {
node_to_xml_string_with_options(doc, &node.clone().into_node(), &SerializeOptions::default())
}
pub fn node_to_xml_string_with_options(
_doc: &XmlDocument,
node: &XmlNode,
options: &SerializeOptions,
) -> Result<String> {
let mut output = Vec::new();
let mut serializer = XmlSerializer::new(&mut output, options.clone());
if options.xml_declaration {
serializer.write_declaration()?;
}
serializer.write_node(node, 0)?;
Ok(String::from_utf8(output)?)
}
pub fn document_to_xml_string(doc: &XmlDocument) -> Result<String> {
document_to_xml_string_with_options(doc, &SerializeOptions::default())
}
pub fn document_to_xml_string_with_options(
doc: &XmlDocument,
options: &SerializeOptions,
) -> Result<String> {
let root = doc.get_root_element()?;
let mut opts = options.clone();
opts.xml_declaration = true;
node_to_xml_string_with_options(doc, &root, &opts)
}
enum Target<'a> {
Document(&'a XmlDocument),
Node(&'a XmlNode),
RoNode(&'a XmlRoNode),
}
pub struct Printer<'a> {
target: Target<'a>,
options: SerializeOptions,
}
impl<'a> From<&'a XmlDocument> for Printer<'a> {
fn from(doc: &'a XmlDocument) -> Self {
Self {
target: Target::Document(doc),
options: SerializeOptions {
xml_declaration: true,
..SerializeOptions::default()
},
}
}
}
impl<'a> From<&'a XmlNode> for Printer<'a> {
fn from(node: &'a XmlNode) -> Self {
Self {
target: Target::Node(node),
options: SerializeOptions::default(),
}
}
}
impl<'a> From<&'a XmlRoNode> for Printer<'a> {
fn from(node: &'a XmlRoNode) -> Self {
Self {
target: Target::RoNode(node),
options: SerializeOptions::default(),
}
}
}
impl<'a> Printer<'a> {
pub fn pretty(mut self) -> Self {
self.options.indent = true;
self
}
pub fn indent(mut self, indent: impl Into<String>) -> Self {
self.options.indent = true;
self.options.indent_str = indent.into();
self
}
pub fn declaration(mut self, yes: bool) -> Self {
self.options.xml_declaration = yes;
self
}
pub fn encoding(mut self, encoding: impl Into<String>) -> Self {
self.options.encoding = encoding.into();
self
}
pub fn write_to<W: Write>(self, writer: &mut W) -> Result<()> {
let owned: Option<XmlNode> = match &self.target {
Target::Document(doc) => Some(doc.get_root_element()?),
Target::RoNode(node) => Some((*node).clone().into_node()),
Target::Node(_) => None,
};
let node = match &self.target {
Target::Node(node) => *node,
_ => owned
.as_ref()
.expect("owned node present for document/ro-node"),
};
let mut serializer = XmlSerializer::new(writer, self.options.clone());
if self.options.xml_declaration {
serializer.write_declaration()?;
}
serializer.write_node(node, 0)?;
Ok(())
}
pub fn into_bytes(self) -> Result<Vec<u8>> {
let mut buf = Vec::new();
self.write_to(&mut buf)?;
Ok(buf)
}
#[allow(clippy::wrong_self_convention)]
pub fn to_string(self) -> Result<String> {
Ok(String::from_utf8(self.into_bytes()?)?)
}
}
struct XmlSerializer<W: Write> {
writer: W,
options: SerializeOptions,
}
impl<W: Write> XmlSerializer<W> {
fn new(writer: W, options: SerializeOptions) -> Self {
Self { writer, options }
}
fn write_declaration(&mut self) -> Result<()> {
write!(
self.writer,
"<?xml version=\"1.0\" encoding=\"{}\"?>",
self.options.encoding
)?;
if self.options.indent {
writeln!(self.writer)?;
}
Ok(())
}
fn write_node(&mut self, node: &XmlNode, depth: usize) -> Result<()> {
match node.get_type() {
NodeType::Document => {
for child in node.get_child_nodes() {
self.write_node(&child, depth)?;
}
}
NodeType::Element => {
self.write_element(node, depth)?;
}
NodeType::Text => {
if let Some(content) = node.get_content() {
self.write_escaped_text(&content)?;
}
}
NodeType::CData => {
if let Some(content) = node.get_content() {
write!(self.writer, "<![CDATA[{}]]>", content)?;
}
}
NodeType::Comment => {
if let Some(content) = node.get_content() {
write!(self.writer, "<!--{}-->", content)?;
}
}
NodeType::ProcessingInstruction => {
let name = node.get_name();
if let Some(content) = node.get_content() {
write!(self.writer, "<?{} {}?>", name, content)?;
} else {
write!(self.writer, "<?{}?>", name)?;
}
}
NodeType::Attribute => {
}
NodeType::Namespace => {
}
}
Ok(())
}
fn write_element(&mut self, node: &XmlNode, depth: usize) -> Result<()> {
let qname = node.qname();
let attributes = node.get_attributes();
let namespace_decls = node.get_namespace_declarations();
let children = node.get_child_nodes();
if self.options.indent && depth > 0 {
for _ in 0..depth {
write!(self.writer, "{}", self.options.indent_str)?;
}
}
write!(self.writer, "<{}", qname)?;
for ns in &namespace_decls {
if ns.prefix().is_empty() {
write!(self.writer, " xmlns=\"{}\"", escape_attribute(ns.uri()))?;
} else {
write!(
self.writer,
" xmlns:{}=\"{}\"",
ns.prefix(),
escape_attribute(ns.uri())
)?;
}
}
for (name, value) in &attributes {
let attr_qname = if let Some((prefix, _uri)) = node.get_attribute_ns_info(name) {
if prefix.is_empty() {
name.clone()
} else {
format!("{}:{}", prefix, name)
}
} else {
name.clone()
};
write!(
self.writer,
" {}=\"{}\"",
attr_qname,
escape_attribute(value)
)?;
}
if children.is_empty() {
write!(self.writer, "/>")?;
} else {
write!(self.writer, ">")?;
let has_element_children = children.iter().any(|c| c.is_element());
if self.options.indent && has_element_children {
writeln!(self.writer)?;
}
for child in &children {
self.write_node(child, depth + 1)?;
if self.options.indent && child.is_element() {
writeln!(self.writer)?;
}
}
if self.options.indent && has_element_children {
for _ in 0..depth {
write!(self.writer, "{}", self.options.indent_str)?;
}
}
write!(self.writer, "</{}>", qname)?;
}
Ok(())
}
fn write_escaped_text(&mut self, text: &str) -> Result<()> {
for ch in text.chars() {
match ch {
'&' => write!(self.writer, "&")?,
'<' => write!(self.writer, "<")?,
'>' => write!(self.writer, ">")?,
_ => write!(self.writer, "{}", ch)?,
}
}
Ok(())
}
}
fn escape_attribute(value: &str) -> String {
let mut result = String::with_capacity(value.len());
for ch in value.chars() {
match ch {
'&' => result.push_str("&"),
'<' => result.push_str("<"),
'>' => result.push_str(">"),
'"' => result.push_str("""),
'\'' => result.push_str("'"),
_ => result.push(ch),
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse;
#[test]
fn test_serialize_simple() {
let doc = parse(r#"<root attr="value"><child>text</child></root>"#).unwrap();
let root = doc.get_root_element().unwrap();
let xml = node_to_xml_string(&doc, &root).unwrap();
assert!(xml.contains("<root"));
assert!(xml.contains("attr=\"value\""));
assert!(xml.contains("<child>text</child>"));
assert!(xml.contains("</root>"));
}
#[test]
fn test_serialize_namespaced() {
let doc = parse(
r#"<gml:root xmlns:gml="http://www.opengis.net/gml">
<gml:child>text</gml:child>
</gml:root>"#,
)
.unwrap();
let root = doc.get_root_element().unwrap();
let xml = node_to_xml_string(&doc, &root).unwrap();
assert!(xml.contains("xmlns:gml="));
assert!(xml.contains("<gml:root"));
}
#[test]
fn test_serialize_pretty() {
let doc = parse(r#"<root><a/><b/></root>"#).unwrap();
let root = doc.get_root_element().unwrap();
let xml =
node_to_xml_string_with_options(&doc, &root, &SerializeOptions::pretty()).unwrap();
assert!(xml.contains('\n'));
}
#[test]
fn test_escape_special_chars() {
let doc = parse(r#"<root attr="&test"><text></root>"#).unwrap();
let root = doc.get_root_element().unwrap();
let xml = node_to_xml_string(&doc, &root).unwrap();
assert!(xml.contains("&") || xml.contains("&test"));
assert!(xml.contains("<") || xml.contains("<text>"));
}
#[test]
fn test_printer_document_includes_declaration_by_default() {
let doc = parse(r#"<root><child>hi</child></root>"#).unwrap();
let xml = Printer::from(&doc).to_string().unwrap();
assert!(xml.contains("<?xml"));
assert!(xml.contains("<child>hi</child>"));
}
#[test]
fn test_printer_node_has_no_declaration_by_default() {
let doc = parse(r#"<root><child>hi</child></root>"#).unwrap();
let root = doc.get_root_element().unwrap();
let xml = Printer::from(&root).to_string().unwrap();
assert!(!xml.contains("<?xml"));
assert!(xml.contains("<root>"));
}
#[test]
fn test_printer_pretty_and_declaration_toggle() {
let doc = parse(r#"<root><a/><b/></root>"#).unwrap();
let xml = Printer::from(&doc)
.declaration(false)
.pretty()
.to_string()
.unwrap();
assert!(!xml.contains("<?xml"));
assert!(xml.contains('\n'));
}
#[test]
fn test_printer_write_to_and_into_bytes_match() {
let doc = parse(r#"<root><a/></root>"#).unwrap();
let bytes = Printer::from(&doc).into_bytes().unwrap();
let mut buf = Vec::new();
Printer::from(&doc).write_to(&mut buf).unwrap();
assert_eq!(bytes, buf);
}
#[test]
fn test_printer_readonly_node() {
let doc = parse(r#"<root><child>hi</child></root>"#).unwrap();
let root = doc.get_root_element().unwrap();
let ro = XmlRoNode::from_node(root);
let xml = Printer::from(&ro).to_string().unwrap();
assert!(xml.contains("<child>hi</child>"));
}
}