moxml 0.5.26 → 0.5.28
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/serialize.rb +19 -7
- data/lib/moxml/adapter/leptris.rb +13 -0
- data/lib/moxml/adapter/oga.rb +6 -0
- data/lib/moxml/adapter/ox.rb +6 -0
- data/lib/moxml/adapter/rexml.rb +8 -0
- data/lib/moxml/version.rb +1 -1
- data/spec/integration/shared_examples/node_wrappers/namespace_behavior.rb +6 -0
- data/spec/moxml/adapter/leptris_spec.rb +49 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c393ef51976af7e0ccf0ff232d009863a0b2b7be044b70e8500beedb216bd122
|
|
4
|
+
data.tar.gz: 1718f58a399c09fc218b269faa62dbd52e50e2bbdcdea32e52f9c7e72ad05a8a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf11cb89af1acd465fe769616d925e1c20fb3961953ed29483147ef67a132e09a52f763601a3397887f6ff49d42f4dddc2b27ce6f1f659d99062e332eb401cf2
|
|
7
|
+
data.tar.gz: 5033d59e77e627b40fb054036ac87ee45e5c60408e37dcb6ff2790cca7a3276f4a45ee62d36bab5ec2d9d7af038eb4c1467c84bd3174ace1b5fc73b483d0d8db
|
|
@@ -85,26 +85,37 @@ module Moxml
|
|
|
85
85
|
# detection below is a no-op on correct builds.
|
|
86
86
|
RAW_AMP_RE = /&(?!#{Entity::NAME_PATTERN};|#\d+;|#x[0-9A-Fa-f]+;)/
|
|
87
87
|
|
|
88
|
+
# A raw "<" in text position — the engine intermittently
|
|
89
|
+
# loses the escape when parsing under allocation pressure
|
|
90
|
+
# (leptris-ruby#131), so a decoded < serializes bare and
|
|
91
|
+
# reparsing truncates at it. Valid markup NEVER puts "<"
|
|
92
|
+
# before a non-name, non-delimiter character, so this is
|
|
93
|
+
# deterministic in markup segments (CDATA is literal and
|
|
94
|
+
# excluded by the segment walker).
|
|
95
|
+
RAW_LT_RE = /<(?![A-Za-z_:\/?!])/
|
|
96
|
+
|
|
88
97
|
def normalize_serialization(xml, options)
|
|
89
98
|
# The libxml2-layout serializer (>= 1.9.42) keeps attribute
|
|
90
99
|
# apostrophes literal; older engines escaped them.
|
|
91
100
|
needs_apos = !LIBXML2_LAYOUT_PARITY && xml.include?("'")
|
|
92
101
|
needs_expand = options[:expand_empty] && xml.include?("/>")
|
|
93
|
-
# One scan:
|
|
94
|
-
# ampersand-free output — no include? pre-filter needed.
|
|
102
|
+
# One scan each: both regexes fail fast on clean output.
|
|
95
103
|
needs_amp = xml.match?(RAW_AMP_RE)
|
|
96
|
-
|
|
104
|
+
needs_lt = xml.match?(RAW_LT_RE)
|
|
105
|
+
return xml unless needs_apos || needs_expand || needs_amp || needs_lt
|
|
97
106
|
|
|
98
107
|
out = +""
|
|
99
108
|
pos = 0
|
|
100
109
|
while pos < xml.length
|
|
101
110
|
opener_at, terminator = next_literal_region(xml, pos)
|
|
102
111
|
if opener_at.nil?
|
|
103
|
-
out << normalize_markup(xml[pos..], needs_apos, needs_expand,
|
|
112
|
+
out << normalize_markup(xml[pos..], needs_apos, needs_expand,
|
|
113
|
+
needs_amp: needs_amp, needs_lt: needs_lt)
|
|
104
114
|
break
|
|
105
115
|
end
|
|
106
116
|
|
|
107
|
-
out << normalize_markup(xml[pos...opener_at], needs_apos, needs_expand,
|
|
117
|
+
out << normalize_markup(xml[pos...opener_at], needs_apos, needs_expand,
|
|
118
|
+
needs_amp: needs_amp, needs_lt: needs_lt)
|
|
108
119
|
search_from = opener_at + opener_at_offset(terminator)
|
|
109
120
|
close = xml.index(terminator, search_from)
|
|
110
121
|
close_end = close.nil? ? xml.length : close + terminator.length
|
|
@@ -136,7 +147,7 @@ module Moxml
|
|
|
136
147
|
terminator == "-->" ? 4 : 0
|
|
137
148
|
end
|
|
138
149
|
|
|
139
|
-
def normalize_markup(markup, needs_apos, needs_expand, needs_amp: false)
|
|
150
|
+
def normalize_markup(markup, needs_apos, needs_expand, needs_amp: false, needs_lt: false)
|
|
140
151
|
markup = markup.gsub("'", "'") if needs_apos
|
|
141
152
|
if needs_expand
|
|
142
153
|
markup = markup.gsub(EMPTY_ELEMENT_RE) do
|
|
@@ -144,8 +155,9 @@ module Moxml
|
|
|
144
155
|
end
|
|
145
156
|
end
|
|
146
157
|
# Segment-aware: this never sees CDATA/comment/PI content,
|
|
147
|
-
# where
|
|
158
|
+
# where bare & and < are literal data.
|
|
148
159
|
markup = markup.gsub(RAW_AMP_RE, "&") if needs_amp
|
|
160
|
+
markup = markup.gsub(RAW_LT_RE, "<") if needs_lt
|
|
149
161
|
markup
|
|
150
162
|
end
|
|
151
163
|
end
|
|
@@ -288,6 +288,19 @@ module Moxml
|
|
|
288
288
|
return set_attribute_namespace(node, namespace) if node.is_a?(::Leptris::XML::Attr)
|
|
289
289
|
|
|
290
290
|
element = node
|
|
291
|
+
if namespace.nil?
|
|
292
|
+
# Nil-clear contract (issue #164): undeclare the default
|
|
293
|
+
# namespace (xmlns="") and drop any name prefix — the
|
|
294
|
+
# element then reports no namespace, matching the other
|
|
295
|
+
# adapters. Elements that carried a PREFIX cannot fully
|
|
296
|
+
# detach the engine's namespace link (leptris-ruby#132):
|
|
297
|
+
# the name is unqualified and the undeclaration added, but
|
|
298
|
+
# the serializer may re-attach the old prefix until the
|
|
299
|
+
# engine grows a clear entry.
|
|
300
|
+
element.name = element.name.split(":", 2)[-1] if element.name.include?(":")
|
|
301
|
+
element.default_namespace = ""
|
|
302
|
+
return element
|
|
303
|
+
end
|
|
291
304
|
prefix = namespace.is_a?(String) ? nil : namespace.prefix
|
|
292
305
|
uri = namespace.is_a?(String) ? namespace : namespace.href
|
|
293
306
|
if prefix.nil? || prefix.empty?
|
data/lib/moxml/adapter/oga.rb
CHANGED
|
@@ -170,6 +170,12 @@ module Moxml
|
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
def set_namespace(element, ns_or_string)
|
|
173
|
+
if ns_or_string.nil?
|
|
174
|
+
element.namespace_name = nil
|
|
175
|
+
set_attribute(element, "xmlns", "")
|
|
176
|
+
return element
|
|
177
|
+
end
|
|
178
|
+
|
|
173
179
|
element.namespace_name = ns_or_string.to_s
|
|
174
180
|
element
|
|
175
181
|
end
|
data/lib/moxml/adapter/ox.rb
CHANGED
|
@@ -149,6 +149,12 @@ module Moxml
|
|
|
149
149
|
def set_namespace(element, ns)
|
|
150
150
|
return element unless element.is_a?(::Ox::Element) || element.is_a?(::Ox::Node)
|
|
151
151
|
|
|
152
|
+
if ns.nil?
|
|
153
|
+
set_attribute(element, "xmlns", "")
|
|
154
|
+
element.name = element.name.to_s.sub(/\A(?:\w+):/, "")
|
|
155
|
+
return element
|
|
156
|
+
end
|
|
157
|
+
|
|
152
158
|
prefix = ns.prefix
|
|
153
159
|
# attributes don't have attributes but can have a namespace prefix
|
|
154
160
|
if element.is_a?(::Ox::Element)
|
data/lib/moxml/adapter/rexml.rb
CHANGED
|
@@ -485,6 +485,14 @@ module Moxml
|
|
|
485
485
|
|
|
486
486
|
# add a namespace prefix to the element name AND a namespace definition
|
|
487
487
|
def set_namespace(element, ns)
|
|
488
|
+
if ns.nil?
|
|
489
|
+
# Nil-clear contract (issue #164): the XML Namespaces 1.0
|
|
490
|
+
# undeclaration, plus an unqualified name.
|
|
491
|
+
element.add_namespace(nil, "") if element.is_a?(::REXML::Element)
|
|
492
|
+
element.name = element.name.to_s.sub(/\A(?:\w+):/, "")
|
|
493
|
+
return element
|
|
494
|
+
end
|
|
495
|
+
|
|
488
496
|
prefix = ns.name.to_s.empty? ? "xmlns" : ns.name.to_s
|
|
489
497
|
if element.is_a?(::REXML::Element)
|
|
490
498
|
element.add_namespace(prefix,
|
data/lib/moxml/version.rb
CHANGED
|
@@ -280,6 +280,12 @@ RSpec.shared_examples "Moxml::Namespace" do
|
|
|
280
280
|
expect(root.namespace.uri).to eq("http://example.org/1")
|
|
281
281
|
expect(child.namespace.uri).to eq("http://example.org/2")
|
|
282
282
|
end
|
|
283
|
+
|
|
284
|
+
it "accepts namespace = nil without raising (issue #164)" do
|
|
285
|
+
cleared = context.parse(%(<r xmlns="urn:clear"><c>t</c></r>))
|
|
286
|
+
child = cleared.root.children.first
|
|
287
|
+
expect { child.namespace = nil }.not_to raise_error
|
|
288
|
+
end
|
|
283
289
|
end
|
|
284
290
|
end
|
|
285
291
|
end
|
|
@@ -243,6 +243,55 @@ RSpec.describe Moxml::Adapter::Leptris do
|
|
|
243
243
|
expect(doc.root.children.to_a.select(&:text?)).to be_empty
|
|
244
244
|
end
|
|
245
245
|
|
|
246
|
+
it "repairs a raw < in text position (issue #167 / leptris-ruby#131)" do
|
|
247
|
+
# The engine intermittently drops the escape when parsing under
|
|
248
|
+
# allocation pressure: a decoded < serializes bare and the
|
|
249
|
+
# output fails to reparse. Valid markup never puts < before a
|
|
250
|
+
# non-name character, so the damage is deterministically
|
|
251
|
+
# repairable in markup segments.
|
|
252
|
+
repaired = described_class.normalize_serialization(
|
|
253
|
+
%(<?xml version="1.0"?><r><pre>A <\n B</pre></r>\n), {}
|
|
254
|
+
)
|
|
255
|
+
expect(repaired).to include("A <")
|
|
256
|
+
expect(repaired).not_to match("A <")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it "keeps a literal < inside CDATA untouched by the repair" do
|
|
260
|
+
doc = ctx.parse(%(<r><![CDATA[A < B]]></r>))
|
|
261
|
+
expect(doc.to_xml).to include("<![CDATA[A < B]]>")
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it "survives parse-under-allocation-pressure without emitting raw < (issue #167)" do
|
|
265
|
+
xml = "<?xml version=\"1.0\"?><doc>#{%(<s><pre alt="A B">A <\n B</pre></s>) * 10}</doc>"
|
|
266
|
+
30.times do
|
|
267
|
+
100.times { |j| "pressure#{j}" * 20 }
|
|
268
|
+
out = ctx.parse(xml, noblanks: true).to_xml(
|
|
269
|
+
declaration: true, encoding: "UTF-8", indent: 2, expand_empty: false,
|
|
270
|
+
)
|
|
271
|
+
expect(out).not_to match("A <"), "raw unescaped < leaked into text"
|
|
272
|
+
expect(Nokogiri::XML(out, &:strict)).to be_a(Nokogiri::XML::Document)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it "clears the namespace with nil — full contract (issue #164)" do
|
|
277
|
+
doc = ctx.parse(%(<r xmlns="urn:clear"><c>t</c></r>))
|
|
278
|
+
child = doc.root.children.first
|
|
279
|
+
child.namespace = nil
|
|
280
|
+
expect(child.namespace).to be_nil
|
|
281
|
+
expect(doc.to_xml).to include(%(<c xmlns="">t</c>))
|
|
282
|
+
reparsed = ctx.parse(doc.to_xml)
|
|
283
|
+
expect(reparsed.root.children.first.namespace).to be_nil
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
it "does not raise clearing a namespace from a prefixed element (issue #164)" do
|
|
287
|
+
# The engine cannot detach a prefix element's namespace link
|
|
288
|
+
# (leptris-ruby#132); the name is unqualified and the
|
|
289
|
+
# undeclaration added meanwhile.
|
|
290
|
+
doc = ctx.parse(%(<r xmlns:p="urn:p"><p:c>t</p:c></r>))
|
|
291
|
+
child = doc.root.children.first
|
|
292
|
+
expect { child.namespace = nil }.not_to raise_error
|
|
293
|
+
end
|
|
294
|
+
|
|
246
295
|
it "refuses noblanks on readonly parses while the strip path is active" do
|
|
247
296
|
# The strip mutates the tree; readonly freezes it at parse. On
|
|
248
297
|
# bindings whose engine flag is libxml2-safe (probe), the flag
|