1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use crate::plugins::Plugin;
use crate::tree::{Document, Node};
pub struct RemoveEmptyContainers;
impl Plugin for RemoveEmptyContainers {
fn apply(&self, doc: &mut Document) {
process_nodes(&mut doc.root);
}
}
fn process_nodes(nodes: &mut Vec<Node>) {
nodes.retain(|node| {
if let Node::Element(elem) = node {
// List of container tags
let is_container = matches!(
elem.name.as_str(),
"defs"
| "g"
| "marker"
| "mask"
| "missing-glyph"
| "pattern"
| "switch"
| "symbol"
);
if is_container && elem.children.is_empty() {
// If it has id, carefully considering removal?
// svgo removeEmptyContainers removes them even if they have ID,
// UNLESS it's "svg" (root) or inner svg.
// But wait, pattern/mask with ID but empty is useless (renders nothing).
// So safe to remove.
return false;
}
}
true
});
for node in nodes {
if let Node::Element(elem) = node {
process_nodes(&mut elem.children);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::printer;
#[test]
fn test_remove_empty_g() {
let input = "<svg><g/></svg>";
let _expected = "<svg></svg>"; // Expect <svg/> or <svg></svg> depending on printer (parser produces children vec, if empty, printer prints short if no children)
// Actually printer prints <svg></svg> (with children). Wait.
// My printer always uses self-closing for empty? No?
// Let's check expected. Parser returns Node::Element("svg", [], []).
// Printer: if children empty, print self-closing "/>".
// Wait, input <svg><g/></svg>. svg has 1 child (g). g has 0 children.
// removeEmptyContainers removes g. svg has 0 children.
// Printer prints <svg/> (self closing).
let mut doc = parser::parse(input).unwrap();
RemoveEmptyContainers.apply(&mut doc);
// However, the test output expectation depends on how parser/printer handles root.
// If my printer logic handles root self-closing, it should be <svg/>.
// Let's use flexible assertion or match my printer code.
// Previous tests used <svg></svg> expectation for empty content inside?
// Let's check `test_remove_invisible_rect` in `remove_useless_stroke_and_fill`.
// It failed because it expected `<svg></svg>` but got `<svg/>`.
// So my printer DOES print self-closing!
let output = printer::print(&doc);
assert!(output == "<svg/>" || output == "<svg></svg>");
}
#[test]
fn test_remove_empty_defs() {
// <defs/> usually removed by removeUselessDefs, but this catches it too
let input = "<svg><defs/></svg>";
let mut doc = parser::parse(input).unwrap();
RemoveEmptyContainers.apply(&mut doc);
let output = printer::print(&doc);
assert!(output == "<svg/>" || output == "<svg></svg>");
}
#[test]
fn test_keep_filled() {
let input = "<svg><g><rect/></g></svg>";
let mut doc = parser::parse(input).unwrap();
RemoveEmptyContainers.apply(&mut doc);
assert_eq!(printer::print(&doc), input);
}
}