class Asciimath2UnitsML::Conv

Constants

Dim2D
U2D

Public Class Methods

new(options = {}) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 19
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

Public Instance Methods

Asciimath2UnitsML(expression) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 115
def Asciimath2UnitsML(expression)
  xml = Nokogiri::XML(asciimath2mathml(expression))
  MathML2UnitsML(xml).to_xml
end
MathML2UnitsML(xml) click to toggle source

www.w3.org/TR/mathml-units/ section 2: delimit number Invisible-Times unit

# File lib/asciimath2unitsml/parse.rb, line 122
def MathML2UnitsML(xml)
  xml.is_a? String and xml = Nokogiri::XML(xml)
  xml.xpath(".//m:mtext", "m" => MATHML_NS).each do |x|
    next unless %r{^unitsml\(.+\)$}.match?(x.text)

    text = x.text.sub(%r{^unitsml\((.+)\)$}m, "\\1")
    units, origtext, normtext, quantity, name, symbol, multiplier =
      parse(text)
    rendering = if symbol
                  embeddedmathml(asciimath2mathml(symbol))
                else
                  mathmlsymbol(units, false, multiplier)
                end
    x.replace("#{delimspace(rendering, x)}"\
              "<mrow xref='#{unit_id(origtext)}'>#{rendering}</mrow>\n"\
              "#{unitsml(units, origtext, normtext, quantity, name)}")
  end
  dedup_ids(xml)
end
ambig_units() click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 187
def ambig_units
  u = @units_id.each_with_object({}) do |(_k, v), m|
    v.symbolids.each do |x|
      next if %r{[*/^]}.match?(x)
      next unless v.symbols_hash[x][:html] != x

      m[v.symbols_hash[x][:html]] ||= []
      m[v.symbols_hash[x][:html]] << x
    end
  end
  u.each_key { |k| u[k] = u[k].unshift(k) if @symbols.dig(k, :html) == k }
  render_ambig_units(u)
end
asciimath2mathml(expression) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 174
def asciimath2mathml(expression)
  AsciiMath::MathMLBuilder.new(msword: true).append_expression(
    AsciiMath.parse(HTMLEntities.new.decode(expression)).ast,
  ).to_s.gsub(/<math>/, "<math xmlns='#{MATHML_NS}'>")
end
combine_prefixes(p1, p2) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 126
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
compose_name(_units, text) click to toggle source

TODO: compose name from the component units

# File lib/asciimath2unitsml/unit.rb, line 61
def compose_name(_units, text)
  text
end
decompose_unit(u) click to toggle source

treat g not kg as base unit: we have stripped the prefix k in parsing reduce units down to basic units

# File lib/asciimath2unitsml/conv.rb, line 106
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
decompose_units(units) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 65
def decompose_units(units)
  gather_units(units_only(units).map { |u| decompose_unit(u) }.flatten)
end
dedup_ids(xml) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 160
def dedup_ids(xml)
  %w(Unit Dimension Prefix Quantity).each do |t|
    xml.xpath(".//m:#{t}/@xml:id", "m" => UNITSML_NS).map(&:text)
      .uniq.each do |v|
      xml.xpath(".//*[@xml:id = '#{v}']").each_with_index do |n, i|
        next if i.zero?

        n.remove
      end
    end
  end
  xml
end
delimspace(rendering, elem) click to toggle source

if previous sibling's last descendent non-whitespace is MathML and mn or mi, no space

# File lib/asciimath2unitsml/parse.rb, line 144
def delimspace(rendering, elem)
  prec_text_elem =
    elem.xpath("./preceding-sibling::*[namespace-uri() = '#{MATHML_NS}']/"\
               "descendant::text()[normalize-space()!='']"\
               "[last()]/parent::*").last
  return "" if prec_text_elem.nil? ||
    !%w(mn mi).include?(prec_text_elem&.name)

  text = HTMLEntities.new.encode(Nokogiri::XML("<mrow>#{rendering}</mrow>")
    .text.strip)
  if /\p{L}|\p{N}/.match?(text)
    "<mo rspace='thickmathspace'>&#x2062;</mo>"
  else "<mo>&#x2062;</mo>"
  end
