canon 0.3.7 → 0.3.8

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: b1ce43e1e3cb461ac60687f0e9cdd815916f85cd40a87c8d70f0a071c56b1c04
4
- data.tar.gz: 3eff7d2db22b5fe57674e64aa5f6b5ebb1d4b82d6f3afde5c45444f57f2c8e55
3
+ metadata.gz: 7e6a28999cdd57c9fb642e7f334546ef2ebe4802057481ef9c9bcf93252b8590
4
+ data.tar.gz: 072ae76310a6f936b81a73f2305bac61cb0b1100f36509724f67c5a763fb718f
5
5
  SHA512:
6
- metadata.gz: 7428800d8125c5173b61338ce8929f1c3700684ccba7cae25847a1d5cc533c99eea946a46cd5f567841b978597670e26d546849b853b11bd88cb6f72c023c647
7
- data.tar.gz: 77cdd82d177e2d476aece71503a70487ee31f6593f30d401e748199944fbc9230756c5bba08b73411dae62621c5d08c16c1fdb4ec427a5e96f0d43955cea0414
6
+ metadata.gz: '049a48f66a243b20993f677e8789eff4b08ed72f0b22fa9288453d3806718f2a9f7414c7774e51de30290d19d5b012419f50eb8cdf75762944b4f5c28ed6d54b'
7
+ data.tar.gz: b57afbf6f649b27c8a4833742673f2ae7dea0a4608610795d8ae38c76398ed66e833d4a5d29f832e45ecb1f5ced1e23125afc6b383f45ffc545b6257aca986bb
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.7"
4
+ VERSION = "0.3.8"
5
5
  end
@@ -212,7 +212,6 @@ inherited_namespaces: nil)
212
212
  doc = Canon::XmlParsing.moxml_context.parse(xml_string,
213
213
  readonly: true,
214
214
  strict: false)
215
- check_moxml_relative_namespace_uris(doc)
216
215
  result = build_from_moxml(doc, preserve_whitespace: preserve_whitespace)
217
216
  result.parse_errors = doc.parse_errors if doc.parse_errors.any?
218
217
  # Canon owns this document's full lifecycle: the canon tree holds
@@ -223,23 +222,6 @@ inherited_namespaces: nil)
223
222
  result
224
223
  end
225
224
 
226
- def self.check_moxml_relative_namespace_uris(node)
227
- case node
228
- when Moxml::Element
229
- node.namespace_definitions.each do |ns|
230
- href = ns.uri
231
- next if href.nil? || href.empty?
232
- if relative_uri?(href)
233
- raise Canon::Error,
234
- "Relative namespace URI not allowed: #{href}"
235
- end
236
- end
237
- node.children.each { |child| check_moxml_relative_namespace_uris(child) }
238
- when Moxml::Document
239
- node.children.each { |child| check_moxml_relative_namespace_uris(child) }
240
- end
241
- end
242
-
243
225
  def self.build_from_moxml(moxml_doc, preserve_whitespace: false)
244
226
  root = Nodes::RootNode.new
245
227
  skip_types = [Moxml::Doctype]
@@ -267,32 +249,52 @@ preserve_whitespace: false)
267
249
  frames = []
268
250
  own_namespaces = {}.compare_by_identity
269
251
 
270
- moxml_element.materialize do |record|
252
+ # materialize_fields: the zero-allocation hot path (moxml#143).
253
+ # Flat reused buffers — attributes stride 4, namespaces stride
254
+ # 2 — valid only inside the block, so the element builder
255
+ # copies them into pairs before returning.
256
+ moxml_element.materialize_fields do |kind, qname, prefix, namespace_uri, namespaces, attributes, text, depth|
271
257
  # The record contract is root-subtree-only since moxml 0.5.11
272
258
  # (moxml#140). Older 0.5.x releases — still allowed by canon's
273
259
  # gemspec floor — leaked epilog document-level records at depth
274
260
  # 0, which the document-children enumeration also covers; one
275
261
  # integer compare per record keeps those working.
276
- next if record[:depth].zero? && record[:kind] != :element
262
+ next if depth.zero? && kind != :element
263
+
264
+ # Relative-namespace validation rides the stream (the
265
+ # namespaces buffer is the element's own declarations) — no
266
+ # separate wrapper-tree walk per parse.
267
+ if kind == :element
268
+ i = 1
269
+ while i < namespaces.size
270
+ href = namespaces[i]
271
+ if !href.nil? && !href.empty? && relative_uri?(href)
272
+ raise Canon::Error,
273
+ "Relative namespace URI not allowed: #{href}"
274
+ end
275
+ i += 2
276
+ end
277
+ end
277
278
 
