saxon-rb 0.4.0-java → 0.5.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +429 -42
- data/Gemfile +2 -2
- data/README.md +317 -10
- data/Rakefile +237 -7
- data/lib/net/sf/saxon/Saxon-HE/{9.9.1-5/Saxon-HE-9.9.1-5.jar → 9.9.1-6/Saxon-HE-9.9.1-6.jar} +0 -0
- data/lib/saxon-rb.rb +1 -0
- data/lib/{saxon_jars.rb → saxon-rb_jars.rb} +2 -2
- data/lib/saxon.rb +13 -0
- data/lib/saxon/axis_iterator.rb +8 -1
- data/lib/saxon/configuration.rb +1 -0
- data/lib/saxon/item_type.rb +12 -17
- data/lib/saxon/item_type/lexical_string_conversion.rb +136 -58
- data/lib/saxon/item_type/value_to_ruby.rb +13 -0
- data/lib/saxon/loader.rb +4 -1
- data/lib/saxon/nokogiri.rb +78 -0
- data/lib/saxon/occurrence_indicator.rb +32 -3
- data/lib/saxon/processor.rb +32 -1
- data/lib/saxon/qname.rb +37 -2
- data/lib/saxon/s9api.rb +5 -0
- data/lib/saxon/sequence_type.rb +131 -0
- data/lib/saxon/source.rb +207 -71
- data/lib/saxon/version.rb +1 -1
- data/lib/saxon/xdm.rb +7 -0
- data/lib/saxon/xdm/array.rb +16 -0
- data/lib/saxon/xdm/atomic_value.rb +7 -1
- data/lib/saxon/xdm/empty_sequence.rb +13 -0
- data/lib/saxon/xdm/external_object.rb +1 -0
- data/lib/saxon/xdm/function_item.rb +1 -0
- data/lib/saxon/xdm/item.rb +7 -0
- data/lib/saxon/xdm/map.rb +38 -0
- data/lib/saxon/xdm/node.rb +19 -1
- data/lib/saxon/xdm/sequence_like.rb +15 -0
- data/lib/saxon/xdm/value.rb +21 -5
- data/lib/saxon/xpath.rb +9 -0
- data/lib/saxon/xpath/compiler.rb +36 -1
- data/lib/saxon/xpath/executable.rb +53 -28
- data/lib/saxon/xpath/static_context.rb +19 -39
- data/lib/saxon/xpath/variable_declaration.rb +16 -49
- data/lib/saxon/xslt.rb +12 -0
- data/lib/saxon/xslt/compiler.rb +75 -6
- data/lib/saxon/xslt/evaluation_context.rb +19 -3
- data/lib/saxon/xslt/executable.rb +204 -14
- data/saxon-rb.gemspec +1 -1
- metadata +9 -7
- data/saxon.gemspec +0 -30
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative '../qname'
|
2
2
|
require_relative './variable_declaration'
|
3
|
+
|
3
4
|
module Saxon
|
4
5
|
module XPath
|
5
6
|
# Raised when an attempt to declare a variable is made using a string for
|
@@ -10,11 +11,13 @@ module Saxon
|
|
10
11
|
@variable_name, @prefix = variable_name, prefix
|
11
12
|
end
|
12
13
|
|
14
|
+
# error message reports which unbound prefix is a problem, and how it was used
|
13
15
|
def to_s
|
14
16
|
"Namespace prefix ‘#{@prefix}’ for variable name ‘#{@variable_name}’ is not bound to a URI"
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
20
|
+
# @api private
|
18
21
|
# Represents the static context for a compiled XPath. {StaticContext}s are immutable.
|
19
22
|
class StaticContext
|
20
23
|
# methods used by both {StaticContext} and {StaticContext::DSL}
|
@@ -41,11 +44,12 @@ module Saxon
|
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
47
|
+
# @api public
|
44
48
|
# Provides the hooks for constructing a {StaticContext} with a DSL.
|
45
|
-
# @api private
|
46
49
|
class DSL
|
47
50
|
include Common
|
48
51
|
|
52
|
+
# @api private
|
49
53
|
# 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
|
50
54
|
# new {StaticContext} with the results
|
51
55
|
# @param block [Proc] a Proc/lambda (or <tt>to_proc</tt>'d containing DSL calls
|
@@ -70,28 +74,23 @@ module Saxon
|
|
70
74
|
#
|
71
75
|
# @param namespaces [Hash{String, Symbol => String}]
|
72
76
|
def namespace(namespaces = {})
|
73
|
-
@declared_namespaces = @declared_namespaces.merge(namespaces.
|
77
|
+
@declared_namespaces = @declared_namespaces.merge(namespaces.map { |k, v| [k.to_s, v] }.to_h).freeze
|
74
78
|
end
|
75
79
|
|
76
80
|
# Declare a XPath variable's existence in the context
|
77
81
|
#
|
78
82
|
# @param qname [String, Saxon::QName] The name of the variable as
|
79
|
-
#
|
80
|
-
#
|
81
|
-
# @param
|
82
|
-
# variable, either as a string using the same form as an XSLT
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
|
87
|
-
# can parse into an XDM type (e.g. <tt>xs:string</tthat
|
88
|
-
# {Saxon::ItemType} can parse into an XDM type (e.g.
|
89
|
-
# <tt>xs:string</tt> or <tt>element()</tt>).
|
90
|
-
# If it's nil, then the default <tt>item()*</tt> – anything – type declaration is used
|
91
|
-
def variable(qname, type = nil)
|
83
|
+
# explicit QName or prefix:name string form. The string form requires
|
84
|
+
# the namespace prefix to have already been declared with {#namespace}
|
85
|
+
# @param sequence_type [String, Saxon::SequenceType, null] The type of
|
86
|
+
# the variable, either as a string using the same form as an XSLT
|
87
|
+
# <tt>as=""</tt> type definition, or as a {Saxon::SequenceType} directly.
|
88
|
+
#
|
89
|
+
# If it's nil, then the default <tt>item()*</tt> – anything – type declaration is used
|
90
|
+
def variable(qname, sequence_type = nil)
|
92
91
|
qname = resolve_variable_qname(qname)
|
93
92
|
@declared_variables = @declared_variables.merge({
|
94
|
-
qname => resolve_variable_declaration(qname,
|
93
|
+
qname => resolve_variable_declaration(qname, sequence_type)
|
95
94
|
}).freeze
|
96
95
|
end
|
97
96
|
|
@@ -101,29 +100,8 @@ module Saxon
|
|
101
100
|
Saxon::QName.resolve(qname_or_string, @declared_namespaces)
|
102
101
|
end
|
103
102
|
|
104
|
-
def
|
105
|
-
|
106
|
-
when String
|
107
|
-
occurence_char = type_decl[-1]
|
108
|
-
occurence = case occurence_char
|
109
|
-
when '?'
|
110
|
-
{zero_or_one: type_decl[0..-2]}
|
111
|
-
when '+'
|
112
|
-
{one_or_more: type_decl[0..-2]}
|
113
|
-
when '*'
|
114
|
-
{zero_or_more: type_decl[0..-2]}
|
115
|
-
else
|
116
|
-
{one: type_decl}
|
117
|
-
end
|
118
|
-
when Hash
|
119
|
-
type_decl
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def resolve_variable_declaration(qname, type)
|
124
|
-
args_hash = resolve_variable_type_decl(type) || {}
|
125
|
-
args_hash[:qname] = qname
|
126
|
-
Saxon::XPath::VariableDeclaration.new(args_hash)
|
103
|
+
def resolve_variable_declaration(qname, sequence_type = nil)
|
104
|
+
Saxon::XPath::VariableDeclaration.new(qname, Saxon.SequenceType(sequence_type || 'item()*'))
|
127
105
|
end
|
128
106
|
end
|
129
107
|
|
@@ -153,6 +131,8 @@ module Saxon
|
|
153
131
|
Saxon::QName.resolve(qname_or_string, declared_namespaces)
|
154
132
|
end
|
155
133
|
|
134
|
+
# @api private
|
135
|
+
# Create a new {StaticContext} based on this one. Passed Proc is evaluated in the same way as {DSL.define}
|
156
136
|
def define(block)
|
157
137
|
DSL.define(block, args_hash)
|
158
138
|
end
|
@@ -1,67 +1,34 @@
|
|
1
|
-
require_relative '../
|
2
|
-
require_relative '../occurrence_indicator'
|
1
|
+
require_relative '../sequence_type'
|
3
2
|
|
4
3
|
module Saxon
|
5
4
|
module XPath
|
6
|
-
# Represents an XPath variable declaration in the static context of a
|
5
|
+
# Represents an XPath variable declaration in the static context of a
|
6
|
+
# compiled XPath, providing an idiomatic Ruby way to deal with these.
|
7
7
|
class VariableDeclaration
|
8
8
|
# @return [Saxon::QName]
|
9
9
|
attr_reader :qname
|
10
|
-
# @return [Saxon::
|
11
|
-
attr_reader :
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@qname = opts.fetch(:qname)
|
20
|
-
@item_type, @occurrences = extract_type_decl(opts.reject { |k, v| k == :qname })
|
10
|
+
# @return [Saxon::SequenceType]
|
11
|
+
attr_reader :sequence_type
|
12
|
+
|
13
|
+
# @param qname [Saxon::QName] the name of the variable
|
14
|
+
# @param sequence_type [Saxon::SequenceType] the SequenceType of the
|
15
|
+
# variable
|
16
|
+
def initialize(qname, sequence_type)
|
17
|
+
@qname = qname
|
18
|
+
@sequence_type = sequence_type || Saxon.SequenceType('item()*')
|
21
19
|
end
|
22
20
|
|
23
|
-
# VariableDeclarations compare equal if their qname
|
21
|
+
# VariableDeclarations compare equal if their qname and sequence_type are equal
|
24
22
|
# @param other [Saxon::VariableDeclaration]
|
25
23
|
# @return [Boolean]
|
26
24
|
def ==(other)
|
27
|
-
VariableDeclaration === other && qname == other.qname &&
|
25
|
+
VariableDeclaration === other && qname == other.qname && sequence_type == other.sequence_type
|
28
26
|
end
|
29
27
|
|
30
28
|
# @api private
|
29
|
+
# return the arguments XPathCompiler.declareVariable expects
|
31
30
|
def compiler_args
|
32
|
-
[qname.to_java, item_type.to_java,
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def self.valid_opt_keys
|
38
|
-
@valid_opt_keys ||= [:qname] + Saxon::OccurrenceIndicator.indicator_names
|
39
|
-
end
|
40
|
-
|
41
|
-
def extract_type_decl(type_decl)
|
42
|
-
raise VariableDeclarationError, type_decl.keys if type_decl.length > 1
|
43
|
-
unless (type_decl.keys - Saxon::OccurrenceIndicator.indicator_names).empty?
|
44
|
-
raise VariableDeclarationError, type_decl.keys
|
45
|
-
end
|
46
|
-
|
47
|
-
return [Saxon::ItemType.get_type('item()'), Saxon::OccurrenceIndicator.zero_or_more] if type_decl.empty?
|
48
|
-
occurrence, type = type_decl.first
|
49
|
-
[Saxon::ItemType.get_type(type), Saxon::OccurrenceIndicator.send(occurrence)]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Raised when an attempt to declare a variable is made using an occurrence
|
54
|
-
# indicator that does not exist
|
55
|
-
class VariableDeclarationError < StandardError
|
56
|
-
attr_reader :keys
|
57
|
-
|
58
|
-
def initialize(keys)
|
59
|
-
@keys = keys
|
60
|
-
end
|
61
|
-
|
62
|
-
# reports allowable occurrence indicator keys and what was actually passed in
|
63
|
-
def to_s
|
64
|
-
"requires :qname, and optionally one of #{Saxon::OccurrenceIndicator.indicator_names}, was passed #{keys.join(', ')}"
|
31
|
+
[qname.to_java, sequence_type.item_type.to_java, sequence_type.occurrence_indicator.to_java]
|
65
32
|
end
|
66
33
|
end
|
67
34
|
end
|
data/lib/saxon/xslt.rb
CHANGED
@@ -3,6 +3,18 @@ require_relative './xslt/compiler'
|
|
3
3
|
module Saxon
|
4
4
|
# Classes for compiling, configuring, and executing XSLT transformations
|
5
5
|
# against XDM nodes or documents
|
6
|
+
#
|
7
|
+
# Using XSLT involves creating a compiler, compiling an XSLT file into an
|
8
|
+
# executable, and then invoking that executable through applying templates
|
9
|
+
# against an XML document, calling a named template, or calling a named
|
10
|
+
# function.
|
11
|
+
#
|
12
|
+
# The easiest way to create an {XSLT::Compiler} instance is by using the
|
13
|
+
# {Saxon::Processor#xslt_compiler} method.
|
14
|
+
#
|
15
|
+
# @see Saxon::XSLT::Compiler
|
16
|
+
# @see Saxon::XSLT::Executable
|
17
|
+
# @see Saxon::Processor#xslt_compiler
|
6
18
|
module XSLT
|
7
19
|
end
|
8
20
|
end
|
data/lib/saxon/xslt/compiler.rb
CHANGED
@@ -4,7 +4,67 @@ require_relative './executable'
|
|
4
4
|
|
5
5
|
module Saxon
|
6
6
|
module XSLT
|
7
|
-
#
|
7
|
+
# The simplest way to construct an {XSLT::Compiler} is to call
|
8
|
+
# {Saxon::Processor#xslt_compiler}.
|
9
|
+
#
|
10
|
+
# processor = Saxon::Processor.create
|
11
|
+
# # Simplest, default options
|
12
|
+
# compiler = processor.xslt_compiler
|
13
|
+
#
|
14
|
+
# In order to set compile-time options, declare static compile-time
|
15
|
+
# parameters then pass a block to the method using the DSL syntax (see
|
16
|
+
# {Saxon::XSLT::EvaluationContext::DSL}
|
17
|
+
#
|
18
|
+
# compiler = processor.xslt_compiler {
|
19
|
+
# static_parameters 'param' => 'value'
|
20
|
+
# default_collation 'https://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive/'
|
21
|
+
# }
|
22
|
+
#
|
23
|
+
# The static evaluation context for a Compiler cannot be changed, you must
|
24
|
+
# create a new one with the context you want. It’s very simple to create a
|
25
|
+
# new Compiler based on an existing one. Declaring a parameter with a an
|
26
|
+
# existing name overwrites the old value.
|
27
|
+
#
|
28
|
+
# new_compiler = compiler.create {
|
29
|
+
# static_parameters 'param' => 'new value'
|
30
|
+
# }
|
31
|
+
# new_compiler.default_collation #=> "https://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive/"
|
32
|
+
#
|
33
|
+
# If you wanted to remove a value, you need to start from scratch. You can,
|
34
|
+
# of course, extract any data you want from a compiler instance separately
|
35
|
+
# and use that to create a new one.
|
36
|
+
#
|
37
|
+
# params = compiler.static_parameters
|
38
|
+
# new_compiler = processor.xslt_compiler {
|
39
|
+
# static_parameters params
|
40
|
+
# }
|
41
|
+
# new_compiler.default_collation #=> nil
|
42
|
+
#
|
43
|
+
# Once you have a compiler, call {Compiler#compile} and pass in a
|
44
|
+
# {Saxon::Source} or an existing {Saxon::XDM::Node}. Parameters and other
|
45
|
+
# run-time configuration options can be set using a block in the same way as
|
46
|
+
# creating a compiler. You'll be returned a {Saxon::XSLT::Executable}.
|
47
|
+
#
|
48
|
+
# source = Saxon::Source.create('my.xsl')
|
49
|
+
# xslt = compiler.compile(source) {
|
50
|
+
# initial_template_parameters 'param' => 'other value'
|
51
|
+
# }
|
52
|
+
#
|
53
|
+
# You can also pass in (or override) parameters at stylesheet execution
|
54
|
+
# time, but if you'll be executing the same stylesheet against many
|
55
|
+
# documents with the same initial parameters then setting them at compile
|
56
|
+
# time is simpler.
|
57
|
+
#
|
58
|
+
# Global and initial template parameters can be set at compiler creation
|
59
|
+
# time, compile time, or execution time. Static parameters can only be set
|
60
|
+
# at compiler creation or compile time.
|
61
|
+
#
|
62
|
+
# xslt = compiler.compile(source) {
|
63
|
+
# static_parameters 'static-param' => 'static value'
|
64
|
+
# global_parameters 'param' => 'global value'
|
65
|
+
# initial_template_parameters 'param' => 'other value'
|
66
|
+
# initial_template_tunnel_parameters 'param' => 'tunnel value'
|
67
|
+
# }
|
8
68
|
class Compiler
|
9
69
|
# Create a new <tt>XSLT::Compiler</tt> using the supplied Processor.
|
10
70
|
# Passing a block gives access to a DSL for setting up the compiler's
|
@@ -42,14 +102,23 @@ module Saxon
|
|
42
102
|
# Saxon::XDM::AtomicValue>] parameters required at compile time as QName => value hash
|
43
103
|
|
44
104
|
# @param source [Saxon::Source] the Source to compile
|
105
|
+
# @yield the block is executed in the context of an {XSLT::EvaluationContext} DSL instance
|
45
106
|
# @return [Saxon::XSLT::Executable] the executable stylesheet
|
46
107
|
def compile(source, &block)
|
108
|
+
new_evaluation_context = evaluation_context.define(block)
|
109
|
+
s9_compiler = new_compiler(new_evaluation_context)
|
47
110
|
Saxon::XSLT::Executable.new(
|
48
|
-
|
49
|
-
|
111
|
+
s9_compiler.compile(source.to_java),
|
112
|
+
new_evaluation_context
|
50
113
|
)
|
51
114
|
end
|
52
115
|
|
116
|
+
# Allows the creation of a new {Compiler} starting from a copy of this
|
117
|
+
# Compiler's static context. As with {.create}, passing a block gives
|
118
|
+
# access to a DSL for setting up the compiler's static context.
|
119
|
+
#
|
120
|
+
# @yield An XSLT compiler DSL block
|
121
|
+
# @return [Saxon::XSLT::Compiler] the new compiler instance
|
53
122
|
def create(&block)
|
54
123
|
new_evaluation_context = evaluation_context.define(block)
|
55
124
|
self.class.new(@s9_processor, new_evaluation_context)
|
@@ -57,10 +126,10 @@ module Saxon
|
|
57
126
|
|
58
127
|
private
|
59
128
|
|
60
|
-
def new_compiler
|
129
|
+
def new_compiler(evaluation_context)
|
61
130
|
compiler = @s9_processor.newXsltCompiler
|
62
|
-
compiler.declareDefaultCollation(default_collation) unless default_collation.nil?
|
63
|
-
static_parameters.each do |qname, value|
|
131
|
+
compiler.declareDefaultCollation(evaluation_context.default_collation) unless evaluation_context.default_collation.nil?
|
132
|
+
evaluation_context.static_parameters.each do |qname, value|
|
64
133
|
compiler.setParameter(qname.to_java, value.to_java)
|
65
134
|
end
|
66
135
|
compiler
|
@@ -3,6 +3,7 @@ require_relative '../qname'
|
|
3
3
|
|
4
4
|
module Saxon
|
5
5
|
module XSLT
|
6
|
+
# @api private
|
6
7
|
# Represents the evaluation context for an XSLT compiler, and stylesheets
|
7
8
|
# compiled using one. {EvaluationContext}s are immutable.
|
8
9
|
class EvaluationContext
|
@@ -47,11 +48,12 @@ module Saxon
|
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
51
|
+
# @api public
|
50
52
|
# Provides the hooks for constructing a {EvaluationContext} with a DSL.
|
51
|
-
# @api private
|
52
53
|
class DSL
|
53
54
|
include Common
|
54
55
|
|
56
|
+
# @api private
|
55
57
|
# 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
|
56
58
|
# new {EvaluationContext} with the results
|
57
59
|
# @param block [Proc] a Proc/lambda (or <tt>to_proc</tt>'d containing DSL calls
|
@@ -130,6 +132,7 @@ module Saxon
|
|
130
132
|
|
131
133
|
include Common
|
132
134
|
|
135
|
+
# @api private
|
133
136
|
# Executes the Proc/lambda passed in with a new instance of
|
134
137
|
# {EvaluationContext} as <tt>self</tt>, allowing the DSL methods to be
|
135
138
|
# called in a DSL-ish way
|
@@ -142,21 +145,34 @@ module Saxon
|
|
142
145
|
|
143
146
|
attr_reader :default_collation, :static_parameters, :global_parameters, :initial_template_parameters, :initial_template_tunnel_parameters
|
144
147
|
|
148
|
+
# @api private
|
149
|
+
# When passed a Proc, create a new EvaluationContext based on this one, with the same DSL available as in {.define}.
|
150
|
+
#
|
151
|
+
# When passed {nil}, simply return the EvaluationContext unaltered.
|
145
152
|
def define(block)
|
146
153
|
block.nil? ? self : DSL.define(block, args_hash)
|
147
154
|
end
|
148
155
|
end
|
149
156
|
|
157
|
+
# Error raised when a global and static parameter of the same name are
|
158
|
+
# declared
|
150
159
|
class GlobalAndStaticParameterClashError < StandardError
|
151
160
|
end
|
152
161
|
|
162
|
+
# parameter shorthand name/value-to-full QName/XDM::Value helper module
|
153
163
|
module ParameterHelper
|
164
|
+
# process shorthand parameter hash into fully-qualified QName / Value hash
|
165
|
+
# @param parameters [Hash<String, Saxon::QName => Object>]
|
166
|
+
# @return [Hash<Saxon::QName => Saxon::XDM::Value>]
|
167
|
+
# @see Saxon::QName.resolve() for more about the QName resolution process
|
168
|
+
# @see Saxon::XDM.Value() for more about the conversion of Object into XDM Values
|
154
169
|
def self.process_parameters(parameters)
|
155
|
-
|
170
|
+
parameters.map { |qname, value|
|
156
171
|
[Saxon::QName.resolve(qname), Saxon::XDM.Value(value)]
|
157
|
-
}
|
172
|
+
}.to_h
|
158
173
|
end
|
159
174
|
|
175
|
+
# generate Java Map from fully qualified parameter hash
|
160
176
|
def self.to_java(parameters)
|
161
177
|
Hash[parameters.map { |k,v| [k.to_java, v.to_java] }].to_java
|
162
178
|
end
|
@@ -7,6 +7,49 @@ require_relative '../qname'
|
|
7
7
|
module Saxon
|
8
8
|
module XSLT
|
9
9
|
# Represents a compiled XSLT stylesheet ready to be executed
|
10
|
+
#
|
11
|
+
# Once you have a compiled stylesheet, then it can be executed against a
|
12
|
+
# source document in a variety of ways.
|
13
|
+
#
|
14
|
+
# First, you can use the traditional *apply templates* ,method, which was
|
15
|
+
# the only way in XSLT 1.
|
16
|
+
#
|
17
|
+
# input = Saxon::Source.create('input.xml')
|
18
|
+
# result = xslt.apply_templates(input)
|
19
|
+
#
|
20
|
+
# Next, you can call a specific named template (new in XSLT 2).
|
21
|
+
#
|
22
|
+
# result = xslt.call_template('template-name')
|
23
|
+
#
|
24
|
+
# Note that there's no input document here. If your XSLT needs a global
|
25
|
+
# context item set when you invoke it via a named template, then you can do
|
26
|
+
# that, too:
|
27
|
+
#
|
28
|
+
# input = processor.XML('input.xml')
|
29
|
+
# result = xslt.call_template('template-name', {
|
30
|
+
# global_context_item: input
|
31
|
+
# })
|
32
|
+
#
|
33
|
+
# Global and initial template parameters can be set at compiler creation
|
34
|
+
# time, compile time, or execution time.
|
35
|
+
#
|
36
|
+
# Initial template parameters relate to parameters passed to the *first*
|
37
|
+
# template run (either the first template matched when called with
|
38
|
+
# {Executable#apply_templates}, or the named template called with
|
39
|
+
# {Executable#call_template}).
|
40
|
+
#
|
41
|
+
# <b>Initial template parameters</b> are essentially implied +<xsl:with-parameter
|
42
|
+
# tunnel="no">+ elements. <b>Initial template tunnel parameters</b> are implied
|
43
|
+
# +<xsl:with-parameter tunnel="yes">+ elements.
|
44
|
+
#
|
45
|
+
# xslt.apply_templates(input, {
|
46
|
+
# global_parameters: {'param' => 'global value'},
|
47
|
+
# initial_template_parameters: {'param' => 'other value'},
|
48
|
+
# initial_template_tunnel_parameters: {'param' => 'tunnel value'}
|
49
|
+
# })
|
50
|
+
#
|
51
|
+
# Remember that if you need to use a parameter name which uses a namespace
|
52
|
+
# prefix, you must use an explicit {Saxon::QName} to refer to it.
|
10
53
|
class Executable
|
11
54
|
extend Forwardable
|
12
55
|
|
@@ -24,14 +67,100 @@ module Saxon
|
|
24
67
|
|
25
68
|
def_delegators :evaluation_context, :global_parameters, :initial_template_parameters, :initial_template_tunnel_parameters
|
26
69
|
|
70
|
+
# Run the XSLT by applying templates against the provided {Saxon::Source}
|
71
|
+
# or {Saxon::XDM::Node}.
|
72
|
+
#
|
73
|
+
# @note Any {QName}s supplied as strings MUST be resolvable as a QName
|
74
|
+
# without extra information, so they must be prefix-less (so, 'name', and
|
75
|
+
# never 'ns:name')
|
76
|
+
#
|
77
|
+
# @param source [Saxon::Source, Saxon::XDM::Node] the Source or Node that
|
78
|
+
# will be used as the global context item
|
79
|
+
# @param opts [Hash] a hash of options for invoking the transformation
|
80
|
+
# @option opts [Boolean] :raw (false) Whether the transformation should be
|
81
|
+
# executed 'raw', because it is expected to return a simple XDM Value
|
82
|
+
# (like a number, or plain text) and not an XML document.
|
83
|
+
# @option opts [String, Saxon::QName] :mode The initial mode to use when
|
84
|
+
# processing starts.
|
85
|
+
# @option opts [Hash<String, Saxon::QName => Object>] :global_parameters
|
86
|
+
# Additional global parameters to set. Setting already-defined
|
87
|
+
# parameters will replace their value for this invocation of the XSLT
|
88
|
+
# only, it won't affect the {XSLT::Compiler}'s context.
|
89
|
+
# @option opts [Hash<String, Saxon::QName => Object>]
|
90
|
+
# :initial_template_parameters Additional parameters to pass to the
|
91
|
+
# first template matched. Setting already-defined parameters will
|
92
|
+
# replace their value for this invocation of the XSLT only, it won't
|
93
|
+
# affect the {XSLT::Compiler}'s context.
|
94
|
+
# @option opts [Hash<String, Saxon::QName => Object>]
|
95
|
+
# :initial_template_tunnel_parameters Additional tunnelling parameters
|
96
|
+
# to pass to the first template matched. Setting already-defined
|
97
|
+
# parameters will replace their value for this invocation of the XSLT
|
98
|
+
# only, it won't affect the {XSLT::Compiler}'s context.
|
99
|
+
# @return [Saxon::XSLT::Result] the transformation result
|
27
100
|
def apply_templates(source, opts = {})
|
28
101
|
transformation(opts).apply_templates(source)
|
29
102
|
end
|
30
103
|
|
31
|
-
|
104
|
+
# Run the XSLT by calling the named template.
|
105
|
+
#
|
106
|
+
# @note Any {QName}s supplied as Strings (e.g. for the template name)
|
107
|
+
# MUST be resolvable as a QName without extra information, so they must be
|
108
|
+
# prefix-less (so, 'name', and never 'ns:name')
|
109
|
+
#
|
110
|
+
# @param template_name [String, Saxon::QName, nil] the name of the
|
111
|
+
# template to be invoked. Passing +nil+ will invoke the default named
|
112
|
+
# template (+xsl:default-template+)
|
113
|
+
# @param opts [Hash] a hash of options for invoking the transformation
|
114
|
+
# @option opts [Boolean] :raw (false) Whether the transformation should be
|
115
|
+
# executed 'raw', because it is expected to return a simple XDM Value
|
116
|
+
# (like a number, or plain text) and not an XML document.
|
117
|
+
# @option opts [String, Saxon::QName] :mode The name of the initial mode
|
118
|
+
# to use when processing starts.
|
119
|
+
# @option opts [Hash<String, Saxon::QName => Object>] :global_parameters
|
120
|
+
# Additional global parameters to set. Setting already-defined
|
121
|
+
# parameters will replace their value for this invocation of the XSLT
|
122
|
+
# only, it won't affect the {XSLT::Compiler}'s context.
|
123
|
+
# @option opts [Hash<String, Saxon::QName => Object>]
|
124
|
+
# :initial_template_parameters Additional parameters to pass to the
|
125
|
+
# first template matched. Setting already-defined parameters will
|
126
|
+
# replace their value for this invocation of the XSLT only, it won't
|
127
|
+
# affect the {XSLT::Compiler}'s context.
|
128
|
+
# @option opts [Hash<String, Saxon::QName => Object>]
|
129
|
+
# :initial_template_tunnel_parameters Additional tunnelling parameters
|
130
|
+
# to pass to the first template matched. Setting already-defined
|
131
|
+
# parameters will replace their value for this invocation of the XSLT
|
132
|
+
# only, it won't affect the {XSLT::Compiler}'s context.
|
133
|
+
# @return [Saxon::XSLT::Result] the transformation result
|
134
|
+
def call_template(template_name = nil, opts = {})
|
32
135
|
transformation(opts).call_template(template_name)
|
33
136
|
end
|
34
137
|
|
138
|
+
# Invoke a named function in the XSLT.
|
139
|
+
#
|
140
|
+
# @note Function name {QName}s have to have prefixes, so they can't be
|
141
|
+
# supplied as {::String}s. Any other {QName}s supplied as {::String}s (e.g.
|
142
|
+
# for the template name) MUST be resolvable as a QName without extra
|
143
|
+
# information, so they must be prefix-less (so, 'name', and never
|
144
|
+
# 'ns:name')
|
145
|
+
# @note the function you're calling needs to be have been defined with
|
146
|
+
# +visibility="public"+ or +visibility="final"+
|
147
|
+
#
|
148
|
+
# @param function_name [Saxon::QName] the name of the function to be
|
149
|
+
# invoked.
|
150
|
+
# @param opts [Hash] a hash of options for invoking the transformation
|
151
|
+
# @option opts [Boolean] :raw (false) Whether the transformation should be
|
152
|
+
# executed 'raw', because it is expected to return a simple XDM Value
|
153
|
+
# (like a number, or plain text) and not an XML document.
|
154
|
+
# @option opts [Hash<String, Saxon::QName => Object>] :global_parameters
|
155
|
+
# Additional global parameters to set. Setting already-defined
|
156
|
+
# parameters will replace their value for this invocation of the XSLT
|
157
|
+
# only, it won't affect the {XSLT::Compiler}'s context.
|
158
|
+
# @return [Saxon::XSLT::Result] the transformation result
|
159
|
+
def call_function(function_name, opts = {})
|
160
|
+
args = opts.fetch(:args, [])
|
161
|
+
transformation(opts.reject { |k, v| k == :args }).call_function(function_name, args)
|
162
|
+
end
|
163
|
+
|
35
164
|
# @return [net.sf.saxon.s9api.XsltExecutable] the underlying Saxon
|
36
165
|
# <tt>XsltExecutable</tt>
|
37
166
|
def to_java
|
@@ -69,21 +198,20 @@ module Saxon
|
|
69
198
|
end
|
70
199
|
end
|
71
200
|
|
72
|
-
|
73
|
-
|
201
|
+
# @api private
|
202
|
+
# Represents a loaded XSLT transformation ready to be applied against a
|
203
|
+
# context node.
|
204
|
+
class Transformation
|
205
|
+
VALID_OPTS = [:raw, :mode, :global_context_item, :global_parameters, :initial_template_parameters, :initial_template_tunnel_parameters]
|
74
206
|
|
75
|
-
|
76
|
-
|
77
|
-
end
|
207
|
+
attr_reader :s9_transformer, :opts
|
208
|
+
private :s9_transformer, :opts
|
78
209
|
|
79
|
-
|
80
|
-
|
81
|
-
|
210
|
+
# Return the default initial template namne for XSLT 3 named-template invocation
|
211
|
+
# @return [Saxon::QName] the default initial template QName
|
212
|
+
def self.default_initial_template
|
213
|
+
@default_initial_template ||= Saxon::QName.clark('{http://www.w3.org/1999/XSL/Transform}initial-template')
|
82
214
|
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class Transformation
|
86
|
-
attr_reader :s9_transformer, :opts
|
87
215
|
|
88
216
|
def initialize(args)
|
89
217
|
@s9_transformer = args.fetch(:s9_transformer)
|
@@ -94,12 +222,24 @@ module Saxon
|
|
94
222
|
@raw = false
|
95
223
|
end
|
96
224
|
|
225
|
+
# Apply templates to Source, using all the context set up when we were
|
226
|
+
# created.
|
97
227
|
def apply_templates(source)
|
98
228
|
transformation_result(:applyTemplates, source)
|
99
229
|
end
|
100
230
|
|
231
|
+
# Call the named template, using all the context set up when we were
|
232
|
+
# created.
|
101
233
|
def call_template(template_name)
|
102
|
-
transformation_result(:callTemplate,
|
234
|
+
transformation_result(:callTemplate, resolve_template_name(template_name))
|
235
|
+
end
|
236
|
+
|
237
|
+
# Call the named function, using all the context set up when we were
|
238
|
+
# created.
|
239
|
+
def call_function(function_name, args)
|
240
|
+
function_name = Saxon::QName.resolve(function_name).to_java
|
241
|
+
args = function_args(args)
|
242
|
+
call_function_result(function_name, args)
|
103
243
|
end
|
104
244
|
|
105
245
|
private
|
@@ -110,12 +250,26 @@ module Saxon
|
|
110
250
|
Result.new(result_xdm_value(s9_transformer.send(*transformer_args)), s9_transformer)
|
111
251
|
end
|
112
252
|
|
253
|
+
def call_function_result(name, args)
|
254
|
+
set_opts!
|
255
|
+
Result.new(result_xdm_value(s9_transformer.callFunction(*[name, args, destination].compact)), s9_transformer)
|
256
|
+
end
|
257
|
+
|
113
258
|
def result_xdm_value(transformer_return_value)
|
114
259
|
XDM.Value(
|
115
260
|
transformer_return_value.nil? ? destination.getXdmNode : transformer_return_value
|
116
261
|
)
|
117
262
|
end
|
118
263
|
|
264
|
+
def resolve_template_name(template_name)
|
265
|
+
return self.class.default_initial_template if template_name.nil?
|
266
|
+
Saxon::QName.resolve(template_name)
|
267
|
+
end
|
268
|
+
|
269
|
+
def function_args(args = [])
|
270
|
+
args.map { |val| Saxon::XDM.Value(val).to_java }.to_java(S9API::XdmValue)
|
271
|
+
end
|
272
|
+
|
119
273
|
def destination
|
120
274
|
@destination ||= begin
|
121
275
|
Saxon::S9API::XdmDestination.new unless raw?
|
@@ -124,6 +278,7 @@ module Saxon
|
|
124
278
|
|
125
279
|
def set_opts!
|
126
280
|
opts.each do |opt, value|
|
281
|
+
raise BadOptionError, opt unless VALID_OPTS.include?(opt)
|
127
282
|
send(opt, value)
|
128
283
|
end
|
129
284
|
end
|
@@ -140,6 +295,10 @@ module Saxon
|
|
140
295
|
s9_transformer.setInitialMode(Saxon::QName.resolve(mode_name).to_java)
|
141
296
|
end
|
142
297
|
|
298
|
+
def global_context_item(xdm_item)
|
299
|
+
s9_transformer.setGlobalContextItem(xdm_item.to_java)
|
300
|
+
end
|
301
|
+
|
143
302
|
def global_parameters(parameters)
|
144
303
|
s9_transformer.setStylesheetParameters(XSLT::ParameterHelper.to_java(parameters))
|
145
304
|
end
|
@@ -152,5 +311,36 @@ module Saxon
|
|
152
311
|
s9_transformer.setInitialTemplateParameters(XSLT::ParameterHelper.to_java(parameters) , true)
|
153
312
|
end
|
154
313
|
end
|
314
|
+
|
315
|
+
# Represents the result of a transformation, providing a simple default
|
316
|
+
# serializer as well
|
317
|
+
class Result
|
318
|
+
attr_reader :xdm_value
|
319
|
+
|
320
|
+
# @api private
|
321
|
+
def initialize(xdm_value, s9_transformer)
|
322
|
+
@xdm_value, @s9_transformer = xdm_value, s9_transformer
|
323
|
+
end
|
324
|
+
|
325
|
+
# Serialize the result to a string using the options specified in
|
326
|
+
# +<xsl:output/>+ in the XSLT
|
327
|
+
def to_s
|
328
|
+
serializer = Serializer.new(@s9_transformer.newSerializer)
|
329
|
+
serializer.serialize(xdm_value.to_java)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Raised if a bad option name is passed in the options hash to
|
334
|
+
# Executable#apply_templates et al
|
335
|
+
class BadOptionError < StandardError
|
336
|
+
def initialize(option_name)
|
337
|
+
@option_name = option_name
|
338
|
+
end
|
339
|
+
|
340
|
+
# return error message including the option name
|
341
|
+
def to_s
|
342
|
+
"Option :#{@option_name} is not a recognised option."
|
343
|
+
end
|
344
|
+
end
|
155
345
|
end
|
156
346
|
end
|