unitsml 0.4.7 → 0.6.0

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake.yml +8 -0
  3. data/.github/workflows/release.yml +7 -1
  4. data/.rubocop.yml +18 -0
  5. data/.rubocop_todo.yml +498 -0
  6. data/Gemfile +16 -11
  7. data/Rakefile +5 -3
  8. data/bin/console +4 -3
  9. data/docs/README.adoc +106 -4
  10. data/lib/unitsml/dimension.rb +49 -35
  11. data/lib/unitsml/errors/base_error.rb +8 -0
  12. data/lib/unitsml/errors/plurimath_load_error.rb +1 -1
  13. data/lib/unitsml/errors.rb +8 -0
  14. data/lib/unitsml/extender.rb +21 -16
  15. data/lib/unitsml/fenced.rb +97 -0
  16. data/lib/unitsml/fenced_numeric.rb +13 -0
  17. data/lib/unitsml/formula.rb +46 -33
  18. data/lib/unitsml/intermediate_exp_rules.rb +76 -0
  19. data/lib/unitsml/model/dimension.rb +4 -14
  20. data/lib/unitsml/model/dimension_quantities/quantity.rb +2 -0
  21. data/lib/unitsml/model/dimension_quantities.rb +17 -0
  22. data/lib/unitsml/model/prefix.rb +4 -8
  23. data/lib/unitsml/model/prefixes/name.rb +5 -4
  24. data/lib/unitsml/model/prefixes/symbol.rb +3 -2
  25. data/lib/unitsml/model/prefixes.rb +10 -0
  26. data/lib/unitsml/model/quantities/name.rb +4 -3
  27. data/lib/unitsml/model/quantities.rb +9 -0
  28. data/lib/unitsml/model/quantity.rb +5 -7
  29. data/lib/unitsml/model/unit.rb +4 -9
  30. data/lib/unitsml/model/units/enumerated_root_unit.rb +1 -0
  31. data/lib/unitsml/model/units/name.rb +4 -3
  32. data/lib/unitsml/model/units/root_units.rb +3 -2
  33. data/lib/unitsml/model/units/symbol.rb +2 -1
  34. data/lib/unitsml/model/units/system.rb +4 -3
  35. data/lib/unitsml/model/units.rb +13 -0
  36. data/lib/unitsml/model.rb +15 -0
  37. data/lib/unitsml/namespace.rb +8 -0
  38. data/lib/unitsml/number.rb +79 -0
  39. data/lib/unitsml/parse.rb +30 -38
  40. data/lib/unitsml/parser.rb +27 -13
  41. data/lib/unitsml/prefix.rb +17 -17
  42. data/lib/unitsml/sqrt.rb +3 -3
  43. data/lib/unitsml/transform.rb +143 -50
  44. data/lib/unitsml/unit.rb +67 -37
  45. data/lib/unitsml/unitsdb/dimension.rb +14 -20
  46. data/lib/unitsml/unitsdb/dimension_quantity.rb +2 -6
  47. data/lib/unitsml/unitsdb/dimensions.rb +3 -9
  48. data/lib/unitsml/unitsdb/prefix_reference.rb +23 -0
  49. data/lib/unitsml/unitsdb/prefixes.rb +17 -5
  50. data/lib/unitsml/unitsdb/quantities.rb +4 -4
  51. data/lib/unitsml/unitsdb/unit.rb +21 -0
  52. data/lib/unitsml/unitsdb/units.rb +19 -18
  53. data/lib/unitsml/unitsdb.rb +14 -5
  54. data/lib/unitsml/utility.rb +133 -103
  55. data/lib/unitsml/version.rb +3 -1
  56. data/lib/unitsml.rb +71 -35
  57. data/unitsdb/Gemfile +6 -0
  58. data/unitsdb/LICENSE.md +53 -0
  59. data/unitsdb/README.adoc +1253 -0
  60. data/unitsdb/RELEASE-NOTES.adoc +269 -0
  61. data/unitsdb/dimensions.yaml +1607 -602
  62. data/unitsdb/prefixes.yaml +842 -301
  63. data/unitsdb/quantities.yaml +3706 -2458
  64. data/unitsdb/scales.yaml +97 -0
  65. data/unitsdb/schemas/README.md +159 -0
  66. data/unitsdb/schemas/dimensions-schema.yaml +153 -0
  67. data/unitsdb/schemas/prefixes-schema.yaml +155 -0
  68. data/unitsdb/schemas/quantities-schema.yaml +117 -0
  69. data/unitsdb/schemas/scales-schema.yaml +106 -0
  70. data/unitsdb/schemas/unit_systems-schema.yaml +116 -0
  71. data/unitsdb/schemas/units-schema.yaml +215 -0
  72. data/unitsdb/spec/units_spec.rb +13 -10
  73. data/unitsdb/unit_systems.yaml +77 -15
  74. data/unitsdb/units.yaml +13517 -9974
  75. data/unitsdb/validate_schemas.rb +203 -0
  76. data/unitsml.gemspec +4 -1
  77. metadata +47 -7
  78. data/lib/unitsml/error.rb +0 -8
  79. data/unitsdb/docs/README.adoc +0 -12
  80. data/unitsdb/docs/navigation.adoc +0 -7
