plurimath 0.11.0 → 0.11.1
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/.rubocop_todo.yml +354 -34
- data/Gemfile +2 -2
- data/README.adoc +49 -9
- data/lib/plurimath/deprecation.rb +2 -1
- data/lib/plurimath/errors/configuration_error.rb +5 -1
- data/lib/plurimath/errors/deprecation_error.rb +2 -1
- data/lib/plurimath/errors/invalid_number.rb +17 -0
- data/lib/plurimath/errors/unsupported_base.rb +18 -0
- data/lib/plurimath/errors/{formatter/unsupported_locale.rb → unsupported_locale.rb} +1 -1
- data/lib/plurimath/errors.rb +8 -0
- data/lib/plurimath/formatter/numbers/base_notation.rb +7 -2
- data/lib/plurimath/formatter/numbers/format_options.rb +123 -17
- data/lib/plurimath/formatter/numbers/fraction.rb +7 -16
- data/lib/plurimath/formatter/numbers/integer.rb +3 -0
- data/lib/plurimath/formatter/numbers/notation_renderer.rb +35 -6
- data/lib/plurimath/formatter/numbers/number_renderer.rb +16 -4
- data/lib/plurimath/formatter/numbers/precision_resolver.rb +16 -3
- data/lib/plurimath/formatter/numbers/sign_renderer.rb +2 -1
- data/lib/plurimath/formatter/numbers/significant.rb +4 -2
- data/lib/plurimath/formatter/numbers/source.rb +18 -3
- data/lib/plurimath/formatter/standard.rb +6 -0
- data/lib/plurimath/formatter/supported_locales.rb +1 -1
- data/lib/plurimath/formatter.rb +0 -2
- data/lib/plurimath/html/transform.rb +24 -12
- data/lib/plurimath/math/core.rb +17 -14
- data/lib/plurimath/math/function/overleftrightarrow.rb +2 -2
- data/lib/plurimath/math/number.rb +11 -1
- data/lib/plurimath/math.rb +4 -1
- data/lib/plurimath/number_formatter.rb +45 -4
- data/lib/plurimath/omml/formula_transformation.rb +2 -1
- data/lib/plurimath/omml/translator.rb +4 -1
- data/lib/plurimath/unicode_math/parse.rb +93 -53
- data/lib/plurimath/unicode_math/parser.rb +7 -4
- data/lib/plurimath/unicode_math/parsing_rules/absence_rules.rb +33 -21
- data/lib/plurimath/unicode_math/parsing_rules/common_rules.rb +28 -16
- data/lib/plurimath/unicode_math/parsing_rules/constants_rules.rb +18 -7
- data/lib/plurimath/unicode_math/parsing_rules/masked.rb +35 -15
- data/lib/plurimath/unicode_math/parsing_rules/sub_sup.rb +98 -72
- data/lib/plurimath/unicode_math/transform.rb +755 -468
- data/lib/plurimath/version.rb +1 -1
- data/lib/plurimath.rb +1 -0
- metadata +5 -4
- data/lib/plurimath/errors/formatter/unsupported_base.rb +0 -21
|
@@ -5,17 +5,30 @@ module Plurimath
|
|
|
5
5
|
module Numbers
|
|
6
6
|
# Chooses which precision source wins for one render call.
|
|
7
7
|
class PrecisionResolver
|
|
8
|
-
def resolve(source, precision:, base:, significant:,
|
|
8
|
+
def resolve(source, precision:, base:, significant:,
|
|
9
|
+
notation_supported:, digit_count: 0)
|
|
9
10
|
return precision if precision
|
|
10
11
|
|
|
11
|
-
significant_precision = significant_base_precision(source, base,
|
|
12
|
+
significant_precision = significant_base_precision(source, base,
|
|
13
|
+
significant)
|
|
12
14
|
return significant_precision if significant_precision
|
|
13
15
|
|
|
14
16
|
# Source owns input-derived digit lengths; this resolver only decides
|
|
15
17
|
# which precision rule wins. Plain decimal rendering preserves
|
|
16
18
|
# fractional scale, while notation precision controls coefficient
|
|
17
19
|
# digits after the leading digit.
|
|
18
|
-
|
|
20
|
+
if notation_supported
|
|
21
|
+
# A requested digit budget (significant or digit_count) widens the
|
|
22
|
+
# coefficient fraction beyond the source's own significant digits
|
|
23
|
+
# (one digit leads, so the fraction allowance is budget - 1).
|
|
24
|
+
budget = [significant, digit_count].max
|
|
25
|
+
if budget.positive?
|
|
26
|
+
return [budget - 1,
|
|
27
|
+
source.notation_precision].max
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return source.notation_precision
|
|
31
|
+
end
|
|
19
32
|
|
|
20
33
|
source.decimal_precision
|
|
21
34
|
end
|
|
@@ -75,7 +75,8 @@ module Plurimath
|
|
|
75
75
|
|
|
76
76
|
fraction = reversed[0...decimal_index]
|
|
77
77
|
integer = reversed[(decimal_index + 1)..]
|
|
78
|
-
fraction, carry = digit_sequence.increment_reversed(fraction,
|
|
78
|
+
fraction, carry = digit_sequence.increment_reversed(fraction,
|
|
79
|
+
overflow: EMPTY_STRING)
|
|
79
80
|
return fraction + [CANONICAL_DECIMAL] + integer unless carry.positive?
|
|
80
81
|
|
|
81
82
|
integer = increment_integer(integer)
|
|
@@ -83,7 +84,8 @@ module Plurimath
|
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
def increment_integer(reversed)
|
|
86
|
-
digits, carry = digit_sequence.increment_reversed(reversed,
|
|
87
|
+
digits, carry = digit_sequence.increment_reversed(reversed,
|
|
88
|
+
overflow: ZERO)
|
|
87
89
|
digits << "1" if carry.positive?
|
|
88
90
|
digits
|
|
89
91
|
end
|
|
@@ -13,9 +13,13 @@ module Plurimath
|
|
|
13
13
|
|
|
14
14
|
DEFAULT_INTEGER = "0"
|
|
15
15
|
EMPTY_STRING = ""
|
|
16
|
+
# Stricter than BigDecimal(), which tolerates underscores, surrounding
|
|
17
|
+
# junk after partial parses, and Infinity/NaN spellings.
|
|
18
|
+
NUMERIC_PATTERN = /\A[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?\z/
|
|
16
19
|
|
|
17
20
|
def initialize(value)
|
|
18
21
|
@raw = value.to_s
|
|
22
|
+
validate_numeric!(value)
|
|
19
23
|
@decimal = BigDecimal(raw)
|
|
20
24
|
@sign = raw.start_with?("-") ? -1 : 1
|
|
21
25
|
|
|
@@ -33,9 +37,13 @@ module Plurimath
|
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def notation_precision
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
# A zero coefficient keeps the source's stated fraction width.
|
|
41
|
+
return fraction_digits.length if decimal.zero?
|
|
42
|
+
|
|
43
|
+
# The coefficient's fraction width is its significant-digit count
|
|
44
|
+
# minus the single leading digit; the sign and leading zeros carry
|
|
45
|
+
# no precision.
|
|
46
|
+
[significant_digit_count - 1, 0].max
|
|
39
47
|
end
|
|
40
48
|
|
|
41
49
|
def significant_digit_count
|
|
@@ -71,6 +79,13 @@ module Plurimath
|
|
|
71
79
|
|
|
72
80
|
private
|
|
73
81
|
|
|
82
|
+
def validate_numeric!(value)
|
|
83
|
+
valid_type = value.is_a?(Numeric) || value.is_a?(String)
|
|
84
|
+
return if valid_type && NUMERIC_PATTERN.match?(raw)
|
|
85
|
+
|
|
86
|
+
raise Plurimath::Errors::InvalidNumber, value
|
|
87
|
+
end
|
|
88
|
+
|
|
74
89
|
def decimal_parts_integer_length
|
|
75
90
|
parts = to_parts
|
|
76
91
|
return 0 if parts.integer_zero?
|
|
@@ -35,6 +35,12 @@ module Plurimath
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def set_default_options(options)
|
|
38
|
+
unless options.nil? || options.is_a?(Hash)
|
|
39
|
+
raise Plurimath::ConfigurationError.new(
|
|
40
|
+
:invalid_formatter_option, option: :options, value: options, supported: "a Hash"
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
38
44
|
options = options ? options.dup : {}
|
|
39
45
|
apply_default_symbols(options)
|
|
40
46
|
options
|
|
@@ -118,7 +118,7 @@ module Plurimath
|
|
|
118
118
|
locale_key = key_for(locale)
|
|
119
119
|
return locale_key if locale.nil? || locale_key
|
|
120
120
|
|
|
121
|
-
raise UnsupportedLocale.new(locale, LOCALES.keys)
|
|
121
|
+
raise Plurimath::Errors::UnsupportedLocale.new(locale, LOCALES.keys)
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def key_for(locale)
|
data/lib/plurimath/formatter.rb
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
module Formatter
|
|
5
5
|
autoload :SupportedLocales, "#{__dir__}/formatter/supported_locales"
|
|
6
|
-
autoload :UnsupportedBase, "#{__dir__}/errors/formatter/unsupported_base"
|
|
7
|
-
autoload :UnsupportedLocale, "#{__dir__}/errors/formatter/unsupported_locale"
|
|
8
6
|
autoload :Numbers, "#{__dir__}/formatter/numbers"
|
|
9
7
|
autoload :Standard, "#{__dir__}/formatter/standard"
|
|
10
8
|
end
|
|
@@ -202,25 +202,29 @@ module Plurimath
|
|
|
202
202
|
rule(sub_sup: simple(:sub_sup),
|
|
203
203
|
sub_value: simple(:sub_value),
|
|
204
204
|
sup_value: simple(:sup_value)) do
|
|
205
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
205
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
206
|
+
sup_value: sup_value)
|
|
206
207
|
end
|
|
207
208
|
|
|
208
209
|
rule(sub_sup: simple(:sub_sup),
|
|
209
210
|
sub_value: simple(:sub_value),
|
|
210
211
|
sup_value: sequence(:sup_value)) do
|
|
211
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
212
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
213
|
+
sup_value: sup_value)
|
|
212
214
|
end
|
|
213
215
|
|
|
214
216
|
rule(sub_sup: simple(:sub_sup),
|
|
215
217
|
sub_value: sequence(:sub_value),
|
|
216
218
|
sup_value: simple(:sup_value)) do
|
|
217
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
219
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
220
|
+
sup_value: sup_value)
|
|
218
221
|
end
|
|
219
222
|
|
|
220
223
|
rule(sub_sup: simple(:sub_sup),
|
|
221
224
|
sub_value: sequence(:sub_value),
|
|
222
225
|
sup_value: sequence(:sup_value)) do
|
|
223
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
226
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
227
|
+
sup_value: sup_value)
|
|
224
228
|
end
|
|
225
229
|
|
|
226
230
|
# Parslet emits separate shapes for each sub/sup value combination and
|
|
@@ -303,7 +307,8 @@ module Plurimath
|
|
|
303
307
|
sup_value: simple(:sup_value),
|
|
304
308
|
expression: simple(:expression)) do
|
|
305
309
|
TransformUtility.append_expression(
|
|
306
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
310
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
311
|
+
sup_value: sup_value),
|
|
307
312
|
expression,
|
|
308
313
|
)
|
|
309
314
|
end
|
|
@@ -313,7 +318,8 @@ module Plurimath
|
|
|
313
318
|
sup_value: simple(:sup_value),
|
|
314
319
|
expression: sequence(:expression)) do
|
|
315
320
|
TransformUtility.append_expression(
|
|
316
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
321
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
322
|
+
sup_value: sup_value),
|
|
317
323
|
expression,
|
|
318
324
|
)
|
|
319
325
|
end
|
|
@@ -323,7 +329,8 @@ module Plurimath
|
|
|
323
329
|
sup_value: sequence(:sup_value),
|
|
324
330
|
expression: simple(:expression)) do
|
|
325
331
|
TransformUtility.append_expression(
|
|
326
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
332
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
333
|
+
sup_value: sup_value),
|
|
327
334
|
expression,
|
|
328
335
|
)
|
|
329
336
|
end
|
|
@@ -333,7 +340,8 @@ module Plurimath
|
|
|
333
340
|
sup_value: sequence(:sup_value),
|
|
334
341
|
expression: sequence(:expression)) do
|
|
335
342
|
TransformUtility.append_expression(
|
|
336
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
343
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
344
|
+
sup_value: sup_value),
|
|
337
345
|
expression,
|
|
338
346
|
)
|
|
339
347
|
end
|
|
@@ -343,7 +351,8 @@ module Plurimath
|
|
|
343
351
|
sup_value: simple(:sup_value),
|
|
344
352
|
expression: simple(:expression)) do
|
|
345
353
|
TransformUtility.append_expression(
|
|
346
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
354
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
355
|
+
sup_value: sup_value),
|
|
347
356
|
expression,
|
|
348
357
|
)
|
|
349
358
|
end
|
|
@@ -353,7 +362,8 @@ module Plurimath
|
|
|
353
362
|
sup_value: simple(:sup_value),
|
|
354
363
|
expression: sequence(:expression)) do
|
|
355
364
|
TransformUtility.append_expression(
|
|
356
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
365
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
366
|
+
sup_value: sup_value),
|
|
357
367
|
expression,
|
|
358
368
|
)
|
|
359
369
|
end
|
|
@@ -363,7 +373,8 @@ module Plurimath
|
|
|
363
373
|
sup_value: sequence(:sup_value),
|
|
364
374
|
expression: simple(:expression)) do
|
|
365
375
|
TransformUtility.append_expression(
|
|
366
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
376
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
377
|
+
sup_value: sup_value),
|
|
367
378
|
expression,
|
|
368
379
|
)
|
|
369
380
|
end
|
|
@@ -373,7 +384,8 @@ module Plurimath
|
|
|
373
384
|
sup_value: sequence(:sup_value),
|
|
374
385
|
expression: sequence(:expression)) do
|
|
375
386
|
TransformUtility.append_expression(
|
|
376
|
-
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
387
|
+
TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value,
|
|
388
|
+
sup_value: sup_value),
|
|
377
389
|
expression,
|
|
378
390
|
)
|
|
379
391
|
end
|
data/lib/plurimath/math/core.rb
CHANGED
|
@@ -53,14 +53,15 @@ module Plurimath
|
|
|
53
53
|
wrapper_tag << r_tag
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def omml_parameter(field, display_style, tag_name:, options:,
|
|
56
|
+
def omml_parameter(field, display_style, tag_name:, options:,
|
|
57
|
+
namespace: "m")
|
|
57
58
|
tag = ox_element(tag_name, namespace: namespace)
|
|
58
59
|
return empty_tag(tag) unless field
|
|
59
60
|
|
|
60
61
|
field_value = if field.is_a?(Array)
|
|
61
|
-
field.map
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
field.map do |object|
|
|
63
|
+
object.insert_t_tag(display_style, options: options)
|
|
64
|
+
end
|
|
64
65
|
else
|
|
65
66
|
field.insert_t_tag(display_style, options: options)
|
|
66
67
|
end
|
|
@@ -106,7 +107,7 @@ module Plurimath
|
|
|
106
107
|
|
|
107
108
|
options[:array] << field&.to_asciimath_math_zone(
|
|
108
109
|
hashed[:function_spacing], hashed[:last], hashed[:indent], options: options[:options]
|
|
109
|
-
)
|
|
110
|
+
)
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def latex_fields_to_print(field, options = {})
|
|
@@ -131,7 +132,7 @@ module Plurimath
|
|
|
131
132
|
|
|
132
133
|
options[:array] << field&.to_mathml_math_zone(
|
|
133
134
|
hashed[:function_spacing], hashed[:last], hashed[:indent], options: options[:options]
|
|
134
|
-
)
|
|
135
|
+
)
|
|
135
136
|
end
|
|
136
137
|
|
|
137
138
|
def omml_fields_to_print(field, options = {})
|
|
@@ -156,13 +157,13 @@ module Plurimath
|
|
|
156
157
|
|
|
157
158
|
options[:array] << field&.to_unicodemath_math_zone(
|
|
158
159
|
hashed[:function_spacing], hashed[:last], hashed[:indent], options: options[:options]
|
|
159
|
-
)
|
|
160
|
+
)
|
|
160
161
|
end
|
|
161
162
|
|
|
162
163
|
def dump_mathml(field, intent = false, options:)
|
|
163
164
|
dump_ox_nodes(field.mathml_nodes(intent, options: options)).gsub(
|
|
164
165
|
/\n\s*/, ""
|
|
165
|
-
)
|
|
166
|
+
)
|
|
166
167
|
end
|
|
167
168
|
|
|
168
169
|
def dump_omml(field, display_style, options:)
|
|
@@ -170,7 +171,7 @@ module Plurimath
|
|
|
170
171
|
|
|
171
172
|
dump_ox_nodes(field.omml_nodes(display_style, options: options)).gsub(
|
|
172
173
|
/\n\s*/, ""
|
|
173
|
-
)
|
|
174
|
+
)
|
|
174
175
|
end
|
|
175
176
|
|
|
176
177
|
def mathml_nodes(intent, options:)
|
|
@@ -260,8 +261,10 @@ module Plurimath
|
|
|
260
261
|
case field
|
|
261
262
|
when Core
|
|
262
263
|
field.line_breaking(obj)
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
if obj.value_exist?
|
|
265
|
+
updated_object_values(variable, obj: obj,
|
|
266
|
+
update_value: true)
|
|
267
|
+
end
|
|
265
268
|
when Array
|
|
266
269
|
array_line_break_field(field, variable, obj)
|
|
267
270
|
end
|
|
@@ -361,9 +364,9 @@ module Plurimath
|
|
|
361
364
|
return false unless field.is_a?(Math::Symbols::Symbol)
|
|
362
365
|
return true if field&.value&.include?("'")
|
|
363
366
|
|
|
364
|
-
Utility.primes_constants.any?
|
|
365
|
-
|
|
366
|
-
|
|
367
|
+
Utility.primes_constants.any? do |_prefix, prime|
|
|
368
|
+
unicodemath_field_value(field).include?(prime)
|
|
369
|
+
end
|
|
367
370
|
end
|
|
368
371
|
|
|
369
372
|
def paren?
|
|
@@ -14,7 +14,7 @@ module Plurimath
|
|
|
14
14
|
def to_mathml_without_math_tag(intent, options:)
|
|
15
15
|
mover = ox_element("mover")
|
|
16
16
|
first_value = parameter_one&.to_mathml_without_math_tag(intent,
|
|
17
|
-
|
|
17
|
+
options: options)
|
|
18
18
|
if attributes && attributes[:accent]
|
|
19
19
|
mover[:accent] =
|
|
20
20
|
attributes[:accent]
|
|
@@ -36,7 +36,7 @@ module Plurimath
|
|
|
36
36
|
else
|
|
37
37
|
symbol = Symbols::Symbol.new("\u20e1")
|
|
38
38
|
Overset.new(parameter_one, symbol).to_omml_without_math_tag(true,
|
|
39
|
-
|
|
39
|
+
options: options)
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -103,11 +103,21 @@ module Plurimath
|
|
|
103
103
|
elsif formatter.respond_to?(:format)
|
|
104
104
|
formatter.format(options[:formula], self)
|
|
105
105
|
elsif formatter.respond_to?(:localized_number)
|
|
106
|
-
formatter.localized_number(value.to_s
|
|
106
|
+
formatter.localized_number(value.to_s,
|
|
107
|
+
**format_kwargs(options[:format]))
|
|
107
108
|
else
|
|
108
109
|
value
|
|
109
110
|
end
|
|
110
111
|
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
# Empty splat is silently dropped by Ruby, so simple formatter signatures still work.
|
|
116
|
+
def format_kwargs(format)
|
|
117
|
+
return {} unless format.is_a?(Hash) && !format.empty?
|
|
118
|
+
|
|
119
|
+
{ format: format }
|
|
120
|
+
end
|
|
111
121
|
end
|
|
112
122
|
end
|
|
113
123
|
end
|
data/lib/plurimath/math.rb
CHANGED
|
@@ -32,7 +32,10 @@ module Plurimath
|
|
|
32
32
|
unknown_options = options.keys - SUPPORTED_PARSE_OPTIONS
|
|
33
33
|
raise_unknown_parse_options!(unknown_options) unless unknown_options.empty?
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
if options.key?(:locale) && !localized_parse_type?(type)
|
|
36
|
+
raise_unsupported_parse_option!(type,
|
|
37
|
+
:locale)
|
|
38
|
+
end
|
|
36
39
|
options = normalize_parse_options(options)
|
|
37
40
|
|
|
38
41
|
begin
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Plurimath
|
|
4
4
|
class NumberFormatter
|
|
5
|
-
attr_accessor :locale, :
|
|
5
|
+
attr_accessor :locale, :precision, :localize_number, :localizer_symbols
|
|
6
6
|
|
|
7
7
|
def initialize(
|
|
8
8
|
locale = "en",
|
|
@@ -24,7 +24,8 @@ module Plurimath
|
|
|
24
24
|
)
|
|
25
25
|
locale = supported_locale(locale)
|
|
26
26
|
source = Formatter::Numbers::Source.new(number_string)
|
|
27
|
-
options = format_options(source, locale, precision,
|
|
27
|
+
options = format_options(source, locale, precision,
|
|
28
|
+
validated_format(format))
|
|
28
29
|
|
|
29
30
|
if options.notation_supported?
|
|
30
31
|
return notation_renderer(options).render(source, options.notation)
|
|
@@ -39,6 +40,28 @@ module Plurimath
|
|
|
39
40
|
|
|
40
41
|
private
|
|
41
42
|
|
|
43
|
+
# localize_number/localizer_symbols are not per-option-hardened — they feed
|
|
44
|
+
# SymbolResolver directly — so validate their types at the point of use (see
|
|
45
|
+
# #symbol_resolver). That single check covers the constructor, the public
|
|
46
|
+
# writers, and any later mutation, raising a clean ConfigurationError
|
|
47
|
+
# instead of leaking a TypeError out of SymbolResolver.
|
|
48
|
+
def validated_localize_number(value)
|
|
49
|
+
return value if value.nil? || value.is_a?(String)
|
|
50
|
+
|
|
51
|
+
raise Plurimath::ConfigurationError.new(
|
|
52
|
+
:invalid_formatter_option, option: :localize_number, value: value, supported: "a String"
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validated_localizer_symbols(value)
|
|
57
|
+
return {} if value.nil?
|
|
58
|
+
return value if value.is_a?(Hash)
|
|
59
|
+
|
|
60
|
+
raise Plurimath::ConfigurationError.new(
|
|
61
|
+
:invalid_formatter_option, option: :localizer_symbols, value: value, supported: "a Hash"
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
42
65
|
def format_options(source, locale, precision, format)
|
|
43
66
|
Formatter::Numbers::FormatOptions.new(
|
|
44
67
|
source,
|
|
@@ -66,8 +89,8 @@ module Plurimath
|
|
|
66
89
|
def symbol_resolver(locale)
|
|
67
90
|
Formatter::Numbers::SymbolResolver.new(
|
|
68
91
|
locale,
|
|
69
|
-
localizer_symbols: localizer_symbols,
|
|
70
|
-
localize_number: localize_number,
|
|
92
|
+
localizer_symbols: validated_localizer_symbols(localizer_symbols),
|
|
93
|
+
localize_number: validated_localize_number(localize_number),
|
|
71
94
|
)
|
|
72
95
|
end
|
|
73
96
|
|
|
@@ -76,7 +99,25 @@ module Plurimath
|
|
|
76
99
|
end
|
|
77
100
|
|
|
78
101
|
def supported_locale(locale)
|
|
102
|
+
# Locale always falls back to :en for any unsupported value, including
|
|
103
|
+
# nil and non-string/symbol types; it never raises.
|
|
104
|
+
return :en unless locale.is_a?(String) || locale.is_a?(Symbol)
|
|
105
|
+
|
|
79
106
|
Formatter::SupportedLocales::LOCALES.key?(locale.to_sym) ? locale.to_sym : :en
|
|
80
107
|
end
|
|
108
|
+
|
|
109
|
+
def validated_format(format)
|
|
110
|
+
return {} if format.nil?
|
|
111
|
+
unless format.is_a?(Hash)
|
|
112
|
+
raise Plurimath::ConfigurationError.new(
|
|
113
|
+
:invalid_formatter_option,
|
|
114
|
+
option: :format,
|
|
115
|
+
value: format,
|
|
116
|
+
supported: "a Hash of formatter options",
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
format
|
|
121
|
+
end
|
|
81
122
|
end
|
|
82
123
|
end
|
|
@@ -216,7 +216,8 @@ module Plurimath
|
|
|
216
216
|
def accent_value(value)
|
|
217
217
|
accent = value.first
|
|
218
218
|
unless accent.is_a?(Math::Function::UnaryFunction)
|
|
219
|
-
return Math::Function::Overset.new(accent, value.last,
|
|
219
|
+
return Math::Function::Overset.new(accent, value.last,
|
|
220
|
+
{ accent: true })
|
|
220
221
|
end
|
|
221
222
|
|
|
222
223
|
accent.attributes = { accent: true }
|
|
@@ -241,7 +241,10 @@ module Plurimath
|
|
|
241
241
|
chr = first_omml_child(pr&.chr)&.val
|
|
242
242
|
value = omml_argument_value(node.e)
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
if group_character_above?(pr)
|
|
245
|
+
return group_character_above_value(chr,
|
|
246
|
+
value)
|
|
247
|
+
end
|
|
245
248
|
|
|
246
249
|
group_character_below_value(chr, value)
|
|
247
250
|
end
|