diff --git a/ext/java/nokogiri/XmlXpathContext.java b/ext/java/nokogiri/XmlXpathContext.java index 16939f3066b..4558801fc67 100644 --- a/ext/java/nokogiri/XmlXpathContext.java +++ b/ext/java/nokogiri/XmlXpathContext.java @@ -1,5 +1,6 @@ package nokogiri; +import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -13,8 +14,11 @@ import org.apache.xpath.jaxp.JAXPVariableStack; import org.apache.xpath.objects.XObject; import org.jruby.Ruby; +import org.jruby.RubyArray; +import org.jruby.RubyBasicObject; import org.jruby.RubyClass; import org.jruby.RubyObject; +import org.jruby.RubySymbol; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; @@ -114,7 +118,12 @@ public class XmlXpathContext extends RubyObject int jchar = 0; // Find the methods on the handler object - Set methodNames = handler.getMetaClass().getMethods().keySet(); + List methodNames = (RubyArray)((RubyBasicObject)handler).send( + context, + context.getRuntime().newSymbol("public_methods"), + context.getRuntime().newBoolean(false), + null + ); // Find the function calls in the xpath query Matcher xpathFunctionCalls = XPathFunctionCaptureRE.matcher(query); diff --git a/lib/nokogiri/xml.rb b/lib/nokogiri/xml.rb index 9d16fb14989..fa368c283ab 100644 --- a/lib/nokogiri/xml.rb +++ b/lib/nokogiri/xml.rb @@ -67,6 +67,7 @@ def fragment(string, options = ParseOptions::DEFAULT_XML, &block) require_relative "xml/syntax_error" require_relative "xml/xpath" require_relative "xml/xpath_context" +require_relative "xml/xpath_functions" require_relative "xml/builder" require_relative "xml/reader" require_relative "xml/notation" diff --git a/lib/nokogiri/xml/searchable.rb b/lib/nokogiri/xml/searchable.rb index cc493e56f47..639f9770f2e 100644 --- a/lib/nokogiri/xml/searchable.rb +++ b/lib/nokogiri/xml/searchable.rb @@ -253,6 +253,7 @@ def extract_params(params) # :nodoc: ![Hash, String, Symbol].include?(param.class) end params -= [handler] if handler + handler = XPathFunctions.wrap(handler) hashes = [] while Hash === params.last || params.last.nil? diff --git a/lib/nokogiri/xml/xpath_functions.rb b/lib/nokogiri/xml/xpath_functions.rb new file mode 100644 index 00000000000..2ad5f2c5e67 --- /dev/null +++ b/lib/nokogiri/xml/xpath_functions.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "delegate" + +module Nokogiri + module XML + class XPathFunctions < SimpleDelegator + class << self + def wrap(handler) + if handler.nil? + @wrap_nil ||= new(Object.new) + else + new(handler) + end + end + end + end + end +end diff --git a/nokogiri.gemspec b/nokogiri.gemspec index 77836a5560f..8ffd4096a3b 100644 --- a/nokogiri.gemspec +++ b/nokogiri.gemspec @@ -309,6 +309,7 @@ Gem::Specification.new do |spec| "lib/nokogiri/xml/xpath.rb", "lib/nokogiri/xml/xpath/syntax_error.rb", "lib/nokogiri/xml/xpath_context.rb", + "lib/nokogiri/xml/xpath_functions.rb", "lib/nokogiri/xslt.rb", "lib/nokogiri/xslt/stylesheet.rb", "lib/xsd/xmlparser/nokogiri.rb", diff --git a/test/xml/test_xpath.rb b/test/xml/test_xpath.rb index 0de81ba3513..610e6039b98 100644 --- a/test/xml/test_xpath.rb +++ b/test/xml/test_xpath.rb @@ -156,6 +156,24 @@ def test_search_with_xpath_query_uses_custom_selectors_with_arguments end end + def test_search_with_xpath_query_uses_global_custom_selectors_with_arguments + XPathFunctions.class_eval do + def our_filter(*args) + my_filter(*args) + end + end + + set = if Nokogiri.uses_libxml? + @xml.search('//employee/address[our_filter(., "domestic", "Yes")]', @handler) + else + @xml.search('//employee/address[nokogiri:our_filter(., "domestic", "Yes")]', @ns, @handler) + end + refute_empty(set) + set.each do |node| + assert_equal("Yes", node["domestic"]) + end + end + def test_pass_self_to_function set = if Nokogiri.uses_libxml? @xml.xpath('//employee/address[my_filter(., "domestic", "Yes")]', @handler)