asciimath 1.0.9 → 2.0.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 (57) hide show
  1. checksums.yaml +5 -5
  2. data/AST.adoc +457 -0
  3. data/CHANGELOG.adoc +12 -0
  4. data/Gemfile.lock +39 -0
  5. data/README.adoc +27 -3
  6. data/asciimath.gemspec +9 -5
  7. data/lib/asciimath.rb +5 -4
  8. data/lib/asciimath/ast.rb +456 -0
  9. data/lib/asciimath/cli.rb +4 -1
  10. data/lib/asciimath/color_table.rb +21 -0
  11. data/lib/asciimath/html.rb +126 -111
  12. data/lib/asciimath/latex.rb +386 -0
  13. data/lib/asciimath/markup.rb +478 -0
  14. data/lib/asciimath/mathml.rb +189 -87
  15. data/lib/asciimath/parser.rb +498 -343
  16. data/lib/asciimath/symbol_table.rb +25 -0
  17. data/lib/asciimath/version.rb +1 -1
  18. data/spec/ast.rb +144 -0
  19. data/spec/parser_spec.rb +592 -165
  20. data/spec/schema/mathml2/common/common-attribs.xsd +41 -0
  21. data/spec/schema/mathml2/common/math.xsd +126 -0
  22. data/spec/schema/mathml2/common/xlink-href.xsd +20 -0
  23. data/spec/schema/mathml2/content/arith.xsd +90 -0
  24. data/spec/schema/mathml2/content/calculus.xsd +146 -0
  25. data/spec/schema/mathml2/content/common-attrib.xsd +30 -0
  26. data/spec/schema/mathml2/content/constants.xsd +83 -0
  27. data/spec/schema/mathml2/content/constructs.xsd +260 -0
  28. data/spec/schema/mathml2/content/elementary-functions.xsd +117 -0
  29. data/spec/schema/mathml2/content/functions.xsd +73 -0
  30. data/spec/schema/mathml2/content/linear-algebra.xsd +173 -0
  31. data/spec/schema/mathml2/content/logic.xsd +53 -0
  32. data/spec/schema/mathml2/content/relations.xsd +55 -0
  33. data/spec/schema/mathml2/content/semantics.xsd +85 -0
  34. data/spec/schema/mathml2/content/sets.xsd +236 -0
  35. data/spec/schema/mathml2/content/statistics.xsd +136 -0
  36. data/spec/schema/mathml2/content/tokens.xsd +120 -0
  37. data/spec/schema/mathml2/content/vector-calculus.xsd +88 -0
  38. data/spec/schema/mathml2/mathml2.xsd +59 -0
  39. data/spec/schema/mathml2/presentation/action.xsd +44 -0
  40. data/spec/schema/mathml2/presentation/characters.xsd +37 -0
  41. data/spec/schema/mathml2/presentation/common-attribs.xsd +113 -0
  42. data/spec/schema/mathml2/presentation/common-types.xsd +103 -0
  43. data/spec/schema/mathml2/presentation/error.xsd +40 -0
  44. data/spec/schema/mathml2/presentation/layout.xsd +195 -0
  45. data/spec/schema/mathml2/presentation/scripts.xsd +186 -0
  46. data/spec/schema/mathml2/presentation/space.xsd +52 -0
  47. data/spec/schema/mathml2/presentation/style.xsd +69 -0
  48. data/spec/schema/mathml2/presentation/table.xsd +216 -0
  49. data/spec/schema/mathml2/presentation/tokens.xsd +124 -0
  50. data/spec/schema/mathml3/mathml3-common.xsd +99 -0
  51. data/spec/schema/mathml3/mathml3-content.xsd +684 -0
  52. data/spec/schema/mathml3/mathml3-presentation.xsd +2151 -0
  53. data/spec/schema/mathml3/mathml3-strict-content.xsd +186 -0
  54. data/spec/schema/mathml3/mathml3.xsd +9 -0
  55. metadata +102 -10
  56. data/.gitignore +0 -16
  57. data/.travis.yml +0 -18
@@ -1,5 +1,17 @@
1
1
  = Asciimath Changelog
2
2
 
3
+ == 2.0.0
4
+
5
+ Enhancements::
6
+
7
+ * The AsciiMath parser has been significantly improved and should now have reached feature parity with the original Javascript implementation.
8
+ * Issue #16: add initial LaTeX output support (@GarkGarcia)
9
+ * Issue #26: avoid generating `<mfenced>` tags in MathML output
10
+
11
+ Bug fixes::
12
+
13
+ * Issue #14: add support for additional symbols and functions that are supported by Asciimath JS
14
+
3
15
  == 1.0.9
