Add support for XPath namespace aliases
This fixes https://gitlab.com/yorickpeterse/oga/issues/176
This commit is contained in:
parent
da9721cb34
commit
977bd594c8
|
@ -10,6 +10,7 @@ module Oga
|
|||
# document = Oga.parse_xml <<-EOF
|
||||
# <people>
|
||||
# <person age="25">Alice</person>
|
||||
# <ns:person xmlns:ns="http://example.net">Bob</ns:person>
|
||||
# </people>
|
||||
# EOF
|
||||
#
|
||||
|
@ -25,15 +26,23 @@ module Oga
|
|||
#
|
||||
# document.xpath('people/person[@age = $age]', 'age' => 25)
|
||||
#
|
||||
# Using namespace aliases:
|
||||
#
|
||||
# namespaces = {'example' => 'http://example.net'}
|
||||
# document.xpath('people/example:person', namespaces: namespaces)
|
||||
#
|
||||
# @param [String] expression The XPath expression to run.
|
||||
#
|
||||
# @param [Hash] variables Variables to bind. The keys of this Hash should
|
||||
# be String values.
|
||||
#
|
||||
# @param [Hash] namespaces Namespace aliases. The keys of this Hash should
|
||||
# be String values.
|
||||
#
|
||||
# @return [Oga::XML::NodeSet]
|
||||
def xpath(expression, variables = {})
|
||||
def xpath(expression, variables = {}, namespaces: nil)
|
||||
ast = XPath::Parser.parse_with_cache(expression)
|
||||
block = XPath::Compiler.compile_with_cache(ast)
|
||||
block = XPath::Compiler.compile_with_cache(ast, namespaces: namespaces)
|
||||
|
||||
block.call(self, variables)
|
||||
end
|
||||
|
|
|
@ -42,12 +42,16 @@ module Oga
|
|||
# Compiles and caches an AST.
|
||||
#
|
||||
# @see [#compile]
|
||||
def self.compile_with_cache(ast)
|
||||
CACHE.get_or_set(ast) { new.compile(ast) }
|
||||
def self.compile_with_cache(ast, namespaces: nil)
|
||||
cache_key = namespaces ? [ast, namespaces] : ast
|
||||
CACHE.get_or_set(cache_key) { new(namespaces: namespaces).compile(ast) }
|
||||
end
|
||||
|
||||
def initialize
|
||||
# @param [Hash] namespaces
|
||||
def initialize(namespaces: nil)
|
||||
reset
|
||||
|
||||
@namespaces = namespaces
|
||||
end
|
||||
|
||||
# Resets the internal state.
|
||||
|
@ -1385,7 +1389,18 @@ module Oga
|
|||
end
|
||||
|
||||
if ns and ns != STAR
|
||||
ns_match = input.namespace_name.eq(string(ns))
|
||||
if @namespaces
|
||||
ns_uri = @namespaces[ns]
|
||||
ns_match =
|
||||
if ns_uri
|
||||
input.namespace.and(input.namespace.uri.eq(string(ns_uri)))
|
||||
else
|
||||
self.false
|
||||
end
|
||||
else
|
||||
ns_match = input.namespace_name.eq(string(ns))
|
||||
end
|
||||
|
||||
condition = condition ? condition.and(ns_match) : ns_match
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,10 @@ require 'spec_helper'
|
|||
describe Oga::XML::Querying do
|
||||
before do
|
||||
@document = parse('<a>foo</a>')
|
||||
@document2 = parse('<a xmlns:x="y"><x:b>bar</x:b></a>')
|
||||
@namespaces = {
|
||||
"n" => "y"
|
||||
}
|
||||
end
|
||||
|
||||
describe '#xpath' do
|
||||
|
@ -17,6 +21,10 @@ describe Oga::XML::Querying do
|
|||
it 'evaluates an expression using a variable' do
|
||||
expect(@document.xpath('$number', 'number' => 10)).to eq(10)
|
||||
end
|
||||
|
||||
it 'respects custom namespace aliases' do
|
||||
expect(@document2.xpath('a/n:b', namespaces: @namespaces)[0].text).to eq('bar')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#at_xpath' do
|
||||
|
@ -31,6 +39,10 @@ describe Oga::XML::Querying do
|
|||
it 'evaluates an expression using a variable' do
|
||||
expect(@document.at_xpath('$number', 'number' => 10)).to eq(10)
|
||||
end
|
||||
|
||||
it 'respects custom namespace aliases' do
|
||||
expect(@document2.at_xpath('a/n:b', namespaces: @namespaces).text).to eq('bar')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#css' do
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Oga::XPath::Compiler do
|
||||
before do
|
||||
@document = parse('<root xmlns:x="y"><x:a></x:a><b x:num="10"></b></root>')
|
||||
@root = @document.children[0]
|
||||
@a = @root.children[0]
|
||||
@b = @root.children[1]
|
||||
@attr = @b.attributes[0]
|
||||
@namespaces = {
|
||||
"n" => "y"
|
||||
}
|
||||
end
|
||||
|
||||
describe 'with custom namespace aliases' do
|
||||
it 'uses aliases when querying an element' do
|
||||
expect(evaluate_xpath(@document, 'root/n:a', namespaces: @namespaces)).to eq(node_set(@a))
|
||||
end
|
||||
|
||||
it "doesn't use namespaces in XPath expression when querying an element" do
|
||||
expect(evaluate_xpath(@document, 'root/x:a', namespaces: @namespaces)).to eq(node_set)
|
||||
end
|
||||
|
||||
it 'uses aliases when querying an attribute' do
|
||||
expect(evaluate_xpath(@document, 'root/b/@n:num', namespaces: @namespaces)).to eq(node_set(@attr))
|
||||
end
|
||||
|
||||
it "doesn't use namespaces in XPath expression when querying an attribute" do
|
||||
expect(evaluate_xpath(@document, 'root/b/@x:num', namespaces: @namespaces)).to eq(node_set)
|
||||
end
|
||||
|
||||
it 'uses aliases when querying an element with a namespaced attribute' do
|
||||
expect(evaluate_xpath(@document, 'root/b[@n:num]', namespaces: @namespaces)).to eq(node_set(@b))
|
||||
end
|
||||
|
||||
it "doesn't use namespaces in XPath expression when querying an element with a namespaced attribute" do
|
||||
expect(evaluate_xpath(@document, 'root/b[@x:num]', namespaces: @namespaces)).to eq(node_set)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,9 +5,9 @@ module Oga
|
|||
# @param [String] xpath
|
||||
# @return [Oga::XML::NodeSet]
|
||||
#
|
||||
def evaluate_xpath(document, xpath = self.class.description)
|
||||
def evaluate_xpath(document, xpath = self.class.description, namespaces: nil)
|
||||
ast = parse_xpath(xpath)
|
||||
compiler = Oga::XPath::Compiler.new
|
||||
compiler = Oga::XPath::Compiler.new(namespaces: namespaces)
|
||||
block = compiler.compile(ast)
|
||||
|
||||
block.call(document)
|
||||
|
|
Loading…
Reference in New Issue