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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +429 -42
  3. data/Gemfile +2 -2
  4. data/README.md +317 -10
  5. data/Rakefile +237 -7
  6. 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
  7. data/lib/saxon-rb.rb +1 -0
  8. data/lib/{saxon_jars.rb → saxon-rb_jars.rb} +2 -2
  9. data/lib/saxon.rb +13 -0
  10. data/lib/saxon/axis_iterator.rb +8 -1
  11. data/lib/saxon/configuration.rb +1 -0
  12. data/lib/saxon/item_type.rb +12 -17
  13. data/lib/saxon/item_type/lexical_string_conversion.rb +136 -58
  14. data/lib/saxon/item_type/value_to_ruby.rb +13 -0
  15. data/lib/saxon/loader.rb +4 -1
  16. data/lib/saxon/nokogiri.rb +78 -0
  17. data/lib/saxon/occurrence_indicator.rb +32 -3
  18. data/lib/saxon/processor.rb +32 -1
  19. data/lib/saxon/qname.rb +37 -2
  20. data/lib/saxon/s9api.rb +5 -0
  21. data/lib/saxon/sequence_type.rb +131 -0
  22. data/lib/saxon/source.rb +207 -71
  23. data/lib/saxon/version.rb +1 -1
  24. data/lib/saxon/xdm.rb +7 -0
  25. data/lib/saxon/xdm/array.rb +16 -0
  26. data/lib/saxon/xdm/atomic_value.rb +7 -1
  27. data/lib/saxon/xdm/empty_sequence.rb +13 -0
  28. data/lib/saxon/xdm/external_object.rb +1 -0
  29. data/lib/saxon/xdm/function_item.rb +1 -0
  30. data/lib/saxon/xdm/item.rb +7 -0
  31. data/lib/saxon/xdm/map.rb +38 -0
  32. data/lib/saxon/xdm/node.rb +19 -1
  33. data/lib/saxon/xdm/sequence_like.rb +15 -0
  34. data/lib/saxon/xdm/value.rb +21 -5
  35. data/lib/saxon/xpath.rb +9 -0
  36. data/lib/saxon/xpath/compiler.rb +36 -1
  37. data/lib/saxon/xpath/executable.rb +53 -28
  38. data/lib/saxon/xpath/static_context.rb +19 -39
  39. data/lib/saxon/xpath/variable_declaration.rb +16 -49
  40. data/lib/saxon/xslt.rb +12 -0
  41. data/lib/saxon/xslt/compiler.rb +75 -6
  42. data/lib/saxon/xslt/evaluation_context.rb +19 -3
  43. data/lib/saxon/xslt/executable.rb +204 -14
  44. data/saxon-rb.gemspec +1 -1
  45. metadata +9 -7
  46. 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.transform_keys(&:to_s)).freeze
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
- # explicit QName or prefix:name string form. The string form requires
80
- # the namespace prefix to have already been declared with {#namespace}
81
- # @param type [String, Hash{Symbol => String, Class}, null] The type of the
82
- # variable, either as a string using the same form as an XSLT
83
- # <tt>as=""</tt> type definition, or as a hash of one key/value where
84
- # that key is a Symbol taken from {Saxon::OccurenceIndicator} and the
85
- # value is either a Class that {Saxon::ItemType} can convert to its
86
- # XDM equivalent (e.g. {::String}), or a string that {Saxon::ItemType}
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, type)
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 resolve_variable_type_decl(type_decl)
105
- case type_decl
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 '../item_type'
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 compiled XPath, providing an idiomatic Ruby way to deal with these.
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::ItemType]
11
- attr_reader :item_type
12
- # @return [net.sf.saxon.s9api.OccurrenceIndicator]
13
- attr_reader :occurrences
14
-
15
- # @param opts [Hash]
16
- # @option opts [Saxon::QName] :qname The name of the variable as a {Saxon::QName}
17
- def initialize(opts = {})
18
- raise VariableDeclarationError, opts.keys if opts.keys.length > 2
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, item_type, and occurrences are equal
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 && item_type == other.item_type && occurrences == other.occurrences
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, occurrences]
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
@@ -4,7 +4,67 @@ require_relative './executable'
4
4
 
5
5
  module Saxon
6
6
  module XSLT
7
- # Compiles XSLT stylesheets so they can be executed
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
- new_compiler.compile(source.to_java),
49
- evaluation_context.define(block)
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
- Hash[parameters.map { |qname, value|
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
- def call_template(template_name, opts = {})
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
- class Result
73
- attr_reader :xdm_value
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
- def initialize(xdm_value, s9_transformer)
76
- @xdm_value, @s9_transformer = xdm_value, s9_transformer
77
- end
207
+ attr_reader :s9_transformer, :opts
208
+ private :s9_transformer, :opts
78
209
 
79
- def to_s
80
- serializer = Serializer.new(@s9_transformer.newSerializer)
81
- serializer.serialize(xdm_value.to_java)
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, Saxon::QName.resolve(template_name))
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