From f607cc2ebaf24f28330f59049587f3ed13a6de99 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 28 Jun 2026 16:00:28 +0900 Subject: [PATCH 1/3] Fix xpath functions related to names 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. --- lib/rexml/functions.rb | 36 ++++++++++--------------------- test/functions/test_local_name.rb | 18 ++++++++++++++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/rexml/functions.rb b/lib/rexml/functions.rb index c3e9317f..dbc6b435 100644 --- a/lib/rexml/functions.rb +++ b/lib/rexml/functions.rb @@ -61,41 +61,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 - end + node if node.respond_to?(:namespace) end # A node-set is converted to a string by returning the string-value of the diff --git a/test/functions/test_local_name.rb b/test/functions/test_local_name.rb index 97c9e748..f74291b4 100644 --- a/test/functions/test_local_name.rb +++ b/test/functions/test_local_name.rb @@ -16,7 +16,7 @@ def test_one XML - node_set = document.root.children + node_set = document.root.elements.to_a assert_equal("child", REXML::Functions.local_name(node_set)) end @@ -27,7 +27,7 @@ def test_multiple XML - node_set = document.root.children + node_set = document.root.elements.to_a assert_equal("child1", REXML::Functions.local_name(node_set)) end @@ -35,6 +35,20 @@ def test_nonexistent assert_equal("", REXML::Functions.local_name([])) end + def test_attribute + document = REXML::Document.new("") + node_set = [document.root.attributes.get_attribute("x:attr")] + assert_equal("attr", REXML::Functions.local_name(node_set)) + end + + def test_non_named + document = REXML::Document.new("text") + 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("") REXML::Functions.context = {node: document.root} From f5c707dd2e6fe2a6cd030cdd80ca96bb86825258 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 28 Jun 2026 18:17:19 +0900 Subject: [PATCH 2/3] Update XPath internal function names --- lib/rexml/functions.rb | 4 +++- test/functions/test_base.rb | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/rexml/functions.rb b/lib/rexml/functions.rb index dbc6b435..c233d465 100644 --- a/lib/rexml/functions.rb +++ b/lib/rexml/functions.rb @@ -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) diff --git a/test/functions/test_base.rb b/test/functions/test_base.rb index f2c53d43..44f07c11 100644 --- a/test/functions/test_base.rb +++ b/test/functions/test_base.rb @@ -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 From 376e3bcbe4838f3f8125d012f95f6017e89a827c Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Fri, 31 Jul 2026 16:50:19 +0900 Subject: [PATCH 3/3] Update lib/rexml/functions.rb Co-authored-by: NAITOH Jun --- lib/rexml/functions.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rexml/functions.rb b/lib/rexml/functions.rb index c233d465..106a21ee 100644 --- a/lib/rexml/functions.rb +++ b/lib/rexml/functions.rb @@ -79,9 +79,9 @@ def target_named_node(node_set = nil) node = case node_set when nil - node = @context[:node] + @context[:node] when Array - node = XPathParser.sort(node_set).first + XPathParser.sort(node_set).first end node if node.respond_to?(:namespace) end