moxml 0.5.100 → 0.5.102
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/.github/workflows/dependent-tests.yml +7 -0
- data/.github/workflows/opal.yml +7 -0
- data/.github/workflows/rake.yml +7 -0
- data/.github/workflows/round-trip.yml +7 -0
- data/.github/workflows/windows.yml +7 -0
- data/lib/moxml/adapter/base.rb +28 -2
- data/lib/moxml/adapter/leptris/document_parts.rb +1 -1
- data/lib/moxml/adapter/leptris.rb +131 -12
- data/lib/moxml/adapter/libxml.rb +3 -2
- data/lib/moxml/adapter/nokogiri.rb +4 -0
- data/lib/moxml/cdata.rb +3 -0
- data/lib/moxml/comment.rb +3 -0
- data/lib/moxml/context.rb +12 -0
- data/lib/moxml/document.rb +40 -0
- data/lib/moxml/element.rb +49 -0
- data/lib/moxml/node.rb +219 -23
- data/lib/moxml/node_set.rb +26 -0
- data/lib/moxml/text.rb +3 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xpath/compiler.rb +21 -18
- data/spec/moxml/adapter/leptris_binding_cap_spec.rb +29 -0
- data/spec/moxml/adapter/leptris_xpath_identity_spec.rb +49 -0
- data/spec/moxml/at_search_spec.rb +386 -0
- data/spec/moxml/xpath/union_spec.rb +42 -0
- metadata +6 -2
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Nokogiri-compatible node sugar" do
|
|
6
|
+
describe "at/search" do
|
|
7
|
+
let(:doc) do
|
|
8
|
+
Moxml.parse(<<~XML)
|
|
9
|
+
<root xmlns="http://example.com/ns">
|
|
10
|
+
<item id="a"><name>one</name></item>
|
|
11
|
+
<item id="b"><name>two</name></item>
|
|
12
|
+
</root>
|
|
13
|
+
XML
|
|
14
|
+
end
|
|
15
|
+
let(:ns) { { "xmlns" => "http://example.com/ns" } }
|
|
16
|
+
|
|
17
|
+
it "at returns the first match like Nokogiri" do
|
|
18
|
+
node = doc.at("//xmlns:item", ns)
|
|
19
|
+
expect(node["id"]).to eq("a")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "at on document delegates to root search" do
|
|
23
|
+
expect(doc.at("//xmlns:name", ns).text).to eq("one")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "search returns all matches" do
|
|
27
|
+
expect(doc.search("//xmlns:item", ns).size).to eq(2)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "at returns nil when nothing matches" do
|
|
31
|
+
expect(doc.at("//xmlns:missing", ns)).to be_nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "at works on element nodes" do
|
|
35
|
+
item = doc.at("//xmlns:item[@id='b']", ns)
|
|
36
|
+
expect(item.at("xmlns:name", ns).text).to eq("two")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "Node#traverse" do
|
|
41
|
+
it "yields depth-first in document order from an element" do
|
|
42
|
+
doc = Moxml.parse("<r><a><b/></a><c/></r>")
|
|
43
|
+
names = []
|
|
44
|
+
doc.root.traverse { |n| names << n.name }
|
|
45
|
+
expect(names).to eq(%w[r a b c])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "yields the document first from a document traversal" do
|
|
49
|
+
doc = Moxml.parse("<r><a/></r>")
|
|
50
|
+
names = []
|
|
51
|
+
doc.traverse { |n| names << n.name }
|
|
52
|
+
expect(names).to eq(%w[document r a])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "Node#next=/previous=" do
|
|
57
|
+
it "insert a sibling after/before via assignment" do
|
|
58
|
+
doc = Moxml.parse("<r><a/><b/></r>")
|
|
59
|
+
a = doc.at("//a")
|
|
60
|
+
a.next = "<x/>"
|
|
61
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a x b])
|
|
62
|
+
b = doc.at("//b")
|
|
63
|
+
b.previous = "<y/>"
|
|
64
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a x y b])
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "sibling inserts invalidate the parent children cache" do
|
|
69
|
+
it "next= is visible through parent.children when siblings are text" do
|
|
70
|
+
doc = Moxml.parse("<r><a/>\n<b/></r>")
|
|
71
|
+
doc.at("//a").next = "<x/>"
|
|
72
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a x text b])
|
|
73
|
+
expect(doc.at("//x")).not_to be_nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "duality-proof: insert visible through a wrapper that memoized earlier" do
|
|
77
|
+
ctx = Moxml.new(:leptris)
|
|
78
|
+
doc = ctx.parse("<r><a/>\n<b/></r>")
|
|
79
|
+
doc.root.children.map(&:name) # memoize on the root wrapper
|
|
80
|
+
a = doc.at("//a") # possibly a different wrapper instance
|
|
81
|
+
a.next = "<x/>"
|
|
82
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a x text b])
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe "Node#<< append sugar" do
|
|
87
|
+
it "appends parsed fragments from strings" do
|
|
88
|
+
doc = Moxml.parse("<r/>")
|
|
89
|
+
doc.root << "<a/><b/>"
|
|
90
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a b])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "appends plain text as a text node" do
|
|
94
|
+
text_parent = Moxml.parse("<p/>").root
|
|
95
|
+
text_parent << "hello"
|
|
96
|
+
expect(text_parent.text).to eq("hello")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "appends nodes" do
|
|
100
|
+
doc = Moxml.parse("<r/>")
|
|
101
|
+
doc.root << Moxml.parse("<b/>").root
|
|
102
|
+
expect(doc.root.children.map(&:name)).to eq(%w[b])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "returns self for chaining" do
|
|
106
|
+
doc = Moxml.parse("<r/>")
|
|
107
|
+
expect(doc.root << "<a/>").to be(doc.root)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe "Element#delete" do
|
|
112
|
+
it "removes an attribute by name" do
|
|
113
|
+
doc = Moxml.parse("<r annex='yes' id='x'/>")
|
|
114
|
+
doc.root.delete("annex")
|
|
115
|
+
expect(doc.root["annex"]).to be_nil
|
|
116
|
+
expect(doc.root["id"]).to eq("x")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe "Element#children=" do
|
|
121
|
+
it "replaces all children with a string" do
|
|
122
|
+
doc = Moxml.parse("<r><old/>text</r>")
|
|
123
|
+
doc.root.children = "<a/><b/>"
|
|
124
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a b])
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "replaces with a node" do
|
|
128
|
+
doc = Moxml.parse("<r><old/></r>")
|
|
129
|
+
node = Moxml.parse("<x/>").root
|
|
130
|
+
doc.root.children = node
|
|
131
|
+
expect(doc.root.children.map(&:name)).to eq(%w[x])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "replaces with an array of nodes" do
|
|
135
|
+
doc = Moxml.parse("<r><old/></r>")
|
|
136
|
+
frag = Moxml.parse("<w><x/><y/></w>").root.children
|
|
137
|
+
doc.root.children = frag
|
|
138
|
+
expect(doc.root.children.map(&:name)).to eq(%w[x y])
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "invalidates memoized children holders" do
|
|
142
|
+
doc = Moxml.parse("<r><old/></r>")
|
|
143
|
+
root = doc.root
|
|
144
|
+
root.children
|
|
145
|
+
root.children = "<new/>"
|
|
146
|
+
expect(root.children.map(&:name)).to eq(%w[new])
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe "Element#elements" do
|
|
151
|
+
it "returns only element children" do
|
|
152
|
+
doc = Moxml.parse("<r>text<a/><!-- c --><b/></r>")
|
|
153
|
+
expect(doc.root.elements.map(&:name)).to eq(%w[a b])
|
|
154
|
+
expect(doc.root.element_children.map(&:name)).to eq(%w[a b])
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "stays fresh across mutations" do
|
|
158
|
+
doc = Moxml.parse("<r><a/></r>")
|
|
159
|
+
root = doc.root
|
|
160
|
+
expect(root.elements.map(&:name)).to eq(%w[a])
|
|
161
|
+
root.add_child(doc.create_element("b"))
|
|
162
|
+
expect(root.elements.map(&:name)).to eq(%w[a b])
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe "NodeSet#- difference" do
|
|
167
|
+
it "removes nodes present in the other set" do
|
|
168
|
+
doc = Moxml.parse("<r><a/><b/><c/></r>")
|
|
169
|
+
diff = doc.root.children - doc.search("//a")
|
|
170
|
+
expect(diff.map(&:name).sort).to eq(%w[b c])
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe "NodeSet#to_ary" do
|
|
175
|
+
it "flattens inside arrays" do
|
|
176
|
+
doc = Moxml.parse("<r><a/><b/></r>")
|
|
177
|
+
flat = [doc.root, doc.root.elements].flatten
|
|
178
|
+
expect(flat.map(&:name)).to include("r", "a", "b")
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe "Node#ancestors(selector)" do
|
|
183
|
+
it "filters ancestors by element name" do
|
|
184
|
+
doc = Moxml.parse("<r><table><tr><td><p/></td></tr></table></r>")
|
|
185
|
+
p_node = doc.at("//p")
|
|
186
|
+
expect(p_node.ancestors("table").map(&:name)).to eq(%w[table])
|
|
187
|
+
expect(p_node.ancestors.map(&:name)).to include("td", "tr", "table")
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
describe "Node#next_element/previous_element" do
|
|
192
|
+
it "skips interleaved text nodes" do
|
|
193
|
+
doc = Moxml.parse("<r><a/>text<b/></r>")
|
|
194
|
+
a = doc.at("//a")
|
|
195
|
+
expect(a.next_element.name).to eq("b")
|
|
196
|
+
expect(doc.at("//b").previous_element.name).to eq("a")
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
describe "Node#parent= reparenting" do
|
|
201
|
+
it "moves a node under a new parent" do
|
|
202
|
+
doc = Moxml.parse("<r><from><p/></from><to/></r>")
|
|
203
|
+
p_node = doc.at("//p")
|
|
204
|
+
p_node.parent = doc.at("//to")
|
|
205
|
+
expect(doc.at("//to/p")).not_to be_nil
|
|
206
|
+
expect(doc.at("//from/p")).to be_nil
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
describe "NodeSet#to_xml" do
|
|
211
|
+
it "serializes the set's members in order" do
|
|
212
|
+
doc = Moxml.parse("<r><a>x</a>tail</r>")
|
|
213
|
+
expect(doc.root.children.to_xml).to eq("<a>x</a>tail")
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
describe "Node#add_first_child" do
|
|
218
|
+
it "inserts before existing children and handles empty parents" do
|
|
219
|
+
doc = Moxml.parse("<r><b/></r>")
|
|
220
|
+
doc.root.add_first_child("<a/>")
|
|
221
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a b])
|
|
222
|
+
Moxml.parse("<r/>").root.add_first_child("<x/>")
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
describe "Node#remove on a detached node" do
|
|
227
|
+
it "is a no-op returning self (Nokogiri parity)" do
|
|
228
|
+
doc = Moxml.parse("<r><p><c/></p><t/></r>")
|
|
229
|
+
p_node = doc.at("//p")
|
|
230
|
+
t = doc.create_element("d")
|
|
231
|
+
p_node.replace(t)
|
|
232
|
+
expect(p_node.parent).to be_nil
|
|
233
|
+
expect(p_node.remove).to equal(p_node)
|
|
234
|
+
t << p_node
|
|
235
|
+
expect(t.children.map(&:name)).to eq(%w[p])
|
|
236
|
+
expect(doc.at("//d/p/c")).not_to be_nil
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
describe "Element#replace + re-attach under the replacement" do
|
|
241
|
+
it "does not cycle the tree (leptris stale-run splice)" do
|
|
242
|
+
doc = Moxml.parse("<r><a>x</a><b/></r>")
|
|
243
|
+
a = doc.at("//a")
|
|
244
|
+
t = doc.create_element("t")
|
|
245
|
+
a.replace(t)
|
|
246
|
+
t << a
|
|
247
|
+
expect(doc.root.children.map(&:name)).to eq(%w[t b])
|
|
248
|
+
expect(t.children.map(&:name)).to eq(%w[a])
|
|
249
|
+
expect(doc.at("//t/a/text()").text).to eq("x")
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
describe "Node/NodeSet#to_s interpolation" do
|
|
254
|
+
it "serializes instead of Object#to_s" do
|
|
255
|
+
doc = Moxml.parse("<r><a>x</a><b/></r>")
|
|
256
|
+
# rubocop:disable Style/RedundantInterpolation -- the interpolation IS the contract under test
|
|
257
|
+
node = doc.at("//a")
|
|
258
|
+
expect("#{node}").to eq("<a>x</a>")
|
|
259
|
+
children = doc.root.children
|
|
260
|
+
expect("#{children}").to eq("<a>x</a><b></b>")
|
|
261
|
+
# rubocop:enable Style/RedundantInterpolation
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
describe "Element#default_namespace=" do
|
|
266
|
+
it "binds the element to the default namespace" do
|
|
267
|
+
doc = Moxml.parse("<r><math><mi>x</mi></math></r>")
|
|
268
|
+
math = doc.at("//math")
|
|
269
|
+
math.default_namespace = "http://www.w3.org/1998/Math/MathML"
|
|
270
|
+
expect(math.namespace_uri.to_s).to eq("http://www.w3.org/1998/Math/MathML")
|
|
271
|
+
expect(math.to_xml).to include('xmlns="http://www.w3.org/1998/Math/MathML"')
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
describe "Node#to_str" do
|
|
276
|
+
it "yields the text content" do
|
|
277
|
+
doc = Moxml.parse("<r><s>a<b>c</b>d</s></r>")
|
|
278
|
+
expect(doc.at("//s").to_str).to eq("acd")
|
|
279
|
+
expect(doc.at("//b").to_str).to eq("c")
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
describe "Element#content=" do
|
|
284
|
+
it "replaces children with the given text" do
|
|
285
|
+
doc = Moxml.parse("<r><s><b>x</b>y</s></r>")
|
|
286
|
+
s = doc.at("//s")
|
|
287
|
+
s.content = "1 < 2"
|
|
288
|
+
expect(s.children.size).to eq(1)
|
|
289
|
+
expect(s.content).to eq("1 < 2")
|
|
290
|
+
expect(s.to_xml).to eq("<s>1 < 2</s>")
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
describe "Node#next/previous readers" do
|
|
295
|
+
it "return adjacent siblings" do
|
|
296
|
+
doc = Moxml.parse("<r><a/>mid<b/></r>")
|
|
297
|
+
a = doc.at("//a")
|
|
298
|
+
expect(a.next.name).to eq("text")
|
|
299
|
+
expect(a.next.next.name).to eq("b")
|
|
300
|
+
expect(doc.at("//b").previous.name).to eq("text")
|
|
301
|
+
expect(doc.at("//b").previous.previous.name).to eq("a")
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
describe "Node#path for non-element nodes" do
|
|
306
|
+
it "builds type-test segments" do
|
|
307
|
+
doc = Moxml.parse("<r><p>a<!--c--></p><p>b</p></r>")
|
|
308
|
+
expect(doc.at("//p").path).to eq("/r/p[1]")
|
|
309
|
+
texts = doc.xpath("//p/text()")
|
|
310
|
+
expect(texts[0].path).to eq("/r/p[1]/text()")
|
|
311
|
+
expect(doc.xpath("//p/comment()").first.path).to eq("/r/p[1]/comment()")
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
describe "Document#root= with a foreign root" do
|
|
316
|
+
it "adopts the subtree from another document" do
|
|
317
|
+
doc = Moxml.parse("<old><x/></old>")
|
|
318
|
+
other = Moxml.parse(%(<r xmlns="urn:x" a="1"><p>hi<b>t</b></p></r>))
|
|
319
|
+
doc.root = other.root
|
|
320
|
+
expect(doc.to_xml(indent: 0).strip)
|
|
321
|
+
.to eq(%(<r xmlns="urn:x" a="1"><p>hi<b>t</b></p></r>))
|
|
322
|
+
expect(doc.root.namespace_uri.to_s).to eq("urn:x")
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
describe "String operands are markup, not literal text" do
|
|
327
|
+
it "add_child / add_next_sibling / add_previous_sibling parse strings" do
|
|
328
|
+
doc = Moxml.parse("<r><a/></r>")
|
|
329
|
+
a = doc.at("//a")
|
|
330
|
+
a.add_child("<c/>")
|
|
331
|
+
a.add_next_sibling("<d/>")
|
|
332
|
+
a.add_previous_sibling("<b/>")
|
|
333
|
+
expect(a.children.map(&:name)).to eq(%w[c])
|
|
334
|
+
expect(doc.root.children.map(&:name)).to eq(%w[b a d])
|
|
335
|
+
expect(doc.at("//r/a/c")).not_to be_nil
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
describe "Markup-string insertion returns a NodeSet of new nodes" do
|
|
340
|
+
it "add_child / add_next_sibling return the new nodes; nodes return self" do
|
|
341
|
+
doc = Moxml.parse("<r><a/></r>")
|
|
342
|
+
a = doc.at("//a")
|
|
343
|
+
kids = a.add_child("<c1/><c2/>")
|
|
344
|
+
expect(kids).to be_a(Moxml::NodeSet)
|
|
345
|
+
expect(kids.map(&:name)).to eq(%w[c1 c2])
|
|
346
|
+
sibs = a.add_next_sibling("<n1/><n2/>")
|
|
347
|
+
expect(sibs.map(&:name)).to eq(%w[n1 n2])
|
|
348
|
+
expect(doc.root.children.map(&:name)).to eq(%w[a n1 n2])
|
|
349
|
+
prev = a.add_previous_sibling("<p1/><p2/>")
|
|
350
|
+
expect(prev.map(&:name)).to eq(%w[p1 p2])
|
|
351
|
+
expect(doc.root.children.map(&:name)).to eq(%w[p1 p2 a n1 n2])
|
|
352
|
+
expect(a.add_child(doc.at("//p1"))).to equal(a)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
describe "default_namespace= idempotence" do
|
|
357
|
+
it "does not duplicate an identical in-scope default namespace" do
|
|
358
|
+
doc = Moxml.parse(%(<r xmlns="urn:x"><math xmlns="urn:m"/></r>))
|
|
359
|
+
math = doc.at("//m:math", "m" => "urn:m")
|
|
360
|
+
math.default_namespace = "urn:m"
|
|
361
|
+
expect(math.to_xml).to eq(%(<math xmlns="urn:m"></math>))
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
describe "xpath with namespace bindings" do
|
|
366
|
+
it "prefixed star and name tests honor the namespace URI" do
|
|
367
|
+
# m must be DECLARED on the element — a bare m:title with only
|
|
368
|
+
# a default xmlns is a no-namespace element whose literal name
|
|
369
|
+
# carries the colon, and no prefix binding can select it.
|
|
370
|
+
doc = Moxml.parse(%(<r><bibdata/><m:title xmlns:m="urn:x"/></r>))
|
|
371
|
+
hits = doc.root.xpath(".//m:*", "m" => "urn:unitsml")
|
|
372
|
+
expect(hits.map(&:name)).to eq([])
|
|
373
|
+
named = doc.root.at_xpath(".//m:title", "m" => "urn:x")
|
|
374
|
+
expect(named&.name).to eq("title")
|
|
375
|
+
none = doc.root.at_xpath(".//m:title", "m" => "urn:unitsml")
|
|
376
|
+
expect(none).to be_nil
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
describe "Element#inner_xml" do
|
|
381
|
+
it "serializes the children" do
|
|
382
|
+
doc = Moxml.parse("<r><a>x</a><b/></r>")
|
|
383
|
+
expect(doc.root.inner_xml).to eq("<a>x</a><b/>")
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# moxml#315: unions are N-ARY in the parser (a|b|c parses to one
|
|
6
|
+
# union node); the compiler's two-children destructure silently
|
|
7
|
+
# dropped every branch after the second. The reserved xmlns prefix
|
|
8
|
+
# hid the drop for namespaced consumers because those queries rode
|
|
9
|
+
# the native seam; when they fell back to the Ruby engine (or on
|
|
10
|
+
# adapters whose engine IS this one, e.g. ox), branches vanished.
|
|
11
|
+
RSpec.describe "XPath union compilation" do
|
|
12
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
13
|
+
let(:doc) { ctx.parse('<r xmlns="urn:x"><a>1</a><b>2</b><c>3</c></r>') }
|
|
14
|
+
|
|
15
|
+
it "keeps every branch of a flat n-ary union (moxml#315)" do
|
|
16
|
+
r = doc.at_xpath("/xmlns:r", { "xmlns" => "urn:x" })
|
|
17
|
+
names = r.xpath("./xmlns:a|./xmlns:b|./xmlns:c", { "xmlns" => "urn:x" })
|
|
18
|
+
.map(&:text)
|
|
19
|
+
expect(names).to eq(%w[1 2 3])
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "keeps branches under the reserved xmlns prefix through the engine" do
|
|
23
|
+
r = doc.at_xpath("/xmlns:r", { "xmlns" => "urn:x" })
|
|
24
|
+
ast = Moxml::XPath::Parser.parse("./xmlns:a|./xmlns:b|./xmlns:c")
|
|
25
|
+
proc_c = Moxml::XPath::Compiler.compile_with_cache(ast, namespaces: { "xmlns" => "urn:x" })
|
|
26
|
+
expect(proc_c.call(r).to_a.map(&:text)).to eq(%w[1 2 3])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "keeps parenthesized nested unions" do
|
|
30
|
+
r = doc.at_xpath("/xmlns:r", { "xmlns" => "urn:x" })
|
|
31
|
+
ast = Moxml::XPath::Parser.parse("(./xmlns:a|./xmlns:b)|./xmlns:c")
|
|
32
|
+
proc_c = Moxml::XPath::Compiler.compile_with_cache(ast, namespaces: { "xmlns" => "urn:x" })
|
|
33
|
+
expect(proc_c.call(r).to_a.map(&:text)).to eq(%w[1 2 3])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "unions still work on adapters whose engine is the Ruby engine" do
|
|
37
|
+
ctx_ox = Moxml.new(:ox)
|
|
38
|
+
doc_ox = ctx_ox.parse("<r><a>1</a><b>2</b><c>3</c></r>")
|
|
39
|
+
r = doc_ox.root
|
|
40
|
+
expect(r.xpath("./a|./b|./c").map(&:text)).to eq(%w[1 2 3])
|
|
41
|
+
end
|
|
42
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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.102
|
|
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-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Moxml is a unified XML manipulation library that provides a common API
|
|
@@ -441,12 +441,14 @@ files:
|
|
|
441
441
|
- spec/moxml/adapter/entity_restoration_spec.rb
|
|
442
442
|
- spec/moxml/adapter/headed_ox_spec.rb
|
|
443
443
|
- spec/moxml/adapter/leptris_append_gc_spec.rb
|
|
444
|
+
- spec/moxml/adapter/leptris_binding_cap_spec.rb
|
|
444
445
|
- spec/moxml/adapter/leptris_doc_parts_spec.rb
|
|
445
446
|
- spec/moxml/adapter/leptris_parse_skips_spec.rb
|
|
446
447
|
- spec/moxml/adapter/leptris_record_driver_spec.rb
|
|
447
448
|
- spec/moxml/adapter/leptris_sax_dup_attr_spec.rb
|
|
448
449
|
- spec/moxml/adapter/leptris_sax_records_spec.rb
|
|
449
450
|
- spec/moxml/adapter/leptris_spec.rb
|
|
451
|
+
- spec/moxml/adapter/leptris_xpath_identity_spec.rb
|
|
450
452
|
- spec/moxml/adapter/libxml_internals_spec.rb
|
|
451
453
|
- spec/moxml/adapter/libxml_spec.rb
|
|
452
454
|
- spec/moxml/adapter/nokogiri_spec.rb
|
|
@@ -459,6 +461,7 @@ files:
|
|
|
459
461
|
- spec/moxml/adapter_spec.rb
|
|
460
462
|
- spec/moxml/allocation_benchmark_spec.rb
|
|
461
463
|
- spec/moxml/allocation_guard_spec.rb
|
|
464
|
+
- spec/moxml/at_search_spec.rb
|
|
462
465
|
- spec/moxml/attribute_resolver_spec.rb
|
|
463
466
|
- spec/moxml/attribute_spec.rb
|
|
464
467
|
- spec/moxml/builder_spec.rb
|
|
@@ -557,6 +560,7 @@ files:
|
|
|
557
560
|
- spec/moxml/xpath/parser_spec.rb
|
|
558
561
|
- spec/moxml/xpath/ruby/generator_spec.rb
|
|
559
562
|
- spec/moxml/xpath/ruby/node_spec.rb
|
|
563
|
+
- spec/moxml/xpath/union_spec.rb
|
|
560
564
|
- spec/moxml/xpath_capabilities_spec.rb
|
|
561
565
|
- spec/moxml/xpath_spec.rb
|
|
562
566
|
- spec/moxml/xslt/stylesheet_spec.rb
|