data/docs/README.adoc CHANGED
@@ -69,7 +69,7 @@ unit.to_s # => "m"
69
69
  unit.to_xml
70
70
  # =>
71
71
  <Unit xmlns="https://schema.unitsml.org/unitsml/1.0" xml:id="U_NISTu1" dimensionURL="#NISTd1">
72
- <UnitSystem name="SI" type="SI_derived" xml:lang="en-US"/>
72
+ <UnitSystem name="SI" type="SI_derived" xml:lang="en"/>
73
73
  <UnitName xml:lang="en">meter</UnitName>
74
74
  <UnitSymbol type="HTML">m</UnitSymbol>
75
75
  <UnitSymbol type="MathMl">
@@ -102,7 +102,7 @@ unit.to_s # => "um"
102
102
  unit.to_xml
103
103
  # =>
104
104
  <Unit xmlns="https://schema.unitsml.org/unitsml/1.0" xml:id="U_um" dimensionURL="#NISTd1">
105
- <UnitSystem name="SI" type="SI_derived" xml:lang="en-US"/>
105
+ <UnitSystem name="SI" type="SI_derived" xml:lang="en"/>
106
106
  <UnitName xml:lang="en">um</UnitName>
107
107
  <UnitSymbol type="HTML">&micro;m</UnitSymbol>
108
108
  <UnitSymbol type="MathMl"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
@@ -169,6 +169,104 @@ unit.to_unicode # Unicode representation
169
169
  unit.to_xml # XML (in UnitsML) representation
170
170
  ----
171
171
 
