canon 0.3.30 → 0.3.31

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: 308416a45e76807d58f00f40a88d5d6f53617d2a9dac468da8add04fa8175a2f
4
- data.tar.gz: 2fa30986100df7b8238263546513705513635bd6c39f1552f0e0f8774f510f5f
3
+ metadata.gz: 7c7f349890b131b4280956b65c069369fee17b526db3ffacaa4560c3b3beb485
4
+ data.tar.gz: dc2179259f0a7212de6b6aaddd50e61a32fba4627d4b853d60e00172cb819c10
5
5
  SHA512:
6
- metadata.gz: 0ffdb20ffc91117fdf24afb405f1684aea837316c1481ed5cc7e435bff4bf5d409876d9ca5851e187d3cd128b192ce3516b49114e6ac5d56a327dd87330f0ec9
7
- data.tar.gz: aaeac239ac4707d8034eb92c8205753e3ec94ff203bf0b4023c33d0b7a92d57742e632ee29998de5a2ed041e45ea7bf2453b2479bb24ed9b9164dacba8db4ab1
6
+ metadata.gz: 6e3085a46f5ae85c542a419ae35b961529fc76d1f232d72f3599eb54b1f6588e0bbc9d8f1ffcdb6e31e61a5dcf4a3d8c884ddf0999f9a1f2062e22997640052a
7
+ data.tar.gz: 3573d0da07ad60bd4aac8a3e3ffb149d61c400a2708c10d8dcca9280abae0dc12ac7d442786fd12be62ea584345e67dd2807ca63bc598578a4013e9159cd0ad7
@@ -74,6 +74,25 @@ module Canon
74
74
  # Store resolved match options hash for use in comparison logic
75
75
  opts[:match_opts] = match_opts_hash
76
76
 
77
+ # FAST PATH: leptris Merkle digest — equal root-subtree
78
+ # digests plus identical document-level skeletons prove
79
+ # content identity (modulo whitespace-only text nodes and
80
+ # attribute order), which implies equivalence wherever
81
+ # canon's whitespace handling is the same symmetric
82
+ # parse-time strip. Callers whose whitespace semantics
83
+ # differ from that skip the gate: strict attribute order
84
+ # (invisible to the digest), user-configured whitespace
85
+ # element lists, xml:space documents (checked inside the
86
+ # gate), and verbose callers who need the report.
87
+ if !(opts[:verbose] ||
88
+ match_opts_hash[:attribute_order] == :strict ||
89
+ match_opts_hash[:preserve_whitespace_elements] ||
90
+ match_opts_hash[:collapse_whitespace_elements] ||
91
+ match_opts_hash[:strip_whitespace_elements]) &&
92
+ Xml::DigestGate.equal?(n1, n2)
93
+ return true
94
+ end
95
+
77
96
  # Create child_opts with resolved options
78
97
  child_opts = opts.merge(child_opts)
