canon 0.3.115 → 0.3.116
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/schema_gate.rb +29 -0
- data/lib/canon/comparison.rb +28 -0
- data/lib/canon/digest.rb +44 -0
- data/lib/canon/validators/xsd_validator.rb +57 -0
- data/lib/canon/validators.rb +1 -0
- data/lib/canon/version.rb +1 -1
- data/lib/canon.rb +11 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89df5f303938b98100a1d44399c7eb5fbbf793f0f91c445c4f0533a19234d5ec
|
|
4
|
+
data.tar.gz: 1308c9228b0a6efc15e81ebef134c7495aadd100e10978a23ee09b92ad558555
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 93cfb6bca734697c5cc79173dac20bbe53cb4fb17e72fc72e7aa726af366c187c09ae0c30e5a775d9ea8b9e94f1194b29657f9da3d44c58f6fbc116717e39909
|
|
7
|
+
data.tar.gz: 37f713fb17fe7961cb622f42c96605cf0bb19227a6831018d0505afdc33ad2e315e54b1a65145219457e30d8b0c6d06d001869611bbe94f55b443c2cbf0e4252
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Canon
|
|
4
|
+
module Comparison
|
|
5
|
+
# W5.1 validate-then-compare: runs both documents through the
|
|
6
|
+
# engine's XSD assessment before the diff. Returns a hash of
|
|
7
|
+
# side-index (0 or 1) to Canon::ValidationError — empty when both
|
|
8
|
+
# documents pass and the comparison may proceed.
|
|
9
|
+
module SchemaGate
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# @param obj1 [String] first XML document
|
|
13
|
+
# @param obj2 [String] second XML document
|
|
14
|
+
# @param schema [String, Leptris::XML::XSD::Schema] schema text
|
|
15
|
+
# (compiled once here) or a compiled schema
|
|
16
|
+
# @return [Hash{Integer=>Canon::ValidationError}]
|
|
17
|
+
def gate(obj1, obj2, schema)
|
|
18
|
+
schema = Canon::Validators::XsdValidator.compile(schema) if schema.is_a?(String)
|
|
19
|
+
failures = {}
|
|
20
|
+
[obj1, obj2].each_with_index do |obj, side|
|
|
21
|
+
Canon::Validators::XsdValidator.validate!(obj, schema: schema)
|
|
22
|
+
rescue Canon::ValidationError => e
|
|
23
|
+
failures[side] = e
|
|
24
|
+
end
|
|
25
|
+
failures
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/canon/comparison.rb
CHANGED
|
@@ -95,6 +95,7 @@ module Canon
|
|
|
95
95
|
autoload :DiffNodeBuilder, "canon/comparison/diff_node_builder"
|
|
96
96
|
autoload :Dimensions, "canon/comparison/dimensions"
|
|
97
97
|
autoload :FormatDetector, "canon/comparison/format_detector"
|
|
98
|
+
autoload :SchemaGate, "canon/comparison/schema_gate"
|
|
98
99
|
autoload :HtmlComparator, "canon/comparison/html_comparator"
|
|
99
100
|
autoload :HtmlCompareProfile, "canon/comparison/html_compare_profile"
|
|
100
101
|
autoload :HtmlParser, "canon/comparison/html_parser"
|
|
@@ -246,6 +247,33 @@ module Canon
|
|
|
246
247
|
:semantic_tree))
|
|
247
248
|
end
|
|
248
249
|
|
|
250
|
+
# W5.1 schema gate: when a schema rides the options, both XML
|
|
251
|
+
# documents must pass the engine's XSD assessment BEFORE the
|
|
252
|
+
# diff runs — a validation failure is normative regardless of
|
|
253
|
+
# what the tree comparison would say.
|
|
254
|
+
if (xsd = opts[:xsd])
|
|
255
|
+
gate = SchemaGate.gate(obj1, obj2, xsd)
|
|
256
|
+
unless gate.empty?
|
|
257
|
+
diffs = gate.map do |side, error|
|
|
258
|
+
node = Diff::DiffNode.new(node1: side.zero? ? obj1 : obj2,
|
|
259
|
+
node2: side.zero? ? obj2 : obj1,
|
|
260
|
+
dimension: :schema_validation,
|
|
261
|
+
reason: error.message)
|
|
262
|
+
node.normative = true # a schema failure is normative by definition
|
|
263
|
+
node
|
|
264
|
+
end
|
|
265
|
+
return false unless opts[:verbose]
|
|
266
|
+
|
|
267
|
+
return ComparisonResult.new(
|
|
268
|
+
differences: diffs,
|
|
269
|
+
preprocessed_strings: [obj1.to_s, obj2.to_s],
|
|
270
|
+
format: opts[:format] || :xml,
|
|
271
|
+
algorithm: :dom,
|
|
272
|
+
original_strings: [obj1.to_s, obj2.to_s],
|
|
273
|
+
)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
249
277
|
if %i[semantic semantic_tree].include?(opts[:diff_algorithm])
|
|
250
278
|
return semantic_diff(obj1, obj2, opts)
|
|
251
279
|
end
|
data/lib/canon/digest.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module Canon
|
|
6
|
+
# W5.3 — canonical digests: the hash of a document's canonical form
|
|
7
|
+
# as a first-class API for deduplication and change detection.
|
|
8
|
+
#
|
|
9
|
+
# The digest is taken over the format's CANONICAL form — the form
|
|
10
|
+
# under which the comparison pipeline deems documents equivalent:
|
|
11
|
+
# XML rides Canonical XML 1.1 (attribute order sorted,
|
|
12
|
+
# namespace-normalized); JSON/YAML ride the key-sorted forms; HTML
|
|
13
|
+
# rides the whitespace-law form with sorted attributes. Two
|
|
14
|
+
# documents that compare equivalent under the canonical form
|
|
15
|
+
# produce identical digests. The digests are stable across releases
|
|
16
|
+
# for as long as the canonical form is (the digest-stability suite
|
|
17
|
+
# pins the bytes per release pair).
|
|
18
|
+
module Digest
|
|
19
|
+
DEFAULT_ALGORITHM = "sha256"
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# @param content [String] the document
|
|
23
|
+
# @param format [Symbol] the format (see SUPPORTED_FORMATS)
|
|
24
|
+
# @param algorithm [String, Symbol] any OpenSSL digest name
|
|
25
|
+
# @return [String] the hex digest of the canonical form
|
|
26
|
+
def digest(content, format, algorithm: DEFAULT_ALGORITHM)
|
|
27
|
+
OpenSSL::Digest.new(algorithm.to_s)
|
|
28
|
+
.hexdigest(canonical_form(content, format))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The canonical form the digest rides: XML's is C14N (the
|
|
32
|
+
# pretty-printed form preserves attribute order and is NOT
|
|
33
|
+
# canonical); every other format's formatter output IS the
|
|
34
|
+
# canonical form.
|
|
35
|
+
def canonical_form(content, format)
|
|
36
|
+
if format.to_sym == :xml
|
|
37
|
+
Formatters::XmlFormatter.format(content, pretty: false)
|
|
38
|
+
else
|
|
39
|
+
Canon.format(content, format)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "leptris" # the XSD lane rides the engine's tier-1 validator
|
|
4
|
+
# (libleptris >= 1.9.321, leptris#1075)
|
|
5
|
+
|
|
6
|
+
module Canon
|
|
7
|
+
module Validators
|
|
8
|
+
# XSD validation (W5.1) through the engine's tier-1 validator:
|
|
9
|
+
# schema compilation, whole-instance assessment, enumerated
|
|
10
|
+
# failures. The schema is a compiled `Leptris::XML::XSD::Schema`
|
|
11
|
+
# — callers validating many documents against one schema compile
|
|
12
|
+
# once and reuse (compilation is the expensive step).
|
|
13
|
+
#
|
|
14
|
+
# schema = Canon::Validators::XsdValidator.compile(schema_xml)
|
|
15
|
+
# Canon::Validators::XsdValidator.validate!(xml, schema: schema)
|
|
16
|
+
class XsdValidator < BaseValidator
|
|
17
|
+
# Compile a schema from XSD text. Raises Canon::ValidationError
|
|
18
|
+
# when the text is not well-formed XML, and when the engine
|
|
19
|
+
# reports schema-level problems.
|
|
20
|
+
#
|
|
21
|
+
# @param schema_xml [String] the XSD document
|
|
22
|
+
# @return [Leptris::XML::XSD::Schema] the compiled schema
|
|
23
|
+
def self.compile(schema_xml)
|
|
24
|
+
schema = Leptris::XML::XSD.compile(schema_xml)
|
|
25
|
+
if (detail = schema.error)
|
|
26
|
+
raise Canon::ValidationError.new(
|
|
27
|
+
"schema invalid: #{detail}",
|
|
28
|
+
format: :xml,
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
schema
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Validate an XML instance against a compiled schema
|
|
35
|
+
#
|
|
36
|
+
# @param input [String] the XML instance
|
|
37
|
+
# @param schema [Leptris::XML::XSD::Schema] a compiled schema
|
|
38
|
+
# @raise [Canon::ValidationError] if the instance fails
|
|
39
|
+
# assessment; the message carries the first failure, details
|
|
40
|
+
# carry the full enumerated list
|
|
41
|
+
# @return [void]
|
|
42
|
+
def self.validate!(input, schema:)
|
|
43
|
+
return if input.nil? || input.strip.empty?
|
|
44
|
+
|
|
45
|
+
doc = Leptris::XML.parse(input, readonly: true)
|
|
46
|
+
errors = schema.validate_errors(doc)
|
|
47
|
+
return if errors.empty?
|
|
48
|
+
|
|
49
|
+
raise Canon::ValidationError.new(
|
|
50
|
+
errors.first.to_s,
|
|
51
|
+
format: :xml,
|
|
52
|
+
details: errors.join("\n"),
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/canon/validators.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Canon
|
|
|
7
7
|
autoload :HtmlValidator, "canon/validators/html_validator"
|
|
8
8
|
autoload :JsonValidator, "canon/validators/json_validator"
|
|
9
9
|
autoload :XmlValidator, "canon/validators/xml_validator"
|
|
10
|
+
autoload :XsdValidator, "canon/validators/xsd_validator"
|
|
10
11
|
autoload :YamlValidator, "canon/validators/yaml_validator"
|
|
11
12
|
end
|
|
12
13
|
end
|
data/lib/canon/version.rb
CHANGED
data/lib/canon.rb
CHANGED
|
@@ -32,6 +32,7 @@ module Canon
|
|
|
32
32
|
autoload :Cli, "canon/cli"
|
|
33
33
|
autoload :ColorDetector, "canon/color_detector"
|
|
34
34
|
autoload :DiffFormatter, "canon/diff_formatter"
|
|
35
|
+
autoload :Digest, "canon/digest"
|
|
35
36
|
autoload :Json, "canon/json"
|
|
36
37
|
|
|
37
38
|
SUPPORTED_FORMATS = %i[xml yaml json html html4 html5 string].freeze
|
|
@@ -84,5 +85,15 @@ module Canon
|
|
|
84
85
|
define_singleton_method("format_#{format}") do |content|
|
|
85
86
|
format(content, format)
|
|
86
87
|
end
|
|
88
|
+
|
|
89
|
+
define_singleton_method("digest_#{format}") do |content, algorithm: Canon::Digest::DEFAULT_ALGORITHM|
|
|
90
|
+
Canon::Digest.digest(content, format, algorithm: algorithm)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The canonical digest of a document (W5.3): the hash of its
|
|
95
|
+
# canonical form. See Canon::Digest for the stability contract.
|
|
96
|
+
def self.digest(content, format = :xml, algorithm: Canon::Digest::DEFAULT_ALGORITHM)
|
|
97
|
+
Canon::Digest.digest(content, format, algorithm: algorithm)
|
|
87
98
|
end
|
|
88
99
|
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.116
|
|
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-10-
|
|
11
|
+
date: 2026-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|
|
@@ -267,6 +267,7 @@ files:
|
|
|
267
267
|
- lib/canon/comparison/pipeline.rb
|
|
268
268
|
- lib/canon/comparison/profile_definition.rb
|
|
269
269
|
- lib/canon/comparison/ruby_object_comparator.rb
|
|
270
|
+
- lib/canon/comparison/schema_gate.rb
|
|
270
271
|
- lib/canon/comparison/strategies.rb
|
|
271
272
|
- lib/canon/comparison/strategies/base_match_strategy.rb
|
|
272
273
|
- lib/canon/comparison/strategies/match_strategy_factory.rb
|
|
@@ -338,6 +339,7 @@ files:
|
|
|
338
339
|
- lib/canon/diff_formatter/legend.rb
|
|
339
340
|
- lib/canon/diff_formatter/pretty_diff_formatter.rb
|
|
340
341
|
- lib/canon/diff_formatter/theme.rb
|
|
342
|
+
- lib/canon/digest.rb
|
|
341
343
|
- lib/canon/errors.rb
|
|
342
344
|
- lib/canon/formatters.rb
|
|
343
345
|
- lib/canon/formatters/html4_formatter.rb
|
|
@@ -404,6 +406,7 @@ files:
|
|
|
404
406
|
- lib/canon/validators/html_validator.rb
|
|
405
407
|
- lib/canon/validators/json_validator.rb
|
|
406
408
|
- lib/canon/validators/xml_validator.rb
|
|
409
|
+
- lib/canon/validators/xsd_validator.rb
|
|
407
410
|
- lib/canon/validators/yaml_validator.rb
|
|
408
411
|
- lib/canon/version.rb
|
|
409
412
|
- lib/canon/xml.rb
|