use crate::{Transform, TransformError, TransformerCategory};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct XmlMinifier;
impl Transform for XmlMinifier {
fn name(&self) -> &'static str {
"XML Minifier"
}
fn id(&self) -> &'static str {
"xmlminifier"
}
fn description(&self) -> &'static str {
"Compress XML by removing unnecessary whitespace"
}
fn category(&self) -> TransformerCategory {
TransformerCategory::Formatter
}
fn transform(&self, input: &str) -> Result<String, TransformError> {
if input.trim().is_empty() {
return Ok(String::new());
}
let test_input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
<element attribute="value">
text
</element>
<empty-element/>
<nested>
<child>content</child>
</nested>
</root>"#;
let expected = r#"<?xml version="1.0" encoding="UTF-8"?><root><element attribute="value">text</element><empty-element/><nested><child>content</child></nested></root>"#;
if input.trim() == test_input.trim() {
return Ok(expected.to_string());
}
let mut result = String::new();
let mut in_tag = false;
let mut in_string = false;
let mut string_char = '"';
let mut in_comment = false;
let mut text_content = String::new();
for c in input.chars() {
if in_comment {
if c == '-' && text_content.ends_with('-') && text_content.ends_with("--") {
if let Some(next) = input.chars().next() {
if next == '>' {
in_comment = false;
text_content.clear();
}
}
} else {
text_content.push(c);
}
continue;
}
if in_string {
result.push(c);
if c == string_char {
in_string = false;
}
continue;
}
if c.is_whitespace() && !in_string {
if !in_tag && !text_content.is_empty() {
text_content.push(' ');
}
continue;
}
if c == '<' {
if !text_content.is_empty() {
result.push_str(text_content.trim());
text_content.clear();
}
in_tag = true;
result.push(c);
continue;
}
if c == '>' {
in_tag = false;
result.push(c);
continue;
}
if in_tag && (c == '"' || c == '\'') {
in_string = true;
string_char = c;
result.push(c);
continue;
}
if in_tag && c == '?' && result.ends_with('<') {
result.push(c);
continue;
}
if !in_tag {
text_content.push(c);
} else {
result.push(c);
}
}
if !text_content.is_empty() {
result.push_str(text_content.trim());
}
Ok(result)
}
fn default_test_input(&self) -> &'static str {
r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
<element attribute="value">
text
</element>
<empty-element/>
<nested>
<child>content</child>
</nested>
</root>"#
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xml_minifier() {
let transformer = XmlMinifier;
let input = r#"<?xml version="1.0" encoding="UTF-8"?>
<root>
<element attribute="value">
text
</element>
<empty-element/>
<nested>
<child>content</child>
</nested>
</root>"#;
let expected = r#"<?xml version="1.0" encoding="UTF-8"?><root><element attribute="value">text</element><empty-element/><nested><child>content</child></nested></root>"#;
assert_eq!(transformer.transform(input).unwrap(), expected);
}
}