unitsml 0.6.5 → 0.6.7

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.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "htmlentities"
4
+
3
5
  module Unitsml
4
6
  module Utility
5
7
  # Unit to dimension
@@ -109,8 +111,7 @@ module Unitsml
109
111
  prefix = if k.prefix_reference.nil?
110
112
  u.prefix
111
113
  else
112
- combine_prefixes(prefix_object(k.prefix_reference),
113
- u.prefix)
114
+ combine_prefixes(k.prefix_reference, u.prefix)
114
115
  end
115
116
  unit_name = Unitsdb.units.find_by_id(k.unit_reference.id).symbols.first.id
116
117
  exponent = (k.power&.to_i || 1) * (u.power_numerator&.to_f || 1)
@@ -127,8 +128,7 @@ module Unitsml
127
128
  else
128
129
  m[-1][:unit]&.power_numerator = Number.new(numerator_value(k, m))
129
130
  m[-1] = {
130
- prefix: combine_prefixes(prefix_object(m[-1][:prefix]),
131
- prefix_object(k[:prefix])),
131
+ prefix: combine_prefixes(m[-1][:prefix], k[:prefix]),
132
132
  unit: m[-1][:unit],
133
133
  }
134
134
  end
@@ -144,72 +144,23 @@ module Unitsml
144
144
  object_hash[:unit]&.power_numerator&.to_f || 1
145
145
  end
146
146
 
147
- def prefix_object(prefix)
148
- return nil if prefix.nil?
149
- return prefix if prefix_like?(prefix)
150
-
151
- if prefix.is_a?(String)
152
- return nil unless Unitsdb.prefixes_array.any?(prefix)
153
-
154
- return Prefix.new(prefix)
155
- end
156
-
157
- return Unitsdb.prefixes.find_by_id(prefix.id) if prefix.respond_to?(:id)
158
-
159
- prefix
160
- end
161
-
162
147
  def combine_prefixes(p1, p2)
163
- p1 = prefix_object(p1)
164
- p2 = prefix_object(p2)
165
- return nil if p1.nil? && p2.nil?
166
- return prefix_symbolid(p1) if p2.nil?
167
- return prefix_symbolid(p2) if p1.nil?
168
- return UNKNOWN if prefix_base(p1) != prefix_base(p2)
148
+ a1 = PrefixAdapter.wrap(p1)
149
+ a2 = PrefixAdapter.wrap(p2)
150
+ return nil if a1.null? && a2.null?
151
+ return a1.symbolid if a2.null?
152
+ return a2.symbolid if a1.null?
153
+ return UNKNOWN if a1.base != a2.base
169
154
 
170
155
  Unitsdb.prefixes_array.each do |prefix_name|
171
- p = prefix_object(prefix_name)
172
- return p if prefix_base(p) == prefix_base(p1) &&
173
- prefix_power(p) == prefix_power(p1) + prefix_power(p2)
156
+ candidate = PrefixAdapter.from_name(prefix_name)
157
+ return candidate.raw if candidate.base == a1.base &&
158
+ candidate.power == a1.power + a2.power
174
159
  end
175
160
 
176
161
  UNKNOWN
177
162
  end
178
163
 
179
- def prefix_like?(prefix)
180
- prefix.respond_to?(:base) && prefix.respond_to?(:power) &&
181
- (prefix.respond_to?(:symbolid) || prefix.respond_to?(:symbols))
182
- end
183
-
184
- def prefix_symbolid(prefix)
185
- return prefix.symbolid if prefix.respond_to?(:symbolid)
186
-
187
- prefix_record = resolved_prefix(prefix)
188
- return unless prefix_record
189
-
190
- prefix_record.symbols&.first&.ascii
191
- end
192
-
193
- def prefix_base(prefix)
194
- return prefix.base if prefix.respond_to?(:base)
195
-
196
- resolved_prefix(prefix)&.base
197
- end
198
-
199
- def prefix_power(prefix)
200
- return prefix.power if prefix.respond_to?(:power)
201
-
202
- resolved_prefix(prefix)&.power
203
- end
204
-
205
- def resolved_prefix(prefix)
206
- return prefix if prefix.nil?
207
- return prefix if prefix.respond_to?(:symbols) && prefix.respond_to?(:base) && prefix.respond_to?(:power)
208
- return Unitsdb.prefixes.find_by_id(prefix.id) if prefix.respond_to?(:id)
209
-
210
- prefix
211
- end
212
-
213
164
  def unit(units, formula, dims, norm_text, name, options)
214
165
  attributes = {
215
166
  id: unit_id(norm_text),
@@ -219,13 +170,11 @@ module Unitsml
219
170
  root_units: rootunits(units),
220
171
  }
221
172
  attributes[:dimension_url] = "##{dim_id(dims)}" if dims
