plurimath 0.10.7 → 0.11.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +3 -2
  3. data/README.adoc +343 -44
  4. data/lib/plurimath/asciimath/parse.rb +6 -2
  5. data/lib/plurimath/configuration.rb +17 -0
  6. data/lib/plurimath/deprecation.rb +81 -0
  7. data/lib/plurimath/errors/configuration_error.rb +27 -0
  8. data/lib/plurimath/errors/deprecation_error.rb +33 -0
  9. data/lib/plurimath/errors/error.rb +6 -0
  10. data/lib/plurimath/errors/formatter/unsupported_base.rb +1 -1
  11. data/lib/plurimath/errors/formatter/unsupported_locale.rb +18 -0
  12. data/lib/plurimath/errors/omml/unsupported_node_error.rb +1 -1
  13. data/lib/plurimath/errors/parse_error.rb +1 -1
  14. data/lib/plurimath/errors/parse_option_error.rb +34 -0
  15. data/lib/plurimath/formatter/numbers/base.rb +18 -8
  16. data/lib/plurimath/formatter/numbers/base_notation.rb +67 -0
  17. data/lib/plurimath/formatter/numbers/digit_sequence.rb +96 -0
  18. data/lib/plurimath/formatter/numbers/format_options.rb +141 -0
  19. data/lib/plurimath/formatter/numbers/fraction.rb +50 -93
  20. data/lib/plurimath/formatter/numbers/integer.rb +30 -6
  21. data/lib/plurimath/formatter/numbers/notation_renderer.rb +128 -0
  22. data/lib/plurimath/formatter/numbers/number_renderer.rb +66 -0
  23. data/lib/plurimath/formatter/numbers/parts.rb +69 -0
  24. data/lib/plurimath/formatter/numbers/parts_renderer.rb +30 -0
  25. data/lib/plurimath/formatter/numbers/precision_resolver.rb +54 -0
  26. data/lib/plurimath/formatter/numbers/sign_renderer.rb +28 -0
  27. data/lib/plurimath/formatter/numbers/significant.rb +77 -103
  28. data/lib/plurimath/formatter/numbers/source.rb +120 -0
  29. data/lib/plurimath/formatter/numbers/symbol_resolver.rb +55 -0
  30. data/lib/plurimath/formatter/numbers.rb +11 -0
  31. data/lib/plurimath/formatter/standard.rb +32 -42
  32. data/lib/plurimath/formatter/supported_locales.rb +27 -0
  33. data/lib/plurimath/formatter.rb +1 -2
  34. data/lib/plurimath/html/constants.rb +2 -0
  35. data/lib/plurimath/html/parse.rb +77 -14
  36. data/lib/plurimath/html/parser.rb +15 -3
  37. data/lib/plurimath/html/transform.rb +193 -91
  38. data/lib/plurimath/html/transform_utility.rb +61 -0
  39. data/lib/plurimath/html.rb +1 -0
  40. data/lib/plurimath/latex/parse.rb +7 -1
  41. data/lib/plurimath/latex/transform.rb +5 -5
  42. data/lib/plurimath/math/function/lim.rb +6 -0
  43. data/lib/plurimath/math/number.rb +8 -2
  44. data/lib/plurimath/math/symbols/cdot.rb +1 -1
  45. data/lib/plurimath/math/symbols/exclam.rb +1 -1
  46. data/lib/plurimath/math/symbols/minus.rb +1 -1
  47. data/lib/plurimath/math/symbols/percent.rb +1 -1
  48. data/lib/plurimath/math/symbols/pi.rb +1 -1
  49. data/lib/plurimath/math/symbols/slash.rb +1 -1
  50. data/lib/plurimath/math.rb +56 -8
  51. data/lib/plurimath/number_formatter.rb +57 -27
  52. data/lib/plurimath/unicode_math/parse.rb +7 -1
  53. data/lib/plurimath/unicode_math/transform.rb +2 -2
  54. data/lib/plurimath/version.rb +1 -1
  55. data/lib/plurimath.rb +23 -1
  56. metadata +21 -4
  57. data/lib/plurimath/formatter/number_formatter.rb +0 -115
  58. data/lib/plurimath/formatter/numeric_formatter.rb +0 -187