172
+ ==== Explicit parenthesis
173
+
174
+ When converting units to formats like MathML or LaTeX, you can specify whether
175
+ to use explicit parentheses for clarity in complex expressions.
176
+
177
+ A pair of parentheses is typically used for two purposes:
178
+
179
+ . For grouping to indicate operator precedence in mathematical expressions. When
180
+ expressing units, most of the time, parentheses are used to indicate operator
181
+ precedence, parentheses are only kept in cases where it indicates precedence.
182
+ When parentheses are not needed for precedence, such as in the expression
183
+ `(m/s)`, the parentheses can be removed whilch simplifies the expression into
184
+ `m/s`.
185
+
186
+ . For indicating that an expression is a compound unit. These parentheses are
187
+ explicit parentheses that cannot be removed without changing the meaning of the
188
+ expression.
189
+
190
+ UnitsML Ruby provides an option `explicit_parenthesis` to control the inclusion
191
+ of parentheses in the output.
192
+
193
+ In an expression:
194
+
195
+ * `(...)` is considered an implicit parenthesis for grouping and operator
196
+ precedence and can be removed without changing the meaning of the expression.
197
+
198
+ * `((...))` is considered an explicit parenthesis, which indicates that the
199
+ parentheses need to be preserved in the output regardless of operator
200
+ precedence.
201
+
202
+ The `explicit_parenthesis` option allows you to control how parentheses are
203
+ considered in the output formats. It can be set to `true` or `false`.
204
+
205
+ Syntax for using `explicit_parenthesis` is as follows:
206
+
207
+ [source,ruby]
208
+ ----
209
+ unit.to_asciimath(explicit_parenthesis: true) # or false
210
+ unit.to_mathml(explicit_parenthesis: true) # or false
211
+ unit.to_latex(explicit_parenthesis: true) # or false
212
+ ----
213
+
214
+ When `explicit_parenthesis` is set to:
215
+
216
+ `true`:: (default) the usage of `(...)` and `((...))` is differentiated as
217
+ implicit and explicit parentheses, respectively.
218
+
219
+ `false`:: all parentheses are treated as grouping parentheses, there is no
220
+ difference between `(...)` and `((...))`.
221
+
222
+
223
+ .By default `explicit_parenthesis` is `true` and double parentheses are considered explicit parentheses
224
+ [example]
225
+ ====
226
+ [source, ruby]
227
+ ----
228
+ Unitsml.parse("((m/s))").to_asciimath
229
+ > "(m/s^-1)" # Only one of the two pairs of parentheses are included in the output
230
+ ----
231
+ ====
232
+
233
+ If an extra pair of parentheses is included, it will be considered as grouping
234
+ parentheses and will not be included in the Unitsml::Formula or the output.
235
+
236
+ .By default `explicit_parenthesis` is `true` and double parentheses are considered explicit parentheses
237
+ [example]
238
+ ====
239
+ [source, ruby]
240
+ ----
241
+ Unitsml.parse("(((m/s)))").to_asciimath
242
+ > "(m/s^-1)" # the third pair of parentheses are ignored as grouping parentheses
243
+ Unitsml.parse("(((((m/s)))))").to_asciimath
244
+ > "((m/s^-1))" # the fifth pair of parentheses are ignored as grouping parentheses
245
+ ----
246
+
247
+ [source,ruby]
248
+ ----
249
+ unit = Unitsml.parse("((m/s))")
250
+ unit.to_asciimath # equivalent to (explicit_parenthesis: true) => "(m/s^-1)"
251
+ unit = Unitsml.parse("((m/s))*((m/s))")
252
+ unit.to_asciimath(explicit_parenthesis: true) # "(m/s^-1)*(m/s^-1)"
253
+ ----
254
+ ====
255
+
256
+ When `explicit_parenthesis` is `false`, all parentheses are treated as grouping parentheses
257
+ and therefore will be reduced to only the necessary parentheses for operator precedence.
258
+
259
+ .When `explicit_parenthesis` is `false`, all parentheses are treated as grouping parentheses
260
+ [example]
261
+ ====
262
+ [source,ruby]
263
+ ----
264
+ unit = Unitsml.parse("(m/s)")
265
+ unit.to_asciimath(explicit_parenthesis: false) # => "m/s"
266
+ unit = Unitsml.parse("((m/s))*((m/s))")
267
+ unit.to_asciimath(explicit_parenthesis: false) # "m/s^-1*m/s^-1"
268
+ ----
269
+ ====
172
270
 
173
271
 
174
272
  == Installation
@@ -196,7 +294,9 @@ $ gem install unitsml
196
294
 
197
295
  == Usage
198
296
 
199
- *UnitsML* Ruby provides functionality to represent and convert units between different formats including *MathML*, *LaTeX*, *AsciiMath*, *HTML*, *Unicode*, and *XML*(*Unitsml* schema based elements).
297
+ *UnitsML* Ruby provides functionality to represent and convert units between
298
+ different formats including *MathML*, *LaTeX*, *AsciiMath*, *HTML*, *Unicode*,
299
+ and *XML*(*Unitsml* schema based elements).
200
300
 
201
301
  === Basic Usage
202
302
 
