Class: Asciimath2UnitsML::Conv
- Inherits:
-
Object
- Object
- Asciimath2UnitsML::Conv
- Includes:
- Rsec::Helpers
- Defined in:
- lib/asciimath2unitsml/conv.rb,
lib/asciimath2unitsml/read.rb,
lib/asciimath2unitsml/unit.rb,
lib/asciimath2unitsml/parse.rb,
lib/asciimath2unitsml/render.rb,
lib/asciimath2unitsml/validate.rb,
lib/asciimath2unitsml/dimensions.rb
Constant Summary collapse
- U2D =
{ "m" => { dimension: "Length", order: 1, symbol: "L" }, "g" => { dimension: "Mass", order: 2, symbol: "M" }, "kg" => { dimension: "Mass", order: 2, symbol: "M" }, "s" => { dimension: "Time", order: 3, symbol: "T" }, "A" => { dimension: "ElectricCurrent", order: 4, symbol: "I" }, "K" => { dimension: "ThermodynamicTemperature", order: 5, symbol: "Theta" }, "degK" => { dimension: "ThermodynamicTemperature", order: 5, symbol: "Theta" }, "mol" => { dimension: "AmountOfSubstance", order: 6, symbol: "N" }, "cd" => { dimension: "LuminousIntensity", order: 7, symbol: "J" }, "deg" => { dimension: "PlaneAngle", order: 8, symbol: "phi" }, }.freeze
- Dim2D =
{ "dim_L" => U2D["m"], "dim_M" => U2D["g"], "dim_T" => U2D["s"], "dim_I" => U2D["A"], "dim_Theta" => U2D["K"], "dim_N" => U2D["mol"], "dim_J" => U2D["cd"], "dim_phi" => U2D["deg"], }.freeze
Instance Method Summary collapse
- #ambig_units ⇒ Object
- #asciimath2mathml(expression) ⇒ Object
- #Asciimath2UnitsML(expression) ⇒ Object
- #combine_prefixes(p1, p2) ⇒ Object
-
#compose_name(_units, text) ⇒ Object
TODO: compose name from the component units.
-
#decompose_unit(u) ⇒ Object
treat g not kg as base unit: we have stripped the prefix k in parsing reduce units down to basic units.
- #decompose_units(units) ⇒ Object
- #dedup_ids(xml) ⇒ Object
-
#delimspace(rendering, elem) ⇒ Object
if previous sibling’s last descendent non-whitespace is MathML and mn or mi, no space.
- #dim_id(dims) ⇒ Object
- #dimension(normtext) ⇒ Object
- #dimension1(dim) ⇒ Object
- #dimension_components(dims) ⇒ Object
- #dimensions_parser(exponent, multiplier) ⇒ Object
- #dimid2dimensions(normtext) ⇒ Object
- #display_exp(unit) ⇒ Object
- #embeddedmathml(mathml) ⇒ Object
- #flip_name_and_symbol(hash) ⇒ Object
- #flip_name_and_symbols(hash) ⇒ Object
- #float_to_display(float) ⇒ Object
- #gather_dimensions(units) ⇒ Object
- #gather_units(units) ⇒ Object
- #gather_units1(units) ⇒ Object
- #html2adoc(elem) ⇒ Object
- #htmlent(xml) ⇒ Object
- #htmlsymbol(units, normalise) ⇒ Object
- #htmlsymbol_exponent(unit, base) ⇒ Object
-
#initialize(options = {}) ⇒ Conv
constructor
A new instance of Conv.
-
#MathML2UnitsML(xml) ⇒ Object
www.w3.org/TR/mathml-units/ section 2: delimit number Invisible-Times unit.
- #mathmlsymbol(units, normalise, multiplier = nil) ⇒ Object
- #mathmlsymbol1(unit, normalise) ⇒ Object
- #mathmlsymbol1_prefixed(unit, base) ⇒ Object
- #mathmlsymbol_exponent(unit, base) ⇒ Object
- #mathmlsymbolwrap(units, normalise) ⇒ Object
- #multiplier(val) ⇒ Object
- #normalise_units(units) ⇒ Object
- #parse(expr) ⇒ Object
- #parse_dimensions(text) ⇒ Object
- #parse_units(text) ⇒ Object
- #parsers ⇒ Object
- #postprocess(units, text, is_units) ⇒ Object
- #postprocess1(units) ⇒ Object
- #postprocess_extr(text, name) ⇒ Object
- #postprocess_normtext(units, is_units) ⇒ Object
- #prefix(units) ⇒ Object
- #quantity(normtext, quantity) ⇒ Object
- #quantityname(id) ⇒ Object
- #read_yaml(path) ⇒ Object
- #render(unit, style) ⇒ Object
- #render_ambig_units(u) ⇒ Object
- #rootunits(units) ⇒ Object
- #symbol_key(val) ⇒ Object
- #symbolize_keys(hash) ⇒ Object
- #unit(units, _origtext, normtext, dims, name) ⇒ Object
- #unit_id(text) ⇒ Object
- #unitname(units, text, name) ⇒ Object
- #units2dimensions(units) ⇒ Object
- #units2dimensions_dim_input(norm) ⇒ Object
- #units_only(units) ⇒ Object
- #units_parse(exponent, multiplier) ⇒ Object
- #unitsml(units, origtext, normtext, quantity, name) ⇒ Object
- #unitsymbol(units) ⇒ Object
-
#unitsystem(units) ⇒ Object
kg exception.
- #validate_symbols(acc, val) ⇒ Object
- #validate_unit(unit) ⇒ Object
- #validate_unit_symbol_cardinality(sym, key) ⇒ Object
- #validate_yaml(hash, path) ⇒ Object
Constructor Details
#initialize(options = {}) ⇒ Conv
Returns a new instance of Conv.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/asciimath2unitsml/conv.rb', line 19 def initialize( = {}) @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([:multiplier] || "\u22c5") end |
Instance Method Details
#ambig_units ⇒ Object
187 188 189 190 191 192 193 194 195 196 197 198 199 |
# 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) ⇒ Object
174 175 176 177 178 |
# 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 |
#Asciimath2UnitsML(expression) ⇒ Object
115 116 117 118 |
# File 'lib/asciimath2unitsml/parse.rb', line 115 def Asciimath2UnitsML(expression) xml = Nokogiri::XML(asciimath2mathml(expression)) MathML2UnitsML(xml).to_xml end |
#combine_prefixes(p1, p2) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 |
# 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) ⇒ Object
TODO: compose name from the component units
61 62 63 |
# File 'lib/asciimath2unitsml/unit.rb', line 61 def compose_name(_units, text) text end |
#decompose_unit(u) ⇒ Object
treat g not kg as base unit: we have stripped the prefix k in parsing reduce units down to basic units
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# 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) ⇒ Object
65 66 67 |
# 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) ⇒ Object
160 161 162 163 164 165 166 167 168 169 170 171 172 |
# 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) ⇒ Object
if previous sibling’s last descendent non-whitespace is MathML and mn or mi, no space
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# 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'>⁢</mo>" else "<mo>⁢</mo>" end end |
#dim_id(dims) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# 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) ⇒ Object
95 96 97 98 99 100 101 102 103 104 |
# 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) ⇒ Object
63 64 65 66 |
# 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) ⇒ Object
3 4 5 6 7 8 9 10 11 |
# 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) ⇒ Object
13 14 15 16 17 18 19 20 21 22 |
# 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) ⇒ Object
87 88 89 90 91 92 93 |
# 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) ⇒ Object
97 98 99 |
# File 'lib/asciimath2unitsml/parse.rb', line 97 def display_exp(unit) unit[:exponent] && unit[:exponent] != "1" ? "^#{unit[:exponent]}" : "" end |
#embeddedmathml(mathml) ⇒ Object
180 181 182 183 184 185 |
# File 'lib/asciimath2unitsml/parse.rb', line 180 def (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) ⇒ Object
26 27 28 29 30 31 32 |
# 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) ⇒ Object
34 35 36 37 38 39 40 |
# 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) ⇒ Object
46 47 48 |
# 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) ⇒ Object
91 92 93 94 95 96 97 98 99 100 101 102 |
# 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) ⇒ Object
69 70 71 72 73 |
# 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) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# 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) ⇒ Object
217 218 219 220 221 |
# 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) ⇒ Object
19 20 21 22 23 |
# 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) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 |
# 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) ⇒ Object
39 40 41 42 43 44 45 46 47 |
# File 'lib/asciimath2unitsml/render.rb', line 39 def htmlsymbol_exponent(unit, base) if unit[:display_exponent] == "0.5" base = "√#{base}" elsif unit[:display_exponent] exp = "<sup>#{unit[:display_exponent].sub(/-/, '−')}</sup>" base += exp end base end |
#MathML2UnitsML(xml) ⇒ Object
www.w3.org/TR/mathml-units/ section 2: delimit number Invisible-Times unit
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# 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 (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 |
#mathmlsymbol(units, normalise, multiplier = nil) ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 60 |
# 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) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 |
# 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) ⇒ Object
74 75 76 77 78 79 80 81 82 |
# 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) ⇒ Object
84 85 86 87 88 89 90 91 92 93 |
# 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>−</mo><mn>") base = "<msup><mrow>#{base}</mrow><mrow>#{exp}</mrow></msup>" end base end |
#mathmlsymbolwrap(units, normalise) ⇒ Object
95 96 97 98 99 |
# 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) ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 |
# File 'lib/asciimath2unitsml/render.rb', line 3 def multiplier(val) case val when :space { html: " ", mathml: "<mo rspace='thickmathspace'>⁢</mo>" } when :nospace { html: "", mathml: "<mo>⁢</mo>" } else { html: HTMLEntities.new.encode(val), mathml: "<mo>#{HTMLEntities.new.encode(val)}</mo>" } end end |
#normalise_units(units) ⇒ Object
28 29 30 31 32 33 34 35 |
# 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) ⇒ Object
48 49 50 51 52 53 |
# 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) ⇒ Object
65 66 67 68 69 70 71 72 73 |
# 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) ⇒ Object
55 56 57 58 59 60 61 62 63 |
# 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 ⇒ Object
4 5 6 7 8 9 10 11 |
# 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) ⇒ Object
75 76 77 78 79 80 81 |
# 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) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 |
# 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) ⇒ Object
91 92 93 94 95 |
# 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) ⇒ Object
83 84 85 86 87 88 89 |
# 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) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# 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) ⇒ Object
146 147 148 149 150 151 152 153 154 155 156 157 158 |
# 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) ⇒ Object
138 139 140 141 142 143 144 |
# 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) ⇒ Object
3 4 5 6 |
# 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) ⇒ Object
15 16 17 |
# File 'lib/asciimath2unitsml/render.rb', line 15 def render(unit, style) @symbols[unit][style] || unit end |
#render_ambig_units(u) ⇒ Object
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# 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) ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# 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) ⇒ Object
50 51 52 53 54 55 56 57 |
# 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) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 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) ⇒ Object
13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# 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) ⇒ Object
7 8 9 10 11 |
# 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) ⇒ Object
55 56 57 58 |
# 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) ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# 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) ⇒ Object
54 55 56 57 58 59 60 61 |
# 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) ⇒ Object
3 4 5 |
# File 'lib/asciimath2unitsml/unit.rb', line 3 def units_only(units) units.reject { |u| u[:multiplier] } end |
#units_parse(exponent, multiplier) ⇒ Object
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# 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) ⇒ Object
160 161 162 163 164 165 166 167 168 169 |
# 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) ⇒ Object
65 66 67 68 69 70 |
# 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) ⇒ Object
kg exception
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# 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) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 |
# 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) ⇒ Object
15 16 17 18 19 20 21 22 23 24 25 26 |
# 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) ⇒ Object
41 42 43 44 45 46 47 48 |
# 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) ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 |
# 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 |