Skip to content

Commit

Permalink
deprecate: non-namespaced custom XPath handler functions
Browse files Browse the repository at this point in the history
See #2147 for more context.
  • Loading branch information
flavorjones committed Apr 28, 2023
1 parent 839aa37 commit 37efa57
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 10 deletions.
4 changes: 2 additions & 2 deletions ext/nokogiri/nokogiri.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ xmlParserCtxtPtr noko_xml_sax_parser_context_unwrap(VALUE rb_context);
#define DISCARD_CONST_QUAL_XMLCHAR(v) DISCARD_CONST_QUAL(xmlChar *, v)

#if HAVE_RB_CATEGORY_WARNING
# define NOKO_WARN_DEPRECATION(message) rb_category_warning(RB_WARN_CATEGORY_DEPRECATED, message)
# define NOKO_WARN_DEPRECATION(message...) rb_category_warning(RB_WARN_CATEGORY_DEPRECATED, message)
#else
# define NOKO_WARN_DEPRECATION(message) rb_warning(message)
# define NOKO_WARN_DEPRECATION(message...) rb_warning(message)
#endif

void Nokogiri_structured_error_func_save(libxmlStructuredErrorHandlerState *handler_state);
Expand Down
7 changes: 7 additions & 0 deletions ext/nokogiri/xml_xpath_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@ handler_lookup(void *data, const xmlChar *c_name, const xmlChar *c_ns_uri)
{
VALUE rb_handler = (VALUE)data;
if (rb_respond_to(rb_handler, rb_intern((const char *)c_name))) {
if (c_ns_uri == NULL) {
NOKO_WARN_DEPRECATION(
"A custom XPath or CSS handler function named '%s' is being invoked without a namespace."
" Please update your query to reference this function as 'nokogiri:%s'."
" Invoking custom handler functions without a namespace is deprecated and support will be removed in a future release of Nokogiri.",
c_name, c_name);
}
return method_caller;
}

Expand Down
21 changes: 13 additions & 8 deletions lib/nokogiri/xml/searchable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ module Searchable
# node.search('.//address[@domestic=$value]', nil, {:value => 'Yes'})
#
# 💡 Custom XPath functions and CSS pseudo-selectors may also be defined. To define custom
# functions create a class and implement the function you want to define. The first argument
# to the method will be the current matching NodeSet. Any other arguments are ones that you
# pass in. Note that this class may appear anywhere in the argument list. For example:
# functions create a class and implement the function you want to define, which will be in the
# `nokogiri` namespace in XPath queries.
#
# The first argument to the method will be the current matching NodeSet. Any other arguments
# are ones that you pass in. Note that this class may appear anywhere in the argument
# list. For example:
#
# handler = Class.new {
# def regex node_set, regex
# node_set.find_all { |node| node['some_attribute'] =~ /#{regex}/ }
# end
# }.new
# node.search('.//title[regex(., "\w+")]', 'div.employee:regex("[0-9]+")', handler)
# node.search('.//title[nokogiri:regex(., "\w+")]', 'div.employee:regex("[0-9]+")', handler)
#
# See Searchable#xpath and Searchable#css for further usage help.
def search(*args)
Expand Down Expand Up @@ -160,16 +163,18 @@ def at_css(*args)
# node.xpath('.//address[@domestic=$value]', nil, {:value => 'Yes'})
#
# 💡 Custom XPath functions may also be defined. To define custom functions create a class and
# implement the function you want to define. The first argument to the method will be the
# current matching NodeSet. Any other arguments are ones that you pass in. Note that this
# class may appear anywhere in the argument list. For example:
# implement the function you want to define, which will be in the `nokogiri` namespace.
#
# The first argument to the method will be the current matching NodeSet. Any other arguments
# are ones that you pass in. Note that this class may appear anywhere in the argument
# list. For example:
#
# handler = Class.new {
# def regex(node_set, regex)
# node_set.find_all { |node| node['some_attribute'] =~ /#{regex}/ }
# end
# }.new
# node.xpath('.//title[regex(., "\w+")]', handler)
# node.xpath('.//title[nokogiri:regex(., "\w+")]', handler)
#
def xpath(*args)
paths, handler, ns, binds = extract_params(args)
Expand Down
18 changes: 18 additions & 0 deletions test/xml/test_xpath.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@ def test_css_search_with_ambiguous_integer_or_string_attributes
refute_nil(doc.at_css("img[width=200]"))
end

def test_xpath_with_nonnamespaced_custom_function_is_deprecated_but_works
skip_unless_libxml2("only deprecated in CRuby")

result = nil
assert_output("", /Invoking custom handler functions without a namespace is deprecated/) do
result = @xml.xpath("anint()", @handler)
end
assert_equal(1230456, result)
end

def test_xpath_with_namespaced_custom_function_is_not_deprecated
result = nil
assert_silent do
result = @xml.xpath("nokogiri:anint()", @handler)
end
assert_equal(1230456, result)
end

def test_css_search_uses_custom_selectors_with_arguments
set = @xml.css('employee > address:my_filter("domestic", "Yes")', @handler)
refute_empty(set)
Expand Down

0 comments on commit 37efa57

Please sign in to comment.