@@ -275,7 +375,9 @@ repository.
275
375
 
276
376
  == Development
277
377
 
278
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
378
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
379
+ `bundle exec spec` to run the tests. You can also run `bin/console` for an
380
+ interactive prompt that will allow you to experiment.
279
381
 
280
382
  === Submodules
281
383
 
@@ -9,63 +9,59 @@ module Unitsml
9
9
  @power_numerator = power_numerator
10
10
  end
11
11
 
12
- def ==(object)
13
- self.class == object.class &&
14
- dimension_name == object&.dimension_name &&
15
- power_numerator == object&.power_numerator
12
+ def ==(other)
13
+ self.class == other.class &&
14
+ dimension_name == other&.dimension_name &&
15
+ power_numerator == other&.power_numerator
16
16
  end
17
17
 
18
18
  def dim_instance
19
- @dim ||= Unitsdb.dimensions.find_parsables_by_id(dimension_name)
19
+ @dim_instance ||= Unitsdb.dimensions.find_parsables_by_id(dimension_name)
20
20
  end
21
21
 
22
22
  def dim_symbols
23
- dim_instance.send(@dim.processed_keys.last).dim_symbols.first
24
- end
25
-
26
- def to_mathml(_)
27
- # MathML key's value in unitsdb/dimensions.yaml file includes mi tags only.
28
- value = ::Mml::Mi.from_xml(dim_symbols.mathml)
29
- method_name = power_numerator ? :msup : :mi
30
- if power_numerator
31
- value = ::Mml::Msup.new(
32
- mrow_value: [
33
- ::Mml::Mrow.new(mi_value: [value]),
34
- ::Mml::Mrow.new(
35
- mn_value: [::Mml::Mn.new(value: power_numerator)],
36
- ),
37
- ]
38
- )
39
- end
23
+ dim_instance.send(dim_instance.processed_keys.last).symbols.first
24
+ end
25
+
26
+ def to_mathml(options)
27
+ # MathML key's value in unitsdb/dimensions.yaml
28
+ # file includes mi tags only.
29
+ value = ::Mml::V4::Mi.from_xml(dim_symbols.mathml)
30
+ method_name = if power_numerator
31
+ value = msup_tag(value, options)
32
+ :msup
33
+ else
34
+ :mi
35
+ end
40
36
  { method_name: method_name, value: value }
41
37
  end
42
38
 
43
- def to_latex(_)
44
- power_numerator_generic_code(:latex)
39
+ def to_latex(options)
40
+ power_numerator_generic_code(:latex, options)
45
41
  end
46
42
 
47
- def to_asciimath(_)
48
- power_numerator_generic_code(:ascii)
43
+ def to_asciimath(options)
44
+ power_numerator_generic_code(:ascii, options)
49
45
  end
50
46
 
51
- def to_unicode(_)
52
- power_numerator_generic_code(:unicode)
47
+ def to_unicode(options)
48
+ power_numerator_generic_code(:unicode, options)
53
49
  end
54
50
 
55
- def to_html(_)
51
+ def to_html(options)
56
52
  value = dim_symbols.html
57
- value = "#{value}<sup>#{power_numerator}</sup>" if power_numerator
53
+ value = "#{value}#{html_numerator_conversion(options)}" if power_numerator
58
54
  value
59
55
  end
60
56
 
61
57
  def generate_id
62
- "#{dimension_name.split('_').last}#{power_numerator}"
58
+ "#{dimension_name.split('_').last}#{power_numerator&.raw_value}"
63
59
  end
64
60
 
65
61
  def to_xml(_)
66
62
  attributes = {
67
63
  symbol: dim_instance.processed_symbol,
68
- power_numerator: power_numerator || 1,
64
+ power_numerator: power_numerator&.raw_value || 1
69
65
  }
70
66
  Model::DimensionQuantities.const_get(modelize(element_name)).new(attributes)
71
67
  end
@@ -75,20 +71,38 @@ module Unitsml
75
71
  end
76
72
 
77
73
  def modelize(value)
