require "asciimath" require "nokogiri" require "htmlentities" require "yaml" require "rsec" require_relative "read" require_relative "dimensions" require_relative "string" require_relative "parse" require_relative "render" require_relative "unit" require_relative "validate" module Asciimath2UnitsML MATHML_NS = "http://www.w3.org/1998/Math/MathML".freeze UNITSML_NS = "https://schema.unitsml.org/unitsml/1.0".freeze class Conv def initialize(options = {}) @dimensions_id = read_yaml("../unitsdb/dimensions.yaml") .each_with_object({}) do |(k, v), m| m[k.to_s] = UnitsDB::Dimension.new(k, v) end @dimensions = flip_name_and_symbols(@dimensions_id) @prefixes_id = read_yaml("../unitsdb/prefixes.yaml") .each_with_object({}) do |(k, v), m| m[k] = UnitsDB::Prefix.new(k, v) end @prefixes = flip_name_and_symbol(@prefixes_id) @quantities = read_yaml("../unitsdb/quantities.yaml") .each_with_object({}) do |(k, v), m| m[k.to_s] = UnitsDB::Quantity.new(k, v) end @units_id = read_yaml("../unitsdb/units.yaml") .each_with_object({}) do |(k, v), m| m[k.to_s] = UnitsDB::Unit.new(k.to_s, v) end @units = flip_name_and_symbols(@units_id) @symbols = @units.merge(@dimensions).each_with_object({}) do |(_k, v), m| v.symbolids.each { |x| m[x] = v.symbols_hash[x] } end @parser, @dim_parser = parsers @multiplier = multiplier(options[:multiplier] || "\u22c5") end def float_to_display(float) float.to_f.round(1).to_s.sub(/\.0$/, "") end def prefix(units) units.map { |u| u[:prefix] }.reject(&:nil?).uniq.map do |p| <<~XML #{@prefixes[p].name} #{@prefixes[p].ascii} #{@prefixes[p].unicode} #{@prefixes[p].latex} #{htmlent @prefixes[p].html} XML end.join("\n") end def decompose_units(units) gather_units(units_only(units).map { |u| decompose_unit(u) }.flatten) end def gather_units(units) if units[0][:dim] then gather_dimensions(units) else gather_units1(units) end end def gather_units1(units) units.sort_by { |a| a[:unit] }.each_with_object([]) do |k, m| if m.empty? || m[-1][:unit] != k[:unit] then m << k else m[-1] = { prefix: combine_prefixes( @prefixes[m[-1][:prefix]], @prefixes[k[:prefix]] ), unit: m[-1][:unit], exponent: (k[:exponent]&.to_f || 1) + (m[-1][:exponent]&.to_f || 1), } end end end def gather_dimensions(units) units.sort_by { |a| a[:dim] }.each_with_object([]) do |k, m| if m.empty? || m[-1][:dim] != k[:dim] then m << k else m[-1] = { dim: m[-1][:dim], exponent: (k[:exponent]&.to_f || 1) + (m[-1][:exponent]&.to_f || 1), } end end end # treat g not kg as base unit: we have stripped the prefix k in parsing # reduce units down to basic units def decompose_unit(u) if u[:unit].nil? || u[:unit] == "g" || @units[u[:unit]].system_type == "SI_base" then u elsif !@units[u[:unit]].si_derived_bases { prefix: u[:prefix], unit: "unknown", exponent: u[:exponent] } else @units[u[:unit]].si_derived_bases.each_with_object([]) do |k, m| prefix = if !k[:prefix].nil? && !k[:prefix].empty? combine_prefixes(@prefixes_id[k[:prefix]], @prefixes[u[:prefix]]) else u[:prefix] end m << { prefix: prefix, unit: @units_id[k[:id]].symbolid, exponent: (k[:power]&.to_i || 1) * (u[:exponent]&.to_f || 1) } end end end def combine_prefixes(p1, p2) return nil if p1.nil? && p2.nil? return p1.symbolid if p2.nil? return p2.symbolid if p1.nil? return "unknown" if p1.base != p2.base @prefixes.each do |_, p| return p.symbolid if p.base == p1.base && p.power == p1.power + p2.power end "unknown" end def quantityname(id) ret = "" @quantities[id].names.each do |q| ret += %(#{q}) end ret end def quantity(normtext, quantity) return unless @units[normtext] && @units[normtext].quantities.size == 1 || @quantities[quantity] id = quantity || @units[normtext].quantities.first @units[normtext]&.dimension and dim = %( dimensionURL="##{@units[normtext].dimension}") <<~XML #{quantityname(id)} XML end def unitsml(units, origtext, normtext, quantity, name) dims = units2dimensions(units) <<~XML #{unit(units, origtext, normtext, dims, name)} #{prefix(units)} #{dimension(normtext)} #{dimension_components(dims)} #{quantity(normtext, quantity)} XML end end end