Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 14 additions & 26 deletions lib/rexml/functions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ def initialize
:variables,
:variables=,
:context=,
:get_namespace,
:target_named_node,
:send,
:compare_language,
:string_value,
]
class << self
def method_added(name)
Expand Down Expand Up @@ -60,41 +62,27 @@ def id( object )
end

def local_name(node_set=nil)
get_namespace(node_set) do |node|
return node.local_name
end
""
target_named_node(node_set)&.local_name || ""
end

def namespace_uri( node_set=nil )
get_namespace( node_set ) do |node|
return node.namespace
end
""
target_named_node(node_set)&.namespace || ""
end

def name( node_set=nil )
get_namespace( node_set ) do |node|
return node.expanded_name
end
""
target_named_node(node_set)&.expanded_name || ""
end

# Helper method.
def get_namespace( node_set = nil )
if node_set == nil
yield @context[:node] if @context[:node].respond_to?(:namespace)
else
if node_set.kind_of? Array
result = []
XPathParser.sort(node_set).each do |node|
result << yield(node) if node.respond_to?(:namespace)
end
result
elsif node_set.respond_to? :namespace
yield node_set
def target_named_node(node_set = nil)
node =
case node_set
when nil
node = @context[:node]
when Array
node = XPathParser.sort(node_set).first
end
Comment on lines +80 to 84

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is regression.

  • before(master)
> doc = REXML::Document.new("<root xmlns:x='http://example.com/x/'><x:child/></root>")
> REXML::Functions.local_name(doc.root.elements[1]) #=> "child"
> REXML::Functions.local_name("not a node")  #=> ""
  • after(this PR)
> doc = REXML::Document.new("<root xmlns:x='http://example.com/x/'><x:child/></root>")
> REXML::Functions.local_name(doc.root.elements[1]) #=> ""
> REXML::Functions.local_name("not a node")  #=> ""
Suggested change
when nil
node = @context[:node]
when Array
node = XPathParser.sort(node_set).first
end
when nil
node = @context[:node]
when Array
node = XPathParser.sort(node_set).first
else
node = node_set
end

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really a regression? I think it's a dead branch which should be cleaned up.
XPath values are: boolean, string, number, and nodeset. there is no non-nodeset-single-node.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If REXML::Functions.local_name(node) #=> node.local_name is considered a public API, it is better to not change the behavior. What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REXML::Functions.local_name(node) #=> node.local_name is not part of the public API.

This change is not a regression.
I'm sorry.

https://docs.ruby-lang.org/ja/latest/class/REXML=3a=3aFunctions.html

内部用なのでユーザは使わないでください。

(I'm sorry for writing in Japanese.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the code so that it doesn't use REXML::Functions.local_name(node).

  • before(rexml 3.4.4)
> doc = REXML::Document.new("<root xmlns:x='http://example.com/x/'><x:child/></root>")
> child = doc.root.elements[1] #=> <x:child/>
> REXML::XPath.match(doc, "local-name($x)", nil, { "x" => child }) #=> ["child"]
> REXML::XPath.match(doc, "local-name('not a node')") #=> [""]
  • after(this PR)
> doc = REXML::Document.new("<root xmlns:x='http://example.com/x/'><x:child/></root>")
> child = doc.root.elements[1] #=> <x:child/>
> REXML::XPath.match(doc, "local-name($x)", nil, { "x" => child }) #=> [""]
> REXML::XPath.match(doc, "local-name('not a node')") #=> [""]

XPath values are: boolean, string, number, and nodeset. there is no non-nodeset-single-node.

However, I agree that “single nodes do not need to be considered.”

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable in the code below is invalid for now.

REXML::XPath.match(doc, "local-name($x)", nil, { "x" => child })

Example:

REXML::XPath.match(doc, "($x)/root", nil, { "x" => [doc] })
# => [<root xmlns:x='http://example.com/x/'> ... </>]
REXML::XPath.match(doc, "($x)/root", nil, { "x" => doc })
# => [<UNDEFINED> ... </>]

It will be valid in #342 (comment)

end
node if node.respond_to?(:namespace)
end

# A node-set is converted to a string by returning the string-value of the
Expand Down
10 changes: 10 additions & 0 deletions test/functions/test_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ def setup
REXML::Functions.context = nil
end

def test_available_functions
expected_functions = %w[
boolean ceiling concat contains count false floor id lang last local-name
name namespace-uri normalize-space not number position round starts-with
string string-length substring substring-after substring-before sum translate true
]
methods = REXML::FunctionsClass.class_variable_get(:@@available_functions).keys.sort
assert_equal expected_functions, methods.map { |m| m.to_s.tr('_', '-') }.sort
end

def test_functions
# trivial text() test
# confuse-a-function
Expand Down
18 changes: 16 additions & 2 deletions test/functions/test_local_name.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_one
<x:child/>
</root>
XML
node_set = document.root.children
node_set = document.root.elements.to_a
assert_equal("child", REXML::Functions.local_name(node_set))
end

Expand All @@ -27,14 +27,28 @@ def test_multiple
<x:child2/>
</root>
XML
node_set = document.root.children
node_set = document.root.elements.to_a
assert_equal("child1", REXML::Functions.local_name(node_set))
end

def test_nonexistent
assert_equal("", REXML::Functions.local_name([]))
end

def test_attribute
document = REXML::Document.new("<root xmlns:x='http://example.com/x/' x:attr='value' />")
node_set = [document.root.attributes.to_a.last]
assert_equal("attr", REXML::Functions.local_name(node_set))
end

def test_non_named
document = REXML::Document.new("<root>text<!-- comment --><a/></root>")
children = document.root.children
assert_equal("", REXML::Functions.local_name([children[0]]))
assert_equal("", REXML::Functions.local_name([children[1]]))
assert_equal("", REXML::Functions.local_name(children))
end

def test_context
document = REXML::Document.new("<root/>")
REXML::Functions.context = {node: document.root}
Expand Down
Loading