@@ -6,11 +6,12 @@ module Plurimath
6
6
  rule(:space) { match["\s"].repeat(1) }
7
7
  rule(:unary) { array_to_expression(Constants::UNARY_CLASSES, :unary) }
8
8
  rule(:binary) { str("lim").as(:binary) }
9
+ rule(:linebreak) { parse_void_tag("br").as(:linebreak) }
9
10
  rule(:sub_tag) { parse_sub_sup_tags("sub") }
10
11
  rule(:sup_tag) { parse_sub_sup_tags("sup") }
11
12
 
12
13
  rule(:mod) do
13
- (parse_tag(:open) >> str("mod").as(:binary) >> parse_tag(:close)) |
14
+ wrapped_tag(str("mod").as(:binary)) |
14
15
  str("mod").as(:binary)
15
16
  end
16
17
 
@@ -27,12 +28,12 @@ module Plurimath
27
28
  end
28
29
 
29
30
  rule(:open_paren) do
30
- (parse_tag(:open) >> lparen >> parse_tag(:close)) |
31
+ wrapped_tag(lparen) |
31
32
  lparen
32
33
  end
33
34
 
34
35
  rule(:close_paren) do
35
- (parse_tag(:open) >> rparen >> parse_tag(:close)) |
36
+ wrapped_tag(rparen) |
36
37
  rparen
37
38
  end
38
39
 
@@ -57,12 +58,12 @@ module Plurimath
57
58
  end
58
59
 
59
60
  rule(:unary_functions) do
60
- (parse_tag(:open) >> unary >> parse_tag(:close)) |
61
+ wrapped_tag(unary) |
61
62
  unary
62
63
  end
63
64
 
64
65
  rule(:binary_functions) do
65
- (parse_tag(:open) >> binary >> parse_tag(:close)) |
66
+ wrapped_tag(binary) |
66
67
  binary
67
68
  end
68
69
 
@@ -73,11 +74,11 @@ module Plurimath
73
74
 
74
75
  rule(:symbol_text_or_tag) do
75
76
  tag_parse |
76
- (str("&") >> match["a-zA-Z0-9"].repeat(2) >> str(";")).as(:symbol) |
77
- (match["0-9"].repeat(1) >> str(".") >> match["0-9"].repeat(1)).as(:number) |
77
+ html_entity.as(:symbol) |
78
+ (match["0-9"].repeat(1) >> decimal_marker >> match["0-9"].repeat(1)).as(:number) |
78
79
  match["0-9"].repeat(1).as(:number) |
79
80
  match["a-zA-Z"].as(:text) |
80
- match["^0-9a-zA-Z<>/(){}\\[\\]\s"].as(:symbol)
81
+ match["^0-9a-zA-Z<>(){}\\[\\]\s"].as(:symbol)
81
82
  end
82
83
 
83
84
  rule(:intermediate_exp) do
@@ -85,6 +86,7 @@ module Plurimath
85
86
  (symbol_text_or_tag.as(:sub_sup) >> sub_sup_tags) |
86
87
  sub_sup |
87
88
  parse_classes |
89
+ linebreak |
88
90
  symbol_text_or_tag |
89
91
  space
90
92
  end
@@ -109,8 +111,9 @@ module Plurimath
109
111
  rule(:tag_parse) do
110
112
  parse_sub_sup_tags("table") |
111
113
  parse_sub_sup_tags("tr") |
112
- parse_sub_sup_tags("td") |
113
- (parse_tag(:open) >> sequence.as(:sequence) >> parse_tag(:close))
114
+ # Formula has no header-cell node; HTML <th> is parsed as Td.
115
+ parse_sub_sup_tags(%w[td th], "td") |
116
+ wrapped_tag(sequence.as(:sequence))
114
117
  end
115
118
 
116
119
  rule(:expression) do
@@ -136,15 +139,75 @@ module Plurimath
136
139
  str(string).as(name)
137
140
  end
138
141
 
139
- def parse_tag(opts)
142
+ def decimal_marker
143
+ str(Plurimath.configuration.decimal)
144
+ end
145
+
146
+ def parse_tag(opts, tag_name = nil, capture_name: nil)
140
147
  tag = str("<")
141
148
  tag = tag >> str("/") if opts == :close
