use super::{Pass, PassResult};
use crate::ast::{Document, NodeKind};
const REMOVABLE_CONTAINERS: &[&str] = &[
"g",
"defs",
"symbol",
"clipPath",
"mask",
"pattern",
"linearGradient",
"radialGradient",
"filter",
"marker",
];
pub struct RemoveEmptyContainers;
impl Pass for RemoveEmptyContainers {
fn name(&self) -> &'static str {
"removeEmptyContainers"
}
fn run(&self, doc: &mut Document) -> PassResult {
let mut changed = false;
let ids = doc.traverse();
for id in ids {
let node = doc.node(id);
if let NodeKind::Element(ref elem) = node.kind
&& REMOVABLE_CONTAINERS.contains(&elem.name.as_str())
{
let has_meaningful_children =
doc.children(id).any(|child| match &doc.node(child).kind {
NodeKind::Text(t) => !t.trim().is_empty(),
_ => true,
});
if !has_meaningful_children {
if elem.attr("id").is_none() {
doc.remove(id);
changed = true;
}
}
}
}
if changed {
PassResult::Changed
} else {
PassResult::Unchanged
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse;
use crate::serializer::serialize;
#[test]
fn removes_empty_g() {
let input = r#"<svg xmlns="http://www.w3.org/2000/svg"><g></g><rect/></svg>"#;
let mut doc = parse(input).unwrap();
assert_eq!(RemoveEmptyContainers.run(&mut doc), PassResult::Changed);
let output = serialize(&doc);
assert!(!output.contains("<g>"));
}
#[test]
fn keeps_g_with_children() {
let input = r#"<svg xmlns="http://www.w3.org/2000/svg"><g><rect/></g></svg>"#;
let mut doc = parse(input).unwrap();
assert_eq!(RemoveEmptyContainers.run(&mut doc), PassResult::Unchanged);
}
#[test]
fn keeps_empty_g_with_id() {
let input = r#"<svg xmlns="http://www.w3.org/2000/svg"><g id="target"></g></svg>"#;
let mut doc = parse(input).unwrap();
assert_eq!(RemoveEmptyContainers.run(&mut doc), PassResult::Unchanged);
}
#[test]
fn removes_nested_empty_containers_across_passes() {
let input = r#"<svg xmlns="http://www.w3.org/2000/svg"><g><g></g></g></svg>"#;
let mut doc = parse(input).unwrap();
assert_eq!(RemoveEmptyContainers.run(&mut doc), PassResult::Changed);
assert_eq!(RemoveEmptyContainers.run(&mut doc), PassResult::Changed);
let output = serialize(&doc);
assert!(!output.contains("<g>"));
}
}