unitsml 0.2.0

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.
@@ -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