use std::collections::HashSet;
use xmlwriter::XmlWriter;
use super::OptimizeConfig;
use super::xml_passes;
pub fn write_node(
node: roxmltree::Node,
writer: &mut XmlWriter,
config: &OptimizeConfig,
editor_prefixes: &[String],
declared_ns: &mut HashSet<String>,
) {
match node.node_type() {
roxmltree::NodeType::Root => {
for child in node.children() {
write_node(child, writer, config, editor_prefixes, declared_ns);
}
}
roxmltree::NodeType::Element => {
write_element(node, writer, config, editor_prefixes, declared_ns);
}
roxmltree::NodeType::Text => {
write_text(node, writer, config);
}
roxmltree::NodeType::Comment => {
if !config.remove_comments
&& let Some(text) = node.text()
{
writer.write_comment(text);
}
}
roxmltree::NodeType::PI => {
}
}
}
fn write_text(node: roxmltree::Node, writer: &mut XmlWriter, config: &OptimizeConfig) {
let text = node.text().unwrap_or("");
if config.minify {
let trimmed = xml_passes::minify_text(text);
if !trimmed.is_empty() {
writer.write_text(&trimmed);
}
} else {
writer.write_text(text);
}
}
fn write_element(
node: roxmltree::Node,
writer: &mut XmlWriter,
config: &OptimizeConfig,
editor_prefixes: &[String],
declared_ns: &mut HashSet<String>,
) {
let tag = node.tag_name();
if xml_passes::is_editor_element(tag, editor_prefixes) {
return;
}
if config.remove_metadata && tag.name() == "metadata" {
return;
}
let children: Vec<roxmltree::Node> = node
.children()
.filter(|c| !should_skip_child(c, config, editor_prefixes))
.collect();
if config.collapse_groups
&& xml_passes::is_collapsible_container(tag.name())
&& children.is_empty()
{
return;
}
if config.collapse_groups
&& tag.name() == "g"
&& children.len() == 1
&& xml_passes::has_no_meaningful_attributes(&node, editor_prefixes)
{
write_node(children[0], writer, config, editor_prefixes, declared_ns);
return;
}
write_element_tag(node, writer, config, editor_prefixes, declared_ns);
}
fn write_element_tag(
node: roxmltree::Node,
writer: &mut XmlWriter,
config: &OptimizeConfig,
editor_prefixes: &[String],
declared_ns: &mut HashSet<String>,
) {
let elem_name = node.tag_name().name().to_string();
writer.start_element(&elem_name);
write_attributes(&node, writer, editor_prefixes);
write_namespace_declarations(&node, writer, editor_prefixes, declared_ns);
for child in node.children() {
write_node(child, writer, config, editor_prefixes, declared_ns);
}
writer.end_element();
}
fn write_attributes(node: &roxmltree::Node, writer: &mut XmlWriter, editor_prefixes: &[String]) {
for attr in node.attributes() {
if xml_passes::is_editor_attribute(&attr, editor_prefixes) {
continue;
}
if attr.value().is_empty() {
continue;
}
let attr_name = format_attr_name(&attr);
if let Some(prefix) = attr_name.strip_prefix("xmlns:")
&& editor_prefixes.contains(&prefix.to_string())
{
continue;
}
writer.write_attribute(&attr_name, attr.value());
}
}
fn should_skip_child(
node: &roxmltree::Node,
config: &OptimizeConfig,
editor_prefixes: &[String],
) -> bool {
match node.node_type() {
roxmltree::NodeType::Element => {
let tag = node.tag_name();
if xml_passes::is_editor_element(tag, editor_prefixes) {
return true;
}
if config.remove_metadata && tag.name() == "metadata" {
return true;
}
false
}
roxmltree::NodeType::Comment => config.remove_comments,
roxmltree::NodeType::Text if config.minify => {
let text = node.text().unwrap_or("");
xml_passes::minify_text(text).is_empty()
}
roxmltree::NodeType::PI => true,
_ => false,
}
}
fn format_attr_name(attr: &roxmltree::Attribute) -> String {
if let Some(ns) = attr.namespace()
&& let Some(prefix) = namespace_uri_to_prefix(ns)
{
return format!("{prefix}:{}", attr.name());
}
attr.name().to_string()
}
fn write_namespace_declarations(
node: &roxmltree::Node,
writer: &mut XmlWriter,
editor_prefixes: &[String],
declared_ns: &mut HashSet<String>,
) {
for ns in node.namespaces() {
let uri = ns.uri();
if declared_ns.contains(uri) {
continue;
}
if let Some(prefix) = ns.name() {
if editor_prefixes.contains(&prefix.to_string()) {
continue;
}
writer.write_attribute(&format!("xmlns:{prefix}"), uri);
} else {
writer.write_attribute("xmlns", uri);
}
declared_ns.insert(uri.to_string());
}
}
fn namespace_uri_to_prefix(uri: &str) -> Option<&'static str> {
match uri {
"http://www.w3.org/1999/xlink" => Some("xlink"),
"http://www.w3.org/XML/1998/namespace" => Some("xml"),
_ => None,
}
}