78
- value&.split("_")&.map(&:capitalize)&.join
74
+ value&.split('_')&.map(&:capitalize)&.join
79
75
  end
80
76
 
81
77
  private
82
78
 
83
- def power_numerator_generic_code(method_name)
79
+ def html_numerator_conversion(options)
80
+ "<sup>#{power_numerator.to_html(options)}</sup>"
81
+ end
82
+
83
+ def power_numerator_generic_code(method_name, options)
84
84
  value = dim_symbols.public_send(method_name)
85
85
  return value unless power_numerator
86
86
 
87
- "#{value}^#{power_numerator}"
87
+ method_name = :asciimath if method_name == :ascii
88
+ "#{value}^#{power_numerator&.public_send(:"to_#{method_name}", options)}"
88
89
  end
89
90
 
90
91
  def element_name
91
92
  dim_instance.processed_keys.first
92
93
  end
94
+
95
+ def msup_tag(value, options)
96
+ mathml = power_numerator.to_mathml(options)
97
+ msup = ::Mml::V4::Msup.new(
98
+ mrow_value: [::Mml::V4::Mrow.new(mi_value: [value])]
99
+ )
100
+ [mathml].flatten.each do |record|
101
+ record_values = msup.public_send("#{record[:method_name]}_value") || []
102
+ record_values += [record[:value]]
103
+ msup.public_send("#{record[:method_name]}_value=", record_values)
104
+ end
105
+ msup
106
+ end
93
107
  end
94
108
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ module Errors
5
+ class BaseError < StandardError
6
+ end
7
+ end
8
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Unitsml
4
4
  module Errors
5
- class PlurimathLoadError < Unitsml::Error
5
+ class PlurimathLoadError < Unitsml::Errors::BaseError
6
6
  def to_s
7
7
  <<~MESSAGE
8
8
  [unitsml] Error: Failed to require 'plurimath'.
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ module Errors
5
+ autoload :BaseError, 'unitsml/errors/base_error'
6
+ autoload :PlurimathLoadError, 'unitsml/errors/plurimath_load_error'
7
+ end
8
+ end
@@ -8,22 +8,22 @@ module Unitsml
8
8
  @symbol = symbol
9
9
  end
10
10
 
11
- def ==(object)
12
- self.class == object.class &&
13
- symbol == object&.symbol
11
+ def ==(other)
12
+ self.class == other.class &&
13
+ symbol == other&.symbol
14
14
  end
15
15
 
16
16
  def to_mathml(options)
17
- rspace = "thickmathspace" if options[:multiplier] == :space
18
- extender = multiplier(options[:multiplier] || "", unicode: true)
17
+ rspace = 'thickmathspace' if options[:multiplier] == :space
18
+ extender = multiplier(options[:multiplier] || '', unicode: true)
19
19
  {
20
20
  method_name: :mo,
21
- value: ::Mml::Mo.new(value: extender, rspace: rspace),
21
+ value: ::Mml::V4::Mo.new(value: extender, rspace: rspace)
22
22
  }
23
23
  end
24
24
 
25
25
  def to_latex(options)
26
- multiplier(options[:multiplier] || "/")
26
+ multiplier(options[:multiplier] || '/')
27
27
  end
28
28
 
29
29
  def to_asciimath(options)
@@ -31,12 +31,11 @@ module Unitsml
31
31
  end
32
32
 
33
33
  def to_html(options)
34
- multiplier(options[:multiplier] || "", unicode: true, html: true)
34
+ multiplier(options[:multiplier] || '', unicode: true, html: true)
35
35
  end
36
36
 
37
37
  def to_unicode(options)
38
- extender = options[:multiplier] ||
39
- symbol == "*" ? "·" : symbol
38
+ extender = options[:multiplier] || unicode_extender
40
39
  multiplier(extender)
41
40
  end
42
41
 
@@ -45,16 +44,22 @@ module Unitsml
45
44
  def multiplier(extender, unicode: false, html: false)
46
45
  case extender
47
46
  when :space
