math_ml 0.14 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/math_ml/latex.rb CHANGED
@@ -1,1106 +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.tap{|op| op[:stretchy]="false"} << 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
- el[:stretchy] = "false"
618
- when :n
619
- el = Number.new
620
- else
621
- raise ParseError.new("Inner data broken.")
622
- end
623
-
624
- case s
625
- when Fixnum
626
- s = MathML.pcstring("&\#x#{s.to_s(16)};", true)
627
- when ::Symbol
628
- s = symbol_table.convert(s)
629
- else
630
- MathML.pcstring(s, true)
631
- end
632
-
633
- return s if plain
634
- el << s
635
- el.as_display_style if su==:u
636
- el
637
- end
638
-
639
- def parse_command
640
- com = @scanner[1]
641
- matched = @scanner.matched
642
- pos = @scanner.pos-matched.size
643
- macro = @macro.commands(com)
644
- if macro
645
- begin
646
- flg = @expanded_command.include?(com)
647
- @expanded_command.push(com)
648
- raise CircularReferenceCommand if flg
649
- option = (macro.option && @scanner.scan_option) ? @scanner[1] : nil
650
- params = Array.new
651
- (1..macro.num).each do
652
- params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
653
- raise ParseError.new("Need more parameter.") unless params.last
654
- end
655
- r = parse_into(@macro.expand_command(com, params, option), Array.new)
656
- return r
657
- rescue CircularReferenceCommand
658
- if @expanded_command.size>1
659
- raise
660
- else
661
- @scanner.pos = pos
662
- raise ParseError.new("Circular reference.")
663
- end
664
- rescue ParseError => e
665
- if @expanded_command.size>1
666
- raise
667
- else
668
- @scanner.pos = pos
669
- raise ParseError.new(%[Error in macro(#{e.message} "#{e.rest.strip}").])
670
- end
671
- ensure
672
- @expanded_command.pop
673
- end
674
- elsif @commands.key?(com)
675
- m = @commands[com]
676
- m = com unless m
677
- return __send__("cmd_#{m.to_s}")
678
- end
679
- parse_symbol_command(com)
680
- end
681
-
682
- def parse_mathfont(font)
683
- f = @font
684
- @font = font
685
- begin
686
- push_container(Row.new){|r| r << parse_any}
687
- ensure
688
- @font = f
689
- end
690
- end
691
-
692
- def parse_group
693
- font = @font
694
- begin
695
- g = @group_begins[@scanner[1]]
696
- g = @scanner[1] unless g
697
- __send__("grp_#{g.to_s}")
698
- ensure
699
- @font = font
700
- end
701
- end
702
- end
703
-
704
- module BuiltinCommands
705
- OVERS = {'hat'=>'circ', 'breve'=>'smile', 'grave'=>'grave',
706
- 'acute'=>'acute', 'dot'=>'sdot', 'ddot'=>'nldr', 'dddot'=>'mldr', 'tilde'=>'tilde',
707
- 'bar'=>'macr', 'vec'=>'rightarrow', 'check'=>'vee', 'widehat'=>'circ',
708
- 'overline'=>'macr', 'widetilde'=>'tilde', 'overbrace'=>'OverBrace'}
709
- UNDERS = {'underbrace'=>'UnderBrace', 'underline'=>'macr'}
710
-
711
- def initialize
712
- add_commands("\\"=>:backslash)
713
- add_commands("entity", "stackrel", "frac", "sqrt", "mbox")
714
- add_multi_command(:hat_etc, *OVERS.keys)
715
- add_multi_command(:underbrace_etc, *UNDERS.keys)
716
- add_multi_command(:quad_etc, " ", "quad", "qquad", ",", ":", ";", "!")
717
- add_multi_command(:it_etc, "it", "rm", "bf")
718
- add_multi_command(:mathit_etc, "mathit", "mathrm", "mathbf", "bm", "mathbb", "mathscr", "mathfrak")
719
- add_sym_cmd(Builtin::Symbol::MAP)
720
- add_delimiter(Builtin::Symbol::DELIMITERS)
721
-
722
- super
723
- end
724
-
725
- def cmd_backslash
726
- @ds ? nil : XMLElement.new("br", "xmlns"=>"http://www.w3.org/1999/xhtml")
727
- end
728
-
729
- def cmd_hat_etc
730
- com = @scanner[1]
731
- Over.new(parse_any, Operator.new << entitize(OVERS[com]))
732
- end
733
-
734
- def cmd_underbrace_etc
735
- com = @scanner[1]
736
- Under.new(parse_any, Operator.new << entitize(UNDERS[com]))
737
- end
738
-
739
- def cmd_entity
740
- param = @scanner.scan_block ? @scanner[1] : @scanner.scan(/./)
741
- raise ParseError.new("Need parameter.") unless param
742
- unless @unsecure_entity || @entities[param]
743
- param =@scanner.matched[/\A\{#{RE::SPACE}*(.*\})\z/, 1] if @scanner.matched=~RE::BLOCK
744
- @scanner.pos = @scanner.pos-(param.size)
745
- raise ParseError.new("Unregistered entity.")
746
- end
747
- Operator.new << entitize(param)
748
- end
749
-
750
- def cmd_stackrel
751
- o = parse_any; b = parse_any
752
- Over.new(b, o)
753
- end
754
-
755
- def cmd_quad_etc
756
- case @scanner[1]
757
- when ' '
758
- Space.new("1em")
759
- when 'quad'
760
- Space.new("1em")
761
- when 'qquad'
762
- Space.new("2em")
763
- when ','
764
- Space.new("0.167em")
765
- when ':'
766
- Space.new("0.222em")
767
- when ';'
768
- Space.new("0.278em")
769
- when '!'
770
- Space.new("-0.167em")
771
- end
772
- end
773
-
774
- def cmd_it_etc
775
- case @scanner[1]
776
- when 'it'
777
- @font = Font::NORMAL
778
- when 'rm'
779
- @font = Font::ROMAN
780
- when 'bf'
781
- @font = Font::BOLD
782
- end
783
- nil
784
- end
785
-
786
- def cmd_mathit_etc
787
- case @scanner[1]
788
- when 'mathit'
789
- parse_mathfont(Font::NORMAL)
790
- when 'mathrm'
791
- parse_mathfont(Font::ROMAN)
792
- when 'mathbf'
793
- parse_mathfont(Font::BOLD)
794
- when 'bm'
795
- parse_mathfont(Font::BOLD_ITALIC)
796
- when 'mathbb'
797
- parse_mathfont(Font::BLACKBOLD)
798
- when 'mathscr'
799
- parse_mathfont(Font::SCRIPT)
800
- when 'mathfrak'
801
- parse_mathfont(Font::FRAKTUR)
802
- end
803
- end
804
-
805
- def cmd_frac
806
- n = parse_any; d = parse_any
807
- Frac.new(n, d)
808
- end
809
-
810
- def cmd_sqrt
811
- if @scanner.scan_option
812
- i = parse_into(@scanner[1], Array.new)
813
- i = i.size==1 ? i[0] : (Row.new << i)
814
- b = parse_any
815
- Root.new(i, b)
816
- else
817
- Sqrt.new << parse_any
818
- end
819
- end
820
-
821
- def cmd_mbox
822
- @scanner.scan_any
823
- Text.new << (@scanner.matched =~ RE::BLOCK ? @scanner[1] : @scanner.matched)
824
- end
825
- end
826
-
827
- module BuiltinGroups
828
- class CircularReferenceEnvironment < StandardError; end
829
-
830
- def initialize
831
- add_group("begin", "end")
832
- add_group("left", "right", :left_etc)
833
- add_group("bigg", "bigg", :left_etc)
834
- @environments = Hash.new
835
-
836
- super
837
- end
838
-
839
- def add_environment(*a)
840
- @environments = Hash.new unless @environments
841
- if a.size==1 && Hash===a[0]
842
- @environments.merge!(hash)
843
- else
844
- a.each{|i| @environments[i] = false}
845
- end
846
- end
847
-
848
- def grp_begin
849
- matched = @scanner.matched
850
- begin_pos = @scanner.pos-matched.size
851
- en = @scanner.scan_block ? @scanner[1] : @scanner.scan_any
852
- raise ParseError.new('Environment name not exist.') unless en
853
-
854
- macro = @macro.environments(en)
855
- if macro
856
- begin
857
- flg = @expanded_environment.include?(en)
858
- @expanded_environment.push(en)
859
- raise CircularReferenceEnvironment if flg
860
-
861
- pos = @scanner.pos
862
- option = (macro.option && @scanner.scan_option) ? @scanner[1] : nil
863
- params = Array.new
864
- (1..macro.num).each do
865
- params << (@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
866
- raise ParseError.new("Need more parameter.") unless params.last
867
- end
868
- body = ""
869
- grpnest = 0
870
- until @scanner.peek_command=="end" && grpnest==0
871
- if @scanner.eos?
872
- @scanner.pos = pos
873
- raise ParseError.new('Matching \end not exist.')
874
- end
875
- com = @scanner.peek_command
876
- grpnest += 1 if @group_begins.has_key?(com)
877
- grpnest -=1 if @group_ends.has_key?(com) && @group_begins[com]
878
- raise ParseError.new("Syntax error.") if grpnest<0
879
-
880
- body << @scanner.scan_any(true)
881
- end
882
- @scanner.scan_command
883
- raise ParseError.new("Environment mismatched.", @scanner.matched) unless en==(@scanner.scan_block ? @scanner[1] : @scanner.scan_any)
884
- begin
885
- return parse_into(@macro.expand_environment(en, body, params, option), Array.new)
886
- rescue CircularReferenceEnvironment
887
- if @expanded_environment.size>1
888
- raise
889
- else
890
- @scanner.pos = begin_pos
891
- raise ParseError.new("Circular reference.")
892
- end
893
- rescue ParseError => e
894
- if @expanded_environment.size>1
895
- raise
896
- else
897
- @scanner.pos = begin_pos
898
- raise ParseError.new(%[Error in macro(#{e.message} "#{e.rest.strip}").])
899
- end
900
- end
901
- ensure
902
- @expanded_environment.pop
903
- end
904
- end
905
-
906
- raise ParseError.new("Undefined environment.") unless @environments.has_key?(en)
907
- e = @environments[en]
908
- e = en unless e # default method name
909
- __send__("env_#{e.to_s}")
910
- end
911
-
912
- def grp_left_etc
913
- right =
914
- case @scanner[1]
915
- when "left"
916
- "right"
917
- when "bigg"
918
- "bigg"
919
- end
920
-
921
- f = Fenced.new
922
- p = @scanner.pos
923
- o = @scanner.scan_any
924
- raise ParseError.new('Need brace here.') unless o && (o=~RE::BRACES || @delimiters.include?(o[RE::COMMANDS, 1]))
925
- f.open = (o=~RE::BRACES ? o : parse_symbol_command(o[RE::COMMANDS, 1], true))
926
- f << push_container(Row.new) do |r|
927
- until @scanner.peek_command==right
928
- if @scanner.eos?
929
- @scanner.pos = p
930
- raise ParseError.new('Brace not closed.')
931
- end
932
- r << parse_to_element(true)
933
- end
934
- end
935
- @scanner.scan_command # skip right
936
- c = @scanner.scan_any
937
- raise ParseError.new('Need brace here.') unless c=~RE::BRACES || @delimiters.include?(c[RE::COMMANDS, 1])
938
- f.close = (c=~RE::BRACES ? c : parse_symbol_command(c[RE::COMMANDS, 1], true))
939
- f
940
- end
941
- end
942
-
943
- module BuiltinEnvironments
944
- def initialize
945
- add_environment("array", "matrix")
946
-
947
- super
948
- end
949
-
950
- def env_array
951
- layout = @scanner.scan_block ? @scanner.matched : @scanner.scan(/./)
952
- l = Scanner.new(layout=~RE::BLOCK ? layout[RE::BLOCK, 1] : layout)
953
- t = Table.new
954
- aligns = Array.new
955
- vlines = Array.new
956
- vlined = l.check(/\|/)
957
- columned = false
958
- until l.eos?
959
- c = l.scan_any
960
- raise ParseError.new("Syntax error.", layout[/\A.*(#{Regexp.escape(c+l.rest)}.*\z)/m, 1]) unless c=~/[clr\|@]/
961
-
962
- if c=='|'
963
- aligns << Align::CENTER if vlined
964
- vlines << Line::SOLID
965
- vlined = true
966
- columned = false
967
- else
968
- vlines << Line::NONE if columned
969
- vlined = false
970
- columned = true
971
- case c
972
- when 'l'
973
- aligns << Align::LEFT
974
- when 'c'
975
- aligns << Align::CENTER
976
- when 'r'
977
- aligns << Align::RIGHT
978
- when '@'
979
- aligns << Align::CENTER
980
- l.scan_any
981
- end
982
- end
983
- end
984
- t.aligns = aligns
985
- t.vlines = vlines
986
-
987
- layout = layout[RE::BLOCK, 1] if layout=~RE::BLOCK
988
- raise ParseError.new('Need parameter here.') if layout==""
989
-
990
- hlines = Array.new
991
- row_parsed = false
992
- hlined = false
993
- until @scanner.peek_command=="end"
994
- raise ParseError.new('Matching \end not exist.') if @scanner.eos?
995
- if @scanner.peek_command=="hline"
996
- @scanner.scan_command
997
- t << Tr.new unless row_parsed
998
- hlines << Line::SOLID
999
- row_parsed = false
1000
- hlined = true
1001
- else
1002
- hlines << Line::NONE if row_parsed
1003
- t << env_array_row(l.string)
1004
- @scanner.scan(RE::WBSLASH)
1005
- row_parsed = true
1006
- hlined = false
1007
- end
1008
- end
1009
- t.hlines = hlines
1010
-
1011
- if hlined
1012
- tr = Tr.new
1013
- (0..vlines.size).each {|i| tr << Td.new}
1014
- t << tr
1015
- end
1016
-
1017
- @scanner.scan_command
1018
- raise ParseError.new("Environment mismatched.") unless @scanner.check_block && @scanner[1]=="array"
1019
- @scanner.scan_block
1020
- t
1021
- end
1022
-
1023
- def env_array_row(layout)
1024
- l = Scanner.new(layout)
1025
- r = Tr.new
1026
- first_column = true
1027
- vlined = l.check(/\|/)
1028
- until l.eos?
1029
- c = l.scan(/./)
1030
- if c=='|'
1031
- r << Td.new if vlined
1032
- vlined = true
1033
- next
1034
- else
1035
- vlined = false
1036
- case c
1037
- when 'r', 'l', 'c'
1038
- when '@'
1039
- r << parse_into(l.scan_any, Td.new)
1040
- next
1041
- end
1042
- if first_column
1043
- first_column = false
1044
- else
1045
- raise ParseError.new("Need more column.", @scanner.matched.to_s) unless @scanner.scan(/&/)
1046
- end
1047
- r << push_container(Td.new) do |td|
1048
- td << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1049
- end
1050
- end
1051
- end
1052
- r << Td.new if vlined
1053
- raise ParseError.new("Too many column.") if @scanner.check(/&/)
1054
- r
1055
- end
1056
-
1057
- def env_matrix
1058
- t = Table.new
1059
- hlines = Array.new
1060
- hlined = false
1061
- row_parsed = false
1062
- until @scanner.peek_command=="end"
1063
- raise ParseError.new('Matching \end not exist.') if @scanner.eos?
1064
- if @scanner.peek_command=="hline"
1065
- @scanner.scan_command
1066
- t << Tr.new unless row_parsed
1067
- hlines << Line::SOLID
1068
- row_parsed = false
1069
- hlined = true
1070
- else
1071
- hlines << Line::NONE if row_parsed
1072
- t << (r = Tr.new)
1073
- r << (td=Td.new)
1074
- until @scanner.check(RE::WBSLASH) || @scanner.peek_command=="end" || @scanner.eos?
1075
- push_container(td) do |e|
1076
- e << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1077
- end
1078
- r << (td=Td.new) if @scanner.scan(/&/)
1079
- end
1080
- @scanner.scan(RE::WBSLASH)
1081
- row_parsed = true
1082
- hlined = false
1083
- end
1084
- end
1085
- t.hlines = hlines
1086
-
1087
- t << Tr.new if hlined
1088
-
1089
- raise ParseError.new("Need \\end{array}.") unless @scanner.peek_command=="end"
1090
- @scanner.scan_command
1091
- raise ParseError.new("Environment mismatched.") unless @scanner.check_block && @scanner[1]=="matrix"
1092
- @scanner.scan_block
1093
- t
1094
- end
1095
-
1096
- def env_matrix_row
1097
- r = Tr.new
1098
- until @scanner.check(RE::WBSLASH) || @scanner.peek_command=="end"
1099
- r << push_container(Td.new) do |td|
1100
- td << parse_to_element(true) until @scanner.peek_command=="end" || @scanner.check(/(&|\\\\)/) || @scanner.eos?
1101
- end
1102
- end
1103
- end
1104
- end
1105
- 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
1106
1143
  end