end
dim_id(dims) click to toggle source
# File lib/asciimath2unitsml/dimensions.rb, line 68
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 do |d|
    (U2D.dig(d[:unit], :symbol) || Dim2D.dig(d[:id], :symbol)) +
      (d[:exponent] == 1 ? "" : float_to_display(d[:exponent]))
  end.join("")
end
dimension(normtext) click to toggle source
# File lib/asciimath2unitsml/dimensions.rb, line 95
    def dimension(normtext)
      return unless @units[normtext]&.dimension

      dims = dimid2dimensions(@units[normtext]&.dimension)
      <<~XML
        <Dimension xmlns='#{UNITSML_NS}' xml:id="#{@units[normtext]&.dimension}">
        #{dims.map { |u| dimension1(u) }.join("\n")}
        </Dimension>
      XML
    end
dimension1(dim) click to toggle source
# File lib/asciimath2unitsml/dimensions.rb, line 63
def dimension1(dim)
  %(<#{dim[:dimension]} symbol="#{dim[:symbol]}"
  powerNumerator="#{float_to_display(dim[:exponent])}"/>)
end
dimension_components(dims) click to toggle source
# File lib/asciimath2unitsml/dimensions.rb, line 3
    def dimension_components(dims)
      return if dims.nil? || dims.empty?

      <<~XML
        <Dimension xmlns='#{UNITSML_NS}' xml:id="#{dim_id(dims)}">
        #{dims.map { |u| dimension1(u) }.join("\n")}
        </Dimension>
      XML
    end
dimensions_parser(exponent, multiplier) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 13
def dimensions_parser(exponent, multiplier)
  dim1 = /#{@dimensions.keys.sort_by(&:length).reverse.join("|")}/.r
  dimension =
    seq("sqrt(", dim1, ")") { |x| { dim: x[1], display_exponent: "0.5" } } |
    seq(dim1, exponent._? & (multiplier | ")".r)) { |x| { dim: x[0], display_exponent: (x[1][0]) } } |
    seq(dim1, exponent._?).eof { |x| { dim: x[0], display_exponent: (x[1][0]) } }
  dimensions1 = "(".r >> lazy { dimensions } << ")" | dimension
  dimensions = dimensions1.join(multiplier) # rubocop:disable Style/RedundantAssignment
  dimensions
end
dimid2dimensions(normtext) click to toggle source
# File lib/asciimath2unitsml/dimensions.rb, line 87
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
display_exp(unit) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 97
def display_exp(unit)
  unit[:exponent] && unit[:exponent] != "1" ? "^#{unit[:exponent]}" : ""
end
embeddedmathml(mathml) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 180
def embeddedmathml(mathml)
  x = Nokogiri::XML(mathml)
  x.xpath(".//m:mi", "m" => MATHML_NS)
    .each { |mi| mi["mathvariant"] = "normal" }
  x.children.to_xml
end
flip_name_and_symbol(hash) click to toggle source
# File lib/asciimath2unitsml/read.rb, line 26
def flip_name_and_symbol(hash)
  hash.each_with_object({}) do |(_k, v), m|
    next if v.name.nil? || v.name.empty?

    m[v.symbolid] = v
  end
end
flip_name_and_symbols(hash) click to toggle source
# File lib/asciimath2unitsml/read.rb, line 34
def flip_name_and_symbols(hash)
  hash.each_with_object({}) do |(_k, v), m|
    next if v.name.nil? || v.name.empty?

    v.symbolids.each { |s| m[s] = v }
  end
end
float_to_display(float) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 46
def float_to_display(float)
  float.to_f.round(1).to_s.sub(/\.0$/, "")
