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
@@ -5,30 +5,43 @@ module Saxon
5
5
  module XDM
6
6
  # Represents the empty sequence in XDM
7
7
  class EmptySequence
8
+ # Returns an instance. Effectively a Singleton because the EmptySequence
9
+ # is immutable, and empty. An instance is completely interchangeable with
10
+ # another. The instance is cached, but multiple instances may exist across
11
+ # threads. We don't prevent that because it's immaterial.
12
+ # @return [EmptySequence] The empty sequence
8
13
  def self.create
9
14
  @instance ||= new
10
15
  end
11
16
 
12
17
  include SequenceLike
13
18
 
19
+ # @return [Enumerator] an enumerator over an empty Array
14
20
  def sequence_enum
15
21
  [].to_enum
16
22
  end
17
23
 
24
+ # @return [Integer] the size of the sequence (always 0)
18
25
  def sequence_size
19
26
  0
20
27
  end
21
28
 
29
+ # All instances of {EmptySequence} are equal to each other.
30
+ #
31
+ # @param other [Object] the object to compare self against
32
+ # @return [Boolean] Whether this object is equal to the other
22
33
  def ==(other)
23
34
  other.class == self.class
24
35
  end
25
36
 
26
37
  alias_method :eql?, :==
27
38
 
39
+ # @return [Integer] the hash code. All instances have the same hash code.
28
40
  def hash
29
41
  [].hash
30
42
  end
31
43
 
44
+ # @return [net.sf.saxon.s9api.XDMEmptySequence] the underlying Java empty sequence
32
45
  def to_java
33
46
  @s9_xdm_empty_sequence ||= Saxon::S9API::XdmEmptySequence.getInstance
34
47
  end
@@ -13,6 +13,7 @@ module Saxon
13
13
  @s9_xdm_external_object = s9_xdm_external_object
14
14
  end
15
15
 
16
+ # The underlying Saxon XdmExternalObject
16
17
  def to_java
17
18
  @s9_xdm_external_object
18
19
  end
@@ -13,6 +13,7 @@ module Saxon
13
13
  @s9_xdm_function_item = s9_xdm_function_item
14
14
  end
15
15
 
16
+ # The underlying Saxon XdmFunctionItem
16
17
  def to_java
17
18
  @s9_xdm_function_item
18
19
  end
@@ -1,5 +1,12 @@
1
1
  module Saxon
2
2
  module XDM
3
+ # Create one of the XdmItem-derived XDM objects from the passed in argument.
4
+ #
5
+ # Existing XDM::* objects are passed through. s9api.Xdm* Java objects are
6
+ # wrapped appropriately and returned. Ruby Arrays and Hashes are converted
7
+ # to {XDM::Array} and {XDM::Map} instances respectively. Ruby values that
8
+ # respond to +#each+ are converted to an {XDM::Array} (e.g. {Set}). Other
9
+ # Ruby values are converted to {XDM::AtomicValue}.
3
10
  def self.Item(item)
4
11
  case item
5
12
  when Value, AtomicValue, Node, Array, Map, ExternalObject
data/lib/saxon/xdm/map.rb CHANGED
@@ -5,6 +5,12 @@ module Saxon
5
5
  module XDM
6
6
  # Represents an XDM Map
7
7
  class Map
8
+ # Create an {XDM::Map} from a Ruby Hash, by ensuring each key has been
9
+ # converted to an {AtomicValue}, and each value has been converted to an
10
+ # XDM Value of some sort.
11
+ # @return [XDM::Map] the new Map
12
+ # @see XDM.AtomicValue
13
+ # @see XDM.Value
8
14
  def self.create(hash)
9
15
  case hash
10
16
  when Saxon::S9API::XdmMap
@@ -30,39 +36,71 @@ module Saxon
30
36
  @s9_xdm_map = s9_xdm_map
31
37
  end
32
38
 
39
+ # Compare this Map against another. They're equal if they contain the same
40
+ # key, value pairs.
33
41
  def ==(other)
34
42
  return false unless other.is_a?(self.class)
35
43
  to_h == other.to_h
36
44
  end
37
45
 
46
+ # Fetch the value for the key given. +key+ is converted to an
47
+ # {XDM::AtomicValue} if it isn't already one.
48
+ # @param key [Object, XDM::AtomicValue] the key to retrieve
38
49
  def [](key)
39
50
  cached_hash[XDM.AtomicValue(key)]
40
51
  end
41
52
 