4
16
 
5
17
  Bug fixes::
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ asciimath (2.0.0.next1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ mini_portile2 (2.4.0)
11
+ nokogiri (1.10.9)
12
+ mini_portile2 (~> 2.4.0)
13
+ rake (13.0.1)
14
+ rspec (3.9.0)
15
+ rspec-core (~> 3.9.0)
16
+ rspec-expectations (~> 3.9.0)
17
+ rspec-mocks (~> 3.9.0)
18
+ rspec-core (3.9.2)
19
+ rspec-support (~> 3.9.3)
20
+ rspec-expectations (3.9.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.9.0)
23
+ rspec-mocks (3.9.1)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.9.0)
26
+ rspec-support (3.9.3)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ asciimath!
33
+ bundler (> 0)
34
+ nokogiri (~> 1.10.9)
35
+ rake (~> 13.0.0)
36
+ rspec (~> 3.9.0)
37
+
38
+ BUNDLED WITH
39
+ 1.17.2
@@ -1,7 +1,7 @@
1
1
  # AsciiMath
2
2
  ifndef::env-site[:status:]
3
3
 
4
- An http://asciimath.org[AsciiMath] parser and MathML generator written in pure Ruby.
4
+ An http://asciimath.org[AsciiMath] parser and MathML/LaTeX generator written in pure Ruby.
5
5
 
6
6
  ifdef::status[]
7
7
  [discrete]
@@ -48,15 +48,16 @@ parsed_expression = AsciiMath.parse(asciimath)
48
48
 
49
49
  The parsed expression is a set of nested Array and Hash objects.
50
50
 