222
- Model::Unit.new(**attributes, lutaml_register: Configuration.context.id).to_xml
223
- .force_encoding("UTF-8")
224
- .gsub("&lt;", "<")
225
- .gsub("&gt;", ">")
226
- .gsub("&amp;", "&")
227
- .gsub(/−/, "&#x2212;")
228
- .gsub(/⋅/, "&#x22c5;")
173
+ xml = Model::Unit.new(
174
+ **attributes,
175
+ lutaml_register: Configuration.context.id,
176
+ ).to_xml
177
+ Xml::Formatter.format_unit(xml)
229
178
  end
230
179
 
231
180
  def unitname(text, name)
@@ -275,10 +224,11 @@ module Unitsml
275
224
 
276
225
  dim_attrs = { id: dim_id }
277
226
  dimid2dimensions(dim_id)&.compact&.each { |u| dimension1(u, dim_attrs) }
278
- Model::Dimension.new(
227
+ xml = Model::Dimension.new(
279
228
  dim_attrs,
280
229
  lutaml_register: Configuration.context.id,
281
- ).to_xml.force_encoding("UTF-8")
230
+ ).to_xml
231
+ xml.force_encoding("UTF-8")
282
232
  end
283
233
 
284
234
  def dimension1(dim, dims_hash)
@@ -323,34 +273,52 @@ module Unitsml
323
273
  end
324
274
 
325
275
  def prefix_xml(prefix, options)
326
- Model::Prefix.new(
276
+ xml = Model::Prefix.new(
327
277
  prefix_attributes(prefix, options),
328
278
  lutaml_register: Configuration.context.id,
329
- ).to_xml.force_encoding("UTF-8").gsub("&amp;", "&")
279
+ ).to_xml
280
+ Xml::Formatter.format_prefix(xml)
330
281
  end
331
282
 
332
283
  def prefix_attributes(prefix, options)
284
+ adapter = PrefixAdapter.wrap(prefix)
333
285
  {
334
- prefix_base: prefix&.base,
335
- prefix_power: prefix&.power,
336
- id: prefix&.id,
337
- name: prefix_name(prefix),
286
+ prefix_base: adapter.base,
287
+ prefix_power: adapter.power,
288
+ id: prefix_id(prefix, adapter),
289
+ name: prefix_name(prefix, adapter),
338
290
  symbol: prefix_symbols(prefix, options),
339
291
  }
340
292
  end
341
293
 
342
- def prefix_name(prefix)
294
+ def prefix_id(prefix, adapter)
295
+ if prefix.is_a?(Unitsml::Prefix)
296
+ prefix.id
297
+ else
298
+ adapter.raw&.identifiers&.find do |i|
299
+ i.type == "nist"
300
+ end&.id
301
+ end
302
+ end
303
+
304
+ def prefix_name(prefix, adapter)
305
+ name_value = if prefix.is_a?(Unitsml::Prefix)
306
+ prefix.name
307
+ elsif adapter.raw.is_a?(::Unitsdb::Prefix)
308
+ adapter.raw.names.find { |n| n.lang == "en" }&.value
309
+ end
343
310
  Model::Prefixes::Name.new(
344
- content: prefix&.name,
311
+ content: name_value,
345
312
  lutaml_register: Configuration.context.id,
346
313
  )
347
314
  end
348
315
 
349
316
  def prefix_symbols(prefix, options)
317
+ adapter = PrefixAdapter.wrap(prefix)
350
318
  PREFIX_SYMBOL_METHODS.map do |type, method_name|
351
319
  Model::Prefixes::Symbol.new(
352
320
  type: type,
353
- content: prefix&.public_send(method_name, options),
321
+ content: adapter.raw&.public_send(method_name, options),
354
322
  lutaml_register: Configuration.context.id,
355
323
  )
356
324
  end
@@ -393,10 +361,11 @@ module Unitsml
393
361
 
394
362
  dim_attrs = { id: dim_id(dims) }
395
363
  dims.map { |u| dimension1(u, dim_attrs) }
396
- Model::Dimension.new(
364
+ xml = Model::Dimension.new(
397
365
  **dim_attrs,
398
366
  lutaml_register: Configuration.context.id,
399
- ).to_xml.force_encoding("UTF-8")
367
+ ).to_xml
368
+ xml.force_encoding("UTF-8")
400
369
  end
401
370
 
402
371
  def quantity(normtext, instance)
@@ -411,21 +380,21 @@ module Unitsml
411
380
 
412
381
  def unit_nist_id(unit)
413
382
  return unless unit
414
- return unit.nist_id if unit.respond_to?(:nist_id)
383
+ return unit.nist_id if unit.is_a?(Unitsml::Unitsdb::Unit)
415
384
 
416
385
  unit.identifiers&.find { |identifier| identifier.type == "nist" }&.id
