asciimath2unitsml 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ module Asciimath2UnitsML
2
+ class Conv
3
+ def dimension_components(dims)
4
+ return if dims.nil? || dims.empty?
5
+
6
+ <<~XML
7
+ <Dimension xmlns='#{UNITSML_NS}' xml:id="#{dim_id(dims)}">
8
+ #{dims.map { |u| dimension1(u) }.join("\n")}
9
+ </Dimension>
10
+ XML
11
+ end
12
+
13
+ U2D = {
14
+ "m" => { dimension: "Length", order: 1, symbol: "L" },
15
+ "g" => { dimension: "Mass", order: 2, symbol: "M" },
16
+ "kg" => { dimension: "Mass", order: 2, symbol: "M" },
17
+ "s" => { dimension: "Time", order: 3, symbol: "T" },
18
+ "A" => { dimension: "ElectricCurrent", order: 4, symbol: "I" },
19
+ "K" => { dimension: "ThermodynamicTemperature", order: 5,
20
+ symbol: "Theta" },
21
+ "degK" => { dimension: "ThermodynamicTemperature", order: 5,
22
+ symbol: "Theta" },
23
+ "mol" => { dimension: "AmountOfSubstance", order: 6, symbol: "N" },
24
+ "cd" => { dimension: "LuminousIntensity", order: 7, symbol: "J" },
25
+ "deg" => { dimension: "PlaneAngle", order: 8, symbol: "phi" },
26
+ }.freeze
27
+
28
+ Dim2D = {
29
+ "dim_L" => U2D["m"],
30
+ "dim_M" => U2D["g"],
31
+ "dim_T" => U2D["s"],
32
+ "dim_I" => U2D["A"],
33
+ "dim_Theta" => U2D["K"],
34
+ "dim_N" => U2D["mol"],
35
+ "dim_J" => U2D["cd"],
36
+ "dim_phi" => U2D["deg"],
37
+ }.freeze
38
+
39
+ def units2dimensions(units)
40
+ norm = decompose_units(units)
41
+ return units2dimensions_dim_input(norm) if norm[0][:dim]
42
+ return if norm.any? do |u|
43
+ u[:unit] == "unknown" || u[:prefix] == "unknown" || u[:unit].nil?
44
+ end
45
+
46
+ norm.map do |u|
47
+ { dimension: U2D[u[:unit]][:dimension],
48
+ unit: u[:unit],
49
+ exponent: u[:exponent] || 1,
50
+ symbol: U2D[u[:unit]][:symbol] }
51
+ end.sort { |a, b| U2D[a[:unit]][:order] <=> U2D[b[:unit]][:order] }
52
+ end
53
+
54
+ def units2dimensions_dim_input(norm)
55
+ norm.map do |u|
56
+ { dimension: Dim2D[u[:dim]][:dimension],
57
+ exponent: u[:exponent] || 1,
58
+ id: u[:dim],
59
+ symbol: Dim2D[u[:dim]][:symbol] }
60
+ end.sort { |a, b| Dim2D[a[:id]][:order] <=> Dim2D[b[:id]][:order] }
61
+ end
62
+
63
+ def dimension1(dim)
64
+ %(<#{dim[:dimension]} symbol="#{dim[:symbol]}"
65
+ powerNumerator="#{float_to_display(dim[:exponent])}"/>)
66
+ end
67
+
68
+ def dim_id(dims)
69
+ return nil if dims.nil? || dims.empty?
70
+
71
+ dimhash = dims.each_with_object({}) { |h, m| m[h[:dimension]] = h }
72
+ dimsvector = %w(Length Mass Time ElectricCurrent ThermodynamicTemperature
73
+ AmountOfSubstance LuminousIntensity PlaneAngle)
74
+ .map { |h| dimhash.dig(h, :exponent) }.join(":")
75
+ id = @dimensions_id&.values&.select { |d| d.vector == dimsvector }
76
+ &.first&.id and return id.to_s
77
+ "D_" + dims.map do |d|
78
+ (U2D.dig(d[:unit], :symbol) || Dim2D.dig(d[:id], :symbol)) +
79
+ (d[:exponent] == 1 ? "" : float_to_display(d[:exponent]))
80
+ end.join("")
81
+ end
82
+
83
+ def decompose_units(units)
84
+ gather_units(units_only(units).map { |u| decompose_unit(u) }.flatten)
85
+ end
86
+
87
+ def dimid2dimensions(normtext)
88
+ @dimensions_id[normtext].keys.map do |k|
89
+ { dimension: k,
90
+ symbol: U2D.values.select { |v| v[:dimension] == k }.first[:symbol],
91
+ exponent: @dimensions_id[normtext].exponent(k) }
92
+ end
93
+ end
94
+
95
+ def dimension(normtext)
96
+ return unless @units[normtext]&.dimension
97
+
98
+ dims = dimid2dimensions(@units[normtext]&.dimension)
99
+ <<~XML
100
+ <Dimension xmlns='#{UNITSML_NS}' xml:id="#{@units[normtext]&.dimension}">
101
+ #{dims.map { |u| dimension1(u) }.join("\n")}
102
+ </Dimension>
103
+ XML
104
+ end
105
+
106
+ def unitsml(units, origtext, normtext, quantity, name)
107
+ dims = units2dimensions(units)
108
+ <<~XML
109
+ #{unit(units, origtext, normtext, dims, name)}
110
+ #{prefix(units)}
111
+ #{dimension(normtext)}
112
+ #{dimension_components(dims)}
113
+ #{quantity(normtext, quantity)}
114
+ XML
115
+ end
116
+ end
117
+ end
@@ -1,143 +1,111 @@
1
1
  module Asciimath2UnitsML