48
- if html
49
- "&#xa0;"
50
- else
51
- unicode ? "&#x2062;" : " "
52
- end
47
+ space_extender(html, unicode)
53
48
  when :nospace
54
- unicode ? "&#x2062;" : ""
49
+ unicode ? '&#x2062;' : ''
55
50
  else
56
51
  unicode ? Utility.string_to_html_entity(extender) : extender
57
52
  end
58
53
  end
54
+
55
+ def space_extender(html, unicode)
56
+ return '&#xa0;' if html
57
+
58
+ unicode ? '&#x2062;' : ' '
59
+ end
60
+
61
+ def unicode_extender
62
+ symbol == '*' ? '·' : symbol
63
+ end
59
64
  end
60
65
  end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ class Fenced
5
+ include FencedNumeric
6
+
7
+ attr_reader :open_paren, :value, :close_paren
8
+
9
+ def initialize(open_paren, value, close_paren)
10
+ @open_paren = open_paren
11
+ @value = value
12
+ @close_paren = close_paren
13
+ end
14
+
15
+ def ==(other)
16
+ self.class == other.class &&
17
+ open_paren == other&.open_paren &&
18
+ value == other&.value &&
19
+ close_paren == other&.close_paren
20
+ end
21
+
22
+ def to_asciimath(options = {})
23
+ fenced_conversion_for(lang: :asciimath, options: options)
24
+ end
25
+
26
+ def to_latex(options = {})
27
+ fenced_conversion_for(lang: :latex, options: options)
28
+ end
29
+
30
+ def to_mathml(options = {})
31
+ mathml = value.to_mathml(options)
32
+ return mathml unless options[:explicit_parenthesis]
33
+
34
+ fenced = ::Mml::V4::Mrow.new(mo_value: [::Mml::V4::Mo.new(value: open_paren)])
35
+ fenced.ordered = true
36
+ fenced.element_order ||= [xml_order_element('mo')]
37
+ [mathml].flatten.each { |record| add_math_element(fenced, record) }
38
+ fenced.mo_value << ::Mml::V4::Mo.new(value: close_paren)
39
+ fenced.element_order << xml_order_element('mo')
40
+ { method_name: :mrow, value: fenced }
41
+ end
42
+
43
+ def negative?
44
+ value.negative?
45
+ end
46
+
47
+ def update_negative_sign
48
+ value.update_negative_sign
49
+ end
50
+
51
+ def float_to_display
52
+ value.float_to_display
53
+ end
54
+
55
+ def raw_value
56
+ value.raw_value
57
+ end
58
+
59
+ def to_html(options = {})
60
+ fenced_conversion_for(lang: :html, options: options)
61
+ end
62
+
63
+ def to_unicode(options = {})
64
+ fenced_conversion_for(lang: :unicode, options: options)
65
+ end
66
+
67
+ def dimensions_extraction
68
+ case value
69
+ when Dimension
70
+ value
71
+ when Formula, Fenced
72
+ value.dimensions_extraction
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def add_math_element(instance, child_hash)
79
+ method_name = child_hash[:method_name]
80
+ method_value = instance.public_send(:"#{method_name}_value") || []
81
+ method_value += Array(child_hash[:value])
82
+ instance.public_send(:"#{method_name}_value=", method_value)
83
+ instance.element_order << xml_order_element(method_name.to_s)
84
+ end
85
+
86
+ def xml_order_element(tag_name)
87
+ Lutaml::Xml::Element.new('Element', tag_name)
88
+ end
89
+
90
+ def fenced_conversion_for(lang:, options:)
91
+ lang_value = value.send(:"to_#{lang}", options)
92
+ return lang_value unless options[:explicit_parenthesis]
93
+
94
+ "#{open_paren}#{lang_value}#{close_paren}"
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ module FencedNumeric
5
+ def to_i
6
+ value.to_i
7
+ end
8
+
9
+ def to_f
10
+ value.to_f
11
+ end
12
+ end
13
+ end
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mml"
4
- require "htmlentities"
5
- require "unitsml/utility"
3
+ require 'mml'
4
+ require 'htmlentities'
6
5
 