79
98
 
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.30"
4
+ VERSION = "0.3.31"
5
5
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canon
4
+ module Xml
5
+ # Document-level Merkle-digest equivalence gate over leptris'
6
+ # content-defined subtree digests (libleptris #869, leptris-ruby
7
+ # Node#digest since 1.9.144).
8
+ #
9
+ # Equal root digests prove the root subtrees carry identical
10
+ # content (whitespace-only nodes dropped), and every canon match
11
+ # behavior is a relaxation of content identity — so equal digests
12
+ # imply equivalence under any option combination EXCEPT the two
13
+ # restrictions that compare things the digest deliberately
14
+ # ignores (attribute order, comments). The caller excludes those;
15
+ # this module answers the digest question only.
16
+ #
17
+ # A digest miss costs two readonly engine parses (no field
18
+ # materialization, no canon tree) — a fraction of the comparison
19
+ # it precedes. Any parse failure answers false and lets the full
20
+ # pipeline surface the error.
21
+ module DigestGate
22
+ module_function
23
+
24
+ def available?
25
+ return false if RUBY_ENGINE == "opal"
26
+ return false unless Canon::XmlBackend.moxml? &&
27
+ Canon::XmlParsing.moxml_adapter_name == :leptris
28
+
29
+ # Node#digest is the 1.9.144 surface; feature-detect it on a
30
+ # throwaway document rather than probing the class.
31
+ doc = Canon::XmlParsing.moxml_context.parse("<r/>", readonly: true,
32
+ strict: false)
33
+ root = doc.root
34
+ digestable = !root.native.digest(drop_ws: true).nil?
35
+ doc.free
36
+ digestable
37
+ rescue StandardError
38
+ false
39
+ end
40
+
41
+ # True when both documents' root subtrees digest identically
42
+ # AND their document-level skeletons (prolog/epilog comments and
43
+ # PIs — the only doc-level nodes canon compares; doctype and
44
+ # whitespace-only text are excluded) serialize identically.
45
+ # Anything else — parse failure, missing root, digest
46
+ # unavailable — is false: the caller falls through to the full
47
+ # pipeline, which surfaces the real parse errors.
48
+ def equal?(xml1, xml2)
49
+ return false unless xml1.is_a?(String) && xml2.is_a?(String)
50
+
51
+ begin
52
+ # xml:space documents carry attribute-scoped whitespace canon
53
+ # makes normative and the digest cannot see — decline them.
54
+ # The scan also declines non-ASCII-compatible encodings
55
+ # (include? raises) — those are the full pipeline's to
56
+ # normalize.
57
+ return false if xml1.include?("xml:space") || xml2.include?("xml:space")
58
+ rescue Encoding::CompatibilityError
59
+ return false
60
+ end
61
+
62
+ context = Canon::XmlParsing.moxml_context
63
+ begin
64
+ left = fingerprint(context, xml1)
65
+ right = fingerprint(context, xml2)
66
+ !left.nil? && left == right
67
+ rescue StandardError
68
+ # Any engine-level surprise (encodings, broken input) is the
69
+ # full pipeline's domain — it normalizes and surfaces errors.
70
+ false
71
+ end
72
+ end
73
+
74
+ # [root digest, doc-level skeleton] or nil when unparseable.
75
+ def fingerprint(context, xml)
76
+ doc = context.parse(xml, readonly: true, strict: false)
77
+ root = doc.root
78
+ return nil unless root
79
+
80
+ skeleton = doc.children.filter_map do |child|
81
+ next if child.equal?(root)
82
+
83
+ case child
84
+ when Moxml::Comment, Moxml::ProcessingInstruction then child.to_s
85
+ when Moxml::Text then child.content.strip.empty? ? nil : child.to_s
86
+ end
87
+ end
88
+ [root.native.digest(drop_ws: true), skeleton]
89
+ ensure
90
+ doc&.free
91
+ end
92
+ end
93
+ end
94
+ end
data/lib/canon/xml.rb CHANGED
@@ -16,6 +16,7 @@ module Canon
16
16
  module Xml
17
17
  autoload :AttributeHandler, "canon/xml/attribute_handler"
18
18
  autoload :C14n, "canon/xml/c14n"
19
+ autoload :DigestGate, "canon/xml/digest_gate"
19
20
  autoload :CharacterEncoder, "canon/xml/character_encoder"
20
21
  autoload :DataModel, "canon/xml/data_model"
21
22
  autoload :ElementMatcher, "canon/xml/element_matcher"
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.30
4
+ version: 0.3.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -391,6 +391,7 @@ files:
391
391
  - lib/canon/xml/c14n.rb
392
392
  - lib/canon/xml/character_encoder.rb
393
393
  - lib/canon/xml/data_model.rb
394
+ - lib/canon/xml/digest_gate.rb
394
395
  - lib/canon/xml/element_matcher.rb
395
396
  - lib/canon/xml/line_range_mapper.rb
396
397
  - lib/canon/xml/namespace_handler.rb