2
2
  class Conv
3
3
  include Rsec::Helpers
4
-
5
- def read_yaml(path)
6
- validate_yaml(symbolize_keys(YAML.load_file(File.join(File.join(File.dirname(__FILE__), path)))), path)
7
- end
8
-
9
- def flip_name_and_symbol(hash)
10
- hash.each_with_object({}) do |(k, v), m|
11
- next if v.name.nil? || v.name.empty?
12
- m[v.symbolid] = v
13
- end
14
- end
15
-
16
- def flip_name_and_symbols(hash)
17
- hash.each_with_object({}) do |(k, v), m|
18
- next if v.name.nil? || v.name.empty?
19
- v.symbolids.each { |s| m[s] = v }
20
- end
21
- end
22
-
23
- def validate_yaml(hash, path)
24
- return hash if path == "../unitsdb/quantities.yaml"
25
- return hash if path == "../unitsdb/dimensions.yaml"
26
- hash.each_with_object({}) do |(k, v), m|
27
- path == "../unitsdb/units.yaml" and validate_unit(v)
28
- m = validate_symbols(m, v)
29
- v[:unit_symbols]&.each { |s| validate_unit_symbol_cardinality(s, k) }
30
- end
31
- hash
32
- end
33
-
34
- def validate_unit(v)
35
- if v[:quantity_reference]
36
- v[:quantity_reference].is_a?(Array) or
37
- raise StandardError.new "No quantity_reference array provided for unit: #{v}"
38
- end
39
- if v[:unit_name]
40
- v[:unit_name].is_a?(Array) or raise StandardError.new "No unit_name array provided for unit: #{v}"
41
- end
4
+ def parsers
5
+ exponent = /\^\(-?\d+\)/.r.map { |m| m.sub(/\^/, "").gsub(/[()]/, "") } |
6
+ /\^-?\d+/.r.map { |m| m.sub(/\^/, "") }
7
+ multiplier = %r{\*|//|/}.r.map { |x| { multiplier: x[0] } }
8
+ units = units_parse(exponent, multiplier)
9
+ dimensions = dimensions_parser(exponent, multiplier)
10
+ [units.eof, dimensions.eof]
42
11
  end
43
12
 
44
- def validate_symbols(m, v)
45
- symbol = symbol_key(v)
46
- !symbol.nil? or raise StandardError.new "No symbol provided for unit: #{v}"
47
- Array(symbol)&.each do |s|
48
- m[s] && s != "1" and
49
- raise StandardError.new "symbol #{s} is not unique in #{v}: already used for #{m[s]}"
50
- m[s] = v
51
- end
52
- m
53
- end
54
-
55
- def validate_unit_symbol_cardinality(us, k)
56
- return true if us.nil?
57
- !us[:id].nil? && !us[:ascii].nil? && !us[:html].nil? && !us[:mathml].nil? && !us[:latex].nil? &&
58
- !us[:unicode].nil? and return true
59
- raise StandardError.new "malformed unit_symbol for #{k}: #{us}"
60
- end
61
-
62
- def symbol_key(v)
63
- symbol = v[:unit_symbols]&.each_with_object([]) { |s, m| m << (s["id"] || s[:id]) } ||
64
- v.dig(:symbol, :ascii) || v[:symbol] #|| v[:short]
65
- symbol = [symbol] if !symbol.nil? && v[:unit_symbols] && !symbol.is_a?(Array)
66
- symbol
67
- end
68
-
69
- def symbolize_keys(hash)
70
- return hash if hash.is_a? String
71
- hash.inject({}) do |result, (key, value)|
72
- new_key = case key
73
- when String then key.to_sym
74
- else key
75
- end
76
- new_value = case value
77
- when Hash then symbolize_keys(value)
78
- when Array then value.map { |m| symbolize_keys(m) }
79
- else value
80
- end
81
- result[new_key] = new_value
82
- result
83
- end
13
+ def dimensions_parser(exponent, multiplier)
14
+ dim1 = /#{@dimensions.keys.sort_by(&:length).reverse.join("|")}/.r
15
+ dimension =
16
+ seq("sqrt(", dim1, ")") { |x| { dim: x[1], display_exponent: "0.5" } } |
17
+ seq(dim1, exponent._? & (multiplier | ")".r)) { |x| { dim: x[0], display_exponent: (x[1][0]) } } |
18
+ seq(dim1, exponent._?).eof { |x| { dim: x[0], display_exponent: (x[1][0]) } }
19
+ dimensions1 = "(".r >> lazy { dimensions } << ")" | dimension
20
+ dimensions = dimensions1.join(multiplier) # rubocop:disable Style/RedundantAssignment
21
+ dimensions
84
22
  end
85
23
 
86
- def parser
24
+ def units_parse(exponent, multiplier)
87
25
  prefix2 = /#{@prefixes.keys.select { |x| x.size == 2 }.join("|")}/.r
88
26
  prefix1 = /#{@prefixes.keys.select { |x| x.size == 1 }.join("|")}/.r
89
27
  unit_keys = @units.keys.reject do |k|
90
28
  /\*|\^|\/|^1$/.match(k) || @units[k].prefixed
91
29
  end.map { |k| Regexp.escape(k) }
92
30
  unit1 = /#{unit_keys.sort_by(&:length).reverse.join("|")}/.r
93
- exponent = /\^\(-?\d+\)/.r.map { |m| m.sub(/\^/, "").gsub(/[()]/, "") } |
94
- /\^-?\d+/.r.map { |m| m.sub(/\^/, "") }
95
- multiplier = %r{\*|//|/}.r.map { |x| { multiplier: x[0] } }
96
- unit =
31
+
32
+ unit =
97
33
  seq("sqrt(", unit1, ")") { |x| { prefix: nil, unit: x[1], display_exponent: "0.5" } } |
98
34
  seq("sqrt(", prefix1, unit1, ")") { |x| { prefix: x[1], unit: x[2], display_exponent: "0.5" } } |
99
35
  seq("sqrt(", prefix2, unit1, ")") { |x| { prefix: x[1], unit: x[2], display_exponent: "0.5" } } |
100
- seq(unit1, exponent._? & multiplier) { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0] )} } |
101
- seq(unit1, exponent._?).eof { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0] )} } |
102
- seq(prefix1, unit1, exponent._? ) { |x| { prefix: x[0], unit: x[1], display_exponent: (x[2][0] ) } } |
103
- seq(prefix2, unit1, exponent._? ) { |x| { prefix: x[0], unit: x[1], display_exponent: (x[2][0] ) } } |
104
- "1".r.map { |_| { prefix: nil, unit: "1", display_exponent: nil } }
105
- units = seq(prefix2, "-") { |x| [{ prefix: x[0], unit: nil, display_exponent: nil }] } |
36
+ seq(unit1, exponent._? & (multiplier | ")".r)) { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0]) } } |
37
+ seq(unit1, exponent._?).eof { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0]) } } |
38
+ seq(prefix1, unit1, exponent._?) { |x| { prefix: x[0], unit: x[1], display_exponent: (x[2][0]) } } |
39
+ seq(prefix2, unit1, exponent._?) { |x| { prefix: x[0], unit: x[1], display_exponent: (x[2][0]) } } |
40
+ "1".r.map { |_| { prefix: nil, unit: "1", display_exponent: nil } }
41
+ units1 = "(".r >> lazy { units } << ")" | unit
42
+ units = seq(prefix2, "-") { |x| [{ prefix: x[0], unit: nil, display_exponent: nil }] } | # rubocop:disable Style/RedundantAssignment
106
43
  seq(prefix1, "-") { |x| [{ prefix: x[0], unit: nil, display_exponent: nil }] } |
