1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | var nodes = require('./nodes')
|
13 | , filters = require('./filters')
|
14 | , doctypes = require('./doctypes')
|
15 | , selfClosing = require('./self-closing')
|
16 | , inlineTags = require('./inline-tags')
|
17 | , utils = require('./utils');
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | var Compiler = module.exports = function Compiler(node, options) {
|
51 | this.options = options = options || {};
|
52 | this.node = node;
|
53 | this.hasCompiledDoctype = false;
|
54 | this.hasCompiledTag = false;
|
55 | this.pp = options.pretty || false;
|
56 | this.debug = false !== options.compileDebug;
|
57 | this.indents = 0;
|
58 | if (options.doctype) this.setDoctype(options.doctype);
|
59 | };
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | Compiler.prototype = {
|
66 |
|
67 | |
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 | compile: function(){
|
74 | this.buf = ['var interp;'];
|
75 | this.lastBufferedIdx = -1
|
76 | this.visit(this.node);
|
77 | return this.buf.join('\n');
|
78 | },
|
79 |
|
80 | |
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | setDoctype: function(name){
|
90 | var doctype = doctypes[(name || 'default').toLowerCase()];
|
91 | if (!doctype) throw new Error('unknown doctype "' + name + '"');
|
92 | this.doctype = doctype;
|
93 | this.terse = '5' == name || 'html' == name;
|
94 | this.xml = 0 == this.doctype.indexOf('<?xml');
|
95 | },
|
96 |
|
97 | |
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 | buffer: function(str, esc){
|
106 | if (esc) str = utils.escape(str);
|
107 |
|
108 | if (this.lastBufferedIdx == this.buf.length) {
|
109 | this.lastBuffered += str;
|
110 | this.buf[this.lastBufferedIdx - 1] = "buf.push('" + this.lastBuffered + "');"
|
111 | } else {
|
112 | this.buf.push("buf.push('" + str + "');");
|
113 | this.lastBuffered = str;
|
114 | this.lastBufferedIdx = this.buf.length;
|
115 | }
|
116 | },
|
117 |
|
118 | |
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 | line: function(node){
|
126 | if (false === node.instrumentLineNumber) return;
|
127 | this.buf.push('__.lineno = ' + node.line + ';');
|
128 | },
|
129 |
|
130 | |
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | visit: function(node){
|
138 | if (this.debug) this.line(node);
|
139 | return this.visitNode(node);
|
140 | },
|
141 |
|
142 | |
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | visitNode: function(node){
|
150 | var name = node.constructor.name
|
151 | || node.constructor.toString().match(/function ([^(\s]+)()/)[1];
|
152 | return this['visit' + name](node);
|
153 | },
|
154 |
|
155 | |
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | visitBlock: function(block){
|
163 | var len = len = block.nodes.length;
|
164 | for (var i = 0; i < len; ++i) {
|
165 | this.visit(block.nodes[i]);
|
166 | }
|
167 | },
|
168 |
|
169 | |
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | visitDoctype: function(doctype){
|
179 | if (doctype && (doctype.val || !this.doctype)) {
|
180 | this.setDoctype(doctype.val || 'default');
|
181 | }
|
182 |
|
183 | if (this.doctype) this.buffer(this.doctype);
|
184 | this.hasCompiledDoctype = true;
|
185 | },
|
186 |
|
187 | |
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 | visitMixin: function(mixin){
|
196 | var name = mixin.name.replace(/-/g, '_') + '_mixin'
|
197 | , args = mixin.args || '';
|
198 |
|
199 | if (mixin.block) {
|
200 | this.buf.push('var ' + name + ' = function(' + args + '){');
|
201 | this.visit(mixin.block);
|
202 | this.buf.push('}');
|
203 | } else {
|
204 | this.buf.push(name + '(' + args + ');');
|
205 | }
|
206 | },
|
207 |
|
208 | |
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 | visitTag: function(tag){
|
217 | this.indents++;
|
218 | var name = tag.name;
|
219 |
|
220 | if (!this.hasCompiledTag) {
|
221 | if (!this.hasCompiledDoctype && 'html' == name) {
|
222 | this.visitDoctype();
|
223 | }
|
224 | this.hasCompiledTag = true;
|
225 | }
|
226 |
|
227 |
|
228 | if (this.pp && inlineTags.indexOf(name) == -1) {
|
229 | this.buffer('\\n' + Array(this.indents).join(' '));
|
230 | }
|
231 |
|
232 | if (~selfClosing.indexOf(name) && !this.xml) {
|
233 | this.buffer('<' + name);
|
234 | this.visitAttributes(tag.attrs);
|
235 | this.terse
|
236 | ? this.buffer('>')
|
237 | : this.buffer('/>');
|
238 | } else {
|
239 |
|
240 | if (tag.attrs.length) {
|
241 | this.buffer('<' + name);
|
242 | if (tag.attrs.length) this.visitAttributes(tag.attrs);
|
243 | this.buffer('>');
|
244 | } else {
|
245 | this.buffer('<' + name + '>');
|
246 | }
|
247 | if (tag.code) this.visitCode(tag.code);
|
248 | if (tag.text) this.buffer(utils.text(tag.text.nodes[0].trimLeft()));
|
249 | this.escape = 'pre' == tag.name;
|
250 | this.visit(tag.block);
|
251 |
|
252 |
|
253 | if (this.pp && !~inlineTags.indexOf(name) && !tag.textOnly) {
|
254 | this.buffer('\\n' + Array(this.indents).join(' '));
|
255 | }
|
256 |
|
257 | this.buffer('</' + name + '>');
|
258 | }
|
259 | this.indents--;
|
260 | },
|
261 |
|
262 | |
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | visitFilter: function(filter){
|
270 | var fn = filters[filter.name];
|
271 |
|
272 |
|
273 | if (!fn) {
|
274 | if (filter.isASTFilter) {
|
275 | throw new Error('unknown ast filter "' + filter.name + ':"');
|
276 | } else {
|
277 | throw new Error('unknown filter ":' + filter.name + '"');
|
278 | }
|
279 | }
|
280 | if (filter.isASTFilter) {
|
281 | this.buf.push(fn(filter.block, this, filter.attrs));
|
282 | } else {
|
283 | var text = filter.block.nodes.join('');
|
284 | this.buffer(utils.text(fn(text, filter.attrs)));
|
285 | }
|
286 | },
|
287 |
|
288 | |
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 | visitText: function(text){
|
296 | text = utils.text(text.nodes.join(''));
|
297 | if (this.escape) text = escape(text);
|
298 | this.buffer(text);
|
299 | this.buffer('\\n');
|
300 | },
|
301 |
|
302 | |
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | visitComment: function(comment){
|
310 | if (!comment.buffer) return;
|
311 | if (this.pp) this.buffer('\\n' + Array(this.indents + 1).join(' '));
|
312 | this.buffer('<!--' + utils.escape(comment.val) + '-->');
|
313 | },
|
314 |
|
315 | |
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 | visitBlockComment: function(comment){
|
323 | if (!comment.buffer) return;
|
324 | if (0 == comment.val.indexOf('if')) {
|
325 | this.buffer('<!--[' + comment.val + ']>');
|
326 | this.visit(comment.block);
|
327 | this.buffer('<![endif]-->');
|
328 | } else {
|
329 | this.buffer('<!--' + comment.val);
|
330 | this.visit(comment.block);
|
331 | this.buffer('-->');
|
332 | }
|
333 | },
|
334 |
|
335 | |
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 | visitCode: function(code){
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 | if (code.buffer) {
|
351 | var val = code.val.trimLeft();
|
352 | this.buf.push('var __val__ = ' + val);
|
353 | val = 'null == __val__ ? "" : __val__';
|
354 | if (code.escape) val = 'escape(' + val + ')';
|
355 | this.buf.push("buf.push(" + val + ");");
|
356 | } else {
|
357 | this.buf.push(code.val);
|
358 | }
|
359 |
|
360 |
|
361 | if (code.block) {
|
362 | if (!code.buffer) this.buf.push('{');
|
363 | this.visit(code.block);
|
364 | if (!code.buffer) this.buf.push('}');
|
365 | }
|
366 | },
|
367 |
|
368 | |
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 | visitEach: function(each){
|
376 | this.buf.push(''
|
377 | + '// iterate ' + each.obj + '\n'
|
378 | + '(function(){\n'
|
379 | + ' if (\'number\' == typeof ' + each.obj + '.length) {\n'
|
380 | + ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
|
381 | + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
|
382 |
|
383 | this.visit(each.block);
|
384 |
|
385 | this.buf.push(''
|
386 | + ' }\n'
|
387 | + ' } else {\n'
|
388 | + ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
|
389 |
|
390 |
|
391 |
|
392 | + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
|
393 |
|
394 | this.visit(each.block);
|
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 | this.buf.push(' }\n }\n}).call(this);\n');
|
401 | },
|
402 |
|
403 | |
404 |
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 | visitAttributes: function(attrs){
|
411 | var buf = []
|
412 | , classes = [];
|
413 |
|
414 | if (this.terse) buf.push('terse: true');
|
415 |
|
416 | attrs.forEach(function(attr){
|
417 | if (attr.name == 'class') {
|
418 | classes.push('(' + attr.val + ')');
|
419 | } else {
|
420 | var pair = "'" + attr.name + "':(" + attr.val + ')';
|
421 | buf.push(pair);
|
422 | }
|
423 | });
|
424 |
|
425 | if (classes.length) {
|
426 | classes = classes.join(" + ' ' + ");
|
427 | buf.push("class: " + classes);
|
428 | }
|
429 |
|
430 | buf = buf.join(', ').replace('class:', '"class":');
|
431 |
|
432 | this.buf.push("buf.push(attrs({ " + buf + " }));");
|
433 | }
|
434 | };
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 | function escape(html){
|
445 | return String(html)
|
446 | .replace(/&(?!\w+;)/g, '&')
|
447 | .replace(/</g, '<')
|
448 | .replace(/>/g, '>')
|
449 | .replace(/"/g, '"');
|
450 | }
|