7
6
  module Unitsml
8
7
  class Formula
@@ -20,25 +19,25 @@ module Unitsml
20
19
  @norm_text = norm_text
21
20
  end
22
21
 
23
- def ==(object)
24
- self.class == object.class &&
25
- value == object&.value &&
26
- explicit_value == object&.explicit_value &&
27
- root == object.root
22
+ def ==(other)
23
+ self.class == other.class &&
24
+ value == other&.value &&
25
+ explicit_value == other&.explicit_value &&
26
+ root == other.root
28
27
  end
29
28
 
30
29
  def to_mathml(options = {})
31
30
  if root
32
- update_options(options)
31
+ options = update_options(options)
33
32
  nullify_mml_models if plurimath_available?
34
- math = ::Mml::MathWithNamespace.new(display: "block")
33
+ math = ::Mml::V4::Math.new(display: 'block')
35
34
  math.ordered = true
36
35
  math.element_order ||= []
37
36
  value.each { |instance| process_value(math, instance.to_mathml(options)) }
38
-
39
- generated_math = math.to_xml.gsub(/&amp;(.*?)(?=<\/)/, '&\1')
37
+ generated_math = math.to_xml.gsub(%r{&amp;(.*?)(?=</)}, '&\1')
40
38
  reset_mml_models if plurimath_available?
41
- generated_math
39
+
40
+ generated_math.force_encoding('UTF-8')
42
41
  else
43
42
  value.map { |obj| obj.to_mathml(options) }
44
43
  end
@@ -61,7 +60,7 @@ module Unitsml
61
60
  end
62
61
 
63
62
  def to_xml(options = {})
64
- update_options(options)
63
+ options = update_options(options)
65
64
  if (dimensions_array = extract_dimensions(value)).any?
66
65
  dimensions(sort_dims(dimensions_array), options)
67
66
  elsif @orig_text.match(/-$/)
@@ -79,18 +78,29 @@ module Unitsml
79
78
  Plurimath::Math.parse(to_mathml(options), :mathml)
80
79
  end
81
80
 
81
+ def dimensions_extraction
82
+ extract_dimensions(value)
83
+ end
84
+
82
85
  private
83
86
 
84
87
  def extract_dimensions(formula)
85
88
  formula.each_with_object([]) do |term, dimensions|
86
- if term.is_a?(Dimension)
89
+ case term
90
+ when Dimension
87
91
  dimensions << term
88
- elsif term.is_a?(Sqrt) && term.value.is_a?(Dimension)
89
- sqrt_term = term.value.dup
90
- sqrt_term.power_numerator = "0.5"
91
- dimensions << sqrt_term
92
- elsif term.is_a?(Formula)
92
+ when Sqrt
93
+ if term.value.is_a?(Dimension)
94
+ sqrt_term = term.value.dup
95
+ sqrt_term.power_numerator = Number.new('0.5')
96
+ dimensions << sqrt_term
97
+ elsif term.value.is_a?(Fenced)
98
+ dimensions.concat(Array(term.value.dimensions_extraction))
99
+ end
100
+ when Formula
93
101
  dimensions.concat(extract_dimensions(term.value))
102
+ when Fenced
103
+ dimensions.concat(Array(term.dimensions_extraction))
94
104
  end
95
105
  end
96
106
  end
@@ -106,26 +116,28 @@ module Unitsml
106
116
  next unless term.value.is_a?(Unit)
107
117
 
108
118
  units_arr << term.value
119
+ when Fenced
120
+ units_arr.concat(extract_units([term.value]))
109
121
  end
110
122
  end
111
123
  end
112
124
 
113
125
  def units(options)
114
126
  all_units = extract_units(value)
115
- norm_text = Utility.postprocess_normtext(all_units)
127
+ norm_text = all_units.map(&:xml_postprocess_name).join('*')
116
128
  dims = Utility.units2dimensions(extract_units(value))