107
- unit.join(multiplier)
108
- parser = units.eof
44
+ units1.join(multiplier)
45
+ units
109
46
  end
110
47
 
111
- def parse(x)
112
- text = Array(x.split(/,\s*/))
48
+ def parse(expr)
49
+ text = Array(expr.split(/,\s*/))
50
+ if /dim_/.match?(text[0]) then parse_dimensions(text)
51
+ else parse_units(text)
52
+ end
53
+ end
54
+
55
+ def parse_units(text)
113
56
  units = @parser.parse!(text[0])
114
57
  if !units || Rsec::INVALID[units]
115
58
  raise Rsec::SyntaxError.new "error parsing UnitsML expression", x, 1, 0
116
59
  end
60
+
61
+ Rsec::Fail.reset
62
+ postprocess(units, text, true)
63
+ end
64
+
65
+ def parse_dimensions(text)
66
+ units = @dim_parser.parse!(text[0])
67
+ if !units || Rsec::INVALID[units]
68
+ raise Rsec::SyntaxError.new "error parsing UnitsML expression", x, 1, 0
69
+ end
70
+
117
71
  Rsec::Fail.reset
118
- postprocess(units, text)
119
- end
120
-
121
- def postprocess(units, text)
122
- units = postprocess1(units)
123
- quantity = text[1..-1]&.select { |x| /^quantity:/.match(x) }&.first&.sub(/^quantity:\s*/, "")
124
- name = text[1..-1]&.select { |x| /^name:/.match(x) }&.first&.sub(/^name:\s*/, "")
125
- symbol = text[1..-1]&.select { |x| /^symbol:/.match(x) }&.first&.sub(/^symbol:\s*/, "")
126
- multiplier = text[1..-1]&.select { |x| /^multiplier:/.match(x) }&.first&.sub(/^multiplier:\s*/, "")
127
- normtext = units_only(units).each.map do |u|
128
- exp = u[:exponent] && u[:exponent] != "1" ? "^#{u[:exponent]}" : ""
129
- "#{u[:prefix]}#{u[:unit]}#{exp}"
72
+ postprocess(units, text, false)
73
+ end
74
+
75
+ def postprocess(units, text, is_units)
76
+ units = postprocess1(units.flatten)
77
+ normtext = postprocess_normtext(units, is_units)
78
+ [units, text[0], normtext, postprocess_extr(text, "quantity"),
79
+ postprocess_extr(text, "name"), postprocess_extr(text, "symbol"),
80
+ postprocess_extr(text, "multiplier")]
81
+ end
82
+
83
+ def postprocess_normtext(units, is_units)
84
+ units_only(units).each.map do |u|
85
+ if is_units then "#{u[:prefix]}#{u[:unit]}#{display_exp(u)}"
86
+ else "#{u[:dim]}#{display_exp(u)}"
87
+ end
130
88
  end.join("*")
