Class: Asciimath2UnitsML::Conv
- Inherits:
-
Object
- Object
- Asciimath2UnitsML::Conv
- Includes:
- Rsec::Helpers
- Defined in:
- lib/asciimath2unitsml/conv.rb,
lib/asciimath2unitsml/parse.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" }, "mol" => { dimension: "AmountOfSubstance", order: 6, symbol: "N" }, "cd" => { dimension: "LuminousIntensity", order: 7, symbol: "J" }, }
Instance Method Summary collapse
- #asciimath2mathml(expression) ⇒ Object
- #Asciimath2UnitsML(expression) ⇒ Object
- #combine_prefixes(p1, p2) ⇒ Object
-
#compose_name(units, text) ⇒ Object
TODO: compose name from the component units.
- #dim_id(dims) ⇒ Object
- #dimension(dims) ⇒ Object
- #dimension1(u) ⇒ Object
- #flip_name_and_id(yaml) ⇒ Object
- #gather_units(units) ⇒ Object
- #htmlsymbol(units) ⇒ 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) ⇒ Object
- #mathmlsymbolwrap(units) ⇒ Object
- #multiplier(x) ⇒ Object
- #normalise_unit(u) ⇒ Object
- #normalise_units(units) ⇒ Object
- #parse(x) ⇒ Object
- #parser ⇒ Object
- #prefix(units) ⇒ Object
- #read_yaml(path) ⇒ Object
- #rootunits(units) ⇒ Object
- #symbolize_keys(hash) ⇒ Object
- #unit(units, text, dims) ⇒ Object
- #unit_id(text) ⇒ Object
- #unitname(units, text) ⇒ Object
- #units2dimensions(units) ⇒ Object
- #unitsml(units, text) ⇒ Object
- #unitsymbol(units) ⇒ Object
- #unitsystem(units) ⇒ Object
Constructor Details
#initialize(options = {}) ⇒ Conv
Returns a new instance of Conv.
14 15 16 17 18 19 20 21 22 |
# File 'lib/asciimath2unitsml/conv.rb', line 14 def initialize( = {}) @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) @parser = parser @multiplier = multiplier([:multiplier] || "\u00b7") end |
Instance Method Details
#asciimath2mathml(expression) ⇒ Object
86 87 88 89 90 |
# File 'lib/asciimath2unitsml/parse.rb', line 86 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
69 70 71 72 |
# File 'lib/asciimath2unitsml/parse.rb', line 69 def Asciimath2UnitsML(expression) xml = Nokogiri::XML(asciimath2mathml(expression)) MathML2UnitsML(xml).to_xml end |
#combine_prefixes(p1, p2) ⇒ Object
189 190 191 192 193 194 195 196 197 198 |
# File 'lib/asciimath2unitsml/conv.rb', line 189 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] @prefixes.each do |p| return p[:symbol] 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
69 70 71 |
# File 'lib/asciimath2unitsml/conv.rb', line 69 def compose_name(units, text) text end |
#dim_id(dims) ⇒ Object
156 157 158 159 |
# File 'lib/asciimath2unitsml/conv.rb', line 156 def dim_id(dims) return nil if dims.nil? || dims.empty? "D_" + dims.map { |d| U2D[d[:unit]][:symbol] + (d[:exponent] == 1 ? "" : d[:exponent].to_s) }.join("") end |
#dimension(dims) ⇒ Object
132 133 134 135 136 137 138 139 |
# File 'lib/asciimath2unitsml/conv.rb', line 132 def dimension(dims) return if dims.nil? || dims.empty? " <Dimension xmlns='\#{UNITSML_NS}' xml:id=\"\#{dim_id(dims)}\">\n \#{dims.map { |u| dimension1(u) }.join(\"\\n\") }\n </Dimension>\n END\nend\n" |
#dimension1(u) ⇒ Object
152 153 154 |
# File 'lib/asciimath2unitsml/conv.rb', line 152 def dimension1(u) %(<#{u[:dimension]} symbol="#{u[:symbol]}" powerNumerator="#{u[:exponent]}"/>) end |
#flip_name_and_id(yaml) ⇒ Object
9 10 11 12 13 14 15 16 17 |
# File 'lib/asciimath2unitsml/parse.rb', line 9 def flip_name_and_id(yaml) yaml.each_with_object({}) do |(k, v), m| next if v[:name].nil? || v[:name].empty? symbol = v[:symbol] || v[:short] m[symbol.to_sym] = v m[symbol.to_sym][:symbol] = symbol m[symbol.to_sym][:id] = k.to_s end end |
#gather_units(units) ⇒ Object
165 166 167 168 169 170 171 172 173 174 |
# File 'lib/asciimath2unitsml/conv.rb', line 165 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 else m[-1] = { prefix: combine_prefixes(@prefixes[m[-1][:prefix]], @prefixes[k[:prefix]]), unit: m[-1][:unit], exponent: (k[:exponent]&.to_i || 1) + (m[-1][:exponent]&.to_i || 1) } end end end |
#htmlsymbol(units) ⇒ Object
80 81 82 83 84 85 |
# File 'lib/asciimath2unitsml/conv.rb', line 80 def htmlsymbol(units) units.map do |u| u[:exponent] and exp = "<sup>#{u[:exponent].sub(/-/, "−")}</sup>" "#{u[:prefix]}#{u[:unit]}#{exp}" end.join(@multiplier[:html]) end |
#MathML2UnitsML(xml) ⇒ Object
www.w3.org/TR/mathml-units/ section 2: delimit number Invisible-Times unit
75 76 77 78 79 80 81 82 83 84 |
# File 'lib/asciimath2unitsml/parse.rb', line 75 def MathML2UnitsML(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 = parse(text) delim = x&.previous_element&.name == "mn" ? "<mo rspace='thickmathspace'>⁢</mo>" : "" x.replace("#{delim}<mrow xref='#{unit_id(text)}'>#{mathmlsymbol(units)}</mrow>\n#{unitsml(units, text)}") end xml end |
#mathmlsymbol(units) ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/asciimath2unitsml/conv.rb', line 87 def mathmlsymbol(units) exp = units.map do |u| base = "<mi mathvariant='normal'>#{u[:prefix]}#{u[:unit]}</mi>" if u[:exponent] exp = "<mn>#{u[:exponent]}</mn>".sub(/<mn>-/, "<mo>−</mo><mn>") "<msup><mrow>#{base}</mrow><mrow>#{exp}</mrow></msup>" else base end end.join(@multiplier[:mathml]) end |
#mathmlsymbolwrap(units) ⇒ Object
99 100 101 102 103 104 105 |
# File 'lib/asciimath2unitsml/conv.rb', line 99 def mathmlsymbolwrap(units) " <math xmlns='\#{MATHML_NS}'>\n <mrow>\#{mathmlsymbol(units)}</mrow>\n </math>\n END\nend\n" |
#multiplier(x) ⇒ Object
24 25 26 27 28 29 30 31 32 33 |
# File 'lib/asciimath2unitsml/conv.rb', line 24 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 |
#normalise_unit(u) ⇒ Object
176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/asciimath2unitsml/conv.rb', line 176 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] } 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) } end end end |
#normalise_units(units) ⇒ Object
161 162 163 |
# File 'lib/asciimath2unitsml/conv.rb', line 161 def normalise_units(units) gather_units(units.map { |u| normalise_unit(u) }.flatten) end |
#parse(x) ⇒ Object
49 50 51 52 53 54 55 56 |
# File 'lib/asciimath2unitsml/parse.rb', line 49 def parse(x) units = @parser.parse(x) if !units || Rsec::INVALID[units] raise Rsec::SyntaxError.new "error parsing UnitsML expression", x, 1, 0 end Rsec::Fail.reset units end |
#parser ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/asciimath2unitsml/parse.rb', line 34 def parser prefix = /#{@prefixes.keys.join("|")}/.r unit_keys = @units.keys.reject do |k| @units[k][:type]&.include?("buildable") || /\*|\^/.match(k) end.map { |k| Regexp.escape(k) } unit1 = /#{unit_keys.sort_by(&:length).reverse.join("|")}/.r exponent = /\^-?\d+/.r.map { |m| m.sub(/\^/, "") } multiplier = /\*/.r unit = seq(unit1, exponent._?) { |x| { prefix: nil, unit: x[0], exponent: x[1][0] } } | seq(prefix, unit1, exponent._?) { |x| { prefix: x[0][0], unit: x[1], exponent: x[2][0] } } units_tail = seq(multiplier, unit) { |u| u[1] } units = seq(unit, units_tail.star) { |x| [x[0], x[1]].flatten } parser = units.eof end |
#prefix(units) ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/asciimath2unitsml/conv.rb', line 119 def prefix(units) units.map { |u| u[:prefix] }.reject { |u| u.nil? }.uniq.map do |p1| p = p1.to_sym " <Prefix xmlns='\#{UNITSML_NS}' prefixBase='\#{@prefixes[p][:base]}'\n prefixPower='\#{@prefixes[p][:power]}' xml:id='\#{@prefixes[p][:id]}'>\n <PrefixName xml:lang=\"en\">\#{@prefixes[p][:name]}</PrefixName>\n <PrefixSymbol type=\"ASCII\">\#{@prefixes[p][:symbol]}</PrefixSymbol>\n </Prefix>\n END\n end.join(\"\\n\")\nend\n" |
#read_yaml(path) ⇒ Object
5 6 7 |
# File 'lib/asciimath2unitsml/parse.rb', line 5 def read_yaml(path) symbolize_keys(YAML.load_file(File.join(File.join(File.dirname(__FILE__), path)))) end |
#rootunits(units) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/asciimath2unitsml/conv.rb', line 107 def rootunits(units) return if units.size == 1 exp = units.map do |u| prefix = " prefix='#{u[:prefix]}'" if u[:prefix] exponent = " powerNumerator='#{u[:exponent]}'" if u[:exponent] "<EnumeratedRootUnit unit='#{@units[u[:unit].to_sym][:name]}'#{prefix}#{exponent}/>" end.join("\n") " <RootUnits>\#{exp}</RootUnits>\n END\nend\n" |
#symbolize_keys(hash) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/asciimath2unitsml/parse.rb', line 19 def symbolize_keys(hash) 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) else value end result[new_key] = new_value result end end |
#unit(units, text, dims) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/asciimath2unitsml/conv.rb', line 40 def unit(units, text, dims) dimid = dim_id(dims) " <Unit xmlns='\#{UNITSML_NS}' xml:id='\#{unit_id(text)}'\#{dimid ? \" dimensionURL='#\#{dimid}'\" : \"\"}>\n \#{unitsystem(units)}\n \#{unitname(units, text)}\n \#{unitsymbol(units)}\n \#{rootunits(units)}\n </Unit>\n END\nend\n" |
#unit_id(text) ⇒ Object
35 36 37 38 |
# File 'lib/asciimath2unitsml/conv.rb', line 35 def unit_id(text) "U_" + (@units[text.to_sym] ? @units[text.to_sym][:id] : text.gsub(/\*/, ".").gsub(/\^/, "")) end |
#unitname(units, text) ⇒ Object
63 64 65 66 |
# File 'lib/asciimath2unitsml/conv.rb', line 63 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 |
#units2dimensions(units) ⇒ Object
141 142 143 144 145 146 147 148 149 150 |
# File 'lib/asciimath2unitsml/conv.rb', line 141 def units2dimensions(units) norm = normalise_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, symbol: U2D[u[:unit]][:symbol] } end.sort { |a, b| U2D[a[:unit]][:order] <=> U2D[b[:unit]][:order] } end |
#unitsml(units, text) ⇒ Object
200 201 202 203 204 205 206 207 |
# File 'lib/asciimath2unitsml/conv.rb', line 200 def unitsml(units, text) dims = units2dimensions(units) " \#{unit(units, text, dims)}\n \#{prefix(units)}\n \#{dimension(dims)}\n END\nend\n" |
#unitsymbol(units) ⇒ Object
73 74 75 76 77 78 |
# File 'lib/asciimath2unitsml/conv.rb', line 73 def unitsymbol(units) " <UnitSymbol type=\"HTML\">\#{htmlsymbol(units)}</UnitSymbol>\n <UnitSymbol type=\"MathML\">\#{mathmlsymbolwrap(units)}</UnitSymbol>\n END\nend\n" |
#unitsystem(units) ⇒ Object
52 53 54 55 56 57 58 59 60 61 |
# File 'lib/asciimath2unitsml/conv.rb', line 52 def unitsystem(units) ret = [] 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 |