hamlit 2.11.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +17 -11
- data/CHANGELOG.md +29 -1
- data/Gemfile +1 -7
- data/LICENSE.txt +26 -23
- data/REFERENCE.md +4 -4
- data/bin/update-haml +125 -0
- data/ext/hamlit/hamlit.c +0 -1
- data/lib/hamlit/attribute_builder.rb +2 -2
- data/lib/hamlit/attribute_compiler.rb +3 -3
- data/lib/hamlit/compiler/children_compiler.rb +18 -4
- data/lib/hamlit/compiler/comment_compiler.rb +1 -0
- data/lib/hamlit/filters/escaped.rb +1 -1
- data/lib/hamlit/filters/markdown.rb +1 -0
- data/lib/hamlit/filters/preserve.rb +1 -1
- data/lib/hamlit/filters/text_base.rb +1 -1
- data/lib/hamlit/filters/tilt_base.rb +1 -1
- data/lib/hamlit/parser.rb +6 -2
- data/lib/hamlit/parser/haml_attribute_builder.rb +164 -0
- data/lib/hamlit/parser/haml_buffer.rb +20 -130
- data/lib/hamlit/parser/haml_compiler.rb +1 -553
- data/lib/hamlit/parser/haml_error.rb +29 -25
- data/lib/hamlit/parser/haml_escapable.rb +1 -0
- data/lib/hamlit/parser/haml_generator.rb +1 -0
- data/lib/hamlit/parser/haml_helpers.rb +41 -59
- data/lib/hamlit/parser/{haml_xss_mods.rb → haml_helpers/xss_mods.rb} +20 -15
- data/lib/hamlit/parser/haml_options.rb +53 -66
- data/lib/hamlit/parser/haml_parser.rb +103 -71
- data/lib/hamlit/parser/haml_temple_engine.rb +123 -0
- data/lib/hamlit/parser/haml_util.rb +10 -40
- data/lib/hamlit/rails_template.rb +1 -1
- data/lib/hamlit/string_splitter.rb +1 -0
- data/lib/hamlit/temple_line_counter.rb +31 -0
- data/lib/hamlit/version.rb +1 -1
- metadata +10 -6
- data/lib/hamlit/parser/MIT-LICENSE +0 -20
- data/lib/hamlit/parser/README.md +0 -30
@@ -12,7 +12,7 @@ module Hamlit
|
|
12
12
|
|
13
13
|
def compile_text(text)
|
14
14
|
if ::Hamlit::HamlUtil.contains_interpolation?(text)
|
15
|
-
[:dynamic, ::Hamlit::HamlUtil.
|
15
|
+
[:dynamic, ::Hamlit::HamlUtil.unescape_interpolation(text)]
|
16
16
|
else
|
17
17
|
[:static, text]
|
18
18
|
end
|
@@ -6,7 +6,7 @@ module Hamlit
|
|
6
6
|
text = node.value[:text].rstrip.gsub(/^/, prefix)
|
7
7
|
if ::Hamlit::HamlUtil.contains_interpolation?(node.value[:text])
|
8
8
|
# original: Haml::Filters#compile
|
9
|
-
text = ::Hamlit::HamlUtil.
|
9
|
+
text = ::Hamlit::HamlUtil.unescape_interpolation(text).gsub(/(\\+)n/) do |s|
|
10
10
|
escapes = $1.size
|
11
11
|
next s if escapes % 2 == 0
|
12
12
|
"#{'\\' * (escapes - 1)}\n"
|
@@ -35,7 +35,7 @@ module Hamlit
|
|
35
35
|
|
36
36
|
def dynamic_compile(node, name, indent_width: 0)
|
37
37
|
# original: Haml::Filters#compile
|
38
|
-
text = ::Hamlit::HamlUtil.
|
38
|
+
text = ::Hamlit::HamlUtil.unescape_interpolation(node.value[:text]).gsub(/(\\+)n/) do |s|
|
39
39
|
escapes = $1.size
|
40
40
|
next s if escapes % 2 == 0
|
41
41
|
"#{'\\' * (escapes - 1)}\n"
|
data/lib/hamlit/parser.rb
CHANGED
@@ -2,13 +2,17 @@
|
|
2
2
|
# Hamlit::Parser uses original Haml::Parser to generate Haml AST.
|
3
3
|
# hamlit/parser/haml_* are modules originally in haml gem.
|
4
4
|
|
5
|
+
require 'hamlit/parser/haml_attribute_builder'
|
5
6
|
require 'hamlit/parser/haml_error'
|
6
7
|
require 'hamlit/parser/haml_util'
|
8
|
+
require 'hamlit/parser/haml_helpers'
|
7
9
|
require 'hamlit/parser/haml_buffer'
|
8
10
|
require 'hamlit/parser/haml_compiler'
|
9
11
|
require 'hamlit/parser/haml_parser'
|
10
|
-
require 'hamlit/parser/haml_helpers'
|
11
12
|
require 'hamlit/parser/haml_options'
|
13
|
+
require 'hamlit/parser/haml_escapable'
|
14
|
+
require 'hamlit/parser/haml_generator'
|
15
|
+
require 'hamlit/parser/haml_temple_engine'
|
12
16
|
|
13
17
|
module Hamlit
|
14
18
|
class Parser
|
@@ -29,7 +33,7 @@ module Hamlit
|
|
29
33
|
template = Hamlit::HamlUtil.check_haml_encoding(template) do |msg, line|
|
30
34
|
raise Hamlit::Error.new(msg, line)
|
31
35
|
end
|
32
|
-
HamlParser.new(
|
36
|
+
HamlParser.new(HamlOptions.new(@options)).call(template)
|
33
37
|
rescue ::Hamlit::HamlError => e
|
34
38
|
error_with_lineno(e)
|
35
39
|
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hamlit
|
4
|
+
module HamlAttributeBuilder
|
5
|
+
# https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
|
6
|
+
INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
|
10
|
+
# @TODO this is an absolutely ridiculous amount of arguments. At least
|
11
|
+
# some of this needs to be moved into an instance method.
|
12
|
+
join_char = hyphenate_data_attrs ? '-' : '_'
|
13
|
+
|
14
|
+
attributes.each do |key, value|
|
15
|
+
if value.is_a?(Hash)
|
16
|
+
data_attributes = attributes.delete(key)
|
17
|
+
data_attributes = flatten_data_attributes(data_attributes, '', join_char)
|
18
|
+
data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
|
19
|
+
verify_attribute_names!(data_attributes.keys)
|
20
|
+
attributes = data_attributes.merge(attributes)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
result = attributes.collect do |attr, value|
|
25
|
+
next if value.nil?
|
26
|
+
|
27
|
+
value = filter_and_join(value, ' ') if attr == 'class'
|
28
|
+
value = filter_and_join(value, '_') if attr == 'id'
|
29
|
+
|
30
|
+
if value == true
|
31
|
+
next " #{attr}" if is_html
|
32
|
+
next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
|
33
|
+
elsif value == false
|
34
|
+
next
|
35
|
+
end
|
36
|
+
|
37
|
+
value =
|
38
|
+
if escape_attrs == :once
|
39
|
+
Hamlit::HamlHelpers.escape_once_without_haml_xss(value.to_s)
|
40
|
+
elsif escape_attrs
|
41
|
+
Hamlit::HamlHelpers.html_escape_without_haml_xss(value.to_s)
|
42
|
+
else
|
43
|
+
value.to_s
|
44
|
+
end
|
45
|
+
" #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
|
46
|
+
end
|
47
|
+
result.compact!
|
48
|
+
result.sort!
|
49
|
+
result.join
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String, nil]
|
53
|
+
def filter_and_join(value, separator)
|
54
|
+
return '' if (value.respond_to?(:empty?) && value.empty?)
|
55
|
+
|
56
|
+
if value.is_a?(Array)
|
57
|
+
value = value.flatten
|
58
|
+
value.map! {|item| item ? item.to_s : nil}
|
59
|
+
value.compact!
|
60
|
+
value = value.join(separator)
|
61
|
+
else
|
62
|
+
value = value ? value.to_s : nil
|
63
|
+
end
|
64
|
+
!value.nil? && !value.empty? && value
|
65
|
+
end
|
66
|
+
|
67
|
+
# Merges two attribute hashes.
|
68
|
+
# This is the same as `to.merge!(from)`,
|
69
|
+
# except that it merges id, class, and data attributes.
|
70
|
+
#
|
71
|
+
# ids are concatenated with `"_"`,
|
72
|
+
# and classes are concatenated with `" "`.
|
73
|
+
# data hashes are simply merged.
|
74
|
+
#
|
75
|
+
# Destructively modifies `to`.
|
76
|
+
#
|
77
|
+
# @param to [{String => String,Hash}] The attribute hash to merge into
|
78
|
+
# @param from [{String => Object}] The attribute hash to merge from
|
79
|
+
# @return [{String => String,Hash}] `to`, after being merged
|
80
|
+
def merge_attributes!(to, from)
|
81
|
+
from.keys.each do |key|
|
82
|
+
to[key] = merge_value(key, to[key], from[key])
|
83
|
+
end
|
84
|
+
to
|
85
|
+
end
|
86
|
+
|
87
|
+
# Merge multiple values to one attribute value. No destructive operation.
|
88
|
+
#
|
89
|
+
# @param key [String]
|
90
|
+
# @param values [Array<Object>]
|
91
|
+
# @return [String,Hash]
|
92
|
+
def merge_values(key, *values)
|
93
|
+
values.inject(nil) do |to, from|
|
94
|
+
merge_value(key, to, from)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def verify_attribute_names!(attribute_names)
|
99
|
+
attribute_names.each do |attribute_name|
|
100
|
+
if attribute_name =~ INVALID_ATTRIBUTE_NAME_REGEX
|
101
|
+
raise HamlInvalidAttributeNameError.new("Invalid attribute name '#{attribute_name}' was rendered")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Merge a couple of values to one attribute value. No destructive operation.
|
109
|
+
#
|
110
|
+
# @param to [String,Hash,nil]
|
111
|
+
# @param from [Object]
|
112
|
+
# @return [String,Hash]
|
113
|
+
def merge_value(key, to, from)
|
114
|
+
if from.kind_of?(Hash) || to.kind_of?(Hash)
|
115
|
+
from = { nil => from } if !from.is_a?(Hash)
|
116
|
+
to = { nil => to } if !to.is_a?(Hash)
|
117
|
+
to.merge(from)
|
118
|
+
elsif key == 'id'
|
119
|
+
merged_id = filter_and_join(from, '_')
|
120
|
+
if to && merged_id
|
121
|
+
merged_id = "#{to}_#{merged_id}"
|
122
|
+
elsif to || merged_id
|
123
|
+
merged_id ||= to
|
124
|
+
end
|
125
|
+
merged_id
|
126
|
+
elsif key == 'class'
|
127
|
+
merged_class = filter_and_join(from, ' ')
|
128
|
+
if to && merged_class
|
129
|
+
merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
|
130
|
+
elsif to || merged_class
|
131
|
+
merged_class ||= to
|
132
|
+
end
|
133
|
+
merged_class
|
134
|
+
else
|
135
|
+
from
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def build_data_keys(data_hash, hyphenate, attr_name="data")
|
140
|
+
Hash[data_hash.map do |name, value|
|
141
|
+
if name == nil
|
142
|
+
[attr_name, value]
|
143
|
+
elsif hyphenate
|
144
|
+
["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
|
145
|
+
else
|
146
|
+
["#{attr_name}-#{name}", value]
|
147
|
+
end
|
148
|
+
end]
|
149
|
+
end
|
150
|
+
|
151
|
+
def flatten_data_attributes(data, key, join_char, seen = [])
|
152
|
+
return {key => data} unless data.is_a?(Hash)
|
153
|
+
|
154
|
+
return {key => nil} if seen.include? data.object_id
|
155
|
+
seen << data.object_id
|
156
|
+
|
157
|
+
data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
|
158
|
+
joined = key == '' ? k : [key, k].join(join_char)
|
159
|
+
hash.merge! flatten_data_attributes(v, joined, join_char, seen)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
require 'hamlit/parser/haml_util'
|
3
|
-
require 'hamlit/parser/haml_compiler'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
3
|
module Hamlit
|
6
4
|
# This class is used only internally. It holds the buffer of HTML that
|
@@ -8,12 +6,8 @@ module Hamlit
|
|
8
6
|
# It's called from within the precompiled code,
|
9
7
|
# and helps reduce the amount of processing done within `instance_eval`ed code.
|
10
8
|
class HamlBuffer
|
11
|
-
|
12
|
-
|
13
|
-
DATA_KEY = 'data'.freeze
|
14
|
-
|
15
|
-
include ::Hamlit::HamlHelpers
|
16
|
-
include ::Hamlit::HamlUtil
|
9
|
+
include Hamlit::HamlHelpers
|
10
|
+
include Hamlit::HamlUtil
|
17
11
|
|
18
12
|
# The string that holds the compiled HTML. This is aliased as
|
19
13
|
# `_erbout` for compatibility with ERB-specific code.
|
@@ -21,10 +15,10 @@ module Hamlit
|
|
21
15
|
# @return [String]
|
22
16
|
attr_accessor :buffer
|
23
17
|
|
24
|
-
# The options hash passed in from {
|
18
|
+
# The options hash passed in from {Hamlit::HamlEngine}.
|
25
19
|
#
|
26
20
|
# @return [{String => Object}]
|
27
|
-
# @see
|
21
|
+
# @see Hamlit::HamlOptions#for_buffer
|
28
22
|
attr_accessor :options
|
29
23
|
|
30
24
|
# The {Buffer} for the enclosing Haml document.
|
@@ -94,11 +88,12 @@ module Hamlit
|
|
94
88
|
|
95
89
|
# @param upper [Buffer] The parent buffer
|
96
90
|
# @param options [{Symbol => Object}] An options hash.
|
97
|
-
# See {
|
91
|
+
# See {Hamlit::HamlEngine#options\_for\_buffer}
|
98
92
|
def initialize(upper = nil, options = {})
|
99
93
|
@active = true
|
100
94
|
@upper = upper
|
101
|
-
@options =
|
95
|
+
@options = HamlOptions.buffer_defaults
|
96
|
+
@options = @options.merge(options) unless options.empty?
|
102
97
|
@buffer = new_encoded_string
|
103
98
|
@tabulation = 0
|
104
99
|
|
@@ -135,70 +130,15 @@ module Hamlit
|
|
135
130
|
@real_tabs += tab_change
|
136
131
|
end
|
137
132
|
|
138
|
-
# the number of arguments here is insane, but passing in an options hash instead of named arguments
|
139
|
-
# causes a significant performance regression
|
140
|
-
def format_script(result, preserve_script, in_tag, preserve_tag, escape_html, nuke_inner_whitespace, interpolated, ugly)
|
141
|
-
result_name = escape_html ? html_escape(result.to_s) : result.to_s
|
142
|
-
|
143
|
-
if ugly
|
144
|
-
result = nuke_inner_whitespace ? result_name.strip : result_name
|
145
|
-
result = preserve(result, preserve_script, preserve_tag)
|
146
|
-
fix_textareas!(result) if toplevel? && result.include?('<textarea')
|
147
|
-
return result
|
148
|
-
end
|
149
|
-
|
150
|
-
# If we're interpolated,
|
151
|
-
# then the custom tabulation is handled in #push_text.
|
152
|
-
# The easiest way to avoid it here is to reset @tabulation.
|
153
|
-
if interpolated
|
154
|
-
old_tabulation = @tabulation
|
155
|
-
@tabulation = 0
|
156
|
-
end
|
157
|
-
|
158
|
-
in_tag_no_nuke = in_tag && !nuke_inner_whitespace
|
159
|
-
preserved_no_nuke = in_tag_no_nuke && preserve_tag
|
160
|
-
tabulation = !preserved_no_nuke && @real_tabs
|
161
|
-
|
162
|
-
result = nuke_inner_whitespace ? result_name.strip : result_name.rstrip
|
163
|
-
result = preserve(result, preserve_script, preserve_tag)
|
164
|
-
|
165
|
-
has_newline = !preserved_no_nuke && result.include?("\n")
|
166
|
-
|
167
|
-
if in_tag_no_nuke && (preserve_tag || !has_newline)
|
168
|
-
@real_tabs -= 1
|
169
|
-
@tabulation = old_tabulation if interpolated
|
170
|
-
return result
|
171
|
-
end
|
172
|
-
|
173
|
-
unless preserved_no_nuke
|
174
|
-
# Precompiled tabulation may be wrong
|
175
|
-
result = "#{tabs}#{result}" if !interpolated && !in_tag && @tabulation > 0
|
176
|
-
|
177
|
-
if has_newline
|
178
|
-
result.gsub! "\n", "\n#{tabs(tabulation)}"
|
179
|
-
|
180
|
-
# Add tabulation if it wasn't precompiled
|
181
|
-
result = "#{tabs(tabulation)}#{result}" if in_tag_no_nuke
|
182
|
-
end
|
183
|
-
|
184
|
-
fix_textareas!(result) if toplevel? && result.include?('<textarea')
|
185
|
-
|
186
|
-
if in_tag_no_nuke
|
187
|
-
result = "\n#{result}\n#{tabs(tabulation-1)}"
|
188
|
-
@real_tabs -= 1
|
189
|
-
end
|
190
|
-
@tabulation = old_tabulation if interpolated
|
191
|
-
result
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
133
|
def attributes(class_id, obj_ref, *attributes_hashes)
|
196
134
|
attributes = class_id
|
197
135
|
attributes_hashes.each do |old|
|
198
|
-
|
136
|
+
result = {}
|
137
|
+
old.each { |k, v| result[k.to_s] = v }
|
138
|
+
HamlAttributeBuilder.merge_attributes!(attributes, result)
|
199
139
|
end
|
200
|
-
|
201
|
-
|
140
|
+
HamlAttributeBuilder.merge_attributes!(attributes, parse_object_ref(obj_ref)) if obj_ref
|
141
|
+
HamlAttributeBuilder.build_attributes(
|
202
142
|
html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
|
203
143
|
end
|
204
144
|
|
@@ -213,61 +153,6 @@ module Hamlit
|
|
213
153
|
buffer << buffer.slice!(capture_position..-1).rstrip
|
214
154
|
end
|
215
155
|
|
216
|
-
# Merges two attribute hashes.
|
217
|
-
# This is the same as `to.merge!(from)`,
|
218
|
-
# except that it merges id, class, and data attributes.
|
219
|
-
#
|
220
|
-
# ids are concatenated with `"_"`,
|
221
|
-
# and classes are concatenated with `" "`.
|
222
|
-
# data hashes are simply merged.
|
223
|
-
#
|
224
|
-
# Destructively modifies both `to` and `from`.
|
225
|
-
#
|
226
|
-
# @param to [{String => String}] The attribute hash to merge into
|
227
|
-
# @param from [{String => #to_s}] The attribute hash to merge from
|
228
|
-
# @return [{String => String}] `to`, after being merged
|
229
|
-
def self.merge_attrs(to, from)
|
230
|
-
from[ID_KEY] = ::Hamlit::HamlCompiler.filter_and_join(from[ID_KEY], '_') if from[ID_KEY]
|
231
|
-
if to[ID_KEY] && from[ID_KEY]
|
232
|
-
to[ID_KEY] << "_#{from.delete(ID_KEY)}"
|
233
|
-
elsif to[ID_KEY] || from[ID_KEY]
|
234
|
-
from[ID_KEY] ||= to[ID_KEY]
|
235
|
-
end
|
236
|
-
|
237
|
-
from[CLASS_KEY] = ::Hamlit::HamlCompiler.filter_and_join(from[CLASS_KEY], ' ') if from[CLASS_KEY]
|
238
|
-
if to[CLASS_KEY] && from[CLASS_KEY]
|
239
|
-
# Make sure we don't duplicate class names
|
240
|
-
from[CLASS_KEY] = (from[CLASS_KEY].to_s.split(' ') | to[CLASS_KEY].split(' ')).sort.join(' ')
|
241
|
-
elsif to[CLASS_KEY] || from[CLASS_KEY]
|
242
|
-
from[CLASS_KEY] ||= to[CLASS_KEY]
|
243
|
-
end
|
244
|
-
|
245
|
-
from.keys.each do |key|
|
246
|
-
next unless from[key].kind_of?(Hash) || to[key].kind_of?(Hash)
|
247
|
-
|
248
|
-
from_data = from.delete(key)
|
249
|
-
# forces to_data & from_data into a hash
|
250
|
-
from_data = { nil => from_data } if from_data && !from_data.is_a?(Hash)
|
251
|
-
to[key] = { nil => to[key] } if to[key] && !to[key].is_a?(Hash)
|
252
|
-
|
253
|
-
if from_data && !to[key]
|
254
|
-
to[key] = from_data
|
255
|
-
elsif from_data && to[key]
|
256
|
-
to[key].merge! from_data
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
to.merge!(from)
|
261
|
-
end
|
262
|
-
|
263
|
-
private
|
264
|
-
|
265
|
-
def preserve(result, preserve_script, preserve_tag)
|
266
|
-
return ::Hamlit::HamlHelpers.preserve(result) if preserve_tag
|
267
|
-
return ::Hamlit::HamlHelpers.find_and_preserve(result, options[:preserve]) if preserve_script
|
268
|
-
result
|
269
|
-
end
|
270
|
-
|
271
156
|
# Works like #{find_and_preserve}, but allows the first newline after a
|
272
157
|
# preserved opening tag to remain unencoded, and then outdents the content.
|
273
158
|
# This change was motivated primarily by the change in Rails 3.2.3 to emit
|
@@ -277,6 +162,8 @@ module Hamlit
|
|
277
162
|
# @since Haml 4.0.1
|
278
163
|
# @private
|
279
164
|
def fix_textareas!(input)
|
165
|
+
return input unless input.include?('<textarea'.freeze)
|
166
|
+
|
280
167
|
pattern = /<(textarea)([^>]*)>(\n|
)(.*?)<\/textarea>/im
|
281
168
|
input.gsub!(pattern) do |s|
|
282
169
|
match = pattern.match(s)
|
@@ -288,10 +175,13 @@ module Hamlit
|
|
288
175
|
end
|
289
176
|
"<#{match[1]}#{match[2]}>\n#{content}</#{match[1]}>"
|
290
177
|
end
|
178
|
+
input
|
291
179
|
end
|
292
180
|
|
181
|
+
private
|
182
|
+
|
293
183
|
def new_encoded_string
|
294
|
-
"".encode(
|
184
|
+
"".encode(options[:encoding])
|
295
185
|
end
|
296
186
|
|
297
187
|
@@tab_cache = {}
|
@@ -329,7 +219,7 @@ module Hamlit
|
|
329
219
|
id = "#{ prefix }_#{ id }"
|
330
220
|
end
|
331
221
|
|
332
|
-
{
|
222
|
+
{ 'id'.freeze => id, 'class'.freeze => class_name }
|
333
223
|
end
|
334
224
|
|
335
225
|
# Changes a word from camel case to underscores.
|
@@ -1,553 +1 @@
|
|
1
|
-
|
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 == '"' ? """ : "'"
|
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!(/"|"/, '"')
|
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
|