use std::fmt;
use log::warn;
use xmlwriter::XmlWriter;
use crate::{
AttributeId,
AttributeValue,
Document,
ElementId,
FilterSvgAttrs,
Node,
NodeData,
NodeEdge,
NodeType,
QName,
WriteBuffer,
};
use crate::{
ListSeparator,
ValueWriteOptions,
};
pub use xmlwriter::Indent;
#[derive(Debug)]
pub struct WriteOptions {
pub use_single_quote: bool,
pub indent: Indent,
pub attributes_indent: Indent,
pub values: ValueWriteOptions,
}
impl Default for WriteOptions {
fn default() -> Self {
WriteOptions {
indent: Indent::Spaces(4),
attributes_indent: Indent::None,
use_single_quote: false,
values: ValueWriteOptions {
trim_hex_colors: false,
remove_leading_zero: false,
use_compact_path_notation: false,
join_arc_to_flags: false,
remove_duplicated_path_commands: false,
use_implicit_lineto_commands: false,
simplify_transform_matrices: false,
list_separator: ListSeparator::Space,
},
}
}
}
pub(crate) fn write_dom(doc: &Document, opt: &WriteOptions) -> String {
let xml_opt = xmlwriter::Options {
use_single_quote: opt.use_single_quote,
indent: opt.indent,
attributes_indent: opt.attributes_indent,
};
let mut xml = XmlWriter::new(xml_opt);
for edge in doc.root().traverse() {
match edge {
NodeEdge::Start(node) => {
match node.node_type() {
NodeType::Root => {}
NodeType::Element => {
match *node.tag_name() {
QName::Id(id) => xml.start_element(id.as_str()),
QName::Name(ref s) => xml.start_element(s),
}
write_attributes(&node, opt, &mut xml);
if node.has_tag_name(ElementId::Text) {
xml.set_preserve_whitespaces(true);
}
}
NodeType::Comment => {
xml.write_comment(&node.text());
}
NodeType::Text => {
xml.write_text(&node.text());
}
}
}
NodeEdge::End(node) => {
if node.is_element() {
xml.end_element();
}
if node.has_tag_name(ElementId::Text) {
xml.set_preserve_whitespaces(false);
}
}
}
}
xml.end_document()
}
fn write_attributes(
node: &Node,
opt: &WriteOptions,
xml: &mut XmlWriter,
) {
if node.has_tag_name(ElementId::Svg) {
if node.parent().map(|v| v.is_root()) == Some(true) {
xml.write_attribute("xmlns", "http://www.w3.org/2000/svg");
let xlink_needed = node.descendants().any(|n| n.has_attribute(AttributeId::Href));
if xlink_needed {
xml.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
}
}
}
if node.has_id() {
xml.write_attribute("id", &node.id())
}
let attrs = node.attributes();
let mut ids: Vec<_> = attrs.iter().svg()
.map(|(aid, attr)| (aid, attr.name.as_ref()))
.collect();
ids.sort_by_key(|&(x, _)| x as usize);
for &(id, name) in &ids {
let attr = attrs.get(name).unwrap();
let name = match id {
AttributeId::Href => "xlink:href",
AttributeId::Space => "xml:space",
_ => id.as_str(),
};
if id == AttributeId::Unicode {
if let AttributeValue::String(ref s) = attr.value {
xml.write_attribute_raw(name, |buf| write_escaped(s, buf));
} else {
warn!("An invalid 'unicode' attribute value: {:?}.", attr.value);
}
} else {
xml.write_attribute_raw(name, |buf| attr.value.write_buf_opt(&opt.values, buf));
}
}
for attr in attrs.iter() {
if let QName::Name(ref name) = attr.name {
xml.write_attribute_raw(name, |buf| attr.value.write_buf_opt(&opt.values, buf));
}
}
}
fn write_escaped(unicode: &str, out: &mut Vec<u8>) {
use std::io::Write;
if unicode.starts_with("&#") {
out.extend_from_slice(unicode.as_bytes());
} else {
for c in unicode.chars() {
out.extend_from_slice(b"&#x");
write!(out, "{:x}", c as u32).unwrap();
out.push(b';');
}
}
}
impl fmt::Debug for NodeData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.node_type {
NodeType::Root => write!(f, "Root()"),
NodeType::Element => {
write!(f, "Element({}", self.tag_name)?;
write_element_content(self, f, true, true)?;
write!(f, ")")
}
NodeType::Comment => write!(f, "Comment({})", self.text),
NodeType::Text => write!(f, "Text({})", self.text),
}
}
}
impl fmt::Display for NodeData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.node_type {
NodeType::Root => write!(f, ""),
NodeType::Element => {
write!(f, "<{}", self.tag_name)?;
write_element_content(self, f, true, false)?;
write!(f, ">")
}
NodeType::Comment => write!(f, "<!--{}-->", self.text),
NodeType::Text => write!(f, "{}", self.text),
}
}
}
fn write_element_content(
node: &NodeData,
f: &mut fmt::Formatter,
space_before_attrs: bool,
print_linked: bool,
) -> fmt::Result {
if !node.id.is_empty() {
write!(f, " id='{}'", node.id)?;
}
if !node.attributes.is_empty() {
if space_before_attrs {
write!(f, " ")?;
}
write!(f, "{}", node.attributes)?;
}
if print_linked && !node.linked_nodes.is_empty() {
write!(f, "; linked-nodes:")?;
for node in &node.linked_nodes {
write!(f, " '{}'", *node.id())?;
}
}
Ok(())
}