diff --git a/.github/workflows/dependent-repos.json b/.github/workflows/dependent-repos.json
new file mode 100644
index 0000000..e74484b
--- /dev/null
+++ b/.github/workflows/dependent-repos.json
@@ -0,0 +1,5 @@
+{
+ "repo": [
+ "plurimath/plurimath"
+ ]
+}
diff --git a/.github/workflows/depenedent-gems.yml b/.github/workflows/depenedent-gems.yml
new file mode 100644
index 0000000..00459f6
--- /dev/null
+++ b/.github/workflows/depenedent-gems.yml
@@ -0,0 +1,16 @@
+name: dependent-gems-test
+
+on:
+ push:
+ branches: [ main ]
+ tags: [ v* ]
+ pull_request:
+ workflow_dispatch:
+ repository_dispatch:
+ types: [ release-passed ]
+
+jobs:
+ rake:
+ uses: metanorma/ci/.github/workflows/dependent-rake.yml@main
+ with:
+ command: bundle exec rspec
diff --git a/Gemfile b/Gemfile
index 394ef67..c048116 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,6 +6,7 @@ gemspec
gem "byebug"
gem "equivalent-xml"
gem "oga"
+gem "ox"
gem "plurimath"
gem "pry"
gem "rake"
diff --git a/lib/unitsml.rb b/lib/unitsml.rb
index 916e112..22bc5c0 100644
--- a/lib/unitsml.rb
+++ b/lib/unitsml.rb
@@ -1,3 +1,13 @@
+# frozen_string_literal: true
+
+module Unitsml
+ UNITSML_NS = "https://schema.unitsml.org/unitsml/1.0".freeze
+
+ def self.parse(string)
+ Unitsml::Parser.new(string).parse
+ end
+end
+
require "unitsml/error"
require "unitsml/sqrt"
require "unitsml/unit"
@@ -10,9 +20,19 @@
require "unitsml/extender"
require "unitsml/dimension"
require "unitsml/transform"
-
-module Unitsml
- def self.parse(string)
- Unitsml::Parser.new(string).parse
- end
-end
+require "unitsml/unitsdb/units"
+require "unitsml/unitsdb/prefixes"
+require "unitsml/unitsdb/dimension"
+require "unitsml/unitsdb/dimensions"
+require "unitsml/unitsdb/quantities"
+require "unitsml/unitsdb/dimension_quantity"
+require "unitsdb/config"
+Unitsdb::Config.models = {
+ units: Unitsml::Unitsdb::Units,
+ prefixes: Unitsml::Unitsdb::Prefixes,
+ dimension: Unitsml::Unitsdb::Dimension,
+ dimensions: Unitsml::Unitsdb::Dimensions,
+ quantities: Unitsml::Unitsdb::Quantities,
+ dimension_quantity: Unitsml::Unitsdb::DimensionQuantity,
+}
+require "unitsdb"
diff --git a/lib/unitsml/dimension.rb b/lib/unitsml/dimension.rb
index f33c281..e2b5560 100644
--- a/lib/unitsml/dimension.rb
+++ b/lib/unitsml/dimension.rb
@@ -15,53 +15,46 @@ def ==(object)
power_numerator == object&.power_numerator
end
- def dim_hash
- Unitsdb.parsable_dimensions[dimension_name]
+ def dim_instance
+ @dim ||= Unitsdb.dimensions.find_parsables_by_id(dimension_name)
end
def dim_symbols
- dim_hash&.values&.last&.values&.first["dim_symbols"].first
+ dim_instance.send(@dim.processed_keys.last).dim_symbols.first
end
def to_mathml
- value = dim_symbols["mathml"]
- value = Ox.parse(value)
+ # MathML key's value in unitsdb/dimensions.yaml file includes mi tags only.
+ value = ::Mml::Mi.from_xml(dim_symbols.mathml)
+ method_name = power_numerator ? :msup : :mi
if power_numerator
- msup = Utility.ox_element("msup")
- msup << (Utility.ox_element("mrow") << value)
- msup << (
- Utility.ox_element("mrow") << (
- Utility.ox_element("mn") << power_numerator
- )
+ value = ::Mml::Msup.new(
+ mrow_value: [
+ ::Mml::Mrow.new(mi_value: [value]),
+ ::Mml::Mrow.new(
+ mn_value: [::Mml::Mn.new(value: power_numerator)],
+ ),
+ ]
)
- value = msup
end
- value
+ { method_name: method_name, value: value }
end
def to_latex
- value = dim_symbols["latex"]
- if power_numerator
- value = "#{value}^#{power_numerator}"
- end
- value
+ power_numerator_generic_code(:latex)
end
def to_asciimath
- value = dim_symbols["ascii"]
- value = "#{value}^#{power_numerator}" if power_numerator
- value
+ power_numerator_generic_code(:ascii)
end
- def to_html
- value = dim_symbols["html"]
- value = "#{value}#{power_numerator}" if power_numerator
- value
+ def to_unicode
+ power_numerator_generic_code(:unicode)
end
- def to_unicode
- value = dim_symbols["unicode"]
- value = "#{value}^#{power_numerator}" if power_numerator
+ def to_html
+ value = dim_symbols.html
+ value = "#{value}#{power_numerator}" if power_numerator
value
end
@@ -70,16 +63,32 @@ def generate_id
end
def to_xml
- fields = dim_hash[:fields]
- symbol = fields.values.first["symbol"]
- power_numerator_value = power_numerator || 1
- attributes = { symbol: symbol, powerNumerator: power_numerator_value }
- element_name = modelize(fields.keys.first.capitalize)
- Utility.ox_element(element_name, attributes: attributes)
+ attributes = {
+ symbol: dim_instance.processed_symbol,
+ power_numerator: power_numerator || 1,
+ }
+ Model::DimensionQuantities.const_get(modelize(element_name)).new(attributes)
+ end
+
+ def xml_instances_hash
+ { element_name => to_xml }
end
def modelize(value)
value&.split("_")&.map(&:capitalize)&.join
end
+
+ private
+
+ def power_numerator_generic_code(method_name)
+ value = dim_symbols.public_send(method_name)
+ return value unless power_numerator
+
+ "#{value}^#{power_numerator}"
+ end
+
+ def element_name
+ dim_instance.processed_keys.first
+ end
end
end
diff --git a/lib/unitsml/extender.rb b/lib/unitsml/extender.rb
index 8f613d1..3fde79d 100644
--- a/lib/unitsml/extender.rb
+++ b/lib/unitsml/extender.rb
@@ -14,7 +14,10 @@ def ==(object)
end
def to_mathml
- Utility.ox_element("mo") << "⋅"
+ {
+ method_name: :mo,
+ value: ::Mml::Mo.new(value: "⋅"),
+ }
end
def to_latex
diff --git a/lib/unitsml/formula.rb b/lib/unitsml/formula.rb
index 96fc3c2..c4a5285 100644
--- a/lib/unitsml/formula.rb
+++ b/lib/unitsml/formula.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
-require_relative "utility"
+require "mml"
+require "htmlentities"
+require "unitsml/utility"
module Unitsml
class Formula
attr_accessor :value, :explicit_value, :root
@@ -26,14 +28,21 @@ def ==(object)
def to_mathml
if root
- attributes = { xmlns: "http://www.w3.org/1998/Math/MathML", display: "block" }
- math = Utility.ox_element("math", attributes: attributes)
- Ox.dump(
- Utility.update_nodes(
- math,
- value.map(&:to_mathml).flatten,
- ),
- ).gsub(/&(.*?)(?=<\/)/, '&\1')
+ nullify_mml_models if plurimath_available?
+ math = ::Mml::MathWithNamespace.new(display: "block")
+ math.ordered = true
+ math.element_order ||= []
+ value.each do |instance|
+ processed_instance = instance.to_mathml
+ case processed_instance
+ when Array
+ processed_instance.each { |hash| add_math_element(math, hash) }
+ when Hash
+ add_math_element(math, processed_instance)
+ end
+ end
+ reset_mml_models if plurimath_available?
+ math.to_xml.gsub(/&(.*?)(?=<\/)/, '&\1')
else
value.map(&:to_mathml)
end
@@ -56,8 +65,7 @@ def to_unicode
end
def to_xml
- dimensions_array = extract_dimensions(value)
- if (dimensions_array).any?
+ if (dimensions_array = extract_dimensions(value)).any?
dimensions(sort_dims(dimensions_array))
elsif @orig_text.match(/-$/)
prefixes
@@ -76,9 +84,7 @@ def to_plurimath
private
def extract_dimensions(formula)
- dimensions = []
-
- formula.each do |term|
+ formula.each_with_object([]) do |term, dimensions|
if term.is_a?(Dimension)
dimensions << term
elsif term.is_a?(Sqrt) && term.value.is_a?(Dimension)
@@ -89,14 +95,10 @@ def extract_dimensions(formula)
dimensions.concat(extract_dimensions(term.value))
end
end
-
- dimensions
end
def extract_units(formula)
- units_arr = []
-
- formula.each do |term|
+ formula.each_with_object([]) do |term, units_arr|
case term
when Unit
units_arr << term.dup
@@ -108,8 +110,6 @@ def extract_units(formula)
units_arr << term.value
end
end
-
- units_arr
end
def units
@@ -135,13 +135,9 @@ def unique_dimensions(dims, norm_text)
def dimensions(dims)
dim_id = dims.map(&:generate_id).join
- attributes = { xmlns: Utility::UNITSML_NS, "xml:id": "D_#{dim_id}" }
- Ox.dump(
- Utility.update_nodes(
- Utility.ox_element("Dimension", attributes: attributes),
- dims.map(&:to_xml),
- ),
- )
+ attributes = { id: "D_#{dim_id}" }
+ dims.each { |dim| attributes.merge!(dim.xml_instances_hash) }
+ Model::Dimension.new(attributes).to_xml
end
def sort_dims(values)
@@ -162,11 +158,31 @@ def prefixes
end
def ensure_plurimath_defined!
- return if Object.const_defined?(:Plurimath)
+ return if plurimath_available?
require "plurimath"
rescue LoadError => e
raise Errors::PlurimathLoadError
end
+
+ def add_math_element(math_instance, child_hash)
+ method_name = child_hash[:method_name]
+ method_value = math_instance.public_send(:"#{method_name}_value")
+ method_value += Array(child_hash[:value])
+ math_instance.public_send(:"#{method_name}_value=", method_value)
+ math_instance.element_order << Lutaml::Model::XmlAdapter::Element.new("Element", method_name.to_s)
+ end
+
+ def plurimath_available?
+ Object.const_defined?(:Plurimath)
+ end
+
+ def nullify_mml_models
+ Plurimath::Mathml::Parser::CONFIGURATION.each_key { |klass| klass.model(klass) }
+ end
+
+ def reset_mml_models
+ ::Mml::Configuration.custom_models = Plurimath::Mathml::Parser::CONFIGURATION
+ end
end
end
diff --git a/lib/unitsml/model/dimension.rb b/lib/unitsml/model/dimension.rb
new file mode 100644
index 0000000..6a98dae
--- /dev/null
+++ b/lib/unitsml/model/dimension.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require "unitsml/model/dimension_quantities/quantity"
+require "unitsml/model/dimension_quantities/length"
+require "unitsml/model/dimension_quantities/mass"
+require "unitsml/model/dimension_quantities/time"
+require "unitsml/model/dimension_quantities/electric_current"
+require "unitsml/model/dimension_quantities/thermodynamic_temperature"
+require "unitsml/model/dimension_quantities/amount_of_substance"
+require "unitsml/model/dimension_quantities/luminous_intensity"
+require "unitsml/model/dimension_quantities/plane_angle"
+
+module Unitsml
+ module Model
+ class Dimension < Lutaml::Model::Serializable
+ attribute :id, :string
+ attribute :length, DimensionQuantities::Length
+ attribute :mass, DimensionQuantities::Mass
+ attribute :time, DimensionQuantities::Time
+ attribute :electric_current, DimensionQuantities::ElectricCurrent
+ attribute :thermodynamic_temperature, DimensionQuantities::ThermodynamicTemperature
+ attribute :amount_of_substance, DimensionQuantities::AmountOfSubstance
+ attribute :luminous_intensity, DimensionQuantities::LuminousIntensity
+ attribute :plane_angle, DimensionQuantities::PlaneAngle
+
+ xml do
+ root "Dimension"
+ namespace Unitsml::UNITSML_NS
+
+ map_attribute :id, to: :id, namespace: nil, prefix: "xml"
+ map_element :Length, to: :length
+ map_element :Mass, to: :mass
+ map_element :Time, to: :time
+ map_element :ElectricCurrent, to: :electric_current
+ map_element :ThermodynamicTemperature, to: :thermodynamic_temperature
+ map_element :AmountOfSubstance, to: :amount_of_substance
+ map_element :LuminousIntensity, to: :luminous_intensity
+ map_element :PlaneAngle, to: :plane_angle
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/amount_of_substance.rb b/lib/unitsml/model/dimension_quantities/amount_of_substance.rb
new file mode 100644
index 0000000..dc2f787
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/amount_of_substance.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class AmountOfSubstance < Quantity
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/electric_current.rb b/lib/unitsml/model/dimension_quantities/electric_current.rb
new file mode 100644
index 0000000..992f846
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/electric_current.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class ElectricCurrent < Quantity
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/length.rb b/lib/unitsml/model/dimension_quantities/length.rb
new file mode 100644
index 0000000..a6b36b7
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/length.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class Length < Quantity
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/luminous_intensity.rb b/lib/unitsml/model/dimension_quantities/luminous_intensity.rb
new file mode 100644
index 0000000..d3fc4df
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/luminous_intensity.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class LuminousIntensity < Quantity
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/mass.rb b/lib/unitsml/model/dimension_quantities/mass.rb
new file mode 100644
index 0000000..5ba3bca
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/mass.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class Mass < Quantity
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/plane_angle.rb b/lib/unitsml/model/dimension_quantities/plane_angle.rb
new file mode 100644
index 0000000..504a4c4
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/plane_angle.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class PlaneAngle < Quantity
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/quantity.rb b/lib/unitsml/model/dimension_quantities/quantity.rb
new file mode 100644
index 0000000..e0ab9d0
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/quantity.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class Quantity < Lutaml::Model::Serializable
+ attribute :symbol, :string
+ attribute :power_numerator, :string
+
+ xml do
+ map_attribute :symbol, to: :symbol
+ map_attribute :powerNumerator, to: :power_numerator
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/thermodynamic_temperature.rb b/lib/unitsml/model/dimension_quantities/thermodynamic_temperature.rb
new file mode 100644
index 0000000..c5473c3
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/thermodynamic_temperature.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class ThermodynamicTemperature < Quantity
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/dimension_quantities/time.rb b/lib/unitsml/model/dimension_quantities/time.rb
new file mode 100644
index 0000000..dfa6478
--- /dev/null
+++ b/lib/unitsml/model/dimension_quantities/time.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module DimensionQuantities
+ class Time < Quantity
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/prefix.rb b/lib/unitsml/model/prefix.rb
new file mode 100644
index 0000000..ffe0077
--- /dev/null
+++ b/lib/unitsml/model/prefix.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require "unitsml/model/prefixes/name"
+require "unitsml/model/prefixes/symbol"
+
+
+module Unitsml
+ module Model
+ class Prefix < Lutaml::Model::Serializable
+ attribute :name, Prefixes::Name
+ attribute :id, :string
+ attribute :symbol, Prefixes::Symbol, collection: true
+ attribute :prefix_base, :string
+ attribute :prefix_power, :string
+
+ xml do
+ root "Prefix"
+ namespace Unitsml::UNITSML_NS
+
+ map_attribute :prefixBase, to: :prefix_base
+ map_attribute :prefixPower, to: :prefix_power
+ map_attribute :id, to: :id, namespace: nil, prefix: "xml"
+ map_element :PrefixName, to: :name
+ map_element :PrefixSymbol, to: :symbol
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/prefixes/name.rb b/lib/unitsml/model/prefixes/name.rb
new file mode 100644
index 0000000..43bb181
--- /dev/null
+++ b/lib/unitsml/model/prefixes/name.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ class Prefixes
+ class Name < Lutaml::Model::Serializable
+ attribute :lang, :string, default: -> { "en" }
+ attribute :content, :string
+
+ xml do
+ root "PrefixName"
+
+ map_attribute :lang, to: :lang, namespace: nil, prefix: "xml", render_default: true
+ map_content to: :content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/prefixes/symbol.rb b/lib/unitsml/model/prefixes/symbol.rb
new file mode 100644
index 0000000..ead9f73
--- /dev/null
+++ b/lib/unitsml/model/prefixes/symbol.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ class Prefixes
+ class Symbol < Lutaml::Model::Serializable
+ attribute :type, :string
+ attribute :content, :string
+
+ xml do
+ root "PrefixSymbol"
+
+ map_attribute :type, to: :type
+ map_content to: :content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/quantities/name.rb b/lib/unitsml/model/quantities/name.rb
new file mode 100644
index 0000000..17a152b
--- /dev/null
+++ b/lib/unitsml/model/quantities/name.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module Quantities
+ class Name < Lutaml::Model::Serializable
+ attribute :lang, :string, default: -> { "en-US" }
+ attribute :content, :string
+
+ xml do
+ root "QuantityName"
+
+ map_attribute :lang, to: :lang, namespace: nil, prefix: "xml", render_default: true
+ map_content to: :content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/quantity.rb b/lib/unitsml/model/quantity.rb
new file mode 100644
index 0000000..e258dc3
--- /dev/null
+++ b/lib/unitsml/model/quantity.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "unitsml/model/quantities/name"
+
+module Unitsml
+ module Model
+ class Quantity < Lutaml::Model::Serializable
+ attribute :id, :string
+ attribute :name, Quantities::Name, collection: true
+ attribute :quantity_type, :string, default: -> { "base" }
+ attribute :dimension_url, :string
+
+ xml do
+ root "Quantity"
+ namespace Unitsml::UNITSML_NS
+
+ map_attribute :id, to: :id, namespace: nil, prefix: "xml"
+ map_attribute :quantityType, to: :quantity_type, render_default: true
+ map_attribute :dimensionURL, to: :dimension_url
+ map_element :QuantityName, to: :name
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/unit.rb b/lib/unitsml/model/unit.rb
new file mode 100644
index 0000000..225b58f
--- /dev/null
+++ b/lib/unitsml/model/unit.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require "unitsml/model/units/name"
+require "unitsml/model/units/symbol"
+require "unitsml/model/units/system"
+require "unitsml/model/units/root_units"
+
+module Unitsml
+ module Model
+ class Unit < Lutaml::Model::Serializable
+ attribute :id, :string
+ attribute :name, Units::Name
+ attribute :dimension_url, :string
+ attribute :symbol, Units::Symbol, collection: true
+ attribute :system, Units::System, collection: true
+ attribute :root_units, Units::RootUnits
+
+ xml do
+ root "Unit"
+ namespace Unitsml::UNITSML_NS
+
+ map_attribute :dimensionURL, to: :dimension_url
+ map_attribute :id, to: :id, namespace: nil, prefix: "xml"
+ map_element :UnitName, to: :name
+ map_element :UnitSymbol, to: :symbol
+ map_element :UnitSystem, to: :system
+ map_element :RootUnits, to: :root_units
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/units/enumerated_root_unit.rb b/lib/unitsml/model/units/enumerated_root_unit.rb
new file mode 100644
index 0000000..fdc5fa4
--- /dev/null
+++ b/lib/unitsml/model/units/enumerated_root_unit.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module Units
+ class EnumeratedRootUnit < Lutaml::Model::Serializable
+ attribute :unit, :string
+ attribute :prefix, :string
+ attribute :power_numerator, :string
+
+ xml do
+ map_attribute :unit, to: :unit
+ map_attribute :prefix, to: :prefix
+ map_attribute :powerNumerator, to: :power_numerator
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/units/name.rb b/lib/unitsml/model/units/name.rb
new file mode 100644
index 0000000..8eda6c7
--- /dev/null
+++ b/lib/unitsml/model/units/name.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module Units
+ class Name < Lutaml::Model::Serializable
+ attribute :name, :string
+ attribute :lang, :string, default: -> { "en" }
+
+ xml do
+ root "UnitName"
+
+ map_content to: :name
+ map_attribute :lang, to: :lang, namespace: nil, prefix: "xml", render_default: true
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/units/root_units.rb b/lib/unitsml/model/units/root_units.rb
new file mode 100644
index 0000000..acf8c59
--- /dev/null
+++ b/lib/unitsml/model/units/root_units.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require "unitsml/model/units/enumerated_root_unit"
+
+module Unitsml
+ module Model
+ module Units
+ class RootUnits < Lutaml::Model::Serializable
+ attribute :enumerated_root_unit, EnumeratedRootUnit, collection: true
+
+ xml do
+ map_element :EnumeratedRootUnit, to: :enumerated_root_unit
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/units/symbol.rb b/lib/unitsml/model/units/symbol.rb
new file mode 100644
index 0000000..7069a36
--- /dev/null
+++ b/lib/unitsml/model/units/symbol.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module Units
+ class Symbol < Lutaml::Model::Serializable
+ attribute :type, :string
+ attribute :content, :string
+
+ xml do
+ root "UnitSymbol"
+
+ map_attribute :type, to: :type
+ map_content to: :content, with: { from: :content_from_xml, to: :content_to_xml }
+ end
+
+ # Not reading any XML for now.
+ def content_from_xml(**); end
+
+ def content_to_xml(model, parent, doc)
+ doc.add_xml_fragment(parent, model.content)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/model/units/system.rb b/lib/unitsml/model/units/system.rb
new file mode 100644
index 0000000..ab2c87c
--- /dev/null
+++ b/lib/unitsml/model/units/system.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Model
+ module Units
+ class System < Lutaml::Model::Serializable
+ attribute :name, :string
+ attribute :type, :string
+ attribute :lang, :string, default: -> { "en-US" }
+
+ xml do
+ root "UnitSystem"
+
+ map_attribute :name, to: :name
+ map_attribute :type, to: :type
+ map_attribute :lang, to: :lang, namespace: nil, prefix: "xml", render_default: true
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/parse.rb b/lib/unitsml/parse.rb
index 23b40b5..0d9e83c 100644
--- a/lib/unitsml/parse.rb
+++ b/lib/unitsml/parse.rb
@@ -1,11 +1,9 @@
# frozen_string_literal: true
require "parslet"
-require_relative "unitsdb"
+require "unitsml/unitsdb"
module Unitsml
class Parse < Parslet::Parser
- include Unitsml::Unitsdb
-
rule(:power) { str("^") >> intermediate_exp(number) }
rule(:hyphen) { str("-") }
rule(:number) { (hyphen.maybe >> match(/[0-9]/).repeat(1)).as(:integer) }
@@ -14,19 +12,19 @@ class Parse < Parslet::Parser
rule(:unit_and_power) { units >> power.maybe }
rule(:units) do
- @@filtered_units ||= arr_to_expression(Unitsdb.filtered_units, "units")
+ @@filtered_units ||= arr_to_expression(Unitsdb.units.filtered, "units")
end
rule(:single_letter_prefixes) do
- @@prefixes1 ||= arr_to_expression(Unitsdb.prefixes.select { |p| p.size == 1 }, "prefixes")
+ @@prefixes1 ||= arr_to_expression(Unitsdb.prefixes_by_size(1), "prefixes")
end
rule(:double_letter_prefixes) do
- @@prefixes2 ||= arr_to_expression(Unitsdb.prefixes.select { |p| p.size == 2 }, "prefixes")
+ @@prefixes2 ||= arr_to_expression(Unitsdb.prefixes_by_size(2), "prefixes")
end
rule(:dimensions) do
- @@dimensions ||= arr_to_expression(Unitsdb.parsable_dimensions.keys, "dimensions")
+ @@dimensions ||= arr_to_expression(Unitsdb.dimensions.parsables.keys, "dimensions")
end
rule(:prefixes_units) do
diff --git a/lib/unitsml/parser.rb b/lib/unitsml/parser.rb
index 8f46a71..afb9e64 100644
--- a/lib/unitsml/parser.rb
+++ b/lib/unitsml/parser.rb
@@ -14,10 +14,10 @@ def initialize(text)
def parse
nodes = Parse.new.parse(text)
+ transformed = Transform.new.apply(nodes)
+ formula_value = transformed.is_a?(Formula) ? transformed.value : Array(transformed)
formula = Formula.new(
- [
- Transform.new.apply(nodes),
- ],
+ formula_value,
explicit_value: @extras_hash,
root: true,
orig_text: @orig_text,
diff --git a/lib/unitsml/prefix.rb b/lib/unitsml/prefix.rb
index 24bd1c4..6693aeb 100644
--- a/lib/unitsml/prefix.rb
+++ b/lib/unitsml/prefix.rb
@@ -15,59 +15,59 @@ def ==(object)
only_instance == object&.only_instance
end
- def id
- Unitsdb.prefixes_hash.dig(prefix_name, :id)
+ def prefix_instance
+ @prefix ||= Unitsdb.prefixes.find_by_symbol_name(prefix_name)
end
- def name
- fields.dig("name")
+ def id
+ @prefix.id
end
- def fields
- Unitsdb.prefixes_hash.dig(prefix_name, :fields)
+ def name
+ prefix_instance.name
end
def prefixes_symbols
- fields&.dig("symbol")
+ prefix_instance.symbol
end
def to_mathml
symbol = Utility.string_to_html_entity(
Utility.html_entity_to_unicode(
- prefixes_symbols["html"]
+ prefixes_symbols.html
),
)
return symbol unless only_instance
- Utility.ox_element("mi") << symbol
+ { method_name: :mi, value: ::Mml::Mi.new(value: symbol)}
end
def to_latex
- prefixes_symbols["latex"]
+ prefixes_symbols.latex
end
def to_asciimath
- prefixes_symbols["ascii"]
+ prefixes_symbols.ascii
end
def to_html
- prefixes_symbols["html"]
+ prefixes_symbols.html
end
def to_unicode
- prefixes_symbols["unicode"]
+ prefixes_symbols.unicode
end
def symbolid
- prefixes_symbols["ascii"] if prefixes_symbols
+ prefixes_symbols.ascii if prefixes_symbols
end
def base
- fields&.dig("base")
+ prefix_instance.base
end
def power
- fields&.dig("power")
+ prefix_instance.power
end
end
end
diff --git a/lib/unitsml/unit.rb b/lib/unitsml/unit.rb
index 4a5fb3f..8a0463f 100644
--- a/lib/unitsml/unit.rb
+++ b/lib/unitsml/unit.rb
@@ -19,61 +19,61 @@ def ==(object)
power_numerator == object&.power_numerator
end
- def fields_hash
- Unitsdb.units.dig(unit_name, :fields)
+ def unit_instance
+ Unitsdb.units.find_by_name(unit_name)
end
def unit_symbols
- symbols = fields_hash.dig("unit_symbols")
- symbols.find{|symbol| symbol["id"] == unit_name }
+ unit_instance.unit_symbols.find { |symbol| symbol.id == unit_name }
end
def numerator_value(mathml = true)
integer = power_numerator.to_s
unless integer.match?(/-/)
- return mathml ? [Ox.parse("#{integer}")] : integer
+ return mathml ? { mn_value: [::Mml::Mn.from_xml("#{integer}")] } : integer
end
return integer.sub(/(-)(.+)/, '−\2') unless mathml
integer = integer.sub(/(-)(.+)/, '\2')
- integer = Ox.parse(integer)
- mo_tag = (Utility.ox_element("mo") << "−")
- [mo_tag, integer]
+ integer = ::Mml::Mn.from_xml(integer)
+ mo_tag = ::Mml::Mo.new(value: "−")
+ { mo_value: [mo_tag], mn_value: [integer] }
end
def to_mathml
- value = unit_symbols&.dig("mathml")
- value = Ox.parse(value)
- value.nodes.insert(0, prefix.to_mathml) if prefix
+ value = unit_symbols&.mathml
+ tag_name = value.match(/^<(?\w+)/)[:tag]
+ value = ::Mml.const_get(tag_name.capitalize).from_xml(value)
+ value.value = "#{prefix.to_mathml}#{value.value}" if prefix
if power_numerator
- msup = Utility.ox_element("msup")
- msup << (Utility.ox_element("mrow") << value)
- msup << Utility.update_nodes(
- Utility.ox_element("mrow"),
- numerator_value,
+ value = ::Mml::Msup.new(
+ mrow_value: [
+ ::Mml::Mrow.new("#{tag_name}_value": Array(value)),
+ ::Mml::Mrow.new(numerator_value),
+ ]
)
- value = msup
+ tag_name = :msup
end
- value
+ { method_name: tag_name.to_sym, value: value }
end
def to_latex
- value = unit_symbols&.dig("latex")
+ value = unit_symbols&.latex
value = "#{value}^#{power_numerator}" if power_numerator
value = "#{prefix.to_latex}#{value}" if prefix
value
end
def to_asciimath
- value = unit_symbols&.dig("ascii")
+ value = unit_symbols&.ascii
value = "#{value}^#{power_numerator}" if power_numerator
value = "#{prefix.to_asciimath}#{value}" if prefix
value
end
def to_html
- value = unit_symbols&.dig("html")
+ value = unit_symbols&.html
if power_numerator
value = "#{value}#{numerator_value(false)}"
end
@@ -82,14 +82,14 @@ def to_html
end
def to_unicode
- value = unit_symbols&.dig("unicode")
+ value = unit_symbols&.unicode
value = "#{value}^#{power_numerator}" if power_numerator
value = "#{prefix.to_unicode}#{value}" if prefix
value
end
def enumerated_name
- fields_hash.dig("unit_name")&.first
+ unit_instance&.unit_name&.first
end
def prefix_name
@@ -97,15 +97,15 @@ def prefix_name
end
def system_type
- fields_hash.dig("unit_system", "type")
+ unit_instance.unit_system.type
end
def system_name
- fields_hash.dig("unit_system", "name")
+ unit_instance.unit_system.name
end
def si_derived_bases
- fields_hash.dig("si_derived_bases")
+ unit_instance.si_derived_bases
end
end
end
diff --git a/lib/unitsml/unitsdb.rb b/lib/unitsml/unitsdb.rb
index cd8099c..264fd6c 100644
--- a/lib/unitsml/unitsdb.rb
+++ b/lib/unitsml/unitsdb.rb
@@ -1,96 +1,38 @@
# frozen_string_literal: true
-require "yaml"
module Unitsml
module Unitsdb
class << self
- def load_yaml(file_name)
+ def load_file(file_name)
@@hash ||= {}
- @@hash[file_name] ||= YAML.load_file(valid_path(file_name))
- end
-
- def load_dimensions
- @@dim_file = load_yaml(:dimensions)
- end
-
- def load_units
- @@units_file = load_yaml(:units)
+ @@hash[file_name] ||= File.read(valid_path(file_name))
end
def units
- @@units ||= {}
- return @@units unless @@units.empty?
-
- load_units.each do |key, value|
- value["unit_symbols"]&.each do |symbol|
- @@units[symbol["id"]] = { id: key, fields: value } unless symbol["id"]&.empty?
- end
- end
- @@units
+ @@units_file ||= ::Unitsdb::Units.from_yaml(load_file((:units)))
end
def prefixes
- @@prefixes_array ||= prefixes_hash.keys.sort_by(&:length)
+ @@prefixes ||= ::Unitsdb::Prefixes.from_yaml(load_file(:prefixes))
end
- def parsable_dimensions
- @@parsable_dimensions ||= {}
- return @@parsable_dimensions unless @@parsable_dimensions.empty?
-
- dimensions_hash.each do |key, value|
- value.each do |_, v|
- @@parsable_dimensions[find_id(v)] = { id: key, fields: value }
- end
- end
- @@parsable_dimensions
+ def dimensions
+ @@dim_file ||= ::Unitsdb::Dimensions.from_yaml(load_file(:dimensions))
end
def quantities
- @@quantities ||= load_yaml(:quantities)
- end
-
- def filtered_units
- @@filtered_units_array ||= units.keys.reject do |unit|
- ((/\*|\^|\/|^1$/).match?(unit) || units.dig(unit, :fields, "prefixed"))
- end
- end
-
- def prefixes_hash
- @@prefixes_hashes ||= prefixs_ids(load_yaml(:prefixes))
- end
-
- def dimensions_hash
- @@dimensions_hashs ||= insert_vectors(load_dimensions)
+ @@quantities ||= ::Unitsdb::Quantities.from_yaml(load_file((:quantities)))
end
- def prefixs_ids(prefixe_hash, hash = {})
- prefixe_hash&.each do |key, value|
- symbol = value&.dig("symbol", "ascii")
- hash[symbol] = { id: key, fields: value } unless symbol&.empty?
- end
- hash
+ def prefixes_array
+ @@prefixes_array ||= prefixes.ascii_symbols.sort_by(&:length)
end
- def find_id(value)
- return if value == true
- return unless value.is_a?(Hash)
-
- value&.dig("dim_symbols")&.map { |symbol| symbol&.dig("id") }&.first
- end
-
- def vector(dim_hash)
- Utility::DIMS_VECTOR.map { |h| dim_hash.dig(underscore(h), "powerNumerator") }.join(":")
- end
-
- def underscore(str)
- str.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
- end
+ def prefixes_by_size(size)
+ @@sized_prefixes ||= {}
+ return @@sized_prefixes[size] if @@sized_prefixes.key?(size)
- def insert_vectors(dims)
- dims.each do |key, value|
- value[:vector] = vector(value)
- value[:id] = key
- end
+ @@sized_prefixes[size] = prefixes_array.select { |p| p.size == size }
end
def valid_path(file_name)
diff --git a/lib/unitsml/unitsdb/dimension.rb b/lib/unitsml/unitsdb/dimension.rb
new file mode 100644
index 0000000..95f255e
--- /dev/null
+++ b/lib/unitsml/unitsdb/dimension.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Unitsdb
+ class Dimension
+ attr_accessor :vector,
+ :id,
+ :dimensionless
+
+ attr_reader :length,
+ :mass,
+ :time,
+ :electric_current,
+ :thermodynamic_temperature,
+ :amount_of_substance,
+ :luminous_intensity,
+ :plane_angle,
+ :parsables,
+ :parsable,
+ :processed_keys
+
+ def initialize
+ @parsables = {}
+ @processed_keys = []
+ @parsable = false
+ end
+
+ def length=(value)
+ quantities_common_code(:length, value)
+ end
+
+ def mass=(value)
+ quantities_common_code(:mass, value)
+ end
+
+ def time=(value)
+ quantities_common_code(:time, value)
+ end
+
+ def thermodynamic_temperature=(value)
+ quantities_common_code(:thermodynamic_temperature, value)
+ end
+
+ def amount_of_substance=(value)
+ quantities_common_code(:amount_of_substance, value)
+ end
+
+ def luminous_intensity=(value)
+ quantities_common_code(:luminous_intensity, value)
+ end
+
+ def plane_angle=(value)
+ quantities_common_code(:plane_angle, value)
+ end
+
+ def electric_current=(value)
+ quantities_common_code(:electric_current, value)
+ end
+
+ def dim_symbols
+ processed_keys.map { |vec| public_send(vec)&.dim_symbols&.map(&:id) }.flatten.compact
+ end
+
+ def processed_symbol
+ public_send(processed_keys.first).symbol
+ end
+
+ def set_vector
+ @vector ||= Utility::DIMS_VECTOR.map do |h|
+ public_send(Utility.underscore(h))&.power_numerator
+ end.join(":")
+ end
+
+ private
+
+ def quantities_common_code(instance_var, value)
+ return if value.nil?
+
+ instance_variable_set(:"@#{instance_var}", value)
+ @processed_keys << instance_var.to_s
+ return if value.dim_symbols.empty?
+
+ @parsable = true
+ value.dim_symbols_ids(@parsables, id)
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/unitsdb/dimension_quantity.rb b/lib/unitsml/unitsdb/dimension_quantity.rb
new file mode 100644
index 0000000..ccfdeaa
--- /dev/null
+++ b/lib/unitsml/unitsdb/dimension_quantity.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Unitsdb
+ class DimensionQuantity
+ attr_accessor :power_numerator,
+ :symbol,
+ :dim_symbols
+
+ def dim_symbols_ids(hash, dim_id)
+ dim_symbols.each { |dim_sym| hash[dim_sym.id] = dim_id }
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/unitsdb/dimensions.rb b/lib/unitsml/unitsdb/dimensions.rb
new file mode 100644
index 0000000..e7b8cab
--- /dev/null
+++ b/lib/unitsml/unitsdb/dimensions.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Unitsdb
+ class Dimensions
+ attr_accessor :dimensions
+
+ def find_by_vector(vector)
+ vectored
+ find(:vector, vector)
+ end
+
+ def find_by_id(d_id)
+ find(:id, d_id)
+ end
+
+ def find_parsables_by_id(d_id)
+ find(:id, parsables[d_id])
+ end
+
+ def parsables
+ @parsables ||= dimensions.select(&:parsables).each_with_object({}) do |dimension, object|
+ object.merge!(dimension.parsables)
+ end
+ end
+
+ private
+
+ def vectored
+ @vectored ||= dimensions.each(&:set_vector)
+ end
+
+ def find(field, matching_data)
+ dimensions.find { |dim| dim.send(field) == matching_data }
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/unitsdb/prefixes.rb b/lib/unitsml/unitsdb/prefixes.rb
new file mode 100644
index 0000000..21cb37a
--- /dev/null
+++ b/lib/unitsml/unitsdb/prefixes.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Unitsdb
+ class Prefixes
+ attr_accessor :prefixes
+
+ def find_by_symbol_name(ascii_sym)
+ prefixes.find { |prefix| prefix.symbol.ascii == ascii_sym }
+ end
+
+ def ascii_symbols
+ prefixes.each_with_object([]) do |prefix, names_array|
+ symbol = prefix.symbol.ascii
+ next if symbol.empty?
+
+ names_array << symbol
+ end
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/unitsdb/quantities.rb b/lib/unitsml/unitsdb/quantities.rb
new file mode 100644
index 0000000..3bd101a
--- /dev/null
+++ b/lib/unitsml/unitsdb/quantities.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Unitsdb
+ class Quantities
+ attr_accessor :quantities
+
+ def find_by_id(q_id)
+ quantities.find { |quantity| quantity.id == q_id }
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/unitsdb/units.rb b/lib/unitsml/unitsdb/units.rb
new file mode 100644
index 0000000..885a188
--- /dev/null
+++ b/lib/unitsml/unitsdb/units.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Unitsml
+ module Unitsdb
+ class Units
+ attr_accessor :units
+
+ def find_by_id(u_id)
+ units.find { |unit| unit.id == u_id }
+ end
+
+ def find_by_name(u_name)
+ units.find do |unit|
+ unit.unit_symbols.find { |u_sym| u_sym.id == u_name }
+ end
+ end
+
+ def filtered
+ @filtered ||= units_symbol_ids.reject do |unit|
+ ((/\*|\^|\/|^1$/).match?(unit) || find_by_symbol_id(unit).prefixed)
+ end
+ end
+
+ def find_by_symbol_id(sym_id)
+ units_symbol_hash[sym_id]
+ end
+
+ def units_symbol_ids
+ units_symbol_hash.keys
+ end
+
+ def units_symbol_hash
+ @symbol_ids_hash ||= {}
+
+ if @symbol_ids_hash.empty?
+ units.each_with_object(@symbol_ids_hash) do |unit, object|
+ unit.unit_symbols.each { |unit_sym| object[unit_sym.id] = unit }
+ end
+ end
+ @symbol_ids_hash
+ end
+ end
+ end
+end
diff --git a/lib/unitsml/utility.rb b/lib/unitsml/utility.rb
index 4332604..6185afb 100644
--- a/lib/unitsml/utility.rb
+++ b/lib/unitsml/utility.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
-require "ox"
-require "htmlentities"
+require "unitsml/model/unit"
+require "unitsml/model/prefix"
+require "unitsml/model/quantity"
+require "unitsml/model/dimension"
+
module Unitsml
module Utility
Ox.default_options = { encoding: "UTF-8" }
- UNITSML_NS = "https://schema.unitsml.org/unitsml/1.0".freeze
# Unit to dimension
U2D = {
"m" => { dimension: "Length", order: 1, symbol: "L" },
@@ -45,10 +47,12 @@ module Utility
].freeze
class << self
- include Unitsml::Unitsdb
+ def unit_instance(unit)
+ Unitsdb.units.find_by_symbol_id(unit)
+ end
- def fields(unit)
- Unitsdb.units.dig(unit, :fields)
+ def quantity_instance(id)
+ Unitsdb.quantities.find_by_id(id)
end
def units2dimensions(units)
@@ -69,10 +73,9 @@ def units2dimensions(units)
def dim_id(dims)
return nil if dims.nil? || dims.empty?
- dimensions = Unitsdb.dimensions_hash.values
dim_hash = dims.each_with_object({}) { |h, m| m[h[:dimension]] = h }
dims_vector = DIMS_VECTOR.map { |h| dim_hash.dig(h, :exponent) }.join(":")
- id = dimensions.select { |d| d[:vector] == dims_vector }&.first&.dig(:id) and return id.to_s
+ id = Unitsdb.dimensions.find_by_vector(dims_vector)&.id and return id.to_s
"D_" + dims.map do |d|
(U2D.dig(d[:unit], :symbol) || Dim2D.dig(d[:id], :symbol)) +
(d[:exponent] == 1 ? "" : float_to_display(d[:exponent]))
@@ -86,18 +89,18 @@ def decompose_units_list(units)
def decompose_unit(u)
if u&.unit_name == "g" || u.system_type == "SI_base"
{ unit: u, prefix: u&.prefix }
- elsif !u.si_derived_bases
+ elsif u.si_derived_bases.empty?
{ unit: Unit.new("unknown") }
else
- u.si_derived_bases.each_with_object([]) do |k, m|
- prefix = if !k["prefix"].nil? && !k["prefix"].empty?
- combine_prefixes(prefix_object(k["prefix"]), u.prefix)
+ u.si_derived_bases.each_with_object([]) do |k, object|
+ prefix = if !k.prefix.nil? && !k.prefix.empty?
+ combine_prefixes(prefix_object(k.prefix), u.prefix)
else
u.prefix
end
- unit_name = Unitsdb.load_units.dig(k.dig("id"), "unit_symbols", 0, "id")
- exponent = (k["power"]&.to_i || 1) * (u.power_numerator&.to_f || 1)
- m << { prefix: prefix,
+ unit_name = Unitsdb.units.find_by_id(k.id).unit_symbols.first.id
+ exponent = (k.power&.to_i || 1) * (u.power_numerator&.to_f || 1)
+ object << { prefix: prefix,
unit: Unit.new(unit_name, exponent, prefix: prefix),
}
end
@@ -120,9 +123,9 @@ def gather_units(units)
def prefix_object(prefix)
return prefix unless prefix.is_a?(String)
- return nil unless Unitsdb.prefixes.any?(prefix)
+ return nil unless Unitsdb.prefixes_array.any?(prefix)
- prefix.is_a?(String) ? Prefix.new(prefix) : prefix
+ Prefix.new(prefix)
end
def combine_prefixes(p1, p2)
@@ -131,7 +134,7 @@ def combine_prefixes(p1, p2)
return p2.symbolid if p1.nil?
return "unknown" if p1.base != p2.base
- Unitsdb.prefixes_hash.each do |prefix_name, _|
+ Unitsdb.prefixes_array.each do |prefix_name|
p = prefix_object(prefix_name)
return p if p.base == p1.base && p.power == p1.power + p2.power
end
@@ -140,14 +143,15 @@ def combine_prefixes(p1, p2)
end
def unit(units, formula, dims, norm_text, name)
- attributes = { xmlns: UNITSML_NS, "xml:id": unit_id(norm_text) }
- attributes[:dimensionURL] = "##{dim_id(dims)}" if dims
- unit_node = ox_element("Unit", attributes: attributes)
- nodes = Array(unitsystem(units))
- nodes += Array(unitname(units, norm_text, name))
- nodes += Array(unitsymbols(formula))
- nodes += Array(rootunits(units))
- Ox.dump(update_nodes(unit_node, nodes))
+ attributes = {
+ id: unit_id(norm_text),
+ system: unitsystem(units),
+ name: unitname(units, norm_text, name),
+ symbol: unitsymbols(formula),
+ root_units: rootunits(units),
+ }
+ attributes[:dimension_url] = "##{dim_id(dims)}" if dims
+ Model::Unit.new(attributes).to_xml
.gsub("<", "<")
.gsub(">", ">")
.gsub("&", "&")
@@ -156,8 +160,9 @@ def unit(units, formula, dims, norm_text, name)
end
def unitname(units, text, name)
- name ||= Unitsdb.units[text] ? Unit.new(text).enumerated_name : text
- ox_element("UnitName", attributes: { "xml:lang": "en" }) << name
+ Model::Units::Name.new(
+ name: unit_instance(text)&.unit_name&.first || text
+ )
end
def postprocess_normtext(units)
@@ -169,46 +174,43 @@ def display_exp(unit)
end
def unitsymbols(formula)
- [
- (ox_element("UnitSymbol", attributes: { type: "HTML" }) << formula.to_html),
- (ox_element("UnitSymbol", attributes: { type: "MathMl" }) << Ox.parse(formula.to_mathml)),
- ]
+ %w[HTML MathMl].map do |lang|
+ Model::Units::Symbol.new(type: lang, content: formula.public_send(:"to_#{lang.downcase}"))
+ end
end
def unitsystem(units)
ret = []
if units.any? { |u| u.system_name != "SI" }
- ret << ox_element("UnitSystem", attributes: { name: "not_SI", type: "not_SI", "xml:lang": 'en-US' })
+ ret << Model::Units::System.new(name: "not_SI", type: "not_SI")
end
if units.any? { |u| u.system_name == "SI" }
if units.size == 1
base = units[0].system_type == "SI-base"
base = true if units[0].unit_name == "g" && units[0]&.prefix_name == "k"
end
- ret << ox_element("UnitSystem", attributes: { name: "SI", type: (base ? 'SI_base' : 'SI_derived'), "xml:lang": 'en-US' })
+ ret << Model::Units::System.new(name: "SI", type: (base ? 'SI_base' : 'SI_derived'))
end
ret
end
def dimension(norm_text)
- return unless fields(norm_text)&.dig("dimension_url")
-
- dim_id = fields(norm_text).dig("dimension_url").sub("#", '')
- dim_node = ox_element("Dimension", attributes: { xmlns: UNITSML_NS, "xml:id": dim_id })
- Ox.dump(
- update_nodes(
- dim_node,
- dimid2dimensions(dim_id)&.compact&.map { |u| dimension1(u) }
- )
- )
+ dim_url = unit_instance(norm_text)&.dimension_url
+ return unless dim_url
+
+ dim_id = dim_url.sub("#", '')
+ dim_attrs = { id: dim_id }
+ dimid2dimensions(dim_id)&.compact&.each { |u| dimension1(u, dim_attrs) }
+ Model::Dimension.new(dim_attrs).to_xml
end
- def dimension1(dim)
- attributes = {
+ def dimension1(dim, dims_hash)
+ dim_name = dim[:dimension]
+ dim_klass = Model::DimensionQuantities.const_get(dim_name)
+ dims_hash[underscore(dim_name).to_sym] = dim_klass.new(
symbol: dim[:symbol],
- powerNumerator: float_to_display(dim[:exponent])
- }
- ox_element(dim[:dimension], attributes: attributes)
+ power_numerator: float_to_display(dim[:exponent])
+ )
end
def float_to_display(float)
@@ -216,97 +218,79 @@ def float_to_display(float)
end
def dimid2dimensions(normtext)
- dims ||= Unitsdb.load_dimensions[normtext]
- dims&.keys&.reject { |d| d.is_a?(Symbol) }&.map do |k|
- humanized = k.split("_").map(&:capitalize).join
+ dims = Unitsdb.dimensions.find_by_id(normtext)
+ dims&.processed_keys&.map do |processed_key|
+ humanized = processed_key.split("_").map(&:capitalize).join
next unless DIMS_VECTOR.include?(humanized)
+ dim_quantity = dims.public_send(processed_key)
{
dimension: humanized,
- symbol: dims.dig(k, "symbol"),
- exponent: dims.dig(k, "powerNumerator")
+ symbol: dim_quantity.symbol,
+ exponent: dim_quantity.power_numerator,
}
end
end
def prefixes(units)
uniq_prefixes = units.map { |unit| unit.prefix }.compact.uniq {|d| d.prefix_name }
- uniq_prefixes.map do |p|
- prefix_attr = { xmlns: UNITSML_NS, prefixBase: p&.base, prefixPower: p&.power, "xml:id": p&.id }
- prefix_node = ox_element("Prefix", attributes: prefix_attr)
- contents = []
- contents << (ox_element("PrefixName", attributes: { "xml:lang": "en" }) << p&.name)
- contents << (ox_element("PrefixSymbol", attributes: { type: "ASCII" }) << p&.to_asciimath)
- contents << (ox_element("PrefixSymbol", attributes: { type: "unicode" }) << p&.to_unicode)
- contents << (ox_element("PrefixSymbol", attributes: { type: "LaTex" }) << p&.to_latex)
- contents << (ox_element("PrefixSymbol", attributes: { type: "HTML" }) << p&.to_html)
- Ox.dump(update_nodes(prefix_node, contents)).gsub("&", "&")
+ uniq_prefixes.map do |prefix|
+ prefix_attrs = { prefix_base: prefix&.base, prefix_power: prefix&.power, id: prefix&.id }
+ type_and_methods = { ASCII: :to_asciimath, unicode: :to_unicode, LaTex: :to_latex, HTML: :to_html }
+ prefix_attrs[:name] = Model::Prefixes::Name.new(content: prefix&.name)
+ prefix_attrs[:symbol] = type_and_methods.map do |type, method_name|
+ Model::Prefixes::Symbol.new(
+ type: type,
+ content: prefix&.public_send(method_name),
+ )
+ end
+ Model::Prefix.new(prefix_attrs).to_xml.gsub("&", "&")
end.join("\n")
end
def rootunits(units)
return if units.size == 1 && !units[0].prefix
- root_unit = ox_element("RootUnits")
- units.each do |u|
- attributes = { unit: u.enumerated_name }
- attributes[:prefix] = u.prefix_name if u.prefix
- u.power_numerator && u.power_numerator != "1" and
- attributes[:powerNumerator] = u.power_numerator
- root_unit << ox_element("EnumeratedRootUnit", attributes: attributes)
+ enum_root_units = units.map do |unit|
+ attributes = { unit: unit.enumerated_name }
+ attributes[:prefix] = unit.prefix_name if unit.prefix
+ unit.power_numerator && unit.power_numerator != "1" and
+ attributes[:power_numerator] = unit.power_numerator
+ Model::Units::EnumeratedRootUnit.new(attributes)
end
- root_unit
+ Model::Units::RootUnits.new(enumerated_root_unit: enum_root_units)
end
def unit_id(text)
norm_text = text
text = text&.gsub(/[()]/, "")
- /-$/.match(text) and return Unitsdb.prefixes[text.sub(/-$/, "")][:id]
- unit_hash = Unitsdb.units[norm_text]
- "U_#{unit_hash ? unit_hash[:id]&.gsub(/'/, '_') : norm_text&.gsub(/\*/, '.')&.gsub(/\^/, '')}"
+ unit = unit_instance(norm_text)
+ "U_#{unit ? unit.id&.gsub(/'/, '_') : norm_text&.gsub(/\*/, '.')&.gsub(/\^/, '')}"
end
def dimension_components(dims)
return if dims.nil? || dims.empty?
- attributes = { xmlns: UNITSML_NS, "xml:id": dim_id(dims) }
- dim_node = ox_element("Dimension", attributes: attributes)
- Ox.dump(update_nodes(dim_node, dims.map { |u| dimension1(u) }))
+ dim_attrs = { id: dim_id(dims) }
+ dims.map { |u| dimension1(u, dim_attrs) }
+ Model::Dimension.new(dim_attrs).to_xml
end
def quantity(normtext, quantity)
- units = Unitsdb.units
- quantity_references = units.dig(normtext, :fields, "quantity_reference")
- return unless units[normtext] && quantity_references.size == 1 ||
- Unitsdb.quantities[quantity]
-
- id = quantity || quantity_references&.first&.dig("url")
- attributes = { xmlns: UNITSML_NS, "xml:id": id.sub('#', '') }
- dim_url = units.dig(normtext, :fields, "dimension_url")
- dim_url and attributes[:dimensionURL] = "#{dim_url}"
- attributes[:quantityType] = "base"
- quantity_element = ox_element("Quantity", attributes: attributes)
- Ox.dump(update_nodes(quantity_element, quantity_name(id.sub('#', ''))))
+ unit = unit_instance(normtext)
+ return unless unit && unit.quantity_reference.size == 1 ||
+ quantity_instance(quantity)
+
+ id = (quantity || unit.quantity_reference&.first&.url).sub('#', '')
+ dim_url = unit.dimension_url
+ attributes = { id: id, name: quantity_name(id), dimension_url: dim_url }
+ Model::Quantity.new(attributes).to_xml
end
def quantity_name(id)
- ret = []
- Unitsdb.quantities[id]&.dig("quantity_name")&.each do |q|
- node = (ox_element("QuantityName", attributes: { "xml:lang": "en-US" }) << q)
- ret << node
+ quantity_instance(id)&.quantity_name&.map do |content|
+ Model::Quantities::Name.new(content: content)
end
- ret
- end
-
- def ox_element(node, attributes: [])
- element = Ox::Element.new(node)
- attributes&.each { |attr_key, attr_value| element[attr_key] = attr_value }
- element
- end
-
- def update_nodes(element, nodes)
- nodes&.each { |node| element << node unless node.nil? }
- element
end
def string_to_html_entity(string)
@@ -321,6 +305,14 @@ def html_entity_to_unicode(string)
entities = HTMLEntities.new
entities.decode(string)
end
+
+ def underscore(str)
+ str.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
+ end
+
+ def class_name(klass)
+ klass.name.split("::").last
+ end
end
end
end
diff --git a/spec/unitsml/conv/mathml_spec.rb b/spec/unitsml/conv/mathml_spec.rb
index fe52f54..92bf7fe 100644
--- a/spec/unitsml/conv/mathml_spec.rb
+++ b/spec/unitsml/conv/mathml_spec.rb
@@ -2,6 +2,8 @@
RSpec.describe Unitsml::Parser do
+ before(:all) { Lutaml::Model::Config.xml_adapter_type = :ox }
+
subject(:formula) { described_class.new(exp).parse.to_mathml }
context "contains Unitsml #1 example" do
diff --git a/spec/unitsml/conv/xml_spec.rb b/spec/unitsml/conv/xml_spec.rb
index 2651e50..8c1f39f 100644
--- a/spec/unitsml/conv/xml_spec.rb
+++ b/spec/unitsml/conv/xml_spec.rb
@@ -2,6 +2,8 @@
RSpec.describe Unitsml::Parser do
+ before(:all) { Lutaml::Model::Config.xml_adapter_type = :ox }
+
subject(:formula) { described_class.new(exp).parse.to_xml }
context "contains Unitsml #1 example" do
diff --git a/spec/unitsml/parser_spec.rb b/spec/unitsml/parser_spec.rb
index be14096..f6031ae 100644
--- a/spec/unitsml/parser_spec.rb
+++ b/spec/unitsml/parser_spec.rb
@@ -9,14 +9,12 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
+ Unitsml::Unit.new("K"),
+ Unitsml::Extender.new("/"),
Unitsml::Formula.new([
- Unitsml::Unit.new("K"),
- Unitsml::Extender.new("/"),
- Unitsml::Formula.new([
- Unitsml::Unit.new("g", "-1", prefix: Unitsml::Prefix.new("k")),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("m", "-1"),
- ])
+ Unitsml::Unit.new("g", "-1", prefix: Unitsml::Prefix.new("k")),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("m", "-1"),
])
],
explicit_value: nil,
@@ -51,11 +49,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new(
[
- Unitsml::Formula.new([
- Unitsml::Unit.new("cal_th"),
- Unitsml::Extender.new("/"),
- Unitsml::Unit.new("m", "-2", prefix: Unitsml::Prefix.new("c")),
- ])
+ Unitsml::Unit.new("cal_th"),
+ Unitsml::Extender.new("/"),
+ Unitsml::Unit.new("m", "-2", prefix: Unitsml::Prefix.new("c")),
],
explicit_value: { name: "langley" },
norm_text: "cal_th/cm^2",
@@ -89,11 +85,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new(
[
- Unitsml::Formula.new([
- Unitsml::Unit.new("m", prefix: Unitsml::Prefix.new("c")),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("s", "-2")
- ])
+ Unitsml::Unit.new("m", prefix: Unitsml::Prefix.new("c")),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("s", "-2")
],
explicit_value: { symbol: "cm cdot s^-2"},
root: true,
@@ -110,11 +104,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new(
[
- Unitsml::Formula.new([
- Unitsml::Unit.new("m", prefix: Unitsml::Prefix.new("c")),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("s", "-2"),
- ])
+ Unitsml::Unit.new("m", prefix: Unitsml::Prefix.new("c")),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("s", "-2"),
],
explicit_value: { multiplier: "xx" },
norm_text: "cm*s^-2",
@@ -131,11 +123,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new(
[
- Unitsml::Formula.new([
- Unitsml::Unit.new("m", prefix: Unitsml::Prefix.new("m")),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("s", "-2"),
- ]),
+ Unitsml::Unit.new("m", prefix: Unitsml::Prefix.new("m")),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("s", "-2"),
],
root: true,
orig_text: "mm*s^-2",
@@ -302,11 +292,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
- Unitsml::Formula.new([
- Unitsml::Unit.new("g", prefix: Unitsml::Prefix.new("k")),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("s", "-2")
- ])
+ Unitsml::Unit.new("g", prefix: Unitsml::Prefix.new("k")),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("s", "-2")
],
norm_text: "kg*s^-2",
orig_text: "kg*s^-2",
@@ -396,11 +384,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
- Unitsml::Formula.new([
- Unitsml::Unit.new("A"),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("C", "3"),
- ])
+ Unitsml::Unit.new("A"),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("C", "3"),
],
norm_text: "A*C^3",
orig_text: "A*C^3",
@@ -415,11 +401,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
- Unitsml::Formula.new([
- Unitsml::Unit.new("A"),
- Unitsml::Extender.new("/"),
- Unitsml::Unit.new("C", "3"),
- ])
+ Unitsml::Unit.new("A"),
+ Unitsml::Extender.new("/"),
+ Unitsml::Unit.new("C", "3"),
],
norm_text: "A/C^-3",
orig_text: "A/C^-3",
@@ -434,14 +418,12 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
+ Unitsml::Unit.new("J"),
+ Unitsml::Extender.new("/"),
Unitsml::Formula.new([
- Unitsml::Unit.new("J"),
- Unitsml::Extender.new("/"),
- Unitsml::Formula.new([
- Unitsml::Unit.new("g", "-1", prefix: Unitsml::Prefix.new("k")),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("K", "-1"),
- ])
+ Unitsml::Unit.new("g", "-1", prefix: Unitsml::Prefix.new("k")),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("K", "-1"),
])
],
norm_text: "J/kg*K",
@@ -472,11 +454,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
- Unitsml::Formula.new([
- Unitsml::Unit.new("W", prefix: Unitsml::Prefix.new("m")),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("m", "-2", prefix: Unitsml::Prefix.new("c")),
- ])
+ Unitsml::Unit.new("W", prefix: Unitsml::Prefix.new("m")),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("m", "-2", prefix: Unitsml::Prefix.new("c")),
],
norm_text: "mW*cm(-2)",
orig_text: "mW*cm(-2)",
@@ -491,11 +471,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
- Unitsml::Formula.new([
- Unitsml::Dimension.new("dim_Theta"),
- Unitsml::Extender.new("*"),
- Unitsml::Dimension.new("dim_L", "2"),
- ])
+ Unitsml::Dimension.new("dim_Theta"),
+ Unitsml::Extender.new("*"),
+ Unitsml::Dimension.new("dim_L", "2"),
],
norm_text: "dim_Theta*dim_L^2",
orig_text: "dim_Theta*dim_L^2",
@@ -510,11 +488,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
- Unitsml::Formula.new([
- Unitsml::Dimension.new("dim_Theta", "10"),
- Unitsml::Extender.new("*"),
- Unitsml::Dimension.new("dim_L", "2"),
- ])
+ Unitsml::Dimension.new("dim_Theta", "10"),
+ Unitsml::Extender.new("*"),
+ Unitsml::Dimension.new("dim_L", "2"),
],
norm_text: "dim_Theta^10*dim_L^2",
orig_text: "dim_Theta^10*dim_L^2",
@@ -529,11 +505,9 @@
it "returns parslet tree of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
- Unitsml::Formula.new([
- Unitsml::Unit.new("Hz", "10"),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("darcy", "-100"),
- ])
+ Unitsml::Unit.new("Hz", "10"),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("darcy", "-100"),
],
norm_text: "Hz^10*darcy^-100",
orig_text: "Hz^10*darcy^-100",
@@ -548,11 +522,9 @@
it "returns Unitsml::Formula of parsed Unitsml string" do
expected_value = Unitsml::Formula.new([
- Unitsml::Formula.new([
- Unitsml::Unit.new("W"),
- Unitsml::Extender.new("*"),
- Unitsml::Unit.new("m", "-2"),
- ])
+ Unitsml::Unit.new("W"),
+ Unitsml::Extender.new("*"),
+ Unitsml::Unit.new("m", "-2"),
],
norm_text: "W*m^(-2)",
orig_text: "W*m^(-2)",
diff --git a/unitsml.gemspec b/unitsml.gemspec
index 8685471..547de93 100644
--- a/unitsml.gemspec
+++ b/unitsml.gemspec
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib", "unitsdb/**/*.yaml"]
spec.add_dependency "htmlentities"
- spec.add_dependency "ox"
+ spec.add_dependency "mml"
spec.add_dependency "parslet"
+ spec.add_dependency "unitsdb"
end