asciimath2unitsml 0.2.4 → 0.3.3
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/.github/workflows/rake.yml +2 -23
- data/.gitignore +1 -0
- data/.hound.yml +5 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +1 -1
- data/README.adoc +4 -1
- data/Rakefile +1 -1
- data/asciimath2unitsml.gemspec +14 -17
- data/bin/rspec +1 -2
- data/lib/asciimath2unitsml.rb +0 -1
- data/lib/asciimath2unitsml/conv.rb +91 -61
- data/lib/asciimath2unitsml/parse.rb +74 -85
- data/lib/asciimath2unitsml/render.rb +24 -17
- data/lib/asciimath2unitsml/unit.rb +21 -17
- data/lib/asciimath2unitsml/validate.rb +57 -0
- data/lib/asciimath2unitsml/version.rb +1 -1
- data/lib/unitsdb_ruby/unitsdb.rb +21 -11
- data/spec/conv_spec.rb +1250 -986
- data/spec/spec_helper.rb +10 -10
- metadata +26 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 829d573c90a88f5d26b2310d69f5ea575084df208cd3f441342435b0a6a3d353
|
4
|
+
data.tar.gz: 534c2ea7485b1de1c126de112a1ad8c9b10683797034e99313fda199c5f7e572
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6eec2cb24fff7931d236a9a14a6d4ae1a938fe348d1e1d02a4d2f9be8e7f72e124e633ef0ef4b0f1e476ee8a2dee900210e60010600e0b082eb95237d2c70d2
|
7
|
+
data.tar.gz: e6943596be90a3bd1e6336b7bfa395cee4aa41e88ab6e5b2d003216a2271fc72249a018218d65c4ef446f4d246412e2b9fa0ac0d9721095abd4d9baa95515389
|
data/.github/workflows/rake.yml
CHANGED
@@ -16,21 +16,11 @@ jobs:
|
|
16
16
|
strategy:
|
17
17
|
fail-fast: false
|
18
18
|
matrix:
|
19
|
-
ruby: [ '2.7', '2.6', '2.5', '2.4' ]
|
19
|
+
ruby: [ '3.0', '2.7', '2.6', '2.5', '2.4' ]
|
20
20
|
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
21
21
|
experimental: [ false ]
|
22
|
-
include:
|
23
|
-
- ruby: '3.0'
|
24
|
-
os: 'ubuntu-latest'
|
25
|
-
experimental: true
|
26
|
-
- ruby: '3.0'
|
27
|
-
os: 'windows-latest'
|
28
|
-
experimental: true
|
29
|
-
- ruby: '3.0'
|
30
|
-
os: 'macos-latest'
|
31
|
-
experimental: true
|
32
22
|
steps:
|
33
|
-
- uses: actions/checkout@
|
23
|
+
- uses: actions/checkout@v2
|
34
24
|
with:
|
35
25
|
submodules: true
|
36
26
|
|
@@ -40,14 +30,3 @@ jobs:
|
|
40
30
|
bundler-cache: true
|
41
31
|
|
42
32
|
- run: bundle exec rake
|
43
|
-
|
44
|
-
tests-passed:
|
45
|
-
needs: rake
|
46
|
-
runs-on: ubuntu-latest
|
47
|
-
steps:
|
48
|
-
- uses: peter-evans/repository-dispatch@v1
|
49
|
-
with:
|
50
|
-
token: ${{ secrets.METANORMA_CI_PAT_TOKEN || secrets.GITHUB_TOKEN }}
|
51
|
-
repository: ${{ github.repository }}
|
52
|
-
event-type: notify
|
53
|
-
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}'
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.rubocop-https--*
|
data/.hound.yml
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Auto-generated by Cimas: Do not edit it manually!
|
2
|
+
# See https://github.com/metanorma/cimas
|
3
|
+
inherit_from:
|
4
|
+
- https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml
|
5
|
+
|
6
|
+
# local repo-specific modifications
|
7
|
+
# ...
|
8
|
+
|
9
|
+
AllCops:
|
10
|
+
TargetRubyVersion: 2.4
|
data/Gemfile
CHANGED
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/Rakefile
CHANGED
data/asciimath2unitsml.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
lib = File.expand_path("
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require "asciimath2unitsml/version"
|
6
6
|
|
@@ -29,25 +29,23 @@ Gem::Specification.new do |spec|
|
|
29
29
|
.split("\n")
|
30
30
|
.map { |kv_str| kv_str.split(" ") }
|
31
31
|
.each do |(_, submodule_path)|
|
32
|
+
# for each submodule, change working directory to that submodule
|
33
|
+
Dir.chdir(submodule_path) do
|
34
|
+
# issue git ls-files in submodule's directory
|
35
|
+
submodule_files = `git ls-files | grep -i '.yaml$'`.split($\)
|
32
36
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# issue git ls-files in submodule's directory
|
37
|
-
submodule_files = `git ls-files | grep -i '.yaml$'`.split($\)
|
38
|
-
|
39
|
-
submodule_files_paths = submodule_files.map do |filename|
|
40
|
-
File.join submodule_path, filename
|
41
|
-
end
|
42
|
-
|
43
|
-
# add relative paths to gem.files
|
44
|
-
spec.files += submodule_files_paths
|
37
|
+
submodule_files_paths = submodule_files.map do |filename|
|
38
|
+
File.join submodule_path, filename
|
45
39
|
end
|
40
|
+
|
41
|
+
# add relative paths to gem.files
|
42
|
+
spec.files += submodule_files_paths
|
43
|
+
end
|
46
44
|
end
|
47
45
|
|
48
46
|
spec.add_dependency "asciimath"
|
49
47
|
spec.add_dependency "htmlentities"
|
50
|
-
spec.add_dependency "nokogiri", "~> 1.
|
48
|
+
spec.add_dependency "nokogiri", "~> 1.11"
|
51
49
|
spec.add_dependency "rsec", "~> 1.0.0"
|
52
50
|
|
53
51
|
spec.add_development_dependency "bundler"
|
@@ -56,10 +54,9 @@ Gem::Specification.new do |spec|
|
|
56
54
|
spec.add_development_dependency "guard", "~> 2.14"
|
57
55
|
spec.add_development_dependency "guard-rspec", "~> 4.7"
|
58
56
|
spec.add_development_dependency "rake", "~> 12.0"
|
57
|
+
spec.add_development_dependency "rexml"
|
59
58
|
spec.add_development_dependency "rspec", "~> 3.6"
|
60
|
-
spec.add_development_dependency "rubocop", "
|
59
|
+
spec.add_development_dependency "rubocop", "~> 1.5.2"
|
61
60
|
spec.add_development_dependency "simplecov", "~> 0.15"
|
62
61
|
spec.add_development_dependency "timecop", "~> 0.9"
|
63
|
-
spec.add_development_dependency "rexml"
|
64
62
|
end
|
65
|
-
|
data/bin/rspec
CHANGED
data/lib/asciimath2unitsml.rb
CHANGED
@@ -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
|
44
|
-
<<~
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
units.map { |u| u[:prefix] }.reject(&:nil?).uniq.map do |p|
|
49
|
+
<<~XML
|
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>
|
58
|
+
XML
|
54
59
|
end.join("\n")
|
55
60
|
end
|
56
61
|
|
57
62
|
def dimension_components(dims)
|
58
63
|
return if dims.nil? || dims.empty?
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
+
|
65
|
+
<<~XML
|
66
|
+
<Dimension xmlns='#{UNITSML_NS}' xml:id="#{dim_id(dims)}">
|
67
|
+
#{dims.map { |u| dimension1(u) }.join("\n")}
|
68
|
+
</Dimension>
|
69
|
+
XML
|
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
|
-
def dimension1(
|
91
|
-
%(<#{
|
101
|
+
def dimension1(dim)
|
102
|
+
%(<#{dim[:dimension]} symbol="#{dim[:symbol]}"
|
103
|
+
powerNumerator="#{float_to_display(dim[: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,14 +182,17 @@ 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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
189
|
+
@units[normtext]&.dimension and
|
190
|
+
dim = %( dimensionURL="##{@units[normtext].dimension}")
|
191
|
+
<<~XML
|
192
|
+
<Quantity xmlns='#{UNITSML_NS}' xml:id="#{id}"#{dim} quantityType="base">
|
193
|
+
#{quantityname(id)}
|
194
|
+
</Quantity>
|
195
|
+
XML
|
167
196
|
end
|
168
197
|
|
169
198
|
def dimid2dimensions(normtext)
|
@@ -175,24 +204,25 @@ 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
|
-
<<~
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
210
|
+
<<~XML
|
211
|
+
<Dimension xmlns='#{UNITSML_NS}' xml:id="#{@units[normtext]&.dimension}">
|
212
|
+
#{dims.map { |u| dimension1(u) }.join("\n")}
|
213
|
+
</Dimension>
|
214
|
+
XML
|
185
215
|
end
|
186
216
|
|
187
217
|
def unitsml(units, origtext, normtext, quantity, name)
|
188
218
|
dims = units2dimensions(units)
|
189
|
-
<<~
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
219
|
+
<<~XML
|
220
|
+
#{unit(units, origtext, normtext, dims, name)}
|
221
|
+
#{prefix(units)}
|
222
|
+
#{dimension(normtext)}
|
223
|
+
#{dimension_components(dims)}
|
224
|
+
#{quantity(normtext, quantity)}
|
225
|
+
XML
|
196
226
|
end
|
197
227
|
end
|
198
228
|
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,46 +55,58 @@ 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
|
|
111
|
-
def parse(
|
112
|
-
text = Array(
|
70
|
+
def parse(expr)
|
71
|
+
text = Array(expr.split(/,\s*/))
|
113
72
|
units = @parser.parse!(text[0])
|
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)
|
123
|
-
quantity = text[1..-1]&.select
|
124
|
-
|
125
|
-
|
82
|
+
units = postprocess1(units.flatten)
|
83
|
+
quantity = text[1..-1]&.select do |x|
|
84
|
+
/^quantity:/.match(x)
|
85
|
+
end&.first&.sub(/^quantity:\s*/, "")
|
86
|
+
name = text[1..-1]&.select do |x|
|
87
|
+
/^name:/.match(x)
|
88
|
+
end&.first&.sub(/^name:\s*/, "")
|
89
|
+
symbol = text[1..-1]&.select do |x|
|
90
|
+
/^symbol:/.match(x)
|
91
|
+
end&.first&.sub(/^symbol:\s*/, "")
|
92
|
+
multiplier = text[1..-1]&.select do |x|
|
93
|
+
/^multiplier:/.match(x)
|
94
|
+
end&.first&.sub(/^multiplier:\s*/, "")
|
126
95
|
normtext = units_only(units).each.map do |u|
|
127
96
|
exp = u[:exponent] && u[:exponent] != "1" ? "^#{u[:exponent]}" : ""
|
128
97
|
"#{u[:prefix]}#{u[:unit]}#{exp}"
|
129
98
|
end.join("*")
|
130
|
-
[units, text[0], normtext, quantity, name, symbol]
|
99
|
+
[units, text[0], normtext, quantity, name, symbol, multiplier]
|
131
100
|
end
|
132
101
|
|
133
102
|
def postprocess1(units)
|
134
103
|
inverse = false
|
135
|
-
units.each_with_object([]) do |u, m|
|
104
|
+
units.each_with_object([]) do |u, m|
|
136
105
|
if u[:multiplier]
|
137
|
-
inverse =
|
106
|
+
inverse = !inverse if u[:multiplier] == "/"
|
138
107
|
else
|
139
|
-
u[:exponent] =
|
108
|
+
u[:exponent] =
|
109
|
+
inverse ? "-#{u[:display_exponent] || '1'}" : u[:display_exponent]
|
140
110
|
u[:exponent] = u[:exponent]&.sub(/^--+/, "")
|
141
111
|
end
|
142
112
|
m << u
|
@@ -148,32 +118,48 @@ module Asciimath2UnitsML
|
|
148
118
|
MathML2UnitsML(xml).to_xml
|
149
119
|
end
|
150
120
|
|
151
|
-
# https://www.w3.org/TR/mathml-units/ section 2:
|
121
|
+
# https://www.w3.org/TR/mathml-units/ section 2:
|
122
|
+
# delimit number Invisible-Times unit
|
152
123
|
def MathML2UnitsML(xml)
|
153
124
|
xml.is_a? String and xml = Nokogiri::XML(xml)
|
154
125
|
xml.xpath(".//m:mtext", "m" => MATHML_NS).each do |x|
|
155
|
-
next unless %r{^unitsml\(.+\)$}.match(x.text)
|
126
|
+
next unless %r{^unitsml\(.+\)$}.match?(x.text)
|
127
|
+
|
156
128
|
text = x.text.sub(%r{^unitsml\((.+)\)$}m, "\\1")
|
157
|
-
units, origtext, normtext, quantity, name, symbol =
|
158
|
-
|
159
|
-
|
129
|
+
units, origtext, normtext, quantity, name, symbol, multiplier =
|
130
|
+
parse(text)
|
131
|
+
rendering = symbol ? embeddedmathml(asciimath2mathml(symbol)) :
|
132
|
+
mathmlsymbol(units, false, multiplier)
|
133
|
+
x.replace("#{delimspace(rendering, x)}"\
|
134
|
+
"<mrow xref='#{unit_id(origtext)}'>#{rendering}</mrow>\n"\
|
160
135
|
"#{unitsml(units, origtext, normtext, quantity, name)}")
|
161
136
|
end
|
162
137
|
dedup_ids(xml)
|
163
138
|
end
|
164
139
|
|
140
|
+
# if previous sibling's last descendent non-whitespace is MathML and
|
141
|
+
# mn or mi, no space
|
165
142
|
def delimspace(rendering, elem)
|
166
|
-
|
167
|
-
|
143
|
+
prec_text_elem =
|
144
|
+
elem.xpath("./preceding-sibling::*[namespace-uri() = '#{MATHML_NS}']/"\
|
145
|
+
"descendant::text()[normalize-space()!='']"\
|
146
|
+
"[last()]/parent::*").last
|
147
|
+
return "" if prec_text_elem.nil? ||
|
148
|
+
!%w(mn mi).include?(prec_text_elem&.name)
|
149
|
+
|
150
|
+
text = HTMLEntities.new.encode(Nokogiri::XML("<mrow>#{rendering}</mrow>")
|
151
|
+
.text.strip)
|
168
152
|
/\p{L}|\p{N}/.match(text) ?
|
169
153
|
"<mo rspace='thickmathspace'>⁢</mo>" : "<mo>⁢</mo>"
|
170
154
|
end
|
171
155
|
|
172
156
|
def dedup_ids(xml)
|
173
157
|
%w(Unit Dimension Prefix Quantity).each do |t|
|
174
|
-
xml.xpath(".//m:#{t}/@xml:id", "m" => UNITSML_NS).map
|
158
|
+
xml.xpath(".//m:#{t}/@xml:id", "m" => UNITSML_NS).map(&:text)
|
159
|
+
.uniq.each do |v|
|
175
160
|
xml.xpath(".//*[@xml:id = '#{v}']").each_with_index do |n, i|
|
176
|
-
next if i
|
161
|
+
next if i.zero?
|
162
|
+
|
177
163
|
n.remove
|
178
164
|
end
|
179
165
|
end
|
@@ -183,44 +169,47 @@ module Asciimath2UnitsML
|
|
183
169
|
|
184
170
|
def asciimath2mathml(expression)
|
185
171
|
AsciiMath::MathMLBuilder.new(:msword => true).append_expression(
|
186
|
-
AsciiMath.parse(HTMLEntities.new.decode(expression)).ast
|
187
|
-
gsub(/<math>/, "<math xmlns='#{MATHML_NS}'>")
|
172
|
+
AsciiMath.parse(HTMLEntities.new.decode(expression)).ast
|
173
|
+
).to_s.gsub(/<math>/, "<math xmlns='#{MATHML_NS}'>")
|
188
174
|
end
|
189
175
|
|
190
176
|
def embeddedmathml(mathml)
|
191
177
|
x = Nokogiri::XML(mathml)
|
192
|
-
x.xpath(".//m:mi", "m" => MATHML_NS)
|
178
|
+
x.xpath(".//m:mi", "m" => MATHML_NS)
|
179
|
+
.each { |mi| mi["mathvariant"] = "normal" }
|
193
180
|
x.children.to_xml
|
194
181
|
end
|
195
182
|
|
196
183
|
def ambig_units
|
197
|
-
u = @units_id.each_with_object({}) do |(
|
184
|
+
u = @units_id.each_with_object({}) do |(_k, v), m|
|
198
185
|
v.symbolids.each do |x|
|
199
186
|
next if %r{[*/^]}.match(x)
|
200
187
|
next unless v.symbols_hash[x][:html] != x
|
188
|
+
|
201
189
|
m[v.symbols_hash[x][:html]] ||= []
|
202
190
|
m[v.symbols_hash[x][:html]] << x
|
203
191
|
end
|
204
192
|
end
|
205
|
-
u.
|
193
|
+
u.each_key { |k| u[k] = u[k].unshift(k) if @symbols.dig(k, :html) == k }
|
206
194
|
render_ambig_units(u)
|
207
195
|
end
|
208
196
|
|
209
197
|
def render_ambig_units(u)
|
210
198
|
maxcols = 0
|
211
199
|
u.each { |_, v| maxcols = v.size if maxcols < v.size }
|
212
|
-
puts %([cols="#{maxcols + 1}*"]\n|===\n|Symbol | Unit + ID #{
|
200
|
+
puts %([cols="#{maxcols + 1}*"]\n|===\n|Symbol | Unit + ID #{'| ' * (maxcols - 1)}\n)
|
213
201
|
puts "\n\n"
|
214
|
-
u.keys.sort_by { |a| [-u[a].size, a.gsub(%r{\&[^;]+;}, "")
|
202
|
+
u.keys.sort_by { |a| [-u[a].size, a.gsub(%r{\&[^;]+;}, "")
|
203
|
+
.gsub(/[^A-Za-z]/, "").downcase] }.each do |k|
|
215
204
|
print "| #{html2adoc(k)} "
|
216
|
-
u[k].sort_by
|
217
|
-
puts "#{
|
205
|
+
u[k].sort_by(&:size).each { |v1| print "| #{@units[v1].name}: `#{v1}` " }
|
206
|
+
puts "#{'| ' * (maxcols - u[k].size)}\n"
|
218
207
|
end
|
219
208
|
puts "|===\n"
|
220
209
|
end
|
221
210
|
|
222
|
-
def html2adoc(
|
223
|
-
|
211
|
+
def html2adoc(elem)
|
212
|
+
elem.gsub(%r{<i>}, "__").gsub(%r{</i>}, "__")
|
224
213
|
.gsub(%r{<sup>}, "^").gsub(%r{</sup>}, "^")
|
225
214
|
.gsub(%r{<sub>}, "~").gsub(%r{</sub>}, "~")
|
226
215
|
end
|