1. Purpose
This page describes Canon’s HTML format support, including automatic HTML4/HTML5/XHTML detection, browser rendering simulation, and HTML-specific features.
2. Canonicalization
Canon supports HTML 4, HTML5, and XHTML with automatic format detection.
Key features:
-
Automatic HTML vs XHTML detection
-
HTML5 parser for HTML input regardless of declared version (HTML4 and HTML5 share the same content model and parsing whitespace rules — see HTML4 / HTML5 parity)
-
XML parser for XHTML
-
Consistent attribute ordering
-
Whitespace normalization
-
Comment handling in
<style>and<script>tags
html = <<~HTML
<!DOCTYPE html>
<html>
<body>
<div class="foo" id="bar">
Content
</div>
</body>
</html>
HTML
Canon.format(html, :html)
# => Normalized structure with consistent formatting
3. Format defaults
| Dimension | Default Behavior |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Default diff mode: :by_line (line-based diff)
4. Match profiles for HTML
Canon provides predefined profiles optimized for HTML documents. Each profile configures preprocessing, match options, diff algorithm, and formatting.
4.1. strict profile
Purpose: Character-perfect HTML matching
Configuration:
{
preprocessing: :none,
diff_algorithm: :dom,
diff_mode: :by_line, # Line-based diff output (HTML default)
match: {
text_content: :strict,
structural_whitespace: :strict,
attribute_whitespace: :strict,
attribute_order: :strict,
attribute_values: :strict,
comments: :strict
}
}
Use when: Testing exact HTML formatter output, verifying HTML formatting compliance.
4.2. rendered profile
Purpose: Browser-rendered equivalence (most common for HTML)
Configuration:
{
preprocessing: :none,
diff_algorithm: :dom,
diff_mode: :by_line,
match: {
text_content: :normalize,
structural_whitespace: :normalize,
attribute_whitespace: :normalize,
attribute_order: :ignore, # HTML attributes are unordered
attribute_values: :strict,
comments: :ignore
}
}
Use when: Comparing HTML as browsers render it, testing web page output, ignoring formatting that doesn’t affect display. This is the recommended profile for most HTML comparisons.
4.3. spec_friendly profile
Purpose: Test-friendly comparison for RSpec
Configuration:
{
preprocessing: :normalize,
diff_algorithm: :dom,
diff_mode: :by_object, # Tree-based for better test output
match: {
text_content: :normalize,
structural_whitespace: :ignore,
attribute_whitespace: :normalize,
attribute_order: :ignore,
attribute_values: :strict,
comments: :ignore
}
}
Use when: Writing RSpec tests for HTML generation, testing semantic HTML correctness.
4.4. content_only profile
Purpose: Maximum tolerance - only structure matters
Configuration:
{
preprocessing: :normalize,
diff_algorithm: :dom,
diff_mode: :by_object,
match: {
text_content: :normalize,
structural_whitespace: :ignore,
attribute_whitespace: :ignore,
attribute_order: :ignore,
attribute_values: :ignore,
comments: :ignore
}
}
Use when: Only HTML structure needs to match, maximum flexibility for all formatting and attribute differences.
5. HTML-specific features
5.1. Format detection
Automatically detects HTML5, HTML4, or XHTML based on DOCTYPE and structure.
<!-- HTML5 detected -->
<!DOCTYPE html>
<html>...</html>
<!-- HTML4 detected -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>...</html>
<!-- XHTML detected -->
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN">
<html xmlns="http://www.w3.org/1999/xhtml">...</html>
5.2. HTML4 / HTML5 parity
be_html4_equivalent_to and be_html5_equivalent_to apply the same whitespace-sensitivity rules. Whitespace sensitivity is a property of HTML’s content model and is identical across the two HTML versions, so any input that compares equivalent under one matcher must compare equivalent under the other.
Internally, both matchers parse input via Nokogiri::HTML5.fragment. (Earlier releases routed :html and :html4 through Nokogiri::XML.fragment, which silently applied XML whitespace rules — meaning be_html4_equivalent_to could reject inputs that be_html5_equivalent_to correctly accepted.) See https://github.com/lutaml/canon/issues/118 for the full background.
5.3. Whitespace handling
HTML whitespace is collapsed per CSS rendering rules. Empty text nodes between elements are removed. Whitespace-only text between two adjacent inline elements (<span>A</span> <span>B</span>) is preserved because it renders as a visible space; whitespace at a block boundary (between an inline element and a block element, or between two block siblings) is collapsed.
<!-- Before -->
<div>
<p>Hello world</p>
<p>Second paragraph</p>
</div>
<!-- After normalization (with normalize) -->
<div>
<p>Hello world</p>
<p>Second paragraph</p>
</div>
Multiple spaces within text content are collapsed to single spaces when text_content: :normalize is used.
5.3.1. Fixture-ready pretty-print and structural whitespace
When using Canon::PrettyPrinter::Html with fixture_ready: true (the mode
used by the diff pipeline’s PRETTY-PRINTED INPUTS section), Canon strips
stray structural whitespace before formatting. Real-world HTML5 input from
upstream pipelines often carries whitespace-only text nodes between block-level
siblings (<body> → <div>, <br>, <div>, …). libxml’s FORMAT flag
treats any element with a non-whitespace-only text child as mixed content and
refuses to indent its children — producing a single-line blob instead of a
readable tree.
The fixture-ready mode removes whitespace-only text nodes from parents that
are purely structural (no real text content) and are not whitespace-preserving
elements (<pre>, <script>, <style>, <textarea>). Mixed-content runs
like <p>foo <em>bar</em> baz</p> are left untouched so that significant
inline whitespace is preserved.
5.4. Attribute order
HTML attributes are inherently unordered per the HTML specification, so default is :ignore.
<!-- These are always equivalent for HTML -->
<input type="text" id="name" class="form-control">
<input class="form-control" id="name" type="text">
The HTML specification states that attribute order has no meaning, so Canon ignores attribute order by default for HTML.
5.5. Special tags
Comments in <style> and <script> tags are normalized specially to handle CSS/JavaScript syntax.
<style>
/* CSS comments preserved */
body { margin: 0; }
</style>
<script>
// JavaScript comments preserved
console.log("test");
</script>
Canon recognizes that <style> and <script> tags contain non-HTML content and handles them appropriately.
5.6. Class attribute normalization
The HTML class attribute contains space-separated class names, making normalization particularly useful.
<!-- These are equivalent with attribute_whitespace: :normalize -->
<div class="btn primary active">Click</div>
<div class="btn primary active">Click</div>
Multiple spaces between class names are normalized to single spaces.
6. Usage examples
6.1. Basic HTML comparison
html1 = File.read("page1.html")
html2 = File.read("page2.html")
Canon::Comparison.equivalent?(html1, html2,
match_profile: :rendered
)
6.2. Test-friendly HTML comparison
expect(actual_html).to be_html_equivalent_to(expected_html)
.with_profile(:rendered)
7. HTML vs XHTML
Canon handles HTML and XHTML differently:
7.1. HTML (HTML4/HTML5)
-
Uses HTML parser (more lenient)
-
Attribute order ignored by default
-
Whitespace normalized by default
-
Comments ignored by default
7.2. XHTML
-
Uses XML parser (stricter)
-
Follows XML rules
-
Can use XML-specific features
-
Namespace-aware
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>XHTML Document</title>
</head>
<body>
<p>Content</p>
</body>
</html>
XHTML is treated as XML and follows stricter rules.
8. See also
-
Comparison Pipeline - Understanding the 4 layers
-
Match Options - All matching options
-
Choosing Configuration - Decision guide
-
Format Support - Overview of all formats
-
XML Format - XML-specific features
-
JSON Format - JSON-specific features