canon 0.3.45 → 0.3.47

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: a17cd55bc56c7287d027191b11cd33a6bc0999b29abce17bb8c368758379e4ad
4
- data.tar.gz: 7b7c89255ad4134219b512265b6b26c08a1352742dfc1500a821a830fa3cd2d3
3
+ metadata.gz: 8180f10b0965070c408985ca44868fc14ae2256f24dac607492470aea7e5d076
4
+ data.tar.gz: e50498b713b972237a67af3b3f5c2cd5fb0cf1cbc3fef0dbb57f628810ce2341
5
5
  SHA512:
6
- metadata.gz: d4673a7e48e7fb4aa901ece0c6fc3c097e3c046af4aee63b959b218d8809a32ac9e8f4ea3db990ff969f521d9842e826e03f4815da8610fba7138070cc420f8e
7
- data.tar.gz: f5771189aed0ef86eb98b32b9bd94261221688aece84f6f7b44861c66afcde65d80b3b0a2cb599bb96c18aaa575da4ff86d1bb385b48ff7a3ff34c858342a632
6
+ metadata.gz: 52c5eb3fcb8ab3e09c7de5b8b7120f2baad191467aeccab6154254cd0007128f76ae2f5dbd26102abf19c3cf9f289f3c0445db88917562d7b7edafe9aef7ae5b
7
+ data.tar.gz: 373032bcba62f9685c41f09bb0d3dce294c50fa8785252e3737231a67f9a0fdfdb5372c85f3b28da5983faf39d4cdf89723dfae8397969511d2688afecaf3418
@@ -21,19 +21,22 @@ module Canon
21
21
  # @param text1 [String] The first document (preprocessed)
22
22
  # @param text2 [String] The second document (preprocessed)
23
23
  # @return [Array<DiffLine>] The assembled diff lines
24
- def self.build(diff_nodes, text1, text2)
24
+ def self.build(diff_nodes, text1, text2, lines1: nil, lines2: nil)
25
25
  return [] if diff_nodes.nil? || diff_nodes.empty?
26
26
  return [] if text1.nil? || text2.nil?
27
27
 
28
- new(diff_nodes, text1, text2).build
28
+ new(diff_nodes, text1, text2, lines1, lines2).build
29
29
  end
30
30
 
31
- def initialize(diff_nodes, text1, text2)
31
+ def initialize(diff_nodes, text1, text2, lines1 = nil, lines2 = nil)
32
32
  @diff_nodes = diff_nodes
33
33
  @text1 = text1
34
34
  @text2 = text2
35
- @lines1 = text1.split("\n")
36
- @lines2 = text2.split("\n")
35
+ # The caller may hand in the already-split lines (the
36
+ # formatter splits these documents too); re-splitting both
37
+ # documents per pipeline stage was ~2x the line strings.
38
+ @lines1 = lines1 || text1.split("\n")
39
+ @lines2 = lines2 || text2.split("\n")
37
40
  # Build reverse indices for efficient content lookup in gap handling.
38
41
  # Maps content string to array of line indices where that content appears.
39
42
  @line_to_indices1 = build_line_index(@lines1)
@@ -22,21 +22,22 @@ module Canon
22
22
  # @param text1 [String] The first document (preprocessed)
23
23
  # @param text2 [String] The second document (preprocessed)
24
24
  # @return [Array<DiffNode>] The same DiffNodes, enriched in place
25
- def self.build(diff_nodes, text1, text2)
25
+ def self.build(diff_nodes, text1, text2, lines1: nil, lines2: nil)
26
26
  return diff_nodes if diff_nodes.nil? || diff_nodes.empty?
27
27
  return diff_nodes if text1.nil? || text2.nil?
28
28
 
29
- new(diff_nodes, text1, text2).enrich
29
+ new(diff_nodes, text1, text2, lines1, lines2).enrich
30
30
  end
31
31
 
32
- def initialize(diff_nodes, text1, text2)
32
+ def initialize(diff_nodes, text1, text2, lines1 = nil, lines2 = nil)
33
33
  @diff_nodes = diff_nodes
34
34
  @text1 = text1
35
35
  @text2 = text2
36
36
  @line_map1 = SourceLocator.build_line_map(text1)
37
37
  @line_map2 = SourceLocator.build_line_map(text2)
38
- @lines1 = text1.split("\n")
39
- @lines2 = text2.split("\n")
38
+ # Shared split — see DiffLineBuilder's note.
39
+ @lines1 = lines1 || text1.split("\n")
40
+ @lines2 = lines2 || text2.split("\n")
40
41
  # Track occurrences for text_content dimension to find correct element instance