end
gather_dimensions(units) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 91
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
gather_units(units) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 69
def gather_units(units)
  if units[0][:dim] then gather_dimensions(units)
  else gather_units1(units)
  end
end
gather_units1(units) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 75
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
html2adoc(elem) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 217
def html2adoc(elem)
  elem.gsub(%r{<i>}, "__").gsub(%r{</i>}, "__")
    .gsub(%r{<sup>}, "^").gsub(%r{</sup>}, "^")
    .gsub(%r{<sub>}, "~").gsub(%r{</sub>}, "~")
end
htmlent(xml) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 19
def htmlent(xml)
  HTMLEntities.new.decode(xml).split(/([<>&])/).map do |c|
    /[<>'"]/.match?(c) ? c : HTMLEntities.new.encode(c, :hexadecimal)
  end.join
end
htmlsymbol(units, normalise) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 25
def htmlsymbol(units, normalise)
  units.map do |u|
    if u[:multiplier]
      u[:multiplier] == "*" ? @multiplier[:html] : u[:multiplier]
    elsif u[:unit].nil? && u[:prefix]
      @prefixes[u[:prefix]].html
    else
      base = (u[:prefix] || "") +
        render(normalise ? @units[u[:unit]].symbolid : u[:unit], :html)
      htmlsymbol_exponent(u, base)
    end
  end.join
end
htmlsymbol_exponent(unit, base) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 39
def htmlsymbol_exponent(unit, base)
  if unit[:display_exponent] == "0.5"
    base = "&#x221a;#{base}"
  elsif unit[:display_exponent]
    exp = "<sup>#{unit[:display_exponent].sub(/-/, '&#x2212;')}</sup>"
    base += exp
  end
  base
end
mathmlsymbol(units, normalise, multiplier = nil) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 49
def mathmlsymbol(units, normalise, multiplier = nil)
  multiplier = multiplier ? "<mo>#{multiplier}</mo>" : @multiplier[:mathml]
  units.map do |u|
    if u[:multiplier]
      u[:multiplier] == "*" ? multiplier : "<mo>#{u[:multiplier]}</mo>"
    elsif u[:unit].nil? && u[:prefix]
      %(<mi mathvariant='normal'>#{htmlent(@prefixes[u[:prefix]].html)}</mi>)
    else
      mathmlsymbol1(u, normalise)
    end
  end.join
end
mathmlsymbol1(unit, normalise) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 62
def mathmlsymbol1(unit, normalise)
  base = if unit[:dim]
           render(normalise ? @dimensions[unit[:dim]].symbolid : unit[:dim],
                  :mathml)
         else
           render(normalise ? @units[unit[:unit]].symbolid : unit[:unit],
                  :mathml)
         end
  unit[:prefix] and base = mathmlsymbol1_prefixed(unit, base)
  mathmlsymbol_exponent(unit, base)
end
mathmlsymbol1_prefixed(unit, base) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 74
def mathmlsymbol1_prefixed(unit, base)
  prefix = htmlent(@prefixes[unit[:prefix]].html)
  if /<mi mathvariant='normal'>/.match?(base)
    base.sub(/<mi mathvariant='normal'>/,
             "<mi mathvariant='normal'>#{prefix}")
  else
    "<mrow><mi mathvariant='normal'>#{prefix}#{base}</mrow>"
  end
end
mathmlsymbol_exponent(unit, base) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 84
def mathmlsymbol_exponent(unit, base)
  if unit[:display_exponent] == "0.5"
    base = "<msqrt>#{base}</msqrt>"
  elsif unit[:display_exponent]
    exp = "<mn>#{unit[:display_exponent]}</mn>"
      .sub(/<mn>-/, "<mo>&#x2212;</mo><mn>")
    base = "<msup><mrow>#{base}</mrow><mrow>#{exp}</mrow></msup>"
  end
  base
end
mathmlsymbolwrap(units, normalise) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 95
    def mathmlsymbolwrap(units, normalise)
      <<~XML
        <math xmlns='#{MATHML_NS}'><mrow>#{mathmlsymbol(units, normalise)}</mrow></math>
      XML
    end
multiplier(val) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 3
def multiplier(val)
  case val
  when :space
    { html: "&#xA0;", mathml: "<mo rspace='thickmathspace'>&#x2062;</mo>" }
  when :nospace
    { html: "", mathml: "<mo>&#x2062;</mo>" }
  else
    { html: HTMLEntities.new.encode(val),
      mathml: "<mo>#{HTMLEntities.new.encode(val)}</mo>" }
  end
end
normalise_units(units) click to toggle source
# File lib/asciimath2unitsml/unit.rb, line 28
def normalise_units(units)
  units.map do |u|
    u1 = u.dup
    u1[:multiplier] and u1[:multiplier] = "*"
    u1[:exponent] and u1[:display_exponent] = u1[:exponent]
    u1
  end
end
parse(expr) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 48
def parse(expr)
  text = Array(expr.split(/,\s*/))
  if /dim_/.match?(text[0]) then parse_dimensions(text)
  else parse_units(text)
  end
end
parse_dimensions(text) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 65
def parse_dimensions(text)
  units = @dim_parser.parse!(text[0])
  if !units || Rsec::INVALID[units]
    raise Rsec::SyntaxError.new "error parsing UnitsML expression", x, 1, 0
  end

  Rsec::Fail.reset
  postprocess(units, text, false)
end
parse_units(text) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 55
def parse_units(text)
  units = @parser.parse!(text[0])
  if !units || Rsec::INVALID[units]
    raise Rsec::SyntaxError.new "error parsing UnitsML expression", x, 1, 0
  end

  Rsec::Fail.reset
  postprocess(units, text, true)
end
parsers() click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 4
def parsers
  exponent = /\^\(-?\d+\)/.r.map { |m| m.sub(/\^/, "").gsub(/[()]/, "") } |
    /\^-?\d+/.r.map { |m| m.sub(/\^/, "") }
  multiplier = %r{\*|//|/}.r.map { |x| { multiplier: x[0] } }
  units = units_parse(exponent, multiplier)
  dimensions = dimensions_parser(exponent, multiplier)
  [units.eof, dimensions.eof]
end
postprocess(units, text, is_units) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 75
def postprocess(units, text, is_units)
  units = postprocess1(units.flatten)
  normtext = postprocess_normtext(units, is_units)
  [units, text[0], normtext, postprocess_extr(text, "quantity"),
   postprocess_extr(text, "name"), postprocess_extr(text, "symbol"),
   postprocess_extr(text, "multiplier")]
end
postprocess1(units) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 101
def postprocess1(units)
  inverse = false
  units.each_with_object([]) do |u, m|
    if u[:multiplier]
      inverse = !inverse if u[:multiplier] == "/"
    else
      u[:exponent] =
        inverse ? "-#{u[:display_exponent] || '1'}" : u[:display_exponent]
      u[:exponent] = u[:exponent]&.sub(/^--+/, "")
    end
    m << u
  end
end
postprocess_extr(text, name) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 91
def postprocess_extr(text, name)
  text[1..-1]&.select do |x|
    /^#{name}:/.match(x)
  end&.first&.sub(/^#{name}:\s*/, "")
end
postprocess_normtext(units, is_units) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 83
def postprocess_normtext(units, is_units)
  units_only(units).each.map do |u|
    if is_units then "#{u[:prefix]}#{u[:unit]}#{display_exp(u)}"
    else "#{u[:dim]}#{display_exp(u)}"
    end
  end.join("*")
end
prefix(units) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 50
    def prefix(units)
      units.map { |u| u[:prefix] }.reject(&:nil?).uniq.map do |p|
        <<~XML
          <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">#{htmlent @prefixes[p].html}</PrefixSymbol>
          </Prefix>
        XML
      end.join("\n")
    end
quantity(normtext, quantity) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 146
    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
        <Quantity xmlns='#{UNITSML_NS}' xml:id="#{id}"#{dim} quantityType="base">
        #{quantityname(id)}
        </Quantity>
      XML
    end
quantityname(id) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 138
def quantityname(id)
  ret = ""
  @quantities[id].names.each do |q|
    ret += %(<QuantityName xml:lang="en-US">#{q}</QuantityName>)
  end
  ret
end
read_yaml(path) click to toggle source
# File lib/asciimath2unitsml/read.rb, line 3
def read_yaml(path)
  validate_yaml(symbolize_keys(YAML
    .load_file(File.join(File.join(File.dirname(__FILE__), path)))), path)
end
render(unit, style) click to toggle source
# File lib/asciimath2unitsml/render.rb, line 15
def render(unit, style)
  @symbols[unit][style] || unit
end
render_ambig_units(u) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 201
def render_ambig_units(u)
  maxcols = 0
  u.each { |_, v| maxcols = v.size if maxcols < v.size }
  puts %([cols="#{maxcols + 1}*"]\n|===\n|Symbol | Unit + ID #{'| ' * (maxcols - 1)}\n)
  puts "\n\n"
  u.keys.sort_by do |a|
    [-u[a].size, a.gsub(%r{&[^;]+;}, "")
      .gsub(/[^A-Za-z]/, "").downcase]
  end.each do |k|
    print "| #{html2adoc(k)} "
    u[k].sort_by(&:size).each { |v1| print "| #{@units[v1].name}: `#{v1}` " }
    puts "#{'| ' * (maxcols - u[k].size)}\n"
  end
  puts "|===\n"
end
rootunits(units) click to toggle source
# File lib/asciimath2unitsml/unit.rb, line 72
    def rootunits(units)
      return if units_only(units).any? { |x| x[:unit].nil? }
      return if units.size == 1 && !units[0][:prefix]

      exp = units_only(units).map do |u|
        prefix = " prefix='#{u[:prefix]}'" if u[:prefix]
        u[:exponent] && u[:exponent] != "1" and
          arg = " powerNumerator='#{u[:exponent]}'"
        "<EnumeratedRootUnit unit='#{@units[u[:unit]].name}'#{prefix}#{arg}/>"
      end.join("\n")
      <<~XML
        <RootUnits>#{exp}</RootUnits>
      XML
    end
symbol_key(val) click to toggle source
# File lib/asciimath2unitsml/validate.rb, line 50
def symbol_key(val)
  symbol = val[:unit_symbols]&.each_with_object([]) do |s, m|
    m << (s["id"] || s[:id])
  end || val.dig(:symbol, :ascii) || val[:symbol] # || val[:short]
  !symbol.nil? && val[:unit_symbols] && !symbol.is_a?(Array) and
    symbol = [symbol]
  symbol
end
symbolize_keys(hash) click to toggle source
# File lib/asciimath2unitsml/read.rb, line 8
def symbolize_keys(hash)
  return hash if hash.is_a? String

  hash.inject({}) do |result, (key, value)|
    new_key = case key
              when String then key.to_sym
              else key
              end
    new_value = case value
                when Hash then symbolize_keys(value)
                when Array then value.map { |m| symbolize_keys(m) }
                else value
                end
    result[new_key] = new_value
    result
  end
end
unit(units, _origtext, normtext, dims, name) click to toggle source
# File lib/asciimath2unitsml/unit.rb, line 13
    def unit(units, _origtext, normtext, dims, name)
      return if units_only(units).any? { |x| x[:unit].nil? }

      dimid = dim_id(dims)
      norm_units = normalise_units(units)
      <<~XML
        <Unit xmlns='#{UNITSML_NS}' xml:id='#{unit_id(normtext)}'#{dimid ? " dimensionURL='##{dimid}'" : ''}>
        #{unitsystem(units)}
        #{unitname(norm_units, normtext, name)}
        #{unitsymbol(norm_units)}
        #{rootunits(units)}
        </Unit>
      XML
    end
unit_id(text) click to toggle source
# File lib/asciimath2unitsml/unit.rb, line 7
def unit_id(text)
  text = text.gsub(/[()]/, "")
  /-$/.match(text) and return @prefixes[text.sub(/-$/, "")].id
  "U_#{@units[text] ? @units[text].id.gsub(/'/, '_') : text.gsub(/\*/, '.').gsub(/\^/, '')}"
end
unitname(units, text, name) click to toggle source
# File lib/asciimath2unitsml/unit.rb, line 55
def unitname(units, text, name)
  name ||= @units[text] ? @units[text].name : compose_name(units, text)
  "<UnitName xml:lang='en'>#{name}</UnitName>"
end
units2dimensions(units) click to toggle source
# File lib/asciimath2unitsml/dimensions.rb, line 39
def units2dimensions(units)
  norm = decompose_units(units)
  return units2dimensions_dim_input(norm) if norm[0][:dim]
  return if norm.any? do |u|
    u[:unit] == "unknown" || u[:prefix] == "unknown" || u[:unit].nil?
  end

  norm.map do |u|
    { dimension: U2D[u[:unit]][:dimension],
      unit: u[:unit],
      exponent: u[:exponent] || 1,
      symbol: U2D[u[:unit]][:symbol] }
  end.sort { |a, b| U2D[a[:unit]][:order] <=> U2D[b[:unit]][:order] }
end
units2dimensions_dim_input(norm) click to toggle source
# File lib/asciimath2unitsml/dimensions.rb, line 54
def units2dimensions_dim_input(norm)
  norm.map do |u|
    { dimension: Dim2D[u[:dim]][:dimension],
      exponent: u[:exponent] || 1,
      id: u[:dim],
      symbol: Dim2D[u[:dim]][:symbol] }
  end.sort { |a, b| Dim2D[a[:id]][:order] <=> Dim2D[b[:id]][:order] }
end
units_only(units) click to toggle source
# File lib/asciimath2unitsml/unit.rb, line 3
def units_only(units)
  units.reject { |u| u[:multiplier] }
end
units_parse(exponent, multiplier) click to toggle source
# File lib/asciimath2unitsml/parse.rb, line 24
def units_parse(exponent, multiplier)
  prefix2 = /#{@prefixes.keys.select { |x| x.size == 2 }.join("|")}/.r
  prefix1 = /#{@prefixes.keys.select { |x| x.size == 1 }.join("|")}/.r
  unit_keys = @units.keys.reject do |k|
    /\*|\^|\/|^1$/.match(k) || @units[k].prefixed
  end.map { |k| Regexp.escape(k) }
  unit1 = /#{unit_keys.sort_by(&:length).reverse.join("|")}/.r

  unit =
    seq("sqrt(", unit1, ")") { |x| { prefix: nil, unit: x[1], display_exponent: "0.5" } } |
    seq("sqrt(", prefix1, unit1, ")") { |x| { prefix: x[1], unit: x[2], display_exponent: "0.5" } } |
    seq("sqrt(", prefix2, unit1, ")") { |x| { prefix: x[1], unit: x[2], display_exponent: "0.5" } } |
    seq(unit1, exponent._? & (multiplier | ")".r)) { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0]) } } |
    seq(unit1, exponent._?).eof { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0]) } } |
    seq(prefix1, unit1, exponent._?) { |x| { prefix: x[0], unit: x[1], display_exponent: (x[2][0]) } } |
    seq(prefix2, unit1, exponent._?) { |x| { prefix: x[0], unit: x[1], display_exponent: (x[2][0]) } } |
    "1".r.map { |_| { prefix: nil, unit: "1", display_exponent: nil } }
  units1 = "(".r >> lazy { units } << ")" | unit
  units = seq(prefix2, "-") { |x| [{ prefix: x[0], unit: nil, display_exponent: nil }] } | # rubocop:disable Style/RedundantAssignment
    seq(prefix1, "-") { |x| [{ prefix: x[0], unit: nil, display_exponent: nil }] } |
    units1.join(multiplier)
  units
