moxml 0.5.91 → 0.5.92

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: 6b290a2b707e7aec576eabf853e5e8434cb070369477d5ebc41735cadf8a0582
4
- data.tar.gz: 1dd022ba5bd37967027aecaee876750134fb65086d595671c42f6dbebcdc3f8e
3
+ metadata.gz: 4e9550ec966450192384ac9f4059a182a52107fac47420010517fe2fff7c8d1c
4
+ data.tar.gz: ca3e1a10e2a0669991391ce82909360a66fd17cbd98e454aff99f5d1f1bcb4d4
5
5
  SHA512:
6
- metadata.gz: 1ecefd8a5388a448cc018b53b00ae11a5c91786f7641d40da11a5bcb46c957031da1a44d7b00bdb0fc086246750bb6a74af267f7cada75380ca5514a44d897b3
7
- data.tar.gz: d344520c29067fb740f3372c2c2ea16791f9d9e88a7c7faea7e033943852af9a2e40e7e4404e0a7eb9dc9dd223c41d0ff68d2cdab1538f1f1d62c5347ab0fac1
6
+ metadata.gz: b5de2cfb1875e9947b25cabd166f2f1e2c3ec6994d9ea8376a3b42ae5afd7bfe450ae3fed1306e25748cfdedc6223cf46848c5202e1d4cf898d6df2ac04f2abd
7
+ data.tar.gz: 1f5922e2968fdc796df5a183bbd1d876b2c66618eef068c08005c528cf735df0b193b7143f856abe381aa2ec37224c6302359b7f188eb6c3342b680a729c0475
@@ -1140,9 +1140,14 @@ module Moxml
1140
1140
  (prefix = node.prefix || prefix_part(node.name))
1141
1141
  resolved = resolve_prefix_ns(node.element, prefix)
1142
1142
  return resolved if resolved
1143
+ # The xml prefix is reserved (Namespaces in XML 1.0 §2.10):
1144
+ # bound to its URI on every element, never in
1145
+ # namespace_definitions, so the scope walk always misses.
1146
+ return implicit_xml_namespace(node) if prefix == "xml"
1143
1147
  end
1144
1148
 
1145
1149
  ns = node.namespace
1150
+ return implicit_xml_namespace(node) if reserved_xml_uri?(ns)
1146
1151
  return ns unless ns.nil?
1147
1152
 
1148
1153
  # Set-side namespaces (created attributes, renamed elements)
@@ -1174,6 +1179,30 @@ module Moxml
1174
1179
  nil
1175
1180
  end
1176
1181
 
1182
+ # The binding reports an attribute's namespace as a bare URI
1183
+ # String; when no prefix can be recovered from the qualified
1184
+ # name, the reserved xml binding must still answer for xml:*
1185
+ # attributes.
1186
+ RESERVED_XML_URI = "http://www.w3.org/XML/1998/namespace"
1187
+
1188
+ def reserved_xml_uri?(namespace)
1189
+ case namespace
1190
+ when ::Leptris::XML::Namespace
1191
+ namespace.prefix.to_s.empty? && namespace.href == RESERVED_XML_URI
1192
+ when String
1193
+ namespace == RESERVED_XML_URI
1194
+ else
1195
+ false
1196
+ end
1197
+ end
1198
+
1199
+ def implicit_xml_namespace(node)
1200
+ owner = node.is_a?(::Leptris::XML::Attr) ? node.element : node
1201
+ return nil unless owner.is_a?(::Leptris::XML::Element)
1202
+
1203
+ ::Leptris::XML::Namespace.new(owner, RESERVED_XML_URI, prefix: "xml")
1204
+ end
1205
+
1177
1206
  def prefix_part(name)
1178
1207
  name.include?(":") ? name.split(":", 2)[0] : nil
1179
1208
  end
@@ -1889,7 +1918,14 @@ module Moxml
1889
1918
  end
1890
1919
 
1891
1920
  def comment_content(node)
1892
- return NN_CONTENT.bind_call(node) if NATIVE_READ_LAYER && node.is_a?(NN)
1921
+ if NATIVE_READ_LAYER && node.is_a?(NN)
1922
+ content = NN_CONTENT.bind_call(node)
1923
+ # NativeNode#content answers nil for comment-kind nodes
1924
+ # (leptris-ruby#313); the shared-pointer bridge has it.
1925
+ return to_binding(node).content if content.nil?
1926
+
1927
+ return content
1928
+ end
1893
1929
 
1894
1930
  node.content
1895
1931
  end
@@ -50,6 +50,35 @@ module Moxml
50
50
  true
