root/trunk/common/Template.js

Revision 204, 12.6 kB (checked in by ddavis, 2 years ago)

fix whitespace check

  • Property svn:keywords set to Id
Line 
1/*
2Template - Copyright 2005 Six Apart
3$Id$
4
5Copyright (c) 2005, Six Apart, Ltd.
6All rights reserved.
7
8Redistribution and use in source and binary forms, with or without
9modification, are permitted provided that the following conditions are
10met:
11
12    * Redistributions of source code must retain the above copyright
13notice, this list of conditions and the following disclaimer.
14
15    * Redistributions in binary form must reproduce the above
16copyright notice, this list of conditions and the following disclaimer
17in the documentation and/or other materials provided with the
18distribution.
19
20    * Neither the name of "Six Apart" nor the names of its
21contributors may be used to endorse or promote products derived from
22this software without specific prior written permission.
23
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
36*/
37
38
39/* core template object */
40
41Template = new Class( Object, {
42    beginToken: "[#",
43    endToken: "#]",
44   
45   
46    init: function( source ) {
47        if( source )
48            this.compile( source );
49    },
50   
51   
52    compile: function( source ) {
53        var statements = [
54            "context.open();",
55            "with( context.vars ) { "
56        ];
57       
58        var start = 0, end = -this.endToken.length;
59        while( start < source.length ) {
60            end += this.endToken.length;
61           
62            /* plaintext */
63            start = source.indexOf( this.beginToken, end );
64           
65            if( start < 0 )
66                start = source.length;
67            if( start > end )
68                statements.push( "context.write( ", '"' + source.substring( end, start ).escapeJS() + '"', " );" );
69           
70            start += this.beginToken.length;
71           
72            // code
73            if( start >= source.length )
74                break;
75               
76            end = source.indexOf( this.endToken, start );
77           
78            if( end < 0 )
79                throw "Template parsing error: Unable to find matching end token (" + this.endToken + ").";
80               
81            var length = ( end - start );
82           
83            /* empty tag */
84            if( length <= 0 )
85                continue;
86           
87            /* comment */
88            else if( length >= 4 &&
89                source.charAt( start ) == "-" &&
90                source.charAt( start + 1 ) == "-" &&
91                source.charAt( end - 1 ) == "-" &&
92                source.charAt( end - 2 ) == "-" )
93                continue;
94           
95            /* write */
96            else if( source.charAt( start ) == "=" )
97                statements.push( "context.write( ", source.substring( start + 1, end ), " );" );
98           
99            else if( source.charAt( start ) == "*" ) {
100                // commands that effect flow
101               
102                var cmd = source.substring( start + 1, end ).match( /^\s*(\w+)/ );
103                if ( cmd ) {
104                    cmd = cmd[ 1 ];
105               
106                    switch ( cmd ) {
107                        case "return":
108                            statements.push( "return context.close();" );
109                    }
110                }
111           
112            /* filters */
113            } else if( source.charAt( start ) == "|" ) {
114                start += 1;
115
116                /* find the first whitespace */
117                var afterfilters = source.substring( start, end ).search(/\s/);
118               
119                var filters = [];
120                var params = [];
121                if (afterfilters > 0) {
122                    /* pipes or commas must seperate filters
123                     * split the string, reverse and rejoin to reverse it
124                     */
125                    filters = source.substring( start,start + afterfilters ).replace(/(\w+)(\(([^\)]+)\))?/g,"$1|$3").split( "|" );
126                   
127                    afterfilters += 1; /* data starts after whitespace and filter list */
128                } else {
129                    /* default to escapeHTML */
130                    filters = [ "h", "" ];
131                }
132
133                var cmds = [];
134                var params = [];
135                for ( var j = 0; j < filters.length; j++ ) {
136                    if (j % 2)
137                        params.push( filters[ j ] );
138                    else
139                        cmds.push( filters[ j ] );
140                }
141               
142                /* we have to do them in reverse order */
143                filters = cmds.reverse();
144               
145                /* start with our original filter number */
146                var numfilters = filters.length;
147               
148                /* add the text between [#|  #] */
149                filters.push( source.substring( start + afterfilters, end ) );
150               
151                /* adjust each filter into a function call */
152                /* H|substr(-1,1)|u */
153                /* eg. u( substr( H( name ), -1, 1 ) ) */
154                for ( var i = 0; i < numfilters; i++ ) {
155                    filters[ i ] = " context.f." + filters[ i ] + "( ";
156                    filters.push( ", context" );
157                    if ( params[ i ] != "" )
158                        filters.push( ", [" + params[ i ] + "]" );
159                    filters.push( " )" );
160                }
161
162                /* rewrite command params */
163                filters = filters.join( "" );
164                statements.push( "context.write( " + filters + " );");
165            }
166           
167            /* evaluate */
168            else
169                statements.push( source.substring( start, end ) );
170        }
171       
172        statements.push( "} return context.close();" );
173        this.process = new Function( "context", statements.join( "\n" ) );
174    },
175   
176   
177    process: function( context ) {
178        return "";
179    },
180   
181   
182    /* deprecated */
183   
184    exec: function( context ) {
185        log( "Template::exec() method has been deprecated. Please use process() instead or " +
186            "the new static Template.process( name[, vars[, templates]] ) method." );
187        return this.process( context );
188    }
189   
190} );
191
192
193/* static members */
194
195extend( Template, {
196    templates: {},
197   
198   
199    process: function( name, vars, templates ) {
200        var context = new Template.Context( vars, templates );
201        return context.include( name );
202    }
203} );
204
205
206/* context object */
207
208Template.Context = new Class( Object, {
209    init: function( vars, templates ) {
210        this.vars = vars || {};
211        this.templates = templates || Template.templates;
212        this.stack = [];
213        this.out = [];
214        this.f = Template.Filter;
215    },
216   
217   
218    include: function( name ) {
219        if ( !this.templates.hasOwnProperty( name ) ) {
220            log.error( "Template name " + name + " does not exist!" );
221            return;
222        }
223       
224        if ( typeof this.templates[ name ] == "string" )
225            this.templates[ name ] = new Template( this.templates[ name ] );
226        try {
227            return this.templates[ name ].process( this );
228        } catch( e ) {
229            var error = "Error while processing template:" + name + " - " + e.message;
230            log.error( error );
231            throw error;
232        }
233    },
234
235
236    write: function() {
237        this.out.push.apply( this.out, arguments );
238    },
239
240
241    writeln: function() {
242        this.write.apply( this, arguments );
243        this.write( "\n" );
244    },
245
246   
247    clear: function() {
248        this.out.length = 0;
249    },
250
251
252    exit: function() {
253        return this.getOutput();
254    },
255   
256
257    getOutput: function() {
258        return this.out.join( "" );
259    },
260   
261   
262    open: function() {
263        this.stack.push( this.out );
264        this.out = [];
265    },
266   
267   
268    close: function() {
269        var result = this.getOutput();
270        this.out = this.stack.pop() || [];
271        return result;
272    }
273} );
274
275
276/* filters */
277
278Template.Filter = {
279    /* interpolate */
280    i: function( string, context ) {
281        if ( ( typeof string != "string" ) && string && string.toString )
282            string = string.toString();
283        return string.interpolate( context.vars );
284    },
285   
286   
287    /* escapeHTML */
288    h: function( string, context ) {
289        if ( ( typeof string != "string" ) && string && string.toString )
290            string = string.toString();
291        return ( typeof string == "string" )
292            ? string.encodeHTML() : "".encodeHTML( string );
293    },
294
295
296    /* unescapeHTML */
297    H: function( string, context ) {
298        if ( ( typeof string != "string" ) && string && string.toString )
299            string = string.toString();
300        return ( typeof string == "string" )
301            ? string.decodeHTML() : "".encodeHTML( string );
302    },
303
304
305    /* decodeURI */
306    U: function( string, context ) {
307        return decodeURI( string );
308    },
309
310
311    /* escapeURI */
312    u: function( string, context ) {
313        return encodeURI( string ).replace( /\//g, "%2F" );
314    },
315
316
317    /* lowercase */
318    lc: function( string, context ) {
319        if ( ( typeof string != "string" ) && string && string.toString )
320            string = string.toString();
321        return ( typeof string == "string" ) 
322            ? string.toLowerCase() : "".toLowerCase( string );
323    },
324
325
326    /* uppercase */
327    uc: function( string, context ) {
328        if ( ( typeof string != "string" ) && string && string.toString )
329            string = string.toString();
330        return ( typeof string == "string" )
331            ? string.toUpperCase() : "".toUpperCase( string );
332    },
333   
334   
335    /* substr */
336    substr: function( string, context, params ) {
337        if( !params )
338            throw "Template Filter Error: substr() requires at least one parameter";
339       
340        /* allow negative offset */
341        if( params[ 0 ] < 0 )
342            params[ 0 ] = string.length + params[ 0 ];
343       
344        return String.substr.apply( string, params );
345    },
346
347
348    /* removes whitepace before and after */
349    ws: function( string, context ) {
350        if ( ( typeof string != "string" ) && string && string.toString )
351            string = string.toString();
352        return ( typeof string == "string" )
353            ? string.replace( /^\s+/g, "" ).replace( /\s+$/g, "" ) : string;
354    },
355   
356   
357    /* trims to length and adds elipsis */
358    trim: function( string, context, params ) {
359        if ( !params )
360            throw "Template Filter Error: trim() requires at least one parameter";
361       
362        if ( ( typeof string != "string" ) && string && string.toString )
363            string = string.toString();
364       
365        if ( ( typeof string == "string" ) && string.length > params[ 0 ] ) {
366            string = string.substr( 0, params[ 0 ] );
367            /* don't trunc on a word */
368            var newstr = string.replace( /\w+$/, "" );
369            return ( ( newstr == "" ) ? string : newstr ) + "\u2026";
370        } else
371            return string;
372    },
373
374
375    /* returns YYYY-MM-DD from an iso string like: 1995-02-05T13:00:00.000-08:00 */
376    date: function( string, context ) {
377        if ( ( typeof string != "string" ) && string && string.toString )
378            string = string.toString();
379        var date = Date.fromISOString( string );
380        return ( date ) ? date.toISODateString() : "";
381    },
382
383
384    localeDate: function( string, context ) {
385        if ( ( typeof string != "string" ) && string && string.toString )
386            string = string.toString();
387        var date = Date.fromISOString( string );
388        return ( date ) ? date.toLocaleString() : "";
389    },
390
391
392    /* remove html tags */
393    rt: function( string, context ) {
394        if ( ( typeof string != "string" ) && string && string.toString )
395            string = string.toString();
396        return ( typeof string == "string" )
397            ? string.replace( /<\/?[^>]+>/gi, "" ) : string;
398    },
399
400
401    rp: function( string, params ) {
402        if ( ( typeof string != "string" ) && string && string.toString )
403            string = string.toString();
404
405        if ( ( typeof string == "string" ) && params.length == 2 ) {
406            return string.replace( params[ 0 ], params[ 1 ] );
407        } else
408            return string;
409    }
410};
Note: See TracBrowser for help on using the browser.