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 +4 -4
- data/lib/moxml/adapter/leptris.rb +37 -1
- data/lib/moxml/adapter/nokogiri.rb +29 -0
- data/lib/moxml/adapter/rexml.rb +3 -0
- data/lib/moxml/c14n/data_model.rb +28 -9
- data/lib/moxml/c14n.rb +7 -5
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/c14n/adapter_delegation_spec.rb +120 -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: 4e9550ec966450192384ac9f4059a182a52107fac47420010517fe2fff7c8d1c
|
|
4
|
+
data.tar.gz: ca3e1a10e2a0669991391ce82909360a66fd17cbd98e454aff99f5d1f1bcb4d4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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 & 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
|
data/lib/moxml/adapter/rexml.rb
CHANGED
|
@@ -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
|
-
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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:
|
|
102
|
-
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
|
|
56
|
-
# comments, no InclusiveNamespace prefix list) on
|
|
57
|
-
# adapter passed the
|
|
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,
|
|
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
|
@@ -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 < 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 & b < c</doc>",
|
|
33
|
+
"<doc>a & b < 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.
|
|
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
|