lib/asciimath2unitsml/conv.rb in asciimath2unitsml-0.1.3 vs lib/asciimath2unitsml/conv.rb in asciimath2unitsml-0.2.0

- old
+ new

@@ -3,166 +3,66 @@ require "htmlentities" require "yaml" require "rsec" require_relative "string" require_relative "parse" +require_relative "render" +require_relative "unit" module Asciimath2UnitsML MATHML_NS = "http://www.w3.org/1998/Math/MathML".freeze UNITSML_NS = "http://unitsml.nist.gov/2005".freeze class Conv def initialize(options = {}) - @prefixes_id = read_yaml("../unitsdb/prefixes.yaml") - @prefixes = flip_name_and_id(@prefixes_id) - @quantities = read_yaml("../unitsdb/quantities.yaml") - @units_id = read_yaml("../unitsdb/units.yaml") - @units = flip_name_and_id(@units_id) - #temporary - @units[:degC][:render] = "&#xB0;C" - @units[:degF][:render] = "&#xB0;F" - @units[:Ohm][:render] = "&#x3A9;" + @dimensions_id = read_yaml("../unitsdb/dimensions.yaml").each_with_object({}) do |(k, v), m| + m[k.to_s] = UnitsDB::Dimension.new(k, v) + end + @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.each_with_object({}) do |(k, v), m| + v.symbolids.each { |x| m[x] = v.symbols_hash[x] } + end @parser = parser @multiplier = multiplier(options[:multiplier] || "\u00b7") end - def multiplier(x) - case x - when :space - { html: "&#xA0;", mathml: "<mo rspace='thickmathspace'>&#x2062;</mo>" } - when :nospace - { html: "", mathml: "<mo>&#x2062;</mo>" } - else - { html: HTMLEntities.new.encode(x), mathml: "<mo>#{HTMLEntities.new.encode(x)}</mo>" } - end - end - - def units_only(units) - units.reject { |u| u[:multiplier] } - end - - def unit_id(text) - text = text.gsub(/[()]/, "") - "U_" + - (@units[text.to_sym] ? @units[text.to_sym][:id] : text.gsub(/\*/, ".").gsub(/\^/, "")) - end - - def unit(units, origtext, normtext, dims) - dimid = dim_id(dims) - <<~END - <Unit xmlns='#{UNITSML_NS}' xml:id='#{unit_id(origtext)}'#{dimid ? " dimensionURL='##{dimid}'" : ""}> - #{unitsystem(units)} - #{unitname(units, normtext)} - #{unitsymbol(units)} - #{rootunits(units)} - </Unit> - END - end - - def unitsystem(units) - ret = [] - units = units_only(units) - units.any? { |x| @units[x[:unit].to_sym][:si] != true } and - ret << "<UnitSystem name='not_SI' type='not_SI' xml:lang='en-US'/>" - if units.any? { |x| @units[x[:unit].to_sym][:si] == true } - base = units.size == 1 && @units[units[0][:unit].to_sym][:type].include?("si-base") - ret << "<UnitSystem name='SI' type='#{base ? "SI_base" : "SI_derived"}' xml:lang='en-US'/>" - end - ret.join("\n") - end - - def unitname(units, text) - name = @units[text.to_sym] ? @units[text.to_sym][:name] : compose_name(units, text) - "<UnitName xml:lang='en'>#{name}</UnitName>" - end - - # TODO: compose name from the component units - def compose_name(units, text) - text - end - - def unitsymbol(units) - <<~END - <UnitSymbol type="HTML">#{htmlsymbol(units)}</UnitSymbol> - <UnitSymbol type="MathML">#{mathmlsymbolwrap(units)}</UnitSymbol> - END - end - - def render(unit) - #require "byebug"; byebug if unit == "degC" - @units[unit.to_sym][:render] || unit - end - - def htmlsymbol(units) - units.map do |u| - if u[:multiplier] then u[:multiplier] == "*" ? @multiplier[:html] : u[:multiplier] - else - u[:display_exponent] and exp = "<sup>#{u[:display_exponent].sub(/-/, "&#x2212;")}</sup>" - "#{u[:prefix]}#{render(u[:unit])}#{exp}" - end - end.join("") - end - - def mathmlsymbol(units) - exp = units.map do |u| - if u[:multiplier] then u[:multiplier] == "*" ? @multiplier[:mathml] : "<mo>#{u[:multiplier]}</mo>" - else - base = "<mi mathvariant='normal'>#{u[:prefix]}#{render(u[:unit])}</mi>" - if u[:display_exponent] - exp = "<mn>#{u[:display_exponent]}</mn>".sub(/<mn>-/, "<mo>&#x2212;</mo><mn>") - "<msup><mrow>#{base}</mrow><mrow>#{exp}</mrow></msup>" - else - base - end - end - end.join("") - end - - def mathmlsymbolwrap(units) - <<~END - <math xmlns='#{MATHML_NS}'> - <mrow>#{mathmlsymbol(units)}</mrow> - </math> - END - end - - def rootunits(units) - return if units.size == 1 - exp = units_only(units).map do |u| - prefix = " prefix='#{u[:prefix]}'" if u[:prefix] - exponent = " powerNumerator='#{u[:exponent]}'" if u[:exponent] && u[:exponent] != "1" - "<EnumeratedRootUnit unit='#{@units[u[:unit].to_sym][:name]}'#{prefix}#{exponent}/>" - end.join("\n") - <<~END - <RootUnits>#{exp}</RootUnits> - END - end - def prefix(units) - units.map { |u| u[:prefix] }.reject { |u| u.nil? }.uniq.map do |p1| - p = p1.to_sym + units.map { |u| u[:prefix] }.reject { |u| u.nil? }.uniq.map do |p| <<~END - <Prefix xmlns='#{UNITSML_NS}' prefixBase='#{@prefixes[p][:base]}' - prefixPower='#{@prefixes[p][:power]}' xml:id='#{@prefixes[p][:id]}'> - <PrefixName xml:lang="en">#{@prefixes[p][:name]}</PrefixName> - <PrefixSymbol type="ASCII">#{@prefixes[p][:symbol]}</PrefixSymbol> + <Prefix xmlns='#{UNITSML_NS}' prefixBase='#{@prefixes[p].base}' + prefixPower='#{@prefixes[p].power}' xml:id='#{@prefixes[p].id}'> + <PrefixName xml:lang="en">#{@prefixes[p].name}</PrefixName> + <PrefixSymbol type="ASCII">#{@prefixes[p].ascii}</PrefixSymbol> + <PrefixSymbol type="unicode">#{@prefixes[p].unicode}</PrefixSymbol> + <PrefixSymbol type="LaTeX">#{@prefixes[p].latex}</PrefixSymbol> + <PrefixSymbol type="HTML">#{HTMLEntities.new.encode(@prefixes[p].html, :basic)}</PrefixSymbol> </Prefix> END end.join("\n") end - def dimension(dims) + def dimension_components(dims) return if dims.nil? || dims.empty? <<~END <Dimension xmlns='#{UNITSML_NS}' xml:id="#{dim_id(dims)}"> #{dims.map { |u| dimension1(u) }.join("\n") } </Dimension> END end def units2dimensions(units) - norm = normalise_units(units) + norm = decompose_units(units) return if norm.any? { |u| u[:unit] == "unknown" || u[:prefix] == "unknown" } norm.map do |u| { dimension: U2D[u[:unit]][:dimension], unit: u[:unit], exponent: u[:exponent] || 1, @@ -174,15 +74,20 @@ %(<#{u[:dimension]} symbol="#{u[:symbol]}" powerNumerator="#{u[:exponent]}"/>) end def dim_id(dims) return nil if dims.nil? || dims.empty? + dimhash = dims.each_with_object({}) { |h, m| m[h[:dimension]] = h } + dimsvector = %w(Length Mass Time ElectricCurrent ThermodynamicTemperature + AmountOfSubstance LuminousIntensity PlaneAngle) + .map { |h| dimhash.dig(h, :exponent) }.join(":") + id = @dimensions_id&.values&.select { |d| d.vector == dimsvector }&.first&.id and return id.to_s "D_" + dims.map { |d| U2D[d[:unit]][:symbol] + (d[:exponent] == 1 ? "" : d[:exponent].to_s) }.join("") end - def normalise_units(units) - gather_units(units_only(units).map { |u| normalise_unit(u) }.flatten) + def decompose_units(units) + gather_units(units_only(units).map { |u| decompose_unit(u) }.flatten) end def gather_units(units) units.sort { |a, b| a[:unit] <=> b[:unit] }.each_with_object([]) do |k, m| if m.empty? || m[-1][:unit] != k[:unit] then m << k @@ -192,39 +97,82 @@ exponent: (k[:exponent]&.to_i || 1) + (m[-1][:exponent]&.to_i || 1) } end end end - def normalise_unit(u) - if @units[u[:unit].to_sym][:type]&.include?("si-base") then u - elsif !@units[u[:unit].to_sym][:bases] then { prefix: u[:prefix], unit: "unknown", exponent: u[:exponent] } + # 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] == "g" then u + elsif @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].to_sym][:bases].each_with_object([]) do |k, m| - m << { prefix: k["prefix"] ? - combine_prefixes(@prefixes_id[k["prefix"]], @prefixes[u[:prefix]]) : u[:prefix], - unit: @units_id[k["id"].to_sym][:symbol], - exponent: (k["power"]&.to_i || 1) * (u[:exponent]&.to_i || 1) } + @units[u[:unit]].si_derived_bases.each_with_object([]) do |k, m| + m << { prefix: !k[:prefix].nil? && !k[:prefix].empty? ? + combine_prefixes(@prefixes_id[k[:prefix]], @prefixes[u[:prefix]]) : u[:prefix], + unit: @units_id[k[:id]].symbolid, + exponent: (k[:power]&.to_i || 1) * (u[:exponent]&.to_i || 1) } end end end def combine_prefixes(p1, p2) return nil if p1.nil? && p2.nil? - return p1[:symbol] if p2.nil? - return p2[:symbol] if p1.nil? - return "unknown" if p1[:base] != p2[:base] + 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[:symbol] if p[:base] == p1[:base] && p[:power] == p1[:power] + p2[:power] + return p.symbolid if p.base == p1.base && p.power == p1.power + p2.power end "unknown" end - def unitsml(units, origtext, normtext) + def quantityname(id) + ret = "" + @quantities[id].names.each do |q| + ret += %(<QuantityName xml:lang="en-US">#{q}</QuantityName>) + end + ret + end + + def quantity(normtext, quantity) + return unless @units[normtext] && @units[normtext].quantities.size == 1 || @quantities[quantity] + id = quantity || @units[normtext].quantities.first + dim = %( dimensionURL="##{@units[normtext].dimension}") if @units[normtext]&.dimension + <<~END + <Quantity xmlns='#{UNITSML_NS}' xml:id="#{id}"#{dim} quantityType="base"> + #{quantityname(id)} + </Quantity> + END + end + + def dimid2dimensions(normtext) + @dimensions_id[normtext].keys.map do |k| + { dimension: k, + symbol: U2D.values.select { |v| v[:dimension] == k }.first[:symbol], + exponent: @dimensions_id[normtext].exponent(k) } + end + end + + def dimension(normtext) + return unless @units[normtext]&.dimension + dims = dimid2dimensions(@units[normtext]&.dimension) + <<~END + <Dimension xmlns='#{UNITSML_NS}' xml:id="#{@units[normtext]&.dimension}"> + #{dims.map { |u| dimension1(u) }.join("\n") } + </Dimension> + END + end + + def unitsml(units, origtext, normtext, quantity, name) dims = units2dimensions(units) <<~END - #{unit(units, origtext, normtext, dims)} + #{unit(units, origtext, normtext, dims, name)} #{prefix(units)} - #{dimension(dims)} + #{dimension(normtext)} + #{dimension_components(dims)} + #{quantity(normtext, quantity)} END end end end