saxon-rb 0.4.0-java → 0.7.2-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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +429 -42
  3. data/.ruby-version +1 -1
  4. data/.yardopts +1 -0
  5. data/Gemfile +2 -2
  6. data/README.md +358 -10
  7. data/Rakefile +237 -7
  8. data/docs/templates/plugin.rb +73 -0
  9. 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
  10. data/lib/saxon-rb.rb +0 -0
  11. data/lib/{saxon_jars.rb → saxon-rb_jars.rb} +2 -2
  12. data/lib/saxon.rb +13 -0
  13. data/lib/saxon/axis_iterator.rb +8 -1
  14. data/lib/saxon/configuration.rb +16 -13
  15. data/lib/saxon/document_builder.rb +216 -5
  16. data/lib/saxon/feature_flags.rb +11 -0
  17. data/lib/saxon/feature_flags/errors.rb +8 -0
  18. data/lib/saxon/feature_flags/helpers.rb +15 -0
  19. data/lib/saxon/feature_flags/version.rb +100 -0
  20. data/lib/saxon/item_type.rb +129 -89
  21. data/lib/saxon/item_type/lexical_string_conversion.rb +214 -59
  22. data/lib/saxon/item_type/value_to_ruby.rb +25 -0
  23. data/lib/saxon/loader.rb +6 -1
  24. data/lib/saxon/nokogiri.rb +78 -0
  25. data/lib/saxon/occurrence_indicator.rb +32 -3
  26. data/lib/saxon/processor.rb +50 -5
  27. data/lib/saxon/qname.rb +37 -2
  28. data/lib/saxon/s9api.rb +5 -0
  29. data/lib/saxon/sequence_type.rb +131 -0
  30. data/lib/saxon/serializer.rb +3 -137
  31. data/lib/saxon/serializer/destination.rb +80 -0
  32. data/lib/saxon/serializer/object.rb +93 -0
  33. data/lib/saxon/serializer/output_properties.rb +83 -0
  34. data/lib/saxon/source.rb +207 -71
  35. data/lib/saxon/version.rb +7 -1
  36. data/lib/saxon/version/library.rb +89 -0
  37. data/lib/saxon/xdm.rb +7 -0
  38. data/lib/saxon/xdm/array.rb +16 -0
  39. data/lib/saxon/xdm/atomic_value.rb +10 -2
  40. data/lib/saxon/xdm/empty_sequence.rb +13 -0
  41. data/lib/saxon/xdm/external_object.rb +1 -0
  42. data/lib/saxon/xdm/function_item.rb +1 -0
  43. data/lib/saxon/xdm/item.rb +7 -0
  44. data/lib/saxon/xdm/map.rb +38 -0
  45. data/lib/saxon/xdm/node.rb +50 -1
  46. data/lib/saxon/xdm/sequence_like.rb +15 -0
  47. data/lib/saxon/xdm/value.rb +21 -5
  48. data/lib/saxon/xpath.rb +9 -0
  49. data/lib/saxon/xpath/compiler.rb +37 -2
  50. data/lib/saxon/xpath/executable.rb +53 -28
  51. data/lib/saxon/xpath/static_context.rb +25 -40
  52. data/lib/saxon/xpath/variable_declaration.rb +16 -49
  53. data/lib/saxon/xslt.rb +12 -0
  54. data/lib/saxon/xslt/compiler.rb +75 -6
  55. data/lib/saxon/xslt/evaluation_context.rb +30 -4
  56. data/lib/saxon/xslt/executable.rb +206 -29
  57. data/lib/saxon/xslt/invocation.rb +97 -0
  58. data/saxon-rb.gemspec +3 -3
  59. metadata +22 -10
  60. data/saxon.gemspec +0 -30
