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