asciimath2unitsml 0.2.3 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +14 -0
- data/README.adoc +4 -1
- data/asciimath2unitsml.gemspec +1 -1
- data/lib/asciimath2unitsml/conv.rb +80 -50
- data/lib/asciimath2unitsml/parse.rb +56 -79
- data/lib/asciimath2unitsml/render.rb +3 -2
- data/lib/asciimath2unitsml/validate.rb +57 -0
- data/lib/asciimath2unitsml/version.rb +1 -1
- data/lib/unitsdb/units.yaml +2 -2
- data/spec/conv_spec.rb +453 -130
- metadata +9 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f51b82e234fddaf66252f77a276a3acf68dae4356502a3c900253f5d4d4dffcf
|
4
|
+
data.tar.gz: 59dd18f009952329c1c07af2132ec06123c06dd3caea4ee7f22f17c0f4ec1cbc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a925e3db85d3086d676c07c101609e87674d620394f5fa0a536ab3652f3aa9c0cc6a74507d79264e0154bf51ea4121d47b7275a94212bc934b822b0392a9970
|
7
|
+
data.tar.gz: 59d98ebd629a20e8f03dc32a1870474e5808b9d38f51a87feec5eae9f0746535bed9dcf38b1faf3939319bb3989b1d926088e8d834388e48f045070e3749312a
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.rubocop-https--*
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# This project follows the Ribose OSS style guide.
|
2
|
+
# https://github.com/riboseinc/oss-guides
|
3
|
+
# All project-specific additions and overrides should be specified in this file.
|
4
|
+
inherit_from:
|
5
|
+
- https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
|
6
|
+
|
7
|
+
# local repo-specific modifications
|
8
|
+
|
9
|
+
AllCops:
|
10
|
+
DisplayCopNames: false
|
11
|
+
StyleGuideCopsOnly: false
|
12
|
+
TargetRubyVersion: 2.4
|
13
|
+
Rails:
|
14
|
+
Enabled: true
|
data/README.adoc
CHANGED
@@ -53,6 +53,8 @@ from UnitsDB. For example, `unitsml(cal_th/cm^2, name: langley)`.
|
|
53
53
|
The unit-string gives the canonical representation of the unit, but SYMBOL is what will be rendered.
|
54
54
|
For example, `unitsml(cal_th/cm^2, name: langley, symbol: La)`, or `unitsml(mm*s^-2, symbol: mm cdot s^-2)`.
|
55
55
|
(All variables in SYMBOL are rendered upright, as is the default for units.)
|
56
|
+
* `unitsml(unit-string, multiplier: SYMBOL)` provides an alternate symbol for the multiliper of
|
57
|
+
units. The options are an XML entity, or the values `space` or `nospace` (for which see discussion under _Usage_).
|
56
58
|
|
57
59
|
Standalone prefixes can be recognised by replacing the unit with hyphen; so `unitsml(p-)` corresponds
|
58
60
|
to the standalone prefix "pico" (and is rendered as "p").
|
@@ -230,7 +232,7 @@ This table can be generated (in Asciidoc format) through `Asciimath2UnitsML::Con
|
|
230
232
|
| ton | ton of TNT (energy equivalent): `ton_TNT` | ton of refrigeration (12 000 Btu_IT/h): `ton_refrigeration` | | | |
|
231
233
|
| tsp | teaspoon: `tsp` | teaspoon (FDA): `tsp_label` | | | |
|
232
234
|
| yd | yard: `yd` | yard (based on US survey foot): `yd_US_survey` | | | |
|
233
|
-
| &#
|
235
|
+
| ° | degree (degree of arc): `deg` | | | | |
|
234
236
|
| γ | gamma: `gamma` | | | | |
|
235
237
|
| μ | micron: `micron` | | | | |
|
236
238
|
| Ω | ohm: `Ohm` | | | | |
|
@@ -258,3 +260,4 @@ This table can be generated (in Asciidoc format) through `Asciimath2UnitsML::Con
|
|
258
260
|
| °R | degree Rankine: `degR` | | | | |
|
259
261
|
| ƛ~C~ | natural unit of length: `lambda-bar_C` | | | | |
|
260
262
|
|===
|
263
|
+
|
data/asciimath2unitsml.gemspec
CHANGED
@@ -57,7 +57,7 @@ Gem::Specification.new do |spec|
|
|
57
57
|
spec.add_development_dependency "guard-rspec", "~> 4.7"
|
58
58
|
spec.add_development_dependency "rake", "~> 12.0"
|
59
59
|
spec.add_development_dependency "rspec", "~> 3.6"
|
60
|
-
spec.add_development_dependency "rubocop", "
|
60
|
+
spec.add_development_dependency "rubocop", "~> 1.5.2"
|
61
61
|
spec.add_development_dependency "simplecov", "~> 0.15"
|
62
62
|
spec.add_development_dependency "timecop", "~> 0.9"
|
63
63
|
spec.add_development_dependency "rexml"
|
@@ -7,70 +7,78 @@ require_relative "string"
|
|
7
7
|
require_relative "parse"
|
8
8
|
require_relative "render"
|
9
9
|
require_relative "unit"
|
10
|
+
require_relative "validate"
|
10
11
|
|
11
12
|
module Asciimath2UnitsML
|
12
13
|
MATHML_NS = "http://www.w3.org/1998/Math/MathML".freeze
|
13
|
-
UNITSML_NS = "
|
14
|
+
UNITSML_NS = "https://schema.unitsml.org/unitsml/1.0".freeze
|
14
15
|
|
15
16
|
class Conv
|
16
17
|
def initialize(options = {})
|
17
|
-
@dimensions_id = read_yaml("../unitsdb/dimensions.yaml")
|
18
|
+
@dimensions_id = read_yaml("../unitsdb/dimensions.yaml")
|
19
|
+
.each_with_object({}) do |(k, v), m|
|
18
20
|
m[k.to_s] = UnitsDB::Dimension.new(k, v)
|
19
21
|
end
|
20
|
-
@prefixes_id = read_yaml("../unitsdb/prefixes.yaml")
|
22
|
+
@prefixes_id = read_yaml("../unitsdb/prefixes.yaml")
|
23
|
+
.each_with_object({}) do |(k, v), m|
|
21
24
|
m[k] = UnitsDB::Prefix.new(k, v)
|
22
25
|
end
|
23
26
|
@prefixes = flip_name_and_symbol(@prefixes_id)
|
24
|
-
@quantities = read_yaml("../unitsdb/quantities.yaml")
|
27
|
+
@quantities = read_yaml("../unitsdb/quantities.yaml")
|
28
|
+
.each_with_object({}) do |(k, v), m|
|
25
29
|
m[k.to_s] = UnitsDB::Quantity.new(k, v)
|
26
30
|
end
|
27
|
-
@units_id = read_yaml("../unitsdb/units.yaml")
|
31
|
+
@units_id = read_yaml("../unitsdb/units.yaml")
|
32
|
+
.each_with_object({}) do |(k, v), m|
|
28
33
|
m[k.to_s] = UnitsDB::Unit.new(k.to_s, v)
|
29
34
|
end
|
30
35
|
@units = flip_name_and_symbols(@units_id)
|
31
|
-
@symbols = @units.each_with_object({}) do |(
|
36
|
+
@symbols = @units.each_with_object({}) do |(_k, v), m|
|
32
37
|
v.symbolids.each { |x| m[x] = v.symbols_hash[x] }
|
33
38
|
end
|
34
39
|
@parser = parser
|
35
|
-
@multiplier = multiplier(options[:multiplier] || "\
|
40
|
+
@multiplier = multiplier(options[:multiplier] || "\u22c5")
|
36
41
|
end
|
37
42
|
|
38
|
-
def float_to_display(
|
39
|
-
|
43
|
+
def float_to_display(float)
|
44
|
+
float.to_f.round(1).to_s.sub(/\.0$/, "")
|
40
45
|
end
|
41
46
|
|
42
47
|
def prefix(units)
|
43
|
-
units.map { |u| u[:prefix] }.reject
|
48
|
+
units.map { |u| u[:prefix] }.reject(&:nil?).uniq.map do |p|
|
44
49
|
<<~END
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
<Prefix xmlns='#{UNITSML_NS}' prefixBase='#{@prefixes[p].base}'
|
51
|
+
prefixPower='#{@prefixes[p].power}' xml:id='#{@prefixes[p].id}'>
|
52
|
+
<PrefixName xml:lang="en">#{@prefixes[p].name}</PrefixName>
|
53
|
+
<PrefixSymbol type="ASCII">#{@prefixes[p].ascii}</PrefixSymbol>
|
54
|
+
<PrefixSymbol type="unicode">#{@prefixes[p].unicode}</PrefixSymbol>
|
55
|
+
<PrefixSymbol type="LaTeX">#{@prefixes[p].latex}</PrefixSymbol>
|
56
|
+
<PrefixSymbol type="HTML">#{htmlent @prefixes[p].html}</PrefixSymbol>
|
57
|
+
</Prefix>
|
53
58
|
END
|
54
59
|
end.join("\n")
|
55
60
|
end
|
56
61
|
|
57
62
|
def dimension_components(dims)
|
58
63
|
return if dims.nil? || dims.empty?
|
64
|
+
|
59
65
|
<<~END
|
60
|
-
|
61
|
-
|
62
|
-
|
66
|
+
<Dimension xmlns='#{UNITSML_NS}' xml:id="#{dim_id(dims)}">
|
67
|
+
#{dims.map { |u| dimension1(u) }.join("\n")}
|
68
|
+
</Dimension>
|
63
69
|
END
|
64
70
|
end
|
65
71
|
|
66
|
-
|
72
|
+
U2D = {
|
67
73
|
"m" => { dimension: "Length", order: 1, symbol: "L" },
|
68
74
|
"g" => { dimension: "Mass", order: 2, symbol: "M" },
|
69
75
|
"kg" => { dimension: "Mass", order: 2, symbol: "M" },
|
70
76
|
"s" => { dimension: "Time", order: 3, symbol: "T" },
|
71
77
|
"A" => { dimension: "ElectricCurrent", order: 4, symbol: "I" },
|
72
|
-
"K" => { dimension: "ThermodynamicTemperature", order: 5,
|
73
|
-
|
78
|
+
"K" => { dimension: "ThermodynamicTemperature", order: 5,
|
79
|
+
symbol: "Theta" },
|
80
|
+
"degK" => { dimension: "ThermodynamicTemperature", order: 5,
|
81
|
+
symbol: "Theta" },
|
74
82
|
"mol" => { dimension: "AmountOfSubstance", order: 6, symbol: "N" },
|
75
83
|
"cd" => { dimension: "LuminousIntensity", order: 7, symbol: "J" },
|
76
84
|
"deg" => { dimension: "PlaneAngle", order: 8, symbol: "Phi" },
|
@@ -78,28 +86,35 @@ module Asciimath2UnitsML
|
|
78
86
|
|
79
87
|
def units2dimensions(units)
|
80
88
|
norm = decompose_units(units)
|
81
|
-
return if norm.any?
|
89
|
+
return if norm.any? do |u|
|
90
|
+
u[:unit] == "unknown" || u[:prefix] == "unknown" || u[:unit].nil?
|
91
|
+
end
|
92
|
+
|
82
93
|
norm.map do |u|
|
83
94
|
{ dimension: U2D[u[:unit]][:dimension],
|
84
95
|
unit: u[:unit],
|
85
96
|
exponent: u[:exponent] || 1,
|
86
|
-
symbol: U2D[u[:unit]][:symbol] }
|
97
|
+
symbol: U2D[u[:unit]][:symbol] }
|
87
98
|
end.sort { |a, b| U2D[a[:unit]][:order] <=> U2D[b[:unit]][:order] }
|
88
99
|
end
|
89
100
|
|
90
101
|
def dimension1(u)
|
91
|
-
%(<#{u[:dimension]} symbol="#{u[:symbol]}"
|
102
|
+
%(<#{u[:dimension]} symbol="#{u[:symbol]}"
|
103
|
+
powerNumerator="#{float_to_display(u[:exponent])}"/>)
|
92
104
|
end
|
93
105
|
|
94
106
|
def dim_id(dims)
|
95
107
|
return nil if dims.nil? || dims.empty?
|
108
|
+
|
96
109
|
dimhash = dims.each_with_object({}) { |h, m| m[h[:dimension]] = h }
|
97
|
-
dimsvector = %w(Length Mass Time ElectricCurrent ThermodynamicTemperature
|
110
|
+
dimsvector = %w(Length Mass Time ElectricCurrent ThermodynamicTemperature
|
98
111
|
AmountOfSubstance LuminousIntensity PlaneAngle)
|
99
112
|
.map { |h| dimhash.dig(h, :exponent) }.join(":")
|
100
|
-
id = @dimensions_id&.values&.select { |d| d.vector == dimsvector }
|
113
|
+
id = @dimensions_id&.values&.select { |d| d.vector == dimsvector }
|
114
|
+
&.first&.id and return id.to_s
|
101
115
|
"D_" + dims.map do |d|
|
102
|
-
U2D[d[:unit]][:symbol] +
|
116
|
+
U2D[d[:unit]][:symbol] +
|
117
|
+
(d[:exponent] == 1 ? "" : float_to_display(d[:exponent]))
|
103
118
|
end.join("")
|
104
119
|
end
|
105
120
|
|
@@ -108,12 +123,17 @@ module Asciimath2UnitsML
|
|
108
123
|
end
|
109
124
|
|
110
125
|
def gather_units(units)
|
111
|
-
units.
|
126
|
+
units.sort_by { |a| a[:unit] }.each_with_object([]) do |k, m|
|
112
127
|
if m.empty? || m[-1][:unit] != k[:unit] then m << k
|
113
128
|
else
|
114
|
-
m[-1] = {
|
115
|
-
|
116
|
-
|
129
|
+
m[-1] = {
|
130
|
+
prefix: combine_prefixes(
|
131
|
+
@prefixes[m[-1][:prefix]], @prefixes[k[:prefix]]
|
132
|
+
),
|
133
|
+
unit: m[-1][:unit],
|
134
|
+
exponent: (k[:exponent]&.to_f || 1) +
|
135
|
+
(m[-1][:exponent]&.to_f || 1),
|
136
|
+
}
|
117
137
|
end
|
118
138
|
end
|
119
139
|
end
|
@@ -128,8 +148,13 @@ module Asciimath2UnitsML
|
|
128
148
|
{ prefix: u[:prefix], unit: "unknown", exponent: u[:exponent] }
|
129
149
|
else
|
130
150
|
@units[u[:unit]].si_derived_bases.each_with_object([]) do |k, m|
|
131
|
-
|
132
|
-
|
151
|
+
prefix = if !k[:prefix].nil? && !k[:prefix].empty?
|
152
|
+
combine_prefixes(@prefixes_id[k[:prefix]],
|
153
|
+
@prefixes[u[:prefix]])
|
154
|
+
else
|
155
|
+
u[:prefix]
|
156
|
+
end
|
157
|
+
m << { prefix: prefix,
|
133
158
|
unit: @units_id[k[:id]].symbolid,
|
134
159
|
exponent: (k[:power]&.to_i || 1) * (u[:exponent]&.to_f || 1) }
|
135
160
|
end
|
@@ -141,6 +166,7 @@ module Asciimath2UnitsML
|
|
141
166
|
return p1.symbolid if p2.nil?
|
142
167
|
return p2.symbolid if p1.nil?
|
143
168
|
return "unknown" if p1.base != p2.base
|
169
|
+
|
144
170
|
@prefixes.each do |_, p|
|
145
171
|
return p.symbolid if p.base == p1.base && p.power == p1.power + p2.power
|
146
172
|
end
|
@@ -156,13 +182,16 @@ module Asciimath2UnitsML
|
|
156
182
|
end
|
157
183
|
|
158
184
|
def quantity(normtext, quantity)
|
159
|
-
return unless @units[normtext] && @units[normtext].quantities.size == 1 ||
|
185
|
+
return unless @units[normtext] && @units[normtext].quantities.size == 1 ||
|
186
|
+
@quantities[quantity]
|
187
|
+
|
160
188
|
id = quantity || @units[normtext].quantities.first
|
161
|
-
|
189
|
+
@units[normtext]&.dimension and
|
190
|
+
dim = %( dimensionURL="##{@units[normtext].dimension}")
|
162
191
|
<<~END
|
163
|
-
|
164
|
-
|
165
|
-
|
192
|
+
<Quantity xmlns='#{UNITSML_NS}' xml:id="#{id}"#{dim} quantityType="base">
|
193
|
+
#{quantityname(id)}
|
194
|
+
</Quantity>
|
166
195
|
END
|
167
196
|
end
|
168
197
|
|
@@ -175,23 +204,24 @@ module Asciimath2UnitsML
|
|
175
204
|
end
|
176
205
|
|
177
206
|
def dimension(normtext)
|
178
|
-
return unless @units[normtext]&.dimension
|
207
|
+
return unless @units[normtext]&.dimension
|
208
|
+
|
179
209
|
dims = dimid2dimensions(@units[normtext]&.dimension)
|
180
210
|
<<~END
|
181
|
-
|
182
|
-
|
183
|
-
|
211
|
+
<Dimension xmlns='#{UNITSML_NS}' xml:id="#{@units[normtext]&.dimension}">
|
212
|
+
#{dims.map { |u| dimension1(u) }.join("\n")}
|
213
|
+
</Dimension>
|
184
214
|
END
|
185
215
|
end
|
186
216
|
|
187
217
|
def unitsml(units, origtext, normtext, quantity, name)
|
188
218
|
dims = units2dimensions(units)
|
189
219
|
<<~END
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
220
|
+
#{unit(units, origtext, normtext, dims, name)}
|
221
|
+
#{prefix(units)}
|
222
|
+
#{dimension(normtext)}
|
223
|
+
#{dimension_components(dims)}
|
224
|
+
#{quantity(normtext, quantity)}
|
195
225
|
END
|
196
226
|
end
|
197
227
|
end
|
@@ -3,71 +3,29 @@ module Asciimath2UnitsML
|
|
3
3
|
include Rsec::Helpers
|
4
4
|
|
5
5
|
def read_yaml(path)
|
6
|
-
validate_yaml(symbolize_keys(YAML
|
6
|
+
validate_yaml(symbolize_keys(YAML
|
7
|
+
.load_file(File.join(File.join(File.dirname(__FILE__), path)))), path)
|
7
8
|
end
|
8
9
|
|
9
10
|
def flip_name_and_symbol(hash)
|
10
|
-
hash.each_with_object({}) do |(
|
11
|
+
hash.each_with_object({}) do |(_k, v), m|
|
11
12
|
next if v.name.nil? || v.name.empty?
|
13
|
+
|
12
14
|
m[v.symbolid] = v
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
18
|
def flip_name_and_symbols(hash)
|
17
|
-
hash.each_with_object({}) do |(
|
19
|
+
hash.each_with_object({}) do |(_k, v), m|
|
18
20
|
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
|
42
|
-
end
|
43
21
|
|
44
|
-
|
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
|
22
|
+
v.symbolids.each { |s| m[s] = v }
|
51
23
|
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
24
|
end
|
68
25
|
|
69
26
|
def symbolize_keys(hash)
|
70
27
|
return hash if hash.is_a? String
|
28
|
+
|
71
29
|
hash.inject({}) do |result, (key, value)|
|
72
30
|
new_key = case key
|
73
31
|
when String then key.to_sym
|
@@ -97,14 +55,15 @@ module Asciimath2UnitsML
|
|
97
55
|
seq("sqrt(", unit1, ")") { |x| { prefix: nil, unit: x[1], display_exponent: "0.5" } } |
|
98
56
|
seq("sqrt(", prefix1, unit1, ")") { |x| { prefix: x[1], unit: x[2], display_exponent: "0.5" } } |
|
99
57
|
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 } }
|
58
|
+
seq(unit1, exponent._? & (multiplier | ")".r)) { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0]) } } |
|
59
|
+
seq(unit1, exponent._?).eof { |x| { prefix: nil, unit: x[0], display_exponent: (x[1][0]) } } |
|
60
|
+
seq(prefix1, unit1, exponent._? ) { |x| { prefix: x[0], unit: x[1], display_exponent: (x[2][0]) } } |
|
61
|
+
seq(prefix2, unit1, exponent._? ) { |x| { prefix: x[0], unit: x[1], display_exponent: (x[2][0]) } } |
|
62
|
+
"1".r.map { |_| { prefix: nil, unit: "1", display_exponent: nil } }
|
63
|
+
units1 = "(".r >> lazy{units} << ")" | unit
|
105
64
|
units = seq(prefix2, "-") { |x| [{ prefix: x[0], unit: nil, display_exponent: nil }] } |
|
106
65
|
seq(prefix1, "-") { |x| [{ prefix: x[0], unit: nil, display_exponent: nil }] } |
|
107
|
-
|
66
|
+
units1.join(multiplier)
|
108
67
|
parser = units.eof
|
109
68
|
end
|
110
69
|
|
@@ -114,29 +73,32 @@ module Asciimath2UnitsML
|
|
114
73
|
if !units || Rsec::INVALID[units]
|
115
74
|
raise Rsec::SyntaxError.new "error parsing UnitsML expression", x, 1, 0
|
116
75
|
end
|
76
|
+
|
117
77
|
Rsec::Fail.reset
|
118
78
|
postprocess(units, text)
|
119
79
|
end
|
120
80
|
|
121
81
|
def postprocess(units, text)
|
122
|
-
units = postprocess1(units)
|
82
|
+
units = postprocess1(units.flatten)
|
123
83
|
quantity = text[1..-1]&.select { |x| /^quantity:/.match(x) }&.first&.sub(/^quantity:\s*/, "")
|
124
84
|
name = text[1..-1]&.select { |x| /^name:/.match(x) }&.first&.sub(/^name:\s*/, "")
|
125
85
|
symbol = text[1..-1]&.select { |x| /^symbol:/.match(x) }&.first&.sub(/^symbol:\s*/, "")
|
86
|
+
multiplier = text[1..-1]&.select { |x| /^multiplier:/.match(x) }&.first&.sub(/^multiplier:\s*/, "")
|
126
87
|
normtext = units_only(units).each.map do |u|
|
127
88
|
exp = u[:exponent] && u[:exponent] != "1" ? "^#{u[:exponent]}" : ""
|
128
89
|
"#{u[:prefix]}#{u[:unit]}#{exp}"
|
129
90
|
end.join("*")
|
130
|
-
[units, text[0], normtext, quantity, name, symbol]
|
91
|
+
[units, text[0], normtext, quantity, name, symbol, multiplier]
|
131
92
|
end
|
132
93
|
|
133
94
|
def postprocess1(units)
|
134
95
|
inverse = false
|
135
|
-
units.each_with_object([]) do |u, m|
|
96
|
+
units.each_with_object([]) do |u, m|
|
136
97
|
if u[:multiplier]
|
137
|
-
inverse =
|
98
|
+
inverse = !inverse if u[:multiplier] == "/"
|
138
99
|
else
|
139
|
-
u[:exponent] =
|
100
|
+
u[:exponent] =
|
101
|
+
inverse ? "-#{u[:display_exponent] || '1'}" : u[:display_exponent]
|
140
102
|
u[:exponent] = u[:exponent]&.sub(/^--+/, "")
|
141
103
|
end
|
142
104
|
m << u
|
@@ -153,27 +115,39 @@ module Asciimath2UnitsML
|
|
153
115
|
xml.is_a? String and xml = Nokogiri::XML(xml)
|
154
116
|
xml.xpath(".//m:mtext", "m" => MATHML_NS).each do |x|
|
155
117
|
next unless %r{^unitsml\(.+\)$}.match(x.text)
|
118
|
+
|
156
119
|
text = x.text.sub(%r{^unitsml\((.+)\)$}m, "\\1")
|
157
|
-
units, origtext, normtext, quantity, name, symbol = parse(text)
|
158
|
-
rendering = symbol ? embeddedmathml(asciimath2mathml(symbol)) :
|
159
|
-
|
160
|
-
x.replace("#{
|
120
|
+
units, origtext, normtext, quantity, name, symbol, multiplier = parse(text)
|
121
|
+
rendering = symbol ? embeddedmathml(asciimath2mathml(symbol)) :
|
122
|
+
mathmlsymbol(units, false, multiplier)
|
123
|
+
x.replace("#{delimspace(rendering, x)}"\
|
124
|
+
"<mrow xref='#{unit_id(origtext)}'>#{rendering}</mrow>\n"\
|
161
125
|
"#{unitsml(units, origtext, normtext, quantity, name)}")
|
162
126
|
end
|
163
127
|
dedup_ids(xml)
|
164
128
|
end
|
165
129
|
|
166
|
-
|
167
|
-
|
168
|
-
|
130
|
+
# if previous sibling's last descendent non-whitespace is MathML and mn or mi, no space
|
131
|
+
def delimspace(rendering, elem)
|
132
|
+
prec_text_elem =
|
133
|
+
elem.xpath("./preceding-sibling::*[namespace-uri() = '#{MATHML_NS}']/"\
|
134
|
+
"descendant::text()[normalize-space()!=''][last()]/parent::*").last
|
135
|
+
return "" if prec_text_elem.nil? ||
|
136
|
+
!%w(mn mi).include?(prec_text_elem&.name)
|
137
|
+
|
138
|
+
text = HTMLEntities.new.encode(Nokogiri::XML("<mrow>#{rendering}</mrow>")
|
139
|
+
.text.strip)
|
140
|
+
/\p{L}|\p{N}/.match(text) ?
|
169
141
|
"<mo rspace='thickmathspace'>⁢</mo>" : "<mo>⁢</mo>"
|
170
142
|
end
|
171
143
|
|
172
144
|
def dedup_ids(xml)
|
173
145
|
%w(Unit Dimension Prefix Quantity).each do |t|
|
174
|
-
xml.xpath(".//m:#{t}/@xml:id", "m" => UNITSML_NS).map
|
146
|
+
xml.xpath(".//m:#{t}/@xml:id", "m" => UNITSML_NS).map(&:text)
|
147
|
+
.uniq.each do |v|
|
175
148
|
xml.xpath(".//*[@xml:id = '#{v}']").each_with_index do |n, i|
|
176
|
-
next if i
|
149
|
+
next if i.zero?
|
150
|
+
|
177
151
|
n.remove
|
178
152
|
end
|
179
153
|
end
|
@@ -183,26 +157,28 @@ module Asciimath2UnitsML
|
|
183
157
|
|
184
158
|
def asciimath2mathml(expression)
|
185
159
|
AsciiMath::MathMLBuilder.new(:msword => true).append_expression(
|
186
|
-
AsciiMath.parse(HTMLEntities.new.decode(expression)).ast).to_s
|
187
|
-
|
160
|
+
AsciiMath.parse(HTMLEntities.new.decode(expression)).ast).to_s
|
161
|
+
.gsub(/<math>/, "<math xmlns='#{MATHML_NS}'>")
|
188
162
|
end
|
189
163
|
|
190
164
|
def embeddedmathml(mathml)
|
191
165
|
x = Nokogiri::XML(mathml)
|
192
|
-
x.xpath(".//m:mi", "m" => MATHML_NS)
|
166
|
+
x.xpath(".//m:mi", "m" => MATHML_NS)
|
167
|
+
.each { |mi| mi["mathvariant"] = "normal" }
|
193
168
|
x.children.to_xml
|
194
169
|
end
|
195
170
|
|
196
171
|
def ambig_units
|
197
|
-
u = @units_id.each_with_object({}) do |(
|
172
|
+
u = @units_id.each_with_object({}) do |(_k, v), m|
|
198
173
|
v.symbolids.each do |x|
|
199
174
|
next if %r{[*/^]}.match(x)
|
200
175
|
next unless v.symbols_hash[x][:html] != x
|
176
|
+
|
201
177
|
m[v.symbols_hash[x][:html]] ||= []
|
202
178
|
m[v.symbols_hash[x][:html]] << x
|
203
179
|
end
|
204
180
|
end
|
205
|
-
u.
|
181
|
+
u.each_key { |k| u[k] = u[k].unshift(k) if @symbols.dig(k, :html) == k }
|
206
182
|
render_ambig_units(u)
|
207
183
|
end
|
208
184
|
|
@@ -211,16 +187,17 @@ module Asciimath2UnitsML
|
|
211
187
|
u.each { |_, v| maxcols = v.size if maxcols < v.size }
|
212
188
|
puts %([cols="#{maxcols + 1}*"]\n|===\n|Symbol | Unit + ID #{"| " * (maxcols - 1)}\n)
|
213
189
|
puts "\n\n"
|
214
|
-
u.keys.sort_by { |a| [-u[a].size, a.gsub(%r{\&[^;]+;}, "")
|
190
|
+
u.keys.sort_by { |a| [-u[a].size, a.gsub(%r{\&[^;]+;}, "")
|
191
|
+
.gsub(/[^A-Za-z]/, "").downcase] }.each do |k|
|
215
192
|
print "| #{html2adoc(k)} "
|
216
|
-
u[k].sort_by
|
217
|
-
puts "#{
|
193
|
+
u[k].sort_by(&:size).each { |v1| print "| #{@units[v1].name}: `#{v1}` " }
|
194
|
+
puts "#{'| ' * (maxcols - u[k].size) }\n"
|
218
195
|
end
|
219
196
|
puts "|===\n"
|
220
197
|
end
|
221
198
|
|
222
|
-
def html2adoc(
|
223
|
-
|
199
|
+
def html2adoc(elem)
|
200
|
+
elem.gsub(%r{<i>}, "__").gsub(%r{</i>}, "__")
|
224
201
|
.gsub(%r{<sup>}, "^").gsub(%r{</sup>}, "^")
|
225
202
|
.gsub(%r{<sub>}, "~").gsub(%r{</sub>}, "~")
|
226
203
|
end
|