moxml 0.5.143 → 0.5.145
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/document.rb +55 -0
- data/lib/moxml/version.rb +1 -1
- data/spec/consistency/xpath_idioms_spec.rb +22 -3
- 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: 042f6cd21672a36e869b19c158819a3624d3a79a53de1fd083e83a377b577442
|
|
4
|
+
data.tar.gz: 7cacbfbd44bb9a87aa252a638b8ac9d25fb5cb67b6e3845c8fdf9c5cf943df24
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 507216b5acdf26ae9cd5b4d69c12d2519b949fbe2017c39a804954a04659780c5432b4fe8b11262642ca1699e09eb453af08e230a653a9b530f55404b6d6f5f1
|
|
7
|
+
data.tar.gz: 5d19c7d3d7e1ad18864d13b0b3eaf69f481b5f1a0c68fd652f29cd9bff4e7b9f95f4adf57698d77849371bc635f9ba3ef36c945f12a1b63e2a350e50fc886bbb
|
data/lib/moxml/document.rb
CHANGED
|
@@ -227,7 +227,21 @@ module Moxml
|
|
|
227
227
|
self
|
|
228
228
|
end
|
|
229
229
|
|
|
230
|
+
# Relative name-test paths on the document resolve laxly, as
|
|
231
|
+
# descendant-or-self anchored at the root — Nokogiri's own
|
|
232
|
+
# Document#at/#search semantics (moxml#393; its #xpath/#at_xpath are
|
|
233
|
+
# strict document-context, where the only element child is the root,
|
|
234
|
+
# and the adapters disagree there anyway: leptris's engine returns []
|
|
235
|
+
# for every relative path — leptris-ruby#502 — while Nokogiri's
|
|
236
|
+
# #at/#xpath disagree with each other). Every strict match survives
|
|
237
|
+
# the lax rewrite (the only strict-matchable first step is the root
|
|
238
|
+
# itself), so this is purely additive. Non-name-test expressions
|
|
239
|
+
# (axes, node types, ".", "..") keep document context.
|
|
240
|
+
RELATIVE_NAME_TEST = /\A\s*(\*|[A-Za-z_][\w.-]*)(\s*\[|\s*\||\z|\/)/
|
|
241
|
+
|
|
230
242
|
def xpath(expression, namespaces = nil)
|
|
243
|
+
expression = lax_document_path(expression) if relative_name_test?(expression)
|
|
244
|
+
|
|
231
245
|
result = adapter.xpath(@native, expression, namespaces)
|
|
232
246
|
|
|
233
247
|
# Handle different result types:
|
|
@@ -246,6 +260,8 @@ module Moxml
|
|
|
246
260
|
end
|
|
247
261
|
|
|
248
262
|
def at_xpath(expression, namespaces = nil)
|
|
263
|
+
expression = lax_document_path(expression) if relative_name_test?(expression)
|
|
264
|
+
|
|
249
265
|
if (native_node = adapter.at_xpath(@native, expression, namespaces))
|
|
250
266
|
Moxml::Node.wrap(native_node, context)
|
|
251
267
|
end
|
|
@@ -268,5 +284,44 @@ module Moxml
|
|
|
268
284
|
def find_all(xpath)
|
|
269
285
|
xpath(xpath).to_a
|
|
270
286
|
end
|
|
287
|
+
|
|
288
|
+
private
|
|
289
|
+
|
|
290
|
+
def relative_name_test?(expression)
|
|
291
|
+
expression.match?(RELATIVE_NAME_TEST)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Anchor every top-level union branch at the document root
|
|
295
|
+
# ("//"= descendant-or-self): "a/b | c" -> "//a/b | //c" — one
|
|
296
|
+
# adapter call, engine document order preserved, and the
|
|
297
|
+
# attribute-terminal idiom (pinned below) stays intact, which the
|
|
298
|
+
# ".//" form does not (libxml2 rejects .//r/a/@id).
|
|
299
|
+
def lax_document_path(expression)
|
|
300
|
+
split_top_level_unions(expression)
|
|
301
|
+
.map { |branch| "//#{branch.strip}" }
|
|
302
|
+
.join(" | ")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def split_top_level_unions(expression)
|
|
306
|
+
branches = [+""]
|
|
307
|
+
depth = 0
|
|
308
|
+
quote = nil
|
|
309
|
+
expression.each_char do |ch|
|
|
310
|
+
if quote
|
|
311
|
+
quote = nil if ch == quote
|
|
312
|
+
elsif ["'", '"'].include?(ch)
|
|
313
|
+
quote = ch
|
|
314
|
+
elsif ["(", "["].include?(ch)
|
|
315
|
+
depth += 1
|
|
316
|
+
elsif [")", "]"].include?(ch)
|
|
317
|
+
depth -= 1
|
|
318
|
+
elsif ch == "|" && depth.zero?
|
|
319
|
+
branches << +""
|
|
320
|
+
next
|
|
321
|
+
end
|
|
322
|
+
branches.last << ch
|
|
323
|
+
end
|
|
324
|
+
branches
|
|
325
|
+
end
|
|
271
326
|
end
|
|
272
327
|
end
|
data/lib/moxml/version.rb
CHANGED
|
@@ -53,9 +53,28 @@ RSpec.describe "XPath idioms across adapters" do
|
|
|
53
53
|
expect(doc.xpath("//a/@id").first&.value).to eq("x")
|
|
54
54
|
expect(doc.xpath("//@id").first&.value).to eq("x")
|
|
55
55
|
expect(doc.xpath("r/a/@id").first&.value).to eq("x")
|
|
56
|
-
expect(
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
expect(doc.xpath("//*[local-name()='a']/@id").first&.value).to eq("x")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# moxml#393: a relative name-test path on the DOCUMENT resolves
|
|
60
|
+
# laxly, descendant-anchored at the root — Nokogiri's own
|
|
61
|
+
# Document#at/#search semantics, now pinned for every adapter.
|
|
62
|
+
# Axes, node types and "." keep strict document context.
|
|
63
|
+
it "resolves document-context relative name-test paths from the root" do
|
|
64
|
+
doc = ctx.parse(plain_xml)
|
|
65
|
+
expect(doc.at("a/b")&.name).to eq("b")
|
|
66
|
+
expect(doc.at_xpath("a/b")&.name).to eq("b")
|
|
67
|
+
expect(doc.xpath("a/b").map(&:name)).to eq(["b"])
|
|
68
|
+
expect(doc.xpath("a | r").map(&:name).sort).to eq(%w(a r))
|
|
69
|
+
expect(doc.xpath("missing").map(&:name)).to eq([])
|
|
70
|
+
expect(doc.at_xpath("missing")).to be_nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "keeps non-name-test expressions in document context" do
|
|
74
|
+
doc = ctx.parse(plain_xml)
|
|
75
|
+
expect(doc.at_xpath(".")).to be_truthy
|
|
76
|
+
expect(doc.at_xpath("/*")&.name).to eq("r")
|
|
77
|
+
expect(doc.at_xpath("/r")&.name).to eq("r")
|
|
59
78
|
end
|
|
60
79
|
end
|
|
61
80
|
end
|