2007-11-26 01:36:57 +00:00
require 'strscan'
2008-05-31 20:33:05 -07:00
require 'haml/shared'
2007-11-26 01:36:57 +00:00
2007-11-23 09:15:00 +00:00
module Haml
2009-05-01 12:13:41 -07:00
# Handles the internal pre-compilation from Haml into Ruby code,
# which then runs the final creation of the HTML string.
2007-11-23 09:15:00 +00:00
module Precompiler
2008-12-27 22:34:52 -08:00
include Haml :: Util
2007-11-23 09:15:00 +00:00
# Designates an XHTML/XML element.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
ELEMENT = ?%
2009-11-23 14:24:53 -08:00
# Designates a `<div>` element with the given class.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
DIV_CLASS = ?.
2009-11-23 14:24:53 -08:00
# Designates a `<div>` element with the given id.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
DIV_ID = ?#
# Designates an XHTML/XML comment.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
COMMENT = ?/
2008-03-16 15:43:52 -07:00
# Designates an XHTML doctype or script that is never HTML-escaped.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
DOCTYPE = ?!
# Designates script, the result of which is output.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
SCRIPT = ?=
2008-03-16 15:43:52 -07:00
# Designates script that is always HTML-escaped.
2010-01-17 16:46:21 -08:00
# @private
2008-03-14 16:39:19 -07:00
SANITIZE = ?&
2007-11-23 09:15:00 +00:00
# Designates script, the result of which is flattened and output.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
FLAT_SCRIPT = ?~
# Designates script which is run but not output.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
SILENT_SCRIPT = ?-
# When following SILENT_SCRIPT, designates a comment that is not output.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
SILENT_COMMENT = ?#
# Designates a non-parsed line.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
ESCAPE = ?\\
# Designates a block of filtered text.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
FILTER = ?:
# Designates a non-parsed line. Not actually a character.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
PLAIN_TEXT = - 1
# Keeps track of the ASCII values of the characters that begin a
# specially-interpreted line.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
SPECIAL_CHARACTERS = [
ELEMENT ,
DIV_CLASS ,
DIV_ID ,
COMMENT ,
DOCTYPE ,
SCRIPT ,
2008-03-14 16:39:19 -07:00
SANITIZE ,
2007-11-23 09:15:00 +00:00
FLAT_SCRIPT ,
SILENT_SCRIPT ,
ESCAPE ,
FILTER
]
# The value of the character that designates that a line is part
# of a multiline string.
2010-01-17 16:46:21 -08:00
# @private
2007-11-23 09:15:00 +00:00
MULTILINE_CHAR_VALUE = ?|
2009-03-15 19:06:43 -07:00
# Regex to match keywords that appear in the middle of a Ruby block
# with lowered indentation.
# If a block has been started using indentation,
# lowering the indentation with one of these won't end the block.
2007-11-23 09:15:00 +00:00
# For example:
#
# - if foo
# %p yes!
# - else
# %p no!
#
2009-11-23 14:24:53 -08:00
# The block is ended after `%p no!`, because `else`
2007-11-23 09:15:00 +00:00
# is a member of this array.
2010-01-17 16:46:21 -08:00
# @private
2009-09-18 01:36:11 -07:00
MID_BLOCK_KEYWORD_REGEX = / ^- \ s*( #{ %w[ else elsif rescue ensure when end ] . join ( '|' ) } ) \ b /
2007-11-23 09:15:00 +00:00
# The Regex that matches a Doctype command.
2010-01-17 16:46:21 -08:00
# @private
2009-11-20 03:07:21 -08:00
DOCTYPE_REGEX = / ( \ d(?: \ . \ d)?)?[ \ s]*([a-z]*) /i
2007-11-23 09:15:00 +00:00
# The Regex that matches a literal string or symbol value
2010-01-17 16:46:21 -08:00
# @private
2009-10-25 14:13:03 -07:00
LITERAL_VALUE_REGEX = / :( \ w*)|(["'])((?![ \\ # ]| \ 2).| \\ .)* \ 2 /
2007-11-23 09:15:00 +00:00
private
2007-11-23 17:26:05 +00:00
# Returns the precompiled string with the preamble and postamble
2007-11-24 08:35:10 +00:00
def precompiled_with_ambles ( local_names )
2007-11-24 02:32:18 +00:00
preamble = <<END.gsub("\n", ";")
2009-11-03 20:08:43 -08:00
begin
2007-11-23 17:26:05 +00:00
extend Haml :: Helpers
2008-04-24 09:52:26 -07:00
_hamlout = @haml_buffer = Haml :: Buffer . new ( @haml_buffer , #{options_for_buffer.inspect})
2007-11-23 17:26:05 +00:00
_erbout = _hamlout . buffer
2008-11-24 21:23:58 -08:00
__in_erb_template = true
2007-11-24 01:29:57 +00:00
END
2007-11-24 02:32:18 +00:00
postamble = <<END.gsub("\n", ";")
2009-10-29 14:14:30 -07:00
#{precompiled_method_return_value}
2009-11-03 20:08:43 -08:00
ensure
@haml_buffer = @haml_buffer . upper
end
2007-11-23 17:26:05 +00:00
END
2009-07-01 00:19:46 -07:00
preamble + locals_code ( local_names ) + precompiled + postamble
2007-11-24 08:35:10 +00:00
end
2009-10-29 14:14:30 -07:00
# Returns the string used as the return value of the precompiled method.
# This method exists so it can be monkeypatched to return modified values.
def precompiled_method_return_value
" _erbout "
end
2007-11-24 08:35:10 +00:00
def locals_code ( names )
names = names . keys if Hash == names
names . map do | name |
2008-11-17 22:50:13 -08:00
# Can't use || because someone might explicitly pass in false with a symbol
2009-09-15 16:56:28 -03:00
sym_local = " _haml_locals[ #{ name . to_sym . inspect } ] "
str_local = " _haml_locals[ #{ name . to_s . inspect } ] "
2008-11-17 22:50:13 -08:00
" #{ name } = #{ sym_local } .nil? ? #{ str_local } : #{ sym_local } "
2007-11-24 08:35:10 +00:00
end . join ( ';' ) + ';'
2007-11-23 17:26:05 +00:00
end
2010-01-17 16:46:21 -08:00
# @private
2008-12-26 23:16:49 -08:00
class Line < Struct . new ( :text , :unstripped , :full , :index , :precompiler , :eod )
alias_method :eod? , :eod
2008-05-31 20:33:05 -07:00
def tabs
line = self
@tabs || = precompiler . instance_eval do
break 0 if line . text . empty? || ! ( whitespace = line . full [ / ^ \ s+ / ] )
2009-03-21 19:50:15 -07:00
2008-05-31 20:33:05 -07:00
if @indentation . nil?
@indentation = whitespace
2008-05-31 22:03:28 -07:00
if @indentation . include? ( ?\s ) && @indentation . include? ( ?\t )
raise SyntaxError . new ( " Indentation can't use both tabs and spaces. " , line . index )
end
2008-05-31 20:33:05 -07:00
@flat_spaces = @indentation * @template_tabs if flat?
break 1
end
tabs = whitespace . length / @indentation . length
break tabs if whitespace == @indentation * tabs
break @template_tabs if flat? && whitespace =~ / ^ #{ @indentation * @template_tabs } /
raise SyntaxError . new ( <<END.strip.gsub("\n", ' '), line.index)
Inconsistent indentation : #{Haml::Shared.human_indentation whitespace, true} used for indentation,
but the rest of the document was indented using #{Haml::Shared.human_indentation @indentation}.
END
end
end
end
2007-11-25 18:39:34 +00:00
2007-11-23 09:15:00 +00:00
def precompile
2008-08-29 19:07:59 -07:00
@haml_comment = @dont_indent_next_line = @dont_tab_up_next_text = false
@indentation = nil
2008-05-29 12:22:38 -07:00
@line = next_line
2008-05-29 00:23:35 -07:00
resolve_newlines
newline
2007-11-25 18:39:34 +00:00
2008-05-31 20:33:05 -07:00
raise SyntaxError . new ( " Indenting at the beginning of the document is illegal. " , @line . index ) if @line . tabs != 0
2007-11-25 18:39:34 +00:00
2008-05-29 12:22:38 -07:00
while next_line
process_indent ( @line ) unless @line . text . empty?
2007-11-23 09:15:00 +00:00
2007-11-25 19:36:06 +00:00
if flat?
2008-05-29 12:22:38 -07:00
push_flat ( @line )
@line = @next_line
2007-11-26 03:26:16 +00:00
newline
2007-11-25 19:36:06 +00:00
next
end
2007-11-25 18:39:34 +00:00
2008-05-31 20:33:05 -07:00
process_line ( @line . text , @line . index ) unless @line . text . empty? || @haml_comment
2007-11-25 19:36:06 +00:00
2008-05-29 12:22:38 -07:00
if ! flat? && @next_line . tabs - @line . tabs > 1
2008-05-31 20:33:05 -07:00
raise SyntaxError . new ( " The line was indented #{ @next_line . tabs - @line . tabs } levels deeper than the previous line. " , @next_line . index )
2007-11-23 09:15:00 +00:00
end
2008-05-31 20:33:05 -07:00
2008-12-27 21:06:53 -08:00
resolve_newlines unless @next_line . eod?
2008-05-29 12:22:38 -07:00
@line = @next_line
2008-12-27 21:06:53 -08:00
newline unless @next_line . eod?
2007-11-23 09:15:00 +00:00
end
# Close all the open tags
2007-11-25 19:36:06 +00:00
close until @to_close_stack . empty?
2007-11-23 16:32:03 +00:00
flush_merged_text
2007-11-23 09:15:00 +00:00
end
2008-04-07 23:09:17 -07:00
2007-11-23 09:15:00 +00:00
# Processes and deals with lowering indentation.
2007-11-25 20:09:02 +00:00
def process_indent ( line )
2007-11-25 20:22:21 +00:00
return unless line . tabs < = @template_tabs && @template_tabs > 0
2007-11-25 20:23:30 +00:00
to_close = @template_tabs - line . tabs
2009-10-08 04:14:16 -07:00
to_close . times { | i | close unless to_close - 1 - i == 0 && mid_block_keyword? ( line . text ) }
2007-11-23 09:15:00 +00:00
end
# Processes a single line of Haml.
#
# This method doesn't return anything; it simply processes the line and
2009-11-23 14:24:53 -08:00
# adds the appropriate code to `@precompiled`.
2008-05-31 18:52:03 -07:00
def process_line ( text , index )
2007-11-26 03:26:16 +00:00
@index = index + 1
2007-11-23 09:15:00 +00:00
2007-11-25 20:20:44 +00:00
case text [ 0 ]
2009-04-17 02:14:38 -07:00
when DIV_CLASS ; render_div ( text )
when DIV_ID
return push_plain ( text ) if text [ 1 ] == ?{
render_div ( text )
2007-12-26 02:22:23 +00:00
when ELEMENT ; render_tag ( text )
2008-04-28 20:59:43 -07:00
when COMMENT ; render_comment ( text [ 1 .. - 1 ] . strip )
2008-03-16 16:53:08 +08:00
when SANITIZE
2009-07-10 15:35:42 -04:00
return push_plain ( text [ 3 .. - 1 ] . strip , :escape_html = > true ) if text [ 1 .. 2 ] == " == "
2008-12-11 13:51:26 -08:00
return push_script ( text [ 2 .. - 1 ] . strip , :escape_html = > true ) if text [ 1 ] == SCRIPT
2009-07-10 15:35:42 -04:00
return push_flat_script ( text [ 2 .. - 1 ] . strip , :escape_html = > true ) if text [ 1 ] == FLAT_SCRIPT
return push_plain ( text [ 1 .. - 1 ] . strip , :escape_html = > true ) if text [ 1 ] == ?\s
2008-03-14 16:39:19 -07:00
push_plain text
2007-11-23 09:15:00 +00:00
when SCRIPT
2009-07-10 15:35:42 -04:00
return push_plain ( text [ 2 .. - 1 ] . strip ) if text [ 1 ] == SCRIPT
2008-12-11 13:51:26 -08:00
push_script ( text [ 1 .. - 1 ] )
2007-12-26 02:22:23 +00:00
when FLAT_SCRIPT ; push_flat_script ( text [ 1 .. - 1 ] )
2007-11-23 09:15:00 +00:00
when SILENT_SCRIPT
2007-11-25 20:20:44 +00:00
return start_haml_comment if text [ 1 ] == SILENT_COMMENT
2008-07-14 21:10:52 -04:00
raise SyntaxError . new ( <<END.rstrip, index) if text[1..-1].strip == "end"
You don ' t need to use " - end " in Haml . Use indentation instead :
- if foo?
% strong Foo !
- else
Not foo .
END
2007-11-26 03:26:16 +00:00
push_silent ( text [ 1 .. - 1 ] , true )
2008-04-28 21:28:01 -07:00
newline_now
2008-10-29 20:36:53 -07:00
2009-03-15 19:06:43 -07:00
# Handle stuff like - end.join("|")
2009-10-08 04:00:27 -07:00
@to_close_stack . last << false if text =~ / ^- \ s*end \ b / && ! block_opened?
2009-03-15 19:06:43 -07:00
2009-09-18 01:36:11 -07:00
case_stmt = text =~ / ^- \ s*case \ b /
2009-10-08 04:14:16 -07:00
keyword = mid_block_keyword? ( text )
block = block_opened? && ! keyword
# It's important to preserve tabulation modification for keywords
# that involve choosing between posible blocks of code.
if %w[ else elsif when ] . include? ( keyword )
2009-11-25 20:04:04 -08:00
# @to_close_stack may not have a :script on top
# when the preceding "- if" has nothing nested
if @to_close_stack . last && @to_close_stack . last . first == :script
@dont_indent_next_line , @dont_tab_up_next_text = @to_close_stack . last [ 1 .. 2 ]
else
push_and_tabulate ( [ :script , @dont_indent_next_line , @dont_tab_up_next_text ] )
end
2009-10-08 04:14:16 -07:00
# when is unusual in that either it will be indented twice,
# or the case won't have created its own indentation
if keyword == " when "
push_and_tabulate ( [ :script , @dont_indent_next_line , @dont_tab_up_next_text , false ] )
end
elsif block || case_stmt
push_and_tabulate ( [ :script , @dont_indent_next_line , @dont_tab_up_next_text ] )
elsif block && case_stmt
push_and_tabulate ( [ :script , @dont_indent_next_line , @dont_tab_up_next_text ] )
end
2007-12-26 02:22:23 +00:00
when FILTER ; start_filtered ( text [ 1 .. - 1 ] . downcase )
2007-11-23 09:15:00 +00:00
when DOCTYPE
2007-11-25 20:20:44 +00:00
return render_doctype ( text ) if text [ 0 ... 3 ] == '!!!'
2009-07-10 15:35:42 -04:00
return push_plain ( text [ 3 .. - 1 ] . strip , :escape_html = > false ) if text [ 1 .. 2 ] == " == "
return push_script ( text [ 2 .. - 1 ] . strip , :escape_html = > false ) if text [ 1 ] == SCRIPT
return push_flat_script ( text [ 2 .. - 1 ] . strip , :escape_html = > false ) if text [ 1 ] == FLAT_SCRIPT
return push_plain ( text [ 1 .. - 1 ] . strip , :escape_html = > false ) if text [ 1 ] == ?\s
2007-11-25 20:20:44 +00:00
push_plain text
2007-12-26 02:22:23 +00:00
when ESCAPE ; push_plain text [ 1 .. - 1 ]
2007-11-25 20:20:44 +00:00
else push_plain text
2007-11-23 09:15:00 +00:00
end
end
2008-04-07 23:09:17 -07:00
2009-10-08 04:14:16 -07:00
# If the text is a silent script text with one of Ruby's mid-block keywords,
# returns the name of that keyword.
# Otherwise, returns nil.
2007-11-25 20:20:44 +00:00
def mid_block_keyword? ( text )
2009-10-08 04:14:16 -07:00
text [ MID_BLOCK_KEYWORD_REGEX , 1 ]
2007-11-23 09:15:00 +00:00
end
2009-11-23 14:24:53 -08:00
# Evaluates `text` in the context of the scope object, but
2007-11-23 09:15:00 +00:00
# does not output the result.
2007-11-26 03:26:16 +00:00
def push_silent ( text , can_suppress = false )
2008-04-07 23:09:17 -07:00
flush_merged_text
2007-11-25 20:49:19 +00:00
return if can_suppress && options [ :suppress_eval ]
2007-11-26 03:26:16 +00:00
@precompiled << " #{ text } ; "
2007-11-23 09:15:00 +00:00
end
2009-11-23 14:24:53 -08:00
# Adds `text` to `@buffer` with appropriate tabulation
2007-11-23 09:15:00 +00:00
# without parsing it.
2008-05-10 00:26:19 -07:00
def push_merged_text ( text , tab_change = 0 , indent = true )
2008-12-27 21:06:53 -08:00
text = ! indent || @dont_indent_next_line || @options [ :ugly ] ? text : " #{ ' ' * @output_tabs } #{ text } "
@to_merge << [ :text , text , tab_change ]
2008-05-10 00:26:19 -07:00
@dont_indent_next_line = false
2007-11-23 09:15:00 +00:00
end
2008-02-22 23:03:25 -08:00
2009-11-23 14:24:53 -08:00
# Concatenate `text` to `@buffer` without tabulation.
2008-02-22 23:03:25 -08:00
def concat_merged_text ( text )
2008-12-27 21:06:53 -08:00
@to_merge << [ :text , text , 0 ]
2008-02-22 23:03:25 -08:00
end
2008-04-07 23:09:17 -07:00
2008-05-09 16:20:28 -07:00
def push_text ( text , tab_change = 0 )
push_merged_text ( " #{ text } \n " , tab_change )
2007-11-23 09:15:00 +00:00
end
2008-04-07 23:09:17 -07:00
2007-11-23 09:15:00 +00:00
def flush_merged_text
2008-12-27 21:06:53 -08:00
return if @to_merge . empty?
text , tab_change = @to_merge . inject ( [ " " , 0 ] ) do | ( str , mtabs ) , ( type , val , tabs ) |
case type
when :text
2009-05-19 16:11:45 -07:00
[ str << val . inspect [ 1 ... - 1 ] , mtabs + tabs ]
2008-12-27 21:06:53 -08:00
when :script
2008-12-28 14:39:23 -08:00
if mtabs != 0 && ! @options [ :ugly ]
2008-12-27 21:06:53 -08:00
val = " _hamlout.adjust_tabs( #{ mtabs } ); " + val
end
[ str << " \# { #{ val } } " , 0 ]
else
raise SyntaxError . new ( " [HAML BUG] Undefined entry in Haml::Precompiler@to_merge. " )
end
end
2007-11-25 20:49:19 +00:00
2008-12-28 14:39:23 -08:00
@precompiled <<
if @options [ :ugly ]
2009-10-14 17:57:12 -07:00
" _hamlout.buffer << \" #{ text } \" ; "
2008-12-28 14:39:23 -08:00
else
2009-03-28 00:19:13 -07:00
" _hamlout.push_text( \" #{ text } \" , #{ tab_change } , #{ @dont_tab_up_next_text . inspect } ); "
2008-12-28 14:39:23 -08:00
end
2008-12-27 21:06:53 -08:00
@to_merge = [ ]
2008-05-10 00:26:19 -07:00
@dont_tab_up_next_text = false
2008-04-07 23:09:17 -07:00
end
2007-11-23 09:15:00 +00:00
# Renders a block of text as plain text.
# Also checks for an illegally opened block.
2009-07-10 15:35:42 -04:00
def push_plain ( text , options = { } )
2008-05-31 18:52:03 -07:00
if block_opened?
2008-05-29 01:30:29 -07:00
raise SyntaxError . new ( " Illegal nesting: nesting within plain text is illegal. " , @next_line . index )
end
2008-12-11 14:25:54 -08:00
if contains_interpolation? ( text )
2009-10-16 18:31:04 -07:00
options [ :escape_html ] = self . options [ :escape_html ] if options [ :escape_html ] . nil?
push_script (
unescape_interpolation ( text , :escape_html = > options [ :escape_html ] ) ,
:escape_html = > false )
2008-12-11 14:25:54 -08:00
else
push_text text
end
2007-11-23 09:15:00 +00:00
end
2009-11-23 14:24:53 -08:00
# Adds +text+ to `@buffer` while flattening text.
2007-11-25 19:48:37 +00:00
def push_flat ( line )
2008-07-10 23:40:20 -04:00
text = line . full . dup
text = " " unless text . gsub! ( / ^ #{ @flat_spaces } / , '' )
@filter_buffer << " #{ text } \n "
2007-11-23 09:15:00 +00:00
end
2009-11-23 14:24:53 -08:00
# Causes `text` to be evaluated in the context of
# the scope object and the result to be added to `@buffer`.
2007-11-23 09:15:00 +00:00
#
2009-11-23 14:24:53 -08:00
# If `opts[:preserve_script]` is true, Haml::Helpers#find_and_flatten is run on
# the result before it is added to `@buffer`
2008-12-11 13:51:26 -08:00
def push_script ( text , opts = { } )
2008-12-27 21:06:53 -08:00
raise SyntaxError . new ( " There's no Ruby code for = to evaluate. " ) if text . empty?
return if options [ :suppress_eval ]
2009-07-10 15:35:42 -04:00
opts [ :escape_html ] = options [ :escape_html ] if opts [ :escape_html ] . nil?
2008-12-27 21:06:53 -08:00
2009-02-22 00:17:38 -08:00
args = %w[ preserve_script in_tag preserve_tag escape_html nuke_inner_whitespace ]
args . map! { | name | opts [ name . to_sym ] }
args << ! block_opened? << @options [ :ugly ]
no_format = @options [ :ugly ] &&
! ( opts [ :preserve_script ] || opts [ :preserve_tag ] || opts [ :escape_html ] )
2009-02-26 12:59:28 -08:00
output_temp = " (haml_very_temp = haml_temp; haml_temp = nil; haml_very_temp) "
out = " _hamlout. #{ static_method_name ( :format_script , * args ) } ( #{ output_temp } ); "
2008-12-27 21:06:53 -08:00
2008-05-09 20:20:53 -07:00
# Prerender tabulation unless we're in a tag
2008-12-11 13:51:26 -08:00
push_merged_text '' unless opts [ :in_tag ]
2008-05-09 20:20:53 -07:00
2008-12-27 21:06:53 -08:00
unless block_opened?
2009-02-26 12:59:28 -08:00
@to_merge << [ :script , no_format ? " #{ text } \n " : " haml_temp = #{ text } \n #{ out } " ]
2009-02-22 00:17:38 -08:00
concat_merged_text ( " \n " ) unless opts [ :in_tag ] || opts [ :nuke_inner_whitespace ]
2008-12-27 21:06:53 -08:00
@newlines -= 1
return
end
2007-11-25 20:49:19 +00:00
2008-12-27 21:06:53 -08:00
flush_merged_text
2008-04-18 08:48:46 -07:00
2009-02-26 12:59:28 -08:00
push_silent " haml_temp = #{ text } "
2008-04-28 21:28:01 -07:00
newline_now
2009-10-14 17:57:12 -07:00
push_and_tabulate ( [ :loud , " _hamlout.buffer << #{ no_format ? " #{ output_temp } .to_s; " : out } " ,
2009-02-22 00:17:38 -08:00
! ( opts [ :in_tag ] || opts [ :nuke_inner_whitespace ] || @options [ :ugly ] ) ] )
2007-11-23 09:15:00 +00:00
end
2008-04-07 23:09:17 -07:00
2009-11-23 14:24:53 -08:00
# Causes `text` to be evaluated, and Haml::Helpers#find_and_flatten
2007-11-23 09:15:00 +00:00
# to be run on it afterwards.
2009-07-10 15:35:42 -04:00
def push_flat_script ( text , options = { } )
2007-11-23 09:15:00 +00:00
flush_merged_text
2008-04-07 23:09:17 -07:00
2008-04-18 12:25:00 -07:00
raise SyntaxError . new ( " There's no Ruby code for ~ to evaluate. " ) if text . empty?
2009-07-10 15:35:42 -04:00
push_script ( text , options . merge ( :preserve_script = > true ) )
2007-11-23 09:15:00 +00:00
end
def start_haml_comment
2008-05-31 18:52:03 -07:00
return unless block_opened?
2007-11-25 20:49:19 +00:00
@haml_comment = true
push_and_tabulate ( [ :haml_comment ] )
2007-11-23 09:15:00 +00:00
end
2009-11-23 14:24:53 -08:00
# Closes the most recent item in `@to_close_stack`.
2007-11-23 09:15:00 +00:00
def close
2009-01-23 11:25:09 -08:00
tag , * rest = @to_close_stack . pop
send ( " close_ #{ tag } " , * rest )
2007-11-23 09:15:00 +00:00
end
2009-11-23 14:24:53 -08:00
# Puts a line in `@precompiled` that will add the closing tag of
2007-11-23 09:15:00 +00:00
# the most recently opened tag.
2009-01-23 11:25:09 -08:00
def close_element ( value )
2008-05-10 00:26:19 -07:00
tag , nuke_outer_whitespace , nuke_inner_whitespace = value
2008-05-10 03:23:47 -07:00
@output_tabs -= 1 unless nuke_inner_whitespace
2007-11-23 09:15:00 +00:00
@template_tabs -= 1
2008-05-10 03:23:47 -07:00
rstrip_buffer! if nuke_inner_whitespace
push_merged_text ( " </ #{ tag } > " + ( nuke_outer_whitespace ? " " : " \n " ) ,
nuke_inner_whitespace ? 0 : - 1 , ! nuke_inner_whitespace )
2008-05-10 00:26:19 -07:00
@dont_indent_next_line = nuke_outer_whitespace
2007-11-23 09:15:00 +00:00
end
# Closes a Ruby block.
2009-10-08 04:14:16 -07:00
def close_script ( _1 , _2 , push_end = true )
2009-08-27 23:01:56 -07:00
push_silent ( " end " , true ) if push_end
2007-11-23 09:15:00 +00:00
@template_tabs -= 1
end
# Closes a comment.
def close_comment ( has_conditional )
@output_tabs -= 1
@template_tabs -= 1
close_tag = has_conditional ? " <![endif]--> " : " --> "
push_text ( close_tag , - 1 )
end
2008-04-07 23:09:17 -07:00
2007-11-23 09:15:00 +00:00
# Closes a loud Ruby block.
2009-03-15 19:06:43 -07:00
def close_loud ( command , add_newline , push_end = true )
push_silent ( 'end' , true ) if push_end
2007-11-23 09:15:00 +00:00
@precompiled << command
@template_tabs -= 1
2009-01-23 12:19:24 -08:00
concat_merged_text ( " \n " ) if add_newline
2007-11-23 09:15:00 +00:00
end
# Closes a filtered block.
def close_filtered ( filter )
2008-02-23 01:13:36 -08:00
filter . internal_compile ( self , @filter_buffer )
2008-05-31 20:33:05 -07:00
@flat = false
2008-05-31 18:52:03 -07:00
@flat_spaces = nil
2007-11-23 09:15:00 +00:00
@filter_buffer = nil
@template_tabs -= 1
end
def close_haml_comment
@haml_comment = false
@template_tabs -= 1
end
2008-04-07 23:09:17 -07:00
2009-10-08 04:14:16 -07:00
def close_nil ( * args )
2008-10-29 20:36:53 -07:00
@template_tabs -= 1
end
2009-11-23 14:24:53 -08:00
# Iterates through the classes and ids supplied through `.`
# and `#` syntax, and returns a hash with them as attributes,
2007-11-23 09:15:00 +00:00
# that can then be merged with another attributes hash.
def parse_class_and_id ( list )
attributes = { }
list . scan ( / ([ # .])([-_a-zA-Z0-9]+) / ) do | type , property |
case type
when '.'
if attributes [ 'class' ]
attributes [ 'class' ] += " "
else
attributes [ 'class' ] = " "
end
attributes [ 'class' ] += property
2009-11-05 18:38:27 -08:00
when '#' ; attributes [ 'id' ] = property
2007-11-23 09:15:00 +00:00
end
end
attributes
end
2008-04-07 23:09:17 -07:00
def parse_static_hash ( text )
2007-11-23 09:15:00 +00:00
attributes = { }
2009-06-03 15:01:40 -07:00
scanner = StringScanner . new ( text )
scanner . scan ( / \ s+ / )
until scanner . eos?
return unless key = scanner . scan ( LITERAL_VALUE_REGEX )
return unless scanner . scan ( / \ s*=> \ s* / )
return unless value = scanner . scan ( LITERAL_VALUE_REGEX )
2009-09-27 17:37:22 -07:00
return unless scanner . scan ( / \ s*(?:,|$) \ s* / )
2009-06-03 15:01:40 -07:00
attributes [ eval ( key ) . to_s ] = eval ( value ) . to_s
2007-11-23 09:15:00 +00:00
end
2008-05-29 13:47:40 -07:00
text . count ( " \n " ) . times { newline }
2007-11-23 09:15:00 +00:00
attributes
end
# This is a class method so it can be accessed from Buffer.
2008-03-18 16:39:56 -07:00
def self . build_attributes ( is_html , attr_wrapper , attributes = { } )
2007-11-23 09:15:00 +00:00
quote_escape = attr_wrapper == '"' ? " " " : " ' "
other_quote_char = attr_wrapper == '"' ? " ' " : '"'
2008-04-07 23:09:17 -07:00
2007-11-25 20:59:44 +00:00
result = attributes . collect do | attr , value |
next if value . nil?
2008-03-02 17:20:43 -08:00
if value == true
next " #{ attr } " if is_html
next " #{ attr } = #{ attr_wrapper } #{ attr } #{ attr_wrapper } "
elsif value == false
next
2008-02-29 17:56:38 -08:00
end
2008-07-27 20:50:28 -04:00
value = Haml :: Helpers . preserve ( Haml :: Helpers . escape_once ( value . to_s ) )
2008-04-30 02:50:10 -07:00
# We want to decide whether or not to escape quotes
value . gsub! ( '"' , '"' )
2007-11-25 20:59:44 +00:00
this_attr_wrapper = attr_wrapper
if value . include? attr_wrapper
if value . include? other_quote_char
value = value . gsub ( attr_wrapper , quote_escape )
else
this_attr_wrapper = other_quote_char
2007-11-23 09:15:00 +00:00
end
end
2007-11-25 20:59:44 +00:00
" #{ attr } = #{ this_attr_wrapper } #{ value } #{ this_attr_wrapper } "
2008-03-18 16:39:56 -07:00
end
result . compact . sort . join
2007-11-23 09:15:00 +00:00
end
2008-03-18 16:39:56 -07:00
def prerender_tag ( name , self_close , attributes )
attributes_string = Precompiler . build_attributes ( html? , @options [ :attr_wrapper ] , attributes )
2008-02-26 10:57:45 -08:00
" < #{ name } #{ attributes_string } #{ self_close && xhtml? ? ' /' : '' } > "
2007-11-23 09:15:00 +00:00
end
2008-04-07 23:09:17 -07:00
2008-02-10 19:45:36 -05:00
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
def parse_tag ( line )
2008-04-18 11:43:29 -07:00
raise SyntaxError . new ( " Invalid tag: \" #{ line } \" . " ) unless match = line . scan ( / %([-: \ w]+)([- \ w \ . \ # ]*)(.*) / ) [ 0 ]
2008-02-10 19:45:36 -05:00
tag_name , attributes , rest = match
2009-05-19 16:11:45 -07:00
new_attributes_hash = old_attributes_hash = last_line = object_ref = nil
attributes_hashes = [ ]
while rest
case rest [ 0 ]
when ?{
break if old_attributes_hash
old_attributes_hash , rest , last_line = parse_old_attributes ( rest )
attributes_hashes << [ :old , old_attributes_hash ]
when ?(
break if new_attributes_hash
new_attributes_hash , rest , last_line = parse_new_attributes ( rest )
attributes_hashes << [ :new , new_attributes_hash ]
when ?[
break if object_ref
object_ref , rest = balance ( rest , ?[ , ?] )
else ; break
end
end
2008-02-10 19:45:36 -05:00
if rest
2008-05-10 00:26:19 -07:00
nuke_whitespace , action , value = rest . scan ( / (<>|><|[><])?([= \/ \ ~&!])?(.*)? / ) [ 0 ]
nuke_whitespace || = ''
nuke_outer_whitespace = nuke_whitespace . include? '>'
nuke_inner_whitespace = nuke_whitespace . include? '<'
2008-02-10 19:45:36 -05:00
end
2009-05-19 16:11:45 -07:00
2008-02-10 19:45:36 -05:00
value = value . to_s . strip
2009-05-19 16:11:45 -07:00
[ tag_name , attributes , attributes_hashes , object_ref , nuke_outer_whitespace ,
2008-05-29 13:47:40 -07:00
nuke_inner_whitespace , action , value , last_line || @index ]
2008-02-10 19:45:36 -05:00
end
2007-11-23 09:15:00 +00:00
2009-05-19 16:11:45 -07:00
def parse_old_attributes ( line )
2008-05-29 13:47:40 -07:00
line = line . dup
last_line = @index
begin
attributes_hash , rest = balance ( line , ?{ , ?} )
rescue SyntaxError = > e
if line . strip [ - 1 ] == ?, && e . message == " Unbalanced brackets. "
line << " \n " << @next_line . text
last_line += 1
next_line
retry
end
raise e
end
2008-04-27 21:14:12 -07:00
attributes_hash = attributes_hash [ 1 ... - 1 ] if attributes_hash
2008-05-29 13:47:40 -07:00
return attributes_hash , rest , last_line
2008-04-27 21:14:12 -07:00
end
2009-05-19 16:11:45 -07:00
def parse_new_attributes ( line )
line = line . dup
scanner = StringScanner . new ( line )
last_line = @index
attributes = { }
scanner . scan ( / \ ( \ s* / )
2009-07-12 12:12:58 -04:00
loop do
name , value = parse_new_attribute ( scanner )
break if name . nil?
2009-05-19 16:11:45 -07:00
if name == false
text = ( Haml :: Shared . balance ( line , ?( , ?) ) || [ line ] ) . first
raise Haml :: SyntaxError . new ( " Invalid attribute list: #{ text . inspect } . " , last_line - 1 )
end
attributes [ name ] = value
scanner . scan ( / \ s* / )
if scanner . eos?
line << " " << @next_line . text
last_line += 1
next_line
scanner . scan ( / \ s* / )
end
end
static_attributes = { }
dynamic_attributes = " { "
attributes . each do | name , ( type , val ) |
if type == :static
static_attributes [ name ] = val
else
dynamic_attributes << name . inspect << " => " << val << " , "
end
end
dynamic_attributes << " } "
dynamic_attributes = nil if dynamic_attributes == " {} "
return [ static_attributes , dynamic_attributes ] , scanner . rest , last_line
end
def parse_new_attribute ( scanner )
unless name = scanner . scan ( / [-: \ w]+ / )
return if scanner . scan ( / \ ) / )
return false
end
scanner . scan ( / \ s* / )
return name , [ :static , true ] unless scanner . scan ( / = / ) #/end
scanner . scan ( / \ s* / )
unless quote = scanner . scan ( / ["'] / )
return false unless var = scanner . scan ( / (@@?| \ $)? \ w+ / )
return name , [ :dynamic , var ]
end
2009-07-12 10:36:47 -04:00
re = / ((?: \\ .| \ # [^{]|[^ #{ quote } \\ # ])* # ?)( #{ quote } | # \ {) /
2009-05-19 16:11:45 -07:00
content = [ ]
loop do
return false unless scanner . scan ( re )
content << [ :str , scanner [ 1 ] . gsub ( / \\ (.) / , '\1' ) ]
break if scanner [ 2 ] == quote
content << [ :ruby , balance ( scanner , ?{ , ?} , 1 ) . first [ 0 ... - 1 ] ]
end
return name , [ :static , content . first [ 1 ] ] if content . size == 1
return name , [ :dynamic ,
'"' + content . map { | ( t , v ) | t == :str ? v . inspect [ 1 ... - 1 ] : " \# { #{ v } } " } . join + '"' ]
end
2007-11-23 09:15:00 +00:00
# Parses a line that will render as an XHTML tag, and adds the code that will
2009-11-23 14:24:53 -08:00
# render that tag to `@precompiled`.
2007-11-23 09:15:00 +00:00
def render_tag ( line )
2009-05-19 16:11:45 -07:00
tag_name , attributes , attributes_hashes , object_ref , nuke_outer_whitespace ,
2008-05-29 13:47:40 -07:00
nuke_inner_whitespace , action , value , last_line = parse_tag ( line )
2008-04-07 23:09:17 -07:00
2007-11-25 23:56:54 +00:00
raise SyntaxError . new ( " Illegal element: classes and ids must have values. " ) if attributes =~ / [ \ . # ]( \ .| # | \ z) /
2008-05-10 00:26:19 -07:00
# Get rid of whitespace outside of the tag if we need to
2008-05-10 03:23:47 -07:00
rstrip_buffer! if nuke_outer_whitespace
2008-05-10 00:26:19 -07:00
2008-03-02 16:24:47 -08:00
preserve_tag = options [ :preserve ] . include? ( tag_name )
2008-05-11 01:39:02 -07:00
nuke_inner_whitespace || = preserve_tag
2008-05-27 21:16:24 +02:00
preserve_tag && = ! options [ :ugly ]
2008-03-02 16:12:57 -08:00
2009-10-16 18:31:04 -07:00
escape_html = ( action == '&' || ( action != '!' && @options [ :escape_html ] ) )
2007-11-25 23:56:54 +00:00
case action
2008-10-06 09:46:09 -07:00
when '/' ; self_closing = true
2008-03-02 16:12:57 -08:00
when '~' ; parse = preserve_script = true
2007-12-26 02:22:23 +00:00
when '='
2007-11-25 23:56:54 +00:00
parse = true
2009-10-16 18:31:04 -07:00
if value [ 0 ] == ?=
value = unescape_interpolation ( value [ 1 .. - 1 ] . strip , :escape_html = > escape_html )
escape_html = false
end
2008-03-14 16:39:19 -07:00
when '&' , '!'
2009-07-10 15:35:42 -04:00
if value [ 0 ] == ?= || value [ 0 ] == ?~
2008-03-14 16:39:19 -07:00
parse = true
2009-07-10 15:35:42 -04:00
preserve_script = ( value [ 0 ] == ?~ )
2009-10-16 18:31:04 -07:00
if value [ 1 ] == ?=
value = unescape_interpolation ( value [ 2 .. - 1 ] . strip , :escape_html = > escape_html )
escape_html = false
else
value = value [ 1 .. - 1 ] . strip
end
2008-12-11 14:25:54 -08:00
elsif contains_interpolation? ( value )
2009-10-16 18:31:04 -07:00
value = unescape_interpolation ( value , :escape_html = > escape_html )
2008-12-11 14:25:54 -08:00
parse = true
2009-10-16 18:31:04 -07:00
escape_html = false
2008-12-11 14:25:54 -08:00
end
else
if contains_interpolation? ( value )
2009-10-16 18:31:04 -07:00
value = unescape_interpolation ( value , :escape_html = > escape_html )
2008-12-11 14:25:54 -08:00
parse = true
2009-10-16 18:31:04 -07:00
escape_html = false
2008-03-14 16:39:19 -07:00
end
2007-11-25 23:56:54 +00:00
end
2008-03-18 02:19:41 -07:00
2007-11-25 23:56:54 +00:00
if parse && @options [ :suppress_eval ]
parse = false
value = ''
end
2007-11-23 09:15:00 +00:00
2007-11-25 23:56:54 +00:00
object_ref = " nil " if object_ref . nil? || @options [ :suppress_eval ]
2007-11-23 09:15:00 +00:00
2007-11-25 23:56:54 +00:00
attributes = parse_class_and_id ( attributes )
2009-05-19 16:11:45 -07:00
attributes_hashes . map! do | syntax , attributes_hash |
if syntax == :old
static_attributes = parse_static_hash ( attributes_hash )
attributes_hash = nil if static_attributes || @options [ :suppress_eval ]
else
static_attributes , attributes_hash = attributes_hash
end
Buffer . merge_attrs ( attributes , static_attributes ) if static_attributes
attributes_hash
end . compact!
2007-11-23 09:15:00 +00:00
2008-05-31 18:52:03 -07:00
raise SyntaxError . new ( " Illegal nesting: nesting within a self-closing tag is illegal. " , @next_line . index ) if block_opened? && self_closing
raise SyntaxError . new ( " Illegal nesting: content can't be both given on the same line as % #{ tag_name } and nested within it. " , @next_line . index ) if block_opened? && ! value . empty?
2008-05-29 13:47:40 -07:00
raise SyntaxError . new ( " There's no Ruby code for #{ action } to evaluate. " , last_line - 1 ) if parse && value . empty?
raise SyntaxError . new ( " Self-closing tags can't have content. " , last_line - 1 ) if self_closing && ! value . empty?
2007-11-25 23:56:54 +00:00
2008-05-31 18:52:03 -07:00
self_closing || = ! ! ( ! block_opened? && value . empty? && @options [ :autoclose ] . include? ( tag_name ) )
2009-10-25 14:36:09 -07:00
value = nil if value . empty? && ( block_opened? || self_closing )
2008-03-02 16:12:57 -08:00
2008-05-10 03:23:47 -07:00
dont_indent_next_line =
2008-05-31 18:52:03 -07:00
( nuke_outer_whitespace && ! block_opened? ) ||
( nuke_inner_whitespace && block_opened? )
2008-05-10 03:23:47 -07:00
2008-05-09 16:00:08 -07:00
# Check if we can render the tag directly to text and not process it in the buffer
2009-05-19 16:11:45 -07:00
if object_ref == " nil " && attributes_hashes . empty? && ! preserve_script
2008-05-31 18:52:03 -07:00
tag_closed = ! block_opened? && ! self_closing && ! parse
2007-11-25 23:56:54 +00:00
2008-04-18 12:54:15 -07:00
open_tag = prerender_tag ( tag_name , self_closing , attributes )
2008-05-10 00:26:19 -07:00
if tag_closed
open_tag << " #{ value } </ #{ tag_name } > "
open_tag << " \n " unless nuke_outer_whitespace
else
2008-05-10 03:23:47 -07:00
open_tag << " \n " unless parse || nuke_inner_whitespace || ( self_closing && nuke_outer_whitespace )
2008-05-10 00:26:19 -07:00
end
2009-09-15 16:56:28 -03:00
2008-05-10 03:23:47 -07:00
push_merged_text ( open_tag , tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1 ,
2008-05-10 00:26:19 -07:00
! nuke_outer_whitespace )
2007-11-25 23:56:54 +00:00
2008-05-10 03:23:47 -07:00
@dont_indent_next_line = dont_indent_next_line
2007-11-25 23:56:54 +00:00
return if tag_closed
else
flush_merged_text
2009-10-25 14:36:09 -07:00
content = parse ? 'nil' : value . inspect
2009-05-19 16:11:45 -07:00
if attributes_hashes . empty?
attributes_hashes = ''
elsif attributes_hashes . size == 1
attributes_hashes = " , #{ attributes_hashes . first } "
else
attributes_hashes = " , ( #{ attributes_hashes . join ( " ).merge( " ) } ) "
end
2008-05-31 18:52:03 -07:00
args = [ tag_name , self_closing , ! block_opened? , preserve_tag , escape_html ,
2008-05-10 00:26:19 -07:00
attributes , nuke_outer_whitespace , nuke_inner_whitespace
] . map { | v | v . inspect } . join ( ', ' )
2009-05-19 16:11:45 -07:00
push_silent " _hamlout.open_tag( #{ args } , #{ object_ref } , #{ content } #{ attributes_hashes } ) "
2008-05-10 03:23:47 -07:00
@dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
2007-11-23 09:15:00 +00:00
end
2008-03-02 16:12:57 -08:00
2008-04-18 12:54:15 -07:00
return if self_closing
2007-11-23 09:15:00 +00:00
2009-10-25 14:36:09 -07:00
if value . nil?
2008-05-10 00:26:19 -07:00
push_and_tabulate ( [ :element , [ tag_name , nuke_outer_whitespace , nuke_inner_whitespace ] ] )
2008-05-10 03:23:47 -07:00
@output_tabs += 1 unless nuke_inner_whitespace
2007-11-25 23:56:54 +00:00
return
end
2008-04-07 23:09:17 -07:00
2007-11-25 23:56:54 +00:00
if parse
2008-12-11 13:51:26 -08:00
push_script ( value , :preserve_script = > preserve_script , :in_tag = > true ,
:preserve_tag = > preserve_tag , :escape_html = > escape_html ,
:nuke_inner_whitespace = > nuke_inner_whitespace )
2008-05-10 00:26:19 -07:00
concat_merged_text ( " </ #{ tag_name } > " + ( nuke_outer_whitespace ? " " : " \n " ) )
2007-11-23 09:15:00 +00:00
end
end
# Renders a line that creates an XHTML tag and has an implicit div because of
2009-11-23 14:24:53 -08:00
# `.` or `#`.
2007-11-23 09:15:00 +00:00
def render_div ( line )
render_tag ( '%div' + line )
end
# Renders an XHTML comment.
def render_comment ( line )
2008-04-28 20:59:43 -07:00
conditional , line = balance ( line , ?[ , ?] ) if line [ 0 ] == ?[
line . strip!
2007-11-23 09:15:00 +00:00
conditional << " > " if conditional
2008-04-07 23:09:17 -07:00
2008-05-31 18:52:03 -07:00
if block_opened? && ! line . empty?
2008-05-29 01:30:29 -07:00
raise SyntaxError . new ( 'Illegal nesting: nesting within a tag that already has content is illegal.' , @next_line . index )
2007-11-23 09:15:00 +00:00
end
2009-09-15 16:56:28 -03:00
open = " <!-- #{ conditional } "
2008-04-07 23:09:17 -07:00
2007-11-26 00:36:03 +00:00
# Render it statically if possible
2008-04-28 20:59:43 -07:00
unless line . empty?
2009-09-15 16:56:28 -03:00
return push_text ( " #{ open } #{ line } #{ conditional ? " <![endif]--> " : " --> " } " )
2007-11-26 00:36:03 +00:00
end
push_text ( open , 1 )
@output_tabs += 1
push_and_tabulate ( [ :comment , ! conditional . nil? ] )
2008-04-28 20:59:43 -07:00
unless line . empty?
push_text ( line )
2007-11-26 00:36:03 +00:00
close
2007-11-23 09:15:00 +00:00
end
end
2008-04-07 23:09:17 -07:00
2007-11-23 09:15:00 +00:00
# Renders an XHTML doctype or XML shebang.
def render_doctype ( line )
2008-05-31 18:52:03 -07:00
raise SyntaxError . new ( " Illegal nesting: nesting within a header command is illegal. " , @next_line . index ) if block_opened?
2008-02-27 15:16:21 +01:00
doctype = text_for_doctype ( line )
push_text doctype if doctype
2007-11-26 00:36:03 +00:00
end
def text_for_doctype ( text )
text = text [ 3 .. - 1 ] . lstrip . downcase
2008-02-27 15:16:21 +01:00
if text . index ( " xml " ) == 0
return nil if html?
2007-11-23 09:15:00 +00:00
wrapper = @options [ :attr_wrapper ]
2007-11-26 00:36:03 +00:00
return " <?xml version= #{ wrapper } 1.0 #{ wrapper } encoding= #{ wrapper } #{ text . split ( ' ' ) [ 1 ] || " utf-8 " } #{ wrapper } ?> "
end
2008-02-27 15:16:21 +01:00
if html5?
'<!DOCTYPE html>'
2008-02-24 17:10:30 -05:00
else
2008-02-27 15:16:21 +01:00
version , type = text . scan ( DOCTYPE_REGEX ) [ 0 ]
2008-04-07 23:09:17 -07:00
2008-02-27 15:16:21 +01:00
if xhtml?
if version == " 1.1 "
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
2009-11-20 03:07:21 -08:00
elsif version == " 5 "
'<!DOCTYPE html>'
2008-02-27 15:16:21 +01:00
else
case type
when " strict " ; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
when " frameset " ; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
2008-11-20 20:10:52 +00:00
when " mobile " ; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
2008-11-29 21:57:37 -08:00
when " basic " ; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
2008-02-27 15:16:21 +01:00
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
end
end
elsif html4?
case type
when " strict " ; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
when " frameset " ; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
end
2008-02-24 17:10:30 -05:00
end
2007-11-23 09:15:00 +00:00
end
end
2008-04-07 23:09:17 -07:00
2007-11-23 09:15:00 +00:00
# Starts a filtered block.
2007-11-25 20:20:44 +00:00
def start_filtered ( name )
2008-04-19 10:07:40 -07:00
raise Error . new ( " Invalid filter name \" : #{ name } \" . " ) unless name =~ / ^ \ w+$ /
2008-08-29 16:18:24 -07:00
raise Error . new ( " Filter \" #{ name } \" is not defined. " ) unless filter = Filters . defined [ name ]
2007-11-25 20:20:44 +00:00
2007-11-23 09:15:00 +00:00
push_and_tabulate ( [ :filtered , filter ] )
2008-05-31 20:33:05 -07:00
@flat = true
2007-11-23 09:15:00 +00:00
@filter_buffer = String . new
2008-05-31 20:33:05 -07:00
# If we don't know the indentation by now, it'll be set in Line#tabs
@flat_spaces = @indentation * @template_tabs if @indentation
2008-02-22 23:03:25 -08:00
end
2008-05-28 20:09:46 -07:00
def raw_next_line
text = @template . shift
return unless text
index = @template_index
@template_index += 1
return text , index
end
def next_line
text , index = raw_next_line
return unless text
2008-12-26 23:16:49 -08:00
# :eod is a special end-of-document marker
2009-03-21 19:50:15 -07:00
line =
2008-12-26 23:16:49 -08:00
if text == :eod
Line . new '-#' , '-#' , '-#' , index , self , true
else
Line . new text . strip , text . lstrip . chomp , text , index , self , false
end
2008-05-28 23:51:08 -07:00
2008-08-30 15:13:53 -07:00
# `flat?' here is a little outdated,
2009-03-21 19:50:15 -07:00
# so we have to manually check if either the previous or current line
# closes the flat block,
# as well as whether a new block is opened
@line . tabs if @line
unless ( flat? && ! closes_flat? ( line ) && ! closes_flat? ( @line ) ) ||
( @line && @line . text [ 0 ] == ?: && line . full =~ %r[ ^ #{ @line . full [ / ^ \ s+ / ] } \ s ] )
2008-05-29 00:13:34 -07:00
if line . text . empty?
newline
return next_line
end
handle_multiline ( line )
2008-05-28 23:51:08 -07:00
end
2008-05-29 01:34:55 -07:00
@next_line = line
2008-05-28 20:09:46 -07:00
end
2009-03-21 19:50:15 -07:00
def closes_flat? ( line )
line && ! line . text . empty? && line . full !~ / ^ #{ @flat_spaces } /
end
2008-05-29 00:13:34 -07:00
def un_next_line ( line )
2008-05-29 17:06:25 -07:00
@template . unshift line
2008-05-29 00:13:34 -07:00
@template_index -= 1
end
def handle_multiline ( line )
2008-05-29 17:06:25 -07:00
if is_multiline? ( line . text )
2008-08-29 19:07:59 -07:00
line . text . slice! ( - 1 )
2008-05-29 17:06:25 -07:00
while new_line = raw_next_line . first
2008-12-26 23:16:49 -08:00
break if new_line == :eod
2008-05-29 17:06:25 -07:00
newline and next if new_line . strip . empty?
break unless is_multiline? ( new_line . strip )
line . text << new_line . strip [ 0 ... - 1 ]
2008-05-29 00:13:34 -07:00
newline
end
un_next_line new_line
resolve_newlines
end
end
# Checks whether or not +line+ is in a multiline sequence.
def is_multiline? ( text )
text && text . length > 1 && text [ - 1 ] == MULTILINE_CHAR_VALUE && text [ - 2 ] == ?\s
end
2008-02-22 23:03:25 -08:00
def contains_interpolation? ( str )
str . include? ( '#{' )
2007-11-23 09:15:00 +00:00
end
2009-10-16 18:31:04 -07:00
def unescape_interpolation ( str , opts = { } )
2009-01-09 14:54:17 -08:00
res = ''
rest = Haml :: Shared . handle_interpolation str . dump do | scan |
2008-02-23 01:09:23 -08:00
escapes = ( scan [ 2 ] . size - 1 ) / 2
2009-01-09 14:54:17 -08:00
res << scan . matched [ 0 ... - 3 - escapes ]
2008-02-23 01:09:23 -08:00
if escapes % 2 == 1
2009-01-09 14:54:17 -08:00
res << '#{'
2008-02-23 01:09:23 -08:00
else
2009-10-16 18:31:04 -07:00
content = eval ( '"' + balance ( scan , ?{ , ?} , 1 ) [ 0 ] [ 0 ... - 1 ] + '"' )
content = " Haml::Helpers.html_escape( #{ content } ) " if opts [ :escape_html ]
res << '#{' + content + " } " # Use eval to get rid of string escapes
2008-02-23 01:09:23 -08:00
end
2007-11-26 01:36:57 +00:00
end
2009-01-09 14:54:17 -08:00
res + rest
2007-11-26 01:36:57 +00:00
end
2007-11-23 09:15:00 +00:00
2008-08-08 23:00:48 -04:00
def balance ( * args )
2008-08-29 19:07:59 -07:00
res = Haml :: Shared . balance ( * args )
2008-08-08 23:00:48 -04:00
return res if res
2007-11-26 01:36:57 +00:00
raise SyntaxError . new ( " Unbalanced brackets. " )
2007-11-23 09:15:00 +00:00
end
2008-05-31 18:52:03 -07:00
def block_opened?
! flat? && @next_line . tabs > @line . tabs
end
2009-11-23 14:24:53 -08:00
# Pushes value onto `@to_close_stack` and increases
# `@template_tabs`.
2007-11-23 09:15:00 +00:00
def push_and_tabulate ( value )
@to_close_stack . push ( value )
@template_tabs += 1
end
2007-11-25 19:36:06 +00:00
def flat?
2008-05-31 20:33:05 -07:00
@flat
2007-11-25 19:36:06 +00:00
end
2007-11-26 03:26:16 +00:00
2008-04-28 21:28:01 -07:00
def newline
@newlines += 1
end
def newline_now
2007-11-26 03:26:16 +00:00
@precompiled << " \n "
2008-04-28 21:28:01 -07:00
@newlines -= 1
end
def resolve_newlines
2008-05-02 00:48:39 -07:00
return unless @newlines > 0
2009-11-03 20:18:13 -08:00
flush_merged_text unless @to_merge . all? { | type , * _ | type == :text }
2008-04-28 21:28:01 -07:00
@precompiled << " \n " * @newlines
@newlines = 0
2007-11-26 03:26:16 +00:00
end
2008-05-10 03:23:47 -07:00
# Get rid of and whitespace at the end of the buffer
# or the merged text
2009-11-02 15:12:42 -08:00
def rstrip_buffer! ( index = - 1 )
last = @to_merge [ index ]
if last . nil?
2009-06-03 13:18:04 -07:00
push_silent ( " _hamlout.rstrip! " , false )
2008-05-10 03:23:47 -07:00
@dont_tab_up_next_text = true
2008-12-27 21:06:53 -08:00
return
end
case last . first
when :text
last [ 1 ] . rstrip!
if last [ 1 ] . empty?
2009-11-02 15:12:42 -08:00
@to_merge . slice! index
rstrip_buffer! index
2008-12-27 21:06:53 -08:00
end
when :script
last [ 1 ] . gsub! ( / \ (haml_temp, (.*?) \ );$ / , '(haml_temp.rstrip, \1);' )
2009-11-02 15:12:42 -08:00
rstrip_buffer! index - 1
2008-12-27 21:06:53 -08:00
else
raise SyntaxError . new ( " [HAML BUG] Undefined entry in Haml::Precompiler@to_merge. " )
2008-05-10 03:23:47 -07:00
end
end
2007-11-23 09:15:00 +00:00
end
end