53
+ # Fetch the value for the key given, as {Hash#fetch} would. +key+ is
54
+ # converted to an {XDM::AtomicValue} if it isn't already one.
55
+ # @param key [XDM::AtomicValue, Object] the key to retrieve.
56
+ # @see Hash#fetch
42
57
  def fetch(key, *args, &block)
43
58
  cached_hash.fetch(XDM.AtomicValue(key), *args, &block)
44
59
  end
45
60
 
61
+ # Iterate over the Map as {Hash#each} would
62
+ # @yieldparam key [XDM::AtomicValue] the key
63
+ # @yieldparam value [XDM::Value] the value
46
64
  def each(&block)
47
65
  cached_hash.each(&block)
48
66
  end
49
67
 
68
+ # Return a new Map containing only key, value pairs for which the block
69
+ # returns true.
70
+ # @yieldparam key [XDM::AtomicValue] the key
71
+ # @yieldparam value [XDM::Value] the value
72
+ # @see ::Hash#select
50
73
  def select(&block)
51
74
  self.class.create(each.select(&block).to_h)
52
75
  end
53
76
 
77
+ # Return a new Map containing only key, value pairs for which the block
78
+ # DOES NOT return true.
79
+ # @yieldparam key [XDM::AtomicValue] the key
80
+ # @yieldparam value [XDM::Value] the value
81
+ # @see ::Hash#reject
54
82
  def reject(&block)
55
83
  self.class.create(each.reject(&block).to_h)
56
84
  end
57
85
 
86
+ # Create a new Map from the result of merging another Map into this one.
87
+ # In the case of duplicate keys, the value in the provided hash will be
88
+ # used.
89
+ # @yieldparam key [XDM::AtomicValue] the key
90
+ # @yieldparam value [XDM::Value] the value
91
+ # @return [XDM::Map] the new Map
92
+ # @see ::Hash#merge
58
93
  def merge(other)
59
94
  self.class.create(to_h.merge(other.to_h))
60
95
  end
61
96
 
97
+ # @return [S9API::XdmMap] the underlying Saxon XdmMap
62
98
  def to_java
63
99
  @s9_xdm_map
64
100
  end
65
101
 
102
+ # a (frozen) Ruby hash containing the keys and values from the Map.
103
+ # @return [Hash] the Map as a Ruby hash.
66
104
  def to_h
67
105
  cached_hash
68
106
  end
@@ -4,7 +4,8 @@ require_relative 'sequence_like'
4
4
 
5
5
  module Saxon
6
6
  module XDM
7
- # An XPath Data Model Node object, representing an XML document, or an element or one of the other node chunks in the XDM.
7
+ # An XPath Data Model Node object, representing an XML document, or an
8
+ # element or one of the other node chunks in the XDM.
8
9
  class Node
9
10
  include XDM::SequenceLike
10
11
  include XDM::ItemSequenceLike
@@ -23,12 +24,19 @@ module Saxon
23
24
  @s9_xdm_node
24
25
  end
25
26
 
27
+ # The name of the node, as a {Saxon::QName}, or +nil+ if the node is not
28
+ # of a kind that has a name
29
+ # @return [Saxon::QName, null] the name, if there is one
26
30
  def node_name
27
31
  return @node_name if instance_variable_defined?(:@node_name)
28
32
  node_name = s9_xdm_node.getNodeName
29
33
  @node_name = node_name.nil? ? nil : Saxon::QName.new(node_name)
30
34
  end
31
35
 
36
+ # What kind of node this is. Returns one of +:element+, +:text+,
37
+ # +:attribute+, +:namespace+, +:comment+, +:processing_instruction+, or
38
+ # +:comment+
39
+ # @return [Symbol] the kind of node this is
32
40
  def node_kind
33
41
  @node_kind ||= case s9_xdm_node.nodeKind
34
42
  when Saxon::S9API::XdmNodeKind::ELEMENT
@@ -48,6 +56,7 @@ module Saxon
48
56
  end
49
57
  end
50
58
 
59
+ # Does this Node represent the same underlying node as the other?
51
60
  def ==(other)
52
61
  return false unless other.is_a?(XDM::Node)
53
62
  s9_xdm_node.equals(other.to_java)
@@ -55,14 +64,23 @@ module Saxon
55
64
 
56
65
  alias_method :eql?, :==
57
66
 
67
+ # Compute a hash-code for this {Node}.
68
+ #
69
+ # Two {Node}s with the same content will have the same hash code (and will compare using eql?).
70
+ # @see Object#hash
58
71
  def hash