end
unitsml(units, origtext, normtext, quantity, name) click to toggle source
# File lib/asciimath2unitsml/conv.rb, line 160
    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
unitsymbol(units) click to toggle source
# File lib/asciimath2unitsml/unit.rb, line 65
    def unitsymbol(units)
      <<~XML
        <UnitSymbol type="HTML">#{htmlsymbol(units, true)}</UnitSymbol>
        <UnitSymbol type="MathML">#{mathmlsymbolwrap(units, true)}</UnitSymbol>
      XML
    end
unitsystem(units) click to toggle source

kg exception

# File lib/asciimath2unitsml/unit.rb, line 38
def unitsystem(units)
  return if units_only(units).any? { |x| x[:unit].nil? }

  ret = []
  units = units_only(units)
  units.any? { |x| @units[x[:unit]].system_name != "SI" } and
    ret << "<UnitSystem name='not_SI' type='not_SI' xml:lang='en-US'/>"
  if units.any? { |x| @units[x[:unit]].system_name == "SI" }
    base = units.size == 1 &&
      @units[units[0][:unit]].system_type == "SI-base"
    base = true if units.size == 1 && units[0][:unit] == "g" &&
      units[0][:prefix] == "k"
    ret << "<UnitSystem name='SI' type='#{base ? 'SI_base' : 'SI_derived'}' xml:lang='en-US'/>"
  end
  ret.join("\n")
