plurimath 0.5.0 → 0.6.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.rspec +3 -0
  4. data/.rspec-opal +11 -0
  5. data/Gemfile +3 -0
  6. data/Rakefile +11 -0
  7. data/lib/plurimath/asciimath/transform.rb +15 -0
  8. data/lib/plurimath/latex/constants.rb +3 -0
  9. data/lib/plurimath/latex/parse.rb +20 -11
  10. data/lib/plurimath/latex/transform.rb +24 -2
  11. data/lib/plurimath/math/core.rb +88 -0
  12. data/lib/plurimath/math/formula.rb +68 -24
  13. data/lib/plurimath/math/function/base.rb +8 -2
  14. data/lib/plurimath/math/function/binary_function.rb +36 -4
  15. data/lib/plurimath/math/function/color.rb +14 -0
  16. data/lib/plurimath/math/function/fenced.rb +27 -0
  17. data/lib/plurimath/math/function/floor.rb +1 -1
  18. data/lib/plurimath/math/function/font_style.rb +45 -0
  19. data/lib/plurimath/math/function/frac.rb +6 -0
  20. data/lib/plurimath/math/function/int.rb +7 -0
  21. data/lib/plurimath/math/function/left.rb +19 -1
  22. data/lib/plurimath/math/function/lim.rb +6 -0
  23. data/lib/plurimath/math/function/limits.rb +7 -0
  24. data/lib/plurimath/math/function/log.rb +6 -0
  25. data/lib/plurimath/math/function/menclose.rb +6 -0
  26. data/lib/plurimath/math/function/mod.rb +6 -0
  27. data/lib/plurimath/math/function/msgroup.rb +28 -0
  28. data/lib/plurimath/math/function/multiscript.rb +7 -0
  29. data/lib/plurimath/math/function/nary.rb +94 -0
  30. data/lib/plurimath/math/function/oint.rb +6 -0
  31. data/lib/plurimath/math/function/over.rb +6 -0
  32. data/lib/plurimath/math/function/overset.rb +6 -0
  33. data/lib/plurimath/math/function/power.rb +8 -2
  34. data/lib/plurimath/math/function/power_base.rb +10 -31
  35. data/lib/plurimath/math/function/prod.rb +19 -18
  36. data/lib/plurimath/math/function/right.rb +19 -1
  37. data/lib/plurimath/math/function/root.rb +6 -0
  38. data/lib/plurimath/math/function/rule.rb +7 -0
  39. data/lib/plurimath/math/function/semantics.rb +6 -0
  40. data/lib/plurimath/math/function/stackrel.rb +6 -0
  41. data/lib/plurimath/math/function/substack.rb +6 -0
  42. data/lib/plurimath/math/function/sum.rb +26 -25
  43. data/lib/plurimath/math/function/table.rb +52 -24
  44. data/lib/plurimath/math/function/td.rb +28 -0
  45. data/lib/plurimath/math/function/ternary_function.rb +44 -4
  46. data/lib/plurimath/math/function/text.rb +24 -2
  47. data/lib/plurimath/math/function/tr.rb +28 -0
  48. data/lib/plurimath/math/function/unary_function.rb +43 -3
  49. data/lib/plurimath/math/function/underover.rb +7 -55
  50. data/lib/plurimath/math/function/underset.rb +6 -0
  51. data/lib/plurimath/math/function/vec.rb +40 -0
  52. data/lib/plurimath/math/function.rb +7 -5
  53. data/lib/plurimath/math/number.rb +9 -5
  54. data/lib/plurimath/math/symbol.rb +13 -9
  55. data/lib/plurimath/math.rb +1 -3
  56. data/lib/plurimath/mathml/parser.rb +4 -4
  57. data/lib/plurimath/mathml/transform.rb +3 -4
  58. data/lib/plurimath/omml/parser.rb +3 -3
  59. data/lib/plurimath/omml/transform.rb +12 -11
  60. data/lib/plurimath/setup/oga.rb +5 -0
  61. data/lib/plurimath/setup/opal.rb.erb +8 -0
  62. data/lib/plurimath/setup/ox.rb +5 -0
  63. data/lib/plurimath/utility.rb +48 -13
  64. data/lib/plurimath/version.rb +1 -1
  65. data/lib/plurimath/xml_engine/oga.rb +246 -0
  66. data/lib/plurimath/xml_engine/ox.rb +29 -0
  67. data/lib/plurimath/xml_engine.rb +6 -0
  68. data/lib/plurimath.rb +12 -2
  69. metadata +11 -2
