Skip to content

Commit fa0427c

Browse files
tompngnaitoh
andauthored
Fix xpath functions related to names (#343)
Fix and simplify name(nodesets), local-name(nodesets) and namespace-uri(nodesets) node select logic. All functions that uses a single node should use the first document-ordered node, but these functions were wrongly skipping un-named nodes. ```ruby xml = '<root>text<!-- comment --><node/></root>' xpath = 'name(root/node())' REXML::XPath.match(REXML::Document.new(xml), xpath) #=> ["node"] (bug) → [""] Nokogiri::XML.parse(xml).xpath(xpath) #=> "" (expected) ``` --------- Co-authored-by: NAITOH Jun <naitoh@gmail.com>
1 parent 83c920e commit fa0427c

3 files changed

Lines changed: 40 additions & 28 deletions

File tree

lib/rexml/functions.rb

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ def initialize
2222
:variables,
2323
:variables=,
2424
:context=,
25-
:get_namespace,
25+
:target_named_node,
2626
:send,
27+
:compare_language,
28+
:string_value,
2729
]
2830
class << self
2931
def method_added(name)
@@ -61,41 +63,27 @@ def id( object )
6163
end
6264

6365
def local_name(node_set=nil)
64-
get_namespace(node_set) do |node|
65-
return node.local_name
66-
end
67-
""
66+
target_named_node(node_set)&.local_name || ""
6867
end
6968

7069
def namespace_uri( node_set=nil )
71-
get_namespace( node_set ) do |node|
72-
return node.namespace
73-
end
74-
""
70+
target_named_node(node_set)&.namespace || ""
7571
end
7672

7773
def name( node_set=nil )
78-
get_namespace( node_set ) do |node|
79-
return node.expanded_name
80-
end
81-
""
74+
target_named_node(node_set)&.expanded_name || ""
8275
end
8376

8477
# Helper method.
85-
def get_namespace( node_set = nil )
86-
if node_set == nil
87-
yield @context[:node] if @context[:node].respond_to?(:namespace)
88-
else
89-
if node_set.kind_of? Array
90-
result = []
91-
XPathParser.sort(node_set).each do |node|
92-
result << yield(node) if node.respond_to?(:namespace)
93-
end
94-
result
95-
elsif node_set.respond_to? :namespace
96-
yield node_set
78+
def target_named_node(node_set = nil)
79+
node =
80+
case node_set
81+
when nil
82+
@context[:node]
83+
when Array
84+
XPathParser.sort(node_set).first
9785
end
98-
end
86+
node if node.respond_to?(:namespace)
9987
end
10088

10189
# A node-set is converted to a string by returning the string-value of the

test/functions/test_base.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ def setup
1313
REXML::Functions.context = nil
1414
end
1515

16+
def test_available_functions
17+
expected_functions = %w[
18+
boolean ceiling concat contains count false floor id lang last local-name
19+
name namespace-uri normalize-space not number position round starts-with
20+
string string-length substring substring-after substring-before sum translate true
21+
]
22+
methods = REXML::FunctionsClass.class_variable_get(:@@available_functions).keys.sort
23+
assert_equal expected_functions, methods.map { |m| m.to_s.tr('_', '-') }.sort
24+
end
25+
1626
def test_functions
1727
# trivial text() test
1828
# confuse-a-function

test/functions/test_local_name.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def test_one
1616
<x:child/>
1717
</root>
1818
XML
19-
node_set = document.root.children
19+
node_set = document.root.elements.to_a
2020
assert_equal("child", REXML::Functions.local_name(node_set))
2121
end
2222

@@ -27,14 +27,28 @@ def test_multiple
2727
<x:child2/>
2828
</root>
2929
XML
30-
node_set = document.root.children
30+
node_set = document.root.elements.to_a
3131
assert_equal("child1", REXML::Functions.local_name(node_set))
3232
end
3333

3434
def test_nonexistent
3535
assert_equal("", REXML::Functions.local_name([]))
3636
end
3737

38+
def test_attribute
39+
document = REXML::Document.new("<root xmlns:x='http://example.com/x/' x:attr='value' />")
40+
node_set = [document.root.attributes.get_attribute("x:attr")]
41+
assert_equal("attr", REXML::Functions.local_name(node_set))
42+
end
43+
44+
def test_non_named
45+
document = REXML::Document.new("<root>text<!-- comment --><a/></root>")
46+
children = document.root.children
47+
assert_equal("", REXML::Functions.local_name([children[0]]))
48+
assert_equal("", REXML::Functions.local_name([children[1]]))
49+
assert_equal("", REXML::Functions.local_name(children))
50+
end
51+
3852
def test_context
3953
document = REXML::Document.new("<root/>")
4054
REXML::Functions.context = {node: document.root}

0 commit comments

Comments
 (0)