canon 0.3.47 → 0.3.49

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: 8180f10b0965070c408985ca44868fc14ae2256f24dac607492470aea7e5d076
4
- data.tar.gz: e50498b713b972237a67af3b3f5c2cd5fb0cf1cbc3fef0dbb57f628810ce2341
3
+ metadata.gz: 8f3f2bed07760530a3f122f18e184ac0e925db9d6551bacc46ef896552fb79ce
4
+ data.tar.gz: 4de5e38b9be98f354a843bb3fdd1c725534fab28f6078cbf95a7c278d0c2596b
5
5
  SHA512:
6
- metadata.gz: 52c5eb3fcb8ab3e09c7de5b8b7120f2baad191467aeccab6154254cd0007128f76ae2f5dbd26102abf19c3cf9f289f3c0445db88917562d7b7edafe9aef7ae5b
7
- data.tar.gz: 373032bcba62f9685c41f09bb0d3dce294c50fa8785252e3737231a67f9a0fdfdb5372c85f3b28da5983faf39d4cdf89723dfae8397969511d2688afecaf3418
6
+ metadata.gz: '002904526d2626e462005b4a3627540a1d6587c458b60adb871ad03134054485c2712707579106f727fa00066b213d8db903f96be5d695470aed08ec82157461'
7
+ data.tar.gz: 537566ace8ff29b3124bbd9fd2789250d95d539de60b0e8ba89a3d5ebf83dee871fe0e7525d8d76381286f8adf71886957a3c4a29b1887f861fc0201b44bd862
data/CLAUDE.md CHANGED
@@ -145,7 +145,7 @@ Engine A/B testing: `CANON_XML_BACKEND=nokogiri bundle exec rspec` (or `=moxml`
145
145
 
146
146
  ### C14N Engines
147
147
 
148
- `Canon::Xml::C14n.canonicalize` stays on canon's Ruby C14N 1.1 processor by default. leptris' native C-side C14N 1.1 is ~50x faster on large documents and byte-identical on the main corpus, but diverges from the spec on edge cases (attribute ordering, prefix preservation, `>`/tab escaping, document-level PIs leptris#1015, all pinned in `spec/canon/xml/c14n_engine_parity_spec.rb`). `CANON_C14N_BACKEND=leptris` opts in; when the parity gate runs clean the default flips. `canonicalize_subset` and `with_comments: true` always use the Ruby processor.
148
+ `Canon::Xml::C14n.canonicalize` uses leptris' native C-side C14N 1.1 by DEFAULT (since libleptris 1.9.178 / gem 1.9.178.0) 23x faster than the Ruby processor through canon's API (1MB document). The native parse runs `noblanks: true` (compact bytes, matching the Ruby lane's product), relative namespace URIs raise identically, and document-level PIs serialize in spec-correct document order (the Ruby processor's root-first order was non-conformant for prolog PIs). `CANON_C14N_BACKEND=ruby` forces the Ruby processor. `canonicalize_subset` and `with_comments: true` always use the Ruby processor. The parity spec (`spec/canon/xml/c14n_engine_parity_spec.rb`) pins byte-identity; its pending cases track intentional divergences (document-order PIs) and the canon-side CR-reference finding.
149
149
 
150
150
  ### YAML Engines
151
151
 
@@ -41,6 +41,11 @@ module Canon
41
41
  # Track occurrences for text_content dimension to find correct element instance
42
42
  @text_occurrence1 = Hash.new(0)
43
43
  @text_occurrence2 = Hash.new(0)
44
+ # Opener-offset caches: one scan per unique element name per
45
+ # document replaces the per-call regex walk (which was also
46
+ # quadratic on repeated element names).
47
+ @opener_offsets1 = {}
48
+ @opener_offsets2 = {}
44
49
  end
45
50
 
46
51
  def enrich
@@ -1461,27 +1466,44 @@ range_start, range_end)
1461
1466
  # tag (no inside-the-element correction — see
1462
1467
  # locate_element_at_index).
1463
1468
  def count_elements_before_position_open(text, char_offset, opener)
1464
- count = 0
1465
- pos = 0
1466
- while (hit = text.index(opener, pos)) && hit < char_offset
1467
- count += 1
1468
- pos = hit + 1
1469
- end
1470
- count
1469
+ count_openers_before(opener_offsets(text, opener), char_offset)
1471
1470
  end
1472
1471
 
1473
1472
  def count_elements_before_position(text, char_offset, element_name)
1474
- # Index loop instead of copying the prefix per call — the
1475
- # copy was O(offset) on every occurrence check.
1476
- opener = /<#{element_name}[>\s]/
1477
- count = 0
1473
+ # Subtract 1 because the count includes the element we are inside
1474
+ [count_openers_before(opener_offsets(text, opener_for(element_name)),
1475
+ char_offset) - 1, 0].max
1476
+ end
1477
+
1478
+ # Count of opener offsets strictly before char_offset — binary
1479
+ # search over the cached, ascending offset array.
1480
+ def count_openers_before(offsets, char_offset)
1481
+ idx = offsets.bsearch_index { |offset| offset >= char_offset }
1482
+ idx || offsets.length
1483
+ end
1484
+
1485
+ # All opener offsets for one opener regex in one document,
1486
+ # cached per text (one linear scan per unique element name per
1487
+ # document — callers previously re-scanned per occurrence
1488
+ # check, which was also quadratic on repeated element names).
1489
+ def opener_offsets(text, opener)
1490
+ if text.equal?(@text1)
1491
+ (@opener_offsets1[opener.source] ||= scan_opener_offsets(text, opener))
1492
+ elsif text.equal?(@text2)
1493
+ (@opener_offsets2[opener.source] ||= scan_opener_offsets(text, opener))
1494
+ else
1495
+ scan_opener_offsets(text, opener)
1496
+ end
1497
+ end
1498
+
1499
+ def scan_opener_offsets(text, opener)
1500
+ offsets = []
1478
1501
  pos = 0
1479
- while (hit = text.index(opener, pos)) && hit < char_offset
1480
- count += 1
1502
+ while (hit = text.index(opener, pos))
1503
+ offsets << hit
1481
1504
  pos = hit + 1
1482
1505
  end
1483
- # Subtract 1 because the count includes the element we are inside
1484
- [count - 1, 0].max
1506
+ offsets
1485
1507
  end
1486
1508
  end
1487
1509
  end
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.47"
4
+ VERSION = "0.3.49"
5
5
  end
@@ -23,39 +23,37 @@ module Canon
23
23
  end
24
24
 
25
25
  # leptris' C-side C14N 1.1 — 23x faster than the Ruby processor
26
- # through canon's own API (1MB document) and byte-identical on
27
- # the parity corpus, but OPT-IN (CANON_C14N_BACKEND=leptris)
28
- # pending three families verified against libxml2 ground truth
29
- # (leptris#1096 follow-up): (1) whitespace-only text between
30
- # elements is preserved the Ruby lane's parse drops it and
31
- # compact bytes are canon's product for every pretty-printed
32
- # document; (2) whitespace-only processing-instruction data is
33
- # retained (libxml2 drops it); (3) document-level nodes carry
34
- # no "\n" separators (the Ruby lane, like libxml2, inserts
35
- # them). libleptris 1.9.176/177 (gem 1.9.177.0) closed the
36
- # earlier families attribute ordering, prefix loss and
37
- # rebinding, `>`/TAB escaping, redundant namespace
38
- # redeclarations, xmlns:xml omission and document-level PIs
39
- # now serialize in document order (the Ruby processor's
40
- # root-first order is non-conformant for prolog PIs).
41
- # Relative namespace URIs raise exactly as the Ruby lane does.
42
- # Comments mode keeps the Ruby path (the native seam exposes no
43
- # with-comments form).
26
+ # through canon's own API (1MB document) and the DEFAULT lane
27
+ # since libleptris 1.9.178 (gem 1.9.178.0): the leptris#1117
28
+ # families are closed by the native parse-policy knob
29
+ # (`noblanks: true` same compact bytes as the Ruby lane's
30
+ # parse) and by the canonicalizer itself (whitespace-only PI
31
+ # data dropped, document-level "\n" separators inserted). The
32
+ # one residual byte difference vs the Ruby lane is intentional
33
+ # and a correctness improvement: document-level PIs now
34
+ # serialize in document order (REC-xml-c14n 2.1) instead of
35
+ # the Ruby lane's non-conformant root-first reorder. Relative
36
+ # namespace URIs raise exactly as the Ruby lane does.
37
+ # Comments mode keeps the Ruby path regardless (the native
38
+ # seam exposes no with-comments form). CANON_C14N_BACKEND=ruby
39
+ # forces the stdlib lane.
44
40
  def self.native_canonicalize(xml, with_comments)
45
41
  return nil if with_comments
46
42
  return nil if RUBY_ENGINE == "opal"
47
- return nil unless ENV["CANON_C14N_BACKEND"].to_s.casecmp("leptris").zero?
43
+ return nil if ENV["CANON_C14N_BACKEND"].to_s.casecmp("ruby").zero?
48
44
  return nil unless Canon::XmlBackend.moxml? &&
49
45
  Canon::XmlParsing.moxml_adapter_name == :leptris
50
46
  return nil unless native_c14n_available?
51
47
 
52
48
  validate_relative_namespaces!(xml)
53
49
 
54
- doc = Canon::XmlParsing.moxml_context.parse(xml, readonly: true,
55
- strict: false)
50
+ doc = Canon::XmlParsing.moxml_context.parse(xml,
51
+ readonly: true,
52
+ strict: false,
53
+ noblanks: true)
56
54
  begin
57
- doc.native.canonicalize(::Leptris::XML::FFI::C14N_1_1, nil,
58
- mode: ::Leptris::XML::FFI::C14N_MODE_CANONICAL)
55
+ doc.native.c14n(::Leptris::XML::FFI::C14N_1_1, nil,
56
+ mode: ::Leptris::XML::FFI::C14N_MODE_CANONICAL)
59
57
  ensure
60
58
  doc.free
61
59
  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.47
4
+ version: 0.3.49
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.