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,15 @@
1
+ module Saxon
2
+ module FeatureFlags
3
+ # Helper methods to create feature restrictions in the library. To be mixed
4
+ # in to library classes.
5
+ module Helpers
6
+ # specify that the method named can only run if the version constraint is satisfied
7
+ #
8
+ # @param method_name [Symbol] the name of the method
9
+ # @param version_constraint [String] the version constraint (<tt>'>= 9.9'</tt>)
10
+ def requires_saxon_version(method_name, version_constraint)
11
+ Saxon::FeatureFlags::Version.create(self, method_name, version_constraint)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,100 @@
1
+ require_relative '../version/library'
2
+ require_relative 'errors'
3
+
4
+ module Saxon
5
+ module FeatureFlags
6
+ # Restrict a specific method to only work with the specified Saxon version
7
+ class Version
8
+ # Modify the method so that it will only run if the version constraint is
9
+ # satisfied.
10
+ #
11
+ # We can't know what version of Saxon is in use at code load time, only
12
+ # once {Saxon::Loader#load!} has been called, either explicitly or by
13
+ # calling a method which requires a Saxon Java object. Therefore, we have
14
+ # to check this the first time someone calls the method.
15
+ #
16
+ # Creating this check replaces the method on the target class with one
17
+ # that checks the version, and runs the original version if its constraint
18
+ # is satisfied.
19
+ #
20
+ # To avoid performing the check every time the method is called, once we
21
+ # know whether or not the constraint is satisfied, we assume that the
22
+ # verion of Saxon cannot be unloaded and a new one loaded in its place and
23
+ # replace our version checking method with the original (if the constraint
24
+ # is passed), or with one that simply +raise+s the constraint error.
25
+ #
26
+ # @param klass [Class] the class the method lives on
27
+ # @param method_name [Symbol] the name of the method to be constrained
28
+ # @param version_constraint [String] the version constraint
29
+ # (<tt>'>9.9.1.7'</tt> or <tt>'>= 9.9'</tt>)
30
+ def self.create(klass, method_name, version_constraint)
31
+ method = klass.instance_method(method_name)
32
+ version_check = new(version_constraint)
33
+
34
+
35
+ klass.send(:define_method, method_name, ->(*args) {
36
+ if version_check.ok?
37
+ klass.send(:define_method, method_name, method)
38
+ method.bind(self).call(*args)
39
+ else
40
+ klass.send(:define_method, method_name, ->(*args) {
41
+ raise UnavailableInThisSaxonVersionError
42
+ })
43
+ raise UnavailableInThisSaxonVersionError
44
+ end
45
+ })
46
+ end
47
+
48
+ # @return [String] the complete constraint string
49
+ attr_reader :constraint_string
50
+ # @return [Symbol] the extracted comparison operator
51
+ attr_reader :comparison_operator
52
+ # @return [String] the extracted version string
53
+ attr_reader :version_string
54
+
55
+ # Create a version constraint check from the supplied constraint string
56
+ #
57
+ # @param constraint_string [String] the version constraint
58
+ def initialize(constraint_string)
59
+ @constraint_string = constraint_string
60
+ @comparison_operator, @version_string = parse_version_constraint(constraint_string)
61
+ end
62
+
63
+ # Reports if the version constraint is satisfied or not.
64
+ #
65
+ # @return [Boolean] true if the constraint is satisfied, false otherwise
66
+ def ok?
67
+ Saxon::Version::Library.loaded_version.send(comparison_operator, constraint_version)
68
+ end
69
+
70
+ # Generates a {Saxon::Version::Library} representing the version specified
71
+ # in the constraint that can be compared with the loaded Saxon version
72
+ #
73
+ # @return [Saxon::Version::Library] the constraint version
74
+ def constraint_version
75
+ @constraint_version ||= begin
76
+ components = version_string.split('.').map { |n| Integer(n, 10) }
77
+ Saxon::Version::Library.new(version_string, components, 'HE')
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def parse_version_constraint(version_constraint)
84
+ op_string, version_string = version_constraint.split
85
+
86
+ comparison_operator = parse_operator_string(op_string)
87
+ [comparison_operator, version_string]
88
+ end
89
+
90
+ def parse_operator_string(op_string)
91
+ case op_string
92
+ when '~>'
93
+ :pessimistic_compare
94
+ else
95
+ op_string.to_sym
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -6,13 +6,55 @@ require_relative 'item_type/value_to_ruby'
6
6
  module Saxon
7
7
  # Represent XDM types abstractly
8
8
  class ItemType
9
- # Error raised when a Ruby class has no equivalent XDM type to be converted
10
- # into
9
+ # lazy-loading Hash so we can avoid eager-loading saxon Jars, which prevents
10
+ # using external Saxon Jars unless the user is more careful than they should
11
+ # have to be.
12
+ class LazyReadOnlyHash
13
+ include Enumerable
14
+
15
+ attr_reader :loaded_hash, :load_mutex, :init_block
16
+ private :loaded_hash, :load_mutex, :init_block
17
+
18
+ def initialize(&init_block)
19
+ @init_block = init_block
20
+ @load_mutex = Mutex.new
21
+ @loaded_hash = nil
22
+ end
23
+
24
+ def [](key)
25
+ ensure_loaded!
26
+ loaded_hash[key]
27
+ end
28
+
29
+ def fetch(*args, &block)
30
+ ensure_loaded!
31
+ loaded_hash.fetch(*args, &block)
32
+ end
33
+
34
+ def each(&block)
35
+ ensure_loaded!
36
+ loaded_hash.each(&block)
37
+ end
38
+
39
+ private
40
+
41
+ def ensure_loaded!
42
+ return true unless loaded_hash.nil?
43
+ load_mutex.synchronize do
44
+ return true unless loaded_hash.nil?
45
+ @loaded_hash = init_block.call
46
+ end
47
+ end
48
+ end
49
+
50
+ # Error raised when a Ruby class has no equivalent XDM type to
51
+ # be converted into
11
52
  class UnmappedRubyTypeError < StandardError