117
129
  [
118
130
  Utility.unit(all_units, self, dims, norm_text, explicit_value&.dig(:name), options),
119
131
  Utility.prefixes(all_units, options),
120
132
  *unique_dimensions(dims, norm_text),
121
- Utility.quantity(norm_text, explicit_value&.dig(:quantity)),
133
+ Utility.quantity(norm_text, explicit_value&.dig(:quantity))
122
134
  ].join
123
135
  end
124
136
 
125
137
  def unique_dimensions(dims, norm_text)
126
138
  [
127
139
  Utility.dimension(norm_text),
128
- Utility.dimension_components(dims),
140
+ Utility.dimension_components(dims)
129
141
  ].uniq
130
142
  end
131
143
 
@@ -133,31 +145,31 @@ module Unitsml
133
145
  dim_id = dims.map(&:generate_id).join
134
146
  attributes = { id: "D_#{dim_id}" }
135
147
  dims.each { |dim| attributes.merge!(dim.xml_instances_hash(options)) }
136
- Model::Dimension.new(attributes).to_xml
148
+ Model::Dimension.new(attributes).to_xml.force_encoding('UTF-8')
137
149
  end
138
150
 
139
151
  def sort_dims(values)
140
- dims_hash = Utility::Dim2D
152
+ dims_hash = Utility::DIM2D
141
153
  values.sort do |first, second|
142
154
  dims_hash.dig(first.dimension_name, :order) <=> dims_hash.dig(second.dimension_name, :order)
143
155
  end
144
156
  end
145
157
 
146
158
  def prefixes(options)
147
- norm_text = @norm_text&.split("-")&.first
148
- prefix_object = Unit.new("", prefix: Prefix.new(norm_text))
159
+ norm_text = @norm_text&.split('-')&.first
160
+ prefix_object = Unit.new('', prefix: Prefix.new(norm_text))
149
161
  [
150
162
  Utility.prefixes([prefix_object], options),
151
163
  Utility.dimension(norm_text),
152
- Utility.quantity(norm_text, explicit_value&.dig(:quantity)),
164
+ Utility.quantity(norm_text, explicit_value&.dig(:quantity))
153
165
  ].join
154
166
  end
155
167
 
156
168
  def ensure_plurimath_defined!
157
169
  return if plurimath_available?
158
170
 
159
- require "plurimath"
160
- rescue LoadError => e
171
+ require 'plurimath'
172
+ rescue LoadError
161
173
  raise Errors::PlurimathLoadError
162
174
  end
163
175
 
@@ -166,7 +178,7 @@ module Unitsml
166
178
  method_value = math_instance.public_send(:"#{method_name}_value") || []
167
179
  method_value += Array(child_hash[:value])
168
180
  math_instance.public_send(:"#{method_name}_value=", method_value)
169
- math_instance.element_order << Lutaml::Model::Xml::Element.new("Element", method_name.to_s)
181
+ math_instance.element_order << Lutaml::Xml::Element.new('Element', method_name.to_s)
170
182
  end
171
183
 
172
184
  def plurimath_available?
@@ -180,7 +192,7 @@ module Unitsml
180
192
  end
181
193
 
182
194
  def reset_mml_models
183
- ::Mml::Configuration.custom_models = Plurimath::Mathml::Parser::CONFIGURATION
195
+ ::Mml::V4::Configuration.custom_models = Plurimath::Mathml::Parser::CONFIGURATION
184
196
  end
185
197
 
186
198
  def process_value(math, mathml_instances)
@@ -196,7 +208,8 @@ module Unitsml
196
208
  return options unless root
197
209
 
198
210
  multiplier = options[:multiplier] || explicit_value&.dig(:multiplier)
199
- options.merge(multiplier: multiplier).compact
211
+ explicit_parenthesis = options.key?(:explicit_parenthesis) ? options[:explicit_parenthesis] : true
212
+ options.merge(multiplier: multiplier, explicit_parenthesis: explicit_parenthesis).compact
200
213
  end
201
214
  end
202
215
  end