lib/saxon/xslt/evaluation_context.rb
require 'set'
require_relative '../qname'
module Saxon
module XSLT
# @api private
# Represents the evaluation context for an XSLT compiler, and stylesheets
# compiled using one. {EvaluationContext}s are immutable.
class EvaluationContext
# methods used by both {EvaluationContext} and {EvaluationContext::DSL}
module Common
# @param args [Hash]
# @option args [String] default_collation URI of the default collation
# @option args [Hash<String,Symbol,Saxon::QName =>
# Object,Saxon::XDM::Value] static_parameters Hash of QName => value
# bindings for static (compile-time) parameters for this compiler
def initialize(args = {})
@default_collation = args.fetch(:default_collation, nil).freeze
@static_parameters = args.fetch(:static_parameters, {}).freeze
@global_parameters = args.fetch(:global_parameters, {}).freeze
@initial_template_parameters = args.fetch(:initial_template_parameters, {}).freeze
@initial_template_tunnel_parameters = args.fetch(:initial_template_tunnel_parameters, {}).freeze
end
# returns the context details in a hash suitable for initializing a new one
# @return [Hash<Symbol => Hash,null>] the args hash
def args_hash
check_for_clashing_parameters!
{
default_collation: @default_collation,
static_parameters: @static_parameters,
global_parameters: @global_parameters,
initial_template_parameters: @initial_template_parameters,
initial_template_tunnel_parameters: @initial_template_tunnel_parameters
}
end
private
def check_for_clashing_parameters!
@checked ||= begin
if Set.new(@static_parameters.keys).intersect?(Set.new(@global_parameters.keys))
raise GlobalAndStaticParameterClashError
else
true
end
end
end
end
# @api public
# Provides the hooks for constructing a {EvaluationContext} 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 <tt>#instance_exec</tt> and return a
# new {EvaluationContext} with the results
# @param block [Proc] a Proc/lambda (or <tt>to_proc</tt>'d containing DSL calls
# @return [Saxon::XSLT::EvaluationContext]
def self.define(block, args = {})
dsl = new(args)
dsl.instance_exec(&block) unless block.nil?
EvaluationContext.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 {XSLT::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
# Set the value for static parameters (those that must be known at
# compile-time to avoid an error), as a hash of QName => Value pairs.
# Parameter QNames can be declared as Strings or Symbols if they are
# not in any namespace, otherwise an explicit {Saxon::QName} must be
# used. Values can be provided as explicit XDM::Values:
# {Saxon::XDM::Value}, {Saxon::XDM::Node}, and {Saxon::XDM::AtomicValue}, or
# as Ruby objects which will be converted to {Saxon::XDM::AtomicValue}s
# in the usual way.
#
# @param parameters [Hash<String,Symbol,Saxon::QName =>
# Object,Saxon::XDM::Value>] Hash of QName => value
def static_parameters(parameters = {})
@static_parameters = @static_parameters.merge(process_parameters(parameters)).freeze
end
# Set the values for global parameters (those that are available to all templates and functions).
#
# @see EvaluationContext#static_parameters for details of the argument format
#
# @param parameters [Hash<String,Symbol,Saxon::QName =>
# Object,Saxon::XDM::Value>] Hash of QName => value
def global_parameters(parameters = {})
@global_parameters = @global_parameters.merge(process_parameters(parameters)).freeze
end
# Set the values for parameters made available only to the initial
# template invoked (either via apply_templates or call_template),
# effectively as if <tt><xsl:with-param tunnel="no"></tt> was being used.
#
# @see EvaluationContext#static_parameters for details of the argument format
#
# @param parameters [Hash<String,Symbol,Saxon::QName =>
# Object,Saxon::XDM::Value>] Hash of QName => value
def initial_template_parameters(parameters = {})
@initial_template_parameters = @initial_template_parameters.merge(process_parameters(parameters))
end
# Set the values for tunneling parameters made available only to the initial
# template invoked (either via apply_templates or call_template),
# effectively as if <tt><xsl:with-param tunnel="yes"></tt> was being used.
#
# @see EvaluationContext#static_parameters for details of the argument format
#
# @param parameters [Hash<String,Symbol,Saxon::QName =>
# Object,Saxon::XDM::Value>] Hash of QName => value
def initial_template_tunnel_parameters(parameters = {})
@initial_template_tunnel_parameters = @initial_template_tunnel_parameters.merge(process_parameters(parameters))
end
private
def process_parameters(parameters)
XSLT::ParameterHelper.process_parameters(parameters)
end
end
include Common
# @api private
# Executes the Proc/lambda passed in with a new instance of
# {EvaluationContext} as <tt>self</tt>, 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::XSLT::EvaluationContext] 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::QName => Saxon::XDM::Value>] All the static parameters
attr_reader :static_parameters
# @return [Hash<Saxon::QName => Saxon::XDM::Value>] All the global parameters
attr_reader :global_parameters
# @return [Hash<Saxon::QName => Saxon::XDM::Value>] All the initial template parameters
attr_reader :initial_template_parameters
# @return [Hash<Saxon::QName => Saxon::XDM::Value>] All the initial template parameters with tunnelling = "yes"
attr_reader :initial_template_tunnel_parameters
# @api private
# When passed a Proc, create a new EvaluationContext based on this one, with the same DSL available as in {.define}.
#
# When passed {nil}, simply return the EvaluationContext unaltered.
def define(block)
block.nil? ? self : DSL.define(block, args_hash)
end
end
# Error raised when a global and static parameter of the same name are
# declared
class GlobalAndStaticParameterClashError < StandardError
end
# parameter shorthand name/value-to-full QName/XDM::Value helper module
module ParameterHelper
# process shorthand parameter hash into fully-qualified QName / Value hash
# @param parameters [Hash<String, Saxon::QName => Object>]
# @return [Hash<Saxon::QName => Saxon::XDM::Value>]
# @see Saxon::QName.resolve() for more about the QName resolution process
# @see Saxon::XDM.Value() for more about the conversion of Object into XDM Values
def self.process_parameters(parameters)
parameters.map { |qname, value|
[Saxon::QName.resolve(qname), Saxon::XDM.Value(value)]
}.to_h
end
# generate Java Map from fully qualified parameter hash
def self.to_java(parameters)
Hash[parameters.map { |k,v| [k.to_java, v.to_java] }].to_java
end
end
end
end