require_relative '../qname' require_relative './variable_declaration' module Saxon module XPath # Raised when an attempt to declare a variable is made using a string for # the qname with a namespace prefix that has not been declared in the # context yet class MissingVariableNamespaceError < StandardError def initialize(variable_name, prefix) @variable_name, @prefix = variable_name, prefix end # error message reports which unbound prefix is a problem, and how it was used def to_s "Namespace prefix ‘#{@prefix}’ for variable name ‘#{@variable_name}’ is not bound to a URI" end end # @api private # Represents the static context for a compiled XPath. {StaticContext}s are immutable. class StaticContext # methods used by both {StaticContext} and {StaticContext::DSL} module Common # @param args [Hash] # @option args [Hash Saxon::XPath::VariableDeclaration>] :declared_variables Hash of declared variables # @option args [Hash String>] :declared_namespaces Hash of namespace bindings prefix => URI # @option args [Hash java.text.Collator>] :declared_collations Hash of URI => Collator bindings # @option args [String] :default_collation URI of the default collation def initialize(args = {}) @declared_variables = args.fetch(:declared_variables, {}).freeze @declared_namespaces = args.fetch(:declared_namespaces, {}).freeze @default_collation = args.fetch(:default_collation, nil).freeze end # returns the context details in a hash suitable for initializing a new one # @return [Hash Hash,null>] the args hash def args_hash { declared_namespaces: @declared_namespaces, declared_variables: @declared_variables, default_collation: @default_collation } end end # @api public # Provides the hooks for constructing a {StaticContext} with a DSL. class DSL include Common # @api private # Create an instance based on the args hash, and execute the passed in Proc/lambda against it using #instance_exec and return a # new {StaticContext} with the results # @param block [Proc] a Proc/lambda (or to_proc'd containing DSL calls # @return [Saxon::XPath::StaticContext] def self.define(block, args = {}) dsl = new(args) dsl.instance_exec(&block) unless block.nil? StaticContext.new(dsl.args_hash) end # Set the default Collation to use. This should be one of the special # collation URIs Saxon recognises, or one that has been registered # using Saxon::Processor#declare_collations on the Processor that # created the {XPath::Compiler} this context is for. # # @param collation_uri [String] The URI of the Collation to set as the default def default_collation(collation_uri) @default_collation = collation_uri end # Bind prefixes to namespace URIs # # @param namespaces [Hash{String, Symbol => String}] def namespace(namespaces = {}) @declared_namespaces = @declared_namespaces.merge(namespaces.map { |k, v| [k.to_s, v] }.to_h).freeze end # Declare a XPath variable's existence in the context # # @param qname [String, Saxon::QName] The name of the variable as # explicit QName or prefix:name string form. The string form requires # the namespace prefix to have already been declared with {#namespace} # @param sequence_type [String, Saxon::SequenceType, null] The type of # the variable, either as a string using the same form as an XSLT # as="" type definition, or as a {Saxon::SequenceType} directly. # # If it's nil, then the default item()* – anything – type declaration is used def variable(qname, sequence_type = nil) qname = resolve_variable_qname(qname) @declared_variables = @declared_variables.merge({ qname => resolve_variable_declaration(qname, sequence_type) }).freeze end private def resolve_variable_qname(qname_or_string) Saxon::QName.resolve(qname_or_string, @declared_namespaces) end def resolve_variable_declaration(qname, sequence_type = nil) Saxon::XPath::VariableDeclaration.new(qname, Saxon.SequenceType(sequence_type || 'item()*')) end end include Common # Executes the Proc/lambda passed in with a new instance of # {StaticContext} as self, allowing the DSL methods to be # called in a DSL-ish way # # @param block [Proc] the block of DSL calls to be executed # @return [Saxon::XPath::StaticContext] the static context created by the block def self.define(block) DSL.define(block) end # @return [String] The default collation URI as a String attr_reader :default_collation # @return [Hash Saxon::XPath::VariableDeclaration] the declared variables attr_reader :declared_variables # @return [Hash String>] the declared namespaces, as a prefix => uri hash attr_reader :declared_namespaces # @return [Saxon::QName] # @overload resolve_variable_qname(qname) # returns the QName # @param qname_or_string [Saxon::QName] the name as a QName # @overload resolve_variable_qname(string) # resolve the prefix:local_name string into a proper QName by # looking up the prefix in the {#declared_namespaces} # @param qname_or_string [String] the name as a string def resolve_variable_qname(qname_or_string) Saxon::QName.resolve(qname_or_string, declared_namespaces) end # @api private # Create a new {StaticContext} based on this one. Passed Proc is evaluated in the same way as {DSL.define} def define(block) DSL.define(block, args_hash) end end end end