math_ml 0.9

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