require 'zafu/markup'
module Zafu
module ParsingRules
# The context informs the rendering element about the current Node, node class, existing ids, etc. The
# context is inherited by sub-elements.
attr_reader :context
# The helper is used to connect the compiler to the world of the application (read/write templates, access traductions, etc)
attr_reader :helper
# The markup (of class Markup) holds information on the tag (
), tag attributes (.. class='foo') and
# indentation information that should be used when rendered. This context is not inherited.
attr_accessor :markup
# We need this flag to detect cases like
attr_reader :sub_do
def self.included(base)
base.before_parse :remove_erb
base.before_process :unescape_ruby
end
# This callback is run just after the block is initialized (Parser#initialize).
def start(mode)
# tag_context
@markup = Markup.new(@options.delete(:html_tag))
# html_tag
if html_params = @options.delete(:html_tag_params)
# FIXME: make a better parser so that we do not have to worry with '>' at all.
@markup.params = html_params.gsub('>', '>')
end
# end_tag is used to know when to close parsing in sub-do
# Example:
#
#
#
#
@end_tag = @markup.tag || @options.delete(:end_tag) || "r:#{@method}"
@end_tag_count = 1
# code indentation
@markup.space_before = @options.delete(:space_before) # @space_before
if @params =~ /\A([^>]*?)do\s*=('|")([^\2]*?[^\\])\2([^>]*)\Z/
#puts $~.to_a.inspect
# we have a sub 'do'
params = $1
sub_params = $4
sub_method = $3.gsub("\\#{$2}", $2)
@params = Markup.parse_params(params)
# We need this flag to detect cases cases like
@sub_do = true
opts = {:method => sub_method, :params => sub_params}
# 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 = ""
else
@params = Markup.parse_params(@params)
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
# Used to debug parser.
def to_s
"[#{@method}#{@name.blank? ? '' : " '#{@name}'"}#{@params.empty? ? '' : " #{@params.map{|k,v| ":#{k}=>#{v.inspect}"}.join(', ')}"}]" + (@blocks||[]).join('') + "[/#{@method}]"
end
def extract_name
@options[:name] ||
(%w{input select textarea}.include?(@method) ? nil : @params[:name]) ||
@markup.params[:id] ||
@params[:id]
end
def remove_erb(text)
text.gsub('<%', '<%').gsub('%>', '%>').gsub(/<\Z/, '<')
end
def unescape_ruby
@params.each do |k,v|
v.gsub!('>', '>')
v.gsub!('<', '<')
end
@method.gsub!('>', '>')
@method.gsub!('<', '<')
end
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
# scan rules
def scan
# puts "SCAN(#{@method}): [#{@text}]"
if @text =~ %r{\A([^<]*?)(\s*)//!}m
# comment
flush $1
eat $2
scan_comment
elsif @text =~ /\A([^<]*?)(^ *|) $2)
elsif @text[0..8] == ' $2)
end
else
# no more tags
flush
end
end
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 "#{$&.gsub('<', '<').gsub('>','>')} should be </#{@end_tag}>"
end
leave
else
# other html tag closing
flush $&
end
else
# error
flush
end
end
def scan_html_comment(opts={})
if @text =~ /\A/m
# zafu html escaped
eat $&
@text = opts[:space_before] + $1 + @text
elsif @text =~ /\A/m
# html comment
flush $&
else
# error
flush
end
end
def scan_comment
if @text =~ %r{\A//!.*(\n|\Z)}
# zafu html escaped
eat $&
else
# error
flush
end
end
def scan_tag(opts={})
#puts "TAG(#{@method}): [#{@text}]"
if @text =~ /\A]*?)(\/?)>/
#puts "RTAG:#{$~.to_a.inspect}" # ztag
eat $&
opts.merge!(:method=>$1, :params=>$2)
opts.merge!(:text=>'') if $3 != ''
make(:void, opts)
#elsif @text =~ /\A<(\w+)([^>]*?)do\s*=('([^>]*?[^\\]|)'|"([^>]*?[^\\]|)")([^>]*?)(\/?)>/
elsif @text =~ /\A<(\w+)([^>]*?)do\s*=('|")([^\3]*?[^\\]|)\3([^>]*?)(\/?)>/
#puts "DO:#{$~.to_a.inspect}" # do tag
eat $&
reg = $~
opts.merge!(:method=> reg[4].gsub("\\#{reg[3]}", reg[3]), :html_tag=>reg[1], :html_tag_params=>reg[2], :params=>reg[5])
opts.merge!(:text=>'') if reg[6] != ''
make(:void, opts)
elsif @text =~ /\A<(\w+)(([^>]*?)\#\{([^>]*?))(\/?)>/
# html tag with dynamic params
#puts "OTHER_DYN:[#{$&}]"
eat $&
opts.merge!(:method => 'void', :html_tag => $1, :html_tag_params => $2, :params => {})
opts.merge!(:text=>'') if $5 != ''
make(:void, opts)
elsif @text =~ /\A<(\w+)([^>]*?)id\s*=('[^>]*?[^\\]'|"[^>]*?[^\\]")([^>]*?)(\/?)>/
#puts "ID:#{$~.to_a.inspect}" # id tag
eat $&
opts.merge!(:method=>'void', :html_tag=>$1, :params=>{:id => $3[1..-2]}, :html_tag_params=>"#{$2}id=#{$3}#{$4}")
opts.merge!(:text=>'') if $5 != ''
make(:void, opts)
elsif @end_tag && @text =~ /\A<#{@end_tag.gsub('?', '\\?')}([^>]*?)(\/?)>/
#puts "SAME:#{$~.to_a.inspect}" # simple html tag same as end_tag
flush $&
@end_tag_count += 1 unless $2 == '/'
elsif @text =~ /\A<(link|img|script)/
#puts "HTML:[#{$&}]" # html
make(:asset)
elsif @text =~ /\A