haml 3.1.0.alpha.26 → 3.1.0.alpha.27
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of haml might be problematic. Click here for more details.
- data/EDGE_GEM_VERSION +1 -1
- data/REVISION +1 -0
- data/VERSION +1 -1
- data/lib/haml/buffer.rb +3 -3
- data/lib/haml/compiler.rb +432 -0
- data/lib/haml/engine.rb +5 -3
- data/lib/haml/filters.rb +20 -18
- data/lib/haml/helpers.rb +5 -5
- data/lib/haml/parser.rb +706 -0
- data/lib/haml/template.rb +1 -1
- data/lib/haml/template/plugin.rb +10 -9
- data/test/haml/engine_test.rb +34 -1
- data/vendor/sass/lib/sass/version.rb +1 -0
- metadata +5 -3
- data/lib/haml/precompiler.rb +0 -1113
data/lib/haml/template.rb
CHANGED
@@ -29,7 +29,7 @@ module Haml
|
|
29
29
|
require 'haml/helpers/xss_mods'
|
30
30
|
Haml::Helpers.send(:include, Haml::Helpers::XssMods)
|
31
31
|
|
32
|
-
Haml::
|
32
|
+
Haml::Compiler.module_eval do
|
33
33
|
def precompiled_method_return_value_with_haml_xss
|
34
34
|
"::Haml::Util.html_safe(#{precompiled_method_return_value_without_haml_xss})"
|
35
35
|
end
|
data/lib/haml/template/plugin.rb
CHANGED
@@ -51,21 +51,22 @@ module Haml
|
|
51
51
|
# We want to print the same deprecation warning,
|
52
52
|
# so we have to compile in a method call to check for it.
|
53
53
|
#
|
54
|
-
# I don't like having this in the
|
54
|
+
# I don't like having this in the compilation pipeline,
|
55
55
|
# and I'd like to get rid of it once Rails 3.1 is well-established.
|
56
56
|
if defined?(ActionView::OutputBuffer) &&
|
57
57
|
Haml::Util.has?(:instance_method, ActionView::OutputBuffer, :append_if_string=)
|
58
|
-
module
|
59
|
-
def
|
60
|
-
unless
|
61
|
-
text =~ ActionView::Template::Handlers::Erubis::BLOCK_EXPR
|
62
|
-
return
|
58
|
+
module Compiler
|
59
|
+
def compile_silent_script_with_haml_block_deprecation(&block)
|
60
|
+
unless block && !@node.value[:keyword] &&
|
61
|
+
@node.value[:text] =~ ActionView::Template::Handlers::Erubis::BLOCK_EXPR
|
62
|
+
return compile_silent_script_without_haml_block_deprecation(&block)
|
63
63
|
end
|
64
64
|
|
65
|
-
|
65
|
+
@node.value[:text] = "_hamlout.append_if_string= #{@node.value[:text]}"
|
66
|
+
compile_silent_script_without_haml_block_deprecation(&block)
|
66
67
|
end
|
67
|
-
alias_method :
|
68
|
-
alias_method :
|
68
|
+
alias_method :compile_silent_script_without_haml_block_deprecation, :compile_silent_script
|
69
|
+
alias_method :compile_silent_script, :compile_silent_script_with_haml_block_deprecation
|
69
70
|
end
|
70
71
|
|
71
72
|
class Buffer
|
data/test/haml/engine_test.rb
CHANGED
@@ -790,6 +790,31 @@ HTML
|
|
790
790
|
HAML
|
791
791
|
end
|
792
792
|
|
793
|
+
def test_nested_case_assigned_to_var
|
794
|
+
assert_equal(<<HTML, render(<<HAML))
|
795
|
+
bar
|
796
|
+
HTML
|
797
|
+
- if true
|
798
|
+
- var = case 12
|
799
|
+
- when 1; "foo"
|
800
|
+
- when 12; "bar"
|
801
|
+
= var
|
802
|
+
HAML
|
803
|
+
end
|
804
|
+
|
805
|
+
def test_case_assigned_to_multiple_vars
|
806
|
+
assert_equal(<<HTML, render(<<HAML))
|
807
|
+
bar
|
808
|
+
bip
|
809
|
+
HTML
|
810
|
+
- var, vip = case 12
|
811
|
+
- when 1; ["foo", "baz"]
|
812
|
+
- when 12; ["bar", "bip"]
|
813
|
+
= var
|
814
|
+
= vip
|
815
|
+
HAML
|
816
|
+
end
|
817
|
+
|
793
818
|
def test_if_assigned_to_var
|
794
819
|
assert_equal(<<HTML, render(<<HAML))
|
795
820
|
foo
|
@@ -1130,7 +1155,15 @@ HAML
|
|
1130
1155
|
end
|
1131
1156
|
|
1132
1157
|
if Haml::Util.ruby1_8?
|
1133
|
-
|
1158
|
+
# Sometimes, the first backtrace entry is *only* in the message.
|
1159
|
+
# No idea why.
|
1160
|
+
bt =
|
1161
|
+
if expected_message == :compile && err.message.include?("\n")
|
1162
|
+
err.message.split("\n", 2)[1]
|
1163
|
+
else
|
1164
|
+
err.backtrace[0]
|
1165
|
+
end
|
1166
|
+
assert_match(/^#{Regexp.escape(__FILE__)}:#{line_no}/, bt, "Line: #{key}")
|
1134
1167
|
end
|
1135
1168
|
else
|
1136
1169
|
assert(false, "Exception not raised for\n#{key}")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: haml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1.0.alpha.
|
4
|
+
version: 3.1.0.alpha.27
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Weizenbaum
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2010-10-
|
13
|
+
date: 2010-10-27 00:00:00 -04:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -56,7 +56,6 @@ files:
|
|
56
56
|
- lib/haml/helpers/xss_mods.rb
|
57
57
|
- lib/haml/html.rb
|
58
58
|
- lib/haml/html/erb.rb
|
59
|
-
- lib/haml/precompiler.rb
|
60
59
|
- lib/haml/railtie.rb
|
61
60
|
- lib/haml/root.rb
|
62
61
|
- lib/haml/shared.rb
|
@@ -66,6 +65,8 @@ files:
|
|
66
65
|
- lib/haml/template/plugin.rb
|
67
66
|
- lib/haml/util.rb
|
68
67
|
- lib/haml/version.rb
|
68
|
+
- lib/haml/compiler.rb
|
69
|
+
- lib/haml/parser.rb
|
69
70
|
- lib/sass.rb
|
70
71
|
- lib/sass/plugin.rb
|
71
72
|
- lib/sass/rails2_shim.rb
|
@@ -364,6 +365,7 @@ files:
|
|
364
365
|
- VERSION
|
365
366
|
- VERSION_NAME
|
366
367
|
- EDGE_GEM_VERSION
|
368
|
+
- REVISION
|
367
369
|
has_rdoc: false
|
368
370
|
homepage: http://haml-lang.com/
|
369
371
|
licenses: []
|
data/lib/haml/precompiler.rb
DELETED
@@ -1,1113 +0,0 @@
|
|
1
|
-
require 'strscan'
|
2
|
-
require 'cgi'
|
3
|
-
require 'haml/shared'
|
4
|
-
|
5
|
-
module Haml
|
6
|
-
# Handles the internal pre-compilation from Haml into Ruby code,
|
7
|
-
# which then runs the final creation of the HTML string.
|
8
|
-
module Precompiler
|
9
|
-
include Haml::Util
|
10
|
-
|
11
|
-
# Designates an XHTML/XML element.
|
12
|
-
ELEMENT = ?%
|
13
|
-
|
14
|
-
# Designates a `<div>` element with the given class.
|
15
|
-
DIV_CLASS = ?.
|
16
|
-
|
17
|
-
# Designates a `<div>` element with the given id.
|
18
|
-
DIV_ID = ?#
|
19
|
-
|
20
|
-
# Designates an XHTML/XML comment.
|
21
|
-
COMMENT = ?/
|
22
|
-
|
23
|
-
# Designates an XHTML doctype or script that is never HTML-escaped.
|
24
|
-
DOCTYPE = ?!
|
25
|
-
|
26
|
-
# Designates script, the result of which is output.
|
27
|
-
SCRIPT = ?=
|
28
|
-
|
29
|
-
# Designates script that is always HTML-escaped.
|
30
|
-
SANITIZE = ?&
|
31
|
-
|
32
|
-
# Designates script, the result of which is flattened and output.
|
33
|
-
FLAT_SCRIPT = ?~
|
34
|
-
|
35
|
-
# Designates script which is run but not output.
|
36
|
-
SILENT_SCRIPT = ?-
|
37
|
-
|
38
|
-
# When following SILENT_SCRIPT, designates a comment that is not output.
|
39
|
-
SILENT_COMMENT = ?#
|
40
|
-
|
41
|
-
# Designates a non-parsed line.
|
42
|
-
ESCAPE = ?\\
|
43
|
-
|
44
|
-
# Designates a block of filtered text.
|
45
|
-
FILTER = ?:
|
46
|
-
|
47
|
-
# Designates a non-parsed line. Not actually a character.
|
48
|
-
PLAIN_TEXT = -1
|
49
|
-
|
50
|
-
# Keeps track of the ASCII values of the characters that begin a
|
51
|
-
# specially-interpreted line.
|
52
|
-
SPECIAL_CHARACTERS = [
|
53
|
-
ELEMENT,
|
54
|
-
DIV_CLASS,
|
55
|
-
DIV_ID,
|
56
|
-
COMMENT,
|
57
|
-
DOCTYPE,
|
58
|
-
SCRIPT,
|
59
|
-
SANITIZE,
|
60
|
-
FLAT_SCRIPT,
|
61
|
-
SILENT_SCRIPT,
|
62
|
-
ESCAPE,
|
63
|
-
FILTER
|
64
|
-
]
|
65
|
-
|
66
|
-
# The value of the character that designates that a line is part
|
67
|
-
# of a multiline string.
|
68
|
-
MULTILINE_CHAR_VALUE = ?|
|
69
|
-
|
70
|
-
# Regex to match keywords that appear in the middle of a Ruby block
|
71
|
-
# with lowered indentation.
|
72
|
-
# If a block has been started using indentation,
|
73
|
-
# lowering the indentation with one of these won't end the block.
|
74
|
-
# For example:
|
75
|
-
#
|
76
|
-
# - if foo
|
77
|
-
# %p yes!
|
78
|
-
# - else
|
79
|
-
# %p no!
|
80
|
-
#
|
81
|
-
# The block is ended after `%p no!`, because `else`
|
82
|
-
# is a member of this array.
|
83
|
-
MID_BLOCK_KEYWORD_REGEX = /^-\s*(#{%w[else elsif rescue ensure when end].join('|')})\b/
|
84
|
-
|
85
|
-
# The Regex that matches a Doctype command.
|
86
|
-
DOCTYPE_REGEX = /(\d(?:\.\d)?)?[\s]*([a-z]*)/i
|
87
|
-
|
88
|
-
# The Regex that matches a literal string or symbol value
|
89
|
-
LITERAL_VALUE_REGEX = /:(\w*)|(["'])((?![\\#]|\2).|\\.)*\2/
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
# Returns the precompiled string with the preamble and postamble
|
94
|
-
def precompiled_with_ambles(local_names)
|
95
|
-
preamble = <<END.gsub("\n", ";")
|
96
|
-
begin
|
97
|
-
extend Haml::Helpers
|
98
|
-
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
|
99
|
-
_erbout = _hamlout.buffer
|
100
|
-
__in_erb_template = true
|
101
|
-
END
|
102
|
-
postamble = <<END.gsub("\n", ";")
|
103
|
-
#{precompiled_method_return_value}
|
104
|
-
ensure
|
105
|
-
@haml_buffer = @haml_buffer.upper
|
106
|
-
end
|
107
|
-
END
|
108
|
-
preamble + locals_code(local_names) + precompiled + postamble
|
109
|
-
end
|
110
|
-
|
111
|
-
# Returns the string used as the return value of the precompiled method.
|
112
|
-
# This method exists so it can be monkeypatched to return modified values.
|
113
|
-
def precompiled_method_return_value
|
114
|
-
"_erbout"
|
115
|
-
end
|
116
|
-
|
117
|
-
def locals_code(names)
|
118
|
-
names = names.keys if Hash == names
|
119
|
-
|
120
|
-
names.map do |name|
|
121
|
-
# Can't use || because someone might explicitly pass in false with a symbol
|
122
|
-
sym_local = "_haml_locals[#{inspect_obj(name.to_sym)}]"
|
123
|
-
str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
|
124
|
-
"#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local}"
|
125
|
-
end.join(';') + ';'
|
126
|
-
end
|
127
|
-
|
128
|
-
# @private
|
129
|
-
class Line < Struct.new(:text, :unstripped, :full, :index, :precompiler, :eod)
|
130
|
-
alias_method :eod?, :eod
|
131
|
-
|
132
|
-
# @private
|
133
|
-
def tabs
|
134
|
-
line = self
|
135
|
-
@tabs ||= precompiler.instance_eval do
|
136
|
-
break 0 if line.text.empty? || !(whitespace = line.full[/^\s+/])
|
137
|
-
|
138
|
-
if @indentation.nil?
|
139
|
-
@indentation = whitespace
|
140
|
-
|
141
|
-
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
142
|
-
raise SyntaxError.new("Indentation can't use both tabs and spaces.", line.index)
|
143
|
-
end
|
144
|
-
|
145
|
-
@flat_spaces = @indentation * @template_tabs if flat?
|
146
|
-
break 1
|
147
|
-
end
|
148
|
-
|
149
|
-
tabs = whitespace.length / @indentation.length
|
150
|
-
break tabs if whitespace == @indentation * tabs
|
151
|
-
break @template_tabs if flat? && whitespace =~ /^#{@indentation * @template_tabs}/
|
152
|
-
|
153
|
-
raise SyntaxError.new(<<END.strip.gsub("\n", ' '), line.index)
|
154
|
-
Inconsistent indentation: #{Haml::Shared.human_indentation whitespace, true} used for indentation,
|
155
|
-
but the rest of the document was indented using #{Haml::Shared.human_indentation @indentation}.
|
156
|
-
END
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def precompile
|
162
|
-
@haml_comment = @dont_indent_next_line = @dont_tab_up_next_text = false
|
163
|
-
@indentation = nil
|
164
|
-
@line = next_line
|
165
|
-
resolve_newlines
|
166
|
-
newline
|
167
|
-
|
168
|
-
raise SyntaxError.new("Indenting at the beginning of the document is illegal.", @line.index) if @line.tabs != 0
|
169
|
-
|
170
|
-
while next_line
|
171
|
-
process_indent(@line) unless @line.text.empty?
|
172
|
-
|
173
|
-
if flat?
|
174
|
-
push_flat(@line)
|
175
|
-
@line = @next_line
|
176
|
-
next
|
177
|
-
end
|
178
|
-
|
179
|
-
process_line(@line.text, @line.index) unless @line.text.empty? || @haml_comment
|
180
|
-
|
181
|
-
if !flat? && @next_line.tabs - @line.tabs > 1
|
182
|
-
raise SyntaxError.new("The line was indented #{@next_line.tabs - @line.tabs} levels deeper than the previous line.", @next_line.index)
|
183
|
-
end
|
184
|
-
|
185
|
-
resolve_newlines unless @next_line.eod?
|
186
|
-
@line = @next_line
|
187
|
-
newline unless @next_line.eod?
|
188
|
-
end
|
189
|
-
|
190
|
-
# Close all the open tags
|
191
|
-
close until @to_close_stack.empty?
|
192
|
-
flush_merged_text
|
193
|
-
end
|
194
|
-
|
195
|
-
# Processes and deals with lowering indentation.
|
196
|
-
def process_indent(line)
|
197
|
-
return unless line.tabs <= @template_tabs && @template_tabs > 0
|
198
|
-
|
199
|
-
to_close = @template_tabs - line.tabs
|
200
|
-
to_close.times {|i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text)}
|
201
|
-
end
|
202
|
-
|
203
|
-
# Processes a single line of Haml.
|
204
|
-
#
|
205
|
-
# This method doesn't return anything; it simply processes the line and
|
206
|
-
# adds the appropriate code to `@precompiled`.
|
207
|
-
def process_line(text, index)
|
208
|
-
@index = index + 1
|
209
|
-
|
210
|
-
case text[0]
|
211
|
-
when DIV_CLASS; render_div(text)
|
212
|
-
when DIV_ID
|
213
|
-
return push_plain(text) if text[1] == ?{
|
214
|
-
render_div(text)
|
215
|
-
when ELEMENT; render_tag(text)
|
216
|
-
when COMMENT; render_comment(text[1..-1].strip)
|
217
|
-
when SANITIZE
|
218
|
-
return push_plain(text[3..-1].strip, :escape_html => true) if text[1..2] == "=="
|
219
|
-
return push_script(text[2..-1].strip, :escape_html => true) if text[1] == SCRIPT
|
220
|
-
return push_flat_script(text[2..-1].strip, :escape_html => true) if text[1] == FLAT_SCRIPT
|
221
|
-
return push_plain(text[1..-1].strip, :escape_html => true) if text[1] == ?\s
|
222
|
-
push_plain text
|
223
|
-
when SCRIPT
|
224
|
-
return push_plain(text[2..-1].strip) if text[1] == SCRIPT
|
225
|
-
push_script(text[1..-1])
|
226
|
-
when FLAT_SCRIPT; push_flat_script(text[1..-1])
|
227
|
-
when SILENT_SCRIPT
|
228
|
-
return start_haml_comment if text[1] == SILENT_COMMENT
|
229
|
-
|
230
|
-
raise SyntaxError.new(<<END.rstrip, index) if text[1..-1].strip == "end"
|
231
|
-
You don't need to use "- end" in Haml. Un-indent to close a block:
|
232
|
-
- if foo?
|
233
|
-
%strong Foo!
|
234
|
-
- else
|
235
|
-
Not foo.
|
236
|
-
%p This line is un-indented, so it isn't part of the "if" block
|
237
|
-
END
|
238
|
-
|
239
|
-
text = handle_ruby_multiline(text)
|
240
|
-
push_silent(text[1..-1], true)
|
241
|
-
newline_now
|
242
|
-
|
243
|
-
# Handle stuff like - end.join("|")
|
244
|
-
@to_close_stack.last << false if text =~ /^-\s*end\b/ && !block_opened?
|
245
|
-
|
246
|
-
keyword = mid_block_keyword?(text)
|
247
|
-
block = block_opened? && !keyword
|
248
|
-
|
249
|
-
# It's important to preserve tabulation modification for keywords
|
250
|
-
# that involve choosing between posible blocks of code.
|
251
|
-
if %w[else elsif when].include?(keyword)
|
252
|
-
# Whether a script block has already been opened immediately above this line
|
253
|
-
was_opened = @to_close_stack.last && @to_close_stack.last.first == :script
|
254
|
-
if was_opened
|
255
|
-
@dont_indent_next_line, @dont_tab_up_next_text = @to_close_stack.last[1..2]
|
256
|
-
end
|
257
|
-
|
258
|
-
# when is unusual in that either it will be indented twice,
|
259
|
-
# or the case won't have created its own indentation.
|
260
|
-
# Also, if no block has been opened yet, we need to make sure we add an end
|
261
|
-
# once we de-indent.
|
262
|
-
if !was_opened || keyword == "when"
|
263
|
-
push_and_tabulate([
|
264
|
-
:script, @dont_indent_next_line, @dont_tab_up_next_text,
|
265
|
-
!was_opened])
|
266
|
-
end
|
267
|
-
elsif block || text =~ /^-\s*(case|if)\b/
|
268
|
-
push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
|
269
|
-
end
|
270
|
-
when FILTER; start_filtered(text[1..-1].downcase)
|
271
|
-
when DOCTYPE
|
272
|
-
return render_doctype(text) if text[0...3] == '!!!'
|
273
|
-
return push_plain(text[3..-1].strip, :escape_html => false) if text[1..2] == "=="
|
274
|
-
return push_script(text[2..-1].strip, :escape_html => false) if text[1] == SCRIPT
|
275
|
-
return push_flat_script(text[2..-1].strip, :escape_html => false) if text[1] == FLAT_SCRIPT
|
276
|
-
return push_plain(text[1..-1].strip, :escape_html => false) if text[1] == ?\s
|
277
|
-
push_plain text
|
278
|
-
when ESCAPE; push_plain text[1..-1]
|
279
|
-
else push_plain text
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
# If the text is a silent script text with one of Ruby's mid-block keywords,
|
284
|
-
# returns the name of that keyword.
|
285
|
-
# Otherwise, returns nil.
|
286
|
-
def mid_block_keyword?(text)
|
287
|
-
text[MID_BLOCK_KEYWORD_REGEX, 1]
|
288
|
-
end
|
289
|
-
|
290
|
-
# Evaluates `text` in the context of the scope object, but
|
291
|
-
# does not output the result.
|
292
|
-
def push_silent(text, can_suppress = false)
|
293
|
-
flush_merged_text
|
294
|
-
return if can_suppress && options[:suppress_eval]
|
295
|
-
@precompiled << "#{text};"
|
296
|
-
end
|
297
|
-
|
298
|
-
# Adds `text` to `@buffer` with appropriate tabulation
|
299
|
-
# without parsing it.
|
300
|
-
def push_merged_text(text, tab_change = 0, indent = true)
|
301
|
-
text = !indent || @dont_indent_next_line || @options[:ugly] ? text : "#{' ' * @output_tabs}#{text}"
|
302
|
-
@to_merge << [:text, text, tab_change]
|
303
|
-
@dont_indent_next_line = false
|
304
|
-
end
|
305
|
-
|
306
|
-
# Concatenate `text` to `@buffer` without tabulation.
|
307
|
-
def concat_merged_text(text)
|
308
|
-
@to_merge << [:text, text, 0]
|
309
|
-
end
|
310
|
-
|
311
|
-
def push_text(text, tab_change = 0)
|
312
|
-
push_merged_text("#{text}\n", tab_change)
|
313
|
-
end
|
314
|
-
|
315
|
-
def flush_merged_text
|
316
|
-
return if @to_merge.empty?
|
317
|
-
|
318
|
-
str = ""
|
319
|
-
mtabs = 0
|
320
|
-
newlines = 0
|
321
|
-
@to_merge.each do |type, val, tabs|
|
322
|
-
case type
|
323
|
-
when :text
|
324
|
-
str << inspect_obj(val)[1...-1]
|
325
|
-
mtabs += tabs
|
326
|
-
when :script
|
327
|
-
if mtabs != 0 && !@options[:ugly]
|
328
|
-
val = "_hamlout.adjust_tabs(#{mtabs}); " + val
|
329
|
-
end
|
330
|
-
str << "\#{#{"\n" * newlines}#{val}}"
|
331
|
-
mtabs = 0
|
332
|
-
newlines = 0
|
333
|
-
when :newlines
|
334
|
-
newlines += val
|
335
|
-
else
|
336
|
-
raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
unless str.empty?
|
341
|
-
@precompiled <<
|
342
|
-
if @options[:ugly]
|
343
|
-
"_hamlout.buffer << \"#{str}\";"
|
344
|
-
else
|
345
|
-
"_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
|
346
|
-
end
|
347
|
-
end
|
348
|
-
@precompiled << "\n" * newlines
|
349
|
-
@to_merge = []
|
350
|
-
@dont_tab_up_next_text = false
|
351
|
-
end
|
352
|
-
|
353
|
-
# Renders a block of text as plain text.
|
354
|
-
# Also checks for an illegally opened block.
|
355
|
-
def push_plain(text, options = {})
|
356
|
-
if block_opened?
|
357
|
-
raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index)
|
358
|
-
end
|
359
|
-
|
360
|
-
if contains_interpolation?(text)
|
361
|
-
options[:escape_html] = self.options[:escape_html] if options[:escape_html].nil?
|
362
|
-
push_script(
|
363
|
-
unescape_interpolation(text, :escape_html => options[:escape_html]),
|
364
|
-
:escape_html => false)
|
365
|
-
else
|
366
|
-
push_text text
|
367
|
-
end
|
368
|
-
end
|
369
|
-
|
370
|
-
# Adds +text+ to `@buffer` while flattening text.
|
371
|
-
def push_flat(line)
|
372
|
-
text = line.full.dup
|
373
|
-
text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
|
374
|
-
@filter_buffer << "#{text}\n"
|
375
|
-
end
|
376
|
-
|
377
|
-
# Causes `text` to be evaluated in the context of
|
378
|
-
# the scope object and the result to be added to `@buffer`.
|
379
|
-
#
|
380
|
-
# If `opts[:preserve_script]` is true, Haml::Helpers#find_and_flatten is run on
|
381
|
-
# the result before it is added to `@buffer`
|
382
|
-
def push_script(text, opts = {})
|
383
|
-
raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty?
|
384
|
-
text = handle_ruby_multiline(text)
|
385
|
-
return if options[:suppress_eval]
|
386
|
-
opts[:escape_html] = options[:escape_html] if opts[:escape_html].nil?
|
387
|
-
|
388
|
-
args = %w[preserve_script in_tag preserve_tag escape_html nuke_inner_whitespace]
|
389
|
-
args.map! {|name| opts[name.to_sym]}
|
390
|
-
args << !block_opened? << @options[:ugly]
|
391
|
-
|
392
|
-
no_format = @options[:ugly] &&
|
393
|
-
!(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
|
394
|
-
output_expr = "(#{text}\n)"
|
395
|
-
static_method = "_hamlout.#{static_method_name(:format_script, *args)}"
|
396
|
-
|
397
|
-
# Prerender tabulation unless we're in a tag
|
398
|
-
push_merged_text '' unless opts[:in_tag]
|
399
|
-
|
400
|
-
unless block_opened?
|
401
|
-
@to_merge << [:script, no_format ? "#{text}\n" : "#{static_method}(#{output_expr});"]
|
402
|
-
concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
|
403
|
-
@newlines -= 1
|
404
|
-
return
|
405
|
-
end
|
406
|
-
|
407
|
-
flush_merged_text
|
408
|
-
|
409
|
-
push_silent "haml_temp = #{text}"
|
410
|
-
newline_now
|
411
|
-
push_and_tabulate([:loud, "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : "#{static_method}(haml_temp);"}",
|
412
|
-
!(opts[:in_tag] || opts[:nuke_inner_whitespace] || @options[:ugly])])
|
413
|
-
end
|
414
|
-
|
415
|
-
# Causes `text` to be evaluated, and Haml::Helpers#find_and_flatten
|
416
|
-
# to be run on it afterwards.
|
417
|
-
def push_flat_script(text, options = {})
|
418
|
-
flush_merged_text
|
419
|
-
|
420
|
-
raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty?
|
421
|
-
push_script(text, options.merge(:preserve_script => true))
|
422
|
-
end
|
423
|
-
|
424
|
-
def start_haml_comment
|
425
|
-
return unless block_opened?
|
426
|
-
|
427
|
-
@haml_comment = true
|
428
|
-
push_and_tabulate([:haml_comment])
|
429
|
-
end
|
430
|
-
|
431
|
-
# Closes the most recent item in `@to_close_stack`.
|
432
|
-
def close
|
433
|
-
tag, *rest = @to_close_stack.pop
|
434
|
-
send("close_#{tag}", *rest)
|
435
|
-
end
|
436
|
-
|
437
|
-
# Puts a line in `@precompiled` that will add the closing tag of
|
438
|
-
# the most recently opened tag.
|
439
|
-
def close_element(value)
|
440
|
-
tag, nuke_outer_whitespace, nuke_inner_whitespace = value
|
441
|
-
@output_tabs -= 1 unless nuke_inner_whitespace
|
442
|
-
@template_tabs -= 1
|
443
|
-
rstrip_buffer! if nuke_inner_whitespace
|
444
|
-
push_merged_text("</#{tag}>" + (nuke_outer_whitespace ? "" : "\n"),
|
445
|
-
nuke_inner_whitespace ? 0 : -1, !nuke_inner_whitespace)
|
446
|
-
@dont_indent_next_line = nuke_outer_whitespace
|
447
|
-
end
|
448
|
-
|
449
|
-
# Closes a Ruby block.
|
450
|
-
def close_script(_1, _2, push_end = true)
|
451
|
-
push_silent("end", true) if push_end
|
452
|
-
@template_tabs -= 1
|
453
|
-
end
|
454
|
-
|
455
|
-
# Closes a comment.
|
456
|
-
def close_comment(has_conditional)
|
457
|
-
@output_tabs -= 1
|
458
|
-
@template_tabs -= 1
|
459
|
-
close_tag = has_conditional ? "<![endif]-->" : "-->"
|
460
|
-
push_text(close_tag, -1)
|
461
|
-
end
|
462
|
-
|
463
|
-
# Closes a loud Ruby block.
|
464
|
-
def close_loud(command, add_newline, push_end = true)
|
465
|
-
push_silent('end', true) if push_end
|
466
|
-
@precompiled << command
|
467
|
-
@template_tabs -= 1
|
468
|
-
concat_merged_text("\n") if add_newline
|
469
|
-
end
|
470
|
-
|
471
|
-
# Closes a filtered block.
|
472
|
-
def close_filtered(filter)
|
473
|
-
filter.internal_compile(self, @filter_buffer)
|
474
|
-
@flat = false
|
475
|
-
@flat_spaces = nil
|
476
|
-
@filter_buffer = nil
|
477
|
-
@template_tabs -= 1
|
478
|
-
end
|
479
|
-
|
480
|
-
def close_haml_comment
|
481
|
-
@haml_comment = false
|
482
|
-
@template_tabs -= 1
|
483
|
-
end
|
484
|
-
|
485
|
-
def close_nil(*args)
|
486
|
-
@template_tabs -= 1
|
487
|
-
end
|
488
|
-
|
489
|
-
# This is a class method so it can be accessed from {Haml::Helpers}.
|
490
|
-
#
|
491
|
-
# Iterates through the classes and ids supplied through `.`
|
492
|
-
# and `#` syntax, and returns a hash with them as attributes,
|
493
|
-
# that can then be merged with another attributes hash.
|
494
|
-
def self.parse_class_and_id(list)
|
495
|
-
attributes = {}
|
496
|
-
list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property|
|
497
|
-
case type
|
498
|
-
when '.'
|
499
|
-
if attributes['class']
|
500
|
-
attributes['class'] += " "
|
501
|
-
else
|
502
|
-
attributes['class'] = ""
|
503
|
-
end
|
504
|
-
attributes['class'] += property
|
505
|
-
when '#'; attributes['id'] = property
|
506
|
-
end
|
507
|
-
end
|
508
|
-
attributes
|
509
|
-
end
|
510
|
-
|
511
|
-
def parse_static_hash(text)
|
512
|
-
attributes = {}
|
513
|
-
scanner = StringScanner.new(text)
|
514
|
-
scanner.scan(/\s+/)
|
515
|
-
until scanner.eos?
|
516
|
-
return unless key = scanner.scan(LITERAL_VALUE_REGEX)
|
517
|
-
return unless scanner.scan(/\s*=>\s*/)
|
518
|
-
return unless value = scanner.scan(LITERAL_VALUE_REGEX)
|
519
|
-
return unless scanner.scan(/\s*(?:,|$)\s*/)
|
520
|
-
attributes[eval(key).to_s] = eval(value).to_s
|
521
|
-
end
|
522
|
-
text.count("\n").times { newline }
|
523
|
-
attributes
|
524
|
-
end
|
525
|
-
|
526
|
-
# This is a class method so it can be accessed from Buffer.
|
527
|
-
def self.build_attributes(is_html, attr_wrapper, escape_attrs, attributes = {})
|
528
|
-
quote_escape = attr_wrapper == '"' ? """ : "'"
|
529
|
-
other_quote_char = attr_wrapper == '"' ? "'" : '"'
|
530
|
-
|
531
|
-
if attributes['data'].is_a?(Hash)
|
532
|
-
attributes = attributes.dup
|
533
|
-
attributes =
|
534
|
-
Haml::Util.map_keys(attributes.delete('data')) {|name| "data-#{name}"}.merge(attributes)
|
535
|
-
end
|
536
|
-
|
537
|
-
result = attributes.collect do |attr, value|
|
538
|
-
next if value.nil?
|
539
|
-
|
540
|
-
value = filter_and_join(value, ' ') if attr == 'class'
|
541
|
-
value = filter_and_join(value, '_') if attr == 'id'
|
542
|
-
|
543
|
-
if value == true
|
544
|
-
next " #{attr}" if is_html
|
545
|
-
next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
|
546
|
-
elsif value == false
|
547
|
-
next
|
548
|
-
end
|
549
|
-
|
550
|
-
escaped =
|
551
|
-
if escape_attrs == :once
|
552
|
-
Haml::Helpers.escape_once(value.to_s)
|
553
|
-
elsif escape_attrs
|
554
|
-
CGI.escapeHTML(value.to_s)
|
555
|
-
else
|
556
|
-
value.to_s
|
557
|
-
end
|
558
|
-
value = Haml::Helpers.preserve(escaped)
|
559
|
-
if escape_attrs
|
560
|
-
# We want to decide whether or not to escape quotes
|
561
|
-
value.gsub!('"', '"')
|
562
|
-
this_attr_wrapper = attr_wrapper
|
563
|
-
if value.include? attr_wrapper
|
564
|
-
if value.include? other_quote_char
|
565
|
-
value = value.gsub(attr_wrapper, quote_escape)
|
566
|
-
else
|
567
|
-
this_attr_wrapper = other_quote_char
|
568
|
-
end
|
569
|
-
end
|
570
|
-
else
|
571
|
-
this_attr_wrapper = attr_wrapper
|
572
|
-
end
|
573
|
-
" #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
|
574
|
-
end
|
575
|
-
result.compact.sort.join
|
576
|
-
end
|
577
|
-
|
578
|
-
def self.filter_and_join(value, separator)
|
579
|
-
return "" if value == ""
|
580
|
-
value = [value] unless value.is_a?(Array)
|
581
|
-
value = value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
|
582
|
-
return !value.empty? && value
|
583
|
-
end
|
584
|
-
|
585
|
-
def prerender_tag(name, self_close, attributes)
|
586
|
-
attributes_string = Precompiler.build_attributes(
|
587
|
-
html?, @options[:attr_wrapper], @options[:escape_attrs], attributes)
|
588
|
-
"<#{name}#{attributes_string}#{self_close && xhtml? ? ' /' : ''}>"
|
589
|
-
end
|
590
|
-
|
591
|
-
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
592
|
-
def parse_tag(line)
|
593
|
-
raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-:\w\.\#]*)(.*)/)[0]
|
594
|
-
tag_name, attributes, rest = match
|
595
|
-
new_attributes_hash = old_attributes_hash = last_line = object_ref = nil
|
596
|
-
attributes_hashes = []
|
597
|
-
while rest
|
598
|
-
case rest[0]
|
599
|
-
when ?{
|
600
|
-
break if old_attributes_hash
|
601
|
-
old_attributes_hash, rest, last_line = parse_old_attributes(rest)
|
602
|
-
attributes_hashes << [:old, old_attributes_hash]
|
603
|
-
when ?(
|
604
|
-
break if new_attributes_hash
|
605
|
-
new_attributes_hash, rest, last_line = parse_new_attributes(rest)
|
606
|
-
attributes_hashes << [:new, new_attributes_hash]
|
607
|
-
when ?[
|
608
|
-
break if object_ref
|
609
|
-
object_ref, rest = balance(rest, ?[, ?])
|
610
|
-
else; break
|
611
|
-
end
|
612
|
-
end
|
613
|
-
|
614
|
-
if rest
|
615
|
-
nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
|
616
|
-
nuke_whitespace ||= ''
|
617
|
-
nuke_outer_whitespace = nuke_whitespace.include? '>'
|
618
|
-
nuke_inner_whitespace = nuke_whitespace.include? '<'
|
619
|
-
end
|
620
|
-
|
621
|
-
value = value.to_s.strip
|
622
|
-
[tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
623
|
-
nuke_inner_whitespace, action, value, last_line || @index]
|
624
|
-
end
|
625
|
-
|
626
|
-
def parse_old_attributes(line)
|
627
|
-
line = line.dup
|
628
|
-
last_line = @index
|
629
|
-
|
630
|
-
begin
|
631
|
-
attributes_hash, rest = balance(line, ?{, ?})
|
632
|
-
rescue SyntaxError => e
|
633
|
-
if line.strip[-1] == ?, && e.message == "Unbalanced brackets."
|
634
|
-
line << "\n" << @next_line.text
|
635
|
-
last_line += 1
|
636
|
-
next_line
|
637
|
-
retry
|
638
|
-
end
|
639
|
-
|
640
|
-
raise e
|
641
|
-
end
|
642
|
-
|
643
|
-
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
644
|
-
return attributes_hash, rest, last_line
|
645
|
-
end
|
646
|
-
|
647
|
-
def parse_new_attributes(line)
|
648
|
-
line = line.dup
|
649
|
-
scanner = StringScanner.new(line)
|
650
|
-
last_line = @index
|
651
|
-
attributes = {}
|
652
|
-
|
653
|
-
scanner.scan(/\(\s*/)
|
654
|
-
loop do
|
655
|
-
name, value = parse_new_attribute(scanner)
|
656
|
-
break if name.nil?
|
657
|
-
|
658
|
-
if name == false
|
659
|
-
text = (Haml::Shared.balance(line, ?(, ?)) || [line]).first
|
660
|
-
raise Haml::SyntaxError.new("Invalid attribute list: #{text.inspect}.", last_line - 1)
|
661
|
-
end
|
662
|
-
attributes[name] = value
|
663
|
-
scanner.scan(/\s*/)
|
664
|
-
|
665
|
-
if scanner.eos?
|
666
|
-
line << " " << @next_line.text
|
667
|
-
last_line += 1
|
668
|
-
next_line
|
669
|
-
scanner.scan(/\s*/)
|
670
|
-
end
|
671
|
-
end
|
672
|
-
|
673
|
-
static_attributes = {}
|
674
|
-
dynamic_attributes = "{"
|
675
|
-
attributes.each do |name, (type, val)|
|
676
|
-
if type == :static
|
677
|
-
static_attributes[name] = val
|
678
|
-
else
|
679
|
-
dynamic_attributes << inspect_obj(name) << " => " << val << ","
|
680
|
-
end
|
681
|
-
end
|
682
|
-
dynamic_attributes << "}"
|
683
|
-
dynamic_attributes = nil if dynamic_attributes == "{}"
|
684
|
-
|
685
|
-
return [static_attributes, dynamic_attributes], scanner.rest, last_line
|
686
|
-
end
|
687
|
-
|
688
|
-
def parse_new_attribute(scanner)
|
689
|
-
unless name = scanner.scan(/[-:\w]+/)
|
690
|
-
return if scanner.scan(/\)/)
|
691
|
-
return false
|
692
|
-
end
|
693
|
-
|
694
|
-
scanner.scan(/\s*/)
|
695
|
-
return name, [:static, true] unless scanner.scan(/=/) #/end
|
696
|
-
|
697
|
-
scanner.scan(/\s*/)
|
698
|
-
unless quote = scanner.scan(/["']/)
|
699
|
-
return false unless var = scanner.scan(/(@@?|\$)?\w+/)
|
700
|
-
return name, [:dynamic, var]
|
701
|
-
end
|
702
|
-
|
703
|
-
re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
|
704
|
-
content = []
|
705
|
-
loop do
|
706
|
-
return false unless scanner.scan(re)
|
707
|
-
content << [:str, scanner[1].gsub(/\\(.)/, '\1')]
|
708
|
-
break if scanner[2] == quote
|
709
|
-
content << [:ruby, balance(scanner, ?{, ?}, 1).first[0...-1]]
|
710
|
-
end
|
711
|
-
|
712
|
-
return name, [:static, content.first[1]] if content.size == 1
|
713
|
-
return name, [:dynamic,
|
714
|
-
'"' + content.map {|(t, v)| t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}"}.join + '"']
|
715
|
-
end
|
716
|
-
|
717
|
-
# Parses a line that will render as an XHTML tag, and adds the code that will
|
718
|
-
# render that tag to `@precompiled`.
|
719
|
-
def render_tag(line)
|
720
|
-
tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
721
|
-
nuke_inner_whitespace, action, value, last_line = parse_tag(line)
|
722
|
-
|
723
|
-
raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/
|
724
|
-
|
725
|
-
# Get rid of whitespace outside of the tag if we need to
|
726
|
-
rstrip_buffer! if nuke_outer_whitespace
|
727
|
-
|
728
|
-
preserve_tag = options[:preserve].include?(tag_name)
|
729
|
-
nuke_inner_whitespace ||= preserve_tag
|
730
|
-
preserve_tag &&= !options[:ugly]
|
731
|
-
|
732
|
-
escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
|
733
|
-
|
734
|
-
case action
|
735
|
-
when '/'; self_closing = true
|
736
|
-
when '~'; parse = preserve_script = true
|
737
|
-
when '='
|
738
|
-
parse = true
|
739
|
-
if value[0] == ?=
|
740
|
-
value = unescape_interpolation(value[1..-1].strip, :escape_html => escape_html)
|
741
|
-
escape_html = false
|
742
|
-
end
|
743
|
-
when '&', '!'
|
744
|
-
if value[0] == ?= || value[0] == ?~
|
745
|
-
parse = true
|
746
|
-
preserve_script = (value[0] == ?~)
|
747
|
-
if value[1] == ?=
|
748
|
-
value = unescape_interpolation(value[2..-1].strip, :escape_html => escape_html)
|
749
|
-
escape_html = false
|
750
|
-
else
|
751
|
-
value = value[1..-1].strip
|
752
|
-
end
|
753
|
-
elsif contains_interpolation?(value)
|
754
|
-
value = unescape_interpolation(value, :escape_html => escape_html)
|
755
|
-
parse = true
|
756
|
-
escape_html = false
|
757
|
-
end
|
758
|
-
else
|
759
|
-
if contains_interpolation?(value)
|
760
|
-
value = unescape_interpolation(value, :escape_html => escape_html)
|
761
|
-
parse = true
|
762
|
-
escape_html = false
|
763
|
-
end
|
764
|
-
end
|
765
|
-
|
766
|
-
if parse && @options[:suppress_eval]
|
767
|
-
parse = false
|
768
|
-
value = ''
|
769
|
-
end
|
770
|
-
|
771
|
-
object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
|
772
|
-
|
773
|
-
attributes = Precompiler.parse_class_and_id(attributes)
|
774
|
-
attributes_hashes.map! do |syntax, attributes_hash|
|
775
|
-
if syntax == :old
|
776
|
-
static_attributes = parse_static_hash(attributes_hash)
|
777
|
-
attributes_hash = nil if static_attributes || @options[:suppress_eval]
|
778
|
-
else
|
779
|
-
static_attributes, attributes_hash = attributes_hash
|
780
|
-
end
|
781
|
-
Buffer.merge_attrs(attributes, static_attributes) if static_attributes
|
782
|
-
attributes_hash
|
783
|
-
end.compact!
|
784
|
-
|
785
|
-
raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if block_opened? && self_closing
|
786
|
-
raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.", last_line - 1) if parse && value.empty?
|
787
|
-
raise SyntaxError.new("Self-closing tags can't have content.", last_line - 1) if self_closing && !value.empty?
|
788
|
-
|
789
|
-
if block_opened? && !value.empty? && !is_ruby_multiline?(value)
|
790
|
-
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)
|
791
|
-
end
|
792
|
-
|
793
|
-
self_closing ||= !!(!block_opened? && value.empty? && @options[:autoclose].any? {|t| t === tag_name})
|
794
|
-
value = nil if value.empty? && (block_opened? || self_closing)
|
795
|
-
|
796
|
-
dont_indent_next_line =
|
797
|
-
(nuke_outer_whitespace && !block_opened?) ||
|
798
|
-
(nuke_inner_whitespace && block_opened?)
|
799
|
-
|
800
|
-
# Check if we can render the tag directly to text and not process it in the buffer
|
801
|
-
if object_ref == "nil" && attributes_hashes.empty? && !preserve_script
|
802
|
-
tag_closed = !block_opened? && !self_closing && !parse
|
803
|
-
|
804
|
-
open_tag = prerender_tag(tag_name, self_closing, attributes)
|
805
|
-
if tag_closed
|
806
|
-
open_tag << "#{value}</#{tag_name}>"
|
807
|
-
open_tag << "\n" unless nuke_outer_whitespace
|
808
|
-
else
|
809
|
-
open_tag << "\n" unless parse || nuke_inner_whitespace || (self_closing && nuke_outer_whitespace)
|
810
|
-
end
|
811
|
-
|
812
|
-
push_merged_text(open_tag, tag_closed || self_closing || nuke_inner_whitespace ? 0 : 1,
|
813
|
-
!nuke_outer_whitespace)
|
814
|
-
|
815
|
-
@dont_indent_next_line = dont_indent_next_line
|
816
|
-
return if tag_closed
|
817
|
-
else
|
818
|
-
flush_merged_text
|
819
|
-
content = parse ? 'nil' : inspect_obj(value)
|
820
|
-
if attributes_hashes.empty?
|
821
|
-
attributes_hashes = ''
|
822
|
-
elsif attributes_hashes.size == 1
|
823
|
-
attributes_hashes = ", #{attributes_hashes.first}"
|
824
|
-
else
|
825
|
-
attributes_hashes = ", (#{attributes_hashes.join(").merge(")})"
|
826
|
-
end
|
827
|
-
|
828
|
-
args = [tag_name, self_closing, !block_opened?, preserve_tag, escape_html,
|
829
|
-
attributes, nuke_outer_whitespace, nuke_inner_whitespace
|
830
|
-
].map {|v| inspect_obj(v)}.join(', ')
|
831
|
-
push_silent "_hamlout.open_tag(#{args}, #{object_ref}, #{content}#{attributes_hashes})"
|
832
|
-
@dont_tab_up_next_text = @dont_indent_next_line = dont_indent_next_line
|
833
|
-
end
|
834
|
-
|
835
|
-
return if self_closing
|
836
|
-
|
837
|
-
if value.nil?
|
838
|
-
push_and_tabulate([:element, [tag_name, nuke_outer_whitespace, nuke_inner_whitespace]])
|
839
|
-
@output_tabs += 1 unless nuke_inner_whitespace
|
840
|
-
return
|
841
|
-
end
|
842
|
-
|
843
|
-
if parse
|
844
|
-
push_script(value, :preserve_script => preserve_script, :in_tag => true,
|
845
|
-
:preserve_tag => preserve_tag, :escape_html => escape_html,
|
846
|
-
:nuke_inner_whitespace => nuke_inner_whitespace)
|
847
|
-
concat_merged_text("</#{tag_name}>" + (nuke_outer_whitespace ? "" : "\n"))
|
848
|
-
end
|
849
|
-
end
|
850
|
-
|
851
|
-
# Renders a line that creates an XHTML tag and has an implicit div because of
|
852
|
-
# `.` or `#`.
|
853
|
-
def render_div(line)
|
854
|
-
render_tag('%div' + line)
|
855
|
-
end
|
856
|
-
|
857
|
-
# Renders an XHTML comment.
|
858
|
-
def render_comment(line)
|
859
|
-
conditional, line = balance(line, ?[, ?]) if line[0] == ?[
|
860
|
-
line.strip!
|
861
|
-
conditional << ">" if conditional
|
862
|
-
|
863
|
-
if block_opened? && !line.empty?
|
864
|
-
raise SyntaxError.new('Illegal nesting: nesting within a tag that already has content is illegal.', @next_line.index)
|
865
|
-
end
|
866
|
-
|
867
|
-
open = "<!--#{conditional}"
|
868
|
-
|
869
|
-
# Render it statically if possible
|
870
|
-
unless line.empty?
|
871
|
-
return push_text("#{open} #{line} #{conditional ? "<![endif]-->" : "-->"}")
|
872
|
-
end
|
873
|
-
|
874
|
-
push_text(open, 1)
|
875
|
-
@output_tabs += 1
|
876
|
-
push_and_tabulate([:comment, !conditional.nil?])
|
877
|
-
unless line.empty?
|
878
|
-
push_text(line)
|
879
|
-
close
|
880
|
-
end
|
881
|
-
end
|
882
|
-
|
883
|
-
# Renders an XHTML doctype or XML shebang.
|
884
|
-
def render_doctype(line)
|
885
|
-
raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if block_opened?
|
886
|
-
doctype = text_for_doctype(line)
|
887
|
-
push_text doctype if doctype
|
888
|
-
end
|
889
|
-
|
890
|
-
def text_for_doctype(text)
|
891
|
-
text = text[3..-1].lstrip.downcase
|
892
|
-
if text.index("xml") == 0
|
893
|
-
return nil if html?
|
894
|
-
wrapper = @options[:attr_wrapper]
|
895
|
-
return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
|
896
|
-
end
|
897
|
-
|
898
|
-
if html5?
|
899
|
-
'<!DOCTYPE html>'
|
900
|
-
else
|
901
|
-
version, type = text.scan(DOCTYPE_REGEX)[0]
|
902
|
-
|
903
|
-
if xhtml?
|
904
|
-
if version == "1.1"
|
905
|
-
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
|
906
|
-
elsif version == "5"
|
907
|
-
'<!DOCTYPE html>'
|
908
|
-
else
|
909
|
-
case type
|
910
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
911
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
912
|
-
when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
913
|
-
when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
|
914
|
-
when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
915
|
-
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
916
|
-
end
|
917
|
-
end
|
918
|
-
|
919
|
-
elsif html4?
|
920
|
-
case type
|
921
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
|
922
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
|
923
|
-
else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
924
|
-
end
|
925
|
-
end
|
926
|
-
end
|
927
|
-
end
|
928
|
-
|
929
|
-
# Starts a filtered block.
|
930
|
-
def start_filtered(name)
|
931
|
-
raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/
|
932
|
-
raise Error.new("Filter \"#{name}\" is not defined.") unless filter = Filters.defined[name]
|
933
|
-
|
934
|
-
push_and_tabulate([:filtered, filter])
|
935
|
-
@flat = true
|
936
|
-
@filter_buffer = String.new
|
937
|
-
|
938
|
-
# If we don't know the indentation by now, it'll be set in Line#tabs
|
939
|
-
@flat_spaces = @indentation * @template_tabs if @indentation
|
940
|
-
end
|
941
|
-
|
942
|
-
def raw_next_line
|
943
|
-
text = @template.shift
|
944
|
-
return unless text
|
945
|
-
|
946
|
-
index = @template_index
|
947
|
-
@template_index += 1
|
948
|
-
|
949
|
-
return text, index
|
950
|
-
end
|
951
|
-
|
952
|
-
def next_line
|
953
|
-
text, index = raw_next_line
|
954
|
-
return unless text
|
955
|
-
|
956
|
-
# :eod is a special end-of-document marker
|
957
|
-
line =
|
958
|
-
if text == :eod
|
959
|
-
Line.new '-#', '-#', '-#', index, self, true
|
960
|
-
else
|
961
|
-
Line.new text.strip, text.lstrip.chomp, text, index, self, false
|
962
|
-
end
|
963
|
-
|
964
|
-
# `flat?' here is a little outdated,
|
965
|
-
# so we have to manually check if either the previous or current line
|
966
|
-
# closes the flat block,
|
967
|
-
# as well as whether a new block is opened
|
968
|
-
@line.tabs if @line
|
969
|
-
unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
|
970
|
-
(@line && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
|
971
|
-
if line.text.empty?
|
972
|
-
newline
|
973
|
-
return next_line
|
974
|
-
end
|
975
|
-
|
976
|
-
handle_multiline(line)
|
977
|
-
end
|
978
|
-
|
979
|
-
@next_line = line
|
980
|
-
end
|
981
|
-
|
982
|
-
def closes_flat?(line)
|
983
|
-
line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
|
984
|
-
end
|
985
|
-
|
986
|
-
def un_next_line(line)
|
987
|
-
@template.unshift line
|
988
|
-
@template_index -= 1
|
989
|
-
end
|
990
|
-
|
991
|
-
def handle_multiline(line)
|
992
|
-
return unless is_multiline?(line.text)
|
993
|
-
line.text.slice!(-1)
|
994
|
-
while new_line = raw_next_line.first
|
995
|
-
break if new_line == :eod
|
996
|
-
newline and next if new_line.strip.empty?
|
997
|
-
break unless is_multiline?(new_line.strip)
|
998
|
-
line.text << new_line.strip[0...-1]
|
999
|
-
newline
|
1000
|
-
end
|
1001
|
-
un_next_line new_line
|
1002
|
-
resolve_newlines
|
1003
|
-
end
|
1004
|
-
|
1005
|
-
# Checks whether or not +line+ is in a multiline sequence.
|
1006
|
-
def is_multiline?(text)
|
1007
|
-
text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s
|
1008
|
-
end
|
1009
|
-
|
1010
|
-
def handle_ruby_multiline(text)
|
1011
|
-
text = text.rstrip
|
1012
|
-
return text unless is_ruby_multiline?(text)
|
1013
|
-
un_next_line @next_line.full
|
1014
|
-
begin
|
1015
|
-
new_line = raw_next_line.first
|
1016
|
-
break if new_line == :eod
|
1017
|
-
newline and next if new_line.strip.empty?
|
1018
|
-
text << " " << new_line.strip
|
1019
|
-
newline
|
1020
|
-
end while is_ruby_multiline?(new_line.strip)
|
1021
|
-
next_line
|
1022
|
-
resolve_newlines
|
1023
|
-
text
|
1024
|
-
end
|
1025
|
-
|
1026
|
-
def is_ruby_multiline?(text)
|
1027
|
-
text && text.length > 1 && text[-1] == ?, && text[-2] != ?? && text[-3..-2] != "?\\"
|
1028
|
-
end
|
1029
|
-
|
1030
|
-
def contains_interpolation?(str)
|
1031
|
-
str.include?('#{')
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
def unescape_interpolation(str, opts = {})
|
1035
|
-
res = ''
|
1036
|
-
rest = Haml::Shared.handle_interpolation inspect_obj(str) do |scan|
|
1037
|
-
escapes = (scan[2].size - 1) / 2
|
1038
|
-
res << scan.matched[0...-3 - escapes]
|
1039
|
-
if escapes % 2 == 1
|
1040
|
-
res << '#{'
|
1041
|
-
else
|
1042
|
-
content = eval('"' + balance(scan, ?{, ?}, 1)[0][0...-1] + '"')
|
1043
|
-
content = "Haml::Helpers.html_escape((#{content}))" if opts[:escape_html]
|
1044
|
-
res << '#{' + content + "}"# Use eval to get rid of string escapes
|
1045
|
-
end
|
1046
|
-
end
|
1047
|
-
res + rest
|
1048
|
-
end
|
1049
|
-
|
1050
|
-
def balance(*args)
|
1051
|
-
res = Haml::Shared.balance(*args)
|
1052
|
-
return res if res
|
1053
|
-
raise SyntaxError.new("Unbalanced brackets.")
|
1054
|
-
end
|
1055
|
-
|
1056
|
-
def block_opened?
|
1057
|
-
!flat? && @next_line.tabs > @line.tabs
|
1058
|
-
end
|
1059
|
-
|
1060
|
-
# Pushes value onto `@to_close_stack` and increases
|
1061
|
-
# `@template_tabs`.
|
1062
|
-
def push_and_tabulate(value)
|
1063
|
-
@to_close_stack.push(value)
|
1064
|
-
@template_tabs += 1
|
1065
|
-
end
|
1066
|
-
|
1067
|
-
def flat?
|
1068
|
-
@flat
|
1069
|
-
end
|
1070
|
-
|
1071
|
-
def newline
|
1072
|
-
@newlines += 1
|
1073
|
-
end
|
1074
|
-
|
1075
|
-
def newline_now
|
1076
|
-
@precompiled << "\n"
|
1077
|
-
@newlines -= 1
|
1078
|
-
end
|
1079
|
-
|
1080
|
-
def resolve_newlines
|
1081
|
-
return unless @newlines > 0
|
1082
|
-
@to_merge << [:newlines, @newlines]
|
1083
|
-
@newlines = 0
|
1084
|
-
end
|
1085
|
-
|
1086
|
-
# Get rid of and whitespace at the end of the buffer
|
1087
|
-
# or the merged text
|
1088
|
-
def rstrip_buffer!(index = -1)
|
1089
|
-
last = @to_merge[index]
|
1090
|
-
if last.nil?
|
1091
|
-
push_silent("_hamlout.rstrip!", false)
|
1092
|
-
@dont_tab_up_next_text = true
|
1093
|
-
return
|
1094
|
-
end
|
1095
|
-
|
1096
|
-
case last.first
|
1097
|
-
when :text
|
1098
|
-
last[1].rstrip!
|
1099
|
-
if last[1].empty?
|
1100
|
-
@to_merge.slice! index
|
1101
|
-
rstrip_buffer! index
|
1102
|
-
end
|
1103
|
-
when :script
|
1104
|
-
last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
|
1105
|
-
rstrip_buffer! index - 1
|
1106
|
-
when :newlines
|
1107
|
-
rstrip_buffer! index - 1
|
1108
|
-
else
|
1109
|
-
raise SyntaxError.new("[HAML BUG] Undefined entry in Haml::Precompiler@to_merge.")
|
1110
|
-
end
|
1111
|
-
end
|
1112
|
-
end
|
1113
|
-
end
|