12
53
  def initialize(class_name)
13
54
  @class_name = class_name
14
55
  end
15
56
 
57
+ # error message including class name no type equivalent found for
16
58
  def to_s
17
59
  "Ruby class <#{@class_name}> has no XDM type equivalent"
18
60
  end
@@ -26,29 +68,14 @@ module Saxon
26
68
  @type_str = type_str
27
69
  end
28
70
 
71
+ # error message including type string with no matching built-in type
29
72
  def to_s
30
73
  "'#{@type_str}' is not recognised as an XSD built-in type"
31
74
  end
32
75
  end
33
76
 
34
- class Factory
35
- DEFAULT_SEMAPHORE = Mutex.new
36
-
37
- attr_reader :processor
38
-
39
- def initialize(processor)
40
- @processor = processor
41
- end
42
-
43
- def s9_factory
44
- return @s9_factory if instance_variable_defined?(:@s9_factory)
45
- DEFAULT_SEMAPHORE.synchronize do
46
- @s9_factory = S9API::ItemTypeFactory.new(processor.to_java)
47
- end
48
- end
49
- end
50
-
51
77
  TYPE_CACHE_MUTEX = Mutex.new
78
+ private_constant :TYPE_CACHE_MUTEX
52
79
  # A mapping of Ruby types to XDM type constants
53
80
  TYPE_MAPPING = {
54
81
  'String' => :STRING,
@@ -61,88 +88,96 @@ module Saxon
61
88
  'Time' => :DATE_TIME,
62
89
  'BigDecimal' => :DECIMAL,
63
90
  'Integer' => :INTEGER,
91
+ 'Fixnum' => :INTEGER, # Fixnum/Bignum needed for JRuby 9.1/Ruby 2.3
92
+ 'Bignum' => :INTEGER,
64
93
  'Float' => :FLOAT,
65
94
  'Numeric' => :NUMERIC
66
95
  }.freeze
67
96
 
68
97
  # A mapping of QNames to XDM type constants
69
- QNAME_MAPPING = Hash[{
70
- 'anyAtomicType' => :ANY_ATOMIC_VALUE,
71
- 'anyURI' => :ANY_URI,
72
- 'base64Binary' => :BASE64_BINARY,
73
- 'boolean' => :BOOLEAN,
74
- 'byte' => :BYTE,
75
- 'date' => :DATE,
76
- 'dateTime' => :DATE_TIME,
77
- 'dateTimeStamp' => :DATE_TIME_STAMP,
78
- 'dayTimeDuration' => :DAY_TIME_DURATION,
79
- 'decimal' => :DECIMAL,
80
- 'double' => :DOUBLE,
81
- 'duration' => :DURATION,
82
- 'ENTITY' => :ENTITY,
83
- 'float' => :FLOAT,
84
- 'gDay' => :G_DAY,
85
- 'gMonth' => :G_MONTH,
86
- 'gMonthDay' => :G_MONTH_DAY,
87
- 'gYear' => :G_YEAR,
88
- 'gYearMonth' => :G_YEAR_MONTH,
89
- 'hexBinary' => :HEX_BINARY,
90
- 'ID' => :ID,
91
- 'IDREF' => :IDREF,
92
- 'int' => :INT,
93
- 'integer' => :INTEGER,
94
- 'language' => :LANGUAGE,
95
- 'long' => :LONG,
96
- 'Name' => :NAME,
97
- 'NCName' => :NCNAME,
98
- 'negativeInteger' => :NEGATIVE_INTEGER,
99
- 'NMTOKEN' => :NMTOKEN,
100
- 'nonNegativeInteger' => :NON_NEGATIVE_INTEGER,
101
- 'nonPositiveInteger' => :NON_POSITIVE_INTEGER,
102
- 'normalizedString' => :NORMALIZED_STRING,
103
- 'NOTATION' => :NOTATION,
104
- 'numeric' => :NUMERIC,
105
- 'positiveInteger' => :POSITIVE_INTEGER,
106
- 'QName' => :QNAME,
107
- 'short' => :SHORT,
108
- 'string' => :STRING,
109
- 'time' => :TIME,
110
- 'token' => :TOKEN,
111
- 'unsignedByte' => :UNSIGNED_BYTE,
112
- 'unsignedInt' => :UNSIGNED_INT,
113
- 'unsignedLong' => :UNSIGNED_LONG,
114
- 'unsignedShort' => :UNSIGNED_SHORT,
115
- 'untypedAtomic' => :UNTYPED_ATOMIC,
116
- 'yearMonthDuration' => :YEAR_MONTH_DURATION
117
- }.map { |local_name, constant|
118
- qname = Saxon::QName.create({
119
- prefix: 'xs', uri: 'http://www.w3.org/2001/XMLSchema',
120
- local_name: local_name
121
- })
122
- [qname, constant]
123
- }].freeze
98
+ QNAME_MAPPING = LazyReadOnlyHash.new do
99
+ {
100
+ 'anyAtomicType' => :ANY_ATOMIC_VALUE,
101
+ 'anyURI' => :ANY_URI,
102
+ 'base64Binary' => :BASE64_BINARY,
103
+ 'boolean' => :BOOLEAN,
104
+ 'byte' => :BYTE,
105
+ 'date' => :DATE,
106
+ 'dateTime' => :DATE_TIME,
107
+ 'dateTimeStamp' => :DATE_TIME_STAMP,
108
+ 'dayTimeDuration' => :DAY_TIME_DURATION,
109
+ 'decimal' => :DECIMAL,
110
+ 'double' => :DOUBLE,
111
+ 'duration' => :DURATION,
112
+ 'ENTITY' => :ENTITY,
113
+ 'float' => :FLOAT,
114
+ 'gDay' => :G_DAY,
115
+ 'gMonth' => :G_MONTH,
116
+ 'gMonthDay' => :G_MONTH_DAY,
117
+ 'gYear' => :G_YEAR,
118
+ 'gYearMonth' => :G_YEAR_MONTH,
119
+ 'hexBinary' => :HEX_BINARY,
120
+ 'ID' => :ID,
121
+ 'IDREF' => :IDREF,
122
+ 'int' => :INT,
123
+ 'integer' => :INTEGER,
124
+ 'language' => :LANGUAGE,
125
+ 'long' => :LONG,
126
+ 'Name' => :NAME,
127
+ 'NCName' => :NCNAME,
128
+ 'negativeInteger' => :NEGATIVE_INTEGER,
129
+ 'NMTOKEN' => :NMTOKEN,
130
+ 'nonNegativeInteger' => :NON_NEGATIVE_INTEGER,
131
+ 'nonPositiveInteger' => :NON_POSITIVE_INTEGER,
132
+ 'normalizedString' => :NORMALIZED_STRING,
133
+ 'NOTATION' => :NOTATION,
134
+ 'numeric' => :NUMERIC,
135
+ 'positiveInteger' => :POSITIVE_INTEGER,
136
+ 'QName' => :QNAME,
137
+ 'short' => :SHORT,
138
+ 'string' => :STRING,
139
+ 'time' => :TIME,
140
+ 'token' => :TOKEN,
141
+ 'unsignedByte' => :UNSIGNED_BYTE,
142
+ 'unsignedInt' => :UNSIGNED_INT,
143
+ 'unsignedLong' => :UNSIGNED_LONG,
144
+ 'unsignedShort' => :UNSIGNED_SHORT,
145
+ 'untypedAtomic' => :UNTYPED_ATOMIC,
146
+ 'yearMonthDuration' => :YEAR_MONTH_DURATION
147
+ }.map { |local_name, constant|
148
+ qname = Saxon::QName.create({
149
+ prefix: 'xs', uri: 'http://www.w3.org/2001/XMLSchema',
150
+ local_name: local_name
151
+ })
152
+ [qname, constant]
153
+ }.to_h.freeze
154
+ end
124
155
 
125
156
  # A mapping of type names/QNames to XDM type constants
126
- STR_MAPPING = {
127
- 'array(*)' => :ANY_ARRAY,
128
- 'item()' => :ANY_ITEM,
129
- 'map(*)' => :ANY_MAP,
130
- 'node()' => :ANY_NODE
131
- }.merge(
132
- Hash[QNAME_MAPPING.map { |qname, v| [qname.to_s, v] }]
133
- ).freeze
134
-
135
- ATOMIC_VALUE_LEXICAL_STRING_CONVERTORS = Hash[
157
+ STR_MAPPING = LazyReadOnlyHash.new do
158
+ {
159
+ 'array(*)' => :ANY_ARRAY,
160
+ 'item()' => :ANY_ITEM,
161
+ 'map(*)' => :ANY_MAP,
162
+ 'node()' => :ANY_NODE
163
+ }.merge(
164
+ Hash[QNAME_MAPPING.map { |qname, v| [qname.to_s, v] }]
165
+ ).freeze
166
+ end
167
+
168
+ # convertors to generate lexical strings for a given {ItemType}, as a hash keyed on the ItemType
169
+ ATOMIC_VALUE_LEXICAL_STRING_CONVERTORS = LazyReadOnlyHash.new do
136
170
  LexicalStringConversion::Convertors.constants.map { |const|
137
171
  [S9API::ItemType.const_get(const), LexicalStringConversion::Convertors.const_get(const)]
138
- }
139
- ].freeze
172
+ }.to_h.freeze
173
+ end
140
174
 
141
- ATOMIC_VALUE_TO_RUBY_CONVERTORS = Hash[
175
+ # convertors from {XDM::AtomicValue} to a ruby primitve value, as a hash keyed on the ItemType
176
+ ATOMIC_VALUE_TO_RUBY_CONVERTORS = LazyReadOnlyHash.new do
142
177
  ValueToRuby::Convertors.constants.map { |const|
143
178
  [S9API::ItemType.const_get(const), ValueToRuby::Convertors.const_get(const)]
144
- }
145
- ].freeze
179
+ }.to_h.freeze
180
+ end
146
181
 
147
182
  class << self
148
183
  # Get an appropriate {ItemType} for a Ruby type or given a type name as a
@@ -155,6 +190,7 @@ module Saxon
155
190
  # @overload get_type(type_name)
156
191
  # Get the {ItemType} for the name
157
192
  # @param type_name [String] name of the built-in {ItemType} to fetch
193
+ # (e.g. +xs:string+ or +element()+)
158
194
  # @overload get_type(item_type)
159
195
  # Given an instance of {ItemType}, simply return the instance
160
196
  # @param item_type [Saxon::ItemType] an existing ItemType instance
@@ -182,6 +218,8 @@ module Saxon
182
218
 
183
219
  def get_s9_type(arg)
184
220
  case arg
221
+ when S9API::ItemType
222
+ arg
185
223
  when Saxon::QName
186
224
  get_s9_qname_mapped_type(arg)
187
225
  when Class
@@ -250,6 +288,8 @@ module Saxon
250
288
 
251
289
  alias_method :eql?, :==
252
290
 
291
+ # Return a hash code so this can be used as a key in a {::Hash}.
292
+ # @return [Fixnum] the hash code
253
293
  def hash
254
294
  @hash ||= s9_item_type.hashCode
255
295
  end
@@ -5,12 +5,26 @@ module Saxon
5
5
  # A collection of lamba-like objects for converting Ruby values into
6
6
  # lexical strings for specific XSD datatypes
7
7
  module LexicalStringConversion
