canon 0.3.122 → 0.3.123

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: 5c8f274f503852b87469eb653b74b16cff4a24313a642ba665f9d00f4ddc5ca2
4
- data.tar.gz: 1ff259b65b606b03400e5c729ba2384643877a48fdca3e7ff12f6ba5adfae62d
3
+ metadata.gz: e746e8d1590e8be637395d2c68cb17a8b5aa9c742cbdc2c08d2e9becb42164a4
4
+ data.tar.gz: 8b0cefd9a990836130415fa12491628d992746d9521f00d5768c0c39717fc09c
5
5
  SHA512:
6
- metadata.gz: 936d7dd4ca541dc44435294f26ac3f04886668ccfd5c919ef6cd50c033b75d633e33e4fcdbe9cdd809e9400f2c882af7baaf00d1316e6c832b46a87a8c0e6ec7
7
- data.tar.gz: ae0c2379e7aefd1445bd108aa6533c8570858418e02e35653339618d597bfedab7254af12955a5cd7c8717e29a93591bfd458f6b3920fc343cf8af0a8189b0f8
6
+ metadata.gz: 718f08d011e9df8965873679f6e0332b6dd871682257f73dc20046fd1138db5a279719b605833fdc339b05785a0f950337c69d9067eb446a7b4b1b9e468f2bfb
7
+ data.tar.gz: 0a874b40f3f194cd0998f7d01f59710b613f7b044ab7a17357d0fdc8877e71b4ae7058abe3a5d6705450dadb5c3ca93fa20c4f3437ca8c1f7e51aaf36a920af5
@@ -36,14 +36,10 @@ module Canon
36
36
  end
37
37
 
38
38
  def format(html_string)
39
- if @fixture_ready
40
- # The fixture-ready display mode is the one Nokogiri lane
41
- # left (libxml FORMAT|AS_XHTML save flags — moxml#390 filed
42
- # for an engine XHTML mode); everything else is engine-
43
- # served.
44
- Canon::NokogiriLoader.require!("HTML fixture-ready display")
45
- return format_fixture_ready(html_string)
46
- end
39
+ # The fixture-ready display lane is engine-served end to end
40
+ # (moxml 0.6.2's XHTML serialization mode — moxml#390): no
41
+ # Nokogiri requirement remains in this mode.
42
+ return format_fixture_ready(html_string) if @fixture_ready
47
43
 
48
44
  if xhtml?(html_string)
49
45
  format_as_xhtml(html_string)
@@ -91,14 +87,8 @@ module Canon
91
87
  end
92
88
  end
93
89
 
94
- # Fixture-ready serialisation: parse with Nokogiri::HTML5 (so we
95
- # get permissive recovery on real-world Word / XHTML5 / HTML5
96
- # input shapes), then write through libxml's XML writer with
97
- # +FORMAT+ + +AS_XHTML+ + +NO_DECLARATION+. +FORMAT+ inserts
98
- # indentation; +AS_XHTML+ produces well-shaped output (void
99
- # elements self-closed, non-void paired); +NO_DECLARATION+
100
- # suppresses the +<?xml ...?>+ prefix.
101
- # Engine parse + significance-aware strip + the engine's
90
+ # Fixture-ready serialisation, engine-served end to end:
91
+ # engine HTML parse + significance-aware strip + moxml 0.6.2's
102
92
  # XHTML serialization mode (moxml#390 delivered to_xhtml /
103
93
  # to_html(xhtml: true)): void elements self-closed, non-void
104
94
  # paired — the AS_XHTML contract, engine-served.
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.122"
4
+ VERSION = "0.3.123"
5
5
  end
@@ -14,9 +14,14 @@ module Canon
14
14
  # - Predicates: [1] (position), [@attr], [@attr='value']
15
15
  # - Wildcards: *
16
16
  # - Union: expr1 | expr2
17
+ # - Self and parent steps: ., ..
18
+ # - Attribute steps: @attr (selects the attribute node; per C14N
19
+ # law it renders only when its owner element is in the node-set,
20
+ # so @attr/.. is the productive form — XPath 1.0 section 2.2
21
+ # names the owner element)
17
22
  #
18
23
  # Not supported (not needed for C14N subset):
19
- # - Axes other than child and descendant-or-self
24
+ # - Axes other than child, parent, self, descendant-or-self
20
25
  # - Functions (last(), position(), etc.)
21
26
  # - Variables
22
27
  #
@@ -56,6 +61,10 @@ module Canon
56
61
  elsif xpath.start_with?("/")
57
62
  # Absolute path
58
63
  evaluate_absolute(xpath[1..])
64
+ elsif xpath.start_with?("./")
65
+ # Document-rooted self step: the subset context is the
66
+ # document, so ./rest resolves from the root like /rest
67
+ evaluate_absolute(xpath[2..])
59
68
  else
60
69
  # Relative path — treat as descendant
61
70
  evaluate_descendant(xpath)
@@ -68,35 +77,47 @@ module Canon
68
77
  steps = parse_steps(path)
69
78
  return [] if steps.empty?
70
79
 
71
- # Start from root's children
72
- current_nodes = @root.children
73
- apply_steps(current_nodes, steps)
80
+ # Start from root's children; the first element step filters
81
+ # this entry set directly
82
+ apply_steps(@root.children, steps, descend_first: false)
74
83
  end
75
84
 
76
85
  def evaluate_descendant(path)
77
86
  steps = parse_steps(path)
78
87
  return [] if steps.empty?
79
88
 
89
+ # ./ is transparent on the relative law: the subset context is
90
+ # the document, so ./rest searches descendants like rest
91
+ steps.shift while steps.first&.dig(:type) == :self
92
+ return [] if steps.empty?
93
+
80
94
  # Collect all descendant element nodes