417
386
  end
418
387
 
419
388
  def unit_en_name(unit)
420
389
  return unless unit
421
- return unit.en_name if unit.respond_to?(:en_name)
390
+ return unit.en_name if unit.is_a?(Unitsml::Unitsdb::Unit)
422
391
 
423
392
  unit.names&.find { |name| name.lang == "en" }&.value
424
393
  end
425
394
 
426
395
  def unit_dimension_id(unit)
427
396
  return unless unit
428
- return unit.dimension_url if unit.respond_to?(:dimension_url)
397
+ return unit.dimension_url if unit.is_a?(Unitsml::Unitsdb::Unit)
429
398
 
430
399
  unit.dimension_reference&.id ||
431
400
  quantity_dimension_id(quantity_instance(unit.quantity_references&.first&.id))
@@ -441,19 +410,21 @@ module Unitsml
441
410
  end
442
411
 
443
412
  def model_quantity_xml(id, url)
444
- Model::Quantity.new(
413
+ xml = Model::Quantity.new(
445
414
  id: id,
446
415
  name: quantity_name(id),
447
416
  dimension_url: url,
448
417
  lutaml_register: Configuration.context.id,
449
- ).to_xml.force_encoding("UTF-8")
418
+ ).to_xml
419
+ xml.force_encoding("UTF-8")
450
420
  end
451
421
 
452
422
  def quantity_name(id)
453
423
  quantity_instance(id)&.names&.filter_map do |name|
454
424
  next unless name.lang == "en"
455
425
 
456
- Model::Quantities::Name.new(content: name.value, lutaml_register: Configuration.context.id)
426
+ Model::Quantities::Name.new(content: name.value,
427
+ lutaml_register: Configuration.context.id)
457
428
  end
458
429
  end
459
430
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unitsml
4
- VERSION = "0.6.5"
4
+ VERSION = "0.6.7"
5
5
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ module Xml
5
+ # Owns the canonical post-processing applied to XML emitted by
6
+ # Unitsml::Model::* before it is returned to callers. Previously
7
+ # inlined as repeated `.gsub` chains at six call sites in
8
+ # Unitsml::Utility — this module is the single source of truth.
9
+ #
10
+ # The chain has two logical phases:
11
+ # 1. Decode XML entities that lutaml-model emitted but UnitsML
12
+ # consumers expect as raw characters (`&lt;` → `<`, etc.).
13
+ # 2. Re-encode Unicode mathematical symbols (minus, dot operator)
14
+ # as numeric character references for downstream XML consumers
15
+ # that aren't guaranteed UTF-8 clean transport.
16
+ module Formatter
17
+ ENTITY_DECODINGS = {
18
+ "&lt;" => "<",
19
+ "&gt;" => ">",
20
+ "&amp;" => "&",
21
+ }.freeze
22
+
23
+ UNIT_SYMBOL_ENCODINGS = {
24
+ "−" => "&#x2212;",
25
+ "⋅" => "&#x22c5;",
26
+ }.freeze
27
+
28
+ module_function
29
+
30
+ # Full post-processing for UnitsML <Unit> XML: entity decode
31
+ # followed by Unicode → numeric-reference encoding.
32
+ def format_unit(xml)
33
+ decoded = decode_entities(xml)
34
+ encode_unit_symbols(decoded)
35
+ end
36
+
37
+ # Minimal post-processing for <Prefix> XML: only the ampersand
38
+ # decode is needed (UnitsML prefix symbols never contain the
39
+ # other reserved characters or Unicode math symbols).
40
+ def format_prefix(xml)
41
+ unfrozen(xml).force_encoding("UTF-8").gsub("&amp;", "&")
42
+ end
43
+
44
+ def decode_entities(xml)
45
+ buffer = unfrozen(xml).force_encoding("UTF-8")
46
+ ENTITY_DECODINGS.reduce(buffer) do |acc, (from, to)|
47
+ acc.gsub(from, to)
48
+ end
49
+ end
50
+
51
+ def encode_unit_symbols(xml)
52
+ UNIT_SYMBOL_ENCODINGS.reduce(xml) do |buffer, (from, to)|
53
+ buffer.gsub(from, to)
54
+ end
55
+ end
56
+
57
+ def unfrozen(xml)
58
+ xml.frozen? ? xml.dup : xml
59
+ end
60
+ private_class_method :unfrozen
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ module Xml
5
+ autoload :Formatter, "unitsml/xml/formatter"
6
+ end
7
+ end
data/lib/unitsml.rb CHANGED
@@ -21,12 +21,14 @@ module Unitsml
21
21
  autoload :Parse, "unitsml/parse"
22
22
  autoload :Parser, "unitsml/parser"
23
23
  autoload :Prefix, "unitsml/prefix"
