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