131
- [units, text[0], normtext, quantity, name, symbol, multiplier]
89
+ end
90
+
91
+ def postprocess_extr(text, name)
92
+ text[1..-1]&.select do |x|
93
+ /^#{name}:/.match(x)
94
+ end&.first&.sub(/^#{name}:\s*/, "")
95
+ end
96
+
97
+ def display_exp(unit)
98
+ unit[:exponent] && unit[:exponent] != "1" ? "^#{unit[:exponent]}" : ""
132
99
  end
133
100
 
134
101
  def postprocess1(units)
135
102
  inverse = false
136
- units.each_with_object([]) do |u, m|
103
+ units.each_with_object([]) do |u, m|
137
104
  if u[:multiplier]
138
- inverse = (u[:multiplier] == "/")
105
+ inverse = !inverse if u[:multiplier] == "/"
139
106
  else
140
- u[:exponent] = inverse ? "-#{u[:display_exponent] || '1'}" : u[:display_exponent]
107
+ u[:exponent] =
108
+ inverse ? "-#{u[:display_exponent] || '1'}" : u[:display_exponent]
141
109
  u[:exponent] = u[:exponent]&.sub(/^--+/, "")
142
110
  end
143
111
  m << u
@@ -149,36 +117,53 @@ module Asciimath2UnitsML
149
117
  MathML2UnitsML(xml).to_xml
150
118
  end
151
119
 
152
- # https://www.w3.org/TR/mathml-units/ section 2: delimit number Invisible-Times unit
120
+ # https://www.w3.org/TR/mathml-units/ section 2:
121
+ # delimit number Invisible-Times unit
153
122
  def MathML2UnitsML(xml)
154
123
  xml.is_a? String and xml = Nokogiri::XML(xml)
155
124
  xml.xpath(".//m:mtext", "m" => MATHML_NS).each do |x|
156
- next unless %r{^unitsml\(.+\)$}.match(x.text)
125
+ next unless %r{^unitsml\(.+\)$}.match?(x.text)
126
+
157
127
  text = x.text.sub(%r{^unitsml\((.+)\)$}m, "\\1")
158
- units, origtext, normtext, quantity, name, symbol, multiplier = parse(text)
159
- rendering = symbol ? embeddedmathml(asciimath2mathml(symbol)) :
160
- mathmlsymbol(units, false, multiplier)
161
- x.replace("#{delimspace(rendering, x)}<mrow xref='#{unit_id(origtext)}'>#{rendering}</mrow>\n"\
128
+ units, origtext, normtext, quantity, name, symbol, multiplier =
129
+ parse(text)
130
+ rendering = if symbol
131
+ embeddedmathml(asciimath2mathml(symbol))
132
+ else
133
+ mathmlsymbol(units, false, multiplier)
134
+ end
135
+ x.replace("#{delimspace(rendering, x)}"\
136
+ "<mrow xref='#{unit_id(origtext)}'>#{rendering}</mrow>\n"\
162
137
  "#{unitsml(units, origtext, normtext, quantity, name)}")
163
138
  end
164
139
  dedup_ids(xml)
165
140
  end
166
141
 
167
- # if previous sibling's last descendent non-whitespace is MathML and mn or mi, no space
142
+ # if previous sibling's last descendent non-whitespace is MathML and
143
+ # mn or mi, no space
168
144
  def delimspace(rendering, elem)
169
- prec_text_elem = elem.xpath("./preceding-sibling::*[namespace-uri() = '#{MATHML_NS}']/"\
170
- "descendant::text()[normalize-space()!=''][last()]/parent::*").last
171
- return "" if prec_text_elem.nil? || !%w(mn mi).include?(prec_text_elem&.name)
172
- text = HTMLEntities.new.encode(Nokogiri::XML("<mrow>#{rendering}</mrow>").text.strip)
173
- /\p{L}|\p{N}/.match(text) ?
174
- "<mo rspace='thickmathspace'>&#x2062;</mo>" : "<mo>&#x2062;</mo>"
145
+ prec_text_elem =
146
+ elem.xpath("./preceding-sibling::*[namespace-uri() = '#{MATHML_NS}']/"\
147
+ "descendant::text()[normalize-space()!='']"\
148
+ "[last()]/parent::*").last
149
+ return "" if prec_text_elem.nil? ||
150
+ !%w(mn mi).include?(prec_text_elem&.name)
151
+
152
+ text = HTMLEntities.new.encode(Nokogiri::XML("<mrow>#{rendering}</mrow>")
153
+ .text.strip)
154
+ if /\p{L}|\p{N}/.match?(text)
155
+ "<mo rspace='thickmathspace'>&#x2062;</mo>"
156
+ else "<mo>&#x2062;</mo>"
157
+ end
175
158
  end
176
159
 
177
160
  def dedup_ids(xml)
178
161
  %w(Unit Dimension Prefix Quantity).each do |t|
179
- xml.xpath(".//m:#{t}/@xml:id", "m" => UNITSML_NS).map { |a| a.text }.uniq.each do |v|
162
+ xml.xpath(".//m:#{t}/@xml:id", "m" => UNITSML_NS).map(&:text)
163
+ .uniq.each do |v|
180
164
  xml.xpath(".//*[@xml:id = '#{v}']").each_with_index do |n, i|
181
- next if i == 0
165
+ next if i.zero?
166
+
182
167
  n.remove
183
168
  end
184
169
  end
@@ -187,45 +172,50 @@ module Asciimath2UnitsML
187
172
  end
188
173
 
189
174
  def asciimath2mathml(expression)
190
- AsciiMath::MathMLBuilder.new(:msword => true).append_expression(
191
- AsciiMath.parse(HTMLEntities.new.decode(expression)).ast).to_s.
192
- gsub(/<math>/, "<math xmlns='#{MATHML_NS}'>")
175
+ AsciiMath::MathMLBuilder.new(msword: true).append_expression(
176
+ AsciiMath.parse(HTMLEntities.new.decode(expression)).ast,
177
+ ).to_s.gsub(/<math>/, "<math xmlns='#{MATHML_NS}'>")
193
178
  end
194
179
 
195
180
  def embeddedmathml(mathml)
196
181
  x = Nokogiri::XML(mathml)
197
- x.xpath(".//m:mi", "m" => MATHML_NS).each { |mi| mi["mathvariant"] = "normal" }
182
+ x.xpath(".//m:mi", "m" => MATHML_NS)
183
+ .each { |mi| mi["mathvariant"] = "normal" }
198
184
  x.children.to_xml
199
185
  end
200
186
 
201
187
  def ambig_units
202
- u = @units_id.each_with_object({}) do |(k, v), m|
188
+ u = @units_id.each_with_object({}) do |(_k, v), m|
203
189
  v.symbolids.each do |x|
204
- next if %r{[*/^]}.match(x)
190
+ next if %r{[*/^]}.match?(x)
205
191
  next unless v.symbols_hash[x][:html] != x
192
+
206
193
  m[v.symbols_hash[x][:html]] ||= []
207
194
  m[v.symbols_hash[x][:html]] << x
208
195
  end
209
196
  end
210
- u.keys.each { |k| u[k] = u[k].unshift(k) if @symbols.dig(k, :html) == k }
197
+ u.each_key { |k| u[k] = u[k].unshift(k) if @symbols.dig(k, :html) == k }
211
198
  render_ambig_units(u)
212
199
  end
213
200
 
214
201
  def render_ambig_units(u)
215
202
  maxcols = 0
216
203
  u.each { |_, v| maxcols = v.size if maxcols < v.size }
217
- puts %([cols="#{maxcols + 1}*"]\n|===\n|Symbol | Unit + ID #{"| " * (maxcols - 1)}\n)
204
+ puts %([cols="#{maxcols + 1}*"]\n|===\n|Symbol | Unit + ID #{'| ' * (maxcols - 1)}\n)
218
205
  puts "\n\n"
219
- u.keys.sort_by { |a| [-u[a].size, a.gsub(%r{\&[^;]+;}, "").gsub(/[^A-Za-z]/, "").downcase] }.each do |k|
206
+ u.keys.sort_by do |a|
207
+ [-u[a].size, a.gsub(%r{&[^;]+;}, "")
208
+ .gsub(/[^A-Za-z]/, "").downcase]
209
+ end.each do |k|
220
210
  print "| #{html2adoc(k)} "
221
- u[k].sort_by { |v1| v1.size }.each { |v1| print "| #{@units[v1].name}: `#{v1}` " }
222
- puts "#{"| " * (maxcols - u[k].size) }\n"
211
+ u[k].sort_by(&:size).each { |v1| print "| #{@units[v1].name}: `#{v1}` " }
212
+ puts "#{'| ' * (maxcols - u[k].size)}\n"
223
213
  end
224
214
  puts "|===\n"
225
215
  end
226
216
 
227
- def html2adoc(k)
228
- k.gsub(%r{<i>}, "__").gsub(%r{</i>}, "__")
217
+ def html2adoc(elem)
218
+ elem.gsub(%r{<i>}, "__").gsub(%r{</i>}, "__")
229
219
  .gsub(%r{<sup>}, "^").gsub(%r{</sup>}, "^")
230
220
  .gsub(%r{<sub>}, "~").gsub(%r{</sub>}, "~")
231
221
  end