Class: Canon::Xml::ElementMatcher
- Inherits:
-
Object
- Object
- Canon::Xml::ElementMatcher
- Defined in:
- lib/canon/xml/element_matcher.rb
Overview
Matches XML elements semantically across two DOM trees
This class implements intelligent element matching for XML diffs. Instead of naive line-by-line comparison, it semantically matches elements across documents using identity attributes and structural position.
Matching Strategy
Elements are matched in two passes:
- Identity attribute matching: Elements with same identity attribute values are matched (e.g., id="foo" matches id="foo")
- Position-based matching: Remaining elements matched by name and document position
This allows detecting when elements:
- Move to different positions (matched by ID)
- Have content changes (matched, diff shows changes)
- Are added/deleted (no match found)
Identity Attributes
By default, these attributes identify elements:
- id
- ref
- name
- key
Custom identity attributes can be provided to the constructor.
Usage
matcher = ElementMatcher.new
root1 = Canon::Xml::DataModel.from_xml(xml1)
root2 = Canon::Xml::DataModel.from_xml(xml2)
matches = matcher.match_trees(root1, root2)
matches.each do |match|
case match.status
when :matched
# Elements found in both trees
when :deleted
# Element only in first tree
when :inserted
# Element only in second tree
end
end
Defined Under Namespace
Classes: MatchResult
Constant Summary collapse
- DEFAULT_IDENTITY_ATTRS =
Default attributes used to identify elements
%w[id ref name key class].freeze
- EMPTY_PATH =
[].freeze
Class Method Summary collapse
-
.positionally_paired?(elems1, elems2) ⇒ Boolean
Public: true when both lists pair positionally (equal length, equal name/ns/identity at every index).
Instance Method Summary collapse
-
#initialize(identity_attrs: DEFAULT_IDENTITY_ATTRS) ⇒ ElementMatcher
constructor
A new instance of ElementMatcher.
-
#match_children_only(children1, children2) ⇒ Array<MatchResult>
Match one level of children only — no descent into matched pairs.
-
#match_trees(root1, root2) ⇒ Array<MatchResult>
Match elements between two DOM trees.
-
#paired_positionally?(elems1, elems2) ⇒ Boolean
Instance form of .positionally_paired?.
Constructor Details
#initialize(identity_attrs: DEFAULT_IDENTITY_ATTRS) ⇒ ElementMatcher
Returns a new instance of ElementMatcher.
118 119 120 121 |
# File 'lib/canon/xml/element_matcher.rb', line 118 def initialize(identity_attrs: DEFAULT_IDENTITY_ATTRS) @identity_attrs = identity_attrs @matches = [] end |
Class Method Details
.positionally_paired?(elems1, elems2) ⇒ Boolean
Public: true when both lists pair positionally (equal length, equal name/ns/identity at every index). ChildComparison uses this to skip MatchResult allocation on the common same-structure path.
153 154 155 156 157 |
# File 'lib/canon/xml/element_matcher.rb', line 153 def self.positionally_paired?(elems1, elems2) return false if elems1.length != elems2.length || elems1.empty? new.paired_positionally?(elems1, elems2) end |
Instance Method Details
#match_children_only(children1, children2) ⇒ Array<MatchResult>
Match one level of children only — no descent into matched pairs. The comparator walks descendants itself (compare_nodes → ChildComparison per level), so the nested matches match_trees would produce below this level are discarded by its direct- children filter; matching them is wasted work.
143 144 145 146 147 |
# File 'lib/canon/xml/element_matcher.rb', line 143 def match_children_only(children1, children2) @matches = [] match_level(children1, children2, [], recursive: false) @matches end |
#match_trees(root1, root2) ⇒ Array<MatchResult>
Match elements between two DOM trees
128 129 130 131 132 |
# File 'lib/canon/xml/element_matcher.rb', line 128 def match_trees(root1, root2) @matches = [] match_children(root1.children, root2.children, []) @matches end |
#paired_positionally?(elems1, elems2) ⇒ Boolean
Instance form of .positionally_paired?
160 161 162 |
# File 'lib/canon/xml/element_matcher.rb', line 160 def paired_positionally?(elems1, elems2) pairwise_corresponding?(elems1, elems2) end |