142
- tag = tag >> match(/\w+/).repeat
149
+ name_expression = html_tag_name(tag_name)
150
+ name_expression = name_expression.capture(capture_name) if capture_name
151
+ tag = tag >> name_expression
152
+ tag = tag >> tag_attributes if opts == :open
143
153
  tag >> str(">")
144
154
  end
145
155
 
146
- def parse_sub_sup_tags(tag)
147
- str("<#{tag}>") >> sequence.as(:"#{tag}_value") >> str("</#{tag}>")
156
+ def parse_void_tag(tag_name)
157
+ str("<") >> html_tag_name(tag_name) >> tag_attributes >> str(">")
158
+ end
159
+
160
+ def html_entity
161
+ (str("&#x") >> match["0-9a-fA-F"].repeat(1) >> str(";")) |
162
+ (str("&#") >> match["0-9"].repeat(1) >> str(";")) |
163
+ (str("&") >> match["a-zA-Z"] >> match["a-zA-Z0-9"].repeat >> str(";"))
164
+ end
165
+
166
+ def parse_sub_sup_tags(tag_names, transform_name = tag_names)
167
+ Array(tag_names).map do |tag_name|
168
+ parse_tag(:open, tag_name) >>
169
+ sequence.as(:"#{transform_name}_value") >>
170
+ parse_tag(:close, tag_name)
171
+ end.reduce(:|)
172
+ end
173
+
174
+ def wrapped_tag(expression)
175
+ scope do
176
+ parse_tag(:open, capture_name: :html_tag_name) >>
177
+ expression >>
178
+ matching_close_tag
179
+ end
180
+ end
181
+
182
+ def matching_close_tag
183
+ dynamic do |_source, context|
184
+ parse_tag(:close, context.captures[:html_tag_name].to_s)
185
+ end
186
+ end
187
+
188
+ def html_tag_name(tag_name)
189
+ return match["a-zA-Z"] >> match["a-zA-Z0-9:._-"].repeat unless tag_name
190
+
191
+ case_insensitive_string(tag_name) >> tag_name_boundary
192
+ end
193
+
194
+ def tag_name_boundary
195
+ match["\\s/>"].present?
196
+ end
197
+
198
+ def tag_attributes
199
+ (quoted_attribute_value | match["^<>"]).repeat
200
+ end
201
+
202
+ def quoted_attribute_value
203
+ (str('"') >> match['^"'].repeat >> str('"')) |
204
+ (str("'") >> match["^'"].repeat >> str("'"))
205
+ end
206
+
207
+ def case_insensitive_string(value)
208
+ value.chars
209
+ .map { |char| char.match?(/[A-Za-z]/) ? match["#{char.downcase}#{char.upcase}"] : str(char) }
210
+ .reduce(:>>)
148
211
  end
149
212
  end
150
213
  end
@@ -1,24 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
- require "cgi"
4
+
5
5
  module Plurimath
6
6
  class Html
7
7
  class Parser
