zafu 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/History.txt +9 -0
- data/README.rdoc +56 -0
- data/Rakefile +56 -0
- data/lib/zafu/all.rb +17 -0
- data/lib/zafu/compiler.rb +7 -0
- data/lib/zafu/controller_methods.rb +49 -0
- data/lib/zafu/handler.rb +57 -0
- data/lib/zafu/info.rb +4 -0
- data/lib/zafu/markup.rb +186 -0
- data/lib/zafu/mock_helper.rb +42 -0
- data/lib/zafu/node_context.rb +96 -0
- data/lib/zafu/parser.rb +564 -0
- data/lib/zafu/parsing_rules.rb +257 -0
- data/lib/zafu/process/ajax.rb +90 -0
- data/lib/zafu/process/conditional.rb +45 -0
- data/lib/zafu/process/context.rb +45 -0
- data/lib/zafu/process/html.rb +168 -0
- data/lib/zafu/process/ruby_less.rb +145 -0
- data/lib/zafu/template.rb +25 -0
- data/lib/zafu/test_helper.rb +19 -0
- data/lib/zafu.rb +7 -0
- data/rails/init.rb +1 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/markup_test.rb +232 -0
- data/test/mock/params.rb +19 -0
- data/test/node_context_test.rb +190 -0
- data/test/ruby_less_test.rb +37 -0
- data/test/test_helper.rb +9 -0
- data/test/zafu_test.rb +57 -0
- data/zafu.gemspec +83 -0
- metadata +124 -0
data/lib/zafu/parser.rb
ADDED
@@ -0,0 +1,564 @@
|
|
1
|
+
module Zafu
|
2
|
+
|
3
|
+
def self.parser_with_rules(*modules)
|
4
|
+
parser = Class.new(Parser)
|
5
|
+
modules.flatten.each do |mod|
|
6
|
+
parser.send(:include, mod)
|
7
|
+
end
|
8
|
+
parser
|
9
|
+
end
|
10
|
+
|
11
|
+
class Parser
|
12
|
+
@@callbacks = {}
|
13
|
+
attr_accessor :text, :method, :pass, :options, :blocks, :ids, :defined_ids, :parent
|
14
|
+
# Method parameters "<r:show attr='name'/>" (params contains {'attr' => 'name'}).
|
15
|
+
attr_accessor :params
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def new_with_url(path, opts={})
|
19
|
+
helper = opts[:helper] || Zafu::MockHelper.new
|
20
|
+
text, fullpath, base_path = self.get_template_text(path, helper)
|
21
|
+
self.new(text, :helper => helper, :base_path => base_path, :included_history => [fullpath])
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieve the template text in the current folder or as an absolute path.
|
25
|
+
# This method is used when 'including' text
|
26
|
+
def get_template_text(path, helper, base_path=nil)
|
27
|
+
res = helper.send(:get_template_text, path, base_path)
|
28
|
+
return [parser_error("template '#{path}' not found", 'include'), nil, nil] unless res
|
29
|
+
text, fullpath, base_path = *res
|
30
|
+
return res
|
31
|
+
end
|
32
|
+
|
33
|
+
def parser_error(message, method)
|
34
|
+
"<span class='parser_error'><span class='method'>#{method}</span> #{message}</span>"
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_accessor :before_process_callbacks
|
38
|
+
|
39
|
+
def before_process_callbacks
|
40
|
+
@before_process_callbacks ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def before_process(*args)
|
44
|
+
self.before_process_callbacks += args
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(text, opts={})
|
49
|
+
@stack = []
|
50
|
+
@ok = true
|
51
|
+
@blocks = []
|
52
|
+
|
53
|
+
@options = {:mode=>:void, :method=>'void'}.merge(opts)
|
54
|
+
@params = @options.delete(:params) || {}
|
55
|
+
@method = @options.delete(:method)
|
56
|
+
@ids = @options[:ids] ||= {}
|
57
|
+
original_ids = @ids.dup
|
58
|
+
@defined_ids = {} # ids defined in this node or this node's sub blocks
|
59
|
+
mode = @options.delete(:mode)
|
60
|
+
@parent = @options.delete(:parent)
|
61
|
+
|
62
|
+
if opts[:sub]
|
63
|
+
@text = text
|
64
|
+
else
|
65
|
+
@text = before_parse(text)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
start(mode)
|
70
|
+
|
71
|
+
# set name
|
72
|
+
@name ||= @options[:name] || @params[:id]
|
73
|
+
@options[:ids][@name] = self if @name
|
74
|
+
|
75
|
+
unless opts[:sub]
|
76
|
+
@text = after_parse(@text)
|
77
|
+
end
|
78
|
+
@ids.keys.each do |k|
|
79
|
+
if original_ids[k] != @ids[k]
|
80
|
+
@defined_ids[k] = @ids[k]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
@ok
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_erb(context)
|
87
|
+
context[:helper] ||= @options[:helper]
|
88
|
+
process(context)
|
89
|
+
end
|
90
|
+
|
91
|
+
def start(mode)
|
92
|
+
enter(mode)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Hook called when replacing part of an included template with '<r:with part='main'>...</r:with>'
|
96
|
+
# This replaces the current object 'self' which is in the original included template, with the custom version 'obj'.
|
97
|
+
def replace_with(obj)
|
98
|
+
# keep @method (obj's method is always 'with')
|
99
|
+
@blocks = obj.blocks.empty? ? @blocks : obj.blocks
|
100
|
+
@params.merge!(obj.params)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Hook called when including a part "<r:include template='layout' part='title'/>"
|
104
|
+
def include_part(obj)
|
105
|
+
[obj]
|
106
|
+
end
|
107
|
+
|
108
|
+
def empty?
|
109
|
+
@blocks == [] && (@params == {} || @params == {:part => @params[:part]})
|
110
|
+
end
|
111
|
+
|
112
|
+
def process(context={})
|
113
|
+
if @name
|
114
|
+
# we pass the name as 'context' in the children tags
|
115
|
+
@context = context.merge(:name => @name)
|
116
|
+
else
|
117
|
+
@context = context
|
118
|
+
end
|
119
|
+
@result = ""
|
120
|
+
@out_post = ""
|
121
|
+
|
122
|
+
before_process
|
123
|
+
|
124
|
+
@pass = {} # used to pass information to the parent
|
125
|
+
res = nil
|
126
|
+
|
127
|
+
if respond_to?("r_#{@method}".to_sym)
|
128
|
+
res = do_method("r_#{@method}".to_sym)
|
129
|
+
else
|
130
|
+
res = do_method(:r_unknown)
|
131
|
+
end
|
132
|
+
|
133
|
+
# @text contains unparsed data (empty space)
|
134
|
+
after_process(res + @text)
|
135
|
+
end
|
136
|
+
|
137
|
+
def do_method(sym)
|
138
|
+
res = self.send(sym)
|
139
|
+
if res.kind_of?(String)
|
140
|
+
@result << res
|
141
|
+
elsif @result.blank?
|
142
|
+
@result << @method
|
143
|
+
end
|
144
|
+
@result + @out_post
|
145
|
+
end
|
146
|
+
|
147
|
+
def r_void
|
148
|
+
expand_with
|
149
|
+
end
|
150
|
+
|
151
|
+
def r_ignore
|
152
|
+
end
|
153
|
+
|
154
|
+
alias to_s r_void
|
155
|
+
|
156
|
+
def r_inspect
|
157
|
+
expand_with(:preflight=>true)
|
158
|
+
@blocks = []
|
159
|
+
@pass.merge!(@parts||{})
|
160
|
+
self.inspect
|
161
|
+
end
|
162
|
+
|
163
|
+
# basic rule to display errors
|
164
|
+
def r_unknown
|
165
|
+
sp = ""
|
166
|
+
@params.each do |k,v|
|
167
|
+
sp += " #{k}=#{v.inspect.gsub("'","TMPQUOTE").gsub('"',"'").gsub("TMPQUOTE",'"')}"
|
168
|
+
end
|
169
|
+
|
170
|
+
res = "<span class='parser_unknown'><r:#{@method}#{sp}"
|
171
|
+
inner = expand_with
|
172
|
+
if inner != ''
|
173
|
+
res + "></span>#{inner}<span class='parser_unknown'><r:/#{@method}></span>"
|
174
|
+
else
|
175
|
+
res + "/></span>"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Set context with variables (unsafe) from template.
|
180
|
+
def r_expand_with
|
181
|
+
hash = {}
|
182
|
+
@params.each do |k,v|
|
183
|
+
hash["exp_#{k}"] = v.inspect
|
184
|
+
end
|
185
|
+
expand_with(hash)
|
186
|
+
end
|
187
|
+
|
188
|
+
def before_process
|
189
|
+
self.class.before_process_callbacks.each do |callback|
|
190
|
+
self.send(callback)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def after_process(text)
|
195
|
+
text
|
196
|
+
end
|
197
|
+
|
198
|
+
def before_parse(text)
|
199
|
+
text
|
200
|
+
end
|
201
|
+
|
202
|
+
def after_parse(text)
|
203
|
+
text
|
204
|
+
end
|
205
|
+
|
206
|
+
def include_template
|
207
|
+
return parser_error("missing 'template' attribute", 'include') unless @params[:template]
|
208
|
+
if @options[:part] && @options[:part] == @params[:part]
|
209
|
+
# fetching only a part, do not open this element (same as original caller) as it is useless and will make us loop the loop.
|
210
|
+
@method = 'ignore'
|
211
|
+
enter(:void)
|
212
|
+
return
|
213
|
+
end
|
214
|
+
@method = 'void'
|
215
|
+
|
216
|
+
# fetch text
|
217
|
+
@options[:included_history] ||= []
|
218
|
+
|
219
|
+
included_text, absolute_url, base_path = self.class.get_template_text(@params[:template], @options[:helper], @options[:base_path])
|
220
|
+
|
221
|
+
if absolute_url
|
222
|
+
absolute_url += "::#{@params[:part].gsub('/','_')}" if @params[:part]
|
223
|
+
absolute_url += "??#{@options[:part].gsub('/','_')}" if @options[:part]
|
224
|
+
if @options[:included_history].include?(absolute_url)
|
225
|
+
included_text = parser_error("infinity loop: #{(@options[:included_history] + [absolute_url]).join(' --> ')}", 'include')
|
226
|
+
else
|
227
|
+
included_history = @options[:included_history] + [absolute_url]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
res = self.class.new(included_text, :helper => @options[:helper], :base_path => base_path, :included_history => included_history, :part => @params[:part]) # we set :part to avoid loop failure when doing self inclusion
|
231
|
+
|
232
|
+
if @params[:part]
|
233
|
+
if iblock = res.ids[@params[:part]]
|
234
|
+
included_blocks = include_part(iblock)
|
235
|
+
# get all ids from inside the included part:
|
236
|
+
@ids.merge! iblock.defined_ids
|
237
|
+
else
|
238
|
+
included_blocks = [parser_error("'#{@params[:part]}' not found in template '#{@params[:template]}'", 'include')]
|
239
|
+
end
|
240
|
+
else
|
241
|
+
included_blocks = res.blocks
|
242
|
+
@ids.merge! res.ids
|
243
|
+
end
|
244
|
+
|
245
|
+
enter(:void) # normal scan on content
|
246
|
+
# replace 'with'
|
247
|
+
|
248
|
+
not_found = []
|
249
|
+
@blocks.each do |b|
|
250
|
+
next if b.kind_of?(String) || b.method != 'with'
|
251
|
+
if target = res.ids[b.params[:part]]
|
252
|
+
if target.kind_of?(String)
|
253
|
+
# error
|
254
|
+
elsif b.empty?
|
255
|
+
target.method = 'ignore'
|
256
|
+
else
|
257
|
+
target.replace_with(b)
|
258
|
+
end
|
259
|
+
else
|
260
|
+
# part not found
|
261
|
+
not_found << parser_error("'#{b.params[:part]}' not found in template '#{@params[:template]}'", 'with')
|
262
|
+
end
|
263
|
+
end
|
264
|
+
@blocks = included_blocks + not_found
|
265
|
+
end
|
266
|
+
|
267
|
+
# Return a hash of all descendants. Find a specific descendant with descendant['form'] for example.
|
268
|
+
def all_descendants
|
269
|
+
@all_descendants ||= begin
|
270
|
+
d = {}
|
271
|
+
@blocks.each do |b|
|
272
|
+
next if b.kind_of?(String)
|
273
|
+
b.public_descendants.each do |k,v|
|
274
|
+
d[k] ||= []
|
275
|
+
d[k] += v
|
276
|
+
end
|
277
|
+
# latest is used first: use direct children before grandchildren.
|
278
|
+
d[b.method] ||= []
|
279
|
+
d[b.method] << b
|
280
|
+
end
|
281
|
+
d
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Find a direct child with +child[method]+.
|
286
|
+
def child
|
287
|
+
Hash[*@blocks.map do |b|
|
288
|
+
b.kind_of?(String) ? nil : [b.method, b]
|
289
|
+
end.compact.flatten]
|
290
|
+
end
|
291
|
+
|
292
|
+
def descendants(key)
|
293
|
+
all_descendants[key] || []
|
294
|
+
end
|
295
|
+
|
296
|
+
def ancestors
|
297
|
+
@ancestors ||= begin
|
298
|
+
if parent
|
299
|
+
parent.ancestors + [parent]
|
300
|
+
else
|
301
|
+
[]
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
alias public_descendants all_descendants
|
307
|
+
|
308
|
+
# Return the last defined parent for the given key.
|
309
|
+
def ancestor(key)
|
310
|
+
res = nil
|
311
|
+
ancestors.reverse_each do |a|
|
312
|
+
if key == a.method
|
313
|
+
res = a
|
314
|
+
break
|
315
|
+
end
|
316
|
+
end
|
317
|
+
res
|
318
|
+
end
|
319
|
+
|
320
|
+
# Return the last defined descendant for the given key.
|
321
|
+
def descendant(key)
|
322
|
+
descendants(key).last
|
323
|
+
end
|
324
|
+
|
325
|
+
# Return the root block (the one opened first).
|
326
|
+
def root
|
327
|
+
@root ||= parent ? parent.root : self
|
328
|
+
end
|
329
|
+
|
330
|
+
def success?
|
331
|
+
return @ok
|
332
|
+
end
|
333
|
+
|
334
|
+
def flush(str=@text)
|
335
|
+
return if str == ''
|
336
|
+
if @blocks.last.kind_of?(String)
|
337
|
+
@blocks[-1] << str
|
338
|
+
else
|
339
|
+
@blocks << str
|
340
|
+
end
|
341
|
+
@text = @text[str.length..-1]
|
342
|
+
end
|
343
|
+
|
344
|
+
# Build blocks
|
345
|
+
def store(obj)
|
346
|
+
if obj.kind_of?(String) && @blocks.last.kind_of?(String)
|
347
|
+
@blocks[-1] << obj
|
348
|
+
elsif obj != ''
|
349
|
+
@blocks << obj
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# Output ERB code during ast processing.
|
354
|
+
def out(str)
|
355
|
+
@result << str
|
356
|
+
# Avoid double entry when this is the last call in a render method.
|
357
|
+
nil
|
358
|
+
end
|
359
|
+
|
360
|
+
# Output ERB code that will be inserted after @result.
|
361
|
+
def out_post(str)
|
362
|
+
@out_post << str
|
363
|
+
# Avoid double entry when this is the last call in a render method.
|
364
|
+
nil
|
365
|
+
end
|
366
|
+
|
367
|
+
# Advance parser.
|
368
|
+
def eat(arg)
|
369
|
+
if arg.kind_of?(String)
|
370
|
+
len = arg.length
|
371
|
+
elsif arg.kind_of?(Fixnum)
|
372
|
+
len = arg
|
373
|
+
else
|
374
|
+
raise
|
375
|
+
end
|
376
|
+
@text = @text[len..-1]
|
377
|
+
end
|
378
|
+
|
379
|
+
def enter(mode)
|
380
|
+
@stack << mode
|
381
|
+
# puts "ENTER(#{@method},:#{mode}) [#{@text}] #{@zafu_tag_count.inspect}"
|
382
|
+
if mode == :void
|
383
|
+
sym = :scan
|
384
|
+
else
|
385
|
+
sym = "scan_#{mode}".to_sym
|
386
|
+
end
|
387
|
+
while (@text != '' && @stack[-1] == mode)
|
388
|
+
# puts "CONTINUE(#{@method},:#{mode}) [#{@text}] #{@zafu_tag_count.inspect}"
|
389
|
+
self.send(sym)
|
390
|
+
end
|
391
|
+
# puts "LEAVE(#{@method},:#{mode}) [#{@text}] #{@zafu_tag_count.inspect}"
|
392
|
+
end
|
393
|
+
|
394
|
+
def make(mode, opts={})
|
395
|
+
if opts[:text]
|
396
|
+
custom_text = opts.delete(:text)
|
397
|
+
end
|
398
|
+
text = custom_text || @text
|
399
|
+
opts = @options.merge(opts).merge(:sub=>true, :mode=>mode, :parent => self)
|
400
|
+
new_obj = self.class.new(text,opts)
|
401
|
+
if new_obj.success?
|
402
|
+
@text = new_obj.text unless custom_text
|
403
|
+
new_obj.text = ""
|
404
|
+
store new_obj
|
405
|
+
else
|
406
|
+
flush @text[0..(new_obj.text.length - @text.length)] unless custom_text
|
407
|
+
end
|
408
|
+
# puts "MADE #{new_obj.inspect}"
|
409
|
+
# puts "TEXT #{@text.inspect}"
|
410
|
+
new_obj
|
411
|
+
end
|
412
|
+
|
413
|
+
def leave(mode=nil)
|
414
|
+
if mode.nil?
|
415
|
+
@stack = []
|
416
|
+
return
|
417
|
+
end
|
418
|
+
pop = true
|
419
|
+
while @stack != [] && pop
|
420
|
+
pop = @stack.pop
|
421
|
+
break if pop == mode
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def fail
|
426
|
+
@ok = false
|
427
|
+
@stack = []
|
428
|
+
end
|
429
|
+
|
430
|
+
def check_params(*args)
|
431
|
+
missing = []
|
432
|
+
if args[0].kind_of?(Array)
|
433
|
+
# or groups
|
434
|
+
ok = false
|
435
|
+
args.each_index do |i|
|
436
|
+
unless args[i].kind_of?(Array)
|
437
|
+
missing[i] = [args[i]]
|
438
|
+
next
|
439
|
+
end
|
440
|
+
missing[i] = []
|
441
|
+
args[i].each do |arg|
|
442
|
+
missing[i] << arg.to_s unless @params[arg]
|
443
|
+
end
|
444
|
+
if missing[i] == []
|
445
|
+
ok = true
|
446
|
+
break
|
447
|
+
end
|
448
|
+
end
|
449
|
+
if ok
|
450
|
+
return true
|
451
|
+
else
|
452
|
+
out "[#{@method} parameter(s) missing:#{missing[0].sort.join(', ')}]"
|
453
|
+
return false
|
454
|
+
end
|
455
|
+
else
|
456
|
+
args.each do |arg|
|
457
|
+
missing << arg.to_s unless @params[arg]
|
458
|
+
end
|
459
|
+
end
|
460
|
+
if missing != []
|
461
|
+
out "[#{@method} parameter(s) missing:#{missing.sort.join(', ')}]"
|
462
|
+
return false
|
463
|
+
end
|
464
|
+
true
|
465
|
+
end
|
466
|
+
|
467
|
+
def expand_block(block, new_context={})
|
468
|
+
block.process(@context.merge(new_context))
|
469
|
+
end
|
470
|
+
|
471
|
+
def expand_with(acontext={})
|
472
|
+
blocks = acontext.delete(:blocks) || @blocks
|
473
|
+
res = ""
|
474
|
+
|
475
|
+
# FIXME: I think we can delete @pass and @parts stuff now (test first).
|
476
|
+
|
477
|
+
@pass = {} # current object sees some information from it's direct descendants
|
478
|
+
@parts = {}
|
479
|
+
only = acontext[:only]
|
480
|
+
new_context = @context.merge(acontext)
|
481
|
+
|
482
|
+
if acontext[:ignore]
|
483
|
+
new_context[:ignore] = (@context[:ignore] || []) + (acontext[:ignore] || []).uniq
|
484
|
+
end
|
485
|
+
|
486
|
+
if acontext[:no_ignore]
|
487
|
+
new_context[:ignore] = (new_context[:ignore] || []) - acontext[:no_ignore]
|
488
|
+
end
|
489
|
+
|
490
|
+
ignore = new_context[:ignore]
|
491
|
+
|
492
|
+
blocks.each do |b|
|
493
|
+
if b.kind_of?(String)
|
494
|
+
if (!only || only.include?(:string)) && (!ignore || !ignore.include?(:string))
|
495
|
+
res << b
|
496
|
+
end
|
497
|
+
elsif (!only || only.include?(b.method)) && (!ignore || !ignore.include?(b.method))
|
498
|
+
res << b.process(new_context.dup)
|
499
|
+
if pass = b.pass
|
500
|
+
if pass[:part]
|
501
|
+
@parts.merge!(pass[:part])
|
502
|
+
pass.delete(:part)
|
503
|
+
end
|
504
|
+
@pass.merge!(pass)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
res
|
509
|
+
end
|
510
|
+
|
511
|
+
def inspect
|
512
|
+
attributes = []
|
513
|
+
params = []
|
514
|
+
(@params || {}).each do |k,v|
|
515
|
+
unless v.nil?
|
516
|
+
params << "#{k.inspect.gsub('"', "'")}=>'#{v}'"
|
517
|
+
end
|
518
|
+
end
|
519
|
+
attributes << " {= #{params.sort.join(', ')}}" unless params == []
|
520
|
+
|
521
|
+
context = []
|
522
|
+
(@context || {}).each do |k,v|
|
523
|
+
unless v.nil?
|
524
|
+
context << "#{k.inspect.gsub('"', "'")}=>'#{v}'"
|
525
|
+
end
|
526
|
+
end
|
527
|
+
attributes << " {> #{context.sort.join(', ')}}" unless context == []
|
528
|
+
|
529
|
+
pass = []
|
530
|
+
(@pass || {}).each do |k,v|
|
531
|
+
unless v.nil?
|
532
|
+
if v.kind_of?(Array)
|
533
|
+
pass << "#{k.inspect.gsub('"', "'")}=>#{v.inspect.gsub('"', "'")}"
|
534
|
+
elsif v.kind_of?(Parser)
|
535
|
+
pass << "#{k.inspect.gsub('"', "'")}=>['#{v}']"
|
536
|
+
else
|
537
|
+
pass << "#{k.inspect.gsub('"', "'")}=>#{v.inspect.gsub('"', "'")}"
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
attributes << " {< #{pass.sort.join(', ')}}" unless pass == []
|
542
|
+
|
543
|
+
res = []
|
544
|
+
@blocks.each do |b|
|
545
|
+
if b.kind_of?(String)
|
546
|
+
res << b
|
547
|
+
else
|
548
|
+
res << b.inspect
|
549
|
+
end
|
550
|
+
end
|
551
|
+
result = "[#{@method}#{attributes.join('')}"
|
552
|
+
if res != []
|
553
|
+
result += "]#{res}[/#{@method}]"
|
554
|
+
else
|
555
|
+
result += "/]"
|
556
|
+
end
|
557
|
+
result + @text
|
558
|
+
end
|
559
|
+
|
560
|
+
def parser_error(message, method = @method)
|
561
|
+
self.class.parser_error(message, method)
|
562
|
+
end
|
563
|
+
end # Parser
|
564
|
+
end # Zafu
|