@@ -88,8 +88,7 @@ module Plurimath
88
88
 
89
89
  rule(merror: sequence(:merror)) do
90
90
  Math::Function::Merror.new(
91
- merror[0],
92
- merror[1],
91
+ Utility.filter_values(merror),
93
92
  )
94
93
  end
95
94
 
@@ -247,7 +246,7 @@ module Plurimath
247
246
  symbols = Constants::UNICODE_SYMBOLS.transform_keys(&:to_s)
248
247
  text = entities.encode(mtext.flatten.join, :hexadecimal)
249
248
  symbols.each do |code, string|
250
- text.gsub!(code.downcase, "unicode[:#{string}]")
249
+ text = text.gsub(code.downcase, "unicode[:#{string}]")
251
250
  end
252
251
  Math::Function::Text.new(text)
253
252
  end
@@ -257,7 +256,7 @@ module Plurimath
257
256
  symbols = Constants::UNICODE_SYMBOLS.transform_keys(&:to_s)
258
257
  text = entities.encode(ms.first, :hexadecimal)
259
258
  symbols.each do |code, string|
260
- text.gsub!(code.downcase, "unicode[:#{string}]")
259
+ text = text.gsub(code.downcase, "unicode[:#{string}]")
261
260
  end
262
261
  Math::Function::Text.new(text)
263
262
  end
@@ -33,7 +33,7 @@ module Plurimath
33
33
  end
34
34
 
35
35
  def parse
36
- nodes = Ox.load(text, strip_namespace: true)
36
+ nodes = Plurimath.xml_engine.load(text)
37
37
  @hash = { sequence: parse_nodes(nodes.nodes) }
38
38
  nodes = JSON.parse(@hash.to_json, symbolize_names: true)
39
39
  Math::Formula.new(
@@ -71,14 +71,14 @@ module Plurimath
71
71
  end
72
72
 
73
73
  def organize_table_td(node)
74
- node.locate("e/?").each do |child_node|
74
+ node.locate("e/*").each do |child_node|
75
75
  child_node.name = "mtd" if child_node.name == "r"
76
76
  end
77
77
  end
78
78
 
79
79
  def organize_fonts(node)
80
80
  attrs_arr = { val: [] }
81
- node.locate("rPr/?").each do |child|
81
+ node.locate("rPr/*").each do |child|
82
82
  attrs_arr[:val] << child.attributes["val"]
83
83
  end
84
84
  node.attributes.merge! attrs_arr
@@ -98,11 +98,17 @@ module Plurimath
98
98
  open_paren = fenced.shift if fenced&.first&.class_name == "symbol"
99
99
  close_paren = fenced.shift if fenced&.first&.class_name == "symbol"
100
100
  fenced_value = fenced.compact
101
- Math::Function::Fenced.new(
102
- open_paren,
103
- fenced_value,
104
- close_paren,
105
- )
101
+ if fenced_value.length == 1 && fenced_value.first.is_a?(Math::Function::Table)
102
+ fenced_value.first.open_paren = open_paren&.value
103
+ fenced_value.first.close_paren = close_paren&.value
104
+ fenced_value
105
+ else
106
+ Math::Function::Fenced.new(
107
+ open_paren,
108
+ fenced_value,
109
+ close_paren,
110
+ )
111
+ end
106
112
  end
107
113
 
108
114
  rule(dPr: subtree(:dpr)) do
@@ -176,12 +182,7 @@ module Plurimath
176
182
  ternary_class.parameter_three = Utility.filter_values(nary[3])
177
183
  ternary_class
178
184
  else
179
- Math::Formula.new(
180
- [
181
- Utility.nary_fonts(nary),
182
- Utility.filter_values(nary[3]),
183
- ],
184
- )
185
+ Utility.nary_fonts(nary)
185
186
  end
186
187
  end
187
188
 
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "plurimath/xml_engine/oga"
4
+
5
+ Plurimath.xml_engine = Plurimath::XMLEngine::Oga
@@ -0,0 +1,8 @@
1
+ require 'plurimath/math/core'
2
+ require 'plurimath/math/function'
3
+ <% (
4
+ Dir[File.dirname(__dir__)+"/math/function/*.rb"] +
5
+ Dir[File.dirname(__dir__)+"/math/function/**/*.rb"]
6
+ ).each do |f| %>
7
+ require 'plurimath/<%= f.split("lib/plurimath").last.gsub(/.rb$/,'') %>'
8
+ <% end %>
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "plurimath/xml_engine/ox"
4
+
5
+ Plurimath.xml_engine = Plurimath::XMLEngine::Ox
@@ -91,6 +91,12 @@ module Plurimath
91
91
  "{": "}",
92
92
  "[": "]",
93
93
  }.freeze
