1 | define([ |
---|
2 | "dojo/_base/kernel", |
---|
3 | "dojo/_base/lang", |
---|
4 | "dojox/string/tokenize", |
---|
5 | "dojo/_base/json", |
---|
6 | "dojo/dom", |
---|
7 | "dojo/_base/xhr", |
---|
8 | "dojox/string/Builder", |
---|
9 | "dojo/_base/Deferred"], |
---|
10 | function(kernel, lang, Tokenize, json, dom, xhr, StringBuilder, deferred){ |
---|
11 | |
---|
12 | kernel.experimental("dojox.dtl"); |
---|
13 | |
---|
14 | var dd = lang.getObject("dojox.dtl", true); |
---|
15 | /*===== |
---|
16 | dd = { |
---|
17 | // TODO: summary |
---|
18 | }; |
---|
19 | =====*/ |
---|
20 | |
---|
21 | dd._base = {}; |
---|
22 | |
---|
23 | dd.TOKEN_BLOCK = -1; |
---|
24 | dd.TOKEN_VAR = -2; |
---|
25 | dd.TOKEN_COMMENT = -3; |
---|
26 | dd.TOKEN_TEXT = 3; |
---|
27 | |
---|
28 | dd._Context = lang.extend(function(dict){ |
---|
29 | // summary: |
---|
30 | // Pass one of these when rendering a template to tell the template what values to use. |
---|
31 | if(dict){ |
---|
32 | lang._mixin(this, dict); |
---|
33 | if(dict.get){ |
---|
34 | // Preserve passed getter and restore prototype get |
---|
35 | this._getter = dict.get; |
---|
36 | delete this.get; |
---|
37 | } |
---|
38 | } |
---|
39 | }, |
---|
40 | { |
---|
41 | push: function(){ |
---|
42 | var last = this; |
---|
43 | var context = lang.delegate(this); |
---|
44 | context.pop = function(){ return last; } |
---|
45 | return context; |
---|
46 | }, |
---|
47 | pop: function(){ |
---|
48 | throw new Error("pop() called on empty Context"); |
---|
49 | }, |
---|
50 | get: function(key, otherwise){ |
---|
51 | var n = this._normalize; |
---|
52 | |
---|
53 | if(this._getter){ |
---|
54 | var got = this._getter(key); |
---|
55 | if(got !== undefined){ |
---|
56 | return n(got); |
---|
57 | } |
---|
58 | } |
---|
59 | |
---|
60 | if(this[key] !== undefined){ |
---|
61 | return n(this[key]); |
---|
62 | } |
---|
63 | |
---|
64 | return otherwise; |
---|
65 | }, |
---|
66 | _normalize: function(value){ |
---|
67 | if(value instanceof Date){ |
---|
68 | value.year = value.getFullYear(); |
---|
69 | value.month = value.getMonth() + 1; |
---|
70 | value.day = value.getDate(); |
---|
71 | value.date = value.year + "-" + ("0" + value.month).slice(-2) + "-" + ("0" + value.day).slice(-2); |
---|
72 | value.hour = value.getHours(); |
---|
73 | value.minute = value.getMinutes(); |
---|
74 | value.second = value.getSeconds(); |
---|
75 | value.microsecond = value.getMilliseconds(); |
---|
76 | } |
---|
77 | return value; |
---|
78 | }, |
---|
79 | update: function(dict){ |
---|
80 | var context = this.push(); |
---|
81 | if(dict){ |
---|
82 | lang._mixin(this, dict); |
---|
83 | } |
---|
84 | return context; |
---|
85 | } |
---|
86 | }); |
---|
87 | |
---|
88 | var smart_split_re = /("(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+)/g; |
---|
89 | var split_re = /\s+/g; |
---|
90 | var split = function(/*String|RegExp?*/ splitter, /*Integer?*/ limit){ |
---|
91 | splitter = splitter || split_re; |
---|
92 | if(!(splitter instanceof RegExp)){ |
---|
93 | splitter = new RegExp(splitter, "g"); |
---|
94 | } |
---|
95 | if(!splitter.global){ |
---|
96 | throw new Error("You must use a globally flagged RegExp with split " + splitter); |
---|
97 | } |
---|
98 | splitter.exec(""); // Reset the global |
---|
99 | |
---|
100 | var part, parts = [], lastIndex = 0, i = 0; |
---|
101 | while((part = splitter.exec(this))){ |
---|
102 | parts.push(this.slice(lastIndex, splitter.lastIndex - part[0].length)); |
---|
103 | lastIndex = splitter.lastIndex; |
---|
104 | if(limit && (++i > limit - 1)){ |
---|
105 | break; |
---|
106 | } |
---|
107 | } |
---|
108 | parts.push(this.slice(lastIndex)); |
---|
109 | return parts; |
---|
110 | }; |
---|
111 | |
---|
112 | dd.Token = function(token_type, contents){ |
---|
113 | // tags: |
---|
114 | // private |
---|
115 | this.token_type = token_type; |
---|
116 | this.contents = new String(lang.trim(contents)); |
---|
117 | this.contents.split = split; |
---|
118 | this.split = function(){ |
---|
119 | return String.prototype.split.apply(this.contents, arguments); |
---|
120 | } |
---|
121 | }; |
---|
122 | dd.Token.prototype.split_contents = function(/*Integer?*/ limit){ |
---|
123 | var bit, bits = [], i = 0; |
---|
124 | limit = limit || 999; |
---|
125 | while(i++ < limit && (bit = smart_split_re.exec(this.contents))){ |
---|
126 | bit = bit[0]; |
---|
127 | if(bit.charAt(0) == '"' && bit.slice(-1) == '"'){ |
---|
128 | bits.push('"' + bit.slice(1, -1).replace('\\"', '"').replace('\\\\', '\\') + '"'); |
---|
129 | }else if(bit.charAt(0) == "'" && bit.slice(-1) == "'"){ |
---|
130 | bits.push("'" + bit.slice(1, -1).replace("\\'", "'").replace('\\\\', '\\') + "'"); |
---|
131 | }else{ |
---|
132 | bits.push(bit); |
---|
133 | } |
---|
134 | } |
---|
135 | return bits; |
---|
136 | }; |
---|
137 | |
---|
138 | var ddt = dd.text = { |
---|
139 | _get: function(module, name, errorless){ |
---|
140 | // summary: |
---|
141 | // Used to find both tags and filters |
---|
142 | var params = dd.register.get(module, name.toLowerCase(), errorless); |
---|
143 | if(!params){ |
---|
144 | if(!errorless){ |
---|
145 | throw new Error("No tag found for " + name); |
---|
146 | } |
---|
147 | return null; |
---|
148 | } |
---|
149 | |
---|
150 | var fn = params[1]; |
---|
151 | var deps = params[2]; |
---|
152 | |
---|
153 | var parts; |
---|
154 | if(fn.indexOf(":") != -1){ |
---|
155 | parts = fn.split(":"); |
---|
156 | fn = parts.pop(); |
---|
157 | } |
---|
158 | |
---|
159 | // FIXME: THIS DESIGN DOES NOT WORK WITH ASYNC LOADERS! |
---|
160 | var mod = deps; |
---|
161 | if (/\./.test(deps)) { |
---|
162 | deps = deps.replace(/\./g, "/"); |
---|
163 | } |
---|
164 | require([deps], function(){}); |
---|
165 | |
---|
166 | var parent = lang.getObject(mod); |
---|
167 | |
---|
168 | return parent[fn || name] || parent[name + "_"] || parent[fn + "_"]; |
---|
169 | }, |
---|
170 | getTag: function(name, errorless){ |
---|
171 | return ddt._get("tag", name, errorless); |
---|
172 | }, |
---|
173 | getFilter: function(name, errorless){ |
---|
174 | return ddt._get("filter", name, errorless); |
---|
175 | }, |
---|
176 | getTemplate: function(file){ |
---|
177 | return new dd.Template(ddt.getTemplateString(file)); |
---|
178 | }, |
---|
179 | getTemplateString: function(file){ |
---|
180 | return xhr._getText(file.toString()) || ""; |
---|
181 | }, |
---|
182 | _resolveLazy: function(location, sync, json){ |
---|
183 | if(sync){ |
---|
184 | if(json){ |
---|
185 | return json.fromJson(xhr._getText(location)) || {}; |
---|
186 | }else{ |
---|
187 | return dd.text.getTemplateString(location); |
---|
188 | } |
---|
189 | }else{ |
---|
190 | return xhr.get({ |
---|
191 | handleAs: json ? "json" : "text", |
---|
192 | url: location |
---|
193 | }); |
---|
194 | } |
---|
195 | }, |
---|
196 | _resolveTemplateArg: function(arg, sync){ |
---|
197 | if(ddt._isTemplate(arg)){ |
---|
198 | if(!sync){ |
---|
199 | var d = new deferred(); |
---|
200 | d.callback(arg); |
---|
201 | return d; |
---|
202 | } |
---|
203 | return arg; |
---|
204 | } |
---|
205 | return ddt._resolveLazy(arg, sync); |
---|
206 | }, |
---|
207 | _isTemplate: function(arg){ |
---|
208 | return (arg === undefined) || (typeof arg == "string" && (arg.match(/^\s*[<{]/) || arg.indexOf(" ") != -1)); |
---|
209 | }, |
---|
210 | _resolveContextArg: function(arg, sync){ |
---|
211 | if(arg.constructor == Object){ |
---|
212 | if(!sync){ |
---|
213 | var d = new deferred; |
---|
214 | d.callback(arg); |
---|
215 | return d; |
---|
216 | } |
---|
217 | return arg; |
---|
218 | } |
---|
219 | return ddt._resolveLazy(arg, sync, true); |
---|
220 | }, |
---|
221 | _re: /(?:\{\{\s*(.+?)\s*\}\}|\{%\s*(load\s*)?(.+?)\s*%\})/g, |
---|
222 | tokenize: function(str){ |
---|
223 | return Tokenize(str, ddt._re, ddt._parseDelims); |
---|
224 | }, |
---|
225 | _parseDelims: function(varr, load, tag){ |
---|
226 | if(varr){ |
---|
227 | return [dd.TOKEN_VAR, varr]; |
---|
228 | }else if(load){ |
---|
229 | var parts = lang.trim(tag).split(/\s+/g); |
---|
230 | for(var i = 0, part; part = parts[i]; i++){ |
---|
231 | if (/\./.test(part)){ |
---|
232 | part = part.replace(/\./g,"/"); |
---|
233 | } |
---|
234 | require([part]); |
---|
235 | } |
---|
236 | }else{ |
---|
237 | return [dd.TOKEN_BLOCK, tag]; |
---|
238 | } |
---|
239 | } |
---|
240 | }; |
---|
241 | |
---|
242 | dd.Template = lang.extend(function(/*String|dojo._Url*/ template, /*Boolean*/ isString){ |
---|
243 | // summary: |
---|
244 | // The base class for text-based templates. |
---|
245 | // template: String|dojo/_base/url |
---|
246 | // The string or location of the string to |
---|
247 | // use as a template |
---|
248 | // isString: Boolean |
---|
249 | // Indicates whether the template is a string or a url. |
---|
250 | var str = isString ? template : ddt._resolveTemplateArg(template, true) || ""; |
---|
251 | var tokens = ddt.tokenize(str); |
---|
252 | var parser = new dd._Parser(tokens); |
---|
253 | this.nodelist = parser.parse(); |
---|
254 | }, |
---|
255 | { |
---|
256 | update: function(node, context){ |
---|
257 | // summary: |
---|
258 | // Updates this template according to the given context. |
---|
259 | // node: DOMNode|String|dojo/NodeList |
---|
260 | // A node reference or set of nodes |
---|
261 | // context: dojo/base/url|String|Object |
---|
262 | // The context object or location |
---|
263 | return ddt._resolveContextArg(context).addCallback(this, function(contextObject){ |
---|
264 | var content = this.render(new dd._Context(contextObject)); |
---|
265 | if(node.forEach){ |
---|
266 | node.forEach(function(item){ |
---|
267 | item.innerHTML = content; |
---|
268 | }); |
---|
269 | }else{ |
---|
270 | dom.byId(node).innerHTML = content; |
---|
271 | } |
---|
272 | return this; |
---|
273 | }); |
---|
274 | }, |
---|
275 | render: function(context, buffer){ |
---|
276 | // summary: |
---|
277 | // Renders this template. |
---|
278 | // context: Object |
---|
279 | // The runtime context. |
---|
280 | // buffer: StringBuilder? |
---|
281 | // A string buffer. |
---|
282 | buffer = buffer || this.getBuffer(); |
---|
283 | context = context || new dd._Context({}); |
---|
284 | return this.nodelist.render(context, buffer) + ""; |
---|
285 | }, |
---|
286 | getBuffer: function(){ |
---|
287 | return new StringBuilder(); |
---|
288 | } |
---|
289 | }); |
---|
290 | |
---|
291 | var qfRe = /\{\{\s*(.+?)\s*\}\}/g; |
---|
292 | dd.quickFilter = function(str){ |
---|
293 | if(!str){ |
---|
294 | return new dd._NodeList(); |
---|
295 | } |
---|
296 | |
---|
297 | if(str.indexOf("{%") == -1){ |
---|
298 | return new dd._QuickNodeList(Tokenize(str, qfRe, function(token){ |
---|
299 | return new dd._Filter(token); |
---|
300 | })); |
---|
301 | } |
---|
302 | }; |
---|
303 | |
---|
304 | dd._QuickNodeList = lang.extend(function(contents){ |
---|
305 | this.contents = contents; |
---|
306 | }, |
---|
307 | { |
---|
308 | render: function(context, buffer){ |
---|
309 | for(var i = 0, l = this.contents.length; i < l; i++){ |
---|
310 | if(this.contents[i].resolve){ |
---|
311 | buffer = buffer.concat(this.contents[i].resolve(context)); |
---|
312 | }else{ |
---|
313 | buffer = buffer.concat(this.contents[i]); |
---|
314 | } |
---|
315 | } |
---|
316 | return buffer; |
---|
317 | }, |
---|
318 | dummyRender: function(context){ return this.render(context, dd.Template.prototype.getBuffer()).toString(); }, |
---|
319 | clone: function(buffer){ return this; } |
---|
320 | }); |
---|
321 | |
---|
322 | dd._Filter = lang.extend(function(token){ |
---|
323 | // summary: |
---|
324 | // Uses a string to find (and manipulate) a variable |
---|
325 | if(!token) throw new Error("Filter must be called with variable name"); |
---|
326 | this.contents = token; |
---|
327 | |
---|
328 | var cache = this._cache[token]; |
---|
329 | if(cache){ |
---|
330 | this.key = cache[0]; |
---|
331 | this.filters = cache[1]; |
---|
332 | }else{ |
---|
333 | this.filters = []; |
---|
334 | Tokenize(token, this._re, this._tokenize, this); |
---|
335 | this._cache[token] = [this.key, this.filters]; |
---|
336 | } |
---|
337 | }, |
---|
338 | { |
---|
339 | _cache: {}, |
---|
340 | _re: /(?:^_\("([^\\"]*(?:\\.[^\\"])*)"\)|^"([^\\"]*(?:\\.[^\\"]*)*)"|^([a-zA-Z0-9_.]+)|\|(\w+)(?::(?:_\("([^\\"]*(?:\\.[^\\"])*)"\)|"([^\\"]*(?:\\.[^\\"]*)*)"|([a-zA-Z0-9_.]+)|'([^\\']*(?:\\.[^\\']*)*)'))?|^'([^\\']*(?:\\.[^\\']*)*)')/g, |
---|
341 | _values: { |
---|
342 | 0: '"', // _("text") |
---|
343 | 1: '"', // "text" |
---|
344 | 2: "", // variable |
---|
345 | 8: '"' // 'text' |
---|
346 | }, |
---|
347 | _args: { |
---|
348 | 4: '"', // :_("text") |
---|
349 | 5: '"', // :"text" |
---|
350 | 6: "", // :variable |
---|
351 | 7: "'"// :'text' |
---|
352 | }, |
---|
353 | _tokenize: function(){ |
---|
354 | var pos, arg; |
---|
355 | |
---|
356 | for(var i = 0, has = []; i < arguments.length; i++){ |
---|
357 | has[i] = (arguments[i] !== undefined && typeof arguments[i] == "string" && arguments[i]); |
---|
358 | } |
---|
359 | |
---|
360 | if(!this.key){ |
---|
361 | for(pos in this._values){ |
---|
362 | if(has[pos]){ |
---|
363 | this.key = this._values[pos] + arguments[pos] + this._values[pos]; |
---|
364 | break; |
---|
365 | } |
---|
366 | } |
---|
367 | }else{ |
---|
368 | for(pos in this._args){ |
---|
369 | if(has[pos]){ |
---|
370 | var value = arguments[pos]; |
---|
371 | if(this._args[pos] == "'"){ |
---|
372 | value = value.replace(/\\'/g, "'"); |
---|
373 | }else if(this._args[pos] == '"'){ |
---|
374 | value = value.replace(/\\"/g, '"'); |
---|
375 | } |
---|
376 | arg = [!this._args[pos], value]; |
---|
377 | break; |
---|
378 | } |
---|
379 | } |
---|
380 | // Get a named filter |
---|
381 | var fn = ddt.getFilter(arguments[3]); |
---|
382 | if(!lang.isFunction(fn)) throw new Error(arguments[3] + " is not registered as a filter"); |
---|
383 | this.filters.push([fn, arg]); |
---|
384 | } |
---|
385 | }, |
---|
386 | getExpression: function(){ |
---|
387 | return this.contents; |
---|
388 | }, |
---|
389 | resolve: function(context){ |
---|
390 | if(this.key === undefined){ |
---|
391 | return ""; |
---|
392 | } |
---|
393 | |
---|
394 | var str = this.resolvePath(this.key, context); |
---|
395 | |
---|
396 | for(var i = 0, filter; filter = this.filters[i]; i++){ |
---|
397 | // Each filter has the function in [0], a boolean in [1][0] of whether it's a variable or a string |
---|
398 | // and [1][1] is either the variable name of the string content. |
---|
399 | if(filter[1]){ |
---|
400 | if(filter[1][0]){ |
---|
401 | str = filter[0](str, this.resolvePath(filter[1][1], context)); |
---|
402 | }else{ |
---|
403 | str = filter[0](str, filter[1][1]); |
---|
404 | } |
---|
405 | }else{ |
---|
406 | str = filter[0](str); |
---|
407 | } |
---|
408 | } |
---|
409 | |
---|
410 | return str; |
---|
411 | }, |
---|
412 | resolvePath: function(path, context){ |
---|
413 | var current, parts; |
---|
414 | var first = path.charAt(0); |
---|
415 | var last = path.slice(-1); |
---|
416 | if(!isNaN(parseInt(first))){ |
---|
417 | current = (path.indexOf(".") == -1) ? parseInt(path) : parseFloat(path); |
---|
418 | }else if(first == '"' && first == last){ |
---|
419 | current = path.slice(1, -1); |
---|
420 | }else{ |
---|
421 | if(path == "true"){ return true; } |
---|
422 | if(path == "false"){ return false; } |
---|
423 | if(path == "null" || path == "None"){ return null; } |
---|
424 | parts = path.split("."); |
---|
425 | current = context.get(parts[0]); |
---|
426 | |
---|
427 | if(lang.isFunction(current)){ |
---|
428 | var self = context.getThis && context.getThis(); |
---|
429 | if(current.alters_data){ |
---|
430 | current = ""; |
---|
431 | }else if(self){ |
---|
432 | current = current.call(self); |
---|
433 | }else{ |
---|
434 | current = ""; |
---|
435 | } |
---|
436 | } |
---|
437 | |
---|
438 | for(var i = 1; i < parts.length; i++){ |
---|
439 | var part = parts[i]; |
---|
440 | if(current){ |
---|
441 | var base = current; |
---|
442 | if(lang.isObject(current) && part == "items" && current[part] === undefined){ |
---|
443 | var items = []; |
---|
444 | for(var key in current){ |
---|
445 | items.push([key, current[key]]); |
---|
446 | } |
---|
447 | current = items; |
---|
448 | continue; |
---|
449 | } |
---|
450 | |
---|
451 | if(current.get && lang.isFunction(current.get) && current.get.safe){ |
---|
452 | current = current.get(part); |
---|
453 | }else if(current[part] === undefined){ |
---|
454 | current = current[part]; |
---|
455 | break; |
---|
456 | }else{ |
---|
457 | current = current[part]; |
---|
458 | } |
---|
459 | |
---|
460 | if(lang.isFunction(current)){ |
---|
461 | if(current.alters_data){ |
---|
462 | current = ""; |
---|
463 | }else{ |
---|
464 | current = current.call(base); |
---|
465 | } |
---|
466 | }else if(current instanceof Date){ |
---|
467 | current = dd._Context.prototype._normalize(current); |
---|
468 | } |
---|
469 | }else{ |
---|
470 | return ""; |
---|
471 | } |
---|
472 | } |
---|
473 | } |
---|
474 | return current; |
---|
475 | } |
---|
476 | }); |
---|
477 | |
---|
478 | dd._TextNode = dd._Node = lang.extend(function(/*Object*/ obj){ |
---|
479 | // summary: |
---|
480 | // Basic catch-all node |
---|
481 | this.contents = obj; |
---|
482 | }, |
---|
483 | { |
---|
484 | set: function(data){ |
---|
485 | this.contents = data; |
---|
486 | return this; |
---|
487 | }, |
---|
488 | render: function(context, buffer){ |
---|
489 | // summary: |
---|
490 | // Adds content onto the buffer |
---|
491 | return buffer.concat(this.contents); |
---|
492 | }, |
---|
493 | isEmpty: function(){ |
---|
494 | return !lang.trim(this.contents); |
---|
495 | }, |
---|
496 | clone: function(){ return this; } |
---|
497 | }); |
---|
498 | |
---|
499 | dd._NodeList = lang.extend(function(/*Node[]*/ nodes){ |
---|
500 | // summary: |
---|
501 | // Allows us to render a group of nodes |
---|
502 | this.contents = nodes || []; |
---|
503 | this.last = ""; |
---|
504 | }, |
---|
505 | { |
---|
506 | push: function(node){ |
---|
507 | // summary: |
---|
508 | // Add a new node to the list |
---|
509 | this.contents.push(node); |
---|
510 | return this; |
---|
511 | }, |
---|
512 | concat: function(nodes){ |
---|
513 | this.contents = this.contents.concat(nodes); |
---|
514 | return this; |
---|
515 | }, |
---|
516 | render: function(context, buffer){ |
---|
517 | // summary: |
---|
518 | // Adds all content onto the buffer |
---|
519 | for(var i = 0; i < this.contents.length; i++){ |
---|
520 | buffer = this.contents[i].render(context, buffer); |
---|
521 | if(!buffer) throw new Error("Template must return buffer"); |
---|
522 | } |
---|
523 | return buffer; |
---|
524 | }, |
---|
525 | dummyRender: function(context){ |
---|
526 | return this.render(context, dd.Template.prototype.getBuffer()).toString(); |
---|
527 | }, |
---|
528 | unrender: function(){ return arguments[1]; }, |
---|
529 | clone: function(){ return this; }, |
---|
530 | rtrim: function(){ |
---|
531 | while(1){ |
---|
532 | i = this.contents.length - 1; |
---|
533 | if(this.contents[i] instanceof dd._TextNode && this.contents[i].isEmpty()){ |
---|
534 | this.contents.pop(); |
---|
535 | }else{ |
---|
536 | break; |
---|
537 | } |
---|
538 | } |
---|
539 | |
---|
540 | return this; |
---|
541 | } |
---|
542 | }); |
---|
543 | |
---|
544 | dd._VarNode = lang.extend(function(str){ |
---|
545 | // summary: |
---|
546 | // A node to be processed as a variable |
---|
547 | this.contents = new dd._Filter(str); |
---|
548 | }, |
---|
549 | { |
---|
550 | render: function(context, buffer){ |
---|
551 | var str = this.contents.resolve(context); |
---|
552 | if(!str.safe){ |
---|
553 | str = dd._base.escape("" + str); |
---|
554 | } |
---|
555 | return buffer.concat(str); |
---|
556 | } |
---|
557 | }); |
---|
558 | |
---|
559 | dd._noOpNode = new function(){ |
---|
560 | // summary: |
---|
561 | // Adds a no-op node. Useful in custom tags |
---|
562 | this.render = this.unrender = function(){ return arguments[1]; } |
---|
563 | this.clone = function(){ return this; } |
---|
564 | }; |
---|
565 | |
---|
566 | dd._Parser = lang.extend(function(tokens){ |
---|
567 | // summary: |
---|
568 | // Parser used during initialization and for tag groups. |
---|
569 | this.contents = tokens; |
---|
570 | }, |
---|
571 | { |
---|
572 | i: 0, |
---|
573 | parse: function(/*Array?*/ stop_at){ |
---|
574 | // summary: |
---|
575 | // Turns tokens into nodes |
---|
576 | // description: |
---|
577 | // Steps into tags are they're found. Blocks use the parse object |
---|
578 | // to find their closing tag (the stop_at array). stop_at is inclusive, it |
---|
579 | // returns the node that matched. |
---|
580 | var terminators = {}, token; |
---|
581 | stop_at = stop_at || []; |
---|
582 | for(var i = 0; i < stop_at.length; i++){ |
---|
583 | terminators[stop_at[i]] = true; |
---|
584 | } |
---|
585 | |
---|
586 | var nodelist = new dd._NodeList(); |
---|
587 | while(this.i < this.contents.length){ |
---|
588 | token = this.contents[this.i++]; |
---|
589 | if(typeof token == "string"){ |
---|
590 | nodelist.push(new dd._TextNode(token)); |
---|
591 | }else{ |
---|
592 | var type = token[0]; |
---|
593 | var text = token[1]; |
---|
594 | if(type == dd.TOKEN_VAR){ |
---|
595 | nodelist.push(new dd._VarNode(text)); |
---|
596 | }else if(type == dd.TOKEN_BLOCK){ |
---|
597 | if(terminators[text]){ |
---|
598 | --this.i; |
---|
599 | return nodelist; |
---|
600 | } |
---|
601 | var cmd = text.split(/\s+/g); |
---|
602 | if(cmd.length){ |
---|
603 | cmd = cmd[0]; |
---|
604 | var fn = ddt.getTag(cmd); |
---|
605 | if(fn){ |
---|
606 | nodelist.push(fn(this, new dd.Token(type, text))); |
---|
607 | } |
---|
608 | } |
---|
609 | } |
---|
610 | } |
---|
611 | } |
---|
612 | |
---|
613 | if(stop_at.length){ |
---|
614 | throw new Error("Could not find closing tag(s): " + stop_at.toString()); |
---|
615 | } |
---|
616 | |
---|
617 | this.contents.length = 0; |
---|
618 | return nodelist; |
---|
619 | }, |
---|
620 | next_token: function(){ |
---|
621 | // summary: |
---|
622 | // Returns the next token in the list. |
---|
623 | var token = this.contents[this.i++]; |
---|
624 | return new dd.Token(token[0], token[1]); |
---|
625 | }, |
---|
626 | delete_first_token: function(){ |
---|
627 | this.i++; |
---|
628 | }, |
---|
629 | skip_past: function(endtag){ |
---|
630 | while(this.i < this.contents.length){ |
---|
631 | var token = this.contents[this.i++]; |
---|
632 | if(token[0] == dd.TOKEN_BLOCK && token[1] == endtag){ |
---|
633 | return; |
---|
634 | } |
---|
635 | } |
---|
636 | throw new Error("Unclosed tag found when looking for " + endtag); |
---|
637 | }, |
---|
638 | create_variable_node: function(expr){ |
---|
639 | return new dd._VarNode(expr); |
---|
640 | }, |
---|
641 | create_text_node: function(expr){ |
---|
642 | return new dd._TextNode(expr || ""); |
---|
643 | }, |
---|
644 | getTemplate: function(file){ |
---|
645 | return new dd.Template(file); |
---|
646 | } |
---|
647 | }); |
---|
648 | |
---|
649 | dd.register = { |
---|
650 | // summary: |
---|
651 | // A register for filters and tags. |
---|
652 | |
---|
653 | _registry: { |
---|
654 | attributes: [], |
---|
655 | tags: [], |
---|
656 | filters: [] |
---|
657 | }, |
---|
658 | get: function(/*String*/ module, /*String*/ name){ |
---|
659 | // tags: |
---|
660 | // private |
---|
661 | var registry = dd.register._registry[module + "s"]; |
---|
662 | for(var i = 0, entry; entry = registry[i]; i++){ |
---|
663 | if(typeof entry[0] == "string"){ |
---|
664 | if(entry[0] == name){ |
---|
665 | return entry; |
---|
666 | } |
---|
667 | }else if(name.match(entry[0])){ |
---|
668 | return entry; |
---|
669 | } |
---|
670 | } |
---|
671 | }, |
---|
672 | getAttributeTags: function(){ |
---|
673 | // tags: |
---|
674 | // private |
---|
675 | var tags = []; |
---|
676 | var registry = dd.register._registry.attributes; |
---|
677 | for(var i = 0, entry; entry = registry[i]; i++){ |
---|
678 | if(entry.length == 3){ |
---|
679 | tags.push(entry); |
---|
680 | }else{ |
---|
681 | var fn = lang.getObject(entry[1]); |
---|
682 | if(fn && lang.isFunction(fn)){ |
---|
683 | entry.push(fn); |
---|
684 | tags.push(entry); |
---|
685 | } |
---|
686 | } |
---|
687 | } |
---|
688 | return tags; |
---|
689 | }, |
---|
690 | _any: function(type, base, locations){ |
---|
691 | for(var path in locations){ |
---|
692 | for(var i = 0, fn; fn = locations[path][i]; i++){ |
---|
693 | var key = fn; |
---|
694 | if(lang.isArray(fn)){ |
---|
695 | key = fn[0]; |
---|
696 | fn = fn[1]; |
---|
697 | } |
---|
698 | if(typeof key == "string"){ |
---|
699 | if(key.substr(0, 5) == "attr:"){ |
---|
700 | var attr = fn; |
---|
701 | if(attr.substr(0, 5) == "attr:"){ |
---|
702 | attr = attr.slice(5); |
---|
703 | } |
---|
704 | dd.register._registry.attributes.push([attr.toLowerCase(), base + "." + path + "." + attr]); |
---|
705 | } |
---|
706 | key = key.toLowerCase() |
---|
707 | } |
---|
708 | dd.register._registry[type].push([ |
---|
709 | key, |
---|
710 | fn, |
---|
711 | base + "." + path |
---|
712 | ]); |
---|
713 | } |
---|
714 | } |
---|
715 | }, |
---|
716 | tags: function(/*String*/ base, /*Object*/ locations){ |
---|
717 | // summary: |
---|
718 | // Register the specified tag libraries. |
---|
719 | // description: |
---|
720 | // The locations parameter defines the contents of each library as a hash whose keys are the library names and values |
---|
721 | // an array of the tags exported by the library. For example, the tags exported by the logic library would be: |
---|
722 | // | { logic: ["if", "for", "ifequal", "ifnotequal"] } |
---|
723 | // base: |
---|
724 | // The base path of the libraries. |
---|
725 | // locations: |
---|
726 | // An object defining the tags for each library as a hash whose keys are the library names and values |
---|
727 | // an array of the tags or filters exported by the library. |
---|
728 | dd.register._any("tags", base, locations); |
---|
729 | }, |
---|
730 | filters: function(/*String*/ base, /*Object*/ locations){ |
---|
731 | // summary: |
---|
732 | // Register the specified filter libraries. |
---|
733 | // description: |
---|
734 | // The locations parameter defines the contents of each library as a hash whose keys are the library names and values |
---|
735 | // an array of the filters exported by the library. For example, the filters exported by the date library would be: |
---|
736 | // | { "dates": ["date", "time", "timesince", "timeuntil"] } |
---|
737 | // base: |
---|
738 | // The base path of the libraries. |
---|
739 | // locations: |
---|
740 | // An object defining the filters for each library as a hash whose keys are the library names and values |
---|
741 | // an array of the filters exported by the library. |
---|
742 | dd.register._any("filters", base, locations); |
---|
743 | } |
---|
744 | } |
---|
745 | |
---|
746 | var escapeamp = /&/g; |
---|
747 | var escapelt = /</g; |
---|
748 | var escapegt = />/g; |
---|
749 | var escapeqt = /'/g; |
---|
750 | var escapedblqt = /"/g; |
---|
751 | dd._base.escape = function(value){ |
---|
752 | // summary: |
---|
753 | // Escapes a string's HTML |
---|
754 | return dd.mark_safe(value.replace(escapeamp, '&').replace(escapelt, '<').replace(escapegt, '>').replace(escapedblqt, '"').replace(escapeqt, ''')); |
---|
755 | }; |
---|
756 | |
---|
757 | dd._base.safe = function(value){ |
---|
758 | if(typeof value == "string"){ |
---|
759 | value = new String(value); |
---|
760 | } |
---|
761 | if(typeof value == "object"){ |
---|
762 | value.safe = true; |
---|
763 | } |
---|
764 | return value; |
---|
765 | }; |
---|
766 | dd.mark_safe = dd._base.safe; |
---|
767 | |
---|
768 | dd.register.tags("dojox.dtl.tag", { |
---|
769 | "date": ["now"], |
---|
770 | "logic": ["if", "for", "ifequal", "ifnotequal"], |
---|
771 | "loader": ["extends", "block", "include", "load", "ssi"], |
---|
772 | "misc": ["comment", "debug", "filter", "firstof", "spaceless", "templatetag", "widthratio", "with"], |
---|
773 | "loop": ["cycle", "ifchanged", "regroup"] |
---|
774 | }); |
---|
775 | dd.register.filters("dojox.dtl.filter", { |
---|
776 | "dates": ["date", "time", "timesince", "timeuntil"], |
---|
777 | "htmlstrings": ["linebreaks", "linebreaksbr", "removetags", "striptags"], |
---|
778 | "integers": ["add", "get_digit"], |
---|
779 | "lists": ["dictsort", "dictsortreversed", "first", "join", "length", "length_is", "random", "slice", "unordered_list"], |
---|
780 | "logic": ["default", "default_if_none", "divisibleby", "yesno"], |
---|
781 | "misc": ["filesizeformat", "pluralize", "phone2numeric", "pprint"], |
---|
782 | "strings": ["addslashes", "capfirst", "center", "cut", "fix_ampersands", "floatformat", "iriencode", "linenumbers", "ljust", "lower", "make_list", "rjust", "slugify", "stringformat", "title", "truncatewords", "truncatewords_html", "upper", "urlencode", "urlize", "urlizetrunc", "wordcount", "wordwrap"] |
---|
783 | }); |
---|
784 | dd.register.filters("dojox.dtl", { |
---|
785 | "_base": ["escape", "safe"] |
---|
786 | }); |
---|
787 | |
---|
788 | return dd; |
---|
789 | }); |
---|
790 | |
---|