8
+ HTML_ENTITY = /&(?:#x[0-9a-f]+|#\d+|[a-z][a-z0-9]+);/i
9
+
8
10
  attr_accessor :text
9
11
 
10
12
  def initialize(text)
11
- @text = ::CGI.unescapeHTML(text)
13
+ @text = text.to_s
12
14
  end
13
15
 
14
16
  def parse
15
- nodes = Parse.new.parse(text)
17
+ nodes = Parse.new.parse(normalized_text)
16
18
  nodes = JSON.parse(nodes.to_json, symbolize_names: true)
17
19
  transformed_tree = Transform.new.apply(nodes)
18
20
  return transformed_tree if transformed_tree.is_a?(Math::Formula)
19
21
 
20
22
  Math::Formula.new(transformed_tree)
21
23
  end
24
+
25
+ private
26
+
27
+ def normalized_text
28
+ text.gsub(HTML_ENTITY) do |entity|
29
+ decoded_entity = Utility.html_entity_to_unicode(entity)
30
+
31
+ Utility.string_to_html_entity(decoded_entity)
32
+ end
33
+ end
22
34
  end
23
35
  end
24
36
  end
@@ -5,15 +5,28 @@ module Plurimath
5
5
  class Transform < Parslet::Transform
6
6
  rule(text: simple(:text)) { Math::Function::Text.new(text) }
7
7
  rule(unary: simple(:unary)) { Utility.get_class(unary).new }
8
- rule(symbol: simple(:symbol)) do
9
- Utility.symbols_class(symbol, lang: :html)
10
- end
11
- rule(number: simple(:number)) { Math::Number.new(number) }
8
+ rule(symbol: simple(:symbol)) { TransformUtility.symbol(symbol) }
9
+ rule(number: simple(:number)) { Math::Number.new(number) }
10
+ rule(linebreak: simple(:_linebreak)) { Math::Function::Linebreak.new }
12
11
  rule(expression: simple(:exp)) { exp }
13
12
 
13
+ rule(linebreak: simple(:_linebreak),
14
+ expression: simple(:expr)) do
15
+ [
16
+ Math::Function::Linebreak.new,
17
+ expr,
18
+ ]
19
+ end
20
+
21
+ rule(linebreak: simple(:_linebreak),
22
+ expression: sequence(:expr)) do
23
+ [Math::Function::Linebreak.new] + expr
24
+ end
25
+
14
26
  rule(expression: sequence(:exp)) { exp }
15
27
  rule(sequence: simple(:sequence)) { sequence }
16
28
  rule(tr_value: simple(:tr_value)) { Math::Function::Tr.new([tr_value]) }
29
+ rule(tr_value: sequence(:tr_value)) { Math::Function::Tr.new(tr_value) }
17
30
  rule(td_value: simple(:td_value)) { Math::Function::Td.new([td_value]) }
18
31
  rule(sequence: sequence(:sequence)) { sequence }
19
32
  rule(td_value: sequence(:td_value)) { Math::Function::Td.new(td_value) }
@@ -71,6 +84,22 @@ module Plurimath
71
84
  )
72
85
  end
73
86
 
87
+ rule(td_value: simple(:td_value),
88
+ expression: simple(:expr)) do
89
+ [
90
+ Math::Function::Td.new([td_value]),
91
+ expr,
92
+ ]
93
+ end
94
+
95
+ rule(td_value: simple(:td_value),
96
+ expression: sequence(:expr)) do
97
+ expr.insert(
98
+ 0,
99
+ Math::Function::Td.new([td_value]),
100
+ )
101
+ end
102
+
74
103
  rule(unary_function: simple(:unary_function),
75
104
  sequence: simple(:sequence)) do
76
105
  Math::Formula.new(
@@ -104,14 +133,14 @@ module Plurimath
104
133
  rule(symbol: simple(:symbol),
105
134
  expression: simple(:expr)) do
106
135
  [
107
- Utility.symbols_class(symbol, lang: :html),
136
+ TransformUtility.symbol(symbol),
108
137
  expr,
109
138
  ]
110
139
  end
111
140
 
112
141
  rule(symbol: simple(:symbol),
113
142
  expression: sequence(:expr)) do
114
- [Utility.symbols_class(symbol, lang: :html)] + expr
143
+ [TransformUtility.symbol(symbol)] + expr
115
144
  end
116
145
 
117
146
  rule(number: simple(:number),
@@ -145,135 +174,208 @@ module Plurimath
145
174
  rule(symbol: simple(:symbol),
146
175
  parse_parenthesis: simple(:parse_paren)) do
147
176
  [
148
- Utility.symbols_class(symbol, lang: :html),
177
+ TransformUtility.symbol(symbol),
149
178
  parse_paren,
150
179
  ]
151
180
  end
152
181
 
153
182
  rule(sub_sup: simple(:sub_sup),
154
183
  sub_value: simple(:sub_value)) do
155
- if Utility.sub_sup_method?(sub_sup)
156
- sub_sup.parameter_one = sub_value
157
- sub_sup
158
- else
159
- Math::Function::Base.new(
160
- sub_sup,
161
- sub_value,
162
- )
163
- end
184
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value)
164
185
  end
165
186
 
166
187
  rule(sub_sup: simple(:sub_sup),
167
188
  sub_value: sequence(:sub_value)) do
168
- if Utility.sub_sup_method?(sub_sup)
169
- sub_sup.parameter_one = Utility.filter_values(sub_value)
170
- sub_sup
171
- else
172
- Math::Function::Base.new(
173
- sub_sup,
174
- Utility.filter_values(sub_value),
175
- )
176
- end
189
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value)
177
190
  end