278
- node = case record[:kind]
279
+ node = case kind
279
280
  when :element
280
- build_moxml_element_from_record(record, frames,
281
- own_namespaces)
281
+ build_moxml_element_from_fields(
282
+ qname, prefix, namespace_uri, namespaces, attributes,
283
+ depth, frames, own_namespaces
284
+ )
282
285
  when :text, :cdata
283
- content = record[:text].to_s
286
+ content = text.to_s
284
287
  TreeBuilder::DEFAULT.text(content,
285
288
  keep: WhitespacePolicy.keep_dom_text?(
286
289
  content, preserve_whitespace: preserve_whitespace
287
290
  ))
288
291
  when :comment
289
- TreeBuilder::DEFAULT.comment(record[:text])
292
+ TreeBuilder::DEFAULT.comment(text)
290
293
  when :processing_instruction
291
- TreeBuilder::DEFAULT.processing_instruction(record[:qname],
292
- record[:text] || "")
294
+ TreeBuilder::DEFAULT.processing_instruction(qname, text || "")
293
295
  end
294
296
 
295
- frames.push([record[:depth], node]) if node
297
+ frames.push([depth, node]) if node
296
298
  end
297
299
 
298
300
  top = frames.last
@@ -302,21 +304,24 @@ preserve_whitespace: false)
302
304
  top[1]
303
305
  end
304
306
 
305
- def self.build_moxml_element_from_record(record, frames, own_namespaces)
307
+ def self.build_moxml_element_from_fields(qname, prefix, namespace_uri,
308
+ namespaces, attributes, depth,
309
+ frames, own_namespaces)
306
310
  element = TreeBuilder::DEFAULT.element(
307
- name: record[:qname],
308
- prefix: record[:prefix],
309
- namespace_uri: record[:namespace_uri],
310
- attributes: record[:attributes],
311
+ name: qname,
312
+ prefix: prefix,
313
+ namespace_uri: namespace_uri,
314
+ attributes: attributes.each_slice(4).to_a,
311
315
  )
312
316
 
313
317
  # Adopt completed children: they pop in reverse order; reversing
314
318
  # once is O(n) (unshift per child would be O(n^2) on wide trees).
315
319
  children = []
316
- children << frames.pop[1] while frames.any? && frames.last[0] > record[:depth]
320
+ children << frames.pop[1] while frames.any? && frames.last[0] > depth
317
321
  children.reverse_each { |child| element.add_child(child) }
318
322
 
319
- own_namespaces[element] = record[:namespaces]
323
+ # Copy the declarations out of the reused buffer.
324
+ own_namespaces[element] = namespaces.each_slice(2).to_a
320
325
  element
321
326
  end
322
327
 
@@ -10,13 +10,12 @@ module Canon
10
10
  # leptris no HTML parser (see Canon::Html::NokogiriSupport).
11
11
  #
12
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.
13
+ # when installed; see XmlParsing.moxml_adapter_name). Both engines
14
+ # follow the resolved adapter: SAX since leptris#625/#99 were fixed,
15
+ # DOM since moxml#143's materialize_fields stream (zero-allocation
16
+ # flat buffers) made the conversion competitive. Conformance parity
17
+ # is complete (engine_parity_spec 12/12). CANON_XML_BACKEND forces
18
+ # either engine; the CI performance gate is the standing referee.
20
19
  #
21
20
  # HTML stays on Nokogiri on CRuby (Canon::Html::NokogiriSupport) and the
22
21
  # pretty-printers keep the Nokogiri pipeline — pretty-printed bytes are
@@ -61,12 +60,11 @@ module Canon
61
60
  def detect
62
61
  return :moxml if RUBY_ENGINE == "opal"
63
62
 
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.
69
- :nokogiri
63
+ # Both engines now run leptris: SAX since leptris#625/#99
64
+ # (CI-gated), DOM since moxml#143's zero-allocation
65
+ # materialize_fields stream made the conversion competitive.
66
+ # The CI performance gate is the standing referee.
67
+ XmlParsing.moxml_adapter_name == :nokogiri ? :nokogiri : :moxml
70
68
  end
71
69
  end
72
70
  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.7
4
+ version: 0.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.