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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7ef456b1833d080915877e15238d23fd1e3d38bd638a434711bc98d423b78fc
4
- data.tar.gz: 680fe1bbe7097895d5fdb46c29e5e2c624b13ed29792be2ffa941ef3c8bbe3bb
3
+ metadata.gz: 042f6cd21672a36e869b19c158819a3624d3a79a53de1fd083e83a377b577442
4
+ data.tar.gz: 7cacbfbd44bb9a87aa252a638b8ac9d25fb5cb67b6e3845c8fdf9c5cf943df24
5
5
  SHA512:
6
- metadata.gz: 01e458bb31733bbb8a9bf379d37ba30b4d5b30d1670eceffbfad5ae953afa85f1dc272ce3cc99cf719808417a13035dadf00e14fdb6a01d5cce3f4bcc855e8ac
7
- data.tar.gz: e0eae403141507dcc8dd23e3b6962935cac3481382ef268a41105ae919e014fc53e657e4b44d261cdb8e59799678dc0a2f1f103937580ee6bb7b8f4e93372667
6
+ metadata.gz: 507216b5acdf26ae9cd5b4d69c12d2519b949fbe2017c39a804954a04659780c5432b4fe8b11262642ca1699e09eb453af08e230a653a9b530f55404b6d6f5f1
7
+ data.tar.gz: 5d19c7d3d7e1ad18864d13b0b3eaf69f481b5f1a0c68fd652f29cd9bff4e7b9f95f4adf57698d77849371bc635f9ba3ef36c945f12a1b63e2a350e50fc886bbb
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.143"
4
+ VERSION = "0.5.145"
5
5
  end
@@ -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
- doc.xpath("//*[local-name()='a']/@id").first&.value,
58
- ).to eq("x")
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
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.143
4
+ version: 0.5.145
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.