Module: Zafu::ParsingRules

Defined in:
lib/zafu/parsing_rules.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#contextObject (readonly)

The context informs the rendering element about the current Node, node class, existing ids, etc. The context is inherited by sub-elements.



7
8
9
# File 'lib/zafu/parsing_rules.rb', line 7

def context
  @context
end

#helperObject (readonly)

The helper is used to connect the compiler to the world of the application (read/write templates, access traductions, etc)



10
11
12
# File 'lib/zafu/parsing_rules.rb', line 10

def helper
  @helper
end

#markupObject

The markup (of class Markup) holds information on the tag (<li>), tag attributes (.. class=‘foo’) and indentation information that should be used when rendered. This context is not inherited.



14
15
16
# File 'lib/zafu/parsing_rules.rb', line 14

def markup
  @markup
end

#sub_doObject (readonly)

We need this flag to detect cases like <r:with part=‘list’ do=‘other list finder’/>



17
18
19
# File 'lib/zafu/parsing_rules.rb', line 17

def sub_do
  @sub_do
end

Class Method Details

.included(base) ⇒ Object



19
20
21
22
# File 'lib/zafu/parsing_rules.rb', line 19

def self.included(base)
  base.before_parse :remove_erb
  base.before_process :unescape_ruby
end

Instance Method Details

#add_block(text_or_opts, at_start = false) ⇒ Object

Helper during compilation to make a block



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/zafu/parsing_rules.rb', line 345

def add_block(text_or_opts, at_start = false)
  # avoid wrapping objects in [void][/void]
  bak = @blocks
    @blocks = []
    if text_or_opts.kind_of?(String)
      new_blocks = make(:void, :method => 'void', :text => text_or_opts).blocks
    else
      new_blocks = [make(:void, text_or_opts)]
    end
    if at_start
      bak = new_blocks + bak
    else
      bak += new_blocks
    end
  @blocks = bak
  # Force descendants rebuild
  @all_descendants = nil
end

#extract_nameObject



87
88
89
90
91
92
# File 'lib/zafu/parsing_rules.rb', line 87

def extract_name
  @options[:name] ||
  (%w{input select textarea}.include?(@method) ? nil : @params[:name]) ||
  @markup.params[:id] ||
  @params[:id]
end

#get_paramsObject



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/zafu/parsing_rules.rb', line 222

def get_params
  params = Zafu::OrderedHash.new
  raw = ''
  while @text =~ PARAM_KEY_REGEXP
    raw << $&
    eat $&
    key = $1
    
    if @text =~ PARAM_VALUE_REGEXP
      raw_t = $&
      quote = $1
      eat $&
      value = $2.gsub("\\#{quote}", quote)
      if key == 'do'
        # Sub do
        sub, raw = get_params
        sub[:method] = value
        params[:do] = sub
        return params
      else
        raw << raw_t
        params[key.to_sym] = value
      end
    end
  end
  return params, raw
end

#remove_erb(text) ⇒ Object



94
95
96
# File 'lib/zafu/parsing_rules.rb', line 94

def remove_erb(text)
  text.gsub('<%', '&lt;%').gsub('%>', '%&gt;').gsub(/<\Z/, '&lt;')
end

#scanObject

scan rules



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/zafu/parsing_rules.rb', line 119