@@ -0,0 +1,83 @@
1
+ module Saxon
2
+ module Serializer
3
+ # Manage access to the serialization properties of this serializer, with
4
+ # hash-like access.
5
+ #
6
+ # Properties can be set explicitly through this API, or via XSLT or XQuery
7
+ # serialization options like +<xsl:output>+.
8
+ #
9
+ # Properties set explicitly here will override properties set through the
10
+ # document by +<xsl:output>+.
11
+ module OutputProperties
12
+ # @return [Saxon::Serializer::OutputProperties] hash-like access to the Output Properties
13
+ def output_property
14
+ @output_property ||= OutputProperties::Accessor.new(s9_serializer)
15
+ end
16
+
17
+ # @api private
18
+ #
19
+ # The private wrapper class that manages getting and setting output
20
+ # properties on a Serializer in an idiomatic Ruby-like way.
21
+ class Accessor
22
+ # @api private
23
+ # Provides mapping between symbols and the underlying Saxon property
24
+ # instances
25
+ def self.output_properties
26
+ @output_properties ||= Hash[
27
+ Saxon::S9API::Serializer::Property.values.map { |property|
28
+ qname = property.getQName
29
+ key = [
30
+ qname.getPrefix,
31
+ qname.getLocalName.tr('-', '_')
32
+ ].reject { |str| str == '' }.join('_').to_sym
33
+ [key, property]
34
+ }
35
+ ]
36
+ end
37
+
38
+ attr_reader :s9_serializer
39
+
40
+ # @api private
41
+ def initialize(s9_serializer)
42
+ @s9_serializer = s9_serializer
43
+ end
44
+
45
+ # @param [Symbol, Saxon::S9API::Serializer::Property] property The property to fetch
46
+ def [](property)
47
+ s9_serializer.getOutputProperty(resolved_property(property))
48
+ end
49
+
50
+ # @param [Symbol, Saxon::S9API::Serializer::Property] property The property to set
51
+ # @param [String] value The string value of the property
52
+ def []=(property, value)
53
+ s9_serializer.setOutputProperty(resolved_property(property), value)
54
+ end
55
+
56
+ # @overload fetch(property)
57
+ # @param [Symbol, Saxon::S9API::Serializer::Property] property The property to fetch
58
+ # @overload fetch(property, default)
59
+ # @param property [Symbol, Saxon::S9API::Serializer::Property] The property to fetch
60
+ # @param default [Object] The value to return if the property is unset
61
+ def fetch(property, default = nil)
62
+ explicit_value = self[property]
63
+ if explicit_value.nil? && !default.nil?
64
+ default
65
+ else
66
+ explicit_value
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def resolved_property(property_key)
73
+ case property_key
74
+ when Symbol
75
+ self.class.output_properties.fetch(property_key)
76
+ else
77
+ property_key
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,6 +1,7 @@
1
1
  require 'java'
2
2
  require 'saxon/jaxp'
3
3
  require 'uri'
4
+ require 'open-uri'
4
5
  require 'pathname'
5
6
 
6
7
  module Saxon
@@ -8,10 +9,13 @@ module Saxon
8
9
  # the XML bytestream in. Provides some extra methods to make handling closing
9
10
  # the source and its inputstream after consumption more idiomatic
10
11
  class Source
12
+ # Helper methods for getting Java-useful representations of source document
13
+ # strings and files
11
14
  module Helpers
12
15
  # Given a File, or IO object which will return either #path or
13
16
  # #base_uri, return the #base_uri, if present, or the #path, if present, or
14
17
  # nil
18
+ #
15
19
  # @param [File, IO] io A File or IO
16
20
  # object representing the input XML file or data, or a String containing
17
21
  # the XML
@@ -23,30 +27,92 @@ module Saxon
23
27
  io.path if io.respond_to?(:path)
24
28
  end
25
29
 
26
- # Given a File or IO return a Java InputStream
27
- # @param [File, IO, org.jruby.util.IOInputStream, java.io.InputStream]
28
- # io input to be converted to an input stream
30
+ # Given a File or IO return a Java InputStream, or an InputStreamReader if
31
+ # the Encoding is explicitly specified (rather than inferred from the
32
+ # <?xml charset="..."?>) declaration in the source.
33
+ #
34
+ # @param io [File, IO, org.jruby.util.IOInputStream, java.io.InputStream]
35
+ # input to be converted to an input stream
36
+ # @param encoding [Encoding, String] the character encoding to be used to
37
+ # for the stream, overriding the XML parser.
29
38
  # @return [java.io.InputStream] the wrapped input
30
- def self.inputstream(io)
31
- case io
39
+ def self.inputstream(io, encoding = nil)
40
+ stream = case io
32
41
  when org.jruby.util.IOInputStream, java.io.InputStream
33
42
  io
34
43
  else
35
44
  io.to_inputstream if io.respond_to?(:read)
36
45
  end
46
+
47
+ return stream if encoding.nil?
48
+ java.io.InputStreamReader.new(stream, ruby_encoding_to_charset(encoding))
37
49
  end
38
50
 
39
51
  # Given a path return a Java File object
40
- # @param [String, Pathname] path the path to the file
52
+ #
53
+ # @param path [String, Pathname] the path to the file
41
54
  # @return [java.io.File] the Java File object
