canon 0.3.53 → 0.3.55
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/comparison/comparison_result.rb +30 -6
- data/lib/canon/comparison/html_comparator.rb +4 -2
- data/lib/canon/comparison/json_comparator.rb +7 -3
- data/lib/canon/comparison/xml_comparator.rb +89 -20
- data/lib/canon/comparison/yaml_comparator.rb +8 -4
- data/lib/canon/comparison.rb +3 -3
- data/lib/canon/version.rb +1 -1
- data/lib/canon/xml/digest_gate.rb +32 -1
- data/lib/canon/xml/sax/probe.rb +56 -0
- data/lib/canon/xml/sax.rb +14 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50a34a46b082f7aa0555199b09780ec1e8c5f38f76656e2a6899a54b3b96a90f
|
|
4
|
+
data.tar.gz: f2b7cbf27874cc6bbd6ab5615a14ad43a2ea6cb7e8ebdd9a3894efcc112c103c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5474c6581c262c497d51cebfe91b0882a096a3340acfd7f7c77d67ca064d12164cbc423b6c4865138a80a660115755597672c3e7a1dbe380ddf24f94e8c55d0d
|
|
7
|
+
data.tar.gz: 8dac986a479c7820ec6d780c116d906ef34489cbfdf15f19ebad9f013c0ec90070ad75801471e4cd8e4be4f1e9c62faaeaa8b97d42a296286f1c0842ce9d8d9b
|
|
@@ -5,12 +5,14 @@ module Canon
|
|
|
5
5
|
# Encapsulates the result of a comparison operation
|
|
6
6
|
# Provides methods to query equivalence based on normative diffs
|
|
7
7
|
class ComparisonResult
|
|
8
|
-
attr_reader :differences, :
|
|
9
|
-
:match_options, :algorithm,
|
|
8
|
+
attr_reader :differences, :format, :html_version,
|
|
9
|
+
:match_options, :algorithm,
|
|
10
10
|
:parse_errors_expected, :parse_errors_received
|
|
11
11
|
|
|
12
12
|
# @param differences [Array<DiffNode>] Array of difference nodes
|
|
13
|
-
# @param preprocessed_strings [Array<String, String
|
|
13
|
+
# @param preprocessed_strings [Array<String, String>, Proc] Pre-processed
|
|
14
|
+
# content for display, or a Proc returning the Array (materialized on
|
|
15
|
+
# first read)
|
|
14
16
|
# @param format [Symbol] Format type (:xml, :html, :json, :yaml)
|
|
15
17
|
# @param html_version [Symbol, nil] HTML version (:html4 or :html5) for HTML format only
|
|
16
18
|
# @param match_options [Hash, nil] Resolved match options used for comparison
|
|
@@ -23,7 +25,7 @@ html_version: nil, match_options: nil, algorithm: :dom, original_strings: nil,
|
|
|
23
25
|
parse_errors_expected: nil, parse_errors_received: nil)
|
|
24
26
|
@differences = differences
|
|
25
27
|
@preprocessed_strings = preprocessed_strings
|
|
26
|
-
@original_strings = original_strings
|
|
28
|
+
@original_strings = original_strings
|
|
27
29
|
@format = format
|
|
28
30
|
@html_version = html_version
|
|
29
31
|
@match_options = match_options
|
|
@@ -32,6 +34,28 @@ parse_errors_expected: nil, parse_errors_received: nil)
|
|
|
32
34
|
@parse_errors_received = Array(parse_errors_received)
|
|
33
35
|
end
|
|
34
36
|
|
|
37
|
+
# Display strings for the compared inputs. When the comparator
|
|
38
|
+
# supplied a builder Proc (the display strings require serializing
|
|
39
|
+
# both parsed documents), materialization is deferred to the first
|
|
40
|
+
# read: by_object consumers never pay it, by_line and preprocessed
|
|
41
|
+
# display trigger it exactly once.
|
|
42
|
+
#
|
|
43
|
+
# @return [Array<String, String>]
|
|
44
|
+
def preprocessed_strings
|
|
45
|
+
unless @preprocessed_strings.is_a?(Array)
|
|
46
|
+
@preprocessed_strings = @preprocessed_strings.call
|
|
47
|
+
end
|
|
48
|
+
@preprocessed_strings
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Unprocessed input strings; falls back to the preprocessed pair
|
|
52
|
+
# when the comparator did not provide originals.
|
|
53
|
+
#
|
|
54
|
+
# @return [Array<String, String>]
|
|
55
|
+
def original_strings
|
|
56
|
+
@original_strings || preprocessed_strings
|
|
57
|
+
end
|
|
58
|
+
|
|
35
59
|
# Whether either side reported parse errors. Used by the diff
|
|
36
60
|
# formatter to decide whether to render the parse-error banner.
|
|
37
61
|
#
|
|
@@ -147,8 +171,8 @@ show_diffs: :all, diff_mode: :separate, legacy_terminal: false)
|
|
|
147
171
|
formatter.format(
|
|
148
172
|
self,
|
|
149
173
|
@format,
|
|
150
|
-
doc1:
|
|
151
|
-
doc2:
|
|
174
|
+
doc1: preprocessed_strings[0],
|
|
175
|
+
doc2: preprocessed_strings[1],
|
|
152
176
|
html_version: @html_version,
|
|
153
177
|
)
|
|
154
178
|
end
|
|
@@ -131,8 +131,10 @@ module Canon
|
|
|
131
131
|
if opts[:verbose]
|
|
132
132
|
ComparisonResult.new(
|
|
133
133
|
differences: differences,
|
|
134
|
-
preprocessed_strings:
|
|
135
|
-
|
|
134
|
+
preprocessed_strings: lambda do
|
|
135
|
+
[serialize_for_display(node1),
|
|
136
|
+
serialize_for_display(node2)]
|
|
137
|
+
end,
|
|
136
138
|
original_strings: [original_str1, original_str2],
|
|
137
139
|
format: :html,
|
|
138
140
|
html_version: html_version,
|
|
@@ -44,12 +44,16 @@ module Canon
|
|
|
44
44
|
differences, "")
|
|
45
45
|
|
|
46
46
|
if opts[:verbose]
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
preprocessed = lambda do
|
|
48
|
+
[
|
|
49
|
+
obj1.is_a?(String) ? obj1 : JSON.pretty_generate(obj1),
|
|
50
|
+
obj2.is_a?(String) ? obj2 : JSON.pretty_generate(obj2),
|
|
51
|
+
]
|
|
52
|
+
end
|
|
49
53
|
|
|
50
54
|
ComparisonResult.new(
|
|
51
55
|
differences: differences,
|
|
52
|
-
preprocessed_strings:
|
|
56
|
+
preprocessed_strings: preprocessed,
|
|
53
57
|
format: :json,
|
|
54
58
|
match_options: match_opts_hash,
|
|
55
59
|
)
|
|
@@ -82,14 +82,29 @@ module Canon
|
|
|
82
82
|
# parse-time strip. Callers whose whitespace semantics
|
|
83
83
|
# differ from that skip the gate: strict attribute order
|
|
84
84
|
# (invisible to the digest), user-configured whitespace
|
|
85
|
-
# element lists, xml:space documents (checked inside the
|
|
86
|
-
# gate)
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
# element lists, and xml:space documents (checked inside the
|
|
86
|
+
# gate). Attribute order is invisible to the digest, so
|
|
87
|
+
# :strict always falls through for the verdict itself.
|
|
88
|
+
# Verbose reports additionally carry two digest-invisible
|
|
89
|
+
# entries: informative attribute-order DiffNodes (emitted
|
|
90
|
+
# even under :ignore for order-differing documents) and the
|
|
91
|
+
# SAX lane's parse-error banner (issue #130). The verbose
|
|
92
|
+
# lane therefore re-scans both sides through the SAX probe —
|
|
93
|
+
# identical attribute-order signatures and no recover errors
|
|
94
|
+
# prove both entries empty, and a hit returns the same shape
|
|
95
|
+
# the pipeline builds for equivalent documents (empty
|
|
96
|
+
# differences, deferred display strings) without the two
|
|
97
|
+
# full parses and the walk.
|
|
98
|
+
if !(match_opts_hash[:attribute_order] == :strict ||
|
|
89
99
|
match_opts_hash[:preserve_whitespace_elements] ||
|
|
90
100
|
match_opts_hash[:collapse_whitespace_elements] ||
|
|
91
101
|
match_opts_hash[:strip_whitespace_elements]) &&
|
|
92
|
-
Xml::DigestGate.
|
|
102
|
+
Xml::DigestGate.certify(n1, n2) &&
|
|
103
|
+
verbose_report_proven_empty?(n1, n2, opts)
|
|
104
|
+
if opts[:verbose]
|
|
105
|
+
return build_digest_equivalent_result(n1, n2, match_opts_hash)
|
|
106
|
+
end
|
|
107
|
+
|
|
93
108
|
return true
|
|
94
109
|
end
|
|
95
110
|
|
|
@@ -131,11 +146,14 @@ module Canon
|
|
|
131
146
|
|
|
132
147
|
if opts[:verbose]
|
|
133
148
|
# Serialize parsed nodes for consistent formatting
|
|
134
|
-
# This ensures both sides formatted identically, showing only real
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
149
|
+
# This ensures both sides formatted identically, showing only real
|
|
150
|
+
# differences. Deferred: by_object consumers never read these.
|
|
151
|
+
# node1/node2 are parsed Canon nodes, so the deferred
|
|
152
|
+
# re-parse inside is a pass-through.
|
|
153
|
+
preprocessed = deferred_preprocessed(
|
|
154
|
+
node1, node2,
|
|
155
|
+
match_opts_hash[:preprocessing], preserve_whitespace
|
|
156
|
+
)
|
|
139
157
|
|
|
140
158
|
ComparisonResult.new(
|
|
141
159
|
differences: differences,
|
|
@@ -177,20 +195,71 @@ module Canon
|
|
|
177
195
|
# @param n1 [Object] First input
|
|
178
196
|
# @param n2 [Object] Second input
|
|
179
197
|
# @param opts [Hash] Raw options (before merge with DEFAULT_OPTS)
|
|
198
|
+
# The verbose-report half of the gate: the digest cannot see
|
|
199
|
+
# attribute order (informative DiffNodes) or SAX recover
|
|
200
|
+
# errors (parse-error banner). One tree-free probe scan per
|
|
201
|
+
# side answers both; any hit falls through to the pipeline.
|
|
202
|
+
def verbose_report_proven_empty?(n1, n2, opts)
|
|
203
|
+
return true unless opts[:verbose]
|
|
204
|
+
# Identical input parses identically — differences and
|
|
205
|
+
# attribute order coincide by construction; only the SAX
|
|
206
|
+
# recover-error surface (parse-error banner, issue #130)
|
|
207
|
+
# needs its one scan.
|
|
208
|
+
return !Xml::Sax.probe(n1).saw_error? if n1 == n2
|
|
209
|
+
|
|
210
|
+
left = Xml::Sax.probe(n1)
|
|
211
|
+
return false if left.saw_error?
|
|
212
|
+
|
|
213
|
+
right = Xml::Sax.probe(n2)
|
|
214
|
+
!right.saw_error? && left.signature == right.signature
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Digest-proven equivalence for verbose callers. The report is
|
|
218
|
+
# indistinguishable from the full pipeline's for such inputs —
|
|
219
|
+
# certify guarantees no recover errors, the digest guarantees
|
|
220
|
+
# no differences — with display strings deferred exactly like
|
|
221
|
+
# the pipeline's lazy lane.
|
|
222
|
+
#
|
|
223
|
+
# @return [ComparisonResult]
|
|
224
|
+
def build_digest_equivalent_result(n1, n2, match_opts_hash)
|
|
225
|
+
preserve_whitespace = match_opts_hash[:structural_whitespace] == :strict
|
|
226
|
+
ComparisonResult.new(
|
|
227
|
+
differences: [],
|
|
228
|
+
preprocessed_strings: deferred_preprocessed(
|
|
229
|
+
n1, n2,
|
|
230
|
+
match_opts_hash[:preprocessing], preserve_whitespace
|
|
231
|
+
),
|
|
232
|
+
original_strings: [n1, n2],
|
|
233
|
+
format: :xml,
|
|
234
|
+
match_options: match_opts_hash,
|
|
235
|
+
algorithm: :dom,
|
|
236
|
+
)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Deferred display-string pair. Inputs may be raw strings
|
|
240
|
+
# (trivial/digest paths — parsed on materialization with the
|
|
241
|
+
# caller's preprocessing) or already-parsed Canon nodes
|
|
242
|
+
# (compare path — NodeParser passes them through unchanged).
|
|
243
|
+
#
|
|
244
|
+
# @return [Proc] Array<String, String> on call
|
|
245
|
+
def deferred_preprocessed(n1, n2, preprocessing, preserve_whitespace)
|
|
246
|
+
lambda do
|
|
247
|
+
[
|
|
248
|
+
serialize_node(parse_node(n1, preprocessing,
|
|
249
|
+
preserve_whitespace: preserve_whitespace)).gsub("><", ">\n<"),
|
|
250
|
+
serialize_node(parse_node(n2, preprocessing,
|
|
251
|
+
preserve_whitespace: preserve_whitespace)).gsub("><", ">\n<"),
|
|
252
|
+
]
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
180
256
|
# @return [Boolean, ComparisonResult]
|
|
181
257
|
def build_trivial_equivalent_result(n1, n2, opts)
|
|
182
258
|
return true unless opts[:verbose]
|
|
183
259
|
|
|
184
|
-
# Parse nodes for verbose display
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
preserve_whitespace: preserve_whitespace)
|
|
188
|
-
node2 = parse_node(n2, :none,
|
|
189
|
-
preserve_whitespace: preserve_whitespace)
|
|
190
|
-
preprocessed = [
|
|
191
|
-
serialize_node(node1).gsub("><", ">\n<"),
|
|
192
|
-
serialize_node(node2).gsub("><", ">\n<"),
|
|
193
|
-
]
|
|
260
|
+
# Parse nodes for verbose display — deferred with the strings:
|
|
261
|
+
# nothing but the display pair ever reads them.
|
|
262
|
+
preprocessed = deferred_preprocessed(n1, n2, :none, true)
|
|
194
263
|
original1 = n1.is_a?(String) ? n1 : serialize_node(n1)
|
|
195
264
|
original2 = n2.is_a?(String) ? n2 : serialize_node(n2)
|
|
196
265
|
|
|
@@ -69,13 +69,17 @@ module Canon
|
|
|
69
69
|
differences, "")
|
|
70
70
|
|
|
71
71
|
if opts[:verbose]
|
|
72
|
-
# Format YAML for display
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
# Format YAML for display — deferred until first read
|
|
73
|
+
preprocessed = lambda do
|
|
74
|
+
[
|
|
75
|
+
obj1.is_a?(String) ? obj1 : Canon::YamlParsing.dump(obj1),
|
|
76
|
+
obj2.is_a?(String) ? obj2 : Canon::YamlParsing.dump(obj2),
|
|
77
|
+
]
|
|
78
|
+
end
|
|
75
79
|
|
|
76
80
|
ComparisonResult.new(
|
|
77
81
|
differences: differences,
|
|
78
|
-
preprocessed_strings:
|
|
82
|
+
preprocessed_strings: preprocessed,
|
|
79
83
|
format: :yaml,
|
|
80
84
|
match_options: match_opts_hash,
|
|
81
85
|
)
|
data/lib/canon/comparison.rb
CHANGED
|
@@ -407,11 +407,11 @@ module Canon
|
|
|
407
407
|
|
|
408
408
|
# CRITICAL: Use strategy's preprocess_for_display to ensure proper
|
|
409
409
|
# line-breaking. This matches DOM diff preprocessing pattern
|
|
410
|
-
# (xml_comparator.rb
|
|
410
|
+
# (xml_comparator.rb). Deferred via the lazy ComparisonResult seam:
|
|
411
|
+
# by_object consumers never pay the serialization.
|
|
411
412
|
strategy = Comparison::Strategies::SemanticTreeMatchStrategy.new(
|
|
412
413
|
format: format1, match_options: match_options_only,
|
|
413
414
|
)
|
|
414
|
-
str1, str2 = strategy.preprocess_for_display(doc1, doc2)
|
|
415
415
|
|
|
416
416
|
# Store tree diff data in match_options for access via result.
|
|
417
417
|
enhanced_match_options = match_opts_hash.merge(
|
|
@@ -423,7 +423,7 @@ module Canon
|
|
|
423
423
|
# Create ComparisonResult for unified handling.
|
|
424
424
|
result = Canon::Comparison::ComparisonResult.new(
|
|
425
425
|
differences: diff_nodes,
|
|
426
|
-
preprocessed_strings:
|
|
426
|
+
preprocessed_strings: -> { strategy.preprocess_for_display(doc1, doc2) },
|
|
427
427
|
original_strings: [original_str1, original_str2],
|
|
428
428
|
format: format1,
|
|
429
429
|
html_version: %i[html4 html5].include?(format1) ? format1 : nil,
|
data/lib/canon/version.rb
CHANGED
|
@@ -71,11 +71,42 @@ module Canon
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# The shared fingerprint ([root digest, doc-level skeleton])
|
|
75
|
+
# when both documents digest identically AND both parse clean of
|
|
76
|
+
# recover errors; nil otherwise. The verbose lane certifies
|
|
77
|
+
# before skipping the pipeline: error-bearing documents decline
|
|
78
|
+
# so the pipeline can produce the identical verbose report
|
|
79
|
+
# (parse-error banner included) it always has.
|
|
80
|
+
def certify(xml1, xml2)
|
|
81
|
+
return nil unless xml1.is_a?(String) && xml2.is_a?(String)
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
return nil if xml1.include?("xml:space") || xml2.include?("xml:space")
|
|
85
|
+
rescue Encoding::CompatibilityError
|
|
86
|
+
return nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context = Canon::XmlParsing.moxml_context
|
|
90
|
+
begin
|
|
91
|
+
left = fingerprint(context, xml1, clean: true)
|
|
92
|
+
right = fingerprint(context, xml2, clean: true)
|
|
93
|
+
return nil if left.nil? || left != right
|
|
94
|
+
|
|
95
|
+
left
|
|
96
|
+
rescue StandardError
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
74
101
|
# [root digest, doc-level skeleton] or nil when unparseable.
|
|
75
|
-
|
|
102
|
+
# With `clean:` a document carrying recover errors also answers
|
|
103
|
+
# nil — only certify uses that; the boolean lane keeps the
|
|
104
|
+
# historical verdict for recovered-equal pairs.
|
|
105
|
+
def fingerprint(context, xml, clean: false)
|
|
76
106
|
doc = context.parse(xml, readonly: true, strict: false)
|
|
77
107
|
root = doc.root
|
|
78
108
|
return nil unless root
|
|
109
|
+
return nil if clean && doc.parse_errors.any?
|
|
79
110
|
|
|
80
111
|
skeleton = doc.children.filter_map do |child|
|
|
81
112
|
next if Canon::XmlParsing.same_engine_node?(child, root)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Canon
|
|
4
|
+
module Xml
|
|
5
|
+
module Sax
|
|
6
|
+
# Tree-free scan stand-in for the builder protocol. Records
|
|
7
|
+
# recover errors and accumulates an attribute-name-order
|
|
8
|
+
# signature (element open/close tokens plus each non-xmlns
|
|
9
|
+
# attribute name in document order); content itself is ignored —
|
|
10
|
+
# the digest gate covers it. The verbose lane compares the two
|
|
11
|
+
# sides' signatures to prove the absence of the two
|
|
12
|
+
# digest-invisible report entries: informative attribute-order
|
|
13
|
+
# DiffNodes and the SAX-lane parse-error banner (issue #130).
|
|
14
|
+
class Probe
|
|
15
|
+
attr_reader :signature
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@saw_error = false
|
|
19
|
+
@signature = +""
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def saw_error?
|
|
23
|
+
@saw_error
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def error(_string)
|
|
27
|
+
@saw_error = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def warning(_string); end
|
|
31
|
+
|
|
32
|
+
def start_element(name, attrs = [])
|
|
33
|
+
@signature << "/" << name << "\0"
|
|
34
|
+
attrs.each do |attr|
|
|
35
|
+
attr_name = attr[0].to_s
|
|
36
|
+
next if attr_name == "xmlns" || attr_name.start_with?("xmlns:")
|
|
37
|
+
|
|
38
|
+
@signature << "@" << attr_name << "\0"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def end_element(_name)
|
|
43
|
+
@signature << "\\"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def characters(_string); end
|
|
47
|
+
|
|
48
|
+
def cdata_block(_string); end
|
|
49
|
+
|
|
50
|
+
def comment(_string); end
|
|
51
|
+
|
|
52
|
+
def processing_instruction(_name, _content); end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/canon/xml/sax.rb
CHANGED
|
@@ -20,6 +20,7 @@ module Canon
|
|
|
20
20
|
module Sax
|
|
21
21
|
autoload :NokogiriDriver, "canon/xml/sax/nokogiri_driver"
|
|
22
22
|
autoload :MoxmlDriver, "canon/xml/sax/moxml_driver"
|
|
23
|
+
autoload :Probe, "canon/xml/sax/probe"
|
|
23
24
|
|
|
24
25
|
class << self
|
|
25
26
|
# Drive `builder` with the runtime's SAX engine.
|
|
@@ -31,6 +32,19 @@ module Canon
|
|
|
31
32
|
end
|
|
32
33
|
nil
|
|
33
34
|
end
|
|
35
|
+
|
|
36
|
+
# Tree-free scan through the runtime's SAX engine: answers
|
|
37
|
+
# recover errors and an attribute-name-order signature for
|
|
38
|
+
# `xml_string` without building anything.
|
|
39
|
+
def probe(xml_string)
|
|
40
|
+
probe = Probe.new
|
|
41
|
+
if RUBY_ENGINE == "opal" || Canon::XmlParsing.moxml_adapter_name != :nokogiri
|
|
42
|
+
MoxmlDriver.new(probe).parse(xml_string)
|
|
43
|
+
else
|
|
44
|
+
NokogiriDriver.new(probe).parse(xml_string)
|
|
45
|
+
end
|
|
46
|
+
probe
|
|
47
|
+
end
|
|
34
48
|
end
|
|
35
49
|
end
|
|
36
50
|
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.
|
|
4
|
+
version: 0.3.55
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -437,6 +437,7 @@ files:
|
|
|
437
437
|
- lib/canon/xml/sax.rb
|
|
438
438
|
- lib/canon/xml/sax/moxml_driver.rb
|
|
439
439
|
- lib/canon/xml/sax/nokogiri_driver.rb
|
|
440
|
+
- lib/canon/xml/sax/probe.rb
|
|
440
441
|
- lib/canon/xml/sax_builder.rb
|
|
441
442
|
- lib/canon/xml/tree_builder.rb
|
|
442
443
|
- lib/canon/xml/whitespace_normalizer.rb
|