Skip to content

Commit 6e6dfda

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> ``` - XPath : //a/following-sibling:*[1] => ["w", "x", "y", "z"] ```xml <div> <div> <a/> <-- self --> <w/> <-- //a/following-sibling:*[1] --> </div> <a/> <-- self --> <x/> <-- //a/following-sibling:*[1] --> <a/> <-- self --> <y/> <-- //a/following-sibling:*[1] --> <a/> <-- self --> <z/> <-- //a/following-sibling:*[1] --> </div> ```
1 parent de6f40e commit 6e6dfda

File tree

2 files changed

+80
-3
lines changed

2 files changed

+80
-3
lines changed

lib/rexml/xpath_parser.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def match(path_stack, nodeset)
144144
result = expr(path_stack, nodeset)
145145
case result
146146
when Array # nodeset
147-
unnode(result)
147+
unnode(result).uniq
148148
else
149149
[result]
150150
end

test/xpath/test_base.rb

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,12 +416,89 @@ def test_preceding
416416
assert_equal( 4, cs.length )
417417
end
418418

419-
def test_preceding_sibling
420-
d = REXML::Document.new("<a><b><c/><d/><x/></b><b><e/><x/></b></a>")
419+
def test_preceding_multiple
420+
d = REXML::Document.new("
421+
<a>
422+
<b/><c/><d/><d/><e/><f/>
423+
</a>")
424+
matches = REXML::XPath.match(d, "a/d/preceding::node()")
425+
assert_equal(["d", "c", "b"], matches.map(&:name))
426+
end
427+
428+
def test_following_multiple
429+
d = REXML::Document.new("
430+
<a>
431+
<b/><c/><d/><d/><e/><f/>
432+
</a>")
433+
matches = REXML::XPath.match(d, "a/d/following::node()")
434+
assert_equal(["d", "e", "f"], matches.map(&:name))
435+
end
436+
437+
def test_following_sibling_across_multiple_nodes
438+
d = REXML::Document.new("
439+
<a>
440+
<b>
441+
<x/><c/><d/>
442+
</b>
443+
<b>
444+
<x/><e/>
445+
</b>
446+
</a>")
447+
matches = REXML::XPath.match(d, "a/b/x/following-sibling::node()")
448+
assert_equal(["c", "d", "e"], matches.map(&:name))
449+
end
450+
451+
def test_following_sibling_within_single_node
452+
d = REXML::Document.new("
453+
<a>
454+
<b>
455+
<x/><c/><d/><x/><e/>
456+
</b>
457+
</a>")
458+
matches = REXML::XPath.match(d, "a/b/x/following-sibling::node()")
459+
assert_equal(["c", "d", "x", "e"], matches.map(&:name))
460+
end
461+
462+
def test_following_sibling_predicates
463+
d = REXML::Document.new("
464+
<div>
465+
<div>
466+
<a/><w/>
467+
</div>
468+
<a/><x/>
469+
<a/><y/>
470+
<a/><z/>
471+
</div>")
472+
# Finds a node flowing <a/>
473+
matches = REXML::XPath.match(d, "//a/following-sibling::*[1]")
474+
assert_equal(["w", "x", "y", "z"], matches.map(&:name))
475+
end
476+
477+
def test_preceding_sibling_across_multiple_nodes
478+
d = REXML::Document.new("
479+
<a>
480+
<b>
481+
<c/><d/><x/>
482+
</b>
483+
<b>
484+
<e/><x/>
485+
</b>
486+
</a>")
421487
matches = REXML::XPath.match(d, "a/b/x/preceding-sibling::node()")
422488
assert_equal(["e", "d", "c"], matches.map(&:name))
423489
end
424490

491+
def test_preceding_sibling_within_single_node
492+
d = REXML::Document.new("
493+
<a>
494+
<b>
495+
<c/><d/><x/><e/><x/>
496+
</b>
497+
</a>")
498+
matches = REXML::XPath.match(d, "a/b/x/preceding-sibling::node()")
499+
assert_equal(["e", "x", "d", "c"], matches.map(&:name))
500+
end
501+
425502
def test_following
426503
d = Document.new "<a><b id='0'/><b/><b><c id='1'/><c id='2'/></b><b id='1'/></a>"
427504
start = XPath.first( d, "/a/b[@id='0']" )

0 commit comments

Comments
 (0)