plurimath 0.8.17 → 0.8.19
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/lib/plurimath/asciimath/parse.rb +1 -0
- data/lib/plurimath/asciimath/transform.rb +12 -0
- data/lib/plurimath/math/core.rb +63 -4
- data/lib/plurimath/math/formula/mrow.rb +193 -0
- data/lib/plurimath/math/formula/mstyle.rb +17 -0
- data/lib/plurimath/math/formula.rb +311 -32
- data/lib/plurimath/math/function/base.rb +4 -0
- data/lib/plurimath/math/function/color.rb +17 -4
- data/lib/plurimath/math/function/fenced.rb +219 -0
- data/lib/plurimath/math/function/frac.rb +4 -0
- data/lib/plurimath/math/function/linebreak.rb +2 -2
- data/lib/plurimath/math/function/longdiv.rb +3 -0
- data/lib/plurimath/math/function/menclose.rb +3 -0
- data/lib/plurimath/math/function/merror.rb +5 -2
- data/lib/plurimath/math/function/mglyph.rb +27 -0
- data/lib/plurimath/math/function/mlabeledtr.rb +19 -0
- data/lib/plurimath/math/function/mpadded.rb +28 -1
- data/lib/plurimath/math/function/ms.rb +80 -0
- data/lib/plurimath/math/function/msgroup.rb +15 -0
- data/lib/plurimath/math/function/msline.rb +5 -2
- data/lib/plurimath/math/function/multiscript.rb +14 -0
- data/lib/plurimath/math/function/over.rb +3 -0
- data/lib/plurimath/math/function/overset.rb +11 -0
- data/lib/plurimath/math/function/phantom.rb +3 -0
- data/lib/plurimath/math/function/power.rb +3 -0
- data/lib/plurimath/math/function/power_base.rb +3 -0
- data/lib/plurimath/math/function/root.rb +3 -0
- data/lib/plurimath/math/function/scarries.rb +3 -0
- data/lib/plurimath/math/function/semantics.rb +14 -0
- data/lib/plurimath/math/function/sqrt.rb +3 -0
- data/lib/plurimath/math/function/stackrel.rb +3 -0
- data/lib/plurimath/math/function/table.rb +53 -0
- data/lib/plurimath/math/function/td.rb +4 -1
- data/lib/plurimath/math/function/text.rb +22 -2
- data/lib/plurimath/math/function/tr.rb +13 -0
- data/lib/plurimath/math/function/underover.rb +3 -0
- data/lib/plurimath/math/function/underset.rb +44 -0
- data/lib/plurimath/math/number.rb +10 -1
- data/lib/plurimath/math/symbols/gg.rb +4 -4
- data/lib/plurimath/math/symbols/ll.rb +4 -4
- data/lib/plurimath/math/symbols/minus.rb +1 -1
- data/lib/plurimath/math/symbols/symbol.rb +12 -4
- data/lib/plurimath/math.rb +2 -0
- data/lib/plurimath/mathml/parser.rb +45 -86
- data/lib/plurimath/mathml/utility/empty_defined_methods.rb +477 -0
- data/lib/plurimath/mathml/utility/formula_transformation.rb +472 -0
- data/lib/plurimath/mathml/utility.rb +363 -0
- data/lib/plurimath/mathml.rb +1 -0
- data/lib/plurimath/unicode_math/transform.rb +2 -2
- data/lib/plurimath/utility.rb +5 -23
- data/lib/plurimath/version.rb +1 -1
- data/lib/plurimath.rb +9 -0
- data/plurimath.gemspec +4 -2
- metadata +37 -5
- data/lib/plurimath/mathml/transform.rb +0 -413
@@ -0,0 +1,472 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plurimath
|
4
|
+
class Mathml
|
5
|
+
module Utility
|
6
|
+
module FormulaTransformation
|
7
|
+
CONDITIONAL_COMMON_UNARY_FUNCTIONS = %w[
|
8
|
+
scarries
|
9
|
+
phantom
|
10
|
+
mpadded
|
11
|
+
merror
|
12
|
+
].freeze
|
13
|
+
SYMBOL_UPDATABLE_FUNCTIONS = %w[
|
14
|
+
font_style
|
15
|
+
linebreak
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def update_temp(value)
|
21
|
+
@temp_mathml_order = Array(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_temp_order(value, order_name)
|
25
|
+
update_temp(
|
26
|
+
replace_order_with_value(
|
27
|
+
Array(@temp_mathml_order),
|
28
|
+
Array(update_temp_mathml_values(value)),
|
29
|
+
order_name,
|
30
|
+
),
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def filter_values(value, array_to_instance: false, replacing_order: true)
|
35
|
+
return value unless value.is_a?(Array)
|
36
|
+
return array_to_instance ? nil : value if value.empty?
|
37
|
+
|
38
|
+
if is_a?(Math::Formula) && value.length == 1 && value.first.is_mstyle?
|
39
|
+
@displaystyle = value.first.displaystyle
|
40
|
+
end
|
41
|
+
|
42
|
+
if value.length == 1 && value.all? { |val| val.is_a?(Math::Formula) }
|
43
|
+
if array_to_instance
|
44
|
+
filter_values(value.first.value, array_to_instance: true)
|
45
|
+
else
|
46
|
+
value.first.value
|
47
|
+
end
|
48
|
+
elsif value.is_a?(Array) && value.any?(Math::Formula::Mrow)
|
49
|
+
value.each_with_index do |element, index|
|
50
|
+
next unless element.is_a?(Math::Formula::Mrow)
|
51
|
+
|
52
|
+
value[index] = filter_values(
|
53
|
+
Array(element),
|
54
|
+
array_to_instance: true,
|
55
|
+
replacing_order: replacing_order,
|
56
|
+
)
|
57
|
+
end
|
58
|
+
value
|
59
|
+
elsif value_is_ternary_or_nary?(value)
|
60
|
+
value.first.parameter_three = value.delete_at(1)
|
61
|
+
filter_values(
|
62
|
+
Array(value),
|
63
|
+
array_to_instance: array_to_instance,
|
64
|
+
replacing_order: replacing_order,
|
65
|
+
)
|
66
|
+
elsif ternary_naryable?(value)
|
67
|
+
[
|
68
|
+
Math::Function::Nary.new(
|
69
|
+
value.first.parameter_one,
|
70
|
+
value.first.parameter_two,
|
71
|
+
value.first.parameter_three,
|
72
|
+
value.last,
|
73
|
+
),
|
74
|
+
]
|
75
|
+
elsif overset_naryable?(value)
|
76
|
+
[
|
77
|
+
Math::Function::Nary.new(
|
78
|
+
value.first.parameter_two,
|
79
|
+
nil,
|
80
|
+
value.first.parameter_one,
|
81
|
+
value.last,
|
82
|
+
{ type: "undOvr" },
|
83
|
+
),
|
84
|
+
]
|
85
|
+
elsif power_naryable?(value)
|
86
|
+
[
|
87
|
+
Math::Function::Nary.new(
|
88
|
+
value.first.parameter_one,
|
89
|
+
nil,
|
90
|
+
value.first.parameter_two,
|
91
|
+
value.last,
|
92
|
+
{ type: "subSup" },
|
93
|
+
),
|
94
|
+
]
|
95
|
+
elsif array_to_instance && replacing_order
|
96
|
+
value.length > 1 ? Math::Formula.new(value) : value.first
|
97
|
+
else
|
98
|
+
value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_symbols(value)
|
103
|
+
case value
|
104
|
+
when Array
|
105
|
+
array_validations(value)
|
106
|
+
when Math::Symbols::Symbol
|
107
|
+
mathml_symbol_to_class(value.value)
|
108
|
+
when String
|
109
|
+
mathml_symbol_to_class(value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def array_validations(value)
|
114
|
+
value.each_with_index do |val, index|
|
115
|
+
next unless val.is_a?(Math::Symbols::Symbol)
|
116
|
+
|
117
|
+
value[index] = mathml_symbol_to_class(val.value)
|
118
|
+
end
|
119
|
+
value
|
120
|
+
end
|
121
|
+
|
122
|
+
def mathml_symbol_to_class(symbol)
|
123
|
+
Plurimath::Utility.mathml_unary_classes(
|
124
|
+
Array(symbol),
|
125
|
+
lang: :mathml,
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
def replace_order_with_value(order, value, tag_name)
|
130
|
+
value_index = 0
|
131
|
+
value_array = Array(value)
|
132
|
+
|
133
|
+
order.each_with_object([]) do |item, result|
|
134
|
+
next result << item if item != tag_name
|
135
|
+
next if value_index >= value_array.length
|
136
|
+
|
137
|
+
result << value_array[value_index]
|
138
|
+
value_index += 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def update_temp_mathml_values(value)
|
143
|
+
value.each_with_index do |element, index|
|
144
|
+
next unless element.respond_to?(:temp_mathml_order)
|
145
|
+
|
146
|
+
if element.symbol? && element.temp_mathml_order&.none?
|
147
|
+
value[index] = validate_symbols(element)
|
148
|
+
end
|
149
|
+
|
150
|
+
next unless element.temp_mathml_order&.any?
|
151
|
+
|
152
|
+
if element.is_ternary_function?
|
153
|
+
if element.is_a?(Math::Function::Multiscript)
|
154
|
+
multi = multiscript(element.clear_temp_order)
|
155
|
+
element.parameter_one = multi[0]
|
156
|
+
element.parameter_two = filter_values(Array(multi[1])[0])
|
157
|
+
element.parameter_three = filter_values(Array(multi[1])[1])
|
158
|
+
elsif first_element_is_ternary_function?(element)
|
159
|
+
new_element = ternary_element_from(element.temp_mathml_order)
|
160
|
+
new_element.parameter_one = filter_values(
|
161
|
+
Array(element.temp_mathml_order.shift),
|
162
|
+
array_to_instance: true,
|
163
|
+
)
|
164
|
+
new_element.parameter_two = filter_values(
|
165
|
+
Array(element.temp_mathml_order.shift),
|
166
|
+
array_to_instance: true,
|
167
|
+
)
|
168
|
+
value[index] = new_element
|
169
|
+
elsif first_element_is_binary_function?(element)
|
170
|
+
new_element = element.temp_mathml_order.shift
|
171
|
+
new_element.parameter_one = element.temp_mathml_order.shift
|
172
|
+
new_element.parameter_two = element.temp_mathml_order.shift
|
173
|
+
value[index] = new_element
|
174
|
+
elsif element.is_a?(Math::Function::PowerBase)
|
175
|
+
update_power_base_value(element)
|
176
|
+
else
|
177
|
+
element.parameter_one = element.temp_mathml_order.shift
|
178
|
+
element.parameter_two = element.temp_mathml_order.shift
|
179
|
+
element.parameter_three = element.temp_mathml_order.shift
|
180
|
+
end
|
181
|
+
elsif element.is_binary_function?
|
182
|
+
update_binary_function_value(element, value, index)
|
183
|
+
elsif element.is_unary?
|
184
|
+
element.parameter_one = update_unary_function(element)
|
185
|
+
elsif element.symbol? && symbol_updatable?(element)
|
186
|
+
update_symbol(element, value, index)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
value
|
190
|
+
end
|
191
|
+
|
192
|
+
def validated_order(order, rejectable_array: nil)
|
193
|
+
rejectable_array ||= ["comment", "text"]
|
194
|
+
order.reject { |str| rejectable_array.include?(str) }
|
195
|
+
end
|
196
|
+
|
197
|
+
def value_is_ternary_or_nary?(value)
|
198
|
+
return if value.any?(String)
|
199
|
+
|
200
|
+
value.length >= 2 &&
|
201
|
+
value.first.is_ternary_function? &&
|
202
|
+
value.first.parameter_three.nil? &&
|
203
|
+
(!value.first.parameter_one.nil? || !value.first.parameter_two.nil?)
|
204
|
+
end
|
205
|
+
|
206
|
+
def first_element_is_ternary_function?(element)
|
207
|
+
(
|
208
|
+
element.temp_mathml_order.first&.is_ternary_function? &&
|
209
|
+
!(
|
210
|
+
element.temp_mathml_order.first.is_a?(Math::Function::FontStyle) ||
|
211
|
+
element.temp_mathml_order.first.is_a?(Math::Function::Fenced)
|
212
|
+
)
|
213
|
+
) ||
|
214
|
+
ternary_filtered_function?(element)
|
215
|
+
end
|
216
|
+
|
217
|
+
def first_element_is_binary_function?(element)
|
218
|
+
(
|
219
|
+
element.temp_mathml_order.first&.is_binary_function? &&
|
220
|
+
!element.temp_mathml_order.first.is_a?(Math::Function::FontStyle)
|
221
|
+
) ||
|
222
|
+
binary_filtered_function?(element)
|
223
|
+
end
|
224
|
+
|
225
|
+
def binary_filtered_function?(element)
|
226
|
+
new_element = filter_values(
|
227
|
+
[element.temp_mathml_order.first],
|
228
|
+
array_to_instance: true,
|
229
|
+
)
|
230
|
+
new_element&.is_binary_function? && !new_element.all_values_exist?
|
231
|
+
end
|
232
|
+
|
233
|
+
def ternary_filtered_function?(element)
|
234
|
+
new_element = filter_values(
|
235
|
+
[element.temp_mathml_order.first],
|
236
|
+
array_to_instance: true,
|
237
|
+
)
|
238
|
+
new_element&.is_ternary_function? &&
|
239
|
+
!new_element.all_values_exist?
|
240
|
+
end
|
241
|
+
|
242
|
+
def ternary_element_from(order)
|
243
|
+
element = order.shift
|
244
|
+
return element if element.is_ternary_function?
|
245
|
+
|
246
|
+
filter_values([element], array_to_instance: true)
|
247
|
+
end
|
248
|
+
|
249
|
+
def period_to_dot(element, value, index)
|
250
|
+
return element unless element.is_a?(Math::Symbols::Period)
|
251
|
+
|
252
|
+
value[index] = Math::Function::Dot.new
|
253
|
+
end
|
254
|
+
|
255
|
+
def multiscript(value)
|
256
|
+
value.slice_before("mprescripts").map do |val|
|
257
|
+
base_value = val.shift
|
258
|
+
val = Plurimath::Utility.nil_to_none_object(val)
|
259
|
+
part_val = val.partition.with_index { |_, i| i.even? }
|
260
|
+
first_value = part_val[0].empty? ? nil : part_val[0]
|
261
|
+
second_value = part_val[1].empty? ? nil : part_val[1]
|
262
|
+
if base_value.to_s.include?("mprescripts")
|
263
|
+
[first_value, second_value]
|
264
|
+
else
|
265
|
+
Math::Function::PowerBase.new(
|
266
|
+
filter_values(base_value, array_to_instance: true),
|
267
|
+
filter_values(first_value, array_to_instance: true),
|
268
|
+
filter_values(second_value, array_to_instance: true),
|
269
|
+
)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def ternary_naryable?(value)
|
275
|
+
value.length == 2 &&
|
276
|
+
value.first.is_a?(Math::Function::PowerBase) &&
|
277
|
+
value.first.parameter_one.is_nary_symbol?
|
278
|
+
end
|
279
|
+
|
280
|
+
def overset_naryable?(value)
|
281
|
+
value.length == 2 &&
|
282
|
+
value.first.is_a?(Math::Function::Overset) &&
|
283
|
+
value.first.parameter_two.is_nary_symbol?
|
284
|
+
end
|
285
|
+
|
286
|
+
def power_naryable?(value)
|
287
|
+
value.length == 2 &&
|
288
|
+
value.first.is_a?(Math::Function::Power) &&
|
289
|
+
value.first.parameter_one.is_nary_symbol?
|
290
|
+
end
|
291
|
+
|
292
|
+
def update_power_base_value(element)
|
293
|
+
element.parameter_one = filter_values(
|
294
|
+
Array(element.temp_mathml_order.shift),
|
295
|
+
array_to_instance: true,
|
296
|
+
)
|
297
|
+
element.parameter_two = filter_values(
|
298
|
+
Array(element.temp_mathml_order.shift),
|
299
|
+
array_to_instance: true,
|
300
|
+
)
|
301
|
+
return unless element.is_a?(Math::Function::PowerBase)
|
302
|
+
|
303
|
+
element.parameter_three = filter_values(
|
304
|
+
Array(element.temp_mathml_order.shift),
|
305
|
+
array_to_instance: true,
|
306
|
+
)
|
307
|
+
end
|
308
|
+
|
309
|
+
def update_overset_value(element, value, index)
|
310
|
+
case element.temp_mathml_order[1].class_name
|
311
|
+
when "obrace", "ubrace"
|
312
|
+
new_element = element.temp_mathml_order.pop
|
313
|
+
new_element.parameter_one = element.temp_mathml_order.shift
|
314
|
+
when "hat", "ddot"
|
315
|
+
new_element = element.temp_mathml_order.pop
|
316
|
+
new_element.parameter_one = filter_values(
|
317
|
+
Array(element.temp_mathml_order.shift),
|
318
|
+
array_to_instance: true,
|
319
|
+
)
|
320
|
+
return unless element.respond_to?(:options)
|
321
|
+
|
322
|
+
new_element.attributes = element.options
|
323
|
+
when "period", "dot"
|
324
|
+
new_element = period_to_dot(
|
325
|
+
element.temp_mathml_order.pop,
|
326
|
+
value,
|
327
|
+
index,
|
328
|
+
)
|
329
|
+
new_element.parameter_one = filter_values(
|
330
|
+
Array(element.temp_mathml_order.shift),
|
331
|
+
array_to_instance: true,
|
332
|
+
)
|
333
|
+
new_element.attributes = element.options
|
334
|
+
when "bar"
|
335
|
+
new_element = element.temp_mathml_order.pop
|
336
|
+
new_element.parameter_one = element.temp_mathml_order.shift
|
337
|
+
when "vec"
|
338
|
+
new_element = element.temp_mathml_order.pop
|
339
|
+
new_element.parameter_one = filter_values(
|
340
|
+
Array(element.temp_mathml_order.shift),
|
341
|
+
array_to_instance: true,
|
342
|
+
)
|
343
|
+
new_element.attributes = element.options
|
344
|
+
when "ul", "underline"
|
345
|
+
element.temp_mathml_order.pop
|
346
|
+
new_element = Math::Function::Bar.new(
|
347
|
+
filter_values(
|
348
|
+
element.clear_temp_order,
|
349
|
+
array_to_instance: true,
|
350
|
+
),
|
351
|
+
element.options,
|
352
|
+
)
|
353
|
+
when "tilde"
|
354
|
+
new_element = element.temp_mathml_order.pop
|
355
|
+
new_element.parameter_one = filter_values(
|
356
|
+
element.clear_temp_order,
|
357
|
+
array_to_instance: true,
|
358
|
+
)
|
359
|
+
new_element.attributes = element.options
|
360
|
+
else
|
361
|
+
element.parameter_two = element.temp_mathml_order.shift
|
362
|
+
element.parameter_one = element.temp_mathml_order.shift
|
363
|
+
end
|
364
|
+
value[index] = new_element if new_element
|
365
|
+
end
|
366
|
+
|
367
|
+
def update_underset_value(element, value, index)
|
368
|
+
return if update_underset_first_temp_element(element, value, index)
|
369
|
+
|
370
|
+
case element.temp_mathml_order[1]&.class_name
|
371
|
+
when "obrace", "ubrace", "ul", "underline"
|
372
|
+
new_element = element.temp_mathml_order.pop
|
373
|
+
new_element.parameter_one = element.temp_mathml_order.shift
|
374
|
+
when "bar"
|
375
|
+
element.temp_mathml_order.pop
|
376
|
+
new_element = Math::Function::Ul.new(
|
377
|
+
filter_values(
|
378
|
+
element.clear_temp_order,
|
379
|
+
array_to_instance: true,
|
380
|
+
),
|
381
|
+
element.options,
|
382
|
+
)
|
383
|
+
else
|
384
|
+
element.parameter_two = element.temp_mathml_order.shift
|
385
|
+
element.parameter_one = element.temp_mathml_order.shift
|
386
|
+
end
|
387
|
+
value[index] = new_element if new_element
|
388
|
+
end
|
389
|
+
|
390
|
+
def update_underset_first_temp_element(element, value, index)
|
391
|
+
case element.temp_mathml_order[0]
|
392
|
+
when Math::Function::Vec,
|
393
|
+
->(block_element) { updatable_ternary_function?(block_element) }
|
394
|
+
new_element = element.temp_mathml_order.shift
|
395
|
+
new_element.parameter_one = element.temp_mathml_order.shift
|
396
|
+
value[index] = new_element
|
397
|
+
return unless new_element.is_a?(Math::Function::Vec)
|
398
|
+
|
399
|
+
new_element.attributes = element.options
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def updatable_ternary_function?(element)
|
404
|
+
element.is_ternary_function? && !element.any_value_exist?
|
405
|
+
end
|
406
|
+
|
407
|
+
def update_binary_function_value(element, value, index)
|
408
|
+
case element
|
409
|
+
when Math::Function::Semantics, Math::Function::Stackrel
|
410
|
+
element.parameter_one = filter_values(
|
411
|
+
Array(element.clear_temp_order),
|
412
|
+
array_to_instance: true,
|
413
|
+
)
|
414
|
+
when Math::Function::Overset
|
415
|
+
update_overset_value(element, value, index)
|
416
|
+
when Math::Function::Underset
|
417
|
+
update_underset_value(element, value, index)
|
418
|
+
when Math::Function::Power, Math::Function::Base
|
419
|
+
update_power_base_value(element)
|
420
|
+
when Math::Function::Td
|
421
|
+
element.parameter_one = element.clear_temp_order
|
422
|
+
when Math::Function::Root
|
423
|
+
element.parameter_two = element.temp_mathml_order.shift
|
424
|
+
element.parameter_one = element.temp_mathml_order.shift
|
425
|
+
when Math::Function::Menclose
|
426
|
+
element.parameter_two = filter_values(
|
427
|
+
Array(element.clear_temp_order),
|
428
|
+
array_to_instance: true,
|
429
|
+
)
|
430
|
+
else
|
431
|
+
element.parameter_one = element.temp_mathml_order.shift
|
432
|
+
element.parameter_two = element.temp_mathml_order.shift
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def update_unary_function(element)
|
437
|
+
case element.class_name
|
438
|
+
when *CONDITIONAL_COMMON_UNARY_FUNCTIONS
|
439
|
+
filter_values(
|
440
|
+
element.clear_temp_order,
|
441
|
+
array_to_instance: true,
|
442
|
+
)
|
443
|
+
when "sqrt"
|
444
|
+
element.temp_mathml_order.shift
|
445
|
+
else
|
446
|
+
element.clear_temp_order
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def symbol_updatable?(element)
|
451
|
+
temp_element = element.temp_mathml_order.first
|
452
|
+
return false unless temp_element
|
453
|
+
|
454
|
+
temp_element.is_a?(Math::Function::FontStyle) ||
|
455
|
+
temp_element.is_a?(Math::Function::Linebreak)
|
456
|
+
end
|
457
|
+
|
458
|
+
def update_symbol(element, value, index)
|
459
|
+
new_element = element.temp_mathml_order.shift
|
460
|
+
case new_element
|
461
|
+
when Math::Function::FontStyle
|
462
|
+
new_element.parameter_one = validate_symbols(element)
|
463
|
+
value[index] = new_element
|
464
|
+
when Math::Function::Linebreak
|
465
|
+
new_element.parameter_one = validate_symbols(element)
|
466
|
+
value[index] = new_element
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|