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.
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