canon 0.3.58 → 0.3.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1b3bba3210fd3ca95a66a4a3e439f6b9e1a0f95b30ddf4d90f4069a2b5a6d8b
4
- data.tar.gz: fadf6bdf8fb892fbb3998d53754f599b17fac80b099936d77a7dae0881e4f5c5
3
+ metadata.gz: f1cbc73f1d667885f1f989a9534d8123dbcc67ae7eda250cc24cba00e7ad24a7
4
+ data.tar.gz: b9a27d00c02f533c6679237e190eb7f6bc6532fe0f945957bdc5d521bb84217b
5
5
  SHA512:
6
- metadata.gz: e551b02b98c802c3d2593c1f684c89d271b223892fb32dbd20f186fe6cb13e06d18bd079839298325eb1d8a798b962876af9c95177763268ae291fc503828552
7
- data.tar.gz: 8485c2e7e3049386ff977fca9c4162b44d439a2c218067f3aefe4a3b596d0f351b60c0c18a7f9fd0d5bcbf77192a0525c84dac7c1b35fe7f92369737f6dc2308
6
+ metadata.gz: b060895cd58c7321db7d0640a02ca166f3efb9b5cb4c90d5fafb7d615b3faf6c73f4e29d42533d56ac9fd393e625bac473d9cd122273dc81b3598b0be76b86f9
7
+ data.tar.gz: 4b160273200d229151e61842bc994cd8e1d23f495107f495edde383b2fdccda2e9de150a65160d34c5e3177a7bf236282a6e1bb675c72b2bf667deb200e3dab4
@@ -196,7 +196,7 @@ module Canon
196
196
  return false unless Canon::Comparison::NodeInspector.element_node?(element)
197
197
 
198
198
  # List of HTML elements where whitespace is semantically significant
199
- whitespace_sensitive_tags = %w[pre code textarea script style]
199
+ whitespace_sensitive_tags = Core::TreeNode::WHITESPACE_SENSITIVE_TAGS
200
200
  whitespace_sensitive_tags.include?(element.name.downcase)
201
201
  end
202
202
 
@@ -215,9 +215,10 @@ module Canon
215
215
 
216
216
  # Check prefix closure constraint
217
217
  # If ancestors are matched, they must be matched to each other
218
- node1.ancestors.each_with_index do |anc1, idx|
218
+ ancestors1 = node1.ancestors
219
+ anc2_ancestors = node2.ancestors
220
+ ancestors1.each_with_index do |anc1, idx|
219
221
  # Get corresponding ancestor in tree2
220
- anc2_ancestors = node2.ancestors
221
222
  return false if idx >= anc2_ancestors.size
222
223
 
223
224
  anc2 = anc2_ancestors[idx]
@@ -81,34 +81,7 @@ module Canon
81
81
  #
82
82
  # @return [Array<String>] Path components
83
83
  def compute_path
84
- # Build path from root to node
85
- ancestors = @node.ancestors.reverse
86
- components = ancestors.map do |ancestor|
87
- path_component(ancestor)
88
- end
89
-
90
- # Add the node itself
91
- components << path_component(@node)
92
-
93
- components
94
- end
95
-
96
- # Get path component for a node
97
- #
98
- # @param node [TreeNode] Node to get component for
99
- # @return [String]
100
- def path_component(node)
101
- # For element nodes: use label with sorted attributes
102
- # For text nodes: use "#text"
103
- # CRITICAL: Text nodes should use "#text" not "text"
104
- # Check the label - actual text nodes have no label or label == "text"
105
- label_str = node.label.to_s.downcase
106
- if node.label.nil? || label_str.empty? || label_str == "#text" || label_str == "text"
107
- "#text"
108
- else
109
- # Memoized on the node — see TreeNode#signature_component.
110
- node.signature_component(include_attributes: @include_attributes)
111
- end
84
+ @node.signature_path(include_attributes: @include_attributes)
112
85
  end
113
86
 
114
87
  # Compute signature string from path
@@ -66,6 +66,20 @@ module Canon
66
66
  end
67
67
  end
68
68
 
69
+ # Signature path components from root to this node, built by
70
+ # parent chaining (see NodeSignature). Memoized per variant —
71
+ # signatures previously re-walked the ancestor chain per node.
72
+ #
73
+ # @param include_attributes [Boolean]
74
+ # @return [Array<String>]
75
+ def signature_path(include_attributes: true)
76
+ if include_attributes
77
+ @signature_path_attrs ||= build_signature_path(true)
78
+ else
79
+ @signature_path ||= build_signature_path(false)
80
+ end
81
+ end
82
+
69
83
  # Check if this is a leaf node (no children)
70
84
  #
71
85
  # @return [Boolean]
@@ -73,17 +87,40 @@ module Canon
73
87
  children.empty?
74
88
  end
75
89
 
76
- # Elements where whitespace is semantically significant; their
77
- # text value participates in the signature (see NodeSignature).
90
+ # Elements where whitespace is semantically significant (HTML):
91
+ # their text value participates in signatures and matching.
92
+ # Single source for the tree_diff components.
78
93
  WHITESPACE_SENSITIVE_TAGS = %w[pre code textarea script style].freeze
79
94
 
95
+ def build_signature_path(include_attributes)
96
+ component = signature_component(include_attributes: include_attributes)
97
+ if parent
98
+ parent.signature_path(include_attributes: include_attributes) + [component]
99
+ else
100
+ [component]
101
+ end
102
+ end
103
+
80
104
  def build_signature_component(include_attributes)
105
+ label_str = label.to_s.downcase
106
+ if label.nil? || label_str.empty? || label_str == "#text" || label_str == "text"
107
+ return "#text"
108
+ end
109
+
81
110
  component = label.to_s