59
72
  @hash ||= s9_xdm_node.hashCode
60
73
  end
61
74
 
75
+ # Execute the given block for every child node of this
76
+ # @yieldparam node [Saxon::XDM::Node] the child node
62
77
  def each(&block)
63
78
  axis_iterator(:child).each(&block)
64
79
  end
65
80
 
81
+ # Create an {AxisIterator} over this Node for the given XPath axis
82
+ # @param axis [Symbol] the axis to iterate along
83
+ # @see AxisIterator
66
84
  def axis_iterator(axis)
67
85
  AxisIterator.new(self, axis)
68
86
  end
@@ -1,14 +1,25 @@
1
1
  module Saxon
2
2
  module XDM
3
+ # Mixin for objects that are XDM Sequence-like in behaviour
3
4
  module SequenceLike
5
+ # Implementors should return an {Enumerator} over the Sequence. For
6
+ # {XDM::Value}s, this will just be the items in the sequence. For
7
+ # XDM::AtomicValue or XDM::Node, this will be a single-item Enumerator so
8
+ # that Items can be correctly treated as single-item Values.
4
9
  def sequence_enum
5
10
  raise NotImplementedError
6
11
  end
7
12
 
13
+ # Implementors should return the size of the Sequence. For
14
+ # {XDM::AtomicValue} this will always be 1.
15
+ # @return [Integer] the sequence size
8
16
  def sequence_size
9
17
  raise NotImplementedError
10
18
  end
11
19
 
20
+ # Return a new XDM::Value from this Sequence with the passed in value
21
+ # appended to the end.
22
+ # @return [XDM::Value] the new Value
12
23
  def append(other)
13
24
  XDM::Value.create([self, other])
14
25
  end
@@ -17,11 +28,15 @@ module Saxon
17
28
  alias_method :+, :append
18
29
  end
19
30
 
31
+ # Mixin for objects that are Sequence-like but only contain a single item,
32
+ # like {XDM::AtomicValue} or {XDM::Node}
20
33
  module ItemSequenceLike
34
+ # return a single-item Enumerator containing +self+
21
35
  def sequence_enum
22
36
  [self].to_enum
23
37
  end
24
38
 
39
+ # Returns the sequence size, which will always be 1.
25
40
  def sequence_size
26
41
  1
27
42
  end
@@ -19,8 +19,8 @@ module Saxon
19
19
  when 0
20
20
  XDM.EmptySequence()
21
21
  when 1
22
- if existing_value = maybe_xdm_value(items.first)
23
- return existing_value
22
+ if value = maybe_xdm_value(items.first)
23
+ return value
24
24
  end
25
25
  XDM.Item(items.first)
26
26
  else
@@ -30,12 +30,25 @@ module Saxon
30
30
 
31
31
  private
32
32
 
33
- def maybe_xdm_value(item)
34
- return item if item.is_a?(self)
35
- return new(item) if item.instance_of?(Saxon::S9API::XdmValue)
33
+ def maybe_xdm_value(value)
34
+ return value if value.is_a?(self)
35
+ return value if value === XDM.EmptySequence()
36
+ return XDM.EmptySequence() if value.instance_of?(Saxon::S9API::XdmEmptySequence)
37
+ return check_for_empty_or_single_item_value(value) if value.instance_of?(Saxon::S9API::XdmValue)
36
38
  false
37
39
  end
38
40
 
41
+ def check_for_empty_or_single_item_value(s9_value)
42
+ case s9_value.size
43
+ when 0
44
+ XDM.EmptySequence()
45
+ when 1
46
+ XDM.Item(s9_value.item_at(0))
47
+ else
48
+ new(s9_value)
49
+ end
50
+ end
51
+
39
52
  def wrap_items(items)
40
53
  result = []
