plurimath 0.10.1 → 0.10.2
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/README.adoc +57 -1
- data/lib/plurimath/cli.rb +28 -1
- data/lib/plurimath/formatter/numeric_formatter.rb +4 -1
- data/lib/plurimath/math/formula.rb +7 -0
- data/lib/plurimath/math/function/text.rb +1 -1
- data/lib/plurimath/math/number.rb +8 -3
- data/lib/plurimath/mathml/constants.rb +1 -0
- data/lib/plurimath/omml/parser.rb +10 -6
- data/lib/plurimath/omml/transform.rb +15 -4
- data/lib/plurimath/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a2ce7ef29dd4032b83c86b06d92276325630994243ef48e193aa8d3092e2fe22
|
|
4
|
+
data.tar.gz: ebaec8740845b7f1cb76e690f95f349d98d18a0b1342528463d8af3187138d1c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a80a5ad2febd36bad53adc58c6a3d6406ee04bd78c05c2e3ea7eeecf0f924818798de20c745995ee38b161ffc3758389bb80d6ad6487de16183abdc12e88f166
|
|
7
|
+
data.tar.gz: 2f1258c3745a73a0c1d4336f489d1908cfc4691a19b093dc2d0a4bcd19612ad4d449a6f452ff963a27ca01aba6eada160be778e22f7705129fc44f43ad092985
|
data/README.adoc
CHANGED
|
@@ -1135,7 +1135,9 @@ method using a `formatter` option, which serializes the formula into an math
|
|
|
1135
1135
|
representation language.
|
|
1136
1136
|
|
|
1137
1137
|
The formatter should be an instance of `Plurimath::NumberFormatter` or a custom
|
|
1138
|
-
formatter derived from `Plurimath::Formatter::Standard`.
|
|
1138
|
+
formatter derived from `Plurimath::Formatter::Standard`. Custom formatters can
|
|
1139
|
+
define a `format(formula, number)` method to make context-aware formatting
|
|
1140
|
+
decisions based on the surrounding formula (see <<Contextual number formatting>>).
|
|
1139
1141
|
|
|
1140
1142
|
The quick example below demonstrates how to format a number in a formula.
|
|
1141
1143
|
|
|
@@ -1346,6 +1348,60 @@ end
|
|
|
1346
1348
|
====
|
|
1347
1349
|
|
|
1348
1350
|
|
|
1351
|
+
==== Contextual number formatting
|
|
1352
|
+
|
|
1353
|
+
In some cases, certain numbers within a formula should be formatted differently
|
|
1354
|
+
depending on their context. For example, a year like "2024" should not have digit
|
|
1355
|
+
grouping applied, while other numbers in the same formula should.
|
|
1356
|
+
|
|
1357
|
+
The `format` method on the formatter receives both the root
|
|
1358
|
+
`Plurimath::Math::Formula` tree and the current `Plurimath::Math::Number` node
|
|
1359
|
+
being processed, allowing context-aware formatting decisions.
|
|
1360
|
+
|
|
1361
|
+
.Signature of the `format` method
|
|
1362
|
+
[source,ruby]
|
|
1363
|
+
----
|
|
1364
|
+
def format(formula, number)
|
|
1365
|
+
# formula: the root Plurimath::Math::Formula containing the full equation
|
|
1366
|
+
# number: the Plurimath::Math::Number node currently being formatted
|
|
1367
|
+
# returns: a formatted string
|
|
1368
|
+
end
|
|
1369
|
+
----
|
|
1370
|
+
|
|
1371
|
+
Formatters that do not define `format` will fall back to `localized_number`, so
|
|
1372
|
+
existing formatters continue to work without changes.
|
|
1373
|
+
|
|
1374
|
+
To implement contextual formatting, define a `format` method in a custom
|
|
1375
|
+
formatter subclass.
|
|
1376
|
+
|
|
1377
|
+
.Creating a year-aware formatter that skips digit grouping for year-like numbers
|
|
1378
|
+
[example]
|
|
1379
|
+
====
|
|
1380
|
+
[source,ruby]
|
|
1381
|
+
----
|
|
1382
|
+
class YearFormatter < Plurimath::Formatter::Standard
|
|
1383
|
+
def format(formula, number) <1>
|
|
1384
|
+
int_value = Integer(number.value, exception: false)
|
|
1385
|
+
if int_value && int_value > 1800 && int_value < 2200 <2>
|
|
1386
|
+
number.value.to_s
|
|
1387
|
+
else
|
|
1388
|
+
localized_number(number.value.to_s) <3>
|
|
1389
|
+
end
|
|
1390
|
+
end
|
|
1391
|
+
end
|
|
1392
|
+
|
|
1393
|
+
formatter = YearFormatter.new
|
|
1394
|
+
formula = Plurimath::Math.parse("2024 + 1000000", :asciimath)
|
|
1395
|
+
formula.to_latex(formatter: formatter)
|
|
1396
|
+
# => "2024 + 1,000,000" <4>
|
|
1397
|
+
----
|
|
1398
|
+
<1> Define `format` to receive the formula tree and number node.
|
|
1399
|
+
<2> Detect year-like numbers and return them unformatted.
|
|
1400
|
+
<3> Call `localized_number` to apply locale-based formatting for other numbers.
|
|
1401
|
+
<4> "2024" is left as-is, while "1000000" is formatted with digit grouping.
|
|
1402
|
+
====
|
|
1403
|
+
|
|
1404
|
+
|
|
1349
1405
|
[[standard_configuration]]
|
|
1350
1406
|
=== Default number formatting configuration
|
|
1351
1407
|
|
data/lib/plurimath/cli.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
+
require_relative "../plurimath"
|
|
4
5
|
|
|
5
6
|
module Plurimath
|
|
6
|
-
self.autoload :Math, "plurimath/math"
|
|
7
7
|
|
|
8
8
|
class Cli < Thor
|
|
9
9
|
desc "convert", "Convert between math formats"
|
|
@@ -43,6 +43,11 @@ module Plurimath
|
|
|
43
43
|
desc: "Splits only MathML and OMML equations into multiple equations, Boolean only",
|
|
44
44
|
force: :boolean
|
|
45
45
|
|
|
46
|
+
option :xml_engine,
|
|
47
|
+
aliases: "-e",
|
|
48
|
+
default: "ox",
|
|
49
|
+
desc: "XML engine to use for parsing and rendering (ox or oga)"
|
|
50
|
+
|
|
46
51
|
def convert
|
|
47
52
|
input = options[:input]
|
|
48
53
|
input_string = options[:file_path] ? File.read(options[:file_path]) : input
|
|
@@ -50,6 +55,7 @@ module Plurimath
|
|
|
50
55
|
|
|
51
56
|
input_format = options[:input_format]
|
|
52
57
|
output_format = options[:output_format]
|
|
58
|
+
configure_xml_engine(input_format, output_format)
|
|
53
59
|
formula = Plurimath::Math.parse(input_string, input_format)
|
|
54
60
|
return puts formula.to_display(output_format.to_sym) if YAML.safe_load(options[:math_rendering])
|
|
55
61
|
|
|
@@ -75,6 +81,27 @@ module Plurimath
|
|
|
75
81
|
end
|
|
76
82
|
|
|
77
83
|
no_commands do
|
|
84
|
+
def configure_xml_engine(input_format, output_format)
|
|
85
|
+
xml_formats = %w[mathml omml]
|
|
86
|
+
return unless xml_formats.include?(input_format) || xml_formats.include?(output_format)
|
|
87
|
+
|
|
88
|
+
set_xml_engine(options[:xml_engine])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def set_xml_engine(engine)
|
|
92
|
+
engine_class = case engine
|
|
93
|
+
when "ox"
|
|
94
|
+
require_relative "setup/ox_engine"
|
|
95
|
+
Plurimath::XmlEngine::OxEngine
|
|
96
|
+
when "oga"
|
|
97
|
+
require_relative "setup/oga"
|
|
98
|
+
Plurimath::XmlEngine::Oga
|
|
99
|
+
else
|
|
100
|
+
warn_and_exit("Invalid XML engine: #{engine}. Use 'ox' or 'oga'.")
|
|
101
|
+
end
|
|
102
|
+
Plurimath.xml_engine = engine_class
|
|
103
|
+
end
|
|
104
|
+
|
|
78
105
|
def warn_and_exit(message)
|
|
79
106
|
warn(message)
|
|
80
107
|
abort
|
|
@@ -78,7 +78,10 @@ module Plurimath
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def notation_chars(num_str)
|
|
81
|
-
|
|
81
|
+
bd = BigDecimal(num_str)
|
|
82
|
+
return [num_str, 0] if bd.zero?
|
|
83
|
+
|
|
84
|
+
notation_array = bd.to_s("e").split("e")
|
|
82
85
|
notation_array[1] = update_exponent_value(notation_array[1])
|
|
83
86
|
number_str = notation_array[0]
|
|
84
87
|
number_str = number_str.gsub(/0\.(\d)/, '\1.')
|
|
@@ -55,6 +55,7 @@ module Plurimath
|
|
|
55
55
|
|
|
56
56
|
def to_asciimath(formatter: nil, unitsml: {}, options: nil)
|
|
57
57
|
options ||= { formatter: formatter, unitsml: unitsml }.compact
|
|
58
|
+
options[:formula] ||= self
|
|
58
59
|
wrap_render_error(:asciimath) do
|
|
59
60
|
value.map do |val|
|
|
60
61
|
val.to_asciimath(options: asciimath_table_options(options, val))
|
|
@@ -75,6 +76,7 @@ module Plurimath
|
|
|
75
76
|
unitsml: unitsml,
|
|
76
77
|
unary_function_spacing: unary_function_spacing
|
|
77
78
|
}.compact
|
|
79
|
+
options[:formula] ||= self
|
|
78
80
|
return line_breaked_mathml(display_style, intent, options: options) if split_on_linebreak
|
|
79
81
|
|
|
80
82
|
wrap_render_error(:mathml) do
|
|
@@ -120,6 +122,7 @@ module Plurimath
|
|
|
120
122
|
|
|
121
123
|
def to_latex(formatter: nil, unitsml: {}, options: nil)
|
|
122
124
|
options ||= { formatter: formatter, unitsml: unitsml }.compact
|
|
125
|
+
options[:formula] ||= self
|
|
123
126
|
wrap_render_error(:latex) do
|
|
124
127
|
value.map { |val| val.to_latex(options: options) }.join(" ")
|
|
125
128
|
end
|
|
@@ -127,6 +130,7 @@ module Plurimath
|
|
|
127
130
|
|
|
128
131
|
def to_html(formatter: nil, unitsml: {}, options: nil)
|
|
129
132
|
options ||= { formatter: formatter, unitsml: unitsml }.compact
|
|
133
|
+
options[:formula] ||= self
|
|
130
134
|
wrap_render_error(:html) do
|
|
131
135
|
value&.map { |val| val.to_html(options: options) }&.join(" ")
|
|
132
136
|
end
|
|
@@ -136,6 +140,7 @@ module Plurimath
|
|
|
136
140
|
wrap_render_error(:omml) do
|
|
137
141
|
objects = split_on_linebreak ? new_line_support : [self]
|
|
138
142
|
options = { formatter: formatter, unitsml: unitsml }.compact
|
|
143
|
+
options[:formula] ||= self
|
|
139
144
|
para_element = Utility.ox_element("oMathPara", attributes: OMML_NAMESPACES, namespace: "m")
|
|
140
145
|
objects.each.with_index(1) do |object, index|
|
|
141
146
|
para_element << Utility.update_nodes(
|
|
@@ -160,6 +165,7 @@ module Plurimath
|
|
|
160
165
|
|
|
161
166
|
def to_unicodemath(formatter: nil, unitsml: {}, options: nil)
|
|
162
167
|
options ||= { formatter: formatter, unitsml: unitsml }.compact
|
|
168
|
+
options[:formula] ||= self
|
|
163
169
|
wrap_render_error(:unicodemath) do
|
|
164
170
|
Utility.html_entity_to_unicode(unicodemath_value(options: options)).gsub(/\s\/\s/, "/")
|
|
165
171
|
end
|
|
@@ -171,6 +177,7 @@ module Plurimath
|
|
|
171
177
|
unitsml: unitsml,
|
|
172
178
|
unary_function_spacing: unary_function_spacing
|
|
173
179
|
}
|
|
180
|
+
options[:formula] ||= self
|
|
174
181
|
return type_error!(type) unless MATH_ZONE_TYPES.include?(type.downcase.to_sym)
|
|
175
182
|
|
|
176
183
|
math_zone = case type
|
|
@@ -97,7 +97,7 @@ module Plurimath
|
|
|
97
97
|
text = text.join if text.is_a?(Array)
|
|
98
98
|
entities = HTMLEntities.new
|
|
99
99
|
symbols = Mathml::Constants::UNICODE_SYMBOLS.transform_keys(&:to_s)
|
|
100
|
-
text = entities.encode(text, :hexadecimal)
|
|
100
|
+
text = entities.encode(entities.decode(text), :hexadecimal)
|
|
101
101
|
symbols.each do |code, string|
|
|
102
102
|
text = text.gsub(code.downcase, "unicode[:#{string}]")
|
|
103
103
|
end
|
|
@@ -92,9 +92,14 @@ module Plurimath
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
def format_value_with_options(options)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
formatter = options[:formatter]
|
|
96
|
+
if formatter&.respond_to?(:format)
|
|
97
|
+
formatter.format(options[:formula], self)
|
|
98
|
+
elsif formatter&.respond_to?(:localized_number)
|
|
99
|
+
formatter.localized_number(value.to_s)
|
|
100
|
+
else
|
|
101
|
+
value
|
|
102
|
+
end
|
|
98
103
|
end
|
|
99
104
|
end
|
|
100
105
|
end
|
|
@@ -50,12 +50,16 @@ module Plurimath
|
|
|
50
50
|
if node.is_a?(String)
|
|
51
51
|
node == "" ? nil : node
|
|
52
52
|
elsif !node.attributes.empty?
|
|
53
|
-
|
|
54
|
-
node.name =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
if node.attributes.key?("val")
|
|
54
|
+
{ node.name => node.attributes["val"] }
|
|
55
|
+
else
|
|
56
|
+
{
|
|
57
|
+
node.name => {
|
|
58
|
+
attributes: node.attributes,
|
|
59
|
+
value: parse_nodes(node.nodes),
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
end
|
|
59
63
|
else
|
|
60
64
|
customize_tags(node) if CUSTOMIZABLE_TAGS.include?(node.name)
|
|
61
65
|
{ node.name => parse_nodes(node.nodes) }
|
|
@@ -109,8 +109,10 @@ module Plurimath
|
|
|
109
109
|
|
|
110
110
|
rule(dPr: subtree(:dpr)) do
|
|
111
111
|
flatten_dpr = dpr.flatten.compact
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
beg_entry = flatten_dpr.find { |hash| hash[:begChr] }
|
|
113
|
+
end_entry = flatten_dpr.find { |hash| hash[:endChr] }
|
|
114
|
+
open_paren = Utility.string_to_html_entity(beg_entry ? beg_entry.values.first : "(")
|
|
115
|
+
close_paren = Utility.string_to_html_entity(end_entry ? end_entry.values.first : ")")
|
|
114
116
|
sep_chr = flatten_dpr.find { |hash| hash[:sepChr] }
|
|
115
117
|
open_paren_object = Utility.symbol_object(open_paren, lang: :omml) if open_paren && !open_paren.empty?
|
|
116
118
|
close_paren_object = Utility.symbol_object(close_paren, lang: :omml) if close_paren && !close_paren.empty?
|
|
@@ -165,8 +167,17 @@ module Plurimath
|
|
|
165
167
|
index = acc_value.index { |d| d[:chr] }
|
|
166
168
|
acc_value[index] = chr_value
|
|
167
169
|
Utility.unary_function_classes(acc_value, lang: :omml)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
first = acc_value.first
|
|
171
|
+
if first.is_a?(Math::Function::UnaryFunction)
|
|
172
|
+
first.attributes = { accent: true }
|
|
173
|
+
first
|
|
174
|
+
else
|
|
175
|
+
# chr resolved to a Symbol, wrap in Overset
|
|
176
|
+
Math::Function::Overset.new(
|
|
177
|
+
acc_value.last,
|
|
178
|
+
first,
|
|
179
|
+
)
|
|
180
|
+
end
|
|
170
181
|
end
|
|
171
182
|
|
|
172
183
|
rule(func: subtree(:func)) do
|
data/lib/plurimath/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: plurimath
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.10.
|
|
4
|
+
version: 0.10.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ox
|