81
95
  all_elements = []
82
96
  collect_elements(@root, all_elements)
83
97
 
84
- # For each element, try to match the full path starting there
85
- result = []
86
- all_elements.each do |element|
87
- first_step = steps.first
88
- next unless step_matches?(element, first_step)
98
+ first_step = steps.first
99
+ remaining = steps[1..]
89
100
 
90
- if steps.length == 1
91
- result << element
101
+ context =
102
+ case first_step[:type]
103
+ when :attribute
104
+ # //@attr — every matching attribute in the tree
105
+ all_elements.flat_map do |element|
106
+ attribute_matches(element, first_step)
107
+ end
108
+ when :parent
109
+ return [] # no meaningful parents above descendants
92
110
  else
93
- remaining = steps[1..]
94
- matched = apply_steps(element.children, remaining)
95
- result.concat(matched)
111
+ all_elements.select do |element|
112
+ step_matches?(element, first_step)
113
+ end
96
114
  end
97
- end
115
+ return context.uniq if remaining.empty?
98
116
 
99
- result.uniq
117
+ # The remaining steps run from the matches: element steps
118
+ # descend (block semantics), axis steps apply to the nodes
119
+ # themselves
120
+ apply_steps(context, remaining, descend_first: true).uniq
100
121
  end
101
122
 
102
123
  def collect_elements(node, result)
@@ -108,20 +129,54 @@ module Canon
108
129
  end
109
130
  end
110
131
 
111
- def apply_steps(nodes, steps)
112
- return nodes if steps.empty?
132
+ # Linear context model: each step transforms the current node
133
+ # set. Element steps filter one level down from the context
134
+ # (block semantics — the first step filters the entry set
135
+ # itself); parent/self/attribute steps are context steps and
136
+ # operate on the nodes themselves.
137
+ def apply_steps(nodes, steps, descend_first:)
138
+ steps.reduce(nodes) do |current, step|
139
+ break [] if current.empty?
140
+
141
+ if element_step?(step)
142
+ level = descend_first ? descend(current) : current
143
+ current = level.select { |n| step_matches?(n, step) }.uniq
144
+ else
145
+ current = axis_apply(current, step)
146
+ end
147
+ descend_first = true
148
+ current
149
+ end
150
+ end
113
151
 
114
- step = steps.first
115
- remaining = steps[1..]
152
+ def element_step?(step)
153
+ step[:type] == :element
154
+ end
116
155
 
117
- matched = nodes.select { |n| step_matches?(n, step) }
156
+ def descend(nodes)
157
+ nodes.flat_map do |n|
158
+ next [] if n.is_a?(Nodes::AttributeNode)
118
159
 
119
- if remaining.empty?
120
- matched
121
- else
122
- matched.flat_map do |node|
123
- apply_steps(node.children, remaining)
124
- end
160
+ n.children
161
+ end
162
+ end
163
+
164
+ # Context steps: navigate from the nodes themselves.
165
+ def axis_apply(nodes, step)
166
+ case step[:type]
167
+ when :parent
168
+ nodes.filter_map(&:parent).uniq
169
+ when :self
170
+ nodes
171
+ when :attribute
172
+ nodes.grep(Nodes::ElementNode)
173
+ .flat_map { |el| attribute_matches(el, step) }.uniq
174
+ end
175
+ end
176
+
177
+ def attribute_matches(element, step)
178
+ element.attribute_nodes.select do |a|
179
+ attr_name_matches?(a, step[:name])
125
180
  end
126
181
  end
127
182
 
@@ -132,6 +187,17 @@ module Canon
132
187
  predicates_match?(node, step[:predicates])
133
188
  end
134
189
 
190
+ def attr_name_matches?(attr, name)
191
+ return true if name == "*"
192
+
193
+ if name.include?(":")
194
+ prefix, local = name.split(":", 2)
195
+ attr.prefix == prefix && attr.name == local
196
+ else
197
+ attr.name == name
198
+ end
199
+ end
200
+
135
201
  def name_matches?(node, name)
136
202
  return true if name == "*"
137
203
 
@@ -179,7 +245,7 @@ module Canon
179
245
  # Parse a path string into an array of steps.
180
246
  #
181
247
  # @param path [String] XPath path (without leading /)
182
- # @return [Array<Hash>] Array of { name:, predicates: }
248
+ # @return [Array<Hash>] Array of { type:, name:, predicates: }
183
249
  def parse_steps(path)
184
250
  steps = []
185
251
  scanner = StringScanner.new(path)
@@ -189,14 +255,27 @@ module Canon
189
255
  break if scanner.eos?
190
256
 
191
257
  # Skip /
192
- scanner.scan(%r{/})
258
+ scanner.skip(%r{/})
259
+ scanner.skip(/\s+/)
260
+
261
+ if scanner.scan("..")
262
+ steps << { type: :parent, name: nil, predicates: [] }
263
+ next
264
+ elsif scanner.scan(".")
265
+ steps << { type: :self, name: nil, predicates: [] }
266
+ next
267
+ elsif scanner.scan(/@/)
268
+ name = scanner.scan(/[a-zA-Z_][\w.-]*/)
269
+ steps << { type: :attribute, name: name, predicates: [] }
270
+ next
271
+ end
193
272
 
194
273
  name = scan_name(scanner)
195
274
  break if name.nil?
196
275
 
197
276
  predicates = scan_predicates(scanner)
198
277
 
199
- steps << { name: name, predicates: predicates }
278
+ steps << { type: :element, name: name, predicates: predicates }
200
279
  end
201
280
 
202
281
  steps
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.122
4
+ version: 0.3.123
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.6.0
61
+ version: 0.6.2
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.6.0
68
+ version: 0.6.2
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: paint
71
71
  requirement: !ruby/object:Gem::Requirement