mathml 0.8.1 → 0.10.1

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,227 @@
1
+ module MathML
2
+ class Element < XMLElement
3
+ attr_reader :display_style
4
+
5
+ def as_display_style
6
+ @display_style = true
7
+ self
8
+ end
9
+ end
10
+
11
+ module Variant
12
+ NORMAL = "normal"
13
+ BOLD = "bold"
14
+ BOLD_ITALIC = "bold-italic"
15
+ def variant=(v)
16
+ self["mathvariant"] = v
17
+ end
18
+ end
19
+
20
+ module Align
21
+ CENTER = "center"
22
+ LEFT = "left"
23
+ RIGHT = "right"
24
+ end
25
+
26
+ module Line
27
+ SOLID = "solid"
28
+ NONE = "none"
29
+ end
30
+
31
+ class Math < XMLElement
32
+ def initialize(display_style)
33
+ super("math", "xmlns"=>"http://www.w3.org/1998/Math/MathML")
34
+ self[:display] = display_style ? "block" : "inline"
35
+ end
36
+ end
37
+
38
+ class Row < Element
39
+ def initialize
40
+ super("mrow")
41
+ end
42
+ end
43
+
44
+ class None < Element
45
+ def initialize
46
+ super("none")
47
+ end
48
+ end
49
+
50
+ class Space < Element
51
+ def initialize(width)
52
+ super("mspace", "width"=>width)
53
+ end
54
+ end
55
+
56
+ class Fenced < Element
57
+ attr_reader :open, :close
58
+
59
+ def initialize
60
+ super("mfenced")
61
+ end
62
+
63
+ def open=(o)
64
+ o = "" if o.to_s=="." || !o
65
+ o = "{" if o.to_s=="\\{"
66
+ self[:open] = MathML.pcstring(o, true)
67
+ end
68
+
69
+ def close=(c)
70
+ c = "" if c.to_s=="." || !c
71
+ c = "}" if c.to_s=="\\}"
72
+ self[:close] = MathML.pcstring(c, true)
73
+ end
74
+ end
75
+
76
+ class Frac < Element
77
+ def initialize(numerator, denominator)
78
+ super("mfrac")
79
+ self << numerator
80
+ self << denominator
81
+ end
82
+ end
83
+
84
+ class SubSup < Element
85
+ attr_reader :sub, :sup, :body
86
+
87
+ def initialize(display_style, body)
88
+ super("mrow")
89
+ as_display_style if display_style
90
+ @body = body
91
+ end
92
+
93
+ def update_name
94
+ if @sub || @sup
95
+ name = "m"
96
+ name << (@sub ? (@display_style ? "under" : "sub") : "")
97
+ name << (@sup ? (@display_style ? "over" : "sup") : "")
98
+ else
99
+ name = "mrow"
100
+ end
101
+ self.name = name
102
+ end
103
+ private :update_name
104
+
105
+ def update_contents
106
+ contents.clear
107
+ contents << @body
108
+ contents << @sub if @sub
109
+ contents << @sup if @sup
110
+ end
111
+ private :update_contents
112
+
113
+ def update
114
+ update_name
115
+ update_contents
116
+ end
117
+ private :update
118
+
119
+ def sub=(sub)
120
+ @sub = sub
121
+ update
122
+ end
123
+
124
+ def sup=(sup)
125
+ @sup = sup
126
+ update
127
+ end
128
+ end
129
+
130
+ class Over < Element
131
+ def initialize(base, over)
132
+ super("mover")
133
+ self << base << over
134
+ end
135
+ end
136
+
137
+ class Under < Element
138
+ def initialize(base, under)
139
+ super("munder")
140
+ self << base << under
141
+ end
142
+ end
143
+
144
+ class Number < Element
145
+ def initialize
146
+ super("mn")
147
+ end
148
+ end
149
+
150
+ class Identifier < Element
151
+ def initialize
152
+ super("mi")
153
+ end
154
+ end
155
+
156
+ class Operator < Element
157
+ def initialize
158
+ super("mo")
159
+ end
160
+ end
161
+
162
+ class Text < Element
163
+ def initialize
164
+ super("mtext")
165
+ end
166
+ end
167
+
168
+ class Sqrt < Element
169
+ def initialize
170
+ super("msqrt")
171
+ end
172
+ end
173
+
174
+ class Root < Element
175
+ def initialize(index, base)
176
+ super("mroot")
177
+ self << base
178
+ self << index
179
+ end
180
+ end
181
+
182
+ class Table < Element
183
+ def initialize
184
+ super("mtable")
185
+ end
186
+
187
+ def set_align_attribute(name, a, default)
188
+ if a.is_a?(Array) && a.size>0
189
+ value = ""
190
+ a.each do |i|
191
+ value << " "+i
192
+ end
193
+ if value =~ /^( #{default})*$/
194
+ @attributes.delete(name)
195
+ else
196
+ @attributes[name] = value.strip
197
+ end
198
+ else
199
+ @attributes.delete(name)
200
+ end
201
+ end
202
+
203
+ def aligns=(a)
204
+ set_align_attribute("columnalign", a, Align::CENTER)
205
+ end
206
+
207
+ def vlines=(a)
208
+ set_align_attribute("columnlines", a, Line::NONE)
209
+ end
210
+
211
+ def hlines=(a)
212
+ set_align_attribute("rowlines", a, Line::NONE)
213
+ end
214
+ end
215
+
216
+ class Tr < Element
217
+ def initialize
218
+ super("mtr")
219
+ end
220
+ end
221
+
222
+ class Td < Element
223
+ def initialize
224
+ super("mtd")
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,1105 @@
1
+ require "math_ml/latex/builtin"
2
+
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', '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, 'hat', 'breve', 'grave', 'acute', 'dot', 'ddot', 'tilde', 'bar', 'vec', 'check', 'widehat', 'overline', 'widetilde', 'overbrace')
714
+ add_multi_command(:underbrace_etc, 'underbrace', 'underline')
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
1105
+ end