end
validate_symbols(acc, val) click to toggle source
# File lib/asciimath2unitsml/validate.rb, line 28
def validate_symbols(acc, val)
  symbol = symbol_key(val)
  !symbol.nil? or
    raise StandardError.new "No symbol provided for unit: #{val}"
  Array(symbol)&.each do |s|
    acc[s] && s != "1" and
      raise StandardError.new "symbol #{s} is not unique in #{val}: "\
      "already used for #{acc[s]}"
    acc[s] = val
  end
  acc
end
validate_unit(unit) click to toggle source
# File lib/asciimath2unitsml/validate.rb, line 15
def validate_unit(unit)
  if unit[:quantity_reference]
    unit[:quantity_reference].is_a?(Array) or
      raise StandardError
        .new "No quantity_reference array provided for unit: #{unit}"
  end
  if unit[:unit_name]
    unit[:unit_name].is_a?(Array) or
      raise StandardError
        .new "No unit_name array provided for unit: #{unit}"
  end
end
validate_unit_symbol_cardinality(sym, key) click to toggle source
# File lib/asciimath2unitsml/validate.rb, line 41
def validate_unit_symbol_cardinality(sym, key)
  return true if sym.nil?

  !sym[:id].nil? && !sym[:ascii].nil? && !sym[:html].nil? &&
    !sym[:mathml].nil? && !sym[:latex].nil? &&
    !sym[:unicode].nil? and return true
  raise StandardError.new "malformed unit_symbol for #{key}: #{sym}"
end
validate_yaml(hash, path) click to toggle source
# File lib/asciimath2unitsml/validate.rb, line 3
def validate_yaml(hash, path)
  return hash if path == "../unitsdb/quantities.yaml"
  return hash if path == "../unitsdb/dimensions.yaml"

  hash.each_with_object({}) do |(k, v), m|
    path == "../unitsdb/units.yaml" and validate_unit(v)
    m = validate_symbols(m, v)
    v[:unit_symbols]&.each { |s| validate_unit_symbol_cardinality(s, k) }
  end
  hash
end