51
51
  end
52
52
 
53
+ # libxml2-native inclusive C14N: delegated for the default
54
+ # shape (Inclusive 1.0, no comments, no InclusiveNamespaces
55
+ # prefix list) when the probe below proves libxml2's output
56
+ # byte-identical to the Ruby reference (ported from canon).
57
+ # Every other combination keeps the Ruby engine.
58
+ # Lazy, not load-time: the probe exercises the wrapper layer
59
+ # (Document/C14n), which is circular while THIS adapter file
60
+ # is still loading.
61
+ def native_inclusive10(native)
62
+ return nil unless native_c14n_byte_safe?
63
+
64
+ native.canonicalize(::Nokogiri::XML::XML_C14N_1_0)
65
+ end
66
+
67
+ def native_c14n_byte_safe?
68
+ return @native_c14n_byte_safe unless @native_c14n_byte_safe.nil?
69
+
70
+ @native_c14n_byte_safe = begin
71
+ probe_xml = %(<?xml version="1.0"?><doc xmlns:p="urn:p" xmlns="urn:d" b="2" a="1"><e p:x="v" z="w">t &amp; u</e><!-- c --></doc>)
72
+ native_doc = ::Nokogiri::XML(probe_xml) { |c| c.strict.nonet }
73
+ native = native_doc.root.canonicalize(::Nokogiri::XML::XML_C14N_1_0)
74
+ wrapper = Wrappers::Document.new(native_doc, Context.new(:nokogiri))
75
+ reference = Moxml::C14n::Inclusive10.new.canonicalize(wrapper.root)
76
+ native == reference
77
+ rescue StandardError
78
+ false
79
+ end
80
+ end
81
+
53
82
  def set_root(doc, element)
54
83
  doc.root = element
55
84
  end
@@ -278,6 +278,9 @@ module Moxml
278
278
  result = []
279
279
  element.attributes.each_attribute do |attr|
280
280
  next if attr.prefix.to_s.start_with?("xmlns")
281
+ # REXML yields the default declaration with an empty prefix
282
+ # and name "xmlns"; treat it like any other declaration.
283
+ next if attr.name == "xmlns" && attr.prefix.to_s.empty?
281
284
 
282
285
  result << attr
283
286
  end
@@ -33,17 +33,25 @@ module Moxml
33
33
  root = Nodes::RootNode.new
34
34
 
35
35
  if document.root
36
- root.add_child(build_element_node(document.root))
37
- # Iterate ALL document children — not just the root element.
38
- # This captures PIs and comments that appear outside the
39
- # document element, which are part of the canonical form.
36
+ # Document-level children (PIs and comments outside the
37
+ # document element) are part of the canonical form and must
38
+ # keep document order — a leading PI canonicalizes before the
39
+ # root element (spec §2.1; the native engines emit it there
40
+ # too). The document element is the first element child: some
41
+ # adapters mint fresh wrappers per children call, so wrapper
42
+ # identity cannot locate it.
43
+ seen_root = false
40
44
  document.children.each do |child|
41
- next if child.equal?(document.root)
42
- next if child.is_a?(::Moxml::Element)
45
+ if child.is_a?(::Moxml::Element)
46
+ next if seen_root
47
+
48
+ seen_root = true
49
+ end
43
50
 
44
51
  built = build_node(child)
45
52
  root.add_child(built) if built
46
53
  end
54
+ root.add_child(build_element_node(document.root)) unless seen_root
47
55
  end
48
56
 
49
57
  root
@@ -52,7 +60,9 @@ module Moxml
52
60
  def self.build_node(moxml_node)
53
61
  case moxml_node
54
62
  when ::Moxml::Element then build_element_node(moxml_node)
55
- when ::Moxml::Text then build_text_node(moxml_node)
63
+ # CDATA sections are character data to canonicalization: their
64
+ # content is emitted as escaped text (spec §3.1 "text nodes").
65
+ when ::Moxml::Text, ::Moxml::Cdata then build_text_node(moxml_node)
56
66
  when ::Moxml::Comment then build_comment_node(moxml_node)
57
67
  when ::Moxml::ProcessingInstruction then build_pi_node(moxml_node)
58
68
  end
@@ -94,12 +104,21 @@ module Moxml
94
104
  def self.build_attribute_nodes(moxml_element, element)
95
105
  moxml_element.attributes.each do |attr|
96
106
  ns = attr.namespace
