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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc005169c3f0268023980ba0d02d40a3e169c73ec1c75541f1ea71d8cadf6de6
4
- data.tar.gz: 0d9fb467134da2b44ad20d1952d1795f4fa7e233a657b5589ea3b5cf5e99fb26
3
+ metadata.gz: a2ce7ef29dd4032b83c86b06d92276325630994243ef48e193aa8d3092e2fe22
4
+ data.tar.gz: ebaec8740845b7f1cb76e690f95f349d98d18a0b1342528463d8af3187138d1c
5
5
  SHA512:
6
- metadata.gz: c4d59a8681ab5b3b988d3a46ecfadc50661c43cc2d897816d943b3c621a6f8db202a8fca9b59c72fa732384284071aeec20851051b645cb7c29e4126fa0377d3
7
- data.tar.gz: 54013f676433c00728a7b022b5c16ec3b77d2afd1949573ee56c0d90c936600289647cef2a886978e23da5be20af8d232dabf4e6ff9d3454dca77667f69d0f50
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
- notation_array = BigDecimal(num_str).to_s("e").split("e")
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
- return value unless options[:formatter]&.respond_to?(:localized_number)
96
-
97
- options[:formatter].localized_number(value.to_s)
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
@@ -141,6 +141,7 @@ module Plurimath
141
141
  "&#x23df;": "ubrace",
142
142
  "&#x2192;": "vec",
143
143
  "&#x302;": "hat",
144
+ "&#x305;": "bar",
144
145
  "&#x332;": "ul",
145
146
  "&#xaf;": "bar",
146
147
  "&#x26;": "&",
@@ -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
- attributes: node.attributes,
56
- value: parse_nodes(node.nodes),
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
- open_paren = Utility.string_to_html_entity(flatten_dpr.find { |hash| hash[:begChr] }&.values&.first)
113
- close_paren = Utility.string_to_html_entity(flatten_dpr.find { |hash| hash[:endChr] }&.values&.first)
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
- acc_value.first.attributes = { accent: true }
169
- acc_value.first
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plurimath
4
- VERSION = "0.10.1"
4
+ VERSION = "0.10.2"
5
5
  end
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.1
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-23 00:00:00.000000000 Z
11
+ date: 2026-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ox