51
- This expression can then be converted to MathML or HTML (experimental.
51
+ This expression can then be converted to MathML, HTML (experimental) or LaTeX.
52
52
 
53
53
  [source,ruby]
54
54
  ----
55
55
  math_ml = parsed_expression.to_mathml
56
56
  html = parsed_expression.to_html
57
+ latex = parsed_expression.to_latex
57
58
  ----
58
59
 
59
- The MathML or HTML code is returned as a String.
60
+ The MathML, HTML or LaTeX code is returned as a String.
60
61
 
61
62
  ### Command line
62
63
 
@@ -78,6 +79,12 @@ asciimath mathml "an asciimath string"
78
79
  asciimath html "an asciimath string"
79
80
  ----
80
81
 
82
+ .LaTeX Generation
83
+ [source]
84
+ ----
85
+ asciimath latex "an asciimath string"
86
+ ----
87
+
81
88
  This command will print out the generated code on stdout.
82
89
 
83
90
 
@@ -95,6 +102,23 @@ Known issues are as follows:
95
102
  Rendering the HTML output correctly requires the inclusion of `style/math.css` in the html document.
96
103
  There is currently no specific required font for this output, it simply selects a `serif` font family - change the `@font-family` attribute in the `.math-inline` class to select something specific.
97
104
 
105
+ ## Notes on the LaTeX Output
106
+
107
+ All LaTeX commands and environments used in the output are coverved by
108
+ https://ctan.org/pkg/amsmath[`amsmath`] and `amssymb`, with a few exceptions:
109
+
110
+ * `\color`
111
+ * `\cancel`
112
+ * `\mathscr`
113
+ * `\twoheadrightarrowtail`
114
+
115
+ The `\color` command is supported by the
116
+ https://www.ctan.org/pkg/xcolor[`xcolor`] package, which is included in most
117
+ LaTeX distributions. The `\cancel` command is supported by the
118
+ https://www.ctan.org/pkg/cancel[cancel] package, also included in most LaTeX
119
+ distributions. The other commands are supported by the
120
+ https://ctan.org/pkg/stix[`stix`] package.
121
+
98
122
  ## Contributing
99
123
 
100
124
  . Fork it (https://github.com/pepijnve/asciimath/fork)
@@ -1,24 +1,28 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
4
5
  require 'asciimath/version'
6
+ require 'rake/file_list'
5
7
 
6
8
  Gem::Specification.new do |spec|
7
9
  spec.name = "asciimath"
8
10
  spec.version = AsciiMath::VERSION
9
- spec.authors = ["Pepijn Van Eeckhoudt"]
10
- spec.email = ["pepijn@vaneeckhoudt.net"]
11
+ spec.authors = ["Pepijn Van Eeckhoudt", "Gark Garcia"]
12
+ spec.email = ["pepijn@vaneeckhoudt.net", "pablo-ecobar@riseup.net"]
11
13
  spec.summary = %q{AsciiMath parser and converter}
12
14
  spec.description = %q{A pure Ruby AsciiMath parsing and conversion library.}
13
15
  spec.homepage = ""
14
16
  spec.license = "MIT"
15
17
 
16
- spec.files = `git ls-files -z`.split("\x0")
18
+ spec.files = Rake::FileList['**/*'].exclude(*File.read('.gitignore').split)
19
+ .exclude('dump_symbol_table.rb')
17
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
22
  spec.require_paths = ["lib"]
20
23
 
21
24
  spec.add_development_dependency "bundler", "> 0"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency "rspec", "~> 3.1.0"
25
+ spec.add_development_dependency "rake", "~> 13.0.0"
26
+ spec.add_development_dependency "rspec", "~> 3.9.0"
27
+ spec.add_development_dependency "nokogiri", "~> 1.10.9"
24
28
  end
@@ -1,4 +1,5 @@
1
- require File.join(File.dirname(__FILE__), 'asciimath/version')
2
- require File.join(File.dirname(__FILE__), 'asciimath/parser')
3
- require File.join(File.dirname(__FILE__), 'asciimath/mathml')
4
- require File.join(File.dirname(__FILE__), 'asciimath/html')
1
+ require_relative 'asciimath/version'
2
+ require_relative 'asciimath/parser'
3
+ require_relative 'asciimath/mathml'
4
+ require_relative 'asciimath/html'
5
+ require_relative 'asciimath/latex'
@@ -0,0 +1,456 @@
1
+ module AsciiMath
2
+ module AST
3
+ def expression(*e)
4
+ case e.length
5
+ when 0
6
+ nil
7
+ when 1
8
+ e[0]
9
+ else
10
+ Sequence.new(e)
11
+ end
12
+ end
13
+
14
+ def paren(lparen, e, rparen)
15
+ Paren.new(lparen, e, rparen)
16
+ end
17
+
18
+ def group(lparen, e, rparen)
19
+ Group.new(lparen, e, rparen)
20
+ end
21
+
22
+ def subsup(e, sub, sup)
23
+ SubSup.new(e, sub, sup)
24
+ end
25
+
26
+ def sub(e, sub)
27
+ SubSup.new(e, sub, nil)
28
+ end
29
+
30
+ def sup(e, sup)
31
+ SubSup.new(e, nil, sup)
32
+ end
33
+
34
+ def unary(operator, e)
35
+ UnaryOp.new(operator, e)
36
+ end
37
+
38
+ def binary(operator, e1, e2)
39
+ BinaryOp.new(operator, e1, e2)
40
+ end
41
+
42
+ def infix(e1, operator, e2)
43
+ InfixOp.new(operator, e1, e2)
44
+ end
45
+
46
+ def text(value)
47
+ Text.new(value)
48
+ end
49
+
50
+ def number(value)
51
+ Number.new(value)
52
+ end
53
+
54
+ def symbol(symbol, text)
55
+ Symbol.new(symbol, text)
56
+ end
57
+
58
+ def identifier(value)
59
+ Identifier.new(value)
60
+ end
61
+
62
+ def matrix(lparen, rows, rparen)
63
+ Matrix.new(lparen, rows, rparen)
64
+ end
65
+
66
+ def color(r, g, b, text)
67
+ Color.new(r, g, b, text)
68
+ end
69
+
70
+ class Node
71
+ attr_reader :parent
72
+
73
+ def initialize
74
+ @parent = nil
75
+ end
76
+
77
+ protected
78
+
79
+ attr_writer :parent
80
+ end
81
+
82
+ class InnerNode < Node
83
+ include Enumerable
84
+
85
+ def initialize
86
+ super
87
+ @children = []
88
+ end
89
+
90
+ def [](*args)
91
+ @children[*args]
92
+ end
93
+
94
+ def length
95
+ @children.length
96
+ end
97
+
98
+ def each(&block)
99
+ @children.each(&block)
100
+ end
101
+
102
+ protected
103
+
104
+ def child_nodes
105
+ @children
106
+ end
107
+
108
+ def add(node)
109
+ node.parent.remove(node) if node.parent
110
+ node.parent = self
111
+ child_nodes << node
112
+ end
113
+
114
+ def remove(node)
115
+ node.parent = nil
116
+ child_nodes.delete(node)
117
+ end
118
+ end
119
+
120
+ class Sequence < InnerNode
121
+ def initialize(nodes)
122
+ super()
123
+ nodes.each { |node| add(node) }
124
+ end
125
+
126
+ def to_s
127
+ child_nodes.map { |node| node.to_s }.join(" ")
128
+ end
129
+
130
+ def ==(o)
131
+ o.class == self.class && o.child_nodes == child_nodes
132
+ end
133
+ end
134
+
135
+ class Paren < InnerNode
136
+ attr_reader :lparen
137
+ attr_reader :rparen
138
+
139
+ def initialize(lparen, e, rparen)
140
+ super()
141
+ @lparen = lparen
142
+ @rparen = rparen
143
+ add(e) if e
144
+ end
145
+
146
+ def expression
147
+ child_nodes[0]
148
+ end
149
+
150
+ def to_s
151
+ "#{lparen.nil? ? '' : lparen.text}#{expression}#{rparen.nil? ? '' : rparen.text}"
152
+ end
153
+
154
+ def ==(o)
155
+ o.class == self.class && o.lparen == lparen && o.expression == expression && o.rparen == rparen
156
+ end
157
+ end
158
+
159
+ class Group < InnerNode
160
+ attr_reader :lparen
161
+ attr_reader :rparen
162
+
163
+ def initialize(lparen, e, rparen)
164
+ super()
165
+ @lparen = lparen
166
+ @rparen = rparen
167
+ add(e) if e
168
+ end
169
+
170
+ def expression
171
+ child_nodes[0]
172
+ end
173
+
174
+ def to_s
175
+ "#{lparen.nil? ? '' : lparen.text}#{expression}#{rparen.nil? ? '' : rparen.text}"
176
+ end
177
+
178
+ def ==(o)
179
+ o.class == self.class && o.lparen == lparen && o.expression == expression && o.rparen == rparen
180
+ end
181
+ end
182
+
183
+ class SubSup < InnerNode
184
+ def initialize(e, sub, sup)
185
+ super()
186
+ add(e)
187
+ add(sub || Empty.new)
188
+ add(sup || Empty.new)
189
+ end
190
+
191
+ def base_expression
192
+ child_nodes[0]
193
+ end
194
+
195
+ def sub_expression
196
+ child = child_nodes[1]
197
+ child.is_a?(Empty) ? nil : child
198
+ end
199
+
200
+ def sup_expression
201
+ child = child_nodes[2]
202
+ child.is_a?(Empty) ? nil : child
203
+ end
204
+
205
+ def to_s
206
+ s = ""
207
+ s << base_expression.to_s
208
+ sub = sub_expression
209
+ if sub
210
+ s << "_" << sub.to_s
211
+ end
212
+ sup = sup_expression
213
+ if sup
214
+ s << "^" << sup.to_s
215
+ end
216
+ s
217
+ end
218
+
219
+ def ==(o)
220
+ o.class == self.class && o.base_expression == base_expression && o.sub_expression == sub_expression && o.sup_expression == sup_expression
221
+ end
222
+ end
223
+
224
+ class UnaryOp < InnerNode
225
+ def initialize(operator, e)
226
+ super()
227
+ add(operator)
228
+ add(e)
229
+ end
230
+
231
+ def operator
232
+ child_nodes[0]
233
+ end
234
+
235
+ def operand
236
+ child_nodes[1]
237
+ end
238
+
239
+ def to_s
240
+ "#{operator} #{operand}"
241
+ end
242
+
243
+ def ==(o)
244
+ o.class == self.class && o.operator == operator && o.operand == operand
245
+ end
246
+ end
247
+
248
+ class BinaryOp < InnerNode
249
+ def initialize(operator, e1, e2)
250
+ super()
251
+ add(operator)
252
+ add(e1)
253
+ add(e2)
254
+ end
255
+
256
+
257
+ def operator
258
+ child_nodes[0]
259
+ end
260
+
261
+ def operand1
262
+ child_nodes[1]
263
+ end
264
+
265
+ def operand2
266
+ child_nodes[2]
267
+ end
268
+
269
+ def to_s
270
+ "#{operator} #{operand1} #{operand2}"
271
+ end
272
+
273
+ def ==(o)
274
+ o.class == self.class && o.operator == operator && o.operand1 == operand1 && o.operand2 == operand2
275
+ end
276
+ end
277
+
278
+ class InfixOp < InnerNode
279
+ def initialize(operator, e1, e2)
280
+ super()
281
+ add(operator)
282
+ add(e1)
283
+ add(e2)
284
+ end
285
+
286
+
287
+ def operator
288
+ child_nodes[0]
289
+ end
290
+
291
+ def operand1
292
+ child_nodes[1]
293
+ end
294
+
295
+ def operand2
296
+ child_nodes[2]
297
+ end
298
+
299
+ def to_s
300
+ "#{operand1} #{operator} #{operand2}"
301
+ end
302
+
303
+ def ==(o)
304
+ o.class == self.class && o.operator == operator && o.operand1 == operand1 && o.operand2 == operand2
305
+ end
306
+ end
307
+
308
+ class ValueNode < Node
309
+ attr_reader :value
310
+
311
+ def initialize(value)
312
+ super()
313
+ @value = value
314
+ end
315
+
316
+ def to_s
317
+ value.to_s
318
+ end
319
+
320
+ def ==(o)
321
+ o.class == self.class && o.value == value
322
+ end
323
+ end
324
+
325
+ class Text < ValueNode
326
+ def initialize(value)
327
+ super(value.dup.freeze)
328
+ end
329
+
330
+ def to_s
331
+ '"' + super + '"'
332
+ end
333
+ end
334
+
335
+ class Number < ValueNode
336
+ def initialize(value)
337
+ super(value.dup.freeze)
338
+ end
339
+ end
340
+
341
+ class Symbol < ValueNode
342
+ attr_reader :text
343
+
344
+ def initialize(value, text)
345
+ super(value)
346
+ @text = text.dup.freeze
347
+ end
348
+
349
+ def ==(o)
350
+ super && o.text == text
351
+ end
352
+
353
+ def to_s
354
+ text
355
+ end
356
+ end
357
+
358
+ class Identifier < ValueNode
359
+ def initialize(value)
360
+ super(value.dup.freeze)
361
+ end
362
+ end
363
+
364
+ class Color < ValueNode
365
+ attr_reader :text
366
+
367
+ def initialize(r, g, b, text)
368
+ super({:r => r, :g => g, :b => b}.freeze)
369
+ @text = text.dup.freeze
370
+ end
371
+
372
+ def red
373
+ value[:r]
374
+ end
375
+
376
+ def green
377
+ value[:g]
378
+ end
379
+
380
+ def blue
381
+ value[:b]
382
+ end
383
+
384
+ def ==(o)
385
+ o.class == self.class &&
386
+ o.red == red &&
387
+ o.green == green &&
388
+ o.blue == blue &&
389
+ o.text == text
390
+ end
391
+
392
+ def to_hex_rgb
393
+ sprintf('#%02x%02x%02x', red, green, blue)
394
+ end
395
+
396
+ def to_s
397
+ text
398
+ end
399
+ end
400
+
401
+ class Matrix < InnerNode
402
+ attr_reader :lparen
403
+ attr_reader :rparen
404
+
405
+ def initialize(lparen, rows, rparen)
406
+ super()
407
+ @lparen = lparen
408
+ @rparen = rparen
409
+ rows.map { |row| MatrixRow.new(row) }.each { |row_seq| add(row_seq) }
410
+ end
411
+
412
+ def to_s
413
+ s = ""
414
+ s << (lparen.nil? ? '{:' : lparen.text)
415
+ s << child_nodes.map { |node| node.to_s }.join(",")
416
+ s << (rparen.nil? ? ':}' : rparen.text)
417
+ end
418
+
419
+ def ==(o)
420
+ o.class == self.class &&
421
+ o.lparen == lparen &&
422
+ o.child_nodes == child_nodes &&
423
+ o.rparen == rparen
424
+ end
425
+ end
426
+
427
+ class MatrixRow < InnerNode
428
+ def initialize(nodes)
429
+ super()
430
+ nodes.each { |node| add(node || Empty.new) }
431
+ end
432
+
433
+ def to_s
434
+ "(" + child_nodes.map { |node| node.to_s }.join(",") + ")"
435
+ end
436
+
437
+ def ==(o)
438
+ o.class == self.class && o.child_nodes == child_nodes
439
+ end
440
+ end
441
+
442
+ class Empty < Node
443
+ def initialize()
444
+ super
445
+ end
446
+
447
+ def to_s
448
+ ''
449
+ end
450
+
451
+ def ==(o)
452
+ o.class == self.class
453
+ end
454
+ end
455
+ end
456
+ end