def scan
  #puts "SCAN(#{@method}): [#{@text[0..20]}]"
  if @text =~ %r{\A([^<]*?)(\s*)//!}m
    # comment
    found = $1
    flush found
    eat $2
    scan_comment
  elsif @text =~ /\A(([^<]*?)(^ *|))</m
    # Warning: this regexp looks too complicated but it's the only one that
    # works without capturing too much or not enough space in "space_before".
    flush $2
    space = $3
    eat space
    
    if @text[1..1] == '/'
      store space
      scan_close_tag
    elsif %w{! ?}.include?(@text[1..1])
      if @text[2..3] == '--'
        store space
        scan_html_comment
      elsif @text[2..8] == '[CDATA['
        # We do not flush because space has been eaten
        store space
        flush '<![CDATA['
      elsif @text =~ /\A\s*<([^>]+)>/m
        # Doctype/xml
        flush $&
      end
    elsif $1.last == ' ' && @text[0..1] == '< '
      # solitary ' < '
      store space
      flush '< '
      scan
    else
      scan_tag(:space_before => space)
    end
  else
    # no more tags
    flush
  end
end

#scan_assetObject



324
325
326
327
328
329
330
331
# File 'lib/zafu/parsing_rules.rb', line 324

def scan_asset
  @end_tag = @markup.tag
  if @markup.tag == 'script'
    enter(:void)
  else
    enter(:inside_asset)
  end
end

#scan_close_tagObject



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/zafu/parsing_rules.rb', line 163

def scan_close_tag
  if @text =~ /\A<\/([^>]+)>( *\n+|)/m
    # puts "CLOSE:[#{$&}]}" # ztag
    # closing tag
    if $1 == @end_tag
      @end_tag_count -= 1
      if @end_tag_count == 0
        eat $&

        @markup.space_after = $2
        leave
      else
        # keep the tag (false alert)
        flush $&
      end
    elsif $1[0..1] == 'r:'
      # /rtag
      eat $&
      if $1 != @end_tag
        # error bad closing rtag
        store "<span class='parser_error'>#{$&.gsub('<', '&lt;').gsub('>','&gt;')} should be &lt;/#{@end_tag}&gt;</span>"
      end
      leave
    else
      # other html tag closing
      flush $&
    end
  else
    # error
    flush
  end
end

#scan_commentObject



212
213
214
215
216
217
218
219
220
# File 'lib/zafu/parsing_rules.rb', line 212

def scan_comment
  if @text =~ %r{\A//!.*(\n|\Z)}
    # zafu html escaped
    eat $&
  else
    # error
    flush
  end
end

#scan_html_comment(opts = {}) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/zafu/parsing_rules.rb', line 196

def scan_html_comment(opts={})
  if @text =~ /\A<!--\|(.*?)-->/m
    # zafu html escaped
    #puts "ZAFU_HTML_ESCAPED[#{$&}]"
    eat $&
    @text = opts[:space_before] + $1 + @text
  elsif @text =~ /\A<!--.*?-->/m
    # html comment
    #puts "HTML_COMMENT[#{$&}]"
    flush $&
  else
    # error
    flush
  end
end

#scan_inside_assetObject



333
334
335
336
337
338
339
340
341
342
# File 'lib/zafu/parsing_rules.rb', line 333

def scan_inside_asset
  if @text =~ /\A(.*?)<\/#{@end_tag.gsub('?', '\\?')}>/m
    eat $&
    store $1
    leave(:asset)
  else
    # never ending asset
    flush
  end
end

#scan_tag(opts = {}) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/zafu/parsing_rules.rb', line 250

def scan_tag(opts={})
  #puts "TAG(#{@method}): [#{@text[0..20]}]"
  # FIXME: Better parameters parsing could avoid the &gt; hack. Create a "scan_params" method.
  if @text =~ /\A<r:([\w_]+\??)/
    #puts "RTAG:#{$~.to_a.inspect}" # ztag
    method = $1
    eat $&
    params, raw = get_params
    #puts "AFTER(#{@method}): [#{@text[0..20]}]"
    if @text =~ /\A\s*(\/?)>/
      eat $&
      opts.merge!(:method=>method, :params=>params)
      opts.merge!(:text=>'') if $1 != ''
      make(:void, opts)
    else
      # ERROR
      flush
    end
  #elsif @text =~ /\A<(\w+)([^>]*?)do\s*=('([^>]*?[^\\]|)'|"([^>]*?[^\\]|)")([^>]*?)(\/?)>/
  elsif @text =~ /\A<([\w:]+)/
    html_tag = $1
    eat $&
    params, raw = get_params

    #puts "HTML(#{html_tag}):[#{@text}]" # html tag
    if @text =~ /\A\s*(\/?)>/
      eat $&
      is_end_tag = !$1.blank?

      if sub = params.delete(:do)
        # puts "SUB_DO:#{params.inspect}"
        # do tag
        method = sub.delete(:method)
        opts.merge!(:text=>'') if is_end_tag
        opts.merge!(
        :html_tag => html_tag,
        :html_tag_params => params,
        :method => method,
        :params => sub
        )
        make(:void, opts)
      elsif raw =~ /\#\{/ || params[:id]
        # puts "HTML_DYN|ID:#{@params.inspect}"
        # If we have an :id, we need to store this as a block in case it is replaced
        # html tag with dynamic params
        opts.merge!(:text=>'') if is_end_tag
        opts.merge!(:method => 'void', :html_tag => html_tag, :html_tag_params => params)
        make(:void, opts)
      elsif @end_tag && html_tag == @end_tag
        #puts "PLAIN(END):#{@params.inspect}"
        # plain html tag
        store "#{opts[:space_before]}<#{html_tag}#{raw}#{is_end_tag ? '/' : ''}>"
        @end_tag_count += 1 unless is_end_tag
      elsif %w{link img script}.include?(html_tag)    
        #puts "ASSET: [#{@text}]"
        opts.merge!(:text=>'') if is_end_tag
        opts.merge!(:method => 'rename_asset', :html_tag_params => params, :params => params, :html_tag => html_tag)
        make(:asset, opts)
      else
        #puts "PLAIN:<#{html_tag}#{raw}#{is_end_tag ? '/' : ''}>"
        # plain html tag
        store "#{opts[:space_before]}<#{html_tag}#{raw}#{is_end_tag ? '/' : ''}>"
      end
    else
      # ERROR
      flush
    end
  else
    # unknown tag type
    store %Q{<span class='parser_error'>Invalid tag near '#{@text[0..10].gsub('>','&gt;').gsub('<','&lt;')}'</span>}
    @text = ''
  end
end

#single_child_methodObject



107
108
109
110
111
112
113
114
115
116
# File 'lib/zafu/parsing_rules.rb', line 107

def single_child_method
  return @single_child_method if defined?(@single_child_method)
  @single_child_method = if @blocks.size == 1
    single_child = @blocks[0]
    return nil if single_child.kind_of?(String)
    single_child.markup.tag ? nil : single_child.method
  else
    nil
  end
end

#start(mode) ⇒ Object

This callback is run just after the block is initialized (Parser#initialize).



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/zafu/parsing_rules.rb', line 25

def start(mode)
  # tag_context
  @markup = Zafu::Markup.new(@options.delete(:html_tag))

  # html_tag
  if html_params = @options.delete(:html_tag_params)
    @markup.params = html_params
  end

  # end_tag is used to know when to close parsing in sub-do
  # Example:
  # <li do='each' do='images'>
  #   <ul>
  #     <li><r:link/></li> <!-- do not close outer LI now: @end_tag_count != 0 -->
  #   </ul>
  # </li> <!-- close outer LI now: @end_tag_count == 0 -->
  #
  @end_tag = @markup.tag || @options.delete(:end_tag) || "r:#{@method}"
  @end_tag_count = 1

  # code indentation
  @markup.space_before = @options.delete(:space_before)

  if sub = @params.delete(:do)
    # we have a sub 'do'
    sub_method = sub.delete(:method)

    # We need this flag to detect cases cases like <r:with part='list' do='other list finder'/>
    @sub_do = true

    opts = {:method => sub_method, :params => sub}

    # the matching zafu tag will be parsed by the last 'do', we must inform it to halt properly :
    opts[:end_tag] = @end_tag

    sub = make(:void, opts)
    @markup.space_after = sub.markup.space_after
    sub.markup.space_after = ""
  end

  # set name used for include/replace from html_tag if not already set by superclass
  @name = extract_name

  if !@markup.tag && (@markup.tag = @params.delete(:tag))
    # Extract html tag parameters from @params
    @markup.steal_html_params_from(@params)
  end

  if @method == 'include' && @params[:template]
    include_template
  elsif mode == :tag && !sub
    scan_tag
  elsif !sub
    enter(mode)
  end
end

#to_sObject

Used to debug parser.



83
84
85
# File 'lib/zafu/parsing_rules.rb', line 83

def to_s
  "[#{@method}#{@name.blank? ? '' : " '#{@name}'"}#{@params.empty? ? '' : " #{@params.map{|k,v| ":#{k}=>#{v.inspect}"}.join(', ')}"}]" + (@blocks||[]).join('') + "[/#{@method}]"
end

#unescape_rubyObject



98
99
100
101
102
103
104
105
# File 'lib/zafu/parsing_rules.rb', line 98

def unescape_ruby
  @params.each do |k,v|
    v.gsub!('&gt;', '>')
    v.gsub!('&lt;', '<')
  end
  @method.gsub!('&gt;', '>')
  @method.gsub!('&lt;', '<')
end

#wrap_in_block(text_or_opts) ⇒ Object

Helper during compilation to wrap current content in a new block



365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/zafu/parsing_rules.rb', line 365

def wrap_in_block(text_or_opts)
  # avoid wrapping objects in [void][/void]
  bak = @blocks
  @blocks = []
  if text_or_opts.kind_of?(String)
    wrapper = make(:void, :method => 'void', :text => text_or_opts)
  else
    wrapper = make(:void, text_or_opts)
  end
  wrapper.blocks = bak
  @blocks = [wrapper]
  # Force descendants rebuild
  @all_descendants = nil
end