107
+ # The xml prefix is reserved: any attribute bound to the XML
108
+ # namespace URI renders (and sorts) as prefix "xml" regardless
109
+ # of how the adapter reports it (spec §2.3 attribute-axis key).
110
+ prefix = ns&.prefix
111
+ uri = ns&.uri
112
+ if prefix == "xml" || uri == XML_URI
113
+ prefix = "xml"
114
+ uri = XML_URI
115
+ end
97
116
  element.add_attribute(
98
117
  Nodes::AttributeNode.new(
99
118
  name: attr.name,
100
119
  value: attr.value,
101
- namespace_uri: ns&.uri,
102
- prefix: ns&.prefix,
120
+ namespace_uri: uri,
121
+ prefix: prefix,
103
122
  ),
104
123
  )
105
124
  end
data/lib/moxml/c14n.rb CHANGED
@@ -39,7 +39,7 @@ module Moxml
39
39
  # :exclusive10 Exclusive C14N 1.0 (W3C REC-xml-exc-c14n-20020718)
40
40
  def self.canonicalize(node_or_xml, with_comments: false,
41
41
  algorithm: :inclusive10, inclusive_namespaces: [])
42
- if (native = native_inclusive10(node_or_xml, with_comments,
42
+ if (native = native_inclusive10(node_or_xml, algorithm, with_comments,
43
43
  inclusive_namespaces))
44
44
  return native
45
45
  end
@@ -52,13 +52,15 @@ module Moxml
52
52
  end
53
53
 
54
54
  # The leptris engine canonicalizes in C — 23x the Ruby reference.
55
- # Delegated only for the exact default shape (Inclusive 1.0, no
56
- # comments, no InclusiveNamespace prefix list) on wrappers whose
57
- # adapter passed the NATIVE_C14N_BYTE_SAFE load probe; every
55
+ # Delegated only for the exact default shape (Inclusive 1.0
56
+ # algorithm, no comments, no InclusiveNamespace prefix list) on
57
+ # wrappers whose adapter passed the byte-safety probe; every
58
58
  # other combination keeps the Ruby engine (node-set subsets,
59
59
  # xml:base fixup, exclusive, 1.1 stay on the ported
60
60
  # implementation regardless).
61
- def self.native_inclusive10(node_or_xml, with_comments, inclusive_namespaces)
61
+ def self.native_inclusive10(node_or_xml, algorithm, with_comments,
62
+ inclusive_namespaces)
63
+ return nil unless algorithm.nil? || algorithm == :inclusive10
62
64
  return nil if with_comments || !inclusive_namespaces.empty?
63
65
  return nil unless algorithm_shape_inclusive10?(node_or_xml)
64
66
 
data/lib/moxml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.91"
4
+ VERSION = "0.5.92"
5
5
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "moxml/c14n"
5
+
6
+ CORPUS = {
7
+ "cdata content becomes escaped text" => [
8
+ "<doc><![CDATA[a < b]]>tail</doc>",
9
+ "<doc>a &lt; btail</doc>",
10
+ ],
11
+ "prefixed and unprefixed attributes" => [
12
+ %(<doc xmlns:p="urn:p"><e p:x="1" y="2"/></doc>),
13
+ %(<doc xmlns:p="urn:p"><e y="2" p:x="1"></e></doc>),
14
+ ],
15
+ "reserved xml:* attributes keep the xml prefix" => [
16
+ %(<doc xml:base="http://a/b/" xml:lang="en" xml:space="preserve"><e xml:id="i1"/></doc>),
17
+ %(<doc xml:base="http://a/b/" xml:lang="en" xml:space="preserve"><e xml:id="i1"></e></doc>),
18
+ ],
19
+ "default namespace transitions" => [
20
+ %(<doc xmlns="urn:a"><e xmlns=""/><e xmlns="urn:a"/></doc>),
21
+ %(<doc xmlns="urn:a"><e xmlns=""></e><e></e></doc>),
22
+ ],
23
+ "namespace redeclaration" => [
24
+ %(<doc xmlns:m="urn:m"><m:e xmlns:m="urn:m2"/></doc>),
25
+ %(<doc xmlns:m="urn:m"><m:e xmlns:m="urn:m2"></m:e></doc>),
26
+ ],
27
+ "prefixed namespace ordering" => [
28
+ %(<r xmlns:z="urn:z" xmlns:a="urn:a" b="2" a="1"/>),
29
+ %(<r xmlns:a="urn:a" xmlns:z="urn:z" a="1" b="2"></r>),
30
+ ],
31
+ "entities stay escaped" => [
32
+ "<doc>a &amp; b &lt; c</doc>",
33
+ "<doc>a &amp; b &lt; c</doc>",
34
+ ],
35
+ }.freeze
36
+
37
+ RSpec.describe "Moxml::C14n cross-adapter canonicalization" do
38
+ # Every installed adapter must answer identical canonical bytes: the
39
+ # Ruby reference path (Inclusive10) is shared, but attribute/namespace
40
+ # reporting differs per adapter, and two adapters additionally
41
+ # delegate the default shape to a native engine (leptris in C,
42
+ # nokogiri via libxml2).
43
+ describe "byte-identical canonical form across adapters" do
44
+ Moxml::Adapter::AVAILABLE_ADAPTERS.each do |adapter_name|
45
+ context "with the #{adapter_name} adapter" do
46
+ let(:ctx) { Moxml.new(adapter_name) }
47
+
48
+ CORPUS.each do |label, (input, expected)|
49
+ it "canonicalizes #{label}" do
50
+ root = ctx.parse(input).root
51
+ expect(Moxml::C14n.canonicalize_inclusive10(root)).to eq(expected)
52
+ expect(Moxml::C14n.canonicalize(root)).to eq(expected)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "document-level nodes keep document order" do
60
+ # oga surfaces the XML declaration as a <?xml?> PI and keeps the
61
+ # separator space in PI content; ox/headed_ox drop PI content on
62
+ # parse. Both are pre-existing parse-layer divergences, out of
63
+ # scope here.
64
+ let(:adapters) { Moxml::Adapter::AVAILABLE_ADAPTERS - %i[oga ox headed_ox] }
65
+
66
+ it "canonicalizes a leading PI before the root element" do
67
+ adapters.each do |adapter_name|
68
+ doc = Moxml.new(adapter_name)
69
+ .parse(%(<?xml version="1.0"?><?render mode="x"?><doc><e/></doc>))
70
+ expect(Moxml::C14n.canonicalize(doc))
71
+ .to eq(%(<?render mode="x"?>\n<doc><e></e></doc>))
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "native delegation" do
77
+ adapters_with_native = %i[leptris nokogiri].select do |name|
78
+ Moxml::Adapter::AVAILABLE_ADAPTERS.include?(name)
79
+ end
80
+
81
+ adapters_with_native.each do |adapter_name|
82
+ context "with the #{adapter_name} adapter" do
83
+ let(:adapter_class) do
84
+ Moxml::Adapter.const_get(adapter_name.to_s.split("_").map(&:capitalize).join)
85
+ end
86
+
87
+ it "passes the byte-safety probe" do
88
+ expect(adapter_class.native_c14n_byte_safe?).to be(true)
89
+ end
90
+
91
+ it "engages the native path for the default shape" do
92
+ doc = Moxml.new(adapter_name)
93
+ .parse(%(<doc xmlns="urn:d" a="1"><e>t</e></doc>))
94
+ native = Moxml::C14n.native_inclusive10(doc.root, :inclusive10, false, [])
95
+ expect(native).to be_a(String)
96
+ end
97
+
98
+ it "keeps exclusive on the Ruby engine" do
99
+ doc = Moxml.new(adapter_name)
100
+ .parse(%(<root xmlns:foo='urn:foo'><foo:a/></root>))
101
+ expect(Moxml::C14n.canonicalize(doc.root, algorithm: :exclusive10))
102
+ .to eq(Moxml::C14n.canonicalize_exclusive(doc.root))
103
+ end
104
+
105
+ it "keeps inclusive 1.1 on the Ruby engine" do
106
+ doc = Moxml.new(adapter_name).parse(%(<root a="1"/>))
107
+ expect(Moxml::C14n.canonicalize(doc.root, algorithm: :inclusive11))
108
+ .to eq(Moxml::C14n.canonicalize_inclusive11(doc.root))
109
+ end
110
+
111
+ it "keeps with_comments on the Ruby engine" do
112
+ doc = Moxml.new(adapter_name).parse(%(<doc><!-- c --><e/></doc>))
113
+ expect(Moxml::C14n.canonicalize(doc.root, with_comments: true))
114
+ .to eq(Moxml::C14n.canonicalize_inclusive10(doc.root,
115
+ with_comments: true))
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.91
4
+ version: 0.5.92
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -457,6 +457,7 @@ files:
457
457
  - spec/moxml/attribute_resolver_spec.rb
458
458
  - spec/moxml/attribute_spec.rb
459
459
  - spec/moxml/builder_spec.rb
460
+ - spec/moxml/c14n/adapter_delegation_spec.rb
460
461
  - spec/moxml/c14n/api_spec.rb
461
462
  - spec/moxml/c14n/c14n_spec.rb
462
463
  - spec/moxml/c14n/comments_pis_spec.rb