unitsml 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+ require_relative "unitsdb"
5
+ module Unitsml
6
+ class Parse < Parslet::Parser
7
+ include Unitsml::Unitsdb
8
+
9
+ rule(:power) { str("^") >> intermediate_exp(number) }
10
+ rule(:hyphen) { str("-") }
11
+ rule(:number) { (hyphen.maybe >> match(/[0-9]/).repeat(1)).as(:integer) }
12
+ rule(:extender) { (str("//") | str("/") | str("*")).as(:extender) }
13
+ rule(:sequence) { single_letter_prefixes >> units | double_letter_prefixes >> units | units }
14
+ rule(:unit_and_power) { units >> power.maybe }
15
+
16
+ rule(:units) do
17
+ @@filtered_units ||= arr_to_expression(Unitsdb.filtered_units, "units")
18
+ end
19
+
20
+ rule(:single_letter_prefixes) do
21
+ @@prefixes1 ||= arr_to_expression(Unitsdb.prefixes.select { |p| p.size == 1 }, "prefixes")
22
+ end
23
+
24
+ rule(:double_letter_prefixes) do
25
+ @@prefixes2 ||= arr_to_expression(Unitsdb.prefixes.select { |p| p.size == 2 }, "prefixes")
26
+ end
27
+
28
+ rule(:dimensions) do
29
+ @@dimensions ||= arr_to_expression(Unitsdb.parsable_dimensions.keys, "dimensions")
30
+ end
31
+
32
+ rule(:prefixes_units) do
33
+ (sqrt(sequence) >> extend_exp(prefixes_units)) |
34
+ (str("1").as(:units) >> extend_exp(prefixes_units)) |
35
+ (unit_and_power >> extender >> intermediate_exp(prefixes_units).as(:sequence)) |
36
+ unit_and_power |
37
+ (single_letter_prefixes >> unit_and_power >> extender >> intermediate_exp(prefixes_units).as(:sequence)) |
38
+ (single_letter_prefixes >> unit_and_power) |
39
+ (double_letter_prefixes >> unit_and_power >> extender >> intermediate_exp(prefixes_units).as(:sequence)) |
40
+ (double_letter_prefixes >> unit_and_power)
41
+ end
42
+
43
+ rule(:dimension_rules) do
44
+ (sqrt(intermediate_exp(dimensions >> power.maybe)) >> extend_exp(dimension_rules)) |
45
+ (dimensions >> power.maybe >> extend_exp(dimension_rules))
46
+ end
47
+
48
+ rule(:expression) do
49
+ intermediate_exp(prefixes_units) |
50
+ intermediate_exp(dimension_rules) |
51
+ single_letter_prefixes >> hyphen |
52
+ double_letter_prefixes >> hyphen
53
+ end
54
+
55
+ root :expression
56
+
57
+ def arr_to_expression(arr, file_name)
58
+ array = arr&.flatten&.compact&.sort_by(&:length).reverse
59
+ array&.reduce do |expression, expr_string|
60
+ expression = str(expression).as(file_name.to_sym) if expression.is_a?(String)
61
+ expression | str(expr_string).as(file_name.to_sym)
62
+ end
63
+ end
64
+
65
+ def sqrt(rule)
66
+ str("sqrt(") >> rule.as(:sqrt) >> str(")")
67
+ end
68
+
69
+ def extend_exp(rule)
70
+ (extender >> intermediate_exp(rule).as(:sequence)).maybe
71
+ end
72
+
73
+ def intermediate_exp(rule)
74
+ rule | (str("(") >> rule >> str(")"))
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ class Parser
5
+ attr_accessor :text
6
+
7
+ def initialize(text)
8
+ @regexp = %r{(quantity|name|symbol|multiplier):\s*}
9
+ @text = text&.match(/unitsml\((.*)\)/) ? Regexp.last_match[1] : text
10
+ @orig_text = @text
11
+ post_extras
12
+ end
13
+
14
+ def parse
15
+ nodes = Parse.new.parse(text)
16
+ formula = Formula.new(
17
+ [
18
+ Transform.new.apply(nodes),
19
+ ],
20
+ explicit_value: @extras_hash,
21
+ root: true,
22
+ orig_text: @orig_text,
23
+ norm_text: text,
24
+ )
25
+ update_units_exponents(formula.value, false)
26
+ formula
27
+ end
28
+
29
+ def update_units_exponents(array, inverse)
30
+ array.each do |object|
31
+ if object.is_a?(Sqrt)
32
+ object = object.value
33
+ object.power_numerator = "0.5"
34
+ end
35
+
36
+ case object
37
+ when Unit
38
+ next unless inverse
39
+
40
+ exponent = inverse ? "-#{object&.power_numerator || '1'}" : object.power_numerator
41
+ object.power_numerator = exponent&.sub(/^--+/, "")
42
+ when Extender then inverse = !inverse if ["/", "//"].any?(object.symbol)
43
+ when Formula then update_units_exponents(object.value, inverse)
44
+ end
45
+ end
46
+ end
47
+
48
+ def post_extras
49
+ return nil unless @regexp.match?(text)
50
+
51
+ @extras_hash = {}
52
+ texts_array = text&.split(",")&.map(&:strip)
53
+ @text = texts_array&.shift
54
+ texts_array&.map { |text| parse_extras(text) }
55
+ end
56
+
57
+ def parse_extras(text)
58
+ return nil unless @regexp.match?(text)
59
+
60
+ key, _, value = text&.partition(":")
61
+ @extras_hash[key&.to_sym] ||= value&.strip
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ class Prefix
5
+ attr_accessor :prefix_name
6
+
7
+ def initialize(prefix_name)
8
+ @prefix_name = prefix_name
9
+ end
10
+
11
+ def ==(object)
12
+ self.class == object.class &&
13
+ prefix_name == object&.prefix_name
14
+ end
15
+
16
+ def id
17
+ Unitsdb.prefixes_hash.dig(prefix_name, :id)
18
+ end
19
+
20
+ def name
21
+ fields.dig("name")
22
+ end
23
+
24
+ def fields
25
+ Unitsdb.prefixes_hash.dig(prefix_name, :fields)
26
+ end
27
+
28
+ def prefixes_symbols
29
+ fields&.dig("symbol")
30
+ end
31
+
32
+ def to_mathml
33
+ prefixes_symbols["html"]
34
+ end
35
+
36
+ def to_latex
37
+ prefixes_symbols["latex"]
38
+ end
39
+
40
+ def to_asciimath
41
+ prefixes_symbols["ascii"]
42
+ end
43
+
44
+ def to_html
45
+ prefixes_symbols["html"]
46
+ end
47
+
48
+ def to_unicode
49
+ prefixes_symbols["unicode"]
50
+ end
51
+
52
+ def symbolid
53
+ prefixes_symbols["ascii"] if prefixes_symbols
54
+ end
55
+
56
+ def base
57
+ fields&.dig("base")
58
+ end
59
+
60
+ def power
61
+ fields&.dig("power")
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ class Sqrt
5
+ attr_accessor :value
6
+
7
+ def initialize(value)
8
+ @value = value
9
+ end
10
+
11
+ def ==(object)
12
+ self.class == object.class &&
13
+ value == object&.value
14
+ end
15
+
16
+ def to_asciimath
17
+ value&.to_asciimath
18
+ end
19
+
20
+ def to_latex
21
+ value&.to_latex
22
+ end
23
+
24
+ def to_mathml
25
+ value&.to_mathml
26
+ end
27
+
28
+ def to_html
29
+ value&.to_html
30
+ end
31
+
32
+ def to_unicode
33
+ value.to_unicode
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+ module Unitsml
5
+ class Transform < Parslet::Transform
6
+ rule(sqrt: simple(:sqrt)) { Sqrt.new(sqrt) }
7
+ rule(units: simple(:unit)) { Unit.new(unit.to_s) }
8
+ rule(prefixes: simple(:prefix)) { Prefix.new(prefix.to_s) }
9
+ rule(dimensions: simple(:dimension)) { Dimension.new(dimension.to_s) }
10
+
11
+ rule(units: simple(:unit),
12
+ integer: simple(:integer)) do
13
+ Unit.new(unit.to_s, integer.to_s)
14
+ end
15
+
16
+ rule(dimensions: simple(:dimension),
17
+ integer: simple(:integer)) do
18
+ Dimension.new(dimension.to_s, integer.to_s)
19
+ end
20
+
21
+ rule(prefixes: simple(:prefix),
22
+ units: simple(:unit)) do
23
+ Unit.new(unit.to_s, prefix: Prefix.new(prefix.to_s))
24
+ end
25
+
26
+ rule(prefixes: simple(:prefix),
27
+ units: simple(:unit),
28
+ integer: simple(:integer)) do
29
+ prefix_obj = Prefix.new(prefix.to_s)
30
+ Unit.new(unit.to_s, integer.to_s, prefix: prefix_obj)
31
+ end
32
+
33
+ rule(prefixes: simple(:prefix),
34
+ units: simple(:unit),
35
+ integer: simple(:integer),
36
+ extender: simple(:extender),
37
+ sequence: simple(:sequence),) do
38
+ prefix_obj = Prefix.new(prefix.to_s)
39
+ unit_object = Unit.new(unit.to_s, integer.to_s, prefix: prefix_obj)
40
+ Formula.new(
41
+ [
42
+ unit_object,
43
+ Extender.new(extender.to_s),
44
+ sequence,
45
+ ],
46
+ )
47
+ end
48
+
49
+ rule(prefixes: simple(:prefix),
50
+ units: simple(:unit),
51
+ extender: simple(:extender),
52
+ sequence: simple(:sequence)) do
53
+ Formula.new(
54
+ [
55
+ Unit.new(unit.to_s, prefix: Prefix.new(prefix.to_s)),
56
+ Extender.new(extender.to_s),
57
+ sequence,
58
+ ],
59
+ )
60
+ end
61
+
62
+ rule(units: simple(:unit),
63
+ extender: simple(:extender),
64
+ sequence: simple(:sequence)) do
65
+ Formula.new(
66
+ [
67
+ Unit.new(unit.to_s),
68
+ Extender.new(extender.to_s),
69
+ sequence,
70
+ ],
71
+ )
72
+ end
73
+
74
+ rule(units: simple(:unit),
75
+ integer: simple(:integer),
76
+ extender: simple(:extender),
77
+ sequence: simple(:sequence)) do
78
+ Formula.new(
79
+ [
80
+ Unit.new(unit.to_s, integer.to_s),
81
+ Extender.new(extender.to_s),
82
+ sequence,
83
+ ],
84
+ )
85
+ end
86
+
87
+ rule(dimensions: simple(:dimension),
88
+ extender: simple(:extender),
89
+ sequence: simple(:sequence)) do
90
+ Formula.new(
91
+ [
92
+ Dimension.new(dimension.to_s),
93
+ Extender.new(extender.to_s),
94
+ sequence,
95
+ ],
96
+ )
97
+ end
98
+
99
+ rule(dimensions: simple(:dimension),
100
+ integer: simple(:integer),
101
+ extender: simple(:extender),
102
+ sequence: simple(:sequence)) do
103
+ Formula.new(
104
+ [
105
+ Dimension.new(dimension.to_s, integer.to_s),
106
+ Extender.new(extender.to_s),
107
+ sequence,
108
+ ],
109
+ )
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unitsml
4
+ class Unit
5
+ attr_accessor :unit_name, :power_numerator, :prefix
6
+
7
+ def initialize(unit_name,
8
+ power_numerator = nil,
9
+ prefix: nil)
10
+ @prefix = prefix
11
+ @unit_name = unit_name
12
+ @power_numerator = power_numerator
13
+ end
14
+
15
+ def ==(object)
16
+ self.class == object.class &&
17
+ prefix == object&.prefix &&
18
+ unit_name == object&.unit_name &&
19
+ power_numerator == object&.power_numerator
20
+ end
21
+
22
+ def fields_hash
23
+ Unitsdb.units.dig(unit_name, :fields)
24
+ end
25
+
26
+ def unit_symbols
27
+ symbols = fields_hash.dig("unit_symbols")
28
+ symbols.find{|symbol| symbol["id"] == unit_name }
29
+ end
30
+
31
+ def numerator_value(mathml = true)
32
+ integer = power_numerator.to_s
33
+ unless integer.match?(/-/)
34
+ return mathml ? [Ox.parse("<mn>#{integer}</mn>")] : integer
35
+ end
36
+
37
+ return integer.sub(/(-)(.+)/, '&#x2212;\2') unless mathml
38
+
39
+ integer = integer.sub(/(-)(.+)/, '<mn>\2</mn>')
40
+ integer = Ox.parse(integer)
41
+ mo_tag = (Utility.ox_element("mo") << "&#x2212;")
42
+ [mo_tag, integer]
43
+ end
44
+
45
+ def to_mathml
46
+ value = unit_symbols&.dig("mathml")
47
+ value = Ox.parse(value)
48
+ value.nodes.insert(0, prefix.to_mathml) if prefix
49
+ if power_numerator
50
+ msup = Utility.ox_element("msup")
51
+ msup << (Utility.ox_element("mrow") << value)
52
+ msup << Utility.update_nodes(
53
+ Utility.ox_element("mrow"),
54
+ numerator_value,
55
+ )
56
+ value = msup
57
+ end
58
+ value
59
+ end
60
+
61
+ def to_latex
62
+ value = unit_symbols&.dig("latex")
63
+ value = "#{value}^#{power_numerator}" if power_numerator
64
+ value = "#{prefix.to_latex}#{value}" if prefix
65
+ value
66
+ end
67
+
68
+ def to_asciimath
69
+ value = unit_symbols&.dig("ascii")
70
+ value = "#{value}^#{power_numerator}" if power_numerator
71
+ value = "#{prefix.to_asciimath}#{value}" if prefix
72
+ value
73
+ end
74
+
75
+ def to_html
76
+ value = unit_symbols&.dig("html")
77
+ if power_numerator
78
+ value = "#{value}<sup>#{numerator_value(false)}</sup>"
79
+ end
80
+ value = "#{prefix.to_html}#{value}" if prefix
81
+ value
82
+ end
83
+
84
+ def to_unicode
85
+ value = unit_symbols&.dig("unicode")
86
+ value = "#{value}^#{power_numerator}" if power_numerator
87
+ value = "#{prefix.to_unicode}#{value}" if prefix
88
+ value
89
+ end
90
+
91
+ def enumerated_name
92
+ fields_hash.dig("unit_name")&.first
93
+ end
94
+
95
+ def prefix_name
96
+ prefix&.prefix_name
97
+ end
98
+
99
+ def system_type
100
+ fields_hash.dig("unit_system", "type")
101
+ end
102
+
103
+ def system_name
104
+ fields_hash.dig("unit_system", "name")
105
+ end
106
+
107
+ def si_derived_bases
108
+ fields_hash.dig("si_derived_bases")
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ module Unitsml
5
+ module Unitsdb
6
+ class << self
7
+ def load_yaml(file_name)
8
+ @@hash ||= {}
9
+ file_path = File.path(valid_path(file_name))
10
+ @@hash[file_name.to_sym] ||= YAML.load_file(file_path)
11
+ end
12
+
13
+ def load_dimensions
14
+ @@dim_file = load_yaml("dimensions")
15
+ end
16
+
17
+ def load_units
18
+ @@units_file = load_yaml("units")
19
+ end
20
+
21
+ def units
22
+ @@units ||= {}
23
+ return @@units unless @@units.empty?
24
+
25
+ load_units.each do |key, value|
26
+ value["unit_symbols"]&.each do |symbol|
27
+ @@units[symbol["id"]] = { id: key, fields: value } unless symbol["id"]&.empty?
28
+ end
29
+ end
30
+ @@units
31
+ end
32
+
33
+ def prefixes
34
+ @@prefixes_array ||= prefixes_hash.keys.sort_by(&:length)
35
+ end
36
+
37
+ def parsable_dimensions
38
+ @@parsable_dimensions ||= {}
39
+ return @@parsable_dimensions unless @@parsable_dimensions.empty?
40
+
41
+ dimensions_hash.each do |key, value|
42
+ value.each do |_, v|
43
+ @@parsable_dimensions[find_id(v)] = { id: key, fields: value }
44
+ end
45
+ end
46
+ @@parsable_dimensions
47
+ end
48
+
49
+ def quantities
50
+ @@quantities ||= load_yaml("quantities")
51
+ end
52
+
53
+ def filtered_units
54
+ @@filtered_units_array ||= units.keys.reject do |unit|
55
+ ((/\*|\^|\/|^1$/).match?(unit) || units.dig(unit, :fields, "prefixed"))
56
+ end
57
+ end
58
+
59
+ def prefixes_hash
60
+ @@prefixes_hashes ||= prefixs_ids(load_yaml("prefixes"))
61
+ end
62
+
63
+ def dimensions_hash
64
+ @@dimensions_hashs ||= insert_vectors(load_dimensions)
65
+ end
66
+
67
+ def prefixs_ids(prefixe_hash, hash = {})
68
+ prefixe_hash&.each do |key, value|
69
+ symbol = value&.dig("symbol", "ascii")
70
+ hash[symbol] = { id: key, fields: value } unless symbol&.empty?
71
+ end
72
+ hash
73
+ end
74
+
75
+ def find_id(value)
76
+ return if value == true
77
+ return unless value.is_a?(Hash)
78
+
79
+ value&.dig("dim_symbols")&.map { |symbol| symbol&.dig("id") }&.first
80
+ end
81
+
82
+ def vector(dim_hash)
83
+ Utility::DIMS_VECTOR.map { |h| dim_hash.dig(underscore(h), "powerNumerator") }.join(":")
84
+ end
85
+
86
+ def underscore(str)
87
+ str.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
88
+ end
89
+
90
+ def insert_vectors(dims)
91
+ dims.each do |key, value|
92
+ value[:vector] = vector(value)
93
+ value[:id] = key
94
+ end
95
+ end
96
+
97
+ def valid_path(file_name)
98
+ path = "unitsdb/#{file_name}.yaml"
99
+ File.file?(path) ? path : "../#{path}"
100
+ end
101
+ end
102
+ end
103
+ end