1. Purpose
Canon provides a two-tier verbose output architecture for debugging comparison failures. This document explains the output structure, when to use each tier, and implementation details.
2. Overview
Canon’s verbose mode enhances diff output with configuration tables and summaries:
-
Diffs: Always shown when files differ (default behavior)
-
Configuration Tables: Shown with
--verboseflag orCANON_VERBOSE=1 -
Semantic Diff Report: Always shown when differences exist in verbose mode
The --verbose flag adds diagnostic information, not gates diff display.
3. Architecture
The output architecture follows a clear three-tier structure:
╔═════════════════════════════════════════════════════════════════════╗
║ CANON DIFF OUTPUT ARCHITECTURE ║
╚═════════════════════════════════════════════════════════════════════╝
When files differ:
┌─────────────────────────────────────────────────────────────────────┐
│ TIER 1: Configuration Tables (with --verbose flag) │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Match Options Table (if CANON_VERBOSE=1) │ │
│ │ • Shows preprocessing behavior │ │
│ │ • Shows dimension behaviors (strict/normalize/ignore) │ │
│ │ • Explains what each setting means │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Formatter Options Table (if CANON_VERBOSE=1) │ │
│ │ • Shows mode (by_line vs by_object) │ │
│ │ • Shows context_lines, diff_grouping_lines │ │
│ │ • Shows show_diffs filter setting │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Comparison Result Summary (if CANON_VERBOSE=1) │ │
│ │ • Equivalent? (YES/NO) │ │
│ │ • Normative/Informative/Total diff counts │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ TIER 2: Semantic Diff Report (if --verbose and diffs exist) │
│ │
│ For each difference: │
│ • XPath location (e.g., /html/body/div/table/pre/text) │
│ • Dimension classification (attribute_presence, text_content) │
│ • Specific changes (Added: +xmlns:v, +xmlns:o) │
│ • Normative/Informative status │
│ • Dimension-specific formatting │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ TIER 3: Detailed Diff (ALWAYS when files differ) │
│ │
│ Either: │
│ • Line-by-line diff (for HTML, or with --diff-mode by_line) │
│ • Object tree diff (for XML/JSON/YAML, or with --diff-mode │
│ by_object) │
└─────────────────────────────────────────────────────────────────────┘
4. Semantic diff report
4.1. Purpose
The Semantic Diff Report is the core verbose output, always shown when differences exist. It provides dimension-specific, actionable details for each difference.
Unlike the detailed diff (which shows every changed line), the Semantic Diff Report shows a high-level summary of WHAT changed and WHY it matters.
4.2. Output format
======================================================================
SEMANTIC DIFF REPORT (1 difference)
======================================================================
🔍 DIFFERENCE #1/1 [NORMATIVE]
──────────────────────────────────────────────────────────────────────
Dimension: attribute_presence
Location: /html
⊖ Expected (File 1):
<html> with 2 attributes: lang, xmlns:epub
⊕ Actual (File 2):
<html> with 6 attributes: lang, xmlns:epub, xmlns:m, xmlns:o,
xmlns:v, xmlns:w
✨ Changes:
Added: +xmlns:m, +xmlns:o, +xmlns:v, +xmlns:w
======================================================================
4.3. Format structure
Each difference displays:
-
Status indicator:
[NORMATIVE](green) or[INFORMATIVE](yellow) -
Dimension: Which aspect differs (colorized in magenta)
-
Location: XPath for XML/HTML, path for JSON/YAML (colorized in blue)
-
Reason: When the reason contains visualized whitespace characters (░), it is split across two lines with the before and after text vertically aligned for easier visual comparison:
Reason: Text: "░░term2░definition░░" vs.: "░term2░definition░"When no visualized whitespace is present, the reason remains a single line.
-
Expected section: What was in File 1 (red heading, bold)
-
Actual section: What was in File 2 (green heading, bold)
-
Changes summary: Actionable description of the difference (yellow, bold)
When both the Expected and Actual values are short (under 30 characters), they are rendered as compact single lines with aligned colons for succinctness:
⊖ Expected (File 1): "term2 definition"
⊕ Actual (File 2) : "term2 definition"
For longer values, they appear on separate lines with no blank line between them.
4.4. Dimension-specific formats
4.4.1. Attribute presence differences
For missing or extra attributes:
Dimension: attribute_presence
Location: /html/body/p
⊖ Expected: <p> with 2 attributes: id, lang
⊕ Actual: <p> with 4 attributes: id, lang, data-value, aria-label
✨ Changes: Added: +data-value, +aria-label
Shows:
-
Element name (
<p>) -
How many attributes each has
-
Which attributes were added (green with
+prefix) or removed (red with-prefix)
4.4.2. Attribute value differences
For differing attribute values:
Dimension: attribute_values
Location: /html/body/div
⊖ Expected: <div> class=" container fluid "
⊕ Actual: <div> class="container fluid"
✨ Changes: Whitespace normalization difference
Shows:
-
Which specific attribute differs (highlighted in cyan)
-
Exact values on both sides
-
Analysis: "Whitespace difference only", "Whitespace normalization difference", or "Value changed"
4.4.3. Text content differences
For text that differs:
Dimension: text_content
Location: /html/body/div/table/tbody/tr/td/pre/text
⊖ Expected: <text> "
puts \"Hello, world.\"
"
⊕ Actual: <text> "puts \"Hello, world.\" "
✨ Changes: ⚠️ Whitespace preserved (inside <pre>, <code>, etc. -
whitespace is significant)
Shows:
-
Text preview (truncated at 100 characters)
-
Special warning if inside
<pre>,<code>,<textarea>,<script>, or<style>elements (where whitespace is significant)
4.4.4. Structural whitespace differences
For whitespace-only differences (usually informative):
Dimension: structural_whitespace
Location: /root/p
⊖ Expected: <p> "hello␣␣world"
⊕ Actual: <p> "hello␣world"
✨ Changes: Whitespace-only difference (informative)
Shows:
-
Whitespace visualized:
␣for space,→for tab,↵for newline -
Marked as
[INFORMATIVE](yellow)
5. CANON VERBOSE mode
5.1. Purpose
CANON VERBOSE mode adds detailed option tables BEFORE the Semantic Diff Report. These tables help understand:
-
What match options are in effect
-
How the diff formatter is configured
-
Statistics about the comparison result
To enable, set the CANON_VERBOSE environment variable:
CANON_VERBOSE=1 bundle exec rspec spec/my_failing_spec.rb:123
5.2. Match options table
Shows preprocessing and dimension behaviors:
╭────────────────────────────────────────────────────────────────────╮
│ Match Options (HTML) │
├────────────────────┬───────────┬────────────────────────────────────┤
│ Dimension │ Behavior │ Meaning │
├────────────────────┼───────────┼────────────────────────────────────┤
│ preprocessing │ rendered │ As browser-rendered (compacted wh… │
│ text_content │ normalize │ Normalized then compared (normative) │
│ structural_whit… │ ignore │ Differences IGNORED (informative) │
│ attribute_presence │ strict │ Must match exactly (normative) │
│ attribute_values │ normalize │ Normalized then compared (normative) │
│ comments │ ignore │ Differences IGNORED (informative) │
╰────────────────────┴───────────┴────────────────────────────────────╯
Preprocessing behaviors:
-
:none- No preprocessing (compare as-is) -
:c14n- Canonicalize (XML C14N normalization) -
:normalize- Normalize (collapse whitespace, trim lines) -
:format- Pretty-format (consistent indentation) -
:rendered- As browser-rendered (compacted whitespace, to_html)
Dimension behaviors:
-
:ignore- Differences IGNORED (informative, won’t fail test) -
:strict- Must match exactly (normative, will fail test) -
:normalize- Normalized then compared (normative if different after normalization) -
:strip- Strip leading/trailing whitespace only -
:compact- Collapse whitespace runs to single space
5.3. Formatter options table
Shows diff formatting settings:
╭────────────────────────────────────────────────────────────────────╮
│ Formatter Options │
├─────────────────────┬─────────┬────────────────────────────────────┤
│ Option │ Value │ Impact │
├─────────────────────┼─────────┼────────────────────────────────────┤
│ mode │ by_line │ Line-by-line diff │
│ context_lines │ 3 │ 3 lines of context around diffs │
│ show_diffs │ all │ Show all diffs (normative + informative) │
╰─────────────────────┴─────────┴────────────────────────────────────╯
5.4. Comparison result summary
Shows diff statistics:
╭─────────────────────────────────────────────────────────────────────╮
│ Comparison Result Summary │
├────────────────┬─────────┬──────────────────────────────────────────┤
│ Equivalent? │ ✗ NO │ Documents have semantic differences │
│ Normative Diffs │ 1 diffs │ Semantic differences that matter │
│ Informative Diffs │ 0 │ Textual/formatting differences (ignored) │
│ Total Diffs │ 1 │ All differences found │
╰────────────────┴─────────┴──────────────────────────────────────────╯
6. Usage
6.1. Using in RSpec matchers
Verbose mode is activated by using verbose: true in the comparison:
result = Canon::Comparison::XmlComparator.equivalent?(
xml1,
xml2,
verbose: true
)
# Returns ComparisonResult object
# Semantic Diff Report shown if differences exist
With RSpec matchers, verbose mode is automatic on test failure:
# Semantic Diff Report automatically shown on failure
expect(actual_html).to be_html4_equivalent_to(expected_html)
To enable CANON VERBOSE tables:
CANON_VERBOSE=1 bundle exec rspec spec/my_spec.rb:123
7. Implementation
7.1. DiffDetailFormatter module
Location: lib/canon/diff_formatter/diff_detail_formatter.rb
Responsible for:
-
Formatting the Semantic Diff Report
-
Dispatching to dimension-specific formatters
-
Extracting XPath/JSON paths
-
Detecting whitespace-preserving elements (
<pre>,<code>, etc.) -
Colorizing output
Key methods:
-
format_report(differences)- Main entry point -
format_attribute_presence_details()- Format attribute presence diffs -
format_attribute_values_details()- Format attribute value diffs -
format_text_content_details()- Format text content diffs -
extract_xpath(node)- Extract XPath with safety limits -
inside_preserve_element?(node)- Detect whitespace preservation
7.2. DebugOutput module
Location: lib/canon/diff_formatter/debug_output.rb
Responsible for:
-
Rendering CANON VERBOSE option tables
-
Checking if
CANON_VERBOSE=1is set -
Formatting match options with descriptions
-
Formatting formatter options with impact
-
Formatting comparison summary statistics
Key methods:
-
verbose_tables_only()- Returns CANON VERBOSE tables or empty string -
format_match_options_table()- Render match options as table -
format_formatter_options_table()- Render formatter options as table -
format_comparison_summary()- Render result summary as table
7.3. DiffFormatter integration
Location: lib/canon/diff_formatter.rb
The format_comparison_result() method orchestrates output:
def format_comparison_result(comparison_result, expected, actual)
output = []
# 1. CANON VERBOSE tables (ONLY if CANON_VERBOSE=1)
output << DebugOutput.verbose_tables_only(...)
# 2. Semantic Diff Report (ALWAYS if diffs exist)
output << DiffDetailFormatter.format_report(...)
# 3. Detailed diff (ALWAYS)
output << format(differences, ...)
output.compact.join("\n")
end
This ensures the correct output order and separation of concerns.
8. See also
-
Diff Formatting - Diff output customization
-
Comparison Pipeline - 4-layer architecture
-
RSpec Matchers - Using verbose mode in tests
-
CLI - Command-line verbose mode