saxon-rb 0.4.0-java → 0.5.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|