178
191
 
179
192
  rule(sub_sup: simple(:sub_sup),
180
193
  sup_value: simple(:sup_value)) do
181
- if Utility.sub_sup_method?(sub_sup)
182
- sub_sup.parameter_two = sup_value
183
- sub_sup
184
- else
185
- Math::Function::Power.new(
186
- sub_sup,
187
- sup_value,
188
- )
189
- end
194
+ TransformUtility.sub_sup_value(sub_sup, sup_value: sup_value)
190
195
  end
191
196
 
192
197
  rule(sub_sup: simple(:sub_sup),
193
198
  sup_value: sequence(:sup_value)) do
194
- if Utility.sub_sup_method?(sub_sup)
195
- sub_sup.parameter_two = Utility.filter_values(sup_value)
196
- sub_sup
197
- else
198
- Math::Function::Power.new(
199
- sub_sup,
200
- Utility.filter_values(sup_value),
201
- )
202
- end
199
+ TransformUtility.sub_sup_value(sub_sup, sup_value: sup_value)
203
200
  end
204
201
 
205
202
  rule(sub_sup: simple(:sub_sup),
206
203
  sub_value: simple(:sub_value),
207
204
  sup_value: simple(:sup_value)) do
208
- if Utility.sub_sup_method?(sub_sup)
209
- sub_sup.parameter_one = sub_value
210
- sub_sup.parameter_two = sup_value
211
- sub_sup
212
- else
213
- Math::Function::PowerBase.new(
214
- sub_sup,
215
- sub_value,
216
- sup_value,
217
- )
218
- end
205
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value)
219
206
  end
220
207
 
221
208
  rule(sub_sup: simple(:sub_sup),
222
209
  sub_value: simple(:sub_value),
223
210
  sup_value: sequence(:sup_value)) do
224
- if Utility.sub_sup_method?(sub_sup)
225
- sub_sup.parameter_one = sub_value
226
- sub_sup.parameter_two = Utility.filter_values(sup_value)
227
- sub_sup
228
- else
229
- Math::Function::PowerBase.new(
230
- sub_sup,
231
- sub_value,
232
- Utility.filter_values(sup_value),
233
- )
234
- end
211
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value)
235
212
  end
236
213
 
237
214
  rule(sub_sup: simple(:sub_sup),
238
215
  sub_value: sequence(:sub_value),
239
216
  sup_value: simple(:sup_value)) do
240
- if Utility.sub_sup_method?(sub_sup)
241
- sub_sup.parameter_one = Utility.filter_values(sub_value)
242
- sub_sup.parameter_two = sup_value
243
- sub_sup
244
- else
245
- Math::Function::PowerBase.new(
246
- sub_sup,
247
- Utility.filter_values(sub_value),
248
- sup_value,
249
- )
250
- end
217
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value)
251
218
  end
252
219
 
253
220
  rule(sub_sup: simple(:sub_sup),
254
221
  sub_value: sequence(:sub_value),
255
222
  sup_value: sequence(:sup_value)) do
