canon 0.3.5 → 0.3.7
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 +4 -4
- data/lib/canon/html/data_model.rb +1 -1
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/data_model.rb +48 -43
- data/lib/canon/xml/sax.rb +8 -11
- data/lib/canon/xml/sax_builder.rb +23 -60
- data/lib/canon/xml/tree_builder.rb +12 -5
- data/lib/canon/xml_backend.rb +13 -10
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1ce43e1e3cb461ac60687f0e9cdd815916f85cd40a87c8d70f0a071c56b1c04
|
|
4
|
+
data.tar.gz: 3eff7d2db22b5fe57674e64aa5f6b5ebb1d4b82d6f3afde5c45444f57f2c8e55
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7428800d8125c5173b61338ce8929f1c3700684ccba7cae25847a1d5cc533c99eea946a46cd5f567841b978597670e26d546849b853b11bd88cb6f72c023c647
|
|
7
|
+
data.tar.gz: 77cdd82d177e2d476aece71503a70487ee31f6593f30d401e748199944fbc9230756c5bba08b73411dae62621c5d08c16c1fdb4ec427a5e96f0d43955cea0414
|
|
@@ -80,7 +80,7 @@ module Canon
|
|
|
80
80
|
# fragment — a TreeBuilder walk like the XML extractors, with the
|
|
81
81
|
# HTML whitespace policy and xmlns-free attributes.
|
|
82
82
|
def self.build_from_nokogiri(nokogiri_doc)
|
|
83
|
-
builder = Canon::Xml::TreeBuilder
|
|
83
|
+
builder = Canon::Xml::TreeBuilder::DEFAULT
|
|
84
84
|
root = Canon::Xml::Nodes::RootNode.new
|
|
85
85
|
skip_types = defined?(Nokogiri) ? [Nokogiri::XML::DTD] : []
|
|
86
86
|
|
data/lib/canon/version.rb
CHANGED
data/lib/canon/xml/data_model.rb
CHANGED
|
@@ -134,8 +134,6 @@ module Canon
|
|
|
134
134
|
# node kinds, document ordering) live in TreeBuilder and
|
|
135
135
|
# WhitespacePolicy — one dialect, three feeds.
|
|
136
136
|
|
|
137
|
-
TREE_BUILDER = TreeBuilder.new
|
|
138
|
-
|
|
139
137
|
# -- Nokogiri walk: top-down, scope flows down the recursion --
|
|
140
138
|
|
|
141
139
|
def self.build_from_nokogiri(nokogiri_doc, preserve_whitespace: false)
|
|
@@ -145,13 +143,13 @@ module Canon
|
|
|
145
143
|
if nokogiri_doc.is_a?(Nokogiri::XML::Document) && nokogiri_doc.root
|
|
146
144
|
root.add_child(walk_nokogiri(nokogiri_doc.root,
|
|
147
145
|
preserve_whitespace: preserve_whitespace))
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
TreeBuilder::DEFAULT.add_document_children(root, nokogiri_doc.children,
|
|
147
|
+
nokogiri_doc.root, skip_types) do |child|
|
|
150
148
|
walk_nokogiri(child, preserve_whitespace: preserve_whitespace)
|
|
151
149
|
end
|
|
152
150
|
else
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
TreeBuilder::DEFAULT.add_document_children(root, nokogiri_doc.children,
|
|
152
|
+
nil, skip_types) do |child|
|
|
155
153
|
walk_nokogiri(child, preserve_whitespace: preserve_whitespace)
|
|
156
154
|
end
|
|
157
155
|
end
|
|
@@ -163,11 +161,11 @@ module Canon
|
|
|
163
161
|
inherited_namespaces: nil)
|
|
164
162
|
case node
|
|
165
163
|
when Nokogiri::XML::Element
|
|
166
|
-
scope =
|
|
164
|
+
scope = TreeBuilder::DEFAULT.merge_namespace_scope(
|
|
167
165
|
inherited_namespaces,
|
|
168
166
|
node.namespace_definitions.map { |ns| [ns.prefix, ns.href] },
|
|
169
167
|
)
|
|
170
|
-
element =
|
|
168
|
+
element = TreeBuilder::DEFAULT.element(
|
|
171
169
|
name: node.name,
|
|
172
170
|
prefix: node.namespace&.prefix,
|
|
173
171
|
namespace_uri: node.namespace&.href,
|
|
@@ -184,17 +182,17 @@ inherited_namespaces: nil)
|
|
|
184
182
|
end
|
|
185
183
|
element
|
|
186
184
|
when Nokogiri::XML::Text, Nokogiri::XML::CDATA
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
185
|
+
TreeBuilder::DEFAULT.text(node.content,
|
|
186
|
+
keep: WhitespacePolicy.keep_dom_text?(
|
|
187
|
+
node.content,
|
|
188
|
+
preserve_whitespace: preserve_whitespace,
|
|
189
|
+
element_parent: node.parent.is_a?(Nokogiri::XML::Element),
|
|
190
|
+
),
|
|
191
|
+
original: node.to_xml)
|
|
194
192
|
when Nokogiri::XML::Comment
|
|
195
|
-
|
|
193
|
+
TreeBuilder::DEFAULT.comment(node.content)
|
|
196
194
|
when Nokogiri::XML::ProcessingInstruction
|
|
197
|
-
|
|
195
|
+
TreeBuilder::DEFAULT.processing_instruction(node.name, node.content)
|
|
198
196
|
end
|
|
199
197
|
end
|
|
200
198
|
|
|
@@ -207,9 +205,16 @@ inherited_namespaces: nil)
|
|
|
207
205
|
# scopes cannot flow down during the stream).
|
|
208
206
|
|
|
209
207
|
def self.from_moxml_xml(xml_string, preserve_whitespace:)
|
|
210
|
-
|
|
208
|
+
# strict: false keeps the Nokogiri-engine contract — malformed
|
|
209
|
+
# input yields a recovered document plus diagnostics instead of
|
|
210
|
+
# an exception (moxml 0.5.15 defaults strict to true; issue
|
|
211
|
+
# #147 rides recover errors on Document#parse_errors).
|
|
212
|
+
doc = Canon::XmlParsing.moxml_context.parse(xml_string,
|
|
213
|
+
readonly: true,
|
|
214
|
+
strict: false)
|
|
211
215
|
check_moxml_relative_namespace_uris(doc)
|
|
212
216
|
result = build_from_moxml(doc, preserve_whitespace: preserve_whitespace)
|
|
217
|
+
result.parse_errors = doc.parse_errors if doc.parse_errors.any?
|
|
213
218
|
# Canon owns this document's full lifecycle: the canon tree holds
|
|
214
219
|
# no engine references once built, so release the native tree now
|
|
215
220
|
# instead of waiting for the GC finalizer (moxml#134; no-op on
|
|
@@ -243,13 +248,13 @@ inherited_namespaces: nil)
|
|
|
243
248
|
element = build_moxml_subtree_from_records(moxml_doc.root,
|
|
244
249
|
preserve_whitespace: preserve_whitespace)
|
|
245
250
|
root.add_child(element) if element
|
|
246
|
-
|
|
247
|
-
|
|
251
|
+
TreeBuilder::DEFAULT.add_document_children(root, moxml_doc.children,
|
|
252
|
+
moxml_doc.root, skip_types) do |child|
|
|
248
253
|
walk_moxml(child, preserve_whitespace: preserve_whitespace)
|
|
249
254
|
end
|
|
250
255
|
else
|
|
251
|
-
|
|
252
|
-
|
|
256
|
+
TreeBuilder::DEFAULT.add_document_children(root, moxml_doc.children,
|
|
257
|
+
nil, skip_types) do |child|
|
|
253
258
|
walk_moxml(child, preserve_whitespace: preserve_whitespace)
|
|
254
259
|
end
|
|
255
260
|
end
|
|
@@ -276,15 +281,15 @@ preserve_whitespace: false)
|
|
|
276
281
|
own_namespaces)
|
|
277
282
|
when :text, :cdata
|
|
278
283
|
content = record[:text].to_s
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
284
|
+
TreeBuilder::DEFAULT.text(content,
|
|
285
|
+
keep: WhitespacePolicy.keep_dom_text?(
|
|
286
|
+
content, preserve_whitespace: preserve_whitespace
|
|
287
|
+
))
|
|
283
288
|
when :comment
|
|
284
|
-
|
|
289
|
+
TreeBuilder::DEFAULT.comment(record[:text])
|
|
285
290
|
when :processing_instruction
|
|
286
|
-
|
|
287
|
-
|
|
291
|
+
TreeBuilder::DEFAULT.processing_instruction(record[:qname],
|
|
292
|
+
record[:text] || "")
|
|
288
293
|
end
|
|
289
294
|
|
|
290
295
|
frames.push([record[:depth], node]) if node
|
|
@@ -298,7 +303,7 @@ preserve_whitespace: false)
|
|
|
298
303
|
end
|
|
299
304
|
|
|
300
305
|
def self.build_moxml_element_from_record(record, frames, own_namespaces)
|
|
301
|
-
element =
|
|
306
|
+
element = TreeBuilder::DEFAULT.element(
|
|
302
307
|
name: record[:qname],
|
|
303
308
|
prefix: record[:prefix],
|
|
304
309
|
namespace_uri: record[:namespace_uri],
|
|
@@ -316,9 +321,9 @@ preserve_whitespace: false)
|
|
|
316
321
|
end
|
|
317
322
|
|
|
318
323
|
def self.assign_moxml_namespace_scopes(element, inherited, own_namespaces)
|
|
319
|
-
scope =
|
|
320
|
-
|
|
321
|
-
|
|
324
|
+
scope = TreeBuilder::DEFAULT.merge_namespace_scope(inherited,
|
|
325
|
+
own_namespaces[element].to_a)
|
|
326
|
+
TreeBuilder::DEFAULT.attach_namespace_scope(element, scope)
|
|
322
327
|
|
|
323
328
|
element.children.each do |child|
|
|
324
329
|
assign_moxml_namespace_scopes(child, scope, own_namespaces) if child.is_a?(Nodes::ElementNode)
|
|
@@ -331,11 +336,11 @@ preserve_whitespace: false)
|
|
|
331
336
|
inherited_namespaces: nil)
|
|
332
337
|
case node
|
|
333
338
|
when Moxml::Element
|
|
334
|
-
scope =
|
|
339
|
+
scope = TreeBuilder::DEFAULT.merge_namespace_scope(
|
|
335
340
|
inherited_namespaces,
|
|
336
341
|
node.namespace_definitions.map { |ns| [ns.prefix, ns.uri] },
|
|
337
342
|
)
|
|
338
|
-
element =
|
|
343
|
+
element = TreeBuilder::DEFAULT.element(
|
|
339
344
|
name: node.name,
|
|
340
345
|
prefix: node.namespace&.prefix,
|
|
341
346
|
namespace_uri: node.namespace&.uri,
|
|
@@ -352,16 +357,16 @@ inherited_namespaces: nil)
|
|
|
352
357
|
end
|
|
353
358
|
element
|
|
354
359
|
when Moxml::Text, Moxml::Cdata
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
360
|
+
TreeBuilder::DEFAULT.text(node.content,
|
|
361
|
+
keep: WhitespacePolicy.keep_dom_text?(
|
|
362
|
+
node.content,
|
|
363
|
+
preserve_whitespace: preserve_whitespace,
|
|
364
|
+
element_parent: node.parent.is_a?(Moxml::Element),
|
|
365
|
+
))
|
|
361
366
|
when Moxml::Comment
|
|
362
|
-
|
|
367
|
+
TreeBuilder::DEFAULT.comment(node.content)
|
|
363
368
|
when Moxml::ProcessingInstruction
|
|
364
|
-
|
|
369
|
+
TreeBuilder::DEFAULT.processing_instruction(node.target, node.content)
|
|
365
370
|
end
|
|
366
371
|
end
|
|
367
372
|
end
|
data/lib/canon/xml/sax.rb
CHANGED
|
@@ -10,16 +10,13 @@ module Canon
|
|
|
10
10
|
# API to that protocol. OCP: a new engine is a new driver plus one line
|
|
11
11
|
# here; the builder never changes.
|
|
12
12
|
#
|
|
13
|
-
# Engine choice
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# Nokogiri driver; the flip is the one-line change below. With
|
|
21
|
-
# leptris absent (moxml resolving nokogiri) the driver calls Nokogiri
|
|
22
|
-
# directly — the wrapper layer only adds overhead there.
|
|
13
|
+
# Engine choice follows the resolved adapter — independent of the
|
|
14
|
+
# DOM engine default: leptris SAX (corruption leptris#625 and the
|
|
15
|
+
# missing xmlns reporting leptris-ruby#99 both fixed) builds canon
|
|
16
|
+
# trees at parity with or faster than Nokogiri SAX, CI-gated. The
|
|
17
|
+
# DOM engine default stays Nokogiri until the record conversion
|
|
18
|
+
# passes the same gate (see XmlBackend). With leptris absent the
|
|
19
|
+
# driver calls Nokogiri directly.
|
|
23
20
|
module Sax
|
|
24
21
|
autoload :NokogiriDriver, "canon/xml/sax/nokogiri_driver"
|
|
25
22
|
autoload :MoxmlDriver, "canon/xml/sax/moxml_driver"
|
|
@@ -27,7 +24,7 @@ module Canon
|
|
|
27
24
|
class << self
|
|
28
25
|
# Drive `builder` with the runtime's SAX engine.
|
|
29
26
|
def parse(xml_string, builder)
|
|
30
|
-
if RUBY_ENGINE == "opal"
|
|
27
|
+
if RUBY_ENGINE == "opal" || Canon::XmlParsing.moxml_adapter_name != :nokogiri
|
|
31
28
|
MoxmlDriver.new(builder).parse(xml_string)
|
|
32
29
|
else
|
|
33
30
|
NokogiriDriver.new(builder).parse(xml_string)
|
|
@@ -9,6 +9,11 @@ module Canon
|
|
|
9
9
|
# selects the driver. Much faster than DOM parsing + conversion —
|
|
10
10
|
# no intermediate engine DOM tree, no traversal conversion pass.
|
|
11
11
|
#
|
|
12
|
+
# Construction goes through TreeBuilder like every other feed:
|
|
13
|
+
# this class owns only what is SAX-specific — qname parsing, xmlns
|
|
14
|
+
# separation, character-reference decoding, adjacency combining,
|
|
15
|
+
# the namespace stack, and document-level reordering.
|
|
16
|
+
#
|
|
12
17
|
# Usage:
|
|
13
18
|
# root = SaxBuilder.parse(xml_string, preserve_whitespace: false)
|
|
14
19
|
# # root is a Canon::Xml::Nodes::RootNode
|
|
@@ -123,26 +128,23 @@ strip_doctype: false)
|
|
|
123
128
|
end
|
|
124
129
|
end
|
|
125
130
|
|
|
126
|
-
# Push new namespace scope with declarations
|
|
131
|
+
# Push new namespace scope with declarations (own shadows
|
|
132
|
+
# inherited — the same merge the TreeBuilder scope kernel applies)
|
|
127
133
|
new_scope = @namespace_stack.last.merge(ns_hash)
|
|
128
134
|
@namespace_stack.push(new_scope)
|
|
129
135
|
|
|
130
|
-
|
|
131
|
-
ns_uri = new_scope[prefix.to_s]
|
|
132
|
-
|
|
133
|
-
# Create element node
|
|
134
|
-
element = Nodes::ElementNode.new(
|
|
136
|
+
element = TreeBuilder::DEFAULT.element(
|
|
135
137
|
name: local_name,
|
|
136
|
-
namespace_uri: ns_uri,
|
|
137
138
|
prefix: prefix,
|
|
139
|
+
namespace_uri: new_scope[prefix.to_s],
|
|
140
|
+
namespace_scope: new_scope,
|
|
141
|
+
attributes: regular_attrs.map do |attr_name, attr_value|
|
|
142
|
+
attr_prefix, attr_local = parse_qname(attr_name)
|
|
143
|
+
attr_ns_uri = attr_prefix ? new_scope[attr_prefix] : nil
|
|
144
|
+
[attr_local, decode_character_references(attr_value || ""), attr_ns_uri, attr_prefix]
|
|
145
|
+
end,
|
|
138
146
|
)
|
|
139
147
|
|
|
140
|
-
# Add namespace nodes from current scope
|
|
141
|
-
add_namespace_nodes(element, new_scope)
|
|
142
|
-
|
|
143
|
-
# Build and add attribute nodes (excluding xmlns declarations)
|
|
144
|
-
add_attribute_nodes(element, regular_attrs)
|
|
145
|
-
|
|
146
148
|
parent.add_child(element)
|
|
147
149
|
@stack.push(element)
|
|
148
150
|
end
|
|
@@ -206,8 +208,9 @@ strip_doctype: false)
|
|
|
206
208
|
preserve_whitespace: @preserve_whitespace,
|
|
207
209
|
element_parent: parent.node_type == :element)
|
|
208
210
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
parent.add_child(
|
|
212
|
+
TreeBuilder::DEFAULT.text(decoded_string, keep: true, original: raw_string),
|
|
213
|
+
)
|
|
211
214
|
end
|
|
212
215
|
private :append_text
|
|
213
216
|
|
|
@@ -215,9 +218,7 @@ strip_doctype: false)
|
|
|
215
218
|
#
|
|
216
219
|
# @param string [String] Comment content
|
|
217
220
|
def comment(string)
|
|
218
|
-
|
|
219
|
-
comment_node = Nodes::CommentNode.new(value: string)
|
|
220
|
-
parent.add_child(comment_node)
|
|
221
|
+
@stack.last.add_child(TreeBuilder::DEFAULT.comment(string))
|
|
221
222
|
end
|
|
222
223
|
|
|
223
224
|
# Called for processing instructions
|
|
@@ -225,10 +226,9 @@ strip_doctype: false)
|
|
|
225
226
|
# @param name [String] PI target
|
|
226
227
|
# @param content [String] PI content
|
|
227
228
|
def processing_instruction(name, content)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
parent.add_child(pi)
|
|
229
|
+
@stack.last.add_child(
|
|
230
|
+
TreeBuilder::DEFAULT.processing_instruction(name, content || ""),
|
|
231
|
+
)
|
|
232
232
|
end
|
|
233
233
|
|
|
234
234
|
# Return the built tree
|
|
@@ -314,44 +314,7 @@ strip_doctype: false)
|
|
|
314
314
|
[ns_decls, regular_attrs]
|
|
315
315
|
end
|
|
316
316
|
|
|
317
|
-
#
|
|
318
|
-
#
|
|
319
|
-
# @param element [Nodes::ElementNode] Element to add namespaces to
|
|
320
|
-
# @param scope [Hash] Current namespace scope
|
|
321
|
-
def add_namespace_nodes(element, scope)
|
|
322
|
-
scope.each do |prefix, uri|
|
|
323
|
-
ns_node = Nodes::NamespaceNode.new(prefix: prefix, uri: uri)
|
|
324
|
-
element.add_namespace(ns_node)
|
|
325
|
-
end
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Add attribute nodes to element
|
|
329
|
-
#
|
|
330
|
-
# @param element [Nodes::ElementNode] Element to add attributes to
|
|
331
|
-
# @param attrs [Array] Array of [name, value] pairs
|
|
332
|
-
def add_attribute_nodes(element, attrs)
|
|
333
|
-
attrs.each do |attr_name, attr_value|
|
|
334
|
-
attr_prefix, attr_local = parse_qname(attr_name)
|
|
335
|
-
|
|
336
|
-
# Find namespace for attribute (if prefixed)
|
|
337
|
-
attr_ns_uri = attr_prefix ? @namespace_stack.last[attr_prefix] : nil
|
|
338
|
-
|
|
339
|
-
# Decode numeric character references (e.g., & → &)
|
|
340
|
-
# Nokogiri SAX leaves these as-is, but we need them decoded for C14N
|
|
341
|
-
decoded_value = decode_character_references(attr_value || "")
|
|
342
|
-
|
|
343
|
-
attr_node = Nodes::AttributeNode.new(
|
|
344
|
-
name: attr_local,
|
|
345
|
-
value: decoded_value,
|
|
346
|
-
namespace_uri: attr_ns_uri,
|
|
347
|
-
prefix: attr_prefix,
|
|
348
|
-
)
|
|
349
|
-
element.add_attribute(attr_node)
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# Decode numeric character references in a string
|
|
354
|
-
# Handles both &#decimal; and &#xhex; forms
|
|
317
|
+
# Decode numeric character references (e.g., & → &)
|
|
355
318
|
#
|
|
356
319
|
# @param value [String] String potentially containing character references
|
|
357
320
|
# @return [String] String with character references decoded
|
|
@@ -16,6 +16,9 @@ module Canon
|
|
|
16
16
|
# repeats, libleptris deduplicates), so the builder resolves them
|
|
17
17
|
# once — first occurrence wins.
|
|
18
18
|
class TreeBuilder
|
|
19
|
+
# Stateless module: one shared instance serves every feed.
|
|
20
|
+
DEFAULT = new
|
|
21
|
+
|
|
19
22
|
NO_ATTRIBUTES = [].freeze
|
|
20
23
|
XML_NAMESPACE_PREFIX = "xml"
|
|
21
24
|
XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace"
|
|
@@ -54,12 +57,16 @@ module Canon
|
|
|
54
57
|
)
|
|
55
58
|
attach_namespace_scope(element, namespace_scope) if namespace_scope
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
# Duplicate (name, namespace) pairs are invalid XML — first
|
|
61
|
+
# occurrence wins. Attributes per element are few, so a pairwise
|
|
62
|
+
# scan beats a per-element seen-hash on allocation (this is the
|
|
63
|
+
# hot SAX path).
|
|
64
|
+
attributes.each_with_index do |(attr_name, value, attr_namespace_uri, attr_prefix), index|
|
|
65
|
+
duplicate = attributes[0...index].any? do |prior_name, _v, prior_ns, _p|
|
|
66
|
+
prior_name == attr_name && prior_ns == attr_namespace_uri
|
|
67
|
+
end
|
|
68
|
+
next if duplicate
|
|
61
69
|
|
|
62
|
-
seen[key] = true
|
|
63
70
|
element.add_attribute(Nodes::AttributeNode.new(
|
|
64
71
|
name: attr_name,
|
|
65
72
|
value: value,
|
data/lib/canon/xml_backend.rb
CHANGED
|
@@ -9,16 +9,14 @@ module Canon
|
|
|
9
9
|
# - HTML support — always Nokogiri on CRuby; moxml has no HTML adapter and
|
|
10
10
|
# leptris no HTML parser (see Canon::Html::NokogiriSupport).
|
|
11
11
|
#
|
|
12
|
-
# SSOT: moxml owns adapter preference (Moxml::Config prefers leptris
|
|
13
|
-
# installed
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# #detect; CANON_XML_BACKEND=moxml opts in today. Under Opal the engine
|
|
21
|
-
# is moxml (rexml adapter).
|
|
12
|
+
# SSOT: moxml owns adapter preference (Moxml::Config prefers leptris
|
|
13
|
+
# when installed; see XmlParsing.moxml_adapter_name). The SAX engine
|
|
14
|
+
# follows the resolved adapter — leptris (correct since leptris#625
|
|
15
|
+
# and leptris-ruby#99; CI perf-gated). The DOM engine stays Nokogiri
|
|
16
|
+
# until the record conversion passes the same gate (moxml#143's fix
|
|
17
|
+
# landed; still -40%/-37% from_xml on the CI runner). Conformance
|
|
18
|
+
# parity is complete (engine_parity_spec 12/12, zero pendings).
|
|
19
|
+
# CANON_XML_BACKEND=moxml opts into the leptris DOM engine today.
|
|
22
20
|
#
|
|
23
21
|
# HTML stays on Nokogiri on CRuby (Canon::Html::NokogiriSupport) and the
|
|
24
22
|
# pretty-printers keep the Nokogiri pipeline — pretty-printed bytes are
|
|
@@ -63,6 +61,11 @@ module Canon
|
|
|
63
61
|
def detect
|
|
64
62
|
return :moxml if RUBY_ENGINE == "opal"
|
|
65
63
|
|
|
64
|
+
# The SAX engine flipped to leptris (leptris#625/#99 fixed,
|
|
65
|
+
# CI-gated — see Canon::Xml::Sax), but the DOM conversion still
|
|
66
|
+
# trails Nokogiri on the CI runner (-40%/-37% from_xml after
|
|
67
|
+
# moxml#143's emission fix landed). Nokogiri stays the DOM
|
|
68
|
+
# default until that gate passes; flipping is this one line.
|
|
66
69
|
:nokogiri
|
|
67
70
|
end
|
|
68
71
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: canon
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|