42
55
  def self.file(path)
43
56
  java.io.File.new(path.to_s)
44
57
  end
58
+
59
+ # Given a file path and encoding, return a Java InputStreamReader object
60
+ # for the file.
61
+ #
62
+ # @param path [String, Pathname] the path to the file
63
+ # @param encoding [String, Encoding] the file's character encoding
64
+ # @return [java.io.InputStreamReader] a Java InputStreamReader object
65
+ # wrapping a FileInputStream for the file
66
+ def self.file_reader(path, encoding)
67
+ java.io.InputStreamReader.new(java.io.FileInputStream.new(file(path)), ruby_encoding_to_charset(encoding))
68
+ end
69
+
70
+ # Return a File or Reader object for a file, depending on whether the
71
+ # encoding must be explicitly specified or not.
72
+ #
73
+ # @param path [String, Pathname] the path to the file
74
+ # @param encoding [String, Encoding] the file's character encoding
75
+ # @return [java.io.Reader] a Java Reader object
76
+ def self.file_or_reader(path, encoding = nil)
77
+ encoding.nil? ? file(path) : file_reader(path, encoding)
78
+ end
79
+
80
+ # Return a Reader object for the String with an explicitly set encoding.
81
+ # If the encoding is +ASCII_8BIT+ then a binary-mode StreamReader is
82
+ # returned, rather than a character Reader
83
+ #
84
+ # @param string [String] the string
85
+ # @param encoding [String, Encoding] the string's character encoding
86
+ # @return [java.io.InputStream, java.io.Reader] a Java InputStream or Reader object
87
+ def self.string_reader(string, encoding)
88
+ inputstream = StringIO.new(string).to_inputstream
89
+ encoding = ruby_encoding(encoding)
90
+ return inputstream if encoding == ::Encoding::ASCII_8BIT
91
+ java.io.InputStreamReader.new(inputstream, ruby_encoding_to_charset(encoding))
92
+ end
93
+
94
+ # Figure out the equivalent Java +Charset+ for a Ruby {Encoding}.
95
+ #
96
+ # @param encoding [String, Encoding] the encoding to find a +Charset+ for
97
+ def self.ruby_encoding_to_charset(encoding)
98
+ ruby_encoding(encoding).to_java.getEncoding.getCharset
99
+ end
100
+
101
+ # Given a String with an {Encoding} name or an {Encoding} instance, return
102
+ # an {Encoding} instance
103
+ #
104
+ # @param encoding [String, Encoding] the encoding or encoding name
105
+ # @return [Encoding] the encoding
106
+ def self.ruby_encoding(encoding)
107
+ encoding.nil? ? nil : ::Encoding.find(encoding)
108
+ end
45
109
  end
46
110
 
111
+ # Lambda that checks if the given path exists and is a file
47
112
  PathChecker = ->(path) {
48
113
  File.file?(path)
49
114
  }
115
+ # Lambda that checks if the given string is a valid URI
50
116
  URIChecker = ->(uri) {
51
117
  begin
52
118
  URI.parse(uri)
@@ -56,75 +122,144 @@ module Saxon
56
122
  end
57
123
  }
58
124
 
59
- # Generate a Saxon::Source given an IO-like
60
- #
61
- # @param [IO, File] io The IO-like containing XML to be parsed
62
- # @param [Hash] opts
63
- # @option opts [String] :base_uri The Base URI for the Source - an
64
- # absolute URI or relative path that will be used to resolve relative
65
- # URLs in the XML. Setting this will override any path or URI derived
66
- # from the IO-like.
67
- # @return [Saxon::Source] the Saxon::Source wrapping the input
68
- def self.from_io(io, opts = {})
69
- base_uri = opts.fetch(:base_uri) { Helpers.base_uri(io) }
70
- inputstream = Helpers.inputstream(io)
71
- stream_source = Saxon::JAXP::StreamSource.new(inputstream, base_uri)
72
- new(stream_source, inputstream)
73
- end
125
+ class << self
126
+ # Generate a Saxon::Source given an IO-like
127
+ #
128
+ # @param [IO, File] io The IO-like containing XML to be parsed
129
+ # @param [Hash] opts
130
+ # @option opts [String] :base_uri The Base URI for the Source - an
131
+ # absolute URI or relative path that will be used to resolve relative
132
+ # URLs in the XML. Setting this will override any path or URI derived
133
+ # from the IO-like.
134
+ # @option opts [String, Encoding] :encoding The encoding of the source.
135
+ # Note that specifying this will force the parser to ignore the charset
136
+ # if it's set in the XML declaration of the source. Only really useful
137
+ # if there's a discrepancy between the source's declared and actual
138
+ # encoding. Defaults to the <?xml charset="..."?> declaration in the
139
+ # source.
140
+ # @return [Saxon::Source] the Saxon::Source wrapping the input
141
+ def from_io(io, opts = {})
142
+ base_uri = opts.fetch(:base_uri) { Helpers.base_uri(io) }
143
+ encoding = opts.fetch(:encoding, nil)
144
+ inputstream = Helpers.inputstream(io, encoding)
145
+ from_inputstream_or_reader(inputstream, base_uri)
146
+ end
74
147
 
