math_ml 0.13 → 1.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.
data/lib/math_ml/latex.rb CHANGED
@@ -1,1105 +1,1143 @@
1
- require "math_ml/latex/builtin"
1
+ require 'math_ml/latex/builtin'
2
2
 
3
3
  module MathML
4
- module LaTeX
5
- MBEC = /\\.|[^\\]/m
6
-
7
- module RE
8
- SPACE = /(?:\s|%.*$)/
9
- NUMERICS = /(?:\.\d+)|(?:\d+(\.\d+)?)/
10
- OPERATORS = /[,\.\+\-\*=\/\(\)\[\]<>"|;:!]/
11
- ALPHABETS = /[a-zA-Z]/
12
- BLOCK = /\A\{(.*?)\}\z/m
13
- OPTION = /\A\[(.*)\]\z/m
14
- COMMANDS = /\\([a-zA-Z]+|[^a-zA-Z])/
15
- WBSLASH = /\\\\/
16
- BRACES = /\A([.|\[\]\(\)<>])\z/
17
- end
18
-
19
- module Font
20
- NORMAL = 0
21
- BOLD = 1
22
- BLACKBOLD = 2
23
- SCRIPT = 3
24
- FRAKTUR = 4
25
- ROMAN = 5
26
- BOLD_ITALIC = 6
27
- end
28
-
29
- class BlockNotClosed < StandardError; end
30
- class NotEnvironment < StandardError; end
31
- class EnvironmentNotEnd < StandardError; end
32
- class NeedParameter < StandardError; end
33
- class EndMismatchToBegin < StandardError; end
34
- class OptionNotClosed < StandardError; end
35
-
36
- class Scanner < StringScanner
37
- def done
38
- self.string[0, pos]
39
- end
40
-
41
- def scan_space
42
- _scan(/#{RE::SPACE}+/)
43
- end
44
-
45
- def skip_space_and(check_mode)
46
- opos = pos
47
- scan_space
48
- r = yield
49
- self.pos = opos if check_mode || !r
50
- r
51
- end
52
-
53
- unless instance_methods.include?("_eos?")
54
- alias :_eos? :eos?
55
- alias :_check :check
56
- alias :_scan :scan
57
- end
58
-
59
- def check(re)
60
- skip_space_and(true){_check(re)}
61
- end
62
-
63
- def scan(re)
64
- skip_space_and(false){_scan(re)}
65
- end
66
-
67
- def eos?
68
- _eos? || _check(/#{RE::SPACE}+\z/)
69
- end
70
-
71
- def check_command
72
- check(RE::COMMANDS)
73
- end
74
-
75
- def scan_command
76
- scan(RE::COMMANDS)
77
- end
78
-
79
- def peek_command
80
- check_command ? self[1] : nil
81
- end
82
-
83
- def check_block
84
- skip_space_and(true){scan_block}
85
- end
86
-
87
- def scan_block
88
- return nil unless scan(/\{/)
89
- block = "{"
90
- bpos = pos-1
91
- nest = 1
92
- while _scan(/(#{MBEC}*?)([\{\}])/)
93
- block << matched
94
- case self[2]
95
- when "{"
96
- nest+=1
97
- when "}"
98
- nest-=1
99
- break if nest==0
100
- end
101
- end
102
- if nest>0
103
- self.pos = bpos
104
- raise BlockNotClosed
105
- end
106
- self.pos = bpos
107
- _scan(/\A\{(#{Regexp.escape(block[RE::BLOCK, 1].to_s)})\}/)
108
- end
109
-
110
- def check_any(remain_space=false)
111
- skip_space_and(true){scan_any(remain_space)}
112
- end
113
-
114
- def scan_any(remain_space=false)
115
- p = pos
116
- scan_space
117
- r = remain_space ? matched.to_s : ""
118
- case
119
- when s = scan_block
120
- when s = scan_command
121
- else
122
- unless _scan(/./) || remain_space
123
- self.pos = p
124
- return nil
125
- end
126
- s = matched.to_s
127
- end
128
- r << s
129
- end
130
-
131
- def scan_option
132
- return nil unless scan(/\[/)
133
- opt = "["
134
- p = pos-1
135
- until (s=scan_any(true)) =~ /\A#{RE::SPACE}*\]\z/
136
- opt << s
137
- if eos?
138
- self.pos = p
139
- raise OptionNotClosed
140
- end
141
- end
142
- opt << s
143
- self.pos = p
144
- _scan(/\A\[(#{Regexp.escape(opt[RE::OPTION, 1].to_s)})\]/)
145
- end
146
-
147
- def check_option
148
- skip_space_and(true){scan_option}
149
- end
150
- end
151
-
152
- class ParseError < StandardError
153
- attr_accessor :rest, :done
154
- def initialize(message, rest = "", done = "")
155
- @done = done
156
- @rest = rest
157
- super(message)
158
- end
159
-
160
- def inspect
161
- "#{message} : '#{@done}' / '#{@rest}'\n"+backtrace[0..5].join("\n")
162
- end
163
- end
164
-
165
- class Macro
166
- class Command
167
- attr_reader :num, :body, :option
168
- def initialize(n, b, o)
169
- @num = n
170
- @body = b
171
- @option = o
172
- end
173
- end
174
-
175
- class Environment
176
- attr_reader :num, :beginning, :ending, :option
177
- def initialize(n, b, e, o)
178
- @num = n
179
- @beginning = b
180
- @ending = e
181
- @option = o
182
- end
183
- end
184
-
185
- def initialize
186
- @commands = Hash.new
187
- @environments = Hash.new
188
- end
189
-
190
- def parse_error(message, rest="", whole=nil)
191
- rest = whole[/\A.*?(#{Regexp.escape(rest)}.*\z)/, 1] if whole
192
- rest << @scanner.rest
193
- done = @scanner.string[0, @scanner.string.size-rest.size]
194
- ParseError.new(message, rest, done)
195
- end
196
-
197
- def parse(src)
198
- @scanner = Scanner.new(src)
199
- until @scanner.eos?
200
- unless @scanner.scan_command
201
- @scanner.scan_space
202
- raise parse_error("Syntax error.")
203
- end
204
- case @scanner[1]
205
- when "newcommand"
206
- parse_newcommand
207
- when "newenvironment"
208
- parse_newenvironment
209
- else
210
- raise parse_error("Syntax error.", @scanner.matched)
211
- end
212
- end
213
- rescue BlockNotClosed => e
214
- raise parse_error("Block not closed.")
215
- rescue OptionNotClosed => e
216
- raise parse_error("Option not closed.")
217
- end
218
-
219
- def scan_num_of_parameter
220
- if @scanner.scan_option
221
- raise parse_error("Need positive number.", @scanner[1]+"]") unless @scanner[1]=~/\A#{RE::SPACE}*\d+#{RE::SPACE}*\z/
222
- @scanner[1].to_i
223
- else
224
- 0
225
- end
226
- end
227
-
228
- def check_parameter_numbers(src, opt, whole)
229
- s = Scanner.new(src)
230
- until s.eos?
231
- case
232
- when s.scan(/#{MBEC}*?\#(\d+|.)/)
233
- raise parse_error("Need positive number.") unless s[1]=~/\d+/
234
- raise parse_error("Parameter \# too large.", s[1]+s.rest, whole) if s[1].to_i>opt
235
- else
236
- return nil
237
- end
238
- end
239
- end
240
-
241
- def parse_newcommand
242
- case
243
- when @scanner.scan_block
244
- s = Scanner.new(@scanner[1])
245
- raise parse_error("Need newcommand.", s.rest+"}") unless s.scan_command
246
- com = s[1]
247
- raise parse_error("Syntax error." ,s.rest+"}") unless s.eos?
248
- when @scanner.scan_command
249
- s = Scanner.new(@scanner[1])
250
- com = s.scan_command
251
- else
252
- raise parse_error("Need newcommand.")
253
- end
254
-
255
- optnum = scan_num_of_parameter
256
- opt = @scanner.scan_option ? @scanner[1] : nil
257
-
258
- case
259
- when @scanner.scan_block
260
- body = @scanner[1]
261
- when @scanner.scan_command
262
- body = @scanner.matched
263
- else
264
- body = @scanner.scan(/./)
265
- end
266
-
267
- raise parse_error("Need parameter.") unless body
268
-
269
- check_parameter_numbers(body, optnum, @scanner.matched)
270
-
271
- optnum-=1 if opt
272
- @commands[com] = Command.new(optnum, body, opt)
273
- end
274
-
275
- def parse_newenvironment
276
- case
277
- when @scanner.scan_block
278
- env = @scanner[1]
279
- when @scanner.scan_command
280
- raise ParseError.new
281
- when @scanner.scan(/./)
282
- env = @scanner.matched
283
- end
284
- raise parse_error("Syntax error.", env[/\A.*?(\\.*\z)/, 1], @scanner.matched) if env=~/\\/
285
-
286
- optnum = scan_num_of_parameter
287
- opt = @scanner.scan_option ? @scanner[1] : nil
288
-
289
- b = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
290
- raise parse_error("Need begin block.") unless b
291
- check_parameter_numbers(b, optnum, @scanner.matched)
292
- e = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
293
- raise parse_error("Need end block.") unless e
294
- check_parameter_numbers(e, optnum, @scanner.matched)
295
-
296
- optnum -= 1 if opt
297
- @environments[env] = Environment.new(optnum, b, e, opt)
298
- end
299
-
300
- def commands(com)
301
- @commands[com]
302
- end
303
-
304
- def expand_command(com, params, opt=nil)
305
- return nil unless @commands.has_key?(com)
306
- c = @commands[com]
307
- opt = c.option if c.option && !opt
308
- params.unshift(opt) if c.option
309
- raise ParseError.new("Need more parameter.") if params.size < c.num
310
-
311
- c.body.gsub(/(#{MBEC}*?)\#(\d+)/) do
312
- $1.to_s << params[$2.to_i-1]
313
- end
314
- end
315
-
316
- def environments(env)
317
- @environments[env]
318
- end
319
-
320
- def expand_environment(env, body, params, opt=nil)
321
- return nil unless @environments.has_key?(env)
322
- e = @environments[env]
323
- opt = e.option if e.option && !opt
324
- params.unshift(opt) if e.option
325
- raise ParseError.new("Need more parameter.") if params.size < e.num
326
-
327
- bg = e.beginning.gsub(/(#{MBEC}*?)\#(\d+)/) do
328
- $1.to_s << params[$2.to_i-1]
329
- end
330
-
331
- en = e.ending.gsub(/(#{MBEC}*?)\#(\d+)/) do
332
- $1.to_s << params[$2.to_i-1]
333
- end
334
-
335
- " #{bg} #{body} #{en} "
336
- end
337
- end
338
-
339
- module BuiltinCommands; end
340
- module BuiltinGroups; end
341
- module BuiltinEnvironments; end
342
-
343
- class Parser
344
- class CircularReferenceCommand < StandardError; end
345
-
346
- include LaTeX
347
-
348
- include BuiltinEnvironments
349
- include BuiltinGroups
350
- include BuiltinCommands
351
-
352
- BUILTIN_MACRO = <<'EOS'
353
- \newenvironment{smallmatrix}{\begin{matrix}}{\end{matrix}}
354
- \newenvironment{pmatrix}{\left(\begin{matrix}}{\end{matrix}\right)}
355
- \newenvironment{bmatrix}{\left[\begin{matrix}}{\end{matrix}\right]}
356
- \newenvironment{Bmatrix}{\left\{\begin{matrix}}{\end{matrix}\right\}}
357
- \newenvironment{vmatrix}{\left|\begin{matrix}}{\end{matrix}\right|}
358
- \newenvironment{Vmatrix}{\left\|\begin{matrix}}{\end{matrix}\right\|}
359
- EOS
360
-
361
- attr_accessor :unsecure_entity
362
- attr_reader :macro
363
- attr_reader :symbol_table
364
-
365
- def initialize(opt={})
366
- @unsecure_entity = false
367
- @entities = Hash.new
368
- @commands = Hash.new
369
- @symbols = Hash.new
370
- @delimiters = Array.new
371
- @group_begins = Hash.new
372
- @group_ends = Hash.new
373
- @macro = Macro.new
374
- @macro.parse(BUILTIN_MACRO)
375
- @expanded_command = Array.new
376
- @expanded_environment = Array.new
377
- @symbol_table = opt[:symbol] || MathML::Symbol::Default
378
- @symbol_table = MathML::Symbol::MAP[@symbol_table] if @symbol_table.is_a?(::Symbol)
379
-
380
- super()
381
- end
382
-
383
- def add_entity(list)
384
- list.each do |i|
385
- @entities[i] = true
386
- end
387
- end
388
-
389
- def parse(src, displaystyle=false)
390
- @ds = displaystyle
391
- begin
392
- parse_into(src, Math.new(@ds), Font::NORMAL)
393
- rescue ParseError => e
394
- e.done = src[0...(src.size - e.rest.size)]
395
- raise
396
- end
397
- end
398
-
399
- def push_container(container, scanner=@scanner, font=@font)
400
- data = [@container, @scanner, @font]
401
- @container, @scanner, @font = [container, scanner, font]
402
- begin
403
- yield container
404
- container
405
- ensure
406
- @container, @scanner, @font = data
407
- end
408
- end
409
-
410
- def add_plugin(plugin)
411
- self.extend(plugin)
412
- end
413
-
414
- def add_commands(*a)
415
- if a.size==1 && Hash===a[0]
416
- @commands.merge!(a[0])
417
- else
418
- a.each{|i| @commands[i] = false}
419
- end
420
- end
421
-
422
- def add_multi_command(m, *a)
423
- a.each{|i| @commands[i] = m}
424
- end
425
-
426
- def add_sym_cmd(hash)
427
- @symbols.merge!(hash)
428
- end
429
-
430
- def add_delimiter(list)
431
- @delimiters.concat(list)
432
- end
433
-
434
- def add_group(begin_name, end_name, method=nil)
435
- @group_begins[begin_name] = method
436
- @group_ends[end_name] = begin_name
437
- end
438
-
439
- private
440
- def parse_into(src, parent, font=nil)
441
- orig = [@scanner, @container, @font, @ds]
442
- @scanner = Scanner.new(src)
443
- @container = parent
444
- @font = font if font
445
- begin
446
- until @scanner.eos?
447
- @container << parse_to_element(true)
448
- end
449
- @container
450
- rescue BlockNotClosed => e
451
- raise ParseError.new("Block not closed.", @scanner.rest)
452
- rescue NotEnvironment => e
453
- raise ParseError.new("Not environment.", @scanner.rest)
454
- rescue EnvironmentNotEnd => e
455
- raise ParseError.new("Environment not end.", @scanner.rest)
456
- rescue OptionNotClosed => e
457
- raise ParseError.new("Option not closed.", @scanner.rest)
458
- rescue ParseError => e
459
- e.rest << @scanner.rest.to_s
460
- raise
461
- ensure
462
- @scanner, @container, @font, @ds = orig
463
- end
464
- end
465
-
466
- def parse_any(message = "Syntax error.")
467
- raise ParseError.new(message) unless @scanner.scan_any
468
- s = @scanner
469
- @scanner = Scanner.new(@scanner.matched)
470
- begin
471
- parse_to_element
472
- ensure
473
- @scanner = s
474
- end
475
- end
476
-
477
- def parse_to_element(whole_group = false)
478
- if whole_group && @group_begins.has_key?(@scanner.peek_command)
479
- @scanner.scan_command
480
- parse_group
481
- else
482
- case
483
- when @scanner.scan(RE::NUMERICS)
484
- parse_num
485
- when @scanner.scan(RE::ALPHABETS)
486
- parse_char
487
- when @scanner.scan(RE::OPERATORS)
488
- parse_operator
489
- when @scanner.scan_block
490
- parse_block
491
- when @scanner.scan(/_/)
492
- parse_sub
493
- when @scanner.scan(/'+|\^/)
494
- parse_sup
495
- when @scanner.scan(/~/)
496
- Space.new("1em")
497
- when @scanner.scan_command
498
- parse_command
499
- else
500
- raise ParseError.new('Syntax error.')
501
- end
502
- end
503
- end
504
-
505
- def parse_num
506
- n = Number.new
507
- n.extend(Variant).variant = Variant::BOLD if @font==Font::BOLD
508
- n << @scanner.matched
509
- end
510
-
511
- def parse_char
512
- c = @scanner.matched
513
- i = Identifier.new
514
- case @font
515
- when Font::ROMAN
516
- i.extend(Variant).variant = Variant::NORMAL
517
- when Font::BOLD
518
- i.extend(Variant).variant = Variant::BOLD
519
- when Font::BOLD_ITALIC
520
- i.extend(Variant).variant = Variant::BOLD_ITALIC
521
- when Font::BLACKBOLD
522
- c = symbol_table.convert("#{c}opf")
523
- when Font::SCRIPT
524
- c = symbol_table.convert("#{c}scr")
525
- when Font::FRAKTUR
526
- c = symbol_table.convert("#{c}fr")
527
- end
528
- i << c
529
- end
530
-
531
- def parse_operator
532
- o = @scanner.matched
533
- Operator.new << o
534
- end
535
-
536
- def parse_block
537
- os = @scanner
538
- @scanner = Scanner.new(@scanner[1])
539
- begin
540
- push_container(Row.new) do |r|
541
- r << parse_to_element(true) until @scanner.eos?
542
- end
543
- rescue ParseError => e
544
- e.rest << '}'
545
- raise
546
- ensure
547
- @scanner = os
548
- end
549
- end
550
-
551
- def parse_sub
552
- e = @container.pop
553
- e = None.new unless e
554
- e = SubSup.new(@ds && e.display_style, e) unless e.is_a?(SubSup)
555
- raise ParseError.new("Double subscript.", "_") if e.sub
556
- e.sub = parse_any("Subscript not exist.")
557
- e
558
- end
559
-
560
- def parse_sup
561
- e = @container.pop
562
- e = None.new unless e
563
- e = SubSup.new(@ds && e.display_style, e) unless e.is_a?(SubSup)
564
- raise ParseError.new("Double superscript.", @scanner[0]) if e.sup
565
- if /'+/=~@scanner[0]
566
- prime = Operator.new
567
- @scanner[0].size.times do
568
- prime << symbol_table.convert("prime")
569
- end
570
- unless @scanner.scan(/\^/)
571
- e.sup = prime
572
- return e
573
- end
574
- end
575
- sup = parse_any("Superscript not exist.")
576
-
577
- if prime
578
- unless sup.is_a?(Row)
579
- r = Row.new
580
- r << sup
581
- sup = r
582
- end
583
- sup.contents.insert(0, prime)
584
- end
585
-
586
- e.sup = sup
587
- e
588
- end
589
-
590
- def entitize(str)
591
- MathML.pcstring(str.sub(/^(.*)$/){"&#{$1};"}, true)
592
- end
593
-
594
- def parse_symbol_command(com, plain=false)
595
- unless @symbols.include?(com)
596
- @scanner.pos = @scanner.pos-(com.size+1)
597
- raise ParseError.new("Undefined command.")
598
- end
599
- data = @symbols[com]
600
- return nil unless data
601
-
602
- data, s = data
603
- su = data[0]
604
- el = data[1]
605
- el = :o unless el
606
- s = com.dup.untaint.to_sym unless s
607
- s = com if s.is_a?(String) && s.length==0
608
-
609
- case el
610
- when :I
611
- el = Identifier.new
612
- when :i
613
- el = Identifier.new
614
- el.extend(Variant).variant = Variant::NORMAL unless s.is_a?(String)&&s.length>1
615
- when :o
616
- el = Operator.new
617
- when :n
618
- el = Number.new
619
- else
620
- raise ParseError.new("Inner data broken.")
621
- end
622
-
623
- case s
624
- when Fixnum
625
- s = MathML.pcstring("&\#x#{s.to_s(16)};", true)
626
- when ::Symbol
627
- s = symbol_table.convert(s)
628
- else
629
- MathML.pcstring(s, true)
630
- end
631
-
632
- return s if plain
633
- el << s
634
- el.as_display_style if su==:u
635
- el
636
- end
637
-
638
- def parse_command
639
- com = @scanner[1]
640
- matched = @scanner.matched
641
- pos = @scanner.pos-matched.size
642
- macro = @macro.commands(com)
643
- if macro
644
- begin
645
- flg = @expanded_command.include?(com)
646
- @expanded_command.push(com)
647
- raise CircularReferenceCommand if flg
648
- option = (macro.option && @scanner.scan_option) ? @scanner[1] : nil
649
- params = Array.new
650
- (1..macro.num).each do
651
- params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
652
- raise ParseError.new("Need more parameter.") unless params.last
653
- end
654
- r = parse_into(@macro.expand_command(com, params, option), Array.new)
655
- return r
656
- rescue CircularReferenceCommand
657
- if @expanded_command.size>1
658
- raise
659
- else
660
- @scanner.pos = pos
661
- raise ParseError.new("Circular reference.")
662
- end
663
- rescue ParseError => e
664
- if @expanded_command.size>1
665
- raise
666
- else
667
- @scanner.pos = pos
668
- raise ParseError.new(%[Error in macro(#{e.message} "#{e.rest.strip}").])
669
- end
670
- ensure
671
- @expanded_command.pop
672
- end
673
- elsif @commands.key?(com)
674
- m = @commands[com]
675
- m = com unless m
676
- return __send__("cmd_#{m.to_s}")
677
- end
678
- parse_symbol_command(com)
679
- end
680
-
681
- def parse_mathfont(font)
682
- f = @font
683
- @font = font
684
- begin
685
- push_container(Row.new){|r| r << parse_any}
686
- ensure
687
- @font = f
688
- end
689
- end
690
-
691
- def parse_group
692
- font = @font
693
- begin
694
- g = @group_begins[@scanner[1]]
695
- g = @scanner[1] unless g
696
- __send__("grp_#{g.to_s}")
697
- ensure
698
- @font = font
699
- end
700
- end
701
- end
702
-
703
- module BuiltinCommands
704
- OVERS = {'hat'=>'circ', 'breve'=>'smile', 'grave'=>'grave',
705
- 'acute'=>'acute', 'dot'=>'sdot', 'ddot'=>'nldr', 'dddot'=>'mldr', 'tilde'=>'tilde',
706
- 'bar'=>'macr', 'vec'=>'rightarrow', 'check'=>'vee', 'widehat'=>'circ',
707
- 'overline'=>'macr', 'widetilde'=>'tilde', 'overbrace'=>'OverBrace'}
708
- UNDERS = {'underbrace'=>'UnderBrace', 'underline'=>'macr'}
709
-
710
- def initialize
711
- add_commands("\\"=>:backslash)
712
- add_commands("entity", "stackrel", "frac", "sqrt", "mbox")
713
- add_multi_command(:hat_etc, *OVERS.keys)
714
- add_multi_command(:underbrace_etc, *UNDERS.keys)
715
- add_multi_command(:quad_etc, " ", "quad", "qquad", ",", ":", ";", "!")
716
- add_multi_command(:it_etc, "it", "rm", "bf")
717
- add_multi_command(:mathit_etc, "mathit", "mathrm", "mathbf", "bm", "mathbb", "mathscr", "mathfrak")
718
- add_sym_cmd(Builtin::Symbol::MAP)
719
- add_delimiter(Builtin::Symbol::DELIMITERS)
720
-
721
- super
722
- end
723
-
724
- def cmd_backslash
725
- @ds ? nil : XMLElement.new("br", "xmlns"=>"http://www.w3.org/1999/xhtml")
726
- end
727
-
728
- def cmd_hat_etc
729
- com = @scanner[1]
730
- Over.new(parse_any, Operator.new << entitize(OVERS[com]))
731
- end
732
-
733
- def cmd_underbrace_etc
734
- com = @scanner[1]
735
- Under.new(parse_any, Operator.new << entitize(UNDERS[com]))
736
- end
737
-
738
- def cmd_entity
739
- param = @scanner.scan_block ? @scanner[1] : @scanner.scan(/./)
740
- raise ParseError.new("Need parameter.") unless param
741
- unless @unsecure_entity || @entities[param]
742
- param =@scanner.matched[/\A\{#{RE::SPACE}*(.*\})\z/, 1] if @scanner.matched=~RE::BLOCK
743
- @scanner.pos = @scanner.pos-(param.size)
744
- raise ParseError.new("Unregistered entity.")
745
- end
746
- Operator.new << entitize(param)
747
- end
748
-
749
- def cmd_stackrel
750
- o = parse_any; b = parse_any
751
- Over.new(b, o)
752
- end
753
-
754
- def cmd_quad_etc
755
- case @scanner[1]
756
- when ' '
757
- Space.new("1em")
758
- when 'quad'
759
- Space.new("1em")
760
- when 'qquad'
761
- Space.new("2em")
762
- when ','
763
- Space.new("0.167em")
764
- when ':'
765
- Space.new("0.222em")
766
- when ';'
767
- Space.new("0.278em")
768
- when '!'
769
- Space.new("-0.167em")
770
- end
771
- end
772
-
773
- def cmd_it_etc
774
- case @scanner[1]
775
- when 'it'
776
- @font = Font::NORMAL
777
- when 'rm'
778
- @font = Font::ROMAN
779
- when 'bf'
780
- @font = Font::BOLD
781
- end
782
- nil
783
- end
784
-
785
- def cmd_mathit_etc
786
- case @scanner[1]
787
- when 'mathit'
788
- parse_mathfont(Font::NORMAL)
789
- when 'mathrm'
790
- parse_mathfont(Font::ROMAN)
791
- when 'mathbf'
792
- parse_mathfont(Font::BOLD)
793
- when 'bm'
794
- parse_mathfont(Font::BOLD_ITALIC)
795
- when 'mathbb'
796
- parse_mathfont(Font::BLACKBOLD)
797
- when 'mathscr'
798
- parse_mathfont(Font::SCRIPT)
799
- when 'mathfrak'
800
- parse_mathfont(Font::FRAKTUR)
801
- end
802
- end
803
-
804
- def cmd_frac
805
- n = parse_any; d = parse_any
806
- Frac.new(n, d)
807
- end
808
-
809
- def cmd_sqrt
810
- if @scanner.scan_option
811
- i = parse_into(@scanner[1], Array.new)
812
- i = i.size==1 ? i[0] : (Row.new << i)
813
- b = parse_any
814
- Root.new(i, b)
815
- else
816
- Sqrt.new << parse_any
817
- end
818
- end
819
-
820
- def cmd_mbox
821
- @scanner.scan_any
822
- Text.new << (@scanner.matched =~ RE::BLOCK ? @scanner[1] : @scanner.matched)
823
- end
824
- end
825
-
826
- module BuiltinGroups
827
- class CircularReferenceEnvironment < StandardError; end
828
-
829
- def initialize
830
- add_group("begin", "end")
831
- add_group("left", "right", :left_etc)
832
- add_group("bigg", "bigg", :left_etc)
833
- @environments = Hash.new
834
-
835
- super
836
- end
837
-
838
- def add_environment(*a)
839
- @environments = Hash.new unless @environments
840
- if a.size==1 && Hash===a[0]
841
- @environments.merge!(hash)
842
- else
843
- a.each{|i| @environments[i] = false}
844
- end
845
- end
846
-
847
- def grp_begin
848
- matched = @scanner.matched
849
- begin_pos = @scanner.pos-matched.size
850
- en = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
851
- raise ParseError.new('Environment name not exist.') unless en
852
-
853
- macro = @macro.environments(en)
854
- if macro
855
- begin
856
- flg = @expanded_environment.include?(en)
857
- @expanded_environment.push(en)
858
- raise CircularReferenceEnvironment if flg
859
-
860
- pos = @scanner.pos
861
- option = (macro.option && @scanner.scan_option) ? @scanner[1] : nil
862
- params = Array.new
863
- (1..macro.num).each do
864
- params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
865
- raise ParseError.new("Need more parameter.") unless params.last
866
- end
867
- body = ""
868
- grpnest = 0
869
- until @scanner.peek_command=="end" && grpnest==0
870
- if @scanner.eos?
871
- @scanner.pos = pos
872
- raise ParseError.new('Matching \end not exist.')
873
- end
874
- com = @scanner.peek_command
875
- grpnest += 1 if @group_begins.has_key?(com)
876
- grpnest -=1 if @group_ends.has_key?(com) && @group_begins[com]
877
- raise ParseError.new("Syntax error.") if grpnest<0
878
-
879
- body << @scanner.scan_any(true)
880
- end
881
- @scanner.scan_command
882
- raise ParseError.new("Environment mismatched.", @scanner.matched) unless en==(@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
883
- begin
884
- return parse_into(@macro.expand_environment(en, body, params, option), Array.new)
885
- rescue CircularReferenceEnvironment
886
- if @expanded_environment.size>1
887
- raise
888
- else
889
- @scanner.pos = begin_pos
890
- raise ParseError.new("Circular reference.")
891
- end
892
- rescue ParseError => e
893
- if @expanded_environment.size>1
894
- raise
895
- else
896
- @scanner.pos = begin_pos
897
- raise ParseError.new(%[Error in macro(#{e.message} "#{e.rest.strip}").])
898
- end
899
- end
900
- ensure
901
- @expanded_environment.pop
902
- end
903
- end
904
-
905
- raise ParseError.new("Undefined environment.") unless @environments.has_key?(en)
906
- e = @environments[en]
907
- e = en unless e # default method name
908
- __send__("env_#{e.to_s}")
909
- end
910
-
911
- def grp_left_etc
912
- right =
913
- case @scanner[1]
914
- when "left"
915
- "right"
916
- when "bigg"
917
- "bigg"
918
- end
919
-
920
- f = Fenced.new
921
- p = @scanner.pos
922
- o = @scanner.scan_any
923
- raise ParseError.new('Need brace here.') unless o && (o=~RE::BRACES || @delimiters.include?(o[RE::COMMANDS, 1]))
924
- f.open = (o=~RE::BRACES ? o : parse_symbol_command(o[RE::COMMANDS, 1], true))
925
- f << push_container(Row.new) do |r|
926
- until @scanner.peek_command==right
927
- if @scanner.eos?
928
- @scanner.pos = p
929
- raise ParseError.new('Brace not closed.')
930
- end
931
- r << parse_to_element(true)
932
- end
933
- end
934
- @scanner.scan_command # skip right
935
- c = @scanner.scan_any
936
- raise ParseError.new('Need brace here.') unless c=~RE::BRACES || @delimiters.include?(c[RE::COMMANDS, 1])
937
- f.close = (c=~RE::BRACES ? c : parse_symbol_command(c[RE::COMMANDS, 1], true))
938
- f
939
- end
940
- end
941
-
942
- module BuiltinEnvironments
943
- def initialize
944
- add_environment("array", "matrix")
945
-
946
- super
947
- end
948
-
949
- def env_array
950
- layout = @scanner.scan_block ? @scanner.matched : @scanner.scan(/./)
951
- l = Scanner.new(layout=~RE::BLOCK ? layout[RE::BLOCK, 1] : layout)
952
- t = Table.new
953
- aligns = Array.new
954
- vlines = Array.new
955
- vlined = l.check(/\|/)
956
- columned = false
957
- until l.eos?
958
- c = l.scan_any
959
- raise ParseError.new("Syntax error.", layout[/\A.*(#{Regexp.escape(c+l.rest)}.*\z)/m, 1]) unless c=~/[clr\|@]/
960
-
961
- if c=='|'
962
- aligns << Align::CENTER if vlined
963
- vlines << Line::SOLID
964
- vlined = true
965
- columned = false
966
- else
967
- vlines << Line::NONE if columned
968
- vlined = false
969
- columned = true
970
- case c
971
- when 'l'
972
- aligns << Align::LEFT
973
- when 'c'
974
- aligns << Align::CENTER
975
- when 'r'
976
- aligns << Align::RIGHT
977
- when '@'
978
- aligns << Align::CENTER
979
- l.scan_any
980
- end
981
- end
982
- end
983
- t.aligns = aligns
984
- t.vlines = vlines
985
-
986
- layout = layout[RE::BLOCK, 1] if layout=~RE::BLOCK
987
- raise ParseError.new('Need parameter here.') if layout==""
988
-
989
- hlines = Array.new
990
- row_parsed = false
991
- hlined = false
992
- until @scanner.peek_command=="end"
993
- raise ParseError.new('Matching \end not exist.') if @scanner.eos?
994
- if @scanner.peek_command=="hline"
995
- @scanner.scan_command
996
- t << Tr.new unless row_parsed
997
- hlines << Line::SOLID
998
- row_parsed = false
999
- hlined = true
1000
- else
1001
- hlines << Line::NONE if row_parsed
1002
- t << env_array_row(l.string)
1003
- @scanner.scan(RE::WBSLASH)
1004
- row_parsed = true
1005
- hlined = false
1006
- end
1007
- end
1008
- t.hlines = hlines
1009
-
1010
- if hlined
1011
- tr = Tr.new
1012
- (0..vlines.size).each {|i| tr << Td.new}
1013
- t << tr
1014
- end
1015
-
1016
- @scanner.scan_command
1017
- raise ParseError.new("Environment mismatched.") unless @scanner.check_block && @scanner[1]=="array"
1018
- @scanner.scan_block
1019
- t
1020
- end
1021
-
1022
- def env_array_row(layout)
1023
- l = Scanner.new(layout)
1024
- r = Tr.new
1025
- first_column = true
1026
- vlined = l.check(/\|/)
1027
- until l.eos?
1028
- c = l.scan(/./)
1029
- if c=='|'
1030
- r << Td.new if vlined
1031
- vlined = true
1032
- next
1033
- else
1034
- vlined = false
1035
- case c
1036
- when 'r', 'l', 'c'
1037
- when '@'
1038
- r << parse_into(l.scan_any, Td.new)
1039
- next
1040
- end
1041
- if first_column
1042
- first_column = false
1043
- else
1044
- raise ParseError.new("Need more column.", @scanner.matched.to_s) unless @scanner.scan(/&/)
1045
- end
1046
- r << push_container(Td.new) do |td|
1047
- td << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1048
- end
1049
- end
1050
- end
1051
- r << Td.new if vlined
1052
- raise ParseError.new("Too many column.") if @scanner.check(/&/)
1053
- r
1054
- end
1055
-
1056
- def env_matrix
1057
- t = Table.new
1058
- hlines = Array.new
1059
- hlined = false
1060
- row_parsed = false
1061
- until @scanner.peek_command=="end"
1062
- raise ParseError.new('Matching \end not exist.') if @scanner.eos?
1063
- if @scanner.peek_command=="hline"
1064
- @scanner.scan_command
1065
- t << Tr.new unless row_parsed
1066
- hlines << Line::SOLID
1067
- row_parsed = false
1068
- hlined = true
1069
- else
1070
- hlines << Line::NONE if row_parsed
1071
- t << (r = Tr.new)
1072
- r << (td=Td.new)
1073
- until @scanner.check(RE::WBSLASH) || @scanner.peek_command=="end" || @scanner.eos?
1074
- push_container(td) do |e|
1075
- e << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1076
- end
1077
- r << (td=Td.new) if @scanner.scan(/&/)
1078
- end
1079
- @scanner.scan(RE::WBSLASH)
1080
- row_parsed = true
1081
- hlined = false
1082
- end
1083
- end
1084
- t.hlines = hlines
1085
-
1086
- t << Tr.new if hlined
1087
-
1088
- raise ParseError.new("Need \\end{array}.") unless @scanner.peek_command=="end"
1089
- @scanner.scan_command
1090
- raise ParseError.new("Environment mismatched.") unless @scanner.check_block && @scanner[1]=="matrix"
1091
- @scanner.scan_block
1092
- t
1093
- end
1094
-
1095
- def env_matrix_row
1096
- r = Tr.new
1097
- until @scanner.check(RE::WBSLASH) || @scanner.peek_command=="end"
1098
- r << push_container(Td.new) do |td|
1099
- td << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1100
- end
1101
- end
1102
- end
1103
- end
1104
- end
4
+ module LaTeX
5
+ MBEC = /\\.|[^\\]/m
6
+
7
+ module RE
8
+ SPACE = /(?:\s|%.*$)/
9
+ NUMERICS = /(?:\.\d+)|(?:\d+(\.\d+)?)/
10
+ OPERATORS = %r{[,.+\-*=/()\[\]<>"|;:!]}
11
+ ALPHABETS = /[a-zA-Z]/
12
+ BLOCK = /\A\{(.*?)\}\z/m
13
+ OPTION = /\A\[(.*)\]\z/m
14
+ COMMANDS = /\\([a-zA-Z]+|[^a-zA-Z])/
15
+ WBSLASH = /\\\\/
16
+ BRACES = /\A([.|\[\]()<>])\z/
17
+ end
18
+
19
+ module Font
20
+ NORMAL = 0
21
+ BOLD = 1
22
+ BLACKBOLD = 2
23
+ SCRIPT = 3
24
+ FRAKTUR = 4
25
+ ROMAN = 5
26
+ BOLD_ITALIC = 6
27
+ end
28
+
29
+ class BlockNotClosed < StandardError; end
30
+ class NotEnvironment < StandardError; end
31
+ class EnvironmentNotEnd < StandardError; end
32
+ class NeedParameter < StandardError; end
33
+ class EndMismatchToBegin < StandardError; end
34
+ class OptionNotClosed < StandardError; end
35
+
36
+ class Scanner < StringScanner
37
+ def done
38
+ string[0, pos]
39
+ end
40
+
41
+ def scan_space
42
+ _scan(/#{RE::SPACE}+/)
43
+ end
44
+
45
+ def skip_space_and(check_mode)
46
+ opos = pos
47
+ scan_space
48
+ r = yield
49
+ self.pos = opos if check_mode || !r
50
+ r
51
+ end
52
+
53
+ unless instance_methods.include?('_eos?')
54
+ alias _eos? eos?
55
+ alias _check check
56
+ alias _scan scan
57
+ end
58
+
59
+ def check(re)
60
+ skip_space_and(true) { _check(re) }
61
+ end
62
+
63
+ def scan(re)
64
+ skip_space_and(false) { _scan(re) }
65
+ end
66
+
67
+ def eos?
68
+ _eos? || _check(/#{RE::SPACE}+\z/)
69
+ end
70
+
71
+ def check_command
72
+ check(RE::COMMANDS)
73
+ end
74
+
75
+ def scan_command
76
+ scan(RE::COMMANDS)
77
+ end
78
+
79
+ def peek_command
80
+ check_command ? self[1] : nil
81
+ end
82
+
83
+ def check_block
84
+ skip_space_and(true) { scan_block }
85
+ end
86
+
87
+ def scan_block
88
+ return nil unless scan(/\{/)
89
+
90
+ block = '{'
91
+ bpos = pos - 1
92
+ nest = 1
93
+ while _scan(/(#{MBEC}*?)([{}])/)
94
+ block << matched
95
+ case self[2]
96
+ when '{'
97
+ nest += 1
98
+ when '}'
99
+ nest -= 1
100
+ break if nest == 0
101
+ end
102
+ end
103
+ if nest > 0
104
+ self.pos = bpos
105
+ raise BlockNotClosed
106
+ end
107
+ self.pos = bpos
108
+ _scan(/\A\{(#{Regexp.escape(block[RE::BLOCK, 1].to_s)})\}/)
109
+ end
110
+
111
+ def check_any(remain_space = false)
112
+ skip_space_and(true) { scan_any(remain_space) }
113
+ end
114
+
115
+ def scan_any(remain_space = false)
116
+ p = pos
117
+ scan_space
118
+ r = remain_space ? matched.to_s : ''
119
+ case
120
+ when s = scan_block
121
+ when s = scan_command
122
+ else
123
+ unless _scan(/./) || remain_space
124
+ self.pos = p
125
+ return nil
126
+ end
127
+ s = matched.to_s
128
+ end
129
+ r + s
130
+ end
131
+
132
+ def scan_option
133
+ return nil unless scan(/\[/)
134
+
135
+ opt = '['
136
+ p = pos - 1
137
+ until (s = scan_any(true)) =~ /\A#{RE::SPACE}*\]\z/
138
+ opt << s
139
+ if eos?
140
+ self.pos = p
141
+ raise OptionNotClosed
142
+ end
143
+ end
144
+ opt << s
145
+ self.pos = p
146
+ _scan(/\A\[(#{Regexp.escape(opt[RE::OPTION, 1].to_s)})\]/)
147
+ end
148
+
149
+ def check_option
150
+ skip_space_and(true) { scan_option }
151
+ end
152
+ end
153
+
154
+ class ParseError < StandardError
155
+ attr_accessor :rest, :done
156
+
157
+ def initialize(message, rest = '', done = '')
158
+ @done = done
159
+ @rest = rest
160
+ super(message)
161
+ end
162
+
163
+ def inspect
164
+ "#{message} : '#{@done}' / '#{@rest}'\n" + backtrace[0..5].join("\n")
165
+ end
166
+ end
167
+
168
+ class Macro
169
+ class Command
170
+ attr_reader :num, :body, :option
171
+
172
+ def initialize(n, b, o)
173
+ @num = n
174
+ @body = b
175
+ @option = o
176
+ end
177
+ end
178
+
179
+ class Environment
180
+ attr_reader :num, :beginning, :ending, :option
181
+
182
+ def initialize(n, b, e, o)
183
+ @num = n
184
+ @beginning = b
185
+ @ending = e
186
+ @option = o
187
+ end
188
+ end
189
+
190
+ def initialize
191
+ @commands = {}
192
+ @environments = {}
193
+ end
194
+
195
+ def parse_error(message, rest = '', whole = nil)
196
+ rest = whole[/\A.*?(#{Regexp.escape(rest)}.*\z)/, 1] if whole
197
+ rest << @scanner.rest
198
+ done = @scanner.string[0, @scanner.string.size - rest.size]
199
+ ParseError.new(message, rest, done)
200
+ end
201
+
202
+ def parse(src)
203
+ @scanner = Scanner.new(src)
204
+ until @scanner.eos?
205
+ unless @scanner.scan_command
206
+ @scanner.scan_space
207
+ raise parse_error('Syntax error.')
208
+ end
209
+ case @scanner[1]
210
+ when 'newcommand'
211
+ parse_newcommand
212
+ when 'newenvironment'
213
+ parse_newenvironment
214
+ else
215
+ raise parse_error('Syntax error.', @scanner.matched)
216
+ end
217
+ end
218
+ rescue BlockNotClosed => e
219
+ raise parse_error('Block not closed.')
220
+ rescue OptionNotClosed => e
221
+ raise parse_error('Option not closed.')
222
+ end
223
+
224
+ def scan_num_of_parameter
225
+ if @scanner.scan_option
226
+ unless @scanner[1] =~ /\A#{RE::SPACE}*\d+#{RE::SPACE}*\z/
227
+ raise parse_error('Need positive number.',
228
+ @scanner[1] + ']')
229
+ end
230
+
231
+ @scanner[1].to_i
232
+ else
233
+ 0
234
+ end
235
+ end
236
+
237
+ def check_parameter_numbers(src, opt, whole)
238
+ s = Scanner.new(src)
239
+ until s.eos?
240
+ case
241
+ when s.scan(/#{MBEC}*?\#(\d+|.)/)
242
+ raise parse_error('Need positive number.') unless s[1] =~ /\d+/
243
+ raise parse_error("Parameter \# too large.", s[1] + s.rest, whole) if s[1].to_i > opt
244
+ else
245
+ return nil
246
+ end
247
+ end
248
+ end
249
+
250
+ def parse_newcommand
251
+ if @scanner.scan_block
252
+ s = Scanner.new(@scanner[1])
253
+ raise parse_error('Need newcommand.', s.rest + '}') unless s.scan_command
254
+
255
+ com = s[1]
256
+ raise parse_error('Syntax error.', s.rest + '}') unless s.eos?
257
+ elsif @scanner.scan_command
258
+ s = Scanner.new(@scanner[1])
259
+ com = s.scan_command
260
+ else
261
+ raise parse_error('Need newcommand.')
262
+ end
263
+
264
+ optnum = scan_num_of_parameter
265
+ opt = @scanner.scan_option ? @scanner[1] : nil
266
+
267
+ body = if @scanner.scan_block
268
+ @scanner[1]
269
+ elsif @scanner.scan_command
270
+ @scanner.matched
271
+ else
272
+ @scanner.scan(/./)
273
+ end
274
+
275
+ raise parse_error('Need parameter.') unless body
276
+
277
+ check_parameter_numbers(body, optnum, @scanner.matched)
278
+
279
+ optnum -= 1 if opt
280
+ @commands[com] = Command.new(optnum, body, opt)
281
+ end
282
+
283
+ def parse_newenvironment
284
+ if @scanner.scan_block
285
+ env = @scanner[1]
286
+ elsif @scanner.scan_command
287
+ raise ParseError
288
+ elsif @scanner.scan(/./)
289
+ env = @scanner.matched
290
+ end
291
+ raise parse_error('Syntax error.', env[/\A.*?(\\.*\z)/, 1], @scanner.matched) if env =~ /\\/
292
+
293
+ optnum = scan_num_of_parameter
294
+ opt = @scanner.scan_option ? @scanner[1] : nil
295
+
296
+ b = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
297
+ raise parse_error('Need begin block.') unless b
298
+
299
+ check_parameter_numbers(b, optnum, @scanner.matched)
300
+ e = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
301
+ raise parse_error('Need end block.') unless e
302
+
303
+ check_parameter_numbers(e, optnum, @scanner.matched)
304
+
305
+ optnum -= 1 if opt
306
+ @environments[env] = Environment.new(optnum, b, e, opt)
307
+ end
308
+
309
+ def commands(com)
310
+ @commands[com]
311
+ end
312
+
313
+ def expand_command(com, params, opt = nil)
314
+ return nil unless @commands.has_key?(com)
315
+
316
+ c = @commands[com]
317
+ opt = c.option if c.option && !opt
318
+ params.unshift(opt) if c.option
319
+ raise ParseError, 'Need more parameter.' if params.size < c.num
320
+
321
+ c.body.gsub(/(#{MBEC}*?)\#(\d+)/) do
322
+ $1.to_s << params[$2.to_i - 1]
323
+ end
324
+ end
325
+
326
+ def environments(env)
327
+ @environments[env]
328
+ end
329
+
330
+ def expand_environment(env, body, params, opt = nil)
331
+ return nil unless @environments.has_key?(env)
332
+
333
+ e = @environments[env]
334
+ opt = e.option if e.option && !opt
335
+ params.unshift(opt) if e.option
336
+ raise ParseError, 'Need more parameter.' if params.size < e.num
337
+
338
+ bg = e.beginning.gsub(/(#{MBEC}*?)\#(\d+)/) do
339
+ $1.to_s << params[$2.to_i - 1]
340
+ end
341
+
342
+ en = e.ending.gsub(/(#{MBEC}*?)\#(\d+)/) do
343
+ $1.to_s << params[$2.to_i - 1]
344
+ end
345
+
346
+ " #{bg} #{body} #{en} "
347
+ end
348
+ end
349
+
350
+ module BuiltinCommands; end
351
+ module BuiltinGroups; end
352
+ module BuiltinEnvironments; end
353
+
354
+ class Parser
355
+ class CircularReferenceCommand < StandardError; end
356
+
357
+ include LaTeX
358
+
359
+ include BuiltinEnvironments
360
+ include BuiltinGroups
361
+ include BuiltinCommands
362
+
363
+ BUILTIN_MACRO = <<~'EOS'
364
+ \newenvironment{smallmatrix}{\begin{matrix}}{\end{matrix}}
365
+ \newenvironment{pmatrix}{\left(\begin{matrix}}{\end{matrix}\right)}
366
+ \newenvironment{bmatrix}{\left[\begin{matrix}}{\end{matrix}\right]}
367
+ \newenvironment{Bmatrix}{\left\{\begin{matrix}}{\end{matrix}\right\}}
368
+ \newenvironment{vmatrix}{\left|\begin{matrix}}{\end{matrix}\right|}
369
+ \newenvironment{Vmatrix}{\left\|\begin{matrix}}{\end{matrix}\right\|}
370
+ EOS
371
+
372
+ attr_accessor :unsecure_entity
373
+ attr_reader :macro, :symbol_table
374
+
375
+ def initialize(opt = {})
376
+ @unsecure_entity = false
377
+ @entities = {}
378
+ @commands = {}
379
+ @symbols = {}
380
+ @delimiters = []
381
+ @group_begins = {}
382
+ @group_ends = {}
383
+ @macro = Macro.new
384
+ @macro.parse(BUILTIN_MACRO)
385
+ @expanded_command = []
386
+ @expanded_environment = []
387
+ @symbol_table = opt[:symbol] || MathML::Symbol::Default
388
+ @symbol_table = MathML::Symbol::MAP[@symbol_table] if @symbol_table.is_a?(::Symbol)
389
+
390
+ super()
391
+ end
392
+
393
+ def add_entity(list)
394
+ list.each do |i|
395
+ @entities[i] = true
396
+ end
397
+ end
398
+
399
+ def parse(src, displaystyle = false)
400
+ @ds = displaystyle
401
+ begin
402
+ parse_into(src, Math.new(@ds), Font::NORMAL)
403
+ rescue ParseError => e
404
+ e.done = src[0...(src.size - e.rest.size)]
405
+ raise
406
+ end
407
+ end
408
+
409
+ def push_container(container, scanner = @scanner, font = @font)
410
+ data = [@container, @scanner, @font]
411
+ @container = container
412
+ @scanner = scanner
413
+ @font = font
414
+ begin
415
+ yield container
416
+ container
417
+ ensure
418
+ @container, @scanner, @font = data
419
+ end
420
+ end
421
+
422
+ def add_plugin(plugin)
423
+ extend(plugin)
424
+ end
425
+
426
+ def add_commands(*a)
427
+ if a.size == 1 && a[0].is_a?(Hash)
428
+ @commands.merge!(a[0])
429
+ else
430
+ a.each { |i| @commands[i] = false }
431
+ end
432
+ end
433
+
434
+ def add_multi_command(m, *a)
435
+ a.each { |i| @commands[i] = m }
436
+ end
437
+
438
+ def add_sym_cmd(hash)
439
+ @symbols.merge!(hash)
440
+ end
441
+
442
+ def add_delimiter(list)
443
+ @delimiters.concat(list)
444
+ end
445
+
446
+ def add_group(begin_name, end_name, method = nil)
447
+ @group_begins[begin_name] = method
448
+ @group_ends[end_name] = begin_name
449
+ end
450
+
451
+ private
452
+
453
+ def parse_into(src, parent, font = nil)
454
+ orig = [@scanner, @container, @font, @ds]
455
+ @scanner = Scanner.new(src)
456
+ @container = parent
457
+ @font = font if font
458
+ begin
459
+ @container << parse_to_element(true) until @scanner.eos?
460
+ @container
461
+ rescue BlockNotClosed => e
462
+ raise ParseError.new('Block not closed.', @scanner.rest)
463
+ rescue NotEnvironment => e
464
+ raise ParseError.new('Not environment.', @scanner.rest)
465
+ rescue EnvironmentNotEnd => e
466
+ raise ParseError.new('Environment not end.', @scanner.rest)
467
+ rescue OptionNotClosed => e
468
+ raise ParseError.new('Option not closed.', @scanner.rest)
469
+ rescue ParseError => e
470
+ e.rest += @scanner.rest.to_s
471
+ raise
472
+ ensure
473
+ @scanner, @container, @font, @ds = orig
474
+ end
475
+ end
476
+
477
+ def parse_any(message = 'Syntax error.')
478
+ raise ParseError, message unless @scanner.scan_any
479
+
480
+ s = @scanner
481
+ @scanner = Scanner.new(@scanner.matched)
482
+ begin
483
+ parse_to_element
484
+ ensure
485
+ @scanner = s
486
+ end
487
+ end
488
+
489
+ def parse_to_element(whole_group = false)
490
+ if whole_group && @group_begins.has_key?(@scanner.peek_command)
491
+ @scanner.scan_command
492
+ parse_group
493
+ elsif @scanner.scan(RE::NUMERICS)
494
+ parse_num
495
+ elsif @scanner.scan(RE::ALPHABETS)
496
+ parse_char
497
+ elsif @scanner.scan(RE::OPERATORS)
498
+ parse_operator
499
+ elsif @scanner.scan_block
500
+ parse_block
501
+ elsif @scanner.scan(/_/)
502
+ parse_sub
503
+ elsif @scanner.scan(/'+|\^/)
504
+ parse_sup
505
+ elsif @scanner.scan(/~/)
506
+ Space.new('1em')
507
+ elsif @scanner.scan_command
508
+ parse_command
509
+ else
510
+ raise ParseError, 'Syntax error.'
511
+ end
512
+ end
513
+
514
+ def parse_num
515
+ n = Number.new
516
+ n.extend(Variant).variant = Variant::BOLD if @font == Font::BOLD
517
+ n << @scanner.matched
518
+ end
519
+
520
+ def parse_char
521
+ c = @scanner.matched
522
+ i = Identifier.new
523
+ case @font
524
+ when Font::ROMAN
525
+ i.extend(Variant).variant = Variant::NORMAL
526
+ when Font::BOLD
527
+ i.extend(Variant).variant = Variant::BOLD
528
+ when Font::BOLD_ITALIC
529
+ i.extend(Variant).variant = Variant::BOLD_ITALIC
530
+ when Font::BLACKBOLD
531
+ c = symbol_table.convert("#{c}opf")
532
+ when Font::SCRIPT
533
+ c = symbol_table.convert("#{c}scr")
534
+ when Font::FRAKTUR
535
+ c = symbol_table.convert("#{c}fr")
536
+ end
537
+ i << c
538
+ end
539
+
540
+ def parse_operator
541
+ o = @scanner.matched
542
+ Operator.new.tap { |op| op[:stretchy] = 'false' } << o
543
+ end
544
+
545
+ def parse_block
546
+ os = @scanner
547
+ @scanner = Scanner.new(@scanner[1])
548
+ begin
549
+ push_container(Row.new) do |r|
550
+ r << parse_to_element(true) until @scanner.eos?
551
+ end
552
+ rescue ParseError => e
553
+ e.rest << '}'
554
+ raise
555
+ ensure
556
+ @scanner = os
557
+ end
558
+ end
559
+
560
+ def parse_sub
561
+ e = @container.pop
562
+ e ||= None.new
563
+ e = SubSup.new(@ds && e.display_style, e) unless e.is_a?(SubSup)
564
+ raise ParseError.new('Double subscript.', '_') if e.sub
565
+
566
+ e.sub = parse_any('Subscript not exist.')
567
+ e
568
+ end
569
+
570
+ def parse_sup
571
+ e = @container.pop
572
+ e ||= None.new
573
+ e = SubSup.new(@ds && e.display_style, e) unless e.is_a?(SubSup)
574
+ raise ParseError.new('Double superscript.', @scanner[0]) if e.sup
575
+
576
+ if /'+/=~@scanner[0]
577
+ prime = Operator.new
578
+ @scanner[0].size.times do
579
+ prime << symbol_table.convert('prime')
580
+ end
581
+ unless @scanner.scan(/\^/)
582
+ e.sup = prime
583
+ return e
584
+ end
585
+ end
586
+ sup = parse_any('Superscript not exist.')
587
+
588
+ if prime
589
+ unless sup.is_a?(Row)
590
+ r = Row.new
591
+ r << sup
592
+ sup = r
593
+ end
594
+ sup.contents.insert(0, prime)
595
+ end
596
+
597
+ e.sup = sup
598
+ e
599
+ end
600
+
601
+ def entitize(str)
602
+ MathML.pcstring(str.sub(/^(.*)$/) { "&#{$1};" }, true)
603
+ end
604
+
605
+ def parse_symbol_command(com, plain = false)
606
+ unless @symbols.include?(com)
607
+ @scanner.pos = @scanner.pos - (com.size + 1)
608
+ raise ParseError, 'Undefined command.'
609
+ end
610
+ data = @symbols[com]
611
+ return nil unless data
612
+
613
+ data, s = data
614
+ su = data[0]
615
+ el = data[1]
616
+ el ||= :o
617
+ s ||= com.dup.to_sym
618
+ s = com if s.is_a?(String) && s.length == 0
619
+
620
+ case el
621
+ when :I
622
+ el = Identifier.new
623
+ when :i
624
+ el = Identifier.new
625
+ el.extend(Variant).variant = Variant::NORMAL unless s.is_a?(String) && s.length > 1
626
+ when :o
627
+ el = Operator.new
628
+ el[:stretchy] = 'false'
629
+ when :n
630
+ el = Number.new
631
+ else
632
+ raise ParseError, 'Inner data broken.'
633
+ end
634
+
635
+ case s
636
+ when Integer
637
+ s = MathML.pcstring("&\#x#{s.to_s(16)};", true)
638
+ when ::Symbol
639
+ s = symbol_table.convert(s)
640
+ else
641
+ MathML.pcstring(s, true)
642
+ end
643
+
644
+ return s if plain
645
+
646
+ el << s
647
+ el.as_display_style if su == :u
648
+ el
649
+ end
650
+
651
+ def parse_command
652
+ com = @scanner[1]
653
+ matched = @scanner.matched
654
+ pos = @scanner.pos - matched.size
655
+ macro = @macro.commands(com)
656
+ if macro
657
+ begin
658
+ flg = @expanded_command.include?(com)
659
+ @expanded_command.push(com)
660
+ raise CircularReferenceCommand if flg
661
+
662
+ option = macro.option && @scanner.scan_option ? @scanner[1] : nil
663
+ params = []
664
+ (1..macro.num).each do
665
+ params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
666
+ raise ParseError, 'Need more parameter.' unless params.last
667
+ end
668
+ r = parse_into(@macro.expand_command(com, params, option), [])
669
+ return r
670
+ rescue CircularReferenceCommand
671
+ if @expanded_command.size > 1
672
+ raise
673
+ else
674
+ @scanner.pos = pos
675
+ raise ParseError, 'Circular reference.'
676
+ end
677
+ rescue ParseError => e
678
+ if @expanded_command.size > 1
679
+ raise
680
+ else
681
+ @scanner.pos = pos
682
+ raise ParseError, %[Error in macro(#{e.message} "#{e.rest.strip}").]
683
+ end
684
+ ensure
685
+ @expanded_command.pop
686
+ end
687
+ elsif @commands.key?(com)
688
+ m = @commands[com]
689
+ m ||= com
690
+ return __send__("cmd_#{m}")
691
+ end
692
+ parse_symbol_command(com)
693
+ end
694
+
695
+ def parse_mathfont(font)
696
+ f = @font
697
+ @font = font
698
+ begin
699
+ push_container(Row.new) { |r| r << parse_any }
700
+ ensure
701
+ @font = f
702
+ end
703
+ end
704
+
705
+ def parse_group
706
+ font = @font
707
+ begin
708
+ g = @group_begins[@scanner[1]]
709
+ g ||= @scanner[1]
710
+ __send__("grp_#{g}")
711
+ ensure
712
+ @font = font
713
+ end
714
+ end
715
+ end
716
+
717
+ module BuiltinCommands
718
+ OVERS = { 'hat' => 'circ', 'breve' => 'smile', 'grave' => 'grave',
719
+ 'acute' => 'acute', 'dot' => 'sdot', 'ddot' => 'nldr', 'dddot' => 'mldr', 'tilde' => 'tilde',
720
+ 'bar' => 'macr', 'vec' => 'rightarrow', 'check' => 'vee', 'widehat' => 'circ',
721
+ 'overline' => 'macr', 'widetilde' => 'tilde', 'overbrace' => 'OverBrace' }
722
+ UNDERS = { 'underbrace' => 'UnderBrace', 'underline' => 'macr' }
723
+
724
+ def initialize
725
+ add_commands('\\' => :backslash)
726
+ add_commands('entity', 'stackrel', 'frac', 'sqrt', 'mbox')
727
+ add_multi_command(:hat_etc, *OVERS.keys)
728
+ add_multi_command(:underbrace_etc, *UNDERS.keys)
729
+ add_multi_command(:quad_etc, ' ', 'quad', 'qquad', ',', ':', ';', '!')
730
+ add_multi_command(:it_etc, 'it', 'rm', 'bf')
731
+ add_multi_command(:mathit_etc, 'mathit', 'mathrm', 'mathbf', 'bm', 'mathbb', 'mathscr', 'mathfrak')
732
+ add_sym_cmd(Builtin::Symbol::MAP)
733
+ add_delimiter(Builtin::Symbol::DELIMITERS)
734
+
735
+ super
736
+ end
737
+
738
+ def cmd_backslash
739
+ @ds ? nil : XMLElement.new('br', 'xmlns' => 'http://www.w3.org/1999/xhtml')
740
+ end
741
+
742
+ def cmd_hat_etc
743
+ com = @scanner[1]
744
+ Over.new(parse_any, Operator.new << entitize(OVERS[com]))
745
+ end
746
+
747
+ def cmd_underbrace_etc
748
+ com = @scanner[1]
749
+ Under.new(parse_any, Operator.new << entitize(UNDERS[com]))
750
+ end
751
+
752
+ def cmd_entity
753
+ param = @scanner.scan_block ? @scanner[1] : @scanner.scan(/./)
754
+ raise ParseError, 'Need parameter.' unless param
755
+
756
+ unless @unsecure_entity || @entities[param]
757
+ param = @scanner.matched[/\A\{#{RE::SPACE}*(.*\})\z/, 1] if @scanner.matched =~ RE::BLOCK
758
+ @scanner.pos = @scanner.pos - param.size
759
+ raise ParseError, 'Unregistered entity.'
760
+ end
761
+ Operator.new << entitize(param)
762
+ end
763
+
764
+ def cmd_stackrel
765
+ o = parse_any
766
+ b = parse_any
767
+ Over.new(b, o)
768
+ end
769
+
770
+ def cmd_quad_etc
771
+ case @scanner[1]
772
+ when ' '
773
+ Space.new('1em')
774
+ when 'quad'
775
+ Space.new('1em')
776
+ when 'qquad'
777
+ Space.new('2em')
778
+ when ','
779
+ Space.new('0.167em')
780
+ when ':'
781
+ Space.new('0.222em')
782
+ when ';'
783
+ Space.new('0.278em')
784
+ when '!'
785
+ Space.new('-0.167em')
786
+ end
787
+ end
788
+
789
+ def cmd_it_etc
790
+ case @scanner[1]
791
+ when 'it'
792
+ @font = Font::NORMAL
793
+ when 'rm'
794
+ @font = Font::ROMAN
795
+ when 'bf'
796
+ @font = Font::BOLD
797
+ end
798
+ nil
799
+ end
800
+
801
+ def cmd_mathit_etc
802
+ case @scanner[1]
803
+ when 'mathit'
804
+ parse_mathfont(Font::NORMAL)
805
+ when 'mathrm'
806
+ parse_mathfont(Font::ROMAN)
807
+ when 'mathbf'
808
+ parse_mathfont(Font::BOLD)
809
+ when 'bm'
810
+ parse_mathfont(Font::BOLD_ITALIC)
811
+ when 'mathbb'
812
+ parse_mathfont(Font::BLACKBOLD)
813
+ when 'mathscr'
814
+ parse_mathfont(Font::SCRIPT)
815
+ when 'mathfrak'
816
+ parse_mathfont(Font::FRAKTUR)
817
+ end
818
+ end
819
+
820
+ def cmd_frac
821
+ n = parse_any
822
+ d = parse_any
823
+ Frac.new(n, d)
824
+ end
825
+
826
+ def cmd_sqrt
827
+ if @scanner.scan_option
828
+ i = parse_into(@scanner[1], [])
829
+ i = i.size == 1 ? i[0] : (Row.new << i)
830
+ b = parse_any
831
+ Root.new(i, b)
832
+ else
833
+ Sqrt.new << parse_any
834
+ end
835
+ end
836
+
837
+ def cmd_mbox
838
+ @scanner.scan_any
839
+ Text.new << (@scanner.matched =~ RE::BLOCK ? @scanner[1] : @scanner.matched)
840
+ end
841
+ end
842
+
843
+ module BuiltinGroups
844
+ class CircularReferenceEnvironment < StandardError; end
845
+
846
+ def initialize
847
+ add_group('begin', 'end')
848
+ add_group('left', 'right', :left_etc)
849
+ add_group('bigg', 'bigg', :left_etc)
850
+ @environments = {}
851
+
852
+ super
853
+ end
854
+
855
+ def add_environment(*a)
856
+ @environments ||= {}
857
+ if a.size == 1 && a[0].is_a?(Hash)
858
+ @environments.merge!(hash)
859
+ else
860
+ a.each { |i| @environments[i] = false }
861
+ end
862
+ end
863
+
864
+ def grp_begin
865
+ matched = @scanner.matched
866
+ begin_pos = @scanner.pos - matched.size
867
+ en = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
868
+ raise ParseError, 'Environment name not exist.' unless en
869
+
870
+ macro = @macro.environments(en)
871
+ if macro
872
+ begin
873
+ flg = @expanded_environment.include?(en)
874
+ @expanded_environment.push(en)
875
+ raise CircularReferenceEnvironment if flg
876
+
877
+ pos = @scanner.pos
878
+ option = macro.option && @scanner.scan_option ? @scanner[1] : nil
879
+ params = []
880
+ (1..macro.num).each do
881
+ params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
882
+ raise ParseError, 'Need more parameter.' unless params.last
883
+ end
884
+ body = ''
885
+ grpnest = 0
886
+ until @scanner.peek_command == 'end' && grpnest == 0
887
+ if @scanner.eos?
888
+ @scanner.pos = pos
889
+ raise ParseError, 'Matching \end not exist.'
890
+ end
891
+ com = @scanner.peek_command
892
+ grpnest += 1 if @group_begins.has_key?(com)
893
+ grpnest -= 1 if @group_ends.has_key?(com) && @group_begins[com]
894
+ raise ParseError, 'Syntax error.' if grpnest < 0
895
+
896
+ body << @scanner.scan_any(true)
897
+ end
898
+ @scanner.scan_command
899
+ unless en == (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
900
+ raise ParseError.new('Environment mismatched.',
901
+ @scanner.matched)
902
+ end
903
+
904
+ begin
905
+ return parse_into(@macro.expand_environment(en, body, params, option), [])
906
+ rescue CircularReferenceEnvironment
907
+ if @expanded_environment.size > 1
908
+ raise
909
+ else
910
+ @scanner.pos = begin_pos
911
+ raise ParseError, 'Circular reference.'
912
+ end
913
+ rescue ParseError => e
914
+ if @expanded_environment.size > 1
915
+ raise
916
+ else
917
+ @scanner.pos = begin_pos
918
+ raise ParseError, %[Error in macro(#{e.message} "#{e.rest.strip}").]
919
+ end
920
+ end
921
+ ensure
922
+ @expanded_environment.pop
923
+ end
924
+ end
925
+
926
+ raise ParseError, 'Undefined environment.' unless @environments.has_key?(en)
927
+
928
+ e = @environments[en]
929
+ e ||= en # default method name
930
+ __send__("env_#{e}")
931
+ end
932
+
933
+ def grp_left_etc
934
+ right =
935
+ case @scanner[1]
936
+ when 'left'
937
+ 'right'
938
+ when 'bigg'
939
+ 'bigg'
940
+ end
941
+
942
+ f = Fenced.new
943
+ p = @scanner.pos
944
+ o = @scanner.scan_any
945
+ raise ParseError, 'Need brace here.' unless o && (o =~ RE::BRACES || @delimiters.include?(o[RE::COMMANDS, 1]))
946
+
947
+ f.open = (o =~ RE::BRACES ? o : parse_symbol_command(o[RE::COMMANDS, 1], true))
948
+ f << push_container(Row.new) do |r|
949
+ until @scanner.peek_command == right
950
+ if @scanner.eos?
951
+ @scanner.pos = p
952
+ raise ParseError, 'Brace not closed.'
953
+ end
954
+ r << parse_to_element(true)
955
+ end
956
+ end
957
+ @scanner.scan_command # skip right
958
+ c = @scanner.scan_any
959
+ raise ParseError, 'Need brace here.' unless c =~ RE::BRACES || @delimiters.include?(c[RE::COMMANDS, 1])
960
+
961
+ f.close = (c =~ RE::BRACES ? c : parse_symbol_command(c[RE::COMMANDS, 1], true))
962
+ f
963
+ end
964
+ end
965
+
966
+ module BuiltinEnvironments
967
+ def initialize
968
+ add_environment('array', 'matrix')
969
+
970
+ super
971
+ end
972
+
973
+ def env_array
974
+ layout = @scanner.scan_block ? @scanner.matched : @scanner.scan(/./)
975
+ l = Scanner.new(layout =~ RE::BLOCK ? layout[RE::BLOCK, 1] : layout)
976
+ t = Table.new
977
+ aligns = []
978
+ vlines = []
979
+ vlined = l.check(/\|/)
980
+ columned = false
981
+ until l.eos?
982
+ c = l.scan_any
983
+ unless c =~ /[clr|@]/
984
+ raise ParseError.new(
985
+ 'Syntax error.',
986
+ layout[/\A.*(#{Regexp.escape(c + l.rest)}.*\z)/m, 1]
987
+ )
988
+ end
989
+
990
+ if c == '|'
991
+ aligns << Align::CENTER if vlined
992
+ vlines << Line::SOLID
993
+ vlined = true
994
+ columned = false
995
+ else
996
+ vlines << Line::NONE if columned
997
+ vlined = false
998
+ columned = true
999
+ case c
1000
+ when 'l'
1001
+ aligns << Align::LEFT
1002
+ when 'c'
1003
+ aligns << Align::CENTER
1004
+ when 'r'
1005
+ aligns << Align::RIGHT
1006
+ when '@'
1007
+ aligns << Align::CENTER
1008
+ l.scan_any
1009
+ end
1010
+ end
1011
+ end
1012
+ t.aligns = aligns
1013
+ t.vlines = vlines
1014
+
1015
+ layout = layout[RE::BLOCK, 1] if layout =~ RE::BLOCK
1016
+ raise ParseError, 'Need parameter here.' if layout == ''
1017
+
1018
+ hlines = []
1019
+ row_parsed = false
1020
+ hlined = false
1021
+ until @scanner.peek_command == 'end'
1022
+ raise ParseError, 'Matching \end not exist.' if @scanner.eos?
1023
+
1024
+ if @scanner.peek_command == 'hline'
1025
+ @scanner.scan_command
1026
+ t << Tr.new unless row_parsed
1027
+ hlines << Line::SOLID
1028
+ row_parsed = false
1029
+ hlined = true
1030
+ else
1031
+ hlines << Line::NONE if row_parsed
1032
+ t << env_array_row(l.string)
1033
+ @scanner.scan(RE::WBSLASH)
1034
+ row_parsed = true
1035
+ hlined = false
1036
+ end
1037
+ end
1038
+ t.hlines = hlines
1039
+
1040
+ if hlined
1041
+ tr = Tr.new
1042
+ (0..vlines.size).each { |_i| tr << Td.new }
1043
+ t << tr
1044
+ end
1045
+
1046
+ @scanner.scan_command
1047
+ raise ParseError, 'Environment mismatched.' unless @scanner.check_block && @scanner[1] == 'array'
1048
+
1049
+ @scanner.scan_block
1050
+ t
1051
+ end
1052
+
1053
+ def env_array_row(layout)
1054
+ l = Scanner.new(layout)
1055
+ r = Tr.new
1056
+ first_column = true
1057
+ vlined = l.check(/\|/)
1058
+ until l.eos?
1059
+ c = l.scan(/./)
1060
+ if c == '|'
1061
+ r << Td.new if vlined
1062
+ vlined = true
1063
+ next
1064
+ else
1065
+ vlined = false
1066
+ case c
1067
+ when 'r', 'l', 'c'
1068
+ when '@'
1069
+ r << parse_into(l.scan_any, Td.new)
1070
+ next
1071
+ end
1072
+ if first_column
1073
+ first_column = false
1074
+ else
1075
+ raise ParseError.new('Need more column.', @scanner.matched.to_s) unless @scanner.scan(/&/)
1076
+ end
1077
+ r << push_container(Td.new) do |td|
1078
+ td << parse_to_element(true) until @scanner.peek_command == 'end' ||
1079
+ @scanner.check(/(&|\\\\)/) || @scanner.eos?
1080
+ end
1081
+ end
1082
+ end
1083
+ r << Td.new if vlined
1084
+ raise ParseError, 'Too many column.' if @scanner.check(/&/)
1085
+
1086
+ r
1087
+ end
1088
+
1089
+ def env_matrix
1090
+ t = Table.new
1091
+ hlines = []
1092
+ hlined = false
1093
+ row_parsed = false
1094
+ until @scanner.peek_command == 'end'
1095
+ raise ParseError, 'Matching \end not exist.' if @scanner.eos?
1096
+
1097
+ if @scanner.peek_command == 'hline'
1098
+ @scanner.scan_command
1099
+ t << Tr.new unless row_parsed
1100
+ hlines << Line::SOLID
1101
+ row_parsed = false
1102
+ hlined = true
1103
+ else
1104
+ hlines << Line::NONE if row_parsed
1105
+ t << (r = Tr.new)
1106
+ r << (td = Td.new)
1107
+ until @scanner.check(RE::WBSLASH) || @scanner.peek_command == 'end' || @scanner.eos?
1108
+ push_container(td) do |e|
1109
+ e << parse_to_element(true) until @scanner.peek_command == 'end' ||
1110
+ @scanner.check(/(&|\\\\)/) || @scanner.eos?
1111
+ end
1112
+ r << (td = Td.new) if @scanner.scan(/&/)
1113
+ end
1114
+ @scanner.scan(RE::WBSLASH)
1115
+ row_parsed = true
1116
+ hlined = false
1117
+ end
1118
+ end
1119
+ t.hlines = hlines
1120
+
1121
+ t << Tr.new if hlined
1122
+
1123
+ raise ParseError, 'Need \\end{array}.' unless @scanner.peek_command == 'end'
1124
+
1125
+ @scanner.scan_command
1126
+ raise ParseError, 'Environment mismatched.' unless @scanner.check_block && @scanner[1] == 'matrix'
1127
+
1128
+ @scanner.scan_block
1129
+ t
1130
+ end
1131
+
1132
+ def env_matrix_row
1133
+ r = Tr.new
1134
+ until @scanner.check(RE::WBSLASH) || @scanner.peek_command == 'end'
1135
+ r << push_container(Td.new) do |td|
1136
+ td << parse_to_element(true) until @scanner.peek_command == 'end' ||
1137
+ @scanner.check(/(&|\\\\)/) || @scanner.eos?
1138
+ end
1139
+ end
1140
+ end
1141
+ end
1142
+ end
1105
1143
  end