Skip to content

Commit 0716877

Browse files
committed
fix: XPath following, following-sibling, preceding, preceding-sibling for multiple node
## Why? See: #251 (comment) - XPath : a/d/preceding::* => ["d", "c", "b"] ```xml <a> <b/> <!-- a/d/preceding::b --> <c/> <!-- a/d/preceding::c --> <d/> <!-- a/d/preceding::d --> <d/> <!-- self --> <e/> <f/> </a> ``` - XPath : a/d/following::* => ["d", "e", "f"] ```xml <a> <b/> <c/> <d/> <!-- self --> <d/> <!-- a/d/following::d --> <e/> <!-- a/d/following::e --> <f/> <!-- a/d/following::f --> </a> ``` - XPath : a/b/x/following-sibling:* => ["c", "d", "e"] ```xml <a> <b> <x/> <!-- self --> <c/> <!-- a/b/x/following-sibling::c --> <d/> <!-- a/b/x/following-sibling::d --> </b> <b> <x/> <!-- self --> <e/> <!-- a/b/x/following-sibling::e --> </b> </a> ``` - XPath : a/b/x/following-sibling:* => ["c", "d", "x", "e"] ```xml <a> <b> <x/> <!-- self --> <c/> <!-- a/b/x/following-sibling::c --> <d/> <!-- a/b/x/following-sibling::d --> <x/> <!-- a/b/x/following-sibling::x --> <e/> <!-- a/b/x/following-sibling::e --> </b> </a> ``` - XPath : a/b/x/preceding-sibling::* => ["e", "d", "c"] ```xml <a> <b> <c/> <!-- a/b/x/preceding-sibling::c --> <d/> <!-- a/b/x/preceding-sibling::d --> <x/> <!-- self --> </b> <b> <e/> <!-- a/b/x/preceding-sibling::e --> <x/> <!-- self --> </b> </a> ``` - XPath : a/b/x/preceding-sibling::* => ["e", "x", "d", "c"] ```xml <a> <b> <c/> <!-- a/b/x/preceding-sibling::c --> <d/> <!-- a/b/x/preceding-sibling::d --> <x/> <!-- a/b/x/preceding-sibling::x --> <e/> <!-- a/b/x/preceding-sibling::e --> <x/> <!-- self --> </b> </a> ```
1 parent de6f40e commit 0716877

2 files changed

Lines changed: 30 additions & 0 deletions

File tree

lib/rexml/xpath_parser.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ def expr( path_stack, nodeset, context=nil )
297297
descendant(nodeset, false)
298298
end
299299
when :following_sibling
300+
nodeset = nodeset.group_by{|node| node.raw_node.parent}.values.map{|nodes| nodes.min_by{|node| node.position}}
300301
nodeset = step(path_stack) do
301302
nodesets = []
302303
nodeset.each do |node|
@@ -314,6 +315,7 @@ def expr( path_stack, nodeset, context=nil )
314315
nodesets
315316
end
316317
when :preceding_sibling
318+
nodeset = nodeset.group_by{|node| node.raw_node.parent}.values.map{|nodes| nodes.max_by{|node| node.position}}
317319
nodeset = step(path_stack, order: :reverse) do
318320
nodesets = []
319321
nodeset.each do |node|
@@ -331,12 +333,14 @@ def expr( path_stack, nodeset, context=nil )
331333
nodesets
332334
end
333335
when :preceding
336+
nodeset = nodeset.max_by(1){|node| node.position}
334337
nodeset = step(path_stack, order: :reverse) do
335338
unnode(nodeset) do |node|
336339
preceding(node)
337340
end
338341
end
339342
when :following
343+
nodeset = nodeset.min_by(1){|node| node.position}
340344
nodeset = step(path_stack) do
341345
unnode(nodeset) do |node|
342346
following(node)

test/xpath/test_base.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,36 @@ def test_preceding
416416
assert_equal( 4, cs.length )
417417
end
418418

419+
def test_preceding_multiple
420+
d = REXML::Document.new("<a><b/><c/><d/><d/><e/><f/></a>")
421+
matches = REXML::XPath.match(d, "a/d/preceding::node()")
422+
assert_equal(["d", "c", "b"], matches.map(&:name))
423+
end
424+
425+
def test_following_multiple
426+
d = REXML::Document.new("<a><b/><c/><d/><d/><e/><f/></a>")
427+
matches = REXML::XPath.match(d, "a/d/following::node()")
428+
assert_equal(["d", "e", "f"], matches.map(&:name))
429+
end
430+
431+
def test_following_sibling
432+
d = REXML::Document.new("<a><b><x/><c/><d/></b><b><x/><e/></b></a>")
433+
matches = REXML::XPath.match(d, "a/b/x/following-sibling::node()")
434+
assert_equal(["c", "d", "e"], matches.map(&:name))
435+
436+
d = REXML::Document.new("<a><b><x/><c/><d/><x/><e/></b></a>")
437+
matches = REXML::XPath.match(d, "a/b/x/following-sibling::node()")
438+
assert_equal(["c", "d", "x", "e"], matches.map(&:name))
439+
end
440+
419441
def test_preceding_sibling
420442
d = REXML::Document.new("<a><b><c/><d/><x/></b><b><e/><x/></b></a>")
421443
matches = REXML::XPath.match(d, "a/b/x/preceding-sibling::node()")
422444
assert_equal(["e", "d", "c"], matches.map(&:name))
445+
446+
d = REXML::Document.new("<a><b><c/><d/><x/><e/><x/></b></a>")
447+
matches = REXML::XPath.match(d, "a/b/x/preceding-sibling::node()")
448+
assert_equal(["e", "x", "d", "c"], matches.map(&:name))
423449
end
424450

425451
def test_following

0 commit comments

Comments
 (0)