saxon-rb 0.4.0-java → 0.7.2-java

Sign up to get free protection for your applications and to get access to all the features.
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