8
+ # Simple validation helper that checks if a value string matches an
9
+ # allowed lexical string pattern space or not.
10
+ #
11
+ # @param value [Object] the value whose to_s representation should be
12
+ # checked
13
+ # @param item_type [Saxon::ItemType] the ItemType whose lexical pattern
14
+ # space should be checked against
15
+ # @param pattern [Regexp] the lexical pattern space Regexp to use in the
16
+ # checking
17
+ # @return [String] the lexical string for the value and type
18
+ # @raise [Errors::BadRubyValue] if the ruby value doesn't produce a string
19
+ # which validates against the allowed pattern
8
20
  def self.validate(value, item_type, pattern)
9
21
  str = value.to_s
10
- raise Errors::BadRubyValue.new(value, item_type) unless str.match?(pattern)
22
+ raise Errors::BadRubyValue.new(value, item_type) if str.match(pattern).nil?
11
23
  str
12
24
  end
13
25
 
26
+ # Helper class for performing conversion and validation to XDM integer
27
+ # types from Ruby's Fixnum/Bignum/Integer classes
14
28
  class IntegerConversion
15
29
  attr_reader :min, :max
16
30
 
@@ -18,20 +32,40 @@ module Saxon
18
32
  @min, @max = min, max
19
33
  end
20
34
 
35
+ # Returns whether the Ruby integer is within the range allowed for the
36
+ # XDM type
37
+ # @param integer_value [Integer] the ruby integer to check
38
+ # @return [Boolean] whether the value is within bounds
21
39
  def in_bounds?(integer_value)
22
40
  gte_min?(integer_value) && lte_max?(integer_value)
23
41
  end
24
42
 
43
+ # Returns whether the Ruby integer is >= the lower bound of the range
44
+ # allowed for the XDM type
45
+ # @param integer_value [Integer] the ruby integer to check
46
+ # @return [Boolean] whether the value is okay
25
47
  def gte_min?(integer_value)
26
48
  return true if min.nil?
27
49
  integer_value >= min
28
50
  end
29
51
 
52
+ # Returns whether the Ruby integer is <= the upper bound of the range
53
+ # allowed for the XDM type
54
+ # @param integer_value [Integer] the ruby integer to check
55
+ # @return [Boolean] whether the value is okay
30
56
  def lte_max?(integer_value)
31
57
  return true if max.nil?
32
58
  integer_value <= max
33
59
  end
34
60
 
61
+ # Check a value against our type constraints, and return the lexical
62
+ # string representation if it's okay.
63
+ #
64
+ # @param value [Integer] the ruby value
65
+ # @param item_type [Saxon::ItemType] the item type
66
+ # @return [String] the lexical string representation of the value
67
+ # @raise [Errors::RubyValueOutOfBounds] if the value is outside the
68
+ # type's permitted bounds
35
69
  def call(value, item_type)
36
70
  integer_value = case value
37
71
  when ::Numeric
@@ -44,15 +78,42 @@ module Saxon
44
78
  end
45
79
  end
46
80
 
81
+ # Helper class for performing conversion and validation to XDM
82
+ # Floating-point types from Ruby's Float class
47
83
  class FloatConversion
48
84
  def initialize(size = :double)
49
85
  @double = size == :double
50
86
  end
51
87
 
88
+ # Check a value against our type constraints, and return the lexical
89
+ # string representation if it's okay.
90
+ #
91
+ # @param value [Float] the ruby value
92
+ # @param item_type [Saxon::ItemType] the item type
93
+ # @return [String] the lexical string representation of the value
94
+ def call(value, item_type)
95
+ case value
96
+ when ::Float::INFINITY
97
+ 'INF'
98
+ when -::Float::INFINITY
99
+ '-INF'
100
+ when Numeric
101
+ float_value(value).to_s
102
+ else
103
+ LexicalStringConversion.validate(value, item_type, Patterns::FLOAT)
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ # Is this a double-precision XDM float?
110
+ # @return [Boolean] true if we're converting a double-precision float
52
111
  def double?
53
112
  @double
54
113
  end
55
114
 
115
+ # Return the float as either a double-precision or single-precision
116
+ # float as needed
56
117
  def float_value(float_value)
57
118
  return float_value if double?
58
119
  convert_to_single_precision(float_value)
@@ -61,30 +122,72 @@ module Saxon
61
122
  def convert_to_single_precision(float_value)
62
123
  [float_value].pack('f').unpack('f').first
63
124
  end
125
+ end
126
+
127
+ # Convert a value in seconds into an XDM Duration string
128
+ class DurationConversion
129
+ attr_reader :pattern
64
130
 
131
+ def initialize(pattern)
132
+ @pattern = pattern
133
+ end
134
+
135
+ # Produce a lexical Duration string from a numeric Ruby value
136
+ # representing seconds
65
137
  def call(value, item_type)
138
+ return numeric(value) if Numeric === value
139
+ LexicalStringConversion.validate(value, item_type, pattern)
140
+ end
141
+
142
+ private
143
+
144
+ def numeric(value)
145
+ sign = value.negative? ? '-' : ''
66
146
  case value
67
- when ::Float::INFINITY
68
- 'INF'
69
- when -::Float::INFINITY
70
- '-INF'
71
- when Numeric
72
- float_value(value).to_s
147
+ when Integer
148
+ "#{sign}PT#{value.abs}S"
149
+ when BigDecimal
150
+ "#{sign}PT#{value.abs.to_s('F')}S"
73
151
  else
74
- LexicalStringConversion.validate(value, item_type, Patterns::FLOAT)
152
+ sprintf("%sPT%0.9fS", sign, value.abs)
75
153
  end
76
154
  end
77
155
  end
78
156
 
157
+ # Helper class for creating convertors for the various G* Date-related
158
+ # types that allow single values (GDay, GMonth, GYear).
79
159
  class GDateConversion