75
- # Generate a Saxon::Source given a path to a file
76
- #
77
- # @param [String, Pathname] path The path to the XML file to be parsed
78
- # @param [Hash] opts
79
- # @option opts [String] :base_uri The Base URI for the Source - an
80
- # absolute URI or relative path that will be used to resolve relative
81
- # URLs in the XML. Setting this will override the file path.
82
- # @return [Saxon::Source] the Saxon::Source wrapping the input
83
- def self.from_path(path, opts = {})
84
- stream_source = Saxon::JAXP::StreamSource.new(Helpers.file(path))
85
- stream_source.setSystemId(opts[:base_uri]) if opts[:base_uri]
86
- new(stream_source)
87
- end
148
+ # Generate a Saxon::Source given a path to a file
149
+ #
150
+ # @param [String, Pathname] path The path to the XML file to be parsed
151
+ # @param [Hash] opts
152
+ # @option opts [String] :base_uri The Base URI for the Source - an
153
+ # absolute URI or relative path that will be used to resolve relative
154
+ # URLs in the XML. Setting this will override the file path.
155
+ # @option opts [String, Encoding] :encoding The encoding of the source.
156
+ # Note that specifying this will force the parser to ignore the charset
157
+ # if it's set in the XML declaration of the source. Only really useful
158
+ # if there's a discrepancy between the source's declared and actual
159
+ # encoding. Defaults to the <?xml charset="..."?> declaration in the
160
+ # source.
161
+ # @return [Saxon::Source] the Saxon::Source wrapping the input
162
+ def from_path(path, opts = {})
163
+ encoding = opts.fetch(:encoding, nil)
164
+ return from_inputstream_or_reader(Helpers.file(path), opts[:base_uri]) if encoding.nil?
165
+ reader = Helpers.file_reader(path, encoding)
166
+ base_uri = opts.fetch(:base_uri) { File.expand_path(path) }
167
+ from_inputstream_or_reader(reader, base_uri)
168
+ end
88
169
 
89
- # Generate a Saxon::Source given a URI
90
- #
91
- # @param [String, URI] uri The URI to the XML file to be parsed
92
- # @param [Hash] opts
93
- # @option opts [String] :base_uri The Base URI for the Source - an
94
- # absolute URI or relative path that will be used to resolve relative
95
- # URLs in the XML. Setting this will override the given URI.
96
- # @return [Saxon::Source] the Saxon::Source wrapping the input
97
- def self.from_uri(uri, opts = {})
98
- stream_source = Saxon::JAXP::StreamSource.new(uri.to_s)
99
- stream_source.setSystemId(opts[:base_uri]) if opts[:base_uri]
100
- new(stream_source)
101
- end
170
+ # Generate a Saxon::Source given a URI
171
+ #
172
+ # @param [String, URI] uri The URI to the XML file to be parsed
173
+ # @param [Hash] opts
174
+ # @option opts [String] :base_uri The Base URI for the Source - an
175
+ # absolute URI or relative path that will be used to resolve relative
176
+ # URLs in the XML. Setting this will override the given URI.
177
+ # @option opts [String, Encoding] :encoding The encoding of the source.
178
+ # Note that specifying this will force the parser to ignore the charset
179
+ # if it's set in the XML declaration of the source. Only really useful
180
+ # if there's a discrepancy between the source's declared and actual
181
+ # encoding. Defaults to the <?xml charset="..."?> declaration in the
182
+ # source.
183
+ # @return [Saxon::Source] the Saxon::Source wrapping the input
184
+ def from_uri(uri, opts = {})
185
+ encoding = opts.fetch(:encoding, nil)
186
+ return from_io(open(uri), encoding: encoding) if encoding
187
+ from_inputstream_or_reader(uri.to_s, opts[:base_uri])
188
+ end
102
189
 
103
- # Generate a Saxon::Source given a string containing XML
104
- #
105
- # @param [String] string The string containing XML to be parsed
106
- # @param [Hash] opts
107
- # @option opts [String] :base_uri The Base URI for the Source - an
108
- # absolute URI or relative path that will be used to resolve relative
109
- # URLs in the XML. This will be nil unless set.
110
- # @return [Saxon::Source] the Saxon::Source wrapping the input
111
- def self.from_string(string, opts = {})
112
- reader = java.io.StringReader.new(string)
113
- stream_source = Saxon::JAXP::StreamSource.new(reader)
114
- stream_source.setSystemId(opts[:base_uri]) if opts[:base_uri]
115
- new(stream_source, reader)
116
- end
190
+ # Generate a Saxon::Source given a string containing XML
191
+ #
192
+ # @param [String] string The string containing XML to be parsed
193
+ # @param [Hash] opts
194
+ # @option opts [String] :base_uri The Base URI for the Source - an
195
+ # absolute URI or relative path that will be used to resolve relative
196
+ # URLs in the XML. This will be nil unless set.
197
+ # @option opts [String, Encoding] :encoding The encoding of the source.
198
+ # Note that specifying this will force the parser to ignore the charset
199
+ # if it's set in the XML declaration of the source. Only really useful
200
+ # if there's a discrepancy between the encoding of the string and the
201
+ # encoding of the source. Defaults to the encoding of the string, unless
202
+ # that is ASCII-8BIT, in which case the parser will use the
203
+ # <?xml charset="..."?> declaration in the source to pick the encoding.
204
+ # @return [Saxon::Source] the Saxon::Source wrapping the input
205
+ def from_string(string, opts = {})
206
+ encoding = opts.fetch(:encoding) { string.encoding }
207
+ reader = Helpers.string_reader(string, encoding)
208
+ from_inputstream_or_reader(reader, opts[:base_uri])
209
+ end
210
+
211
+ # Generate a Saxon::Source from one of the several inputs allowed.
212
+ #
213
+ # If possible the character encoding of the input source will be left to
214
+ # the XML parser to discover (from the <tt><?xml charset="..."?></tt> XML
215
+ # declaration).
216
+ #
217
+ # The Base URI for the source (its absolute path, or URI) can be set by
218
+ # passing in the +:base_uri+ option. This is the same thing as an XML
219
+ # document's 'System ID' - Base URI is the term most widely used in Ruby
220
+ # libraries for this, so that's what's used here.
221
+ #
222
+ # If the source's character encoding can't be correctly discovered by the
223
+ # parser from the XML declaration (<tt><?xml version="..."
224
+ # charset="..."?></tt> at the top of the document), then it can be passed
225
+ # as the +:encoding+ option.
226
+ #
227
+ # If an existing {Source} is passed in, simply return it.
228
+ #
229
+ # @param [Saxon::Source, IO, File, String, Pathname, URI] input The XML to be parsed
230
+ # @param [Hash] opts
231
+ # @option opts [String] :base_uri The Base URI for the Source - an
232
+ # absolute URI or relative path that will be used to resolve relative
233
+ # URLs in the XML. Setting this will override any path or URI derived
234
+ # from an IO, URI, or Path.
235
+ # @option opts [String, Encoding] :encoding The encoding of the source.
236
+ # Note that specifying this will force the parser to ignore the charset
237
+ # if it's set in the XML declaration of the source. Only really useful
238
+ # if there's a discrepancy between the source's declared and actual
239
+ # encoding. Defaults to the <?xml charset="..."?> declaration in the
240
+ # source.
241
+ # @return [Saxon::Source] the Saxon::Source wrapping the input
242
+ def create(input, opts = {})
243
+ case input
244
+ when Saxon::Source
245
+ input
246
+ when IO, File, java.io.InputStream, StringIO
247
+ from_io(input, opts)
248
+ when Pathname, PathChecker
249
+ from_path(input, opts)
250
+ when URIChecker
251
+ from_uri(input, opts)
252
+ else
253
+ from_string(input, opts)
254
+ end
255
+ end
256
+
257
+ private
117
258
 
118
- def self.create(io_path_uri_or_string, opts = {})
119
- case io_path_uri_or_string
120
- when IO, File, java.io.InputStream, StringIO
121
- from_io(io_path_uri_or_string, opts)
122
- when Pathname, PathChecker
123
- from_path(io_path_uri_or_string, opts)
124
- when URIChecker
125
- from_uri(io_path_uri_or_string, opts)
126
- else
127
- from_string(io_path_uri_or_string, opts)
259
+ def from_inputstream_or_reader(inputstream_or_reader, base_uri = nil)
260
+ stream_source = Saxon::JAXP::StreamSource.new(inputstream_or_reader)
261
+ stream_source.setSystemId(base_uri) if base_uri
262
+ new(stream_source, inputstream_or_reader)
128
263
  end