94
+ TEXT_CLASSES = %w[
95
+ unicode
96
+ symbol
97
+ number
98
+ text
99
+ ].freeze
94
100
 
95
101
  class << self
96
102
  def organize_table(array, column_align: nil, options: nil)
@@ -187,7 +193,7 @@ module Plurimath
187
193
  def ox_element(node, attributes: [], namespace: "")
188
194
  namespace = "#{namespace}:" unless namespace.empty?
189
195
 
190
- element = Ox::Element.new("#{namespace}#{node}")
196
+ element = Plurimath.xml_engine.new_element("#{namespace}#{node}")
191
197
  attributes&.each do |attr_key, attr_value|
192
198
  element[attr_key] = attr_value
193
199
  end
@@ -249,12 +255,14 @@ module Plurimath
249
255
 
250
256
  def nary_fonts(nary)
251
257
  narypr = nary.first.flatten.compact
252
- subsup = narypr.any?("undOvr") ? "underover" : "power_base"
258
+ subsup = narypr.any?("undOvr") ? "undOvr" : "subSup"
253
259
  unicode = narypr.any?(Hash) ? narypr.first[:chr] : "∫"
254
- get_class(subsup).new(
260
+ Math::Function::Nary.new(
255
261
  Math::Symbol.new(string_to_html_entity(unicode)),
256
- nary[1],
257
- nary[2],
262
+ filter_values(nary[1]),
263
+ filter_values(nary[2]),
264
+ filter_values(nary[3]),
265
+ { type: subsup }
258
266
  )
259
267
  end
260
268
 
@@ -283,7 +291,8 @@ module Plurimath
283
291
  end
284
292
 
285
293
  def td_value(td_object)
286
- if td_object.is_a?(String) && td_object.empty?
294
+ str_classes = [String, Parslet::Slice]
295
+ if str_classes.include?(td_object.class) && td_object.to_s.empty?
287
296
  return Math::Function::Text.new(nil)
288
297
  end
289
298
 
@@ -452,7 +461,7 @@ module Plurimath
452
461
  end
453
462
 
454
463
  def left_right_objects(paren, function)
455
- paren = if paren.to_s.match?(/\\{|\\}/)
464
+ paren = if paren.to_s.match?(/\\\{|\\\}/)
456
465
  paren.to_s.gsub(/\\/, "")
457
466
  else
458
467
  Latex::Constants::LEFT_RIGHT_PARENTHESIS[paren.to_sym]
@@ -469,12 +478,7 @@ module Plurimath
469
478
  def mrow_left_right(mrow = [])
470
479
  object = mrow.first
471
480
  !(
472
- (
473
- (
474
- object.is_a?(Math::Function::TernaryFunction) && object.any_value_exist?
475
- ) &&
476
- (mrow.length <= 2)
477
- ) ||
481
+ ((object.is_a?(Math::Function::TernaryFunction) && object.any_value_exist?) && (mrow.length <= 2)) ||
478
482
  (object.is_a?(Math::Function::UnaryFunction) && mrow.length == 1)
479
483
  )
480
484
  end
@@ -564,6 +568,37 @@ module Plurimath
564
568
  end
565
569
  end
566
570
  end
571
+
572
+ def validate_math_zone(object)
573
+ return false unless object
574
+
575
+ if object.is_a?(Math::Formula)
576
+ filter_math_zone_values(object.value).find do |d|
577
+ !d.is_a?(Math::Function::Text)
578
+ end
579
+ else
580
+ !TEXT_CLASSES.include?(object.class_name)
581
+ end
582
+ end
583
+
584
+ def filter_math_zone_values(value)
585
+ return [] if value&.empty?
586
+
587
+ new_arr = []
588
+ temp_array = []
589
+ skip_index = nil
590
+ value.each_with_index do |obj, index|
591
+ object = obj.dup
592
+ next if index == skip_index
593
+ next temp_array << object.value if TEXT_CLASSES.include?(object.class_name)
594
+
595
+ new_arr << Math::Function::Text.new(temp_array.join(" ")) if temp_array.any?
596
+ temp_array = []
597
+ new_arr << object
598
+ end
599
+ new_arr << Math::Function::Text.new(temp_array.join(" ")) if temp_array.any?
600
+ new_arr
601
+ end
567
602
  end
568
603
  end
569
604
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plurimath
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "plurimath/xml_engine"
4
+ require "corelib/array/pack" if RUBY_ENGINE == "opal"
5
+ require "oga"
6
+
7
+ module Plurimath
8
+ module XMLEngine
9
+ class Oga
10
+ class << self
11
+ def new_element(name)
12
+ data = ::Oga::XML::Element.new(name: name)
13
+ Node.new(data)
14
+ end
15
+
16
+ def dump(data, indent: nil)
17
+ Dumper.new(data, indent: indent).dump.out
18
+ end
19
+
20
+ def load(data)
21
+ data = ::Oga::XML::Parser.new(data, html: true).parse
22
+ if data.xml_declaration
23
+ Document.new(data)
24
+ else
25
+ Document.new(data).nodes.first
26
+ end
27
+ end
28
+
29
+ def is_xml_comment?(node)
30
+ node = node.unwrap if node.respond_to? :unwrap
31
+ node.is_a?(Comment)
32
+ end
33
+ end
34
+
35
+ # Create API compatible with Ox, per Plurimath usage
36
+ class Wrapper
37
+ def initialize(value)
38
+ @wrapped = value
39
+ end
40
+
41
+ def unwrap
42
+ @wrapped
43
+ end
44
+
45
+ def ==(other)
46
+ self.class == other.class &&
47
+ @wrapped.inspect == other.unwrap.inspect
48
+ end
49
+ end
50
+
51
+ class Node < Wrapper
52
+ # Ox removes text nodes that are whitespace-only.
53
+ # There exists a weird edge case on which Plurimath depends:
54
+ # <mi> <!-- xxx --> &#x3C0;<!--GREEK SMALL LETTER PI--> </mi>
55
+ # If the last text node of an element that does not contain other
56
+ # elements is a whitespace, it preserves it. The first one can be
57
+ # safely removed.
58
+ def nodes
59
+ children = @wrapped.children
60
+ length = children.length
61
+ preserve_last = true
62
+ children.map.with_index do |i,idx|
63
+ if preserve_last && idx == length-1 && i.is_a?(::Oga::XML::Text)
64
+ i.text
65
+ elsif i.is_a? ::Oga::XML::Text
66
+ remove_indentation(i)
67
+ elsif i.is_a? ::Oga::XML::Comment
68
+ Node.new(i)
69
+ else
70
+ preserve_last = false
71
+ Node.new(i)
72
+ end
73
+ end.compact
74
+ end
75
+
76
+ def [](attr)
77
+ attr = attr.to_s
78
+
79
+ @wrapped.attributes.each do |e|
80
+ return e.value if [e.name, e.name.split(":").last].include? attr
81
+ end
82
+
83
+ nil
84
+ end
85
+
86
+ def []=(attr, value)
87
+ # Here we tap into the internal representation due to some likely
88
+ # bug in Oga
89
+ attr = ::Oga::XML::Attribute.new(name: attr.to_s)
90
+ attr.element = @wrapped
91
+ attr.instance_variable_set(:@value, value.to_s)
92
+ attr.instance_variable_set(:@decoded, true)
93
+ @wrapped.attributes << attr
94
+ end
95
+
96
+ def <<(other)
97
+ other = other.unwrap if other.respond_to? :unwrap
98
+
99
+ case other
100
+ when String
101
+ text = other
102
+ # Here we tap into the internal representation due to some likely
103
+ # bug in Oga
104
+ other = ::Oga::XML::Text.new
105
+ other.instance_variable_set(:@from_plurimath, true)
106
+ other.instance_variable_set(:@text, text)
107
+ other.instance_variable_set(:@decoded, true)
108
+ end
109
+
110
+ @wrapped.children << other.dup
111
+ self
112
+ end
113
+
114
+ def attributes
115
+ @wrapped.attributes.to_h do |e|
116
+ [e.name.split(":").last, e.value]
117
+ end
118
+ end
119
+
120
+ def locate(xpath)
121
+ @wrapped.xpath(xpath).map do |i|
122
+ case i
123
+ when ::Oga::XML::Text
124
+ i.text
125
+ when ::Oga::XML::Attribute
126
+ i.value
127
+ else
128
+ Node.new(i)
129
+ end
130
+ end
131
+ end
132
+
133
+ def name
134
+ @wrapped.name
135
+ end
136
+
137
+ def name=(new_name)
138
+ @wrapped.name = new_name
139
+ end
140
+
141
+ private
142
+
143
+ def remove_indentation(text)
144
+ from_us = text.instance_variable_get(:@from_plurimath)
145
+ !from_us && text.text.strip == "" ? nil : text.text
146
+ end
147
+ end
148
+
149
+ class Document < Node
150
+ end
151
+
152
+ Comment = ::Oga::XML::Comment
153
+
154
+ # Dump the tree just as if we were Ox. This is a limited implementation.
155
+ class Dumper
156
+ def initialize(tree, indent: nil)
157
+ @tree = tree
158
+ @indent = indent
159
+ @depth = 0
160
+ @out = ""
161
+ end
162
+
163
+ def dump(node = @tree)
164
+ case node
165
+ when Node
166
+ nodes = node.nodes
167
+ if nodes.length == 0
168
+ line_break
169
+ @out += "<#{node.unwrap.name}#{dump_attrs(node)}/>"
170
+ else
171
+ line_break
172
+ @out += "<#{node.unwrap.name}#{dump_attrs(node)}>"
173
+ @depth += 1
174
+ nodes.each { |i| dump(i) }
175
+ @depth -= 1
176
+ line_break unless nodes.last.is_a?(::String)
177
+ @out += "</#{node.unwrap.name}>"
178
+ end
179
+ when ::String
180
+ @out += entities(node)
181
+ end
182
+
183
+ line_break if node.object_id == @tree.object_id
184
+
185
+ self
186
+ end
187
+
188
+ attr_reader :out
189
+
190
+ ORD_AMP="&".ord
191
+ ORD_LT="<".ord
192
+ ORD_GT=">".ord
193
+ ORD_APOS="'".ord
194
+ ORD_QUOT='"'.ord
195
+ ORD_NEWLINE="\n".ord
196
+ ORD_CARRIAGERETURN="\r".ord
197
+
198
+ def self.entities(text,attr=false)
199
+ text.to_s.chars.map(&:ord).map do |i|
200
+ if i == ORD_AMP
201
+ "&amp;"
202
+ elsif i == ORD_LT
203
+ "&lt;"
204
+ elsif i == ORD_GT
205
+ "&gt;"
206
+ elsif i == ORD_QUOT && attr
207
+ "&quot;"
208
+ elsif i == ORD_NEWLINE || i == ORD_CARRIAGERETURN
209
+ i.chr("utf-8")
210
+ elsif i < 0x20
211
+ "&#x#{i.to_s(16).rjust(4, "0")};"
212
+ else
213
+ i.chr("utf-8")
214
+ end
215
+ end.join
216
+ end
217
+
218
+ private
219
+
220
+ def dump_attrs(node)
221
+ node.unwrap.attributes.map do |i|
222
+ # Currently, this is not part of the contract. But in the future
223
+ # it may be needed to also handle namespaces:
224
+ #
225
+ # if i.namespace
226
+ # %{ #{i.namespace.name}:#{i.name}="#{attr_entities i.value}"}
227
+ %{ #{i.name}="#{attr_entities i.value}"}
228
+ end.join
229
+ end
230
+
231
+ def entities(text)
232
+ self.class.entities(text)
233
+ end
234
+
235
+ def attr_entities(text)
236
+ self.class.entities(text, true)
237
+ end
238
+
239
+ def line_break
240
+ @out += "\n"
241
+ @out += " " * (@indent * @depth) if @indent
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "plurimath/xml_engine"
4
+ require "ox"
5
+ Ox.default_options = { encoding: "UTF-8" }
6
+
7
+ module Plurimath
8
+ module XMLEngine
9
+ class Ox
10
+ class << self
11
+ def new_element(name)
12
+ ::Ox::Element.new(name)
13
+ end
14
+
15
+ def dump(data, **options)
16
+ ::Ox.dump(data, **options)
17
+ end
18
+
19
+ def load(data)
20
+ ::Ox.load(data, strip_namespace: true)
21
+ end
22
+
23
+ def is_xml_comment?(node)
24
+ node.is_a?(::Ox::Comment)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ module Plurimath
2
+ singleton_class.attr_accessor :xml_engine
3
+
4
+ module XMLEngine
5
+ end
6
+ end
data/lib/plurimath.rb CHANGED
@@ -1,4 +1,14 @@
1
- # frozen_string_literal: true
2
1
 
3
2
  require_relative "plurimath/version"
4
- require_relative "plurimath/math"
3
+
4
+ # Select an XML engine
5
+ if RUBY_ENGINE == 'opal'
6
+ require "plurimath/setup/oga"
7
+ require "plurimath/setup/opal"
8
+ elsif ENV['PLURIMATH_OGA']
9
+ require "plurimath/setup/oga"
10
+ else
11
+ require "plurimath/setup/ox"
12
+ end
13
+
14
+ require "plurimath/math"
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.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-08 00:00:00.000000000 Z
11
+ date: 2023-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parslet
@@ -49,6 +49,8 @@ files:
49
49
  - ".github/workflows/release.yml"
50
50
  - ".gitignore"
51
51
  - ".hound.yml"
52
+ - ".rspec"
53
+ - ".rspec-opal"
52
54
  - ".rubocop.yml"
53
55
  - AsciiMath-Supported-Data.adoc
54
56
  - CODE_OF_CONDUCT.md
@@ -146,6 +148,7 @@ files:
146
148
  - lib/plurimath/math/function/msgroup.rb
147
149
  - lib/plurimath/math/function/msline.rb
148
150
  - lib/plurimath/math/function/multiscript.rb
151
+ - lib/plurimath/math/function/nary.rb
149
152
  - lib/plurimath/math/function/norm.rb
150
153
  - lib/plurimath/math/function/obrace.rb
151
154
  - lib/plurimath/math/function/oint.rb
@@ -202,10 +205,16 @@ files:
202
205
  - lib/plurimath/omml.rb
203
206
  - lib/plurimath/omml/parser.rb
204
207
  - lib/plurimath/omml/transform.rb
208
+ - lib/plurimath/setup/oga.rb
209
+ - lib/plurimath/setup/opal.rb.erb
210
+ - lib/plurimath/setup/ox.rb
205
211
  - lib/plurimath/unicode.rb
206
212
  - lib/plurimath/unitsml.rb
207
213
  - lib/plurimath/utility.rb
208
214
  - lib/plurimath/version.rb
215
+ - lib/plurimath/xml_engine.rb
216
+ - lib/plurimath/xml_engine/oga.rb
217
+ - lib/plurimath/xml_engine/ox.rb
209
218
  - plurimath.gemspec
210
219
  homepage: https://github.com/plurimath/plurimath
211
220
  licenses: