hamlit 2.12.0-java → 2.13.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,553 +1 @@
1
- require 'hamlit/parser/haml_util'
2
- require 'hamlit/parser/haml_parser'
3
-
4
- module Hamlit
5
- class HamlCompiler
6
- include ::Hamlit::HamlUtil
7
-
8
- attr_accessor :options
9
-
10
- def initialize(options)
11
- @options = options
12
- @output_tabs = 0
13
- @to_merge = []
14
- @precompiled = ''
15
- @node = nil
16
- end
17
-
18
- def compile(node)
19
- parent, @node = @node, node
20
- if node.children.empty?
21
- send(:"compile_#{node.type}")
22
- else
23
- send(:"compile_#{node.type}") {node.children.each {|c| compile c}}
24
- end
25
- ensure
26
- @node = parent
27
- end
28
-
29
- # The source code that is evaluated to produce the Haml document.
30
- #
31
- # This is automatically converted to the correct encoding
32
- # (see {file:REFERENCE.md#encodings the `:encoding` option}).
33
- #
34
- # @return [String]
35
- def precompiled
36
- encoding = Encoding.find(@options.encoding)
37
- return @precompiled.force_encoding(encoding) if encoding == Encoding::ASCII_8BIT
38
- return @precompiled.encode(encoding)
39
- end
40
-
41
- def precompiled_with_return_value
42
- "#{precompiled};#{precompiled_method_return_value}"
43
- end
44
-
45
- # Returns the precompiled string with the preamble and postamble.
46
- #
47
- # Initializes to ActionView::OutputBuffer when available; this is necessary
48
- # to avoid ordering issues with partial layouts in Rails. If not available,
49
- # initializes to nil.
50
- def precompiled_with_ambles(local_names)
51
- preamble = <<END.tr!("\n", ';')
52
- begin
53
- extend ::Hamlit::HamlHelpers
54
- _hamlout = @haml_buffer = ::Hamlit::HamlBuffer.new(haml_buffer, #{options.for_buffer.inspect})
55
- _erbout = _hamlout.buffer
56
- @output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil
57
- END
58
- postamble = <<END.tr!("\n", ';')
59
- #{precompiled_method_return_value}
60
- ensure
61
- @haml_buffer = @haml_buffer.upper if @haml_buffer
62
- end
63
- END
64
- "#{preamble}#{locals_code(local_names)}#{precompiled}#{postamble}"
65
- end
66
-
67
- private
68
-
69
- # Returns the string used as the return value of the precompiled method.
70
- # This method exists so it can be monkeypatched to return modified values.
71
- def precompiled_method_return_value
72
- "_erbout"
73
- end
74
-
75
- def locals_code(names)
76
- names = names.keys if Hash === names
77
-
78
- names.each_with_object('') do |name, code|
79
- # Can't use || because someone might explicitly pass in false with a symbol
80
- sym_local = "_haml_locals[#{inspect_obj(name.to_sym)}]"
81
- str_local = "_haml_locals[#{inspect_obj(name.to_s)}]"
82
- code << "#{name} = #{sym_local}.nil? ? #{str_local} : #{sym_local};"
83
- end
84
- end
85
-
86
- def compile_root
87
- @dont_indent_next_line = @dont_tab_up_next_text = false
88
- @output_line = 1
89
- yield if block_given?
90
- flush_merged_text
91
- end
92
-
93
- def compile_plain
94
- push_text @node.value[:text]
95
- end
96
-
97
- def nuke_inner_whitespace?(node)
98
- if node.value && node.value[:nuke_inner_whitespace]
99
- true
100
- elsif node.parent
101
- nuke_inner_whitespace?(node.parent)
102
- else
103
- false
104
- end
105
- end
106
-
107
- def compile_script(&block)
108
- push_script(@node.value[:text],
109
- :preserve_script => @node.value[:preserve],
110
- :escape_html => @node.value[:escape_html],
111
- :nuke_inner_whitespace => nuke_inner_whitespace?(@node),
112
- &block)
113
- end
114
-
115
- def compile_silent_script
116
- return if @options.suppress_eval
117
- push_silent(@node.value[:text])
118
- keyword = @node.value[:keyword]
119
-
120
- if block_given?
121
- # Store these values because for conditional statements,
122
- # we want to restore them for each branch
123
- @node.value[:dont_indent_next_line] = @dont_indent_next_line
124
- @node.value[:dont_tab_up_next_text] = @dont_tab_up_next_text
125
- yield
126
- push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
127
- elsif keyword == "end"
128
- if @node.parent.children.last.equal?(@node)
129
- # Since this "end" is ending the block,
130
- # we don't need to generate an additional one
131
- @node.parent.value[:dont_push_end] = true
132
- end
133
- # Don't restore dont_* for end because it isn't a conditional branch.
134
- elsif ::Hamlit::HamlParser::MID_BLOCK_KEYWORDS.include?(keyword)
135
- # Restore dont_* for this conditional branch
136
- @dont_indent_next_line = @node.parent.value[:dont_indent_next_line]
137
- @dont_tab_up_next_text = @node.parent.value[:dont_tab_up_next_text]
138
- end
139
- end
140
-
141
- def compile_haml_comment; end
142
-
143
- def compile_tag
144
- t = @node.value
145
-
146
- # Get rid of whitespace outside of the tag if we need to
147
- rstrip_buffer! if t[:nuke_outer_whitespace]
148
-
149
- dont_indent_next_line =
150
- (t[:nuke_outer_whitespace] && !block_given?) ||
151
- (t[:nuke_inner_whitespace] && block_given?)
152
-
153
- if @options.suppress_eval
154
- object_ref = :nil
155
- parse = false
156
- value = t[:parse] ? nil : t[:value]
157
- attributes_hashes = {}
158
- preserve_script = false
159
- else
160
- object_ref = t[:object_ref]
161
- parse = t[:parse]
162
- value = t[:value]
163
- attributes_hashes = t[:attributes_hashes]
164
- preserve_script = t[:preserve_script]
165
- end
166
-
167
- if @options[:trace]
168
- t[:attributes].merge!({"data-trace" => @options.filename.split('/views').last << ":" << @node.line.to_s})
169
- end
170
-
171
- # Check if we can render the tag directly to text and not process it in the buffer
172
- if (object_ref == :nil) && attributes_hashes.empty? && !preserve_script
173
- tag_closed = !block_given? && !t[:self_closing] && !parse
174
-
175
- open_tag = prerender_tag(t[:name], t[:self_closing], t[:attributes])
176
- if tag_closed
177
- open_tag << "#{value}</#{t[:name]}>"
178
- open_tag << "\n" unless t[:nuke_outer_whitespace]
179
- elsif !(parse || t[:nuke_inner_whitespace] ||
180
- (t[:self_closing] && t[:nuke_outer_whitespace]))
181
- open_tag << "\n"
182
- end
183
-
184
- push_merged_text(open_tag,
185
- tag_closed || t[:self_closing] || t[:nuke_inner_whitespace] ? 0 : 1,
186
- !t[:nuke_outer_whitespace])
187
-
188
- @dont_indent_next_line = dont_indent_next_line
189
- return if tag_closed
190
- else
191
- if attributes_hashes.empty?
192
- attributes_hashes = ''
193
- elsif attributes_hashes.size == 1
194
- attributes_hashes = ", #{attributes_hashes.first}"
195
- else
196
- attributes_hashes = ", #{attributes_hashes.join(", ")}"
197
- end
198
-
199
- push_merged_text "<#{t[:name]}", 0, !t[:nuke_outer_whitespace]
200
- push_generated_script(
201
- "_hamlout.attributes(#{inspect_obj(t[:attributes])}, #{object_ref}#{attributes_hashes})")
202
- concat_merged_text(
203
- if t[:self_closing] && @options.xhtml?
204
- " />#{"\n" unless t[:nuke_outer_whitespace]}"
205
- else
206
- ">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
207
- end)
208
-
209
- if value && !parse
210
- concat_merged_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
211
- elsif !t[:nuke_inner_whitespace] && !t[:self_closing]
212
- @to_merge << [:text, '', 1]
213
- end
214
-
215
- @dont_indent_next_line = dont_indent_next_line
216
- end
217
-
218
- return if t[:self_closing]
219
-
220
- if value.nil?
221
- @output_tabs += 1 unless t[:nuke_inner_whitespace]
222
- yield if block_given?
223
- @output_tabs -= 1 unless t[:nuke_inner_whitespace]
224
- rstrip_buffer! if t[:nuke_inner_whitespace]
225
- push_merged_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}",
226
- t[:nuke_inner_whitespace] ? 0 : -1, !t[:nuke_inner_whitespace])
227
- @dont_indent_next_line = t[:nuke_outer_whitespace]
228
- return
229
- end
230
-
231
- if parse
232
- push_script(value, t.merge(:in_tag => true))
233
- concat_merged_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
234
- end
235
- end
236
-
237
- def compile_comment
238
- condition = "#{@node.value[:conditional]}>" if @node.value[:conditional]
239
- revealed = @node.value[:revealed]
240
-
241
- open = "<!--#{condition}#{'<!-->' if revealed}"
242
-
243
- close = "#{'<!--' if revealed}#{'<![endif]' if condition}-->"
244
-
245
- unless block_given?
246
- push_merged_text("#{open} ")
247
-
248
- if @node.value[:parse]
249
- push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
250
- else
251
- push_merged_text(@node.value[:text], 0, false)
252
- end
253
-
254
- push_merged_text(" #{close}\n", 0, false)
255
- return
256
- end
257
-
258
- push_text(open, 1)
259
- @output_tabs += 1
260
- yield if block_given?
261
- @output_tabs -= 1
262
- push_text(close, -1)
263
- end
264
-
265
- def compile_doctype
266
- doctype = text_for_doctype
267
- push_text doctype if doctype
268
- end
269
-
270
- def compile_filter
271
- unless filter = Filters.defined[@node.value[:name]]
272
- name = @node.value[:name]
273
- if ["maruku", "textile"].include?(name)
274
- raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:install_haml_contrib, name), @node.line - 1)
275
- else
276
- raise ::Hamlit::HamlError.new(::Hamlit::HamlError.message(:filter_not_defined, name), @node.line - 1)
277
- end
278
- end
279
- filter.internal_compile(self, @node.value[:text])
280
- end
281
-
282
- def text_for_doctype
283
- if @node.value[:type] == "xml"
284
- return nil if @options.html?
285
- wrapper = @options.attr_wrapper
286
- return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{@node.value[:encoding] || "utf-8"}#{wrapper} ?>"
287
- end
288
-
289
- if @options.html5?
290
- '<!DOCTYPE html>'
291
- else
292
- if @options.xhtml?
293
- if @node.value[:version] == "1.1"
294
- '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
295
- elsif @node.value[:version] == "5"
296
- '<!DOCTYPE html>'
297
- else
298
- case @node.value[:type]
299
- when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
300
- when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
301
- when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
302
- when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
303
- when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
304
- else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
305
- end
306
- end
307
-
308
- elsif @options.html4?
309
- case @node.value[:type]
310
- when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
311
- when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
312
- else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
313
- end
314
- end
315
- end
316
- end
317
-
318
- # Evaluates `text` in the context of the scope object, but
319
- # does not output the result.
320
- def push_silent(text, can_suppress = false)
321
- flush_merged_text
322
- return if can_suppress && @options.suppress_eval?
323
- newline = (text == "end") ? ";" : "\n"
324
- @precompiled << "#{resolve_newlines}#{text}#{newline}"
325
- @output_line = @output_line + text.count("\n") + newline.count("\n")
326
- end
327
-
328
- # Adds `text` to `@buffer` with appropriate tabulation
329
- # without parsing it.
330
- def push_merged_text(text, tab_change = 0, indent = true)
331
- text = !indent || @dont_indent_next_line || @options.ugly ? text : "#{' ' * @output_tabs}#{text}"
332
- @to_merge << [:text, text, tab_change]
333
- @dont_indent_next_line = false
334
- end
335
-
336
- # Concatenate `text` to `@buffer` without tabulation.
337
- def concat_merged_text(text)
338
- @to_merge << [:text, text, 0]
339
- end
340
-
341
- def push_text(text, tab_change = 0)
342
- push_merged_text("#{text}\n", tab_change)
343
- end
344
-
345
- def flush_merged_text
346
- return if @to_merge.empty?
347
-
348
- mtabs = 0
349
- @to_merge.map! do |type, val, tabs|
350
- case type
351
- when :text
352
- mtabs += tabs
353
- inspect_obj(val)[1...-1]
354
- when :script
355
- if mtabs != 0 && !@options.ugly
356
- val = "_hamlout.adjust_tabs(#{mtabs}); " + val
357
- end
358
- mtabs = 0
359
- "\#{#{val}}"
360
- else
361
- raise ::Hamlit::HamlSyntaxError.new("[HAML BUG] Undefined entry in ::Hamlit::HamlCompiler@to_merge.")
362
- end
363
- end
364
- str = @to_merge.join
365
-
366
- unless str.empty?
367
- @precompiled <<
368
- if @options.ugly
369
- "_hamlout.buffer << \"#{str}\";"
370
- else
371
- "_hamlout.push_text(\"#{str}\", #{mtabs}, #{@dont_tab_up_next_text.inspect});"
372
- end
373
- end
374
- @to_merge = []
375
- @dont_tab_up_next_text = false
376
- end
377
-
378
- # Causes `text` to be evaluated in the context of
379
- # the scope object and the result to be added to `@buffer`.
380
- #
381
- # If `opts[:preserve_script]` is true, Haml::Helpers#find_and_preserve is run on
382
- # the result before it is added to `@buffer`
383
- def push_script(text, opts = {})
384
- return if @options.suppress_eval?
385
-
386
- args = [:preserve_script, :in_tag, :preserve_tag, :escape_html, :nuke_inner_whitespace]
387
- args.map! {|name| !!opts[name]}
388
- args << !block_given? << @options.ugly
389
-
390
- no_format = @options.ugly &&
391
- !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
392
-
393
- # Prerender tabulation unless we're in a tag
394
- push_merged_text '' unless opts[:in_tag]
395
-
396
- unless block_given?
397
- format_script_method = "_hamlout.format_script((#{text}\n),#{args.join(',')});"
398
- push_generated_script(no_format ? "#{text}\n" : format_script_method)
399
- concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
400
- return
401
- end
402
-
403
- flush_merged_text
404
- push_silent "haml_temp = #{text}"
405
- yield
406
- push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
407
- format_script_method = "_hamlout.format_script(haml_temp,#{args.join(',')});"
408
- @precompiled << "_hamlout.buffer << #{no_format ? "haml_temp.to_s;" : format_script_method}"
409
- concat_merged_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace] || @options.ugly
410
- end
411
-
412
- def push_generated_script(text)
413
- @to_merge << [:script, resolve_newlines + text]
414
- @output_line += text.count("\n")
415
- end
416
-
417
- # This is a class method so it can be accessed from Buffer.
418
- def self.build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
419
- # @TODO this is an absolutely ridiculous amount of arguments. At least
420
- # some of this needs to be moved into an instance method.
421
- quote_escape = attr_wrapper == '"' ? "&#x0022;" : "&#x0027;"
422
- other_quote_char = attr_wrapper == '"' ? "'" : '"'
423
- join_char = hyphenate_data_attrs ? '-' : '_'
424
-
425
- attributes.each do |key, value|
426
- if value.is_a?(Hash)
427
- data_attributes = attributes.delete(key)
428
- data_attributes = flatten_data_attributes(data_attributes, '', join_char)
429
- data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
430
- attributes = data_attributes.merge(attributes)
431
- end
432
- end
433
-
434
- result = attributes.collect do |attr, value|
435
- next if value.nil?
436
-
437
- value = filter_and_join(value, ' ') if attr == 'class'
438
- value = filter_and_join(value, '_') if attr == 'id'
439
-
440
- if value == true
441
- next " #{attr}" if is_html
442
- next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
443
- elsif value == false
444
- next
445
- end
446
-
447
- escaped =
448
- if escape_attrs == :once
449
- ::Hamlit::HamlHelpers.escape_once(value.to_s)
450
- elsif escape_attrs
451
- ::Hamlit::HamlHelpers.html_escape(value.to_s)
452
- else
453
- value.to_s
454
- end
455
- value = ::Hamlit::HamlHelpers.preserve(escaped)
456
- if escape_attrs
457
- # We want to decide whether or not to escape quotes
458
- value.gsub!(/&quot;|&#x0022;/, '"')
459
- this_attr_wrapper = attr_wrapper
460
- if value.include? attr_wrapper
461
- if value.include? other_quote_char
462
- value.gsub!(attr_wrapper, quote_escape)
463
- else
464
- this_attr_wrapper = other_quote_char
465
- end
466
- end
467
- else
468
- this_attr_wrapper = attr_wrapper
469
- end
470
- " #{attr}=#{this_attr_wrapper}#{value}#{this_attr_wrapper}"
471
- end
472
- result.compact!
473
- result.sort!
474
- result.join
475
- end
476
-
477
- def self.filter_and_join(value, separator)
478
- return '' if (value.respond_to?(:empty?) && value.empty?)
479
-
480
- if value.is_a?(Array)
481
- value.flatten!
482
- value.map! {|item| item ? item.to_s : nil}
483
- value.compact!
484
- value = value.join(separator)
485
- else
486
- value = value ? value.to_s : nil
487
- end
488
- !value.nil? && !value.empty? && value
489
- end
490
-
491
- def self.build_data_keys(data_hash, hyphenate, attr_name="data")
492
- Hash[data_hash.map do |name, value|
493
- if name == nil
494
- [attr_name, value]
495
- elsif hyphenate
496
- ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
497
- else
498
- ["#{attr_name}-#{name}", value]
499
- end
500
- end]
501
- end
502
-
503
- def self.flatten_data_attributes(data, key, join_char, seen = [])
504
- return {key => data} unless data.is_a?(Hash)
505
-
506
- return {key => nil} if seen.include? data.object_id
507
- seen << data.object_id
508
-
509
- data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
510
- joined = key == '' ? k : [key, k].join(join_char)
511
- hash.merge! flatten_data_attributes(v, joined, join_char, seen)
512
- end
513
- end
514
-
515
- def prerender_tag(name, self_close, attributes)
516
- attributes_string = ::Hamlit::HamlCompiler.build_attributes(
517
- @options.html?, @options.attr_wrapper, @options.escape_attrs, @options.hyphenate_data_attrs, attributes)
518
- "<#{name}#{attributes_string}#{self_close && @options.xhtml? ? ' /' : ''}>"
519
- end
520
-
521
- def resolve_newlines
522
- diff = @node.line - @output_line
523
- return "" if diff <= 0
524
- @output_line = @node.line
525
- "\n" * diff
526
- end
527
-
528
- # Get rid of and whitespace at the end of the buffer
529
- # or the merged text
530
- def rstrip_buffer!(index = -1)
531
- last = @to_merge[index]
532
- if last.nil?
533
- push_silent("_hamlout.rstrip!", false)
534
- @dont_tab_up_next_text = true
535
- return
536
- end
537
-
538
- case last.first
539
- when :text
540
- last[1].rstrip!
541
- if last[1].empty?
542
- @to_merge.slice! index
543
- rstrip_buffer! index
544
- end
545
- when :script
546
- last[1].gsub!(/\(haml_temp, (.*?)\);$/, '(haml_temp.rstrip, \1);')
547
- rstrip_buffer! index - 1
548
- else
549
- raise ::Hamlit::HamlSyntaxError.new("[HAML BUG] Undefined entry in ::Hamlit::HamlCompiler@to_merge.")
550
- end
551
- end
552
- end
553
- end
1
+ class Hamlit::HamlCompiler; end