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] = "°C"
- @units[:degF][:render] = "°F"
- @units[:Ohm][:render] = "Ω"
+ @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: " ", mathml: "<mo rspace='thickmathspace'>⁢</mo>" }
- when :nospace
- { html: "", mathml: "<mo>⁢</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(/-/, "−")}</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>−</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