41
42
  @text_occurrence1 = Hash.new(0)
42
43
  @text_occurrence2 = Hash.new(0)
@@ -44,12 +44,22 @@ module Canon
44
44
  # Compute line number width BEFORE formatting
45
45
  compute_line_num_width(doc1, doc2)
46
46
 
47
+ # The pipeline splits both documents once here; every stage
48
+ # downstream reuses the arrays (three redundant full-doc
49
+ # splits per render before).
50
+ @pipeline_lines1 = doc1.split("\n")
51
+ @pipeline_lines2 = doc2.split("\n")
52
+
47
53
  # Phase 1: Enrich DiffNodes with character positions
48
- Canon::Diff::DiffNodeEnricher.build(@differences, doc1, doc2)
54
+ Canon::Diff::DiffNodeEnricher.build(@differences, doc1, doc2,
55
+ lines1: @pipeline_lines1,
56
+ lines2: @pipeline_lines2)
49
57
 
50
58
  # Phase 2: Assemble DiffLines from enriched DiffNodes
51
59
  diff_lines = Canon::Diff::DiffLineBuilder.build(@differences, doc1,
52
- doc2)
60
+ doc2,
61
+ lines1: @pipeline_lines1,
62
+ lines2: @pipeline_lines2)
53
63
 
54
64
  # Layers 3-5: Build report through pipeline
55
65
  report = Canon::Diff::DiffReportBuilder.build(
@@ -178,8 +188,8 @@ module Canon
178
188
  def format_report(report, doc1, doc2)
179
189
  return "" if report.contexts.empty?
180
190
 
181
- lines1 = doc1.split("\n")
182
- lines2 = doc2.split("\n")
191
+ lines1 = @pipeline_lines1 || doc1.split("\n")
192
+ lines2 = @pipeline_lines2 || doc2.split("\n")
183
193
 
184
194
  output = []
185
195
 
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.45"
4
+ VERSION = "0.3.47"
5
5
  end
@@ -23,19 +23,24 @@ 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). libleptris 1.9.164
27
- # closed four of the five leptris#1015 families (attribute
28
- # ordering, prefix loss and rebinding, `>` and TAB escaping —
29
- # unpinned in the parity spec since gem 1.9.174.0). A local
30
- # default-flip attempt surfaced three MORE divergences the
31
- # edge corpus did not cover, so the lane stays opt-in
32
- # (CANON_C14N_BACKEND=leptris) until they close (leptris#1096):
33
- # redundant namespace redeclarations are kept (Ruby removes
34
- # them), xmlns:xml with the standard URI is kept (Ruby omits
35
- # it), and document-level processing instructions are dropped.
36
- # The Ruby lane additionally validates relative namespace URIs
37
- # at parse time a future flip must carry that check into the
38
- # native wrapper. Comments mode keeps the Ruby path regardless.
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).
39
44
  def self.native_canonicalize(xml, with_comments)
40
45
  return nil if with_comments
41
46
  return nil if RUBY_ENGINE == "opal"
@@ -44,6 +49,8 @@ module Canon
44
49
  Canon::XmlParsing.moxml_adapter_name == :leptris
45
50
  return nil unless native_c14n_available?
46
51
 
52
+ validate_relative_namespaces!(xml)
53
+
47
54
  doc = Canon::XmlParsing.moxml_context.parse(xml, readonly: true,
48
55
  strict: false)
49
56
  begin
@@ -58,6 +65,25 @@ module Canon
58
65
  nil
59
66
  end
60
67
 
68
+ # Canon::Error parity with the Ruby lane, whose parse rejects
69
+ # relative namespace URIs. A declaration-shaped scan; a
70
+ # relative-URI-shaped string inside CDATA is the only false
71
+ # positive this can produce.
72
+ RELATIVE_NS_DECL = /xmlns(?::[\w.-]+)?="([^"]*)"/
73
+ URI_SCHEME = %r{\A[a-zA-Z][a-zA-Z0-9+.-]*:}
74
+
75
+ def self.validate_relative_namespaces!(xml)
76
+ return unless xml.is_a?(String)
77
+
78
+ xml.scan(RELATIVE_NS_DECL) do |(uri)|
79
+ next if uri.nil? || uri.empty?
80
+
81
+ unless uri.match?(URI_SCHEME)
82
+ raise Canon::Error, "Relative namespace URI not allowed: #{uri}"
83
+ end
84
+ end
85
+ end
86
+
61
87
  def self.native_c14n_available?
62
88
  return @native_c14n_available unless @native_c14n_available.nil?
63
89
 
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.45
4
+ version: 0.3.47
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.