diff --git a/lib/oga/xml/querying.rb b/lib/oga/xml/querying.rb
index 1eee81c..80691b0 100644
--- a/lib/oga/xml/querying.rb
+++ b/lib/oga/xml/querying.rb
@@ -10,6 +10,7 @@ module Oga
# document = Oga.parse_xml <<-EOF
#
# Alice
+ # Bob
#
# 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
diff --git a/lib/oga/xpath/compiler.rb b/lib/oga/xpath/compiler.rb
index ec711b3..d407fdf 100644
--- a/lib/oga/xpath/compiler.rb
+++ b/lib/oga/xpath/compiler.rb
@@ -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
diff --git a/spec/oga/xml/querying_spec.rb b/spec/oga/xml/querying_spec.rb
index e0400e1..df20813 100644
--- a/spec/oga/xml/querying_spec.rb
+++ b/spec/oga/xml/querying_spec.rb
@@ -3,6 +3,10 @@ require 'spec_helper'
describe Oga::XML::Querying do
before do
@document = parse('foo')
+ @document2 = parse('bar')
+ @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
diff --git a/spec/oga/xpath/compiler/namespace_aliases_spec.rb b/spec/oga/xpath/compiler/namespace_aliases_spec.rb
new file mode 100644
index 0000000..98726b7
--- /dev/null
+++ b/spec/oga/xpath/compiler/namespace_aliases_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Oga::XPath::Compiler do
+ before do
+ @document = parse('')
+ @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
diff --git a/spec/support/evaluation_helpers.rb b/spec/support/evaluation_helpers.rb
index 5bcbb55..f006d8f 100644
--- a/spec/support/evaluation_helpers.rb
+++ b/spec/support/evaluation_helpers.rb
@@ -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)