UNPKG

7.44 kBtext/coffeescriptView Raw
1{escapeHTML} = require('../util/text')
2
3# Base class for the syntax tree.
4#
5# This will provide some methods that subclasses must use in order to generate
6# some output:
7#
8# * {#markText}
9# * {#markRunningCode}
10# * {#markInsertingCode}
11#
12# Each node must mark the `@opener` attribute and can optionally mark the `@closer`
13# attribute.
14#
15# @abstract
16#
17module.exports = class Node
18
19 # Hidden unicode marker to remove left whitespace after template rendering.
20 @CLEAR_WHITESPACE_LEFT = '\u0091'
21
22 # Hidden unicode marker to remove right whitespace after template rendering.
23 @CLEAR_WHITESPACE_RIGHT = '\u0092'
24
25 # Constructs a syntax node.
26 #
27 # @param [String] expression the Haml expression to evaluate
28 # @param [Object] options the node options
29 # @option options [Node] parentNode the parent node
30 # @option options [Number] blockLevel the HTML block level
31 # @option options [Number] codeBlockLevel the CoffeeScript block level
32 # @option options [Boolean] escapeHtml whether to escape the rendered HTML or not
33 # @option options [String] format the template format, either `xhtml`, `html4` or `html5`
34 #
35 constructor: (@expression = '', options = {}) ->
36 @parentNode = options.parentNode
37 @children = []
38
39 @opener = @closer = null
40
41 # A silent node swallows all output
42 @silent = false
43
44 # Preserve whitespace on all children
45 @preserveTags = options.preserveTags.split(',')
46 @preserve = false
47
48 @wsRemoval = {
49 around: false
50 inside: false
51 }
52
53 @escapeHtml = options.escapeHtml
54 @escapeAttributes = options.escapeAttributes
55 @cleanValue = options.cleanValue
56 @format = options.format
57 @hyphenateDataAttrs = options.hyphenateDataAttrs
58 @selfCloseTags = options.selfCloseTags.split(',')
59 @uglify = options.uglify
60
61 @codeBlockLevel = options.codeBlockLevel
62 @blockLevel = options.blockLevel
63
64 @placement = options.placement
65 @namespace = options.namespace
66 @name = options.name
67
68 # Add a child node.
69 #
70 # @param [Node] child the child node
71 #
72 addChild: (child) ->
73 @children.push child
74 @
75
76 # Get the opening tag for the node.
77 #
78 # This may add a hidden unicode control character for
79 # later whitespace processing:
80 #
81 # * `\u0091` Cleanup surrounding whitespace to the left
82 # * `\u0092` Cleanup surrounding whitespace to the right
83 #
84 # @return [String] the opening tag
85 #
86 getOpener: ->
87 @opener.text = Node.CLEAR_WHITESPACE_LEFT + @opener.text if @wsRemoval.around and @opener.text
88 @opener.text += Node.CLEAR_WHITESPACE_RIGHT if @wsRemoval.inside and @opener.text
89
90 @opener
91
92 # Get the closing tag for the node.
93 #
94 # This may add a hidden unicode control character for
95 # later whitespace processing:
96 #
97 # * `\u0091` Cleanup surrounding whitespace to the left
98 # * `\u0092` Cleanup surrounding whitespace to the right
99 #
100 # @return [String] the closing tag
101 #
102 getCloser: ->
103 @closer.text = Node.CLEAR_WHITESPACE_LEFT + @closer.text if @wsRemoval.inside and @closer.text
104 @closer.text += Node.CLEAR_WHITESPACE_RIGHT if @wsRemoval.around and @closer.text
105
106 @closer
107
108 # Traverse up the tree to see if a parent node
109 # is preserving output space.
110 #
111 # @return [Boolean] true when preserved
112 #
113 isPreserved: ->
114 return true if @preserve
115
116 if @parentNode
117 @parentNode.isPreserved()
118 else
119 false
120
121 # Traverse up the tree to see if a parent node
122 # is a comment node.
123 #
124 # @return [Boolean] true when within a comment
125 #
126 isCommented: ->
127 return true if @constructor.name is 'Comment'
128
129 if @parentNode
130 @parentNode.isCommented()
131 else
132 false
133
134 # Creates a marker for static outputted text.
135 #
136 # @param [String] html the html to output
137 # @param [Boolean] escape whether to escape the generated output
138 # @return [Object] the marker
139 #
140 markText: (text, escape = false) ->
141 {
142 type : 'text'
143 cw : @codeBlockLevel
144 hw : if @uglify then 0 else @blockLevel - @codeBlockLevel
145 text : if escape then escapeHTML(text) else text
146 }
147
148 # Creates a marker for running CoffeeScript
149 # code that doesn't generate any output.
150 #
151 # @param [String] code the CoffeeScript code
152 # @return [Object] the marker
153 #
154 markRunningCode: (code) ->
155 {
156 type : 'run'
157 cw : @codeBlockLevel
158 code : code
159 }
160
161 # Creates a marker for inserting CoffeeScript
162 # code that generate an output.
163 #
164 # @param [String] code the CoffeeScript code
165 # @param [Boolean] escape whether to escape the generated output
166 # @param [Boolean] preserve when preserve all newlines
167 # @param [Boolean] findAndPreserve when preserve newlines within preserved tags
168 # @return [Object] the marker
169 #
170 markInsertingCode: (code, escape = false, preserve = false, findAndPreserve = false) ->
171 {
172 type : 'insert'
173 cw : @codeBlockLevel
174 hw : if @uglify then 0 else @blockLevel - @codeBlockLevel
175 escape : escape
176 preserve : preserve
177 findAndPreserve : findAndPreserve
178 code : code
179 }
180
181 # Template method that must be implemented by each
182 # Node subclass. This evaluates the `@expression`
183 # and save marks the output type on the `@opener` and
184 # `@closer` attributes if applicable.
185 #
186 # @abstract
187 #
188 evaluate: ->
189
190 # Render the node and its children.
191 #
192 # Always use `@opener` and `@closer` for content checks,
193 # but `@getOpener()` and `@getCloser()` for outputting,
194 # because they may contain whitespace removal control
195 # characters.
196 #
197 # @return [Array] all markers
198 #
199 render: ->
200 output = []
201
202 # Swallow child output when silent
203 return output if @silent
204
205 # Nodes without children
206 if @children.length is 0
207
208 # Non self closing tag
209 if @opener and @closer
210
211 # Merge tag into a single line
212 tag = @getOpener()
213 tag.text += @getCloser().text
214
215 output.push tag
216
217 # Self closing tag
218 else
219
220 # Whitespace preserved child tag are outputted by the preserving tag
221 if not @preserve && @isPreserved()
222 output.push @getOpener()
223
224 # Normal self closing tag
225 else
226 output.push @getOpener()
227
228 # Nodes with children
229 else
230
231 # Non self closing Haml tag
232 if @opener and @closer
233
234 # Whitespace preserving tag combines children into a single line
235 if @preserve
236
237 # Preserved tags removes the inside whitespace
238 @wsRemoval.inside = true
239
240 output.push @getOpener()
241
242 for child in @children
243 for rendered in child.render()
244 # Move all children's block level to the preserving tag
245 rendered.hw = @blockLevel
246 output.push rendered
247
248 output.push @getCloser()
249
250 # Non preserving tag
251 else
252 output.push @getOpener()
253 output = output.concat(child.render()) for child in @children
254 output.push @getCloser()
255
256 # Block with only an opener
257 else if @opener
258 output.push @getOpener()
259 output = output.concat(child.render()) for child in @children
260
261 # Text and code node or Haml nodes without content
262 else
263 output.push @markText(child.render().text) for child in @children
264
265 output