256
- if Utility.sub_sup_method?(sub_sup)
257
- sub_sup.parameter_one = Utility.filter_values(sub_value)
258
- sub_sup.parameter_two = Utility.filter_values(sup_value)
259
- sub_sup
260
- else
261
- Math::Function::PowerBase.new(
262
- sub_sup,
263
- Utility.filter_values(sub_value),
264
- Utility.filter_values(sup_value),
265
- )
266
- end
223
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value)
224
+ end
225
+
226
+ # Parslet emits separate shapes for each sub/sup value combination and
227
+ # trailing expression form. Keep this cluster mechanical: build the
228
+ # sub/sup node, then append the remaining expression in input order.
229
+ rule(sub_sup: simple(:sub_sup),
230
+ sup_value: sequence(:sup_value),
231
+ expression: simple(:expression)) do
232
+ TransformUtility.append_expression(
233
+ TransformUtility.sub_sup_value(sub_sup, sup_value: sup_value),
234
+ expression,
235
+ )
236
+ end
237
+
238
+ rule(sub_sup: simple(:sub_sup),
239
+ sub_value: simple(:sub_value),
240
+ expression: simple(:expression)) do
241
+ TransformUtility.append_expression(
242
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value),
243
+ expression,
244
+ )
245
+ end
246
+
247
+ rule(sub_sup: simple(:sub_sup),
248
+ sub_value: simple(:sub_value),
249
+ expression: sequence(:expression)) do
250
+ TransformUtility.append_expression(
251
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value),
252
+ expression,
253
+ )
254
+ end
255
+
256
+ rule(sub_sup: simple(:sub_sup),
257
+ sub_value: sequence(:sub_value),
258
+ expression: simple(:expression)) do
259
+ TransformUtility.append_expression(
260
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value),
261
+ expression,
262
+ )
263
+ end
264
+
265
+ rule(sub_sup: simple(:sub_sup),
266
+ sub_value: sequence(:sub_value),
267
+ expression: sequence(:expression)) do
268
+ TransformUtility.append_expression(
269
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value),
270
+ expression,
271
+ )
272
+ end
273
+
274
+ rule(sub_sup: simple(:sub_sup),
275
+ sup_value: simple(:sup_value),
276
+ expression: simple(:expression)) do
277
+ TransformUtility.append_expression(
278
+ TransformUtility.sub_sup_value(sub_sup, sup_value: sup_value),
279
+ expression,
280
+ )
281
+ end
282
+
283
+ rule(sub_sup: simple(:sub_sup),
284
+ sup_value: simple(:sup_value),
285
+ expression: sequence(:expression)) do
286
+ TransformUtility.append_expression(
287
+ TransformUtility.sub_sup_value(sub_sup, sup_value: sup_value),
288
+ expression,
289
+ )
267
290
  end
268
291
 
269
292
  rule(sub_sup: simple(:sub_sup),
270
293
  sup_value: sequence(:sup_value),
294
+ expression: sequence(:expression)) do
295
+ TransformUtility.append_expression(
296
+ TransformUtility.sub_sup_value(sub_sup, sup_value: sup_value),
297
+ expression,
298
+ )
299
+ end
300
+
301
+ rule(sub_sup: simple(:sub_sup),
302
+ sub_value: simple(:sub_value),
303
+ sup_value: simple(:sup_value),
271
304
  expression: simple(:expression)) do
272
- power = Math::Function::Power.new(
273
- sub_sup,
274
- Utility.filter_values(sup_value),
305
+ TransformUtility.append_expression(
306
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value),
307
+ expression,
308
+ )
309
+ end
310
+
311
+ rule(sub_sup: simple(:sub_sup),
312
+ sub_value: simple(:sub_value),
313
+ sup_value: simple(:sup_value),
314
+ expression: sequence(:expression)) do
315
+ TransformUtility.append_expression(
316
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value),
317
+ expression,
318
+ )
319
+ end
320
+
321
+ rule(sub_sup: simple(:sub_sup),
322
+ sub_value: simple(:sub_value),
323
+ sup_value: sequence(:sup_value),
324
+ expression: simple(:expression)) do
325
+ TransformUtility.append_expression(
326
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value),
327
+ expression,
328
+ )
329
+ end
330
+
331
+ rule(sub_sup: simple(:sub_sup),
332
+ sub_value: simple(:sub_value),
333
+ sup_value: sequence(:sup_value),
334
+ expression: sequence(:expression)) do
335
+ TransformUtility.append_expression(
336
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value),
337
+ expression,
338
+ )
339
+ end
340
+
341
+ rule(sub_sup: simple(:sub_sup),
342
+ sub_value: sequence(:sub_value),
343
+ sup_value: simple(:sup_value),
344
+ expression: simple(:expression)) do
345
+ TransformUtility.append_expression(
346
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value),
347
+ expression,
348
+ )
349
+ end
350
+
351
+ rule(sub_sup: simple(:sub_sup),
352
+ sub_value: sequence(:sub_value),
353
+ sup_value: simple(:sup_value),
354
+ expression: sequence(:expression)) do
355
+ TransformUtility.append_expression(
356
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value),
357
+ expression,
358
+ )
359
+ end
360
+
361
+ rule(sub_sup: simple(:sub_sup),
362
+ sub_value: sequence(:sub_value),
363
+ sup_value: sequence(:sup_value),
364
+ expression: simple(:expression)) do
365
+ TransformUtility.append_expression(
366
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value),
367
+ expression,
368
+ )
369
+ end
370
+
371
+ rule(sub_sup: simple(:sub_sup),
372
+ sub_value: sequence(:sub_value),
373
+ sup_value: sequence(:sup_value),
374
+ expression: sequence(:expression)) do
375
+ TransformUtility.append_expression(
376
+ TransformUtility.sub_sup_value(sub_sup, sub_value: sub_value, sup_value: sup_value),
377
+ expression,
275
378
  )