129
264
  end
130
265
 
@@ -183,5 +318,6 @@ module Saxon
183
318
  end
184
319
  end
185
320
 
321
+ # Error raised when trying to consume an already-consumed, and closed, Source
186
322
  class SourceClosedError < Exception; end
187
323
  end
@@ -1,3 +1,9 @@
1
+ require 'saxon/s9api'
2
+
1
3
  module Saxon
2
- VERSION = "0.4.0"
4
+ # Provides the saxon-rb and underlying Saxon library versions
5
+ module Version
6
+ # The version of the saxon-rb gem (not of Saxon itself)
7
+ WRAPPER = "0.7.2"
8
+ end
3
9
  end
@@ -0,0 +1,89 @@
1
+ require 'saxon/s9api'
2
+
3
+ module Saxon
4
+ module Version
5
+ # The version of the underlying Saxon library, which we need to discover at
6
+ # runtime based on what version is on the Classpath
7
+ class Library
8
+ # The loaded version of the Saxon Java library
9
+ #
10
+ # @return [Saxon::Version::Library] the version of the loaded library
11
+ def self.loaded_version
12
+ Saxon::Loader.load!
13
+
14
+ sv = Java::net.sf.saxon.Version
15
+ new(sv.getProductVersion, sv.getStructuredVersionNumber, sv.softwareEdition)
16
+ end
17
+
18
+ include Comparable
19
+
20
+ # @return [String] the version string (e.g. '9.9.1.6', '10.0')
21
+ attr_reader :version
22
+ # @return [String] the version components (e.g. <tt>[9, 9, 1, 6]</tt>, <tt>[10, 0]</tt>)
23
+ attr_reader :components
24
+ # @return [Symbol] the edition (+:he+, +:pe+, or +:ee+)
25
+ attr_reader :edition
26
+
27
+ # @param version [String] the version string
28
+ # @param components [Array<Integer>] the version components separated
29
+ # @param edition [String, Symbol] the name of the Saxon edition (e.g. +:he+, +'HE'+)
30
+ def initialize(version, components, edition)
31
+ @version = version.dup.freeze
32
+ @components = components.dup.freeze
33
+ @edition = edition.downcase.to_sym
34
+ end
35
+
36
+ # Comparison against another instance
37
+ #
38
+ # @param other [Saxon::Version::Library] the other version to compare against
39
+ # @return [Integer] -1 for less, 1 for greater, 0 for equal
40
+ def <=>(other)
41
+ return false unless other.is_a?(self.class)
42
+
43
+ n_components = [self.components.length, other.components.length].max
44
+ (0..(n_components - 1)).reduce(0, &comparator(other))
45
+ end
46
+
47
+ # Pessimistic comparison à la rubygems +~>+: do I satisfy the other
48
+ # version if considered as a pessimistic version constraint
49
+ #
50
+ # @param pessimistic_version [Saxon::Version::Library] the version to
51
+ # compare pessimistically
52
+ # @return [Boolean] do I satisfy the constraint?
53
+ def pessimistic_compare(pessimistic_version)
54
+ pessimistic_components = pessimistic_version.components
55
+ pessimistic_components = pessimistic_components + [0] if pessimistic_components.length == 1
56
+ locked = pessimistic_components[0..-2]
57
+ locked = locked.zip(components[0..locked.length])
58
+ variable = [pessimistic_components[-1], components[locked.length]]
59
+
60
+ locked_ok = locked.all? { |check, mine|
61
+ check == mine
62
+ }
63
+
64
+ return false unless locked_ok
65
+
66
+ check, mine = variable
67
+ mine >= check
68
+ end
69
+
70
+ # @return [String] the version string
71
+ def to_s
72
+ version
73
+ end
74
+
75
+ private
76
+
77
+ def comparator(other)
78
+ ->(cmp, i) {
79
+ return cmp unless cmp == 0
80
+
81
+ mine = self.components[i].nil? ? 0 : self.components[i]
82
+ theirs = other.components[i].nil? ? 0 : other.components[i]
83
+
84
+ mine <=> theirs
85
+ }
86
+ end
87
+ end
88
+ end
89
+ end