require 'forwardable' require_relative './static_context' require_relative './executable' module Saxon module XPath # An {XPath::Compiler} turns XPath expressions into executable queries you # can run against XDM Nodes (like XML documents, or parts of XML documents). # # To compile an XPath requires an +XPath::Compiler+ instance. You can create # one by calling {Compiler.create} and passing a {Saxon::Processor}, with an # optional block for context like bound namespaces and declared variables. # Alternately, and much more easily, you can call {Processor#xpath_compiler} # on the {Saxon::Processor} instance you're already working with. # # processor = Saxon::Processor.create # compiler = processor.xpath_compiler { # namespace a: 'http://example.org/a' # variable 'a:var', 'xs:string' # } # # Or... # compiler = Saxon::XPath::Compiler.create(processor) { # namespace a: 'http://example.org/a' # variable 'a:var', 'xs:string' # } # xpath = compiler.compile('//a:element[@attr = $a:var]') # matches = xpath.evaluate(document_node, { # 'a:var' => 'the value' # }) #=> Saxon::XDM::Value # # In order to use prefixed QNames in your XPaths, like +/ns:name/+, then you need # to declare prefix/namespace URI bindings when you create a compiler. # # It's also possible to make use of variables in your XPaths by declaring them at # the compiler creation stage, and then passing in values for them as XPath run # time. class Compiler # Create a new XPath::Compiler using the supplied Processor. # Passing a block gives access to a DSL for setting up the compiler's # static context. # # @param processor [Saxon::Processor] the {Saxon::Processor} to use # @yield An XPath::StaticContext::DSL block # @return [Saxon::XPath::Compiler] the new compiler instance def self.create(processor, &block) static_context = XPath::StaticContext.define(block) new(processor.to_java, static_context) end extend Forwardable attr_reader :static_context private :static_context # @api private # @param s9_processor [net.sf.saxon.s9api.Processor] the Saxon # Processor to wrap # @param static_context [Saxon::XPath::StaticContext] the static context # XPaths compiled using this compiler will have def initialize(s9_processor, static_context) @s9_processor, @static_context = s9_processor, static_context end def_delegators :static_context, :default_collation, :declared_namespaces, :declared_variables # @!attribute [r] default_collation # @return [String] the URI of the default declared collation # @!attribute [r] declared_namespaces # @return [Hash String>] declared namespaces as prefix => URI hash # @!attribute [r] declared_variables # @return [Hash Saxon::XPath::VariableDeclaration>] declared variables as QName => Declaration hash # @param expression [String] the XPath expression to compile # @return [Saxon::XPath::Executable] the executable query def compile(expression) Saxon::XPath::Executable.new(new_compiler.compile(expression), static_context) end # Allows the creation of a new {Compiler} starting from a copy of this # Compiler's static context. As with {.create}, passing a block gives # access to a DSL for setting up the compiler's static context. # # @yield An XPath::StaticContext::DSL block # @return [Saxon::XPath::Compiler] the new compiler instance def create(&block) new_static_context = static_context.define(block) self.class.new(@s9_processor, new_static_context) end private def new_compiler compiler = @s9_processor.newXPathCompiler declared_namespaces.each do |prefix, uri| compiler.declareNamespace(prefix, uri) end declared_variables.each do |_, decl| compiler.declareVariable(*decl.compiler_args) end compiler.declareDefaultCollation(default_collation) unless default_collation.nil? compiler end end end end