41
54
  items.map { |item|
@@ -122,10 +135,12 @@ module Saxon
122
135
 
123
136
  alias_method :enum_for, :to_enum
124
137
 
138
+ # Returns an enumerator over the Sequence
125
139
  def sequence_enum
126
140
  to_enum
127
141
  end
128
142
 
143
+ # @return [Integer] the size of the sequence
129
144
  def sequence_size
130
145
  s9_xdm_value.size
131
146
  end
@@ -137,6 +152,7 @@ module Saxon
137
152
  @s9_xdm_item = s9_xdm_item
138
153
  end
139
154
 
155
+ # Return the underlying s9api XdmItem
140
156
  def to_java
141
157
  @s9_xdm_item
142
158
  end
data/lib/saxon/xpath.rb CHANGED
@@ -3,6 +3,15 @@ require_relative './xpath/compiler'
3
3
  module Saxon
4
4
  # Classes for compiling, configuring, and executing XPath queries against
5
5
  # XDM nodes or documents
6
+ #
7
+ # Using an XPath involves creating a compiler, compiling an XPath into an
8
+ # executable, and then running that XPath executable against an XDM node.
9
+ #
10
+ # The easiest way to create an XPath::Compiler instance is by using the {Saxon::Processor#xpath_compiler} method.
11
+ #
12
+ # @see Saxon::XPath::Compiler
13
+ # @see Saxon::XPath::Executable
14
+ # @see Saxon::Processor#xpath_compiler
6
15
  module XPath
7
16
  end
8
17
  end
@@ -4,7 +4,36 @@ require_relative './executable'
4
4
 
5
5
  module Saxon
6
6
  module XPath
7
- # Compiles XPath expressions so they can be executed
7
+ # An {XPath::Compiler} turns XPath expressions into executable queries you
8
+ # can run against XDM Nodes (like XML documents, or parts of XML documents).
9
+ #
10
+ # To compile an XPath requires an +XPath::Compiler+ instance. You can create
11
+ # one by calling {Compiler.create} and passing a {Saxon::Processor}, with an
12
+ # optional block for context like bound namespaces and declared variables.
13
+ # Alternately, and much more easily, you can call {Processor#xpath_compiler}
14
+ # on the {Saxon::Processor} instance you're already working with.
15
+ #
16
+ # processor = Saxon::Processor.create
17
+ # compiler = processor.xpath_compiler {
18
+ # namespace a: 'http://example.org/a'
19
+ # variable 'a:var', 'xs:string'
20
+ # }
21
+ # # Or...
22
+ # compiler = Saxon::XPath::Compiler.create(processor) {
23
+ # namespace a: 'http://example.org/a'
24
+ # variable 'a:var', 'xs:string'
25
+ # }
26
+ # xpath = compiler.compile('//a:element[@attr = $a:var]')
27
+ # matches = xpath.evaluate(document_node, {
28
+ # 'a:var' => 'the value'
29
+ # }) #=> Saxon::XDM::Value
30
+ #
31
+ # In order to use prefixed QNames in your XPaths, like +/ns:name/+, then you need
32
+ # to declare prefix/namespace URI bindings when you create a compiler.
33
+ #
34
+ # It's also possible to make use of variables in your XPaths by declaring them at
35
+ # the compiler creation stage, and then passing in values for them as XPath run
36
+ # time.
8
37
  class Compiler
9
38
  # Create a new <tt>XPath::Compiler</tt> using the supplied Processor.
10
39
  # Passing a block gives access to a DSL for setting up the compiler's
@@ -46,6 +75,12 @@ module Saxon
46
75
  Saxon::XPath::Executable.new(new_compiler.compile(expression), static_context)
47
76
  end
48
77
 
78
+ # Allows the creation of a new {Compiler} starting from a copy of this
79
+ # Compiler's static context. As with {.create}, passing a block gives
80
+ # access to a DSL for setting up the compiler's static context.
81
+ #
82
+ # @yield An {XPath::StaticContext::DSL} block
83
+ # @return [Saxon::XPath::Compiler] the new compiler instance
49
84
  def create(&block)
50
85
  new_static_context = static_context.define(block)
51
86
  self.class.new(@s9_processor, new_static_context)
@@ -2,7 +2,23 @@ require_relative '../xdm'
2
2
 
3
3
  module Saxon
4
4
  module XPath
5
- # Represents a compiled XPath query ready to be executed
5
+ # Represents a compiled XPath query ready to be executed. An
6
+ # {XPath::Executable} is created by compiling an XPath expression with
7
+ # {XPath::Compiler#compile}.
8
+ #
9
+ # To run the +XPath::Executable+ you can call {XPath::Executable#evaluate},
10
+ # to generate an {XDM::Value} of the results, or you can call
11
+ # {XPath::Executable#as_enum} to return an Enumerator over the results.
12
+ #
13
+ # processor = Saxon::Processor.create
14
+ # compiler = processor.xpath_compiler
15
+ # xpath = compiler.compile('//element[@attr = $var]')
16
+ #
17
+ # matches = xpath.evaluate(document_node, {'var' => 'the value'}) #=> Saxon::XDM::Value
18
+ #
19
+ # xpath.as_enum(document_node, {'var' => 'the value'}).each do |node|
20
+ # ...
21
+ # end
6
22
  class Executable
7
23
  # @return [XPath::StaticContext] the XPath's static context
8
24
  attr_reader :static_context
@@ -16,17 +32,34 @@ module Saxon
16
32
  @s9_xpath_executable, @static_context = s9_xpath_executable, static_context
17
33
  end
18
34
 
19
- # Run the compiled query using a passed-in node as the context item.
20
- # @param context_item [Saxon::XDM::Node] the context item node
21
- # @return [Saxon::XPath::Result] the result of the query as an
22
- # enumerable
23
- def run(context_item, variables = {})
24
- selector = to_java.load
25
- selector.setContextItem(context_item.to_java)
26
- variables.each do |qname_or_string, value|
27
- selector.setVariable(static_context.resolve_variable_qname(qname_or_string).to_java, Saxon::XDM.Value(value).to_java)
28
- end
29
- Result.new(selector.iterator)
35
+
36
+ # Evaluate the XPath against the context node given and return an
37
+ # +Enumerator+ over the result.
38
+ #
39
+ # @param context_item [Saxon::XDM::Node, Saxon::XDM::AtomicValue] the item
40
+ # to be used as the context item for evaluating the XPath from.
41
+ # @param variables [Hash<Saxon::QName => Saxon::XDM::Value, Saxon::XDM::Node,
42
+ # Saxon::XDM::AtomicValue>] any variable values to set within the XPath
43
+ # @return [Enumerator] an Enumerator over the items in the result sequence
44
+ def as_enum(context_item, variables = {})
45
+ generate_selector(context_item, variables).iterator.lazy.
46
+ map { |s9_xdm_object| Saxon::XDM.Value(s9_xdm_object) }
47
+ end
48
+
49
+ # Evaluate the XPath against the context node given and return the result.
50
+ #
51
+ # If the result is a single item, then that will be returned directly
52
+ # (e.g. an {XDM::Node} or an {XDM::AtomicValue}). If the result is an
53
+ # empty sequence then {XDM::EmptySequence} is returned.
54
+ #
55
+ # @param context_item [Saxon::XDM::Node, Saxon::XDM::AtomicValue] the item
56
+ # to be used as the context item for evaluating the XPath from.
57
+ # @param variables [Hash<Saxon::QName => Saxon::XDM::Value, Saxon::XDM::Node,
58
+ # Saxon::XDM::AtomicValue>] any variable values to set within the XPath
59
+ # @return [Saxon::XDM::Value] the XDM value returned
60
+ def evaluate(context_item, variables = {})
61
+ s9_xdm_value = generate_selector(context_item, variables).evaluate
62
+ Saxon::XDM.Value(s9_xdm_value)
30
63
  end
31
64
 
32
65
  # @return [net.sf.saxon.s9api.XPathExecutable] the underlying Saxon
@@ -34,24 +67,16 @@ module Saxon
34
67
  def to_java
35
68
  @s9_xpath_executable
36
69
  end
37
- end
38
70
 
39
- # The result of executing an XPath query as an enumerable object
40
- class Result
41
- include Enumerable
71
+ private
42
72
 
43
- # @api private
44
- # @param result_iterator [java.util.Iterator] the result of calling
45
- # <tt>#iterator</tt> on a Saxon <tt>XPathSelector</tt>
46
- def initialize(result_iterator)
47
- @result_iterator = result_iterator
48
- end
49
-
50
- # Yields <tt>XDM::Node</tt>s from the query result. If no block is passed,
51
- # returns an <tt>Enumerator</tt>
52
- # @yieldparam xdm_node [Saxon::XDM::Node] the name that is yielded
53
- def each(&block)
54
- @result_iterator.lazy.map { |s9_xdm_node| Saxon::XDM::Node.new(s9_xdm_node) }.each(&block)
73
+ def generate_selector(context_item, variables = {})
74
+ selector = to_java.load
75
+ selector.setContextItem(context_item.to_java)
76
+ variables.each do |qname_or_string, value|
77
+ selector.setVariable(static_context.resolve_variable_qname(qname_or_string).to_java, Saxon::XDM.Value(value).to_java)
78
+ end
79
+ selector
55
80
  end
56
81
  end
57
82
  end