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

Example 1. HTML canonicalization example
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

text_content

:normalize

structural_whitespace

:normalize

attribute_whitespace

:normalize

attribute_order

:ignore

attribute_values

:strict

comments

:ignore

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.

Example 2. Format detection examples
<!-- 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.

Example 3. Whitespace handling example
<!-- 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.

Example 4. Attribute order example
<!-- 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.

Example 5. Special tag handling
<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.

Example 6. Class attribute example
<!-- 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)

6.3. Using HTML comparator directly

Canon::Comparison::HtmlComparator.equivalent?(html1, html2,
  match_profile: :rendered
)

6.4. CLI usage

# Basic comparison with rendered profile
canon diff page1.html page2.html \
  --match-profile rendered \
  --verbose

# Strict HTML comparison
canon diff file1.html file2.html \
  --match-profile strict \
  --verbose

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

Example 7. XHTML example
<?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