Skip to content

Commit

Permalink
feat: Add global XPath functions handler
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanhefner committed Aug 30, 2022
1 parent 704a5f8 commit 2a2f9f9
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 7 deletions.
10 changes: 3 additions & 7 deletions ext/java/nokogiri/XmlXpathContext.java
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit 2a2f9f9

Please sign in to comment.