Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add global XPath functions handler #1894

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 3 additions & 7 deletions ext/java/nokogiri/XmlXpathContext.java
@@ -1,6 +1,5 @@
package nokogiri;

import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -104,7 +103,7 @@ public class XmlXpathContext extends RubyObject
{
String query = rbQuery.convertToString().asJavaString();

if (!handler.isNil() && !isContainsPrefix(query)) {
if (!isContainsPrefix(query)) {
//
// The user has passed in a handler, but isn't using the `nokogiri:` prefix as
// instructed in JRuby land, so let's try to be clever and rewrite the query, inserting
Expand All @@ -113,17 +112,14 @@ public class XmlXpathContext extends RubyObject
StringBuilder namespacedQuery = new StringBuilder();
int jchar = 0;

// Find the methods on the handler object
Set<String> methodNames = handler.getMetaClass().getMethods().keySet();

// Find the function calls in the xpath query
Matcher xpathFunctionCalls = XPathFunctionCaptureRE.matcher(query);

while (xpathFunctionCalls.find()) {
namespacedQuery.append(query.subSequence(jchar, xpathFunctionCalls.start()));
jchar = xpathFunctionCalls.start();

if (methodNames.contains(xpathFunctionCalls.group())) {
if (handler.respondsTo(xpathFunctionCalls.group())) {
namespacedQuery.append(NokogiriNamespaceContext.NOKOGIRI_PREFIX);
namespacedQuery.append(":");
}
Expand All @@ -132,7 +128,7 @@ public class XmlXpathContext extends RubyObject
jchar = xpathFunctionCalls.end();
}

if (jchar < query.length() - 1) {
if (jchar < query.length()) {
namespacedQuery.append(query.subSequence(jchar, query.length()));
}
query = namespacedQuery.toString();
Expand Down
1 change: 1 addition & 0 deletions lib/nokogiri/xml.rb
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions lib/nokogiri/xml/searchable.rb
Expand Up @@ -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?
Expand Down
19 changes: 19 additions & 0 deletions 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
1 change: 1 addition & 0 deletions nokogiri.gemspec
Expand Up @@ -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",
Expand Down
34 changes: 34 additions & 0 deletions test/xml/test_xpath.rb
Expand Up @@ -166,6 +166,40 @@ def test_search_with_xpath_query_using_namespaced_custom_function
end
end

def test_search_with_xpath_query_uses_global_custom_selectors_with_arguments
XPathFunctions.include(Module.new 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")]', @handler)
end
refute_empty(set)
set.each do |node|
assert_equal("Yes", node["domestic"])
end
end

def test_search_with_xpath_query_uses_global_custom_selectors_with_arguments_without_namespace
skip("Testing fallback behavior in JRuby") unless Nokogiri.jruby?

XPathFunctions.include(Module.new do
def our_filter(*args)
my_filter(*args)
end
end)

set = @xml.search('//employee/address[our_filter(., "domestic", "Yes")]', @handler)
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)
Expand Down