asciimath2unitsml 0.3.2 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +2 -23
- data/.hound.yml +5 -0
- data/.rubocop.yml +4 -8
- data/Gemfile +1 -1
- data/README.adoc +19 -1
- data/Rakefile +1 -1
- data/asciimath2unitsml.gemspec +15 -18
- data/bin/rspec +1 -2
- data/lib/asciimath2unitsml/conv.rb +32 -89
- data/lib/asciimath2unitsml/dimensions.rb +117 -0
- data/lib/asciimath2unitsml/parse.rb +93 -75
- data/lib/asciimath2unitsml/read.rb +42 -0
- data/lib/asciimath2unitsml/render.rb +48 -31
- data/lib/asciimath2unitsml/unit.rb +29 -23
- data/lib/asciimath2unitsml/validate.rb +27 -25
- data/lib/asciimath2unitsml/version.rb +1 -1
- data/lib/asciimath2unitsml.rb +0 -1
- data/lib/unitsdb/dimensions.yaml +65 -8
- data/lib/unitsdb_ruby/unitsdb.rb +87 -55
- data/spec/conv_spec.rb +1443 -1365
- data/spec/spec_helper.rb +10 -10
- metadata +28 -25
@@ -1,94 +1,101 @@
|
|
1
1
|
module Asciimath2UnitsML
|
2
2
|
class Conv
|
3
3
|
include Rsec::Helpers
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
hash.each_with_object({}) do |(_k, v), m|
|
12
|
-
next if v.name.nil? || v.name.empty?
|
13
|
-
|
14
|
-
m[v.symbolid] = v
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def flip_name_and_symbols(hash)
|
19
|
-
hash.each_with_object({}) do |(_k, v), m|
|
20
|
-
next if v.name.nil? || v.name.empty?
|
21
|
-
|
22
|
-
v.symbolids.each { |s| m[s] = v }
|
23
|
-
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]
|
24
11
|
end
|
25
12
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
when Hash then symbolize_keys(value)
|
36
|
-
when Array then value.map { |m| symbolize_keys(m) }
|
37
|
-
else value
|
38
|
-
end
|
39
|
-
result[new_key] = new_value
|
40
|
-
result
|
41
|
-
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
|
42
22
|
end
|
43
23
|
|
44
|
-
def
|
24
|
+
def units_parse(exponent, multiplier)
|
45
25
|
prefix2 = /#{@prefixes.keys.select { |x| x.size == 2 }.join("|")}/.r
|
46
26
|
prefix1 = /#{@prefixes.keys.select { |x| x.size == 1 }.join("|")}/.r
|
47
27
|
unit_keys = @units.keys.reject do |k|
|
48
28
|
/\*|\^|\/|^1$/.match(k) || @units[k].prefixed
|
49
29
|
end.map { |k| Regexp.escape(k) }
|
50
30
|
unit1 = /#{unit_keys.sort_by(&:length).reverse.join("|")}/.r
|
51
|
-
|
52
|
-
|
53
|
-
multiplier = %r{\*|//|/}.r.map { |x| { multiplier: x[0] } }
|
54
|
-
unit =
|
31
|
+
|
32
|
+
unit =
|
55
33
|
seq("sqrt(", unit1, ")") { |x| { prefix: nil, unit: x[1], display_exponent: "0.5" } } |
|
56
34
|
seq("sqrt(", prefix1, unit1, ")") { |x| { prefix: x[1], unit: x[2], display_exponent: "0.5" } } |
|
57
35
|
seq("sqrt(", prefix2, unit1, ")") { |x| { prefix: x[1], unit: x[2], display_exponent: "0.5" } } |
|
58
36
|
seq(unit1, exponent._? & (multiplier | ")".r)) { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0]) } } |
|
59
37
|
seq(unit1, exponent._?).eof { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0]) } } |
|
60
|
-
seq(prefix1, unit1, exponent._?
|
61
|
-
seq(prefix2, unit1, exponent._?
|
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]) } } |
|
62
40
|
"1".r.map { |_| { prefix: nil, unit: "1", display_exponent: nil } }
|
63
|
-
units1 = "(".r >> lazy{units} << ")" | unit
|
64
|
-
units = seq(prefix2, "-") { |x| [{ prefix: x[0], unit: nil, 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
|
65
43
|
seq(prefix1, "-") { |x| [{ prefix: x[0], unit: nil, display_exponent: nil }] } |
|
66
44
|
units1.join(multiplier)
|
67
|
-
|
45
|
+
units
|
68
46
|
end
|
69
47
|
|
70
|
-
def parse(
|
71
|
-
text = Array(
|
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)
|
72
56
|
units = @parser.parse!(text[0])
|
73
57
|
if !units || Rsec::INVALID[units]
|
74
58
|
raise Rsec::SyntaxError.new "error parsing UnitsML expression", x, 1, 0
|
75
59
|
end
|
76
60
|
|
77
61
|
Rsec::Fail.reset
|
78
|
-
postprocess(units, text)
|
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
|
+
|
71
|
+
Rsec::Fail.reset
|
72
|
+
postprocess(units, text, false)
|
79
73
|
end
|
80
74
|
|
81
|
-
def postprocess(units, text)
|
75
|
+
def postprocess(units, text, is_units)
|
82
76
|
units = postprocess1(units.flatten)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
90
88
|
end.join("*")
|
91
|
-
|
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]}" : ""
|
92
99
|
end
|
93
100
|
|
94
101
|
def postprocess1(units)
|
@@ -110,16 +117,21 @@ module Asciimath2UnitsML
|
|
110
117
|
MathML2UnitsML(xml).to_xml
|
111
118
|
end
|
112
119
|
|
113
|
-
# https://www.w3.org/TR/mathml-units/ section 2:
|
120
|
+
# https://www.w3.org/TR/mathml-units/ section 2:
|
121
|
+
# delimit number Invisible-Times unit
|
114
122
|
def MathML2UnitsML(xml)
|
115
123
|
xml.is_a? String and xml = Nokogiri::XML(xml)
|
116
124
|
xml.xpath(".//m:mtext", "m" => MATHML_NS).each do |x|
|
117
|
-
next unless %r{^unitsml\(.+\)$}.match(x.text)
|
125
|
+
next unless %r{^unitsml\(.+\)$}.match?(x.text)
|
118
126
|
|
119
127
|
text = x.text.sub(%r{^unitsml\((.+)\)$}m, "\\1")
|
120
|
-
units, origtext, normtext, quantity, name, symbol, multiplier =
|
121
|
-
|
122
|
-
|
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
|
123
135
|
x.replace("#{delimspace(rendering, x)}"\
|
124
136
|
"<mrow xref='#{unit_id(origtext)}'>#{rendering}</mrow>\n"\
|
125
137
|
"#{unitsml(units, origtext, normtext, quantity, name)}")
|
@@ -127,18 +139,22 @@ module Asciimath2UnitsML
|
|
127
139
|
dedup_ids(xml)
|
128
140
|
end
|
129
141
|
|
130
|
-
# if previous sibling's last descendent non-whitespace is MathML and
|
142
|
+
# if previous sibling's last descendent non-whitespace is MathML and
|
143
|
+
# mn or mi, no space
|
131
144
|
def delimspace(rendering, elem)
|
132
145
|
prec_text_elem =
|
133
146
|
elem.xpath("./preceding-sibling::*[namespace-uri() = '#{MATHML_NS}']/"\
|
134
|
-
"descendant::text()[normalize-space()!='']
|
147
|
+
"descendant::text()[normalize-space()!='']"\
|
148
|
+
"[last()]/parent::*").last
|
135
149
|
return "" if prec_text_elem.nil? ||
|
136
150
|
!%w(mn mi).include?(prec_text_elem&.name)
|
137
151
|
|
138
152
|
text = HTMLEntities.new.encode(Nokogiri::XML("<mrow>#{rendering}</mrow>")
|
139
153
|
.text.strip)
|
140
|
-
/\p{L}|\p{N}/.match(text)
|
141
|
-
"<mo rspace='thickmathspace'>⁢</mo>"
|
154
|
+
if /\p{L}|\p{N}/.match?(text)
|
155
|
+
"<mo rspace='thickmathspace'>⁢</mo>"
|
156
|
+
else "<mo>⁢</mo>"
|
157
|
+
end
|
142
158
|
end
|
143
159
|
|
144
160
|
def dedup_ids(xml)
|
@@ -156,9 +172,9 @@ module Asciimath2UnitsML
|
|
156
172
|
end
|
157
173
|
|
158
174
|
def asciimath2mathml(expression)
|
159
|
-
AsciiMath::MathMLBuilder.new(:
|
160
|
-
AsciiMath.parse(HTMLEntities.new.decode(expression)).ast
|
161
|
-
|
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}'>")
|
162
178
|
end
|
163
179
|
|
164
180
|
def embeddedmathml(mathml)
|
@@ -171,7 +187,7 @@ module Asciimath2UnitsML
|
|
171
187
|
def ambig_units
|
172
188
|
u = @units_id.each_with_object({}) do |(_k, v), m|
|
173
189
|
v.symbolids.each do |x|
|
174
|
-
next if %r{[*/^]}.match(x)
|
190
|
+
next if %r{[*/^]}.match?(x)
|
175
191
|
next unless v.symbols_hash[x][:html] != x
|
176
192
|
|
177
193
|
m[v.symbols_hash[x][:html]] ||= []
|
@@ -185,13 +201,15 @@ module Asciimath2UnitsML
|
|
185
201
|
def render_ambig_units(u)
|
186
202
|
maxcols = 0
|
187
203
|
u.each { |_, v| maxcols = v.size if maxcols < v.size }
|
188
|
-
puts %([cols="#{maxcols + 1}*"]\n|===\n|Symbol | Unit + ID #{
|
204
|
+
puts %([cols="#{maxcols + 1}*"]\n|===\n|Symbol | Unit + ID #{'| ' * (maxcols - 1)}\n)
|
189
205
|
puts "\n\n"
|
190
|
-
u.keys.sort_by
|
191
|
-
.gsub(
|
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|
|
192
210
|
print "| #{html2adoc(k)} "
|
193
211
|
u[k].sort_by(&:size).each { |v1| print "| #{@units[v1].name}: `#{v1}` " }
|
194
|
-
puts "#{'| ' * (maxcols - u[k].size)
|
212
|
+
puts "#{'| ' * (maxcols - u[k].size)}\n"
|
195
213
|
end
|
196
214
|
puts "|===\n"
|
197
215
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Asciimath2UnitsML
|
2
|
+
class Conv
|
3
|
+
def read_yaml(path)
|
4
|
+
validate_yaml(symbolize_keys(YAML
|
5
|
+
.load_file(File.join(File.join(File.dirname(__FILE__), path)))), path)
|
6
|
+
end
|
7
|
+
|
8
|
+
def symbolize_keys(hash)
|
9
|
+
return hash if hash.is_a? String
|
10
|
+
|
11
|
+
hash.inject({}) do |result, (key, value)|
|
12
|
+
new_key = case key
|
13
|
+
when String then key.to_sym
|
14
|
+
else key
|
15
|
+
end
|
16
|
+
new_value = case value
|
17
|
+
when Hash then symbolize_keys(value)
|
18
|
+
when Array then value.map { |m| symbolize_keys(m) }
|
19
|
+
else value
|
20
|
+
end
|
21
|
+
result[new_key] = new_value
|
22
|
+
result
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def flip_name_and_symbol(hash)
|
27
|
+
hash.each_with_object({}) do |(_k, v), m|
|
28
|
+
next if v.name.nil? || v.name.empty?
|
29
|
+
|
30
|
+
m[v.symbolid] = v
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def flip_name_and_symbols(hash)
|
35
|
+
hash.each_with_object({}) do |(_k, v), m|
|
36
|
+
next if v.name.nil? || v.name.empty?
|
37
|
+
|
38
|
+
v.symbolids.each { |s| m[s] = v }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,13 +1,14 @@
|
|
1
1
|
module Asciimath2UnitsML
|
2
2
|
class Conv
|
3
|
-
def multiplier(
|
4
|
-
case
|
3
|
+
def multiplier(val)
|
4
|
+
case val
|
5
5
|
when :space
|
6
6
|
{ html: " ", mathml: "<mo rspace='thickmathspace'>⁢</mo>" }
|
7
7
|
when :nospace
|
8
8
|
{ html: "", mathml: "<mo>⁢</mo>" }
|
9
9
|
else
|
10
|
-
{ html: HTMLEntities.new.encode(
|
10
|
+
{ html: HTMLEntities.new.encode(val),
|
11
|
+
mathml: "<mo>#{HTMLEntities.new.encode(val)}</mo>" }
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
@@ -15,28 +16,31 @@ module Asciimath2UnitsML
|
|
15
16
|
@symbols[unit][style] || unit
|
16
17
|
end
|
17
18
|
|
18
|
-
def htmlent(
|
19
|
-
HTMLEntities.new.decode(
|
20
|
-
|
19
|
+
def htmlent(xml)
|
20
|
+
HTMLEntities.new.decode(xml).split(/([<>&])/).map do |c|
|
21
|
+
/[<>'"]/.match?(c) ? c : HTMLEntities.new.encode(c, :hexadecimal)
|
22
|
+
end.join
|
21
23
|
end
|
22
24
|
|
23
25
|
def htmlsymbol(units, normalise)
|
24
26
|
units.map do |u|
|
25
|
-
if u[:multiplier]
|
27
|
+
if u[:multiplier]
|
28
|
+
u[:multiplier] == "*" ? @multiplier[:html] : u[:multiplier]
|
26
29
|
elsif u[:unit].nil? && u[:prefix]
|
27
30
|
@prefixes[u[:prefix]].html
|
28
31
|
else
|
29
|
-
base = (u[:prefix] || "") +
|
32
|
+
base = (u[:prefix] || "") +
|
33
|
+
render(normalise ? @units[u[:unit]].symbolid : u[:unit], :html)
|
30
34
|
htmlsymbol_exponent(u, base)
|
31
35
|
end
|
32
|
-
end.join
|
36
|
+
end.join
|
33
37
|
end
|
34
38
|
|
35
|
-
def htmlsymbol_exponent(
|
36
|
-
if
|
39
|
+
def htmlsymbol_exponent(unit, base)
|
40
|
+
if unit[:display_exponent] == "0.5"
|
37
41
|
base = "√#{base}"
|
38
|
-
elsif
|
39
|
-
exp = "<sup>#{
|
42
|
+
elsif unit[:display_exponent]
|
43
|
+
exp = "<sup>#{unit[:display_exponent].sub(/-/, '−')}</sup>"
|
40
44
|
base += exp
|
41
45
|
end
|
42
46
|
base
|
@@ -44,41 +48,54 @@ module Asciimath2UnitsML
|
|
44
48
|
|
45
49
|
def mathmlsymbol(units, normalise, multiplier = nil)
|
46
50
|
multiplier = multiplier ? "<mo>#{multiplier}</mo>" : @multiplier[:mathml]
|
47
|
-
|
48
|
-
if u[:multiplier]
|
51
|
+
units.map do |u|
|
52
|
+
if u[:multiplier]
|
53
|
+
u[:multiplier] == "*" ? multiplier : "<mo>#{u[:multiplier]}</mo>"
|
49
54
|
elsif u[:unit].nil? && u[:prefix]
|
50
55
|
%(<mi mathvariant='normal'>#{htmlent(@prefixes[u[:prefix]].html)}</mi>)
|
51
56
|
else
|
52
57
|
mathmlsymbol1(u, normalise)
|
53
58
|
end
|
54
|
-
end.join
|
59
|
+
end.join
|
60
|
+
end
|
61
|
+
|
62
|
+
def mathmlsymbol1(unit, normalise)
|
63
|
+
base = if unit[:dim]
|
64
|
+
render(normalise ? @dimensions[unit[:dim]].symbolid : unit[:dim],
|
65
|
+
:mathml)
|
66
|
+
else
|
67
|
+
render(normalise ? @units[unit[:unit]].symbolid : unit[:unit],
|
68
|
+
:mathml)
|
69
|
+
end
|
70
|
+
unit[:prefix] and base = mathmlsymbol1_prefixed(unit, base)
|
71
|
+
mathmlsymbol_exponent(unit, base)
|
55
72
|
end
|
56
73
|
|
57
|
-
def
|
58
|
-
|
59
|
-
if
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
74
|
+
def mathmlsymbol1_prefixed(unit, base)
|
75
|
+
prefix = htmlent(@prefixes[unit[:prefix]].html)
|
76
|
+
if /<mi mathvariant='normal'>/.match?(base)
|
77
|
+
base.sub(/<mi mathvariant='normal'>/,
|
78
|
+
"<mi mathvariant='normal'>#{prefix}")
|
79
|
+
else
|
80
|
+
"<mrow><mi mathvariant='normal'>#{prefix}#{base}</mrow>"
|
64
81
|
end
|
65
|
-
mathmlsymbol_exponent(u, base)
|
66
82
|
end
|
67
83
|
|
68
|
-
def mathmlsymbol_exponent(
|
69
|
-
if
|
84
|
+
def mathmlsymbol_exponent(unit, base)
|
85
|
+
if unit[:display_exponent] == "0.5"
|
70
86
|
base = "<msqrt>#{base}</msqrt>"
|
71
|
-
elsif
|
72
|
-
exp = "<mn>#{
|
87
|
+
elsif unit[:display_exponent]
|
88
|
+
exp = "<mn>#{unit[:display_exponent]}</mn>"
|
89
|
+
.sub(/<mn>-/, "<mo>−</mo><mn>")
|
73
90
|
base = "<msup><mrow>#{base}</mrow><mrow>#{exp}</mrow></msup>"
|
74
91
|
end
|
75
92
|
base
|
76
93
|
end
|
77
94
|
|
78
95
|
def mathmlsymbolwrap(units, normalise)
|
79
|
-
<<~
|
80
|
-
|
81
|
-
|
96
|
+
<<~XML
|
97
|
+
<math xmlns='#{MATHML_NS}'><mrow>#{mathmlsymbol(units, normalise)}</mrow></math>
|
98
|
+
XML
|
82
99
|
end
|
83
100
|
end
|
84
101
|
end
|
@@ -7,21 +7,22 @@ module Asciimath2UnitsML
|
|
7
7
|
def unit_id(text)
|
8
8
|
text = text.gsub(/[()]/, "")
|
9
9
|
/-$/.match(text) and return @prefixes[text.sub(/-$/, "")].id
|
10
|
-
"U_
|
10
|
+
"U_#{@units[text] ? @units[text].id.gsub(/'/, '_') : text.gsub(/\*/, '.').gsub(/\^/, '')}"
|
11
11
|
end
|
12
12
|
|
13
|
-
def unit(units,
|
13
|
+
def unit(units, _origtext, normtext, dims, name)
|
14
14
|
return if units_only(units).any? { |x| x[:unit].nil? }
|
15
|
+
|
15
16
|
dimid = dim_id(dims)
|
16
17
|
norm_units = normalise_units(units)
|
17
|
-
<<~
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
<<~XML
|
19
|
+
<Unit xmlns='#{UNITSML_NS}' xml:id='#{unit_id(normtext)}'#{dimid ? " dimensionURL='##{dimid}'" : ''}>
|
20
|
+
#{unitsystem(units)}
|
21
|
+
#{unitname(norm_units, normtext, name)}
|
22
|
+
#{unitsymbol(norm_units)}
|
23
|
+
#{rootunits(units)}
|
24
|
+
</Unit>
|
25
|
+
XML
|
25
26
|
end
|
26
27
|
|
27
28
|
def normalise_units(units)
|
@@ -36,14 +37,17 @@ module Asciimath2UnitsML
|
|
36
37
|
# kg exception
|
37
38
|
def unitsystem(units)
|
38
39
|
return if units_only(units).any? { |x| x[:unit].nil? }
|
40
|
+
|
39
41
|
ret = []
|
40
42
|
units = units_only(units)
|
41
43
|
units.any? { |x| @units[x[:unit]].system_name != "SI" } and
|
42
44
|
ret << "<UnitSystem name='not_SI' type='not_SI' xml:lang='en-US'/>"
|
43
45
|
if units.any? { |x| @units[x[:unit]].system_name == "SI" }
|
44
|
-
base = units.size == 1 &&
|
45
|
-
|
46
|
-
|
46
|
+
base = units.size == 1 &&
|
47
|
+
@units[units[0][:unit]].system_type == "SI-base"
|
48
|
+
base = true if units.size == 1 && units[0][:unit] == "g" &&
|
49
|
+
units[0][:prefix] == "k"
|
50
|
+
ret << "<UnitSystem name='SI' type='#{base ? 'SI_base' : 'SI_derived'}' xml:lang='en-US'/>"
|
47
51
|
end
|
48
52
|
ret.join("\n")
|
49
53
|
end
|
@@ -54,28 +58,30 @@ module Asciimath2UnitsML
|
|
54
58
|
end
|
55
59
|
|
56
60
|
# TODO: compose name from the component units
|
57
|
-
def compose_name(
|
61
|
+
def compose_name(_units, text)
|
58
62
|
text
|
59
63
|
end
|
60
64
|
|
61
65
|
def unitsymbol(units)
|
62
|
-
<<~
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
+
<<~XML
|
67
|
+
<UnitSymbol type="HTML">#{htmlsymbol(units, true)}</UnitSymbol>
|
68
|
+
<UnitSymbol type="MathML">#{mathmlsymbolwrap(units, true)}</UnitSymbol>
|
69
|
+
XML
|
66
70
|
end
|
67
71
|
|
68
72
|
def rootunits(units)
|
69
73
|
return if units_only(units).any? { |x| x[:unit].nil? }
|
70
74
|
return if units.size == 1 && !units[0][:prefix]
|
75
|
+
|
71
76
|
exp = units_only(units).map do |u|
|
72
77
|
prefix = " prefix='#{u[:prefix]}'" if u[:prefix]
|
73
|
-
|
74
|
-
|
78
|
+
u[:exponent] && u[:exponent] != "1" and
|
79
|
+
arg = " powerNumerator='#{u[:exponent]}'"
|
80
|
+
"<EnumeratedRootUnit unit='#{@units[u[:unit]].name}'#{prefix}#{arg}/>"
|
75
81
|
end.join("\n")
|
76
|
-
<<~
|
77
|
-
|
78
|
-
|
82
|
+
<<~XML
|
83
|
+
<RootUnits>#{exp}</RootUnits>
|
84
|
+
XML
|
79
85
|
end
|
80
86
|
end
|
81
87
|
end
|
@@ -12,44 +12,46 @@ module Asciimath2UnitsML
|
|
12
12
|
hash
|
13
13
|
end
|
14
14
|
|
15
|
-
def validate_unit(
|
16
|
-
if
|
17
|
-
|
18
|
-
raise StandardError
|
15
|
+
def validate_unit(unit)
|
16
|
+
if unit[:quantity_reference]
|
17
|
+
unit[:quantity_reference].is_a?(Array) or
|
18
|
+
raise StandardError
|
19
|
+
.new "No quantity_reference array provided for unit: #{unit}"
|
19
20
|
end
|
20
|
-
if
|
21
|
-
|
22
|
-
raise StandardError
|
21
|
+
if unit[:unit_name]
|
22
|
+
unit[:unit_name].is_a?(Array) or
|
23
|
+
raise StandardError
|
24
|
+
.new "No unit_name array provided for unit: #{unit}"
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
def validate_symbols(
|
27
|
-
symbol = symbol_key(
|
28
|
+
def validate_symbols(acc, val)
|
29
|
+
symbol = symbol_key(val)
|
28
30
|
!symbol.nil? or
|
29
|
-
raise StandardError.new "No symbol provided for unit: #{
|
31
|
+
raise StandardError.new "No symbol provided for unit: #{val}"
|
30
32
|
Array(symbol)&.each do |s|
|
31
|
-
|
32
|
-
raise StandardError.new "symbol #{s} is not unique in #{
|
33
|
-
"already used for #{
|
34
|
-
|
33
|
+
acc[s] && s != "1" and
|
34
|
+
raise StandardError.new "symbol #{s} is not unique in #{val}: "\
|
35
|
+
"already used for #{acc[s]}"
|
36
|
+
acc[s] = val
|
35
37
|
end
|
36
|
-
|
38
|
+
acc
|
37
39
|
end
|
38
40
|
|
39
|
-
def validate_unit_symbol_cardinality(
|
40
|
-
return true if
|
41
|
+
def validate_unit_symbol_cardinality(sym, key)
|
42
|
+
return true if sym.nil?
|
41
43
|
|
42
|
-
!
|
43
|
-
!
|
44
|
-
!
|
45
|
-
raise StandardError.new "malformed unit_symbol for #{
|
44
|
+
!sym[:id].nil? && !sym[:ascii].nil? && !sym[:html].nil? &&
|
45
|
+
!sym[:mathml].nil? && !sym[:latex].nil? &&
|
46
|
+
!sym[:unicode].nil? and return true
|
47
|
+
raise StandardError.new "malformed unit_symbol for #{key}: #{sym}"
|
46
48
|
end
|
47
49
|
|
48
|
-
def symbol_key(
|
49
|
-
symbol =
|
50
|
+
def symbol_key(val)
|
51
|
+
symbol = val[:unit_symbols]&.each_with_object([]) do |s, m|
|
50
52
|
m << (s["id"] || s[:id])
|
51
|
-
end ||
|
52
|
-
!symbol.nil? &&
|
53
|
+
end || val.dig(:symbol, :ascii) || val[:symbol] # || val[:short]
|
54
|
+
!symbol.nil? && val[:unit_symbols] && !symbol.is_a?(Array) and
|
53
55
|
symbol = [symbol]
|
54
56
|
symbol
|
55
57
|
end
|
data/lib/asciimath2unitsml.rb
CHANGED