82
111
 
83
112
  # Sorted attributes distinguish same-label nodes while
84
- # ignoring attribute order.
113
+ # ignoring attribute order. Built directly into the
114
+ # component — no intermediate sorted/map/join arrays.
85
115
  if include_attributes && !attributes.empty?
86
- component += "{#{attributes.sort.map { |k, v| "#{k}=#{v}" }.join(',')}}" # rubocop:disable Style/StringConcatenation
116
+ component += "{"
117
+ first = true
118
+ attributes.sort.each do |k, v|
119
+ component << "," unless first
120
+ first = false
121
+ component << k.to_s << "=" << v.to_s
122
+ end
123
+ component << "}"
87
124
  end
88
125
 
89
126
  if include_attributes &&
@@ -95,7 +132,7 @@ module Canon
95
132
 
96
133
  component
97
134
  end
98
- private :build_signature_component
135
+ private :build_signature_component, :build_signature_path
99
136
 
100
137
  # Check if this is a text node (leaf with value)
101
138
  #
@@ -137,10 +174,14 @@ module Canon
137
174
  #
138
175
  # @return [Array<TreeNode>]
139
176
  def descendants
177
+ # Iterative pre-order walk — the recursive form allocated an
178
+ # intermediate array per level.
140
179
  result = []
141
- children.each do |child|
142
- result << child
143
- result.concat(child.descendants)
180
+ stack = children.reverse_each.to_a
181
+ until stack.empty?
182
+ node = stack.pop
183
+ result << node
184
+ stack.concat(node.children.reverse_each.to_a) if node.children.any?
144
185
  end
145
186
  result
146
187
  end
@@ -118,11 +118,19 @@ module Canon
118
118
 
119
119
  def subtrees_match?(node1, node2)
120
120
  return false unless nodes_match?(node1, node2)
121
- return false unless node1.children.size == node2.children.size
122
121
 
123
- node1.children.zip(node2.children).all? do |child1, child2|
124
- subtrees_match?(child1, child2)
122
+ children1 = node1.children
123
+ children2 = node2.children
124
+ return false unless children1.size == children2.size
125
+
126
+ index = 0
127
+ while index < children1.size
128
+ return false unless subtrees_match?(children1[index],
129
+ children2[index])
130
+
131
+ index += 1
125
132
  end
133
+ true
126
134
  end
127
135
 
128
136
  def nodes_match?(node1, node2)
@@ -142,6 +150,11 @@ module Canon
142
150
  return true if (text1.nil? || text1.empty?) && (text2.nil? || text2.empty?)
143
151
  return false if (text1.nil? || text1.empty?) || (text2.nil? || text2.empty?)
144
152
 
153
+ # Identical raw text is equivalent under every mode — skips
154
+ # the gsub normalization for the overwhelmingly common
155
+ # matched-leaf case.
156
+ return true if text1 == text2
157
+
145
158
  norm1 = normalize_text(text1)
146
159
  norm2 = normalize_text(text2)
147
160
  return true if norm1.empty? && norm2.empty?
@@ -158,7 +158,7 @@ module Canon
158
158
  return false unless node
159
159
 
160
160
  # List of HTML elements where whitespace is semantically significant
161
- whitespace_sensitive_tags = %w[pre code textarea script style]
161
+ whitespace_sensitive_tags = Core::TreeNode::WHITESPACE_SENSITIVE_TAGS
162
162
 
163
163
  # Check if this node is whitespace-sensitive
164
164
  if node.is_a?(Core::TreeNode)
@@ -211,7 +211,7 @@ module Canon
211
211
  return false unless node
212
212
 
213
213
  # List of HTML elements where whitespace is semantically significant
214
- whitespace_sensitive_tags = %w[pre code textarea script style]
214
+ whitespace_sensitive_tags = Core::TreeNode::WHITESPACE_SENSITIVE_TAGS
215
215
 
216
216
  # Check if this node is whitespace-sensitive
217
217
  if node.is_a?(Core::TreeNode)
@@ -205,8 +205,10 @@ module Canon
205
205
  attrs1 = node1.attributes
206
206
  attrs2 = node2.attributes
207
207
 
208
- # Check if attribute values differ (ignoring order)
209
- if attrs1.sort.to_h != attrs2.sort.to_h
208
+ # Check if attribute values differ (ignoring order).
209
+ # Identical raw hashes (order included) are trivially equal
210
+ # after sorting — the overwhelmingly common matched case.
211
+ if attrs1 != attrs2 && attrs1.sort.to_h != attrs2.sort.to_h
210
212
  # Actual attribute value differences
211
213
  changes[:attributes] = {
212
214
  old: attrs1,
@@ -611,15 +613,12 @@ module Canon
611
613
  def whitespace_sensitive?(node)
612
614
  return false unless node
613
615
 
614
- # List of HTML elements where whitespace is semantically significant
615
- whitespace_sensitive_tags = %w[pre code textarea script style]
616
-
617
616
  # Check if this node or any ancestor is whitespace-sensitive
618
617
  current = node
619
618
  while current
620
619
  if current.is_a?(Core::TreeNode)
621
620
  label = current.label.to_s.downcase
622
- return true if whitespace_sensitive_tags.include?(label)
621
+ return true if Core::TreeNode::WHITESPACE_SENSITIVE_TAGS.include?(label)
623
622
  end
624
623
 
625
624
  current = current.is_a?(Core::TreeNode) ? current.parent : nil
data/lib/canon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Canon
4
- VERSION = "0.3.58"
4
+ VERSION = "0.3.60"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.58
4
+ version: 0.3.60
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.