diff --git a/crates/zensical/src/compat/mkdocs/plugin/minify/html.rs b/crates/zensical/src/compat/mkdocs/plugin/minify/html.rs
index 8aec48b..6417914 100644
--- a/crates/zensical/src/compat/mkdocs/plugin/minify/html.rs
+++ b/crates/zensical/src/compat/mkdocs/plugin/minify/html.rs
@@ -245,6 +245,23 @@ mod tests {
);
}
+ #[test]
+ fn separates_self_closing_tags_from_unquoted_attribute_values() {
+ let input = concat!(
+ r#""#,
+ );
+ assert_eq!(
+ minify(input, &options(), false, false),
+ concat!(
+ r#""#,
+ )
+ );
+ }
+
#[test]
fn minifies_supported_inline_languages() {
let input = r#"
diff --git a/crates/zensical/src/compat/mkdocs/plugin/minify/html/serializer.rs b/crates/zensical/src/compat/mkdocs/plugin/minify/html/serializer.rs
index 33ed55f..36d3463 100644
--- a/crates/zensical/src/compat/mkdocs/plugin/minify/html/serializer.rs
+++ b/crates/zensical/src/compat/mkdocs/plugin/minify/html/serializer.rs
@@ -229,6 +229,7 @@ impl<'a> Serializer<'a> {
self.output.push_str(&tag.output_name);
let mut preserve = false;
+ let mut unquoted = false;
for attribute in &tag.attributes {
// A configured prefix protects an attribute from normalization;
// the prefix itself is omitted from rendered output.
@@ -265,6 +266,7 @@ impl<'a> Serializer<'a> {
self.output.push(' ');
self.output.push_str(output_name);
let Some(value) = &attribute.value else {
+ unquoted = false;
continue;
};
@@ -274,6 +276,7 @@ impl<'a> Serializer<'a> {
|| (self.options.reduce_boolean_attributes
&& is_boolean_attribute(&tag.name, name))
{
+ unquoted = false;
continue;
}
@@ -285,7 +288,7 @@ impl<'a> Serializer<'a> {
} else {
value.decoded.as_str()
};
- serialize_attribute_value(
+ unquoted = serialize_attribute_value(
&mut self.output,
value,
self.options.remove_optional_attribute_quotes,
@@ -294,6 +297,11 @@ impl<'a> Serializer<'a> {
}
if self_closing && !is_void(&tag.name) {
+ // Without whitespace, the solidus is part of an unquoted value,
+ // so the HTML parser does not acknowledge the self-closing flag.
+ if unquoted {
+ self.output.push(' ');
+ }
self.output.push_str("/>");
} else {
self.output.push('>');
diff --git a/crates/zensical/src/compat/mkdocs/plugin/minify/html/syntax.rs b/crates/zensical/src/compat/mkdocs/plugin/minify/html/syntax.rs
index ec63c8c..7a04eed 100644
--- a/crates/zensical/src/compat/mkdocs/plugin/minify/html/syntax.rs
+++ b/crates/zensical/src/compat/mkdocs/plugin/minify/html/syntax.rs
@@ -148,17 +148,19 @@ pub fn escape_text(output: &mut String, value: &str) {
}
/// Serializes an attribute value with minimal safe quoting.
+///
+/// Returns whether the serialized value is unquoted.
pub fn serialize_attribute_value(
output: &mut String, value: &str, remove_quotes: bool,
preserve_references: bool,
-) {
+) -> bool {
if remove_quotes && !value.is_empty() && value.chars().all(is_unquoted) {
if preserve_references {
output.push_str(value);
} else {
escape_ampersands(output, value);
}
- return;
+ return true;
}
let single = value.matches('\'').count();
@@ -174,6 +176,7 @@ pub fn serialize_attribute_value(
}
}
output.push(quote);
+ false
}
/// Escapes ampersands in an attribute value.