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