80
160
  attr_reader :bounds, :integer_formatter, :validation_pattern
81
161
 
82
- def initialize(args = {})
162
+ # @param args [Hash]
163
+ # @option args [Range] :bounds the integer bounds for values of this type
164
+ # @option args [Regexp] :validation_pattern the pattern used to validate the
165
+ # value when it's a String not an Integer
166
+ # @option args [Proc] :integer_formatter a proc/lambda that will produce a
167
+ # correctly-formatted lexical string from an Integer value
168
+ def initialize(args = {})
83
169
  @bounds = args.fetch(:bounds)
84
170
  @validation_pattern = args.fetch(:validation_pattern)
85
171
  @integer_formatter = args.fetch(:integer_formatter)
86
172
  end
87
173
 
174
+ # @param value [String, Integer] the value to convert
175
+ # @param item_type [XDM::ItemType] the type being converted to
176
+ # @return [String] a correctly formatted String
177
+ def call(value, item_type)
178
+ case value
179
+ when Integer
180
+ check_value_bounds!(value, item_type)
181
+ sprintf(integer_formatter.call(value), value)
182
+ else
183
+ formatted_value = LexicalStringConversion.validate(value, item_type, validation_pattern)
184
+ extract_and_check_value_bounds!(formatted_value, item_type)
185
+ formatted_value
186
+ end
187
+ end
188
+
189
+ private
190
+
88
191
  def extract_value_from_validated_format(formatted_value)
89
192
  Integer(formatted_value.gsub(validation_pattern, '\1'), 10)
90
193
  end
@@ -97,24 +200,31 @@ module Saxon
97
200
  def extract_and_check_value_bounds!(formatted_value, item_type)
98
201
  check_value_bounds!(extract_value_from_validated_format(formatted_value), item_type)
99
202
  end
203
+ end
204
+
205
+ # Convert Bytes. Idiomatically, Ruby uses +ASCII_8BIT+ encoded strings to
206
+ # represent bytes, and so a single character represents a single byte. XDM
207
+ # uses the decimal value of a signed or unsigned 8 bit integer
208
+ class ByteConversion
209
+ attr_reader :unpack_format
210
+
211
+ def initialize(kind = :signed)
212
+ @unpack_format = kind == :unsigned ? 'C' : 'c'
213
+ end
100
214
 
101
215
  def call(value, item_type)
102
- case value
103
- when Integer
104
- check_value_bounds!(value, item_type)
105
- sprintf(integer_formatter.call(value), value)
106
- else
107
- formatted_value = LexicalStringConversion.validate(value, item_type, validation_pattern)
108
- extract_and_check_value_bounds!(formatted_value, item_type)
109
- formatted_value
110
- end
216
+ raise Errors::RubyValueOutOfBounds.new(value, item_type) if value.bytesize != 1
217
+ value = value.to_s.force_encoding(Encoding::ASCII_8BIT)
218
+ value.unpack(unpack_format).first.to_s
111
219
  end
112
220
  end
113
221
 
222
+ # Pattern fragments that can be combined to help create the lexical space
223
+ # patterns in {Patterns}
114
224
  module PatternFragments
225
+ # The time part of the XSD Duration format allows T0H1M1S, T1H1M, T1M1S, T1H1S, T1H, T1M, T1S
115
226
  TIME_DURATION = /(?:T
116
227
  (?:
117
- # The time part of the format allows T0H1M1S, T1H1M, T1M1S, T1H1S, T1H, T1M, T1S
118
228
  [0-9]+[HM]|
119
229
  [0-9]+(?:\.[0-9]+)?S|
120
230
  [0-9]+H[0-9]+M|
@@ -123,43 +233,78 @@ module Saxon
123
233
  [0-9]+H[0-9]+M[0-9]+(?:\.[0-9]+)?S
124
234
  )
125
235
  )?/x
236
+ # XSD Date
126
237
  DATE = /-?[0-9]{4}-[0-9]{2}-[0-9]{2}/
238
+ # XSD DateTime Time
127
239
  TIME = /[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?/
240
+ # XSD DateTime Timezone
128
241
  TIME_ZONE = /(?:[\-+][0-9]{2}:[0-9]{2}|Z)?/
242
+ # Valid first characers in an NCName
129
243
  NCNAME_START_CHAR = '[A-Z]|_|[a-z]|[\u{C0}-\u{D6}]|[\u{D8}-\u{F6}]|[\u{F8}-\u{2FF}]|[\u{370}-\u{37D}]|[\u{37F}-\u{1FFF}]|[\u{200C}-\u{200D}]|[\u{2070}-\u{218F}]|[\u{2C00}-\u{2FEF}]|[\u{3001}-\u{D7FF}]|[\u{F900}-\u{FDCF}]|[\u{FDF0}-\u{FFFD}]|[\u{10000}-\u{EFFFF}]'
244
+ # Valid first characters in an XML Name
130
245
  NAME_START_CHAR = ":|" + NCNAME_START_CHAR
246
+ # Valid characters within an NCName
131
247
  NCNAME_CHAR = NCNAME_START_CHAR + '|-|\.|[0-9]|\u{B7}|[\u{0300}-\u{036F}]|[\u{203F}-\u{2040}]'
248
+ # Valid characters within an XML Name
132
249
  NAME_CHAR = ":|" + NCNAME_CHAR
133
250
  end
134
251
 
252
+ # A collection of lexical space patterns for XDM types
135
253
  module Patterns
254
+ # Construct a Regexp from an array of patterns
255
+ # @param patterns [Array<String>]
256
+ # @return [Regexp]
136
257
  def self.build(*patterns)
137
258
  Regexp.new((['\A'] + patterns.map(&:to_s) + ['\z']).join(''))
138
259
  end
260
+ # @see https://www.w3.org/TR/xmlschema-2/#date-lexical-representation
139
261
  DATE = build(PatternFragments::DATE, PatternFragments::TIME_ZONE)
262
+ # @see https://www.w3.org/TR/xmlschema-2/#dateTime-lexical-representation
140
263
  DATE_TIME = build(PatternFragments::DATE, 'T', PatternFragments::TIME, PatternFragments::TIME_ZONE)
264
+ # @see https://www.w3.org/TR/xmlschema-2/#time-lexical-repr
141
265
  TIME = build(PatternFragments::TIME, PatternFragments::TIME_ZONE)
266
+ # @see https://www.w3.org/TR/xmlschema-2/#duration-lexical-repr
142
267
  DURATION = build(/-?P(?!\z)(?:[0-9]+Y)?(?:[0-9]+M)?(?:[0-9]+D)?/, PatternFragments::TIME_DURATION)
268
+ # @see https://www.w3.org/TR/xmlschema-2/#duration-lexical-repr
143
269
  DAY_TIME_DURATION = build(/-?P(?!\z)(?:[0-9]+D)?/, PatternFragments::TIME_DURATION)
270
+ # @see https://www.w3.org/TR/xmlschema-2/#duration-lexical-repr
144
271
  YEAR_MONTH_DURATION = /\A-?P(?!\z)(?:[0-9]+Y)?(?:[0-9]+M)?\z/
272
+ # @see https://www.w3.org/TR/xmlschema-2/#gDay-lexical-repr
145
273
  G_DAY = build(/---([0-9]{2})/, PatternFragments::TIME_ZONE)
274
+ # @see https://www.w3.org/TR/xmlschema-2/#gMonth-lexical-repr
146
275
  G_MONTH = build(/--([0-9]{2})/, PatternFragments::TIME_ZONE)
276
+ # @see https://www.w3.org/TR/xmlschema-2/#gYear-lexical-repr
147
277
  G_YEAR = build(/(-?[0-9]{4,})/, PatternFragments::TIME_ZONE)
278
+ # @see https://www.w3.org/TR/xmlschema-2/#gYearMonth-lexical-repr
148
279
  G_YEAR_MONTH = build(/-?([0-9]{4,})-([0-9]{2})/, PatternFragments::TIME_ZONE)
280
+ # @see https://www.w3.org/TR/xmlschema-2/#gMonthDay-lexical-repr
149
281
  G_MONTH_DAY = build(/--([0-9]{2})-([0-9]{2})/, PatternFragments::TIME_ZONE)
282
+ # @see https://www.w3.org/TR/xmlschema-2/#integer-lexical-representation
150
283
  INTEGER = /\A[+-]?[0-9]+\z/
284
+ # @see https://www.w3.org/TR/xmlschema-2/#decimal-lexical-representation
151
285
  DECIMAL = /\A[+-]?[0-9]+(?:\.[0-9]+)?\z/
286
+ # @see https://www.w3.org/TR/xmlschema-2/#float-lexical-representation
152
287
  FLOAT = /\A(?:[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][0-9]+)?|-?INF|NaN)\z/
288
+ # @see https://www.w3.org/TR/xmlschema-2/#NCName
153
289
  NCNAME = build("(?:#{PatternFragments::NCNAME_START_CHAR})", "(?:#{PatternFragments::NCNAME_CHAR})*")
290
+ # @see https://www.w3.org/TR/xmlschema-2/#Name
154
291
  NAME = build("(?:#{PatternFragments::NAME_START_CHAR})", "(?:#{PatternFragments::NAME_CHAR})*")
292
+ # @see https://www.w3.org/TR/xmlschema-2/#token
155
293
  TOKEN = /\A[^\u0020\u000A\u000D\u0009]+(?: [^\u0020\u000A\u000D\u0009]+)*\z/
294
+ # @see https://www.w3.org/TR/xmlschema-2/#normalizedString
156
295
  NORMALIZED_STRING = /\A[^\u000A\u000D\u0009]+\z/
296
+ # @see https://www.w3.org/TR/xmlschema-2/#NMTOKEN
157
297
  NMTOKEN = build("(?:#{PatternFragments::NAME_CHAR})+")
298
+ # @see https://www.w3.org/TR/xmlschema-2/#language
158
299
  LANGUAGE = /\A[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*\z/
300
+ # @see https://www.w3.org/TR/xmlschema-2/#base64Binary
159
301
  BASE64_BINARY = /\A(?:(?:[A-Za-z0-9+\/] ?){4})*(?:(?:[A-Za-z0-9+\/] ?){3}[A-Za-z0-9+\/]|(?:[A-Za-z0-9+\/] ?){2}[AEIMQUYcgkosw048] ?=|[A-Za-z0-9+\/] ?[AQgw] ?= ?=)?\z/
160
302
  end
161
303
 
304
+ # Convertors from Ruby values to lexical string representations for a
305
+ # particular XDM type
162
306
  module Convertors
307
+ # @see https://www.w3.org/TR/xmlschema-2/#anyURI
163
308
  ANY_URI = ->(value, item_type) {
164
309
  uri_classes = [URI::Generic]
165
310
  case value
@@ -173,17 +318,17 @@ module Saxon
173
318
  end
174
319
  end
175
320
  }
321
+ # @see https://www.w3.org/TR/xmlschema-2/#base64Binary
176
322
  BASE64_BINARY = ->(value, item_type) {
177
323
  Base64.strict_encode64(value.to_s.force_encoding(Encoding::ASCII_8BIT))
178
324
  }
325
+ # @see https://www.w3.org/TR/xmlschema-2/#boolean
179
326
  BOOLEAN = ->(value, item_type) {
180
327
  value ? 'true' : 'false'
181
328
  }
182
- BYTE = ->(value, item_type) {
183
- raise Errors::RubyValueOutOfBounds.new(value, item_type) if value.bytesize != 1
184
- value = value.to_s.force_encoding(Encoding::ASCII_8BIT)
185
- value.unpack('c').first.to_s
186
- }
329
+ # @see https://www.w3.org/TR/xmlschema-2/#byte
330
+ BYTE = ByteConversion.new
331
+ # @see https://www.w3.org/TR/xmlschema-2/#date
187
332
  DATE = ->(value, item_type) {
188
333
  if value.respond_to?(:strftime)
189
334
  value.strftime('%F')
@@ -191,6 +336,7 @@ module Saxon
191
336
  LexicalStringConversion.validate(value, item_type, Patterns::DATE)
192
337
  end
193
338
  }
339
+ # @see https://www.w3.org/TR/xmlschema-2/#dateTime
194
340
  DATE_TIME = ->(value, item_type) {
195
341
  if value.respond_to?(:strftime)
196
342
  value.strftime('%FT%T%:z')
@@ -198,25 +344,15 @@ module Saxon
198
344
  LexicalStringConversion.validate(value, item_type, Patterns::DATE_TIME)
199
345
  end
200
346
  }
347
+ # @see https://www.w3.org/TR/xmlschema-2/#time
201
348
  TIME = ->(value, item_type) {
202
349
  LexicalStringConversion.validate(value, item_type, Patterns::TIME)
203
350
  }
351
+ # @see https://www.w3.org/TR/xmlschema-2/#dateTime
204
352
  DATE_TIME_STAMP = DATE_TIME
205
- DAY_TIME_DURATION = ->(value, item_type) {
206
- case value
207
- when Integer
208
- sign = value.negative? ? '-' : ''
209
- "#{sign}PT#{value.abs}S"
210
- when BigDecimal
211
- sign = value.negative? ? '-' : ''
212
- "#{sign}PT#{value.abs.to_s('F')}S"
213
- when Numeric
214
- sign = value.negative? ? '-' : ''
215
- sprintf("%sPT%0.9fS", sign, value.abs)
216
- else
217
- LexicalStringConversion.validate(value, item_type, Patterns::DAY_TIME_DURATION)
218
- end
219
- }
353
+ # @see https://www.w3.org/TR/xmlschema-2/#duration
354
+ DAY_TIME_DURATION = DurationConversion.new(Patterns::DAY_TIME_DURATION)
355
+ # @see https://www.w3.org/TR/xmlschema-2/#decimal
220
356
  DECIMAL = ->(value, item_type) {
221
357
  case value
222
358
  when ::Integer
@@ -229,33 +365,25 @@ module Saxon
229
365
  LexicalStringConversion.validate(value, item_type, Patterns::DECIMAL)
230
366
  end
231
367
  }
368
+ # @see https://www.w3.org/TR/xmlschema-2/#double
232
369
  DOUBLE = FloatConversion.new(:single)
233
- DURATION = ->(value, item_type) {
234
- case value
235
- when Integer
236
- sign = value.negative? ? '-' : ''
237
- "#{sign}PT#{value.abs}S"
238
- when BigDecimal
239
- sign = value.negative? ? '-' : ''
240
- "#{sign}PT#{value.abs.to_s('F')}S"
241
- when Numeric
242
- sign = value.negative? ? '-' : ''
243
- sprintf("%sPT%0.9fS", sign, value.abs)
244
- else
245
- LexicalStringConversion.validate(value, item_type, Patterns::DURATION)
246
- end
247
- }
370
+ # @see https://www.w3.org/TR/xmlschema-2/#duration
371
+ DURATION = DurationConversion.new(Patterns::DURATION)
372
+ # @see https://www.w3.org/TR/xmlschema-2/#float
248
373
  FLOAT = FloatConversion.new
374
+ # @see https://www.w3.org/TR/xmlschema-2/#gDay
249
375
  G_DAY = GDateConversion.new({
250
376
  bounds: 1..31,
251
377
  validation_pattern: Patterns::G_DAY,
252
378
  integer_formatter: ->(value) { '---%02d' }
253
379
  })
380
+ # @see https://www.w3.org/TR/xmlschema-2/#gMonth
254
381
  G_MONTH = GDateConversion.new({
255
382
  bounds: 1..12,
256
383
  validation_pattern: Patterns::G_MONTH,
257
384
  integer_formatter: ->(value) { '--%02d' }
258
385
  })
386
+ # @see https://www.w3.org/TR/xmlschema-2/#gMonthDay
259
387
  G_MONTH_DAY = ->(value, item_type) {
260
388
  month_days = {
261
389
  1 => 31,
@@ -278,6 +406,7 @@ module Saxon
278
406
  raise Errors::RubyValueOutOfBounds.new(value, item_type) if day > month_days[month]
279
407
  formatted_value
280
408
  }
409
+ # @see https://www.w3.org/TR/xmlschema-2/#gYear
281
410
  G_YEAR = GDateConversion.new({
282
411
  bounds: ->(value) { value != 0 },
283
412
  validation_pattern: Patterns::G_YEAR,
@@ -285,6 +414,7 @@ module Saxon
285
414
  value.negative? ? '%05d' : '%04d'
286
415
  }
287
416
  })
417
+ # @see https://www.w3.org/TR/xmlschema-2/#gYearMonth
288
418
  G_YEAR_MONTH = ->(value, item_type) {
289
419
  formatted_value = LexicalStringConversion.validate(value, item_type, Patterns::G_YEAR_MONTH)
290
420
  year, month = Patterns::G_YEAR_MONTH.match(formatted_value).captures.take(2).map { |i|
@@ -295,52 +425,74 @@ module Saxon
295
425
  end
296
426
  value
297
427
  }
428
+ # @see https://www.w3.org/TR/xmlschema-2/#hexBinary
298
429
  HEX_BINARY = ->(value, item_type) {
299
430
  value.to_s.force_encoding(Encoding::ASCII_8BIT).each_byte.map { |b| b.to_s(16) }.join
300
431
  }
432
+ # @see https://www.w3.org/TR/xmlschema-2/#int
301
433
  INT = IntegerConversion.new(-2147483648, 2147483647)
434
+ # @see https://www.w3.org/TR/xmlschema-2/#integer
302
435
  INTEGER = IntegerConversion.new(nil, nil)
436
+ # @see https://www.w3.org/TR/xmlschema-2/#language
303
437
  LANGUAGE = ->(value, item_type) {
304
438
  LexicalStringConversion.validate(value, item_type, Patterns::LANGUAGE)
305
439
  }
440
+ # @see https://www.w3.org/TR/xmlschema-2/#long
306
441
  LONG = IntegerConversion.new(-9223372036854775808, 9223372036854775807)
442
+ # @see https://www.w3.org/TR/xmlschema-2/#Name
307
443
  NAME = ->(value, item_type) {
308
444
  LexicalStringConversion.validate(value, item_type, Patterns::NAME)
309
445
  }
446
+ # @see https://www.w3.org/TR/xmlschema-2/#ID
447
+ # @see https://www.w3.org/TR/xmlschema-2/#IDREF
448
+ # @see https://www.w3.org/TR/xmlschema-2/#ENTITY
449
+ # @see https://www.w3.org/TR/xmlschema-2/#NCName
310
450
  ID = IDREF = ENTITY = NCNAME = ->(value, item_type) {
311
451
  LexicalStringConversion.validate(value, item_type, Patterns::NCNAME)
312
452
  }
453
+ # @see https://www.w3.org/TR/xmlschema-2/#negativeInteger
313
454
  NEGATIVE_INTEGER = IntegerConversion.new(nil, -1)
455
+ # @see https://www.w3.org/TR/xmlschema-2/#NMTOKEN
314
456
  NMTOKEN = ->(value, item_type) {
315
457
  LexicalStringConversion.validate(value, item_type, Patterns::NMTOKEN)
316
458
  }
459
+ # @see https://www.w3.org/TR/xmlschema-2/#nonNegativeInteger
317
460
  NON_NEGATIVE_INTEGER = IntegerConversion.new(0, nil)
461
+ # @see https://www.w3.org/TR/xmlschema-2/#nonPositiveInteger
318
462
  NON_POSITIVE_INTEGER = IntegerConversion.new(nil, 0)
463
+ # @see https://www.w3.org/TR/xmlschema-2/#normalizedString
319
464
  NORMALIZED_STRING = ->(value, item_type) {
320
465
  LexicalStringConversion.validate(value, item_type, Patterns::NORMALIZED_STRING)
321
466
  }
467
+ # @see https://www.w3.org/TR/xmlschema-2/#positiveInteger
322
468
  POSITIVE_INTEGER = IntegerConversion.new(1, nil)
469
+ # @see https://www.w3.org/TR/xmlschema-2/#short
323
470
  SHORT = IntegerConversion.new(-32768, 32767)
324
471
  # STRING (It's questionable whether anything needs doing here)
472
+ # @see https://www.w3.org/TR/xmlschema-2/#token
325
473
  TOKEN = ->(value, item_type) {
326
474
  LexicalStringConversion.validate(value, item_type, Patterns::TOKEN)
327
475
  }
328
- UNSIGNED_BYTE = ->(value, item_type) {
329
- raise Errors::RubyValueOutOfBounds.new(value, item_type) if value.bytesize != 1
330
- value = value.to_s.force_encoding(Encoding::ASCII_8BIT)
331
- value.unpack('C').first.to_s
332
- }
476
+ # @see https://www.w3.org/TR/xmlschema-2/#unsignedByte
477
+ UNSIGNED_BYTE = ByteConversion.new(:unsigned)
478
+ # @see https://www.w3.org/TR/xmlschema-2/#unsignedInt
333
479
  UNSIGNED_INT = IntegerConversion.new(0, 4294967295)
480
+ # @see https://www.w3.org/TR/xmlschema-2/#unsignedLong
334
481
  UNSIGNED_LONG = IntegerConversion.new(0, 18446744073709551615)
482
+ # @see https://www.w3.org/TR/xmlschema-2/#unsignedShort
335
483
  UNSIGNED_SHORT = IntegerConversion.new(0, 65535)
484
+ # @see https://www.w3.org/TR/xmlschema-2/#duration
336
485
  YEAR_MONTH_DURATION = ->(value, item_type) {
337
486
  LexicalStringConversion.validate(value, item_type, Patterns::YEAR_MONTH_DURATION)
338
487
  }
488
+ # @see https://www.w3.org/TR/xmlschema-2/#QName
489
+ # @see https://www.w3.org/TR/xmlschema-2/#NOTATION
339
490
  QNAME = NOTATION = ->(value, item_type) {
340
491
  raise Errors::UnconvertableNamespaceSensitveItemType
341
492
  }
342
493
  end
343
494
 
495
+ # Conversion process error classes
344
496
  module Errors
345
497
  # Raised during conversion from Ruby value to XDM Type lexical string
346
498
  # when the ruby value does not conform to the Type's string
@@ -352,6 +504,8 @@ module Saxon
352
504
  @value, @item_type = value, item_type
353
505
  end
354
506
 
507
+ # error message includes Ruby value and the XDM type conversion was
508
+ # being attempted to
355
509
  def to_s
356
510
  "Ruby value #{value.inspect} cannot be converted to an XDM #{item_type.type_name.to_s}"
357
511
  end
@@ -368,6 +522,7 @@ module Saxon
368
522
  @value, @item_type = value, item_type
369
523
  end
370
524
 
525
+ # error message includes Ruby value and the XDM type whose bounds it is outside of
371
526
  def to_s
372
527
  "Ruby value #{value.inspect} is outside the allowed bounds of an XDM #{item_type.type_name.to_s}"
373
528
  end