276
- [power, expression]
277
379
  end
278
380
 
279
381
  rule(lparen: simple(:lparen),
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plurimath
4
+ class Html
5
+ module TransformUtility
6
+ module_function
7
+
8
+ HTML_ENTITY = /\A&(?:#x[0-9a-f]+|#\d+|[a-z][a-z0-9]+);\z/i
9
+
10
+ def symbol(symbol)
11
+ Utility.symbols_class(normalize_symbol(symbol), lang: :html)
12
+ end
13
+
14
+ def append_expression(value, expression)
15
+ return [value] + expression if expression.is_a?(Array)
16
+
17
+ [value, expression]
18
+ end
19
+
20
+ def sub_sup_value(sub_sup, sub_value: nil, sup_value: nil)
21
+ normalized_sub_value = normalize_sub_sup_value(sub_value)
22
+ normalized_sup_value = normalize_sub_sup_value(sup_value)
23
+
24
+ if Utility.sub_sup_method?(sub_sup)
25
+ sub_sup.parameter_one = normalized_sub_value if normalized_sub_value
26
+ sub_sup.parameter_two = normalized_sup_value if normalized_sup_value
27
+ sub_sup
28
+ elsif normalized_sub_value && normalized_sup_value
29
+ Math::Function::PowerBase.new(
30
+ sub_sup,
31
+ normalized_sub_value,
32
+ normalized_sup_value,
33
+ )
34
+ elsif normalized_sup_value
35
+ Math::Function::Power.new(
36
+ sub_sup,
37
+ normalized_sup_value,
38
+ )
39
+ else
40
+ Math::Function::Base.new(
41
+ sub_sup,
42
+ normalized_sub_value,
43
+ )
44
+ end
45
+ end
46
+
47
+ def normalize_sub_sup_value(value)
48
+ return Utility.filter_values(value) if value.is_a?(Array)
49
+
50
+ value
51
+ end
52
+
53
+ def normalize_symbol(symbol)
54
+ symbol = symbol.to_s
55
+ return symbol if symbol.match?(HTML_ENTITY)
56
+
57
+ Utility.string_to_html_entity(symbol)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -6,6 +6,7 @@ module Plurimath
6
6
  autoload :Parse, "#{__dir__}/html/parse"
7
7
  autoload :Parser, "#{__dir__}/html/parser"
8
8
  autoload :Transform, "#{__dir__}/html/transform"
9
+ autoload :TransformUtility, "#{__dir__}/html/transform_utility"
9
10
 
10
11
  attr_accessor :text
11
12
 
@@ -101,7 +101,7 @@ module Plurimath
101
101
  (rparen.absent? >> symbol_class_commands) |
102
102
  (slash >> math_operators_classes) |
103
103
  match["a-zA-Z"].as(:symbols) |
104
- (match["0-9"].repeat(0) >> str(".").maybe >> match["0-9"].repeat(1)).as(:number) |
104
+ (match["0-9"].repeat(0) >> decimal_marker.maybe >> match["0-9"].repeat(1)).as(:number) |
105
105
  match["0-9"].repeat(1).as(:number) |
106
106
  (str("\\\\").as("\\\\") >> match(/\s/).repeat) |
107
107
  str("\\ ").as(:space) |
@@ -197,6 +197,12 @@ module Plurimath
197
197
  end
198
198
  end
199
199
 
200
+ def decimal_marker
201
+ # Latex::Parser entity-encodes input before Parslet sees it, so
202
+ # non-ASCII locale markers must match that encoded parser input.
203
+ str(Utility.string_to_html_entity(Plurimath.configuration.decimal))
204
+ end
205
+
200
206
  def hash_to_expression(hash)
201
207
  @@expression ||= hash.reduce do |expression, (key, value)|
202
208
  if expression.is_a?(Array)