feat(html/codegen): Add option for tag omission of self closing void elements (#4971)

This commit is contained in:
Alexander Akait 2022-06-16 07:50:56 +03:00 committed by GitHub
parent b46878625c
commit d07ab2cb91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 154 additions and 49 deletions

View File

@ -26,6 +26,10 @@ pub struct CodegenConfig {
pub scripting_enabled: bool,
/// Should be used only for `DocumentFragment` code generation
pub context_element: Option<Element>,
/// By default `true` when `minify` enabled, otherwise `false`
pub tag_omission: Option<bool>,
/// By default `false` when `minify` enabled, otherwise `true`
pub self_closing_void_elements: Option<bool>,
}
enum TagOmissionParent<'a> {
@ -44,6 +48,8 @@ where
ctx: Ctx,
// For legacy `<plaintext>`
is_plaintext: bool,
tag_omission: bool,
self_closing_void_elements: bool,
}
impl<W> CodeGenerator<W>
@ -51,17 +57,22 @@ where
W: HtmlWriter,
{
pub fn new(wr: W, config: CodegenConfig) -> Self {
let tag_omission = config.tag_omission.unwrap_or(config.minify);
let self_closing_void_elements = config.tag_omission.unwrap_or(!config.minify);
CodeGenerator {
wr,
config,
ctx: Default::default(),
is_plaintext: false,
tag_omission,
self_closing_void_elements,
}
}
#[emitter]
fn emit_document(&mut self, n: &Document) -> Result {
if self.config.minify {
if self.tag_omission {
self.emit_list_for_tag_omission(TagOmissionParent::Document(n))?;
} else {
self.emit_list(&n.children, ListFormat::NotDelimited)?;
@ -76,7 +87,7 @@ where
Default::default()
};
if self.config.minify {
if self.tag_omission {
self.with_ctx(ctx)
.emit_list_for_tag_omission(TagOmissionParent::DocumentFragment(n))?;
} else {
@ -174,7 +185,7 @@ where
}
let has_attributes = !n.attributes.is_empty();
let can_omit_start_tag = self.config.minify
let can_omit_start_tag = self.tag_omission
&& !has_attributes
&& n.namespace == Namespace::HTML
&& match &*n.tag_name {
@ -327,10 +338,10 @@ where
}
if (matches!(n.namespace, Namespace::SVG | Namespace::MATHML) && is_void_element)
|| (!self.config.minify
&& matches!(n.namespace, Namespace::HTML)
|| (self.self_closing_void_elements
&& n.is_self_closing
&& is_void_element)
&& is_void_element
&& matches!(n.namespace, Namespace::HTML))
{
if self.config.minify {
let need_space = match n.attributes.last() {
@ -387,7 +398,7 @@ where
}
}
if self.config.minify {
if self.tag_omission {
self.with_ctx(ctx)
.emit_list_for_tag_omission(TagOmissionParent::Element(n))?;
} else {
@ -397,7 +408,7 @@ where
}
let can_omit_end_tag = self.is_plaintext
|| (self.config.minify
|| (self.tag_omission
&& n.namespace == Namespace::HTML
&& match &*n.tag_name {
// Tag omission in text/html:

View File

@ -391,6 +391,37 @@ fn test_document_fragment(input: PathBuf) {
);
}
#[testing::fixture("tests/options/self_closing_void_elements/true/**/input.html")]
fn test_self_closing_void_elements_true(input: PathBuf) {
print_document(
&input,
None,
None,
Some(CodegenConfig {
scripting_enabled: false,
minify: false,
tag_omission: Some(false),
self_closing_void_elements: Some(true),
..Default::default()
}),
);
}
#[testing::fixture("tests/options/self_closing_void_elements/false/**/input.html")]
fn test_self_closing_void_elements_false(input: PathBuf) {
print_document(
&input,
None,
None,
Some(CodegenConfig {
scripting_enabled: false,
minify: false,
self_closing_void_elements: Some(false),
..Default::default()
}),
);
}
#[testing::fixture("tests/options/indent_type/**/input.html")]
fn test_indent_type_option(input: PathBuf) {
print_document(
@ -415,6 +446,19 @@ fn parser_verify(input: PathBuf) {
Some(CodegenConfig {
scripting_enabled: false,
minify: true,
tag_omission: Some(false),
..Default::default()
}),
false,
);
verify_document(
&input,
None,
None,
Some(CodegenConfig {
scripting_enabled: false,
minify: true,
tag_omission: Some(true),
..Default::default()
}),
false,
@ -440,6 +484,19 @@ fn parser_recovery_verify(input: PathBuf) {
Some(CodegenConfig {
scripting_enabled: false,
minify: true,
tag_omission: Some(false),
..Default::default()
}),
true,
);
verify_document(
&input,
None,
None,
Some(CodegenConfig {
scripting_enabled: false,
minify: true,
tag_omission: Some(true),
..Default::default()
}),
true,
@ -522,6 +579,13 @@ fn html5lib_tests_verify(input: PathBuf) {
};
let minified_codegen_config = CodegenConfig {
minify: true,
tag_omission: Some(true),
scripting_enabled,
..Default::default()
};
let minified_codegen_config_no_tag_omission = CodegenConfig {
minify: true,
tag_omission: Some(false),
scripting_enabled,
..Default::default()
};
@ -575,12 +639,20 @@ fn html5lib_tests_verify(input: PathBuf) {
);
verify_document_fragment(
&input,
context_element,
context_element.clone(),
Some(parser_config),
None,
Some(minified_codegen_config),
true,
);
verify_document_fragment(
&input,
context_element,
Some(parser_config),
None,
Some(minified_codegen_config_no_tag_omission),
true,
);
} else {
verify_document(
&input,
@ -590,6 +662,14 @@ fn html5lib_tests_verify(input: PathBuf) {
true,
);
verify_document(
&input,
Some(parser_config),
None,
Some(minified_codegen_config_no_tag_omission),
true,
);
let relative_path = input.to_string_lossy().replace('-', "_").replace('\\', "/");
if !IGNORE_TAG_OMISSION

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>

View File

@ -0,0 +1,14 @@
<link rel="stylesheet" href="https://www.google.com/css/maia.css"
type="text/css">
<!-- following are self closing tags --><!-- have no content or child --><html>
<head></head><body><br />
<hr />
<input type="text" />
<img src="#URL" alt="image" />
<area shape="rect" coords="0,0,100,100" href="#URL" />
<!-- SVG and MathML namespace should always have `/` at the end -->
<svg><test/></svg>
<math><test/></math>
</body></html>

View File

@ -0,0 +1,14 @@
<html>
<head><link rel="stylesheet" href="https://www.google.com/css/maia.css" type="text/css">
<!-- following are self closing tags --><!-- have no content or child -->
</head><body><br />
<hr />
<input type="text" />
<img src="#URL" alt="image" />
<area shape="rect" coords="0,0,100,100" href="#URL" />
<!-- SVG and MathML namespace should always have `/` at the end -->
<svg><test /></svg>
<math><test /></math>
</body></html>

View File

@ -0,0 +1,13 @@
<link rel="stylesheet" href="https://www.google.com/css/maia.css"
type="text/css">
<!-- following are self closing tags --><!-- have no content or child --><html>
<head></head><body><br />
<hr />
<input type="text" />
<img src="#URL" alt="image" />
<area shape="rect" coords="0,0,100,100" href="#URL" />
<svg><test/></svg>
<math><test/></math>
</body></html>

View File

@ -0,0 +1,13 @@
<html>
<head><link rel="stylesheet" href="https://www.google.com/css/maia.css" type="text/css">
<!-- following are self closing tags --><!-- have no content or child -->
</head><body><br>
<hr>
<input type="text">
<img src="#URL" alt="image">
<area shape="rect" coords="0,0,100,100" href="#URL">
<svg><test /></svg>
<math><test /></math>
</body></html>