1. Purpose
Configuration profiles bundle many settings into a single named preset, eliminating repetitive configuration blocks across multiple gems. Instead of 60+ lines of per-format settings, use one line:
Canon::Config.instance.profile = :metanorma
Profiles are defined in YAML files and support inheritance, so a debug variant can extend a base profile with only the differences.
2. Built-in profiles
| Profile | Description |
|---|---|
|
Standard Metanorma spec configuration. Sets preprocessing to |
|
Extends |
List all available profiles programmatically:
Canon::Config::ProfileLoader.available_profiles
# => [:metanorma, :metanorma_debug]
3. Element-level whitespace classification
The metanorma profile’s key feature is its element-level whitespace classification. This controls how whitespace differences within specific elements are treated:
Three-way classification:
-
Preserve (
:preserve) — Every whitespace character is significant. Use for elements where exact whitespace matters (like<pre>,<code>). -
Collapse (
:collapse) — Presence matters but whitespace form doesn’t." hello "equals"hello". Differences are formatting-only (informative). Use for elements like<p>,<li>,<td>in prose documents. -
Strip (
:strip) — Whitespace is structural noise, dropped entirely. The default for XML elements not in any list.
Metanorma profile element lists:
# In metanorma profile
Canon::Config.instance.profile = :metanorma
Canon::Config.instance.xml.match.collapse_whitespace_elements
# => ["p", "title", "name", "td", "th", "dt", "dd", "li", ...]
Canon::Config.instance.xml.match.preserve_whitespace_elements
# => ["body", "passthrough"]
How it works with text_content: :normalize:
# With metanorma profile: <p> is in collapse_whitespace_elements
Canon::Config.instance.profile = :metanorma
# These are EQUIVALENT (whitespace in <p> is formatting-only)
Canon::Comparison.equivalent?('<p> hello </p>', '<p>hello</p>')
# => true
# But <body> is in preserve_whitespace_elements — every character matters
# These are NOT EQUIVALENT (whitespace in <body> is normative)
Canon::Comparison.equivalent?('<body> hello </body>', '<body>hello</body>')
# => false
Why this matters:
In Metanorma/DocBook documents, elements like <p>, <li>, <td> contain prose
where whitespace formatting (extra spaces, line breaks) is irrelevant. But <body>
or <passthrough> contain code or exact whitespace that matters.
Without element-level classification, you’d have to choose:
- text_content: :normalize — ignores ALL whitespace, too permissive
- text_content: :strict — requires exact match everywhere, too strict
Element-level classification gives you fine-grained control.
4. Usage
4.1. Programmatic (Ruby API)
Use a Symbol for built-in profiles and a String for file paths:
# Built-in profile (Symbol)
Canon::Config.instance.profile = :metanorma
# Local YAML file (String)
Canon::Config.instance.profile = "/path/to/my_profile.yml"
Canon::Config.instance.profile = "~/my_canon_profile.yml"
Canon::Config.instance.profile = "config/canon_profile.yml"
# Or in a configure block
Canon::Config.configure do |cfg|
cfg.profile = :metanorma
# Override individual settings after profile if needed
cfg.xml.diff.verbose_diff = true
end
# Clear the profile (revert to defaults + programmatic values)
Canon::Config.instance.profile = nil
The type of the value determines how it is resolved:
Symbols are looked up as built-in profile names;
Strings are treated as file paths (with ~ expansion and relative
path resolution against the working directory).
|
Local YAML files can inherit from built-in profiles (see Profile inheritance).
4.2. Environment variable
Set CANON_CONFIG_PROFILE to apply a profile automatically on
initialization:
# Built-in profile
CANON_CONFIG_PROFILE=metanorma bundle exec rspec
# File path
CANON_CONFIG_PROFILE=~/my_profile.yml bundle exec rspec
CANON_CONFIG_PROFILE is distinct from CANON_PROFILE, which
controls the match profile (comparison behavior). The config profile
controls all settings at once.
|
5. Priority chain
With profiles, the resolution chain becomes four layers:
+------------------------------------+
| 1. Environment Variables | <- Highest Priority
| (CANON_XML_DIFF_ALGORITHM) |
+------------------------------------+
| overrides
+------------------------------------+
| 2. Programmatic Configuration |
| (config.xml.diff.algorithm=) |
+------------------------------------+
| overrides
+------------------------------------+
| 3. Profile Values |
| (from YAML profile file) |
+------------------------------------+
| overrides
+------------------------------------+
| 4. Default Values | <- Lowest Priority
| (defined in Canon::Config) |
+------------------------------------+
This means:
-
ENV variables always win (useful for CI overrides)
-
Programmatic setter calls override profile values
-
Profile values override built-in defaults
-
Clearing the profile (
cfg.profile = nil) removes only layer 3
6. Profile inheritance
A profile can inherit from another using the inherits key:
name: my_debug
inherits: metanorma
shared:
diff:
verbose_diff: true
show_prettyprint_received: true
Inheritance rules:
-
Parent values are loaded first, then child values are deep-merged on top
-
Hashes are merged recursively (child keys override parent keys)
-
Arrays are replaced entirely (not concatenated)
-
Single-parent inheritance only
-
Cycle detection prevents infinite loops
-
Local files can inherit from built-in profiles by name
7. Creating custom profiles
7.1. YAML file format
---
name: my_profile (1)
description: My custom config (2)
inherits: metanorma (3)
shared: (4)
preprocessing: format
match:
profile: spec_friendly
diff:
algorithm: dom
context_lines: 5
verbose_diff: false
formats: (5)
xml:
match:
collapse_whitespace_elements:
- p
- title
- td
preserve_whitespace_elements:
- body
- passthrough
html:
diff:
show_raw_inputs: true
| 1 | Profile name (metadata) |
| 2 | Description (metadata) |
| 3 | Optional: inherit from another profile (name or path) |
| 4 | shared settings apply to all formats (xml, html, json, yaml, string) |
| 5 | formats.<name> settings override shared for that specific format |
7.2. Attribute mapping
Profile YAML keys map directly to Canon configuration accessors:
| YAML path | Ruby equivalent |
|---|---|
|
|
|
|
|
|
|
|
All DiffConfig and MatchConfig attributes documented in
Options Across Interfaces
are supported.
8. See also
-
Override System — ENV variable priority
-
Match Options — match profile presets (
:strict,:spec_friendly, etc.) -
Choosing Configuration — decision guide