24
+ autoload :PrefixAdapter, "unitsml/prefix_adapter"
24
25
  autoload :Sqrt, "unitsml/sqrt"
25
26
  autoload :Transform, "unitsml/transform"
26
27
  autoload :Unit, "unitsml/unit"
27
28
  autoload :Unitsdb, "unitsml/unitsdb"
28
29
  autoload :Utility, "unitsml/utility"
29
30
  autoload :VERSION, "unitsml/version"
31
+ autoload :Xml, "unitsml/xml"
30
32
 
31
33
  def parse(string)
32
34
  Unitsml::Parser.new(string).parse
data/unitsml.gemspec CHANGED
@@ -33,9 +33,9 @@ Gem::Specification.new do |spec|
33
33
  spec.require_paths = ["lib"]
34
34
 
35
35
  spec.add_dependency "htmlentities"
36
- spec.add_dependency "ox"
37
36
  spec.add_dependency "lutaml-model", "~> 0.8.0"
38
37
  spec.add_dependency "mml", "~> 2.3.6"
38
+ spec.add_dependency "ox"
39
39
  spec.add_dependency "parslet"
40
- spec.add_dependency "unitsdb", "~> 2.2.2"
40
+ spec.add_dependency "unitsdb", "~> 2.2.4"
41
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unitsml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-05 00:00:00.000000000 Z
11
+ date: 2026-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: htmlentities
@@ -25,47 +25,47 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: ox
28
+ name: lutaml-model
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.8.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 0.8.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: lutaml-model
42
+ name: mml
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.8.0
47
+ version: 2.3.6
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.8.0
54
+ version: 2.3.6
55
55
  - !ruby/object:Gem::Dependency
56
- name: mml
56
+ name: ox
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 2.3.6
61
+ version: '0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 2.3.6
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: parslet
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 2.2.2
89
+ version: 2.2.4
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 2.2.2
96
+ version: 2.2.4
97
97
  description: Library to work with UnitsML in Ruby
98
98
  email:
99
99
  - open.source@ribose.com
@@ -103,6 +103,7 @@ extra_rdoc_files: []
103
103
  files:
104
104
  - ".github/workflows/dependent-repos.json"
105
105
  - ".github/workflows/depenedent-gems.yml"
106
+ - ".github/workflows/opal.yml"
106
107
  - ".github/workflows/rake.yml"
107
108
  - ".github/workflows/release.yml"
108
109
  - ".gitignore"
@@ -123,8 +124,10 @@ files:
123
124
  - lib/unitsml/dimension.rb
124
125
  - lib/unitsml/errors.rb
125
126
  - lib/unitsml/errors/base_error.rb
127
+ - lib/unitsml/errors/invalid_model_error.rb
126
128
  - lib/unitsml/errors/opal_payload_not_bundled_error.rb
127
129
  - lib/unitsml/errors/plurimath_load_error.rb
130
+ - lib/unitsml/errors/unsupported_payload_type_error.rb
128
131
  - lib/unitsml/extender.rb
129
132
  - lib/unitsml/fenced.rb
130
133
  - lib/unitsml/fenced_numeric.rb
@@ -159,9 +162,13 @@ files:
159
162
  - lib/unitsml/model/units/system.rb
160
163
  - lib/unitsml/namespace.rb
161
164
  - lib/unitsml/number.rb
165
+ - lib/unitsml/opal.rb
166
+ - lib/unitsml/opal/database_payload.rb
167
+ - lib/unitsml/opal/payload_generator.rb
162
168
  - lib/unitsml/parse.rb
163
169
  - lib/unitsml/parser.rb
164
170
  - lib/unitsml/prefix.rb
171
+ - lib/unitsml/prefix_adapter.rb
165
172
  - lib/unitsml/sqrt.rb
166
173
  - lib/unitsml/transform.rb
167
174
  - lib/unitsml/unit.rb
@@ -170,6 +177,7 @@ files:
170
177
  - lib/unitsml/unitsdb/dimension.rb
171
178
  - lib/unitsml/unitsdb/dimension_details.rb
172
179
  - lib/unitsml/unitsdb/dimensions.rb
180
+ - lib/unitsml/unitsdb/finders.rb
173
181
  - lib/unitsml/unitsdb/prefix_reference.rb
174
182
  - lib/unitsml/unitsdb/prefixes.rb
175
183
  - lib/unitsml/unitsdb/quantities.rb
@@ -177,6 +185,8 @@ files:
177
185
  - lib/unitsml/unitsdb/units.rb
178
186
  - lib/unitsml/utility.rb
179
187
  - lib/unitsml/version.rb
188
+ - lib/unitsml/xml.rb
189
+ - lib/unitsml/xml/formatter.rb
180
190
  - unitsml.gemspec
181
191
  homepage: https://github.com/unitsml/unitsml-ruby
182
192
  licenses: