From 3c49d96e16c584cfe3ece83936986e6c6578949a Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Tue, 19 May 2009 20:29:49 -0700 Subject: [PATCH 01/29] Integrated patch from RT#37895 --- Makefile.PL | 1 + ToDo | 2 ++ lib/Jemplate.pm | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile.PL b/Makefile.PL index e88c41d..728ca7f 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -18,6 +18,7 @@ all_from 'lib/Jemplate.pm'; #build_requires 'Directory::Scratch'; + requires 'Template' => '2.19'; requires 'File::Find::Rule' => '0.30'; diff --git a/ToDo b/ToDo index a9712bb..f9410be 100644 --- a/ToDo +++ b/ToDo @@ -1,3 +1,5 @@ +TODO: Does use_test_base not work? make test compains that Test::Base is not installed -rokr + FUTURE: 09:55 < nkuttler_> apparently firefox sets the accept header to request xml rather than json, which obviously is incompatible with later JSON.parse calls. I have added a diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index 24958ef..5d82cad 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -232,7 +232,7 @@ sub make_file_list { foreach my $arg (@args) { unless (-e $arg) { next; } # file exists - unless (-s $arg) { next; } # file size > 0 + unless (-s $arg or -d $arg) { next; } # file size > 0 or directory (for Win platform) if (-d $arg) { foreach my $full ( recurse_dir($arg) ) { From 98654d5a6e6d1677d92a8138a48b91820b94dbcf Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Wed, 20 May 2009 08:21:43 -0700 Subject: [PATCH 02/29] Updated json2.js to the latest version --- src/js/json2.compact.js | 2 +- src/js/json2.js | 462 +++++++++++++++++++++------------------- 2 files changed, 240 insertions(+), 224 deletions(-) diff --git a/src/js/json2.compact.js b/src/js/json2.compact.js index 31d28c3..adba741 100644 --- a/src/js/json2.compact.js +++ b/src/js/json2.compact.js @@ -1 +1 @@ -if(!JSON){var JSON}if(!JSON){JSON=function(){function f(n){return n<10?"0"+n:n}Date.prototype.toJSON=function(){return this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z"};var escapeable=/["\\\x00-\x1f\x7f-\x9f]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){return escapeable.test(string)?'"'+string.replace(escapeable,function(a){var c=meta[a];if(typeof c==="string"){return c}c=a.charCodeAt();return"\\u00"+Math.floor(c/16).toString(16)+(c%16).toString(16)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(typeof value.length==="number"&&!(value.propertyIsEnumerable("length"))){length=value.length;for(i=0;i Date: Mon, 8 Jun 2009 10:39:41 -0700 Subject: [PATCH 03/29] Version bump to 0.24_1 (and release) --- Changes | 6 ++++++ lib/Jemplate.pm | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index d5e6f25..188404b 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,9 @@ +--- +version: 0.24_1 +date: Monday June 08 10:37:54 PDT 2009 +changes: +- Fix to get tests working on MSWin32 (rt23883) + --- version: 0.24 date: Fri Sep 19 15:09:35 PDT 2008 diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index 5d82cad..19f3a61 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -5,7 +5,7 @@ use warnings; use Template 2.14; use Getopt::Long; -our $VERSION = '0.23_1'; +our $VERSION = '0.24_1'; use Jemplate::Parser; From 6c3dd44ac2ee2843e244f82f7d5176d7ced78e76 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Mon, 8 Jun 2009 10:41:39 -0700 Subject: [PATCH 04/29] Include Scripts.pm in inc/ --- Changes | 2 +- MANIFEST | 1 + lib/Jemplate.pm | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 188404b..8102dae 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,5 @@ --- -version: 0.24_1 +version: 0.24_2 date: Monday June 08 10:37:54 PDT 2009 changes: - Fix to get tests working on MSWin32 (rt23883) diff --git a/MANIFEST b/MANIFEST index bf43a14..6bf4059 100644 --- a/MANIFEST +++ b/MANIFEST @@ -33,6 +33,7 @@ inc/Module/Install/Metadata.pm inc/Module/Install/TestBase.pm inc/Module/Install/Win32.pm inc/Module/Install/WriteAll.pm +inc/Module/Install/Scripts.pm inc/Spiffy.pm inc/Test/Base.pm inc/Test/Base/Filter.pm diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index 19f3a61..5f2424f 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -5,7 +5,7 @@ use warnings; use Template 2.14; use Getopt::Long; -our $VERSION = '0.24_1'; +our $VERSION = '0.24_2'; use Jemplate::Parser; From a0b21b6740cbe74878ed8675d6bc5745ea215c78 Mon Sep 17 00:00:00 2001 From: SamuraiJack Date: Wed, 10 Jun 2009 13:32:09 +0400 Subject: [PATCH 05/29] - MACRO mentioned as implemented - 'import' on hashes mentioned as implemented --- lib/Jemplate.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index 5d82cad..9e22ecc 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -587,12 +587,13 @@ Jemplate now supports almost all the TT directives, including: * [% LAST %] * [% CLEAR %] * [%# this is a comment %] + * [% MACRO name(param1, param2) BLOCK %] ... [% END %] ALL of the string virtual functions are supported. ALL of the array virtual functions are supported: -ALL of the hash virtual functions are supported (except for import): +ALL of the hash virtual functions are supported: MANY of the standard filters are implemented. From 32f50d1b70bd43ffa97df772345cff0065752801 Mon Sep 17 00:00:00 2001 From: SamuraiJack Date: Wed, 10 Jun 2009 13:35:43 +0400 Subject: [PATCH 06/29] - added myself as author :) - development repo link changed to github --- lib/Jemplate.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index 9e22ecc..8cd1cbe 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -614,8 +614,8 @@ All tests run 100% successful in the above browsers. =head1 DEVELOPMENT -The bleeding edge code is available via Subversion at -http://svn.jemplate.net/repo/trunk/ +The bleeding edge code is available via Git at +git://github.com/ingydotnet/jemplate.git You can run the runtime tests directly from http://svn.jemplate.net/repo/trunk/tests/run/index.html or from the @@ -658,6 +658,8 @@ David A. Coffey Robert Krimen +Nickolay Platonov + =head1 COPYRIGHT Copyright (c) 2006-2008. Ingy döt Net. From 77126b00afd5dc4b68bd0de1097ede2bb112f6e9 Mon Sep 17 00:00:00 2001 From: SamuraiJack Date: Wed, 10 Jun 2009 13:43:20 +0400 Subject: [PATCH 07/29] - some progress on change log --- Changes | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Changes b/Changes index d5e6f25..511c0e6 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,29 @@ +--- +version: 0.24_2 +date: Mon Jun 8 2009 +changes: +- fixed bug, related to empty template body (http://rt.cpan.org/Public/Bug/Display.html?id=37061) +- implemented and tested MACRO directive +- fixed bug in Parser.yp, which produced compilation error for macroses +- added "init" method for Jemplate singleton +- implemented PRE_PROCESS and POST_PROCESS configuration options +- fixed "intermediate instantiation" issue (http://rt.cpan.org/Public/Bug/Display.html?id=37539) +- fixed "iteration over empty object" issue (http://rt.cpan.org/Public/Bug/Display.html?id=37540) +- implemented and tested global scope access via GLOBAL modifier (GLOBAL.jQeuery etc) +- added dummy LOCAL modifier, for symmetry +- added experimental RAW directive (RAW jQuery, equivalent of jQuery = GLOBAL.jQuery) + +VMethods: +- added "list" for lists (http://rt.cpan.org/Public/Bug/Display.html?id=37570) +- fixed "import" for hashes +- added "item" for hashes + +Test suite: +- combined back to a single copy + +Patches: +- Integrated patch from RT#37895 + --- version: 0.24 date: Fri Sep 19 15:09:35 PDT 2008 From a3fa2e9bbcfc8f6efddc2844420954ba137f0ca1 Mon Sep 17 00:00:00 2001 From: SamuraiJack Date: Wed, 10 Jun 2009 13:49:03 +0400 Subject: [PATCH 08/29] - reordered changelog entries --- Changes | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Changes b/Changes index 511c0e6..ab37e5e 100644 --- a/Changes +++ b/Changes @@ -3,20 +3,20 @@ version: 0.24_2 date: Mon Jun 8 2009 changes: - fixed bug, related to empty template body (http://rt.cpan.org/Public/Bug/Display.html?id=37061) -- implemented and tested MACRO directive - fixed bug in Parser.yp, which produced compilation error for macroses -- added "init" method for Jemplate singleton -- implemented PRE_PROCESS and POST_PROCESS configuration options - fixed "intermediate instantiation" issue (http://rt.cpan.org/Public/Bug/Display.html?id=37539) - fixed "iteration over empty object" issue (http://rt.cpan.org/Public/Bug/Display.html?id=37540) -- implemented and tested global scope access via GLOBAL modifier (GLOBAL.jQeuery etc) +- added and tested MACRO directive +- added PRE_PROCESS and POST_PROCESS configuration options +- added and tested global scope access via GLOBAL modifier (GLOBAL.jQeuery etc) - added dummy LOCAL modifier, for symmetry +- added "init" method for Jemplate singleton - added experimental RAW directive (RAW jQuery, equivalent of jQuery = GLOBAL.jQuery) VMethods: - added "list" for lists (http://rt.cpan.org/Public/Bug/Display.html?id=37570) -- fixed "import" for hashes - added "item" for hashes +- fixed "import" for hashes Test suite: - combined back to a single copy From 67e404a6977603506449e29223ec7a6d47110037 Mon Sep 17 00:00:00 2001 From: SamuraiJack Date: Mon, 24 Aug 2009 13:44:45 +0400 Subject: [PATCH 09/29] - rolled back the behavior of 'match' and 'replace' string vmethods in regard of regex modifiers --- bin/jemplate | 982 ++++++++++++++++---------------- lib/Jemplate/Runtime.pm | 974 ++++++++++++++++--------------- lib/Jemplate/Runtime/Compact.pm | 6 +- src/js/kernel.compact.js | 2 +- src/js/kernel.js | 4 +- tests/t/string.t.js | 6 +- 6 files changed, 965 insertions(+), 1009 deletions(-) diff --git a/bin/jemplate b/bin/jemplate index 0b362da..dcdb0bf 100755 --- a/bin/jemplate +++ b/bin/jemplate @@ -17439,16 +17439,7 @@ proto._dotop = function(root, item, args, lvalue) { } else { return root[item]; } - } /* - //this section was for automatic global scope access - else if (atroot && typeof this.data.GLOBAL[item] != 'undefined' && this.__config__.GLOBAL && item != 'LOCAL' ) { - - if (typeof this.data.GLOBAL[item] == 'function' ) { - result = this.data.GLOBAL[item].apply(root,args); - } else { - return this.data.GLOBAL[item]; - } - }*/ else if (lvalue) { + } else if (lvalue) { return root[item] = {}; } else if (this.hash_functions[item] && !atroot || item == 'import') { args.unshift(root); @@ -17470,48 +17461,7 @@ proto._dotop = function(root, item, args, lvalue) { for (var i = 0; i < item.length; i++) result.push(root[item[i]]); return result; } - } /*else if ( (root.constructor != Object) && (root instanceof Object) ) { - //this section was proposed for calling method on blessed reference in Perl - //not sure how well it is playing with javascript - try { - result = root[item].apply(root,args); - } catch (e) { - var my_class = root.constructor.name; - - if (false) throw "Cant locate method"; - - if (root instanceof Array) { - if (this.list_functions[item]) { - args.unshift(root); - result = this.list_functions[item].apply(this,args); - } else if (typeof item == 'string' && /^-?\d+$/.test(item) || typeof item == 'number' ) { - if (typeof root[item] != 'function') return root[item]; - result = root[item].apply(this, args); - } else if (item instanceof Array) { - for (var i = 0; i < item.length; i++) result.push(root[item[i]]); - return result; - } - } else if (typeof root == 'object') { - if (typeof root[item] != 'undefined' && root[item] != null) {//consider undefined == null - if (typeof root[item] == 'function') { - result = root[item].apply(this,args); - } else { - return root[item]; - } - } else if (this.hash_functions[item]) { - args.unshift(root); - result = this.hash_functions[item].apply(this,args); - } - } else if (this.string_functions[item]) { - args.unshift(root); - result = this.string_functions[item].apply(this, args); - } else if (this.list_functions[item]) { - args.unshift([root]); - result = this.list_functions[item].apply(this,args); - } - - } - }*/ else if (this.string_functions[item] && !lvalue) { + } else if (this.string_functions[item] && !lvalue) { args.unshift(root); result = this.string_functions[item].apply(this, args); } else if (this.list_functions[item] && !lvalue) { @@ -17544,10 +17494,6 @@ proto._assign = function(root, item, args, value, set_default) { } if (atroot || root.constructor == Object || root == this.data.GLOBAL) { -//this section was for automatic global scope access -// if (atroot && this.__config__.GLOBAL && typeof root[item] == 'undefined' && typeof this.data.GLOBAL[item] != 'undefined' && !set_default) { -// return this.data.GLOBAL[item] = value; -// } if (root == this.LOCAL_ANCHOR) root = this.data; @@ -17622,7 +17568,7 @@ proto.string_functions.list = function(string) { // match(re) get list of matches proto.string_functions.match = function(string, re, modifiers) { - var regexp = new RegExp(re, modifiers); + var regexp = new RegExp(re, modifiers == undefined ? 'g' : modifiers); var list = string.match(regexp); return list; } @@ -17639,7 +17585,7 @@ proto.string_functions.repeat = function(string, args) { // replace(re, sub, global) replace instances of re with sub proto.string_functions.replace = function(string, re, sub, modifiers) { - var regexp = new RegExp(re,modifiers); + var regexp = new RegExp(re, modifiers == undefined ? 'g' : modifiers); if (! sub) sub = ''; return string.replace(regexp, sub); @@ -18148,8 +18094,8 @@ sub json_json2_internal { var JSON; /* - json2.js - 2008-03-24 + http://www.JSON.org/json2.js + 2009-04-16 Public Domain. @@ -18157,44 +18103,48 @@ var JSON; See http://www.JSON.org/js.html - This file creates a global JSON object containing three methods: stringify, - parse, and quote. - + This file creates a global JSON object containing two methods: stringify + and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object - values are stringified for objects without a toJSON - method. It can be a function or an array. + values are stringified for objects. It can be a + function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each - level. If it is a string (such as '\t'), it contains the - characters used to indent at each level. + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON - method, its toJSON method with be called and the result will be + method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method will - be passed the key associated with the value, and this will be bound - to the object holding the key. + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the object holding the key. + + For example, this would serialize Dates as ISO strings. - This is the toJSON method added to Dates: + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - function toJSON(key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; - } + }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing @@ -18202,35 +18152,24 @@ var JSON; serialized. If your method returns undefined, then the member will be excluded from the serialization. - If no replacer parameter is provided, then a default replacer - will be used: - - function replacer(key, value) { - return Object.hasOwnProperty.call(this, key) ? - value : undefined; - } - - The default replacer is passed the key and value for each item in - the structure. It excludes inherited members. - - If the replacer parameter is an array, then it will be used to - select the members to be serialized. It filters the results such - that only members with keys listed in the replacer array are + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are stringified. - Values that do not have JSON representaions, such as undefined or + Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. - The optional space parameter produces a stringification of the value - that is filled with line breaks and indentation to make it easier to - read. + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then - then indentation will be that many spaces. + the indentation will be that many spaces. Example: @@ -18241,16 +18180,22 @@ var JSON; text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, and - its return value is used instead of the original value. If it - returns what it received, then structure is not modified. If it - returns undefined then the member is deleted. + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. Example: @@ -18270,45 +18215,57 @@ var JSON; return value; }); - - JSON.quote(text) - This method wraps a string in quotes, escaping some characters - as needed. + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); This is a reference implementation. You are free to copy, modify, or redistribute. - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD THIRD PARTY - CODE INTO YOUR PAGES. + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. */ -/*jslint regexp: true, forin: true, evil: true */ +/*jslint evil: true */ /*global JSON */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, floor, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, length, - parse, propertyIsEnumerable, prototype, push, quote, replace, stringify, - test, toJSON, toString + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf */ -if (!JSON) var JSON; -if (!JSON) { - // Create a JSON object only if one does not already exist. We create the -// object in a closure to avoid global variables. +// methods in a closure to avoid creating global variables. - JSON = function () { +if (!this.JSON) { + this.JSON = {}; +} +(function () { - function f(n) { // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - Date.prototype.toJSON = function () { + if (typeof Date.prototype.toJSON !== 'function') { -// Eventually, this method will be based on the date.toISOString method. + Date.prototype.toJSON = function (key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + @@ -18318,175 +18275,179 @@ if (!JSON) { f(this.getUTCSeconds()) + 'Z'; }; + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } - var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; - function quote(string) { + function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. - return escapeable.test(string) ? - '"' + string.replace(escapeable, function (a) { - var c = meta[a]; - if (typeof c === 'string') { - return c; - } - c = a.charCodeAt(); - return '\\u00' + Math.floor(c / 16).toString(16) + - (c % 16).toString(16); - }) + '"' : - '"' + string + '"'; - } + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } - function str(key, holder) { + function str(key, holder) { // Produce a string from holder[key]. - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } // What happens next depends on the value's type. - switch (typeof value) { - case 'string': - return quote(value); + switch (typeof value) { + case 'string': + return quote(value); - case 'number': + case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) ? String(value) : 'null'; + return isFinite(value) ? String(value) : 'null'; - case 'boolean': - case 'null': + case 'boolean': + case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. - return String(value); + return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. - case 'object': + case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. - if (!value) { - return 'null'; - } + if (!value) { + return 'null'; + } // Make an array to hold the partial results of stringifying this object value. - gap += indent; - partial = []; + gap += indent; + partial = []; -// If the object has a dontEnum length property, we'll treat it as an array. +// Is the value an array? - if (typeof value.length === 'number' && - !(value.propertyIsEnumerable('length'))) { + if (Object.prototype.toString.apply(value) === '[object Array]') { -// The object is an array. Stringify every element. Use null as a placeholder +// The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } // Join all of the elements together, separated with commas, and wrap them in // brackets. - v = partial.length === 0 ? '[]' : - gap ? '[\n' + gap + partial.join(',\n' + gap) + - '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } // If the replacer is an array, use it to select the members to be stringified. - if (typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value, rep); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); } } - } else { + } + } else { // Otherwise, iterate through all of the keys in the object. - for (k in value) { - v = str(k, value, rep); + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } + } // Join all of the member texts together, separated with commas, // and wrap them in braces. - v = partial.length === 0 ? '{}' : - gap ? '{\n' + gap + partial.join(',\n' + gap) + - '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - - return null; + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; } + return ''; + } -// Return the JSON object containing the stringify, parse, and quote methods. +// If the JSON object does not yet have a stringify method, give it one. - return { - stringify: function (value, replacer, space) { + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function @@ -18494,123 +18455,124 @@ if (!JSON) { // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. - var i; - gap = ''; - indent = ''; - if (space) { + var i; + gap = ''; + indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; } -// If there is no replacer parameter, use the default replacer. +// If the space parameter is a string, it will be used as the indent string. - if (!replacer) { - rep = function (key, value) { - if (!Object.hasOwnProperty.call(this, key)) { - return undefined; - } - return value; - }; + } else if (typeof space === 'string') { + indent = space; + } -// The replacer can be a function or an array. Otherwise, throw an error. +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. - } else if (typeof replacer === 'function' || - (typeof replacer === 'object' && - typeof replacer.length === 'number')) { - rep = replacer; - } else { - throw new Error('JSON.stringify'); - } + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. - return str('', {'': value}); - }, + return str('', {'': value}); + }; + } - parse: function (text, reviver) { +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. - var j; + var j; - function walk(holder, key) { + function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; } } } - return reviver.call(holder, key, value); } + return reviver.call(holder, key, value); + } + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. -// Parsing happens in three stages. In the first stage, we run the text against -// regular expressions that look for non-JSON patterns. We are especially -// concerned with '()' and 'new' because they can cause invocation, and '=' -// because it can cause mutation. But just to be safe, we want to reject all -// unexpected forms. + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } -// We split the first stage into 4 regexp operations in order to work around +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace all backslash pairs with '@' (a non-JSON character). Second, we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@'). + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { -// In the second stage we use the eval function to compile the text into a +// In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. - j = eval('(' + text + ')'); + j = eval('(' + text + ')'); -// In the optional third stage, we recursively walk the new structure, passing +// In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } // If the text is not JSON parseable, then a SyntaxError is thrown. - throw new SyntaxError('JSON.parse'); - }, - - quote: quote + throw new SyntaxError('JSON.parse'); }; - }(); -} + } +}()); Jemplate.JSON = { @@ -18654,8 +18616,8 @@ Jemplate.JSON = { sub json2 { <<'...'; /* - json2.js - 2008-03-24 + http://www.JSON.org/json2.js + 2009-04-16 Public Domain. @@ -18663,44 +18625,48 @@ sub json2 { See http://www.JSON.org/js.html - This file creates a global JSON object containing three methods: stringify, - parse, and quote. - + This file creates a global JSON object containing two methods: stringify + and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object - values are stringified for objects without a toJSON - method. It can be a function or an array. + values are stringified for objects. It can be a + function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each - level. If it is a string (such as '\t'), it contains the - characters used to indent at each level. + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON - method, its toJSON method with be called and the result will be + method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method will - be passed the key associated with the value, and this will be bound - to the object holding the key. + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the object holding the key. - This is the toJSON method added to Dates: + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - function toJSON(key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; - } + }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing @@ -18708,35 +18674,24 @@ sub json2 { serialized. If your method returns undefined, then the member will be excluded from the serialization. - If no replacer parameter is provided, then a default replacer - will be used: - - function replacer(key, value) { - return Object.hasOwnProperty.call(this, key) ? - value : undefined; - } - - The default replacer is passed the key and value for each item in - the structure. It excludes inherited members. - - If the replacer parameter is an array, then it will be used to - select the members to be serialized. It filters the results such - that only members with keys listed in the replacer array are + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are stringified. - Values that do not have JSON representaions, such as undefined or + Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. - The optional space parameter produces a stringification of the value - that is filled with line breaks and indentation to make it easier to - read. + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then - then indentation will be that many spaces. + the indentation will be that many spaces. Example: @@ -18747,16 +18702,22 @@ sub json2 { text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, and - its return value is used instead of the original value. If it - returns what it received, then structure is not modified. If it - returns undefined then the member is deleted. + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. Example: @@ -18776,45 +18737,57 @@ sub json2 { return value; }); - - JSON.quote(text) - This method wraps a string in quotes, escaping some characters - as needed. + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); This is a reference implementation. You are free to copy, modify, or redistribute. - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD THIRD PARTY - CODE INTO YOUR PAGES. + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. */ -/*jslint regexp: true, forin: true, evil: true */ +/*jslint evil: true */ /*global JSON */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, floor, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, length, - parse, propertyIsEnumerable, prototype, push, quote, replace, stringify, - test, toJSON, toString + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf */ -if (!JSON) var JSON; -if (!JSON) { - // Create a JSON object only if one does not already exist. We create the -// object in a closure to avoid global variables. +// methods in a closure to avoid creating global variables. - JSON = function () { +if (!this.JSON) { + this.JSON = {}; +} +(function () { - function f(n) { // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - Date.prototype.toJSON = function () { + if (typeof Date.prototype.toJSON !== 'function') { -// Eventually, this method will be based on the date.toISOString method. + Date.prototype.toJSON = function (key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + @@ -18824,175 +18797,179 @@ if (!JSON) { f(this.getUTCSeconds()) + 'Z'; }; + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } - var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; - function quote(string) { + function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. - return escapeable.test(string) ? - '"' + string.replace(escapeable, function (a) { - var c = meta[a]; - if (typeof c === 'string') { - return c; - } - c = a.charCodeAt(); - return '\\u00' + Math.floor(c / 16).toString(16) + - (c % 16).toString(16); - }) + '"' : - '"' + string + '"'; - } + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } - function str(key, holder) { + function str(key, holder) { // Produce a string from holder[key]. - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } // What happens next depends on the value's type. - switch (typeof value) { - case 'string': - return quote(value); + switch (typeof value) { + case 'string': + return quote(value); - case 'number': + case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) ? String(value) : 'null'; + return isFinite(value) ? String(value) : 'null'; - case 'boolean': - case 'null': + case 'boolean': + case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. - return String(value); + return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. - case 'object': + case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. - if (!value) { - return 'null'; - } + if (!value) { + return 'null'; + } // Make an array to hold the partial results of stringifying this object value. - gap += indent; - partial = []; + gap += indent; + partial = []; -// If the object has a dontEnum length property, we'll treat it as an array. +// Is the value an array? - if (typeof value.length === 'number' && - !(value.propertyIsEnumerable('length'))) { + if (Object.prototype.toString.apply(value) === '[object Array]') { -// The object is an array. Stringify every element. Use null as a placeholder +// The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } // Join all of the elements together, separated with commas, and wrap them in // brackets. - v = partial.length === 0 ? '[]' : - gap ? '[\n' + gap + partial.join(',\n' + gap) + - '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } // If the replacer is an array, use it to select the members to be stringified. - if (typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value, rep); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); } } - } else { + } + } else { // Otherwise, iterate through all of the keys in the object. - for (k in value) { - v = str(k, value, rep); + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } + } // Join all of the member texts together, separated with commas, // and wrap them in braces. - v = partial.length === 0 ? '{}' : - gap ? '{\n' + gap + partial.join(',\n' + gap) + - '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - - return null; + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; } + return ''; + } -// Return the JSON object containing the stringify, parse, and quote methods. +// If the JSON object does not yet have a stringify method, give it one. - return { - stringify: function (value, replacer, space) { + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function @@ -19000,123 +18977,124 @@ if (!JSON) { // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. - var i; - gap = ''; - indent = ''; - if (space) { + var i; + gap = ''; + indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; } -// If there is no replacer parameter, use the default replacer. +// If the space parameter is a string, it will be used as the indent string. - if (!replacer) { - rep = function (key, value) { - if (!Object.hasOwnProperty.call(this, key)) { - return undefined; - } - return value; - }; + } else if (typeof space === 'string') { + indent = space; + } -// The replacer can be a function or an array. Otherwise, throw an error. +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. - } else if (typeof replacer === 'function' || - (typeof replacer === 'object' && - typeof replacer.length === 'number')) { - rep = replacer; - } else { - throw new Error('JSON.stringify'); - } + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. - return str('', {'': value}); - }, + return str('', {'': value}); + }; + } - parse: function (text, reviver) { +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. - var j; + var j; - function walk(holder, key) { + function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; } } } - return reviver.call(holder, key, value); } + return reviver.call(holder, key, value); + } + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. -// Parsing happens in three stages. In the first stage, we run the text against -// regular expressions that look for non-JSON patterns. We are especially -// concerned with '()' and 'new' because they can cause invocation, and '=' -// because it can cause mutation. But just to be safe, we want to reject all -// unexpected forms. + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } -// We split the first stage into 4 regexp operations in order to work around +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace all backslash pairs with '@' (a non-JSON character). Second, we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@'). + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { -// In the second stage we use the eval function to compile the text into a +// In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. - j = eval('(' + text + ')'); + j = eval('(' + text + ')'); -// In the optional third stage, we recursively walk the new structure, passing +// In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } // If the text is not JSON parseable, then a SyntaxError is thrown. - throw new SyntaxError('JSON.parse'); - }, - - quote: quote + throw new SyntaxError('JSON.parse'); }; - }(); -} + } +}()); ... } @@ -19725,7 +19703,7 @@ use warnings; sub main { return &kernel } sub kernel { <<'...'; -if(typeof Jemplate=="undefined"){var Jemplate=function(){this.init.apply(this,arguments)}}Jemplate.VERSION="0.22";Jemplate.process=function(){var A=new Jemplate(Jemplate.prototype.config);return A.process.apply(A,arguments)};(function(){if(!Jemplate.templateMap){Jemplate.templateMap={}}var proto=Jemplate.prototype={};proto.config={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,FILTERS:{},INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};proto.defaults={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};Jemplate.init=function(config){Jemplate.prototype.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof Jemplate.prototype.config[i]=="undefined"){Jemplate.prototype.config[i]=Jemplate.prototype.defaults[i]}}};proto.init=function(config){this.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof this.config[i]=="undefined"){this.config[i]=Jemplate.prototype.defaults[i]}}};proto.process=function(template,data,output){var context=this.config.CONTEXT||new Jemplate.Context();context.config=this.config;context.stash=new Jemplate.Stash(this.config.STASH,this.config);context.__filter__=new Jemplate.Filter();context.__filter__.config=this.config;context.__plugin__=new Jemplate.Plugin();context.__plugin__.config=this.config;var result;var proc=function(input){try{if(typeof context.config.PRE_PROCESS=="string"){context.config.PRE_PROCESS=[context.config.PRE_PROCESS]}for(var i=0;i/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index 0 + unless (-s $arg or -d $arg) { next; } # file size > 0 or directory (for Win platform) if (-d $arg) { foreach my $full ( recurse_dir($arg) ) { diff --git a/lib/Jemplate/Runtime.pm b/lib/Jemplate/Runtime.pm index 84f94cb..0cab30a 100644 --- a/lib/Jemplate/Runtime.pm +++ b/lib/Jemplate/Runtime.pm @@ -529,16 +529,7 @@ proto._dotop = function(root, item, args, lvalue) { } else { return root[item]; } - } /* - //this section was for automatic global scope access - else if (atroot && typeof this.data.GLOBAL[item] != 'undefined' && this.__config__.GLOBAL && item != 'LOCAL' ) { - - if (typeof this.data.GLOBAL[item] == 'function' ) { - result = this.data.GLOBAL[item].apply(root,args); - } else { - return this.data.GLOBAL[item]; - } - }*/ else if (lvalue) { + } else if (lvalue) { return root[item] = {}; } else if (this.hash_functions[item] && !atroot || item == 'import') { args.unshift(root); @@ -560,48 +551,7 @@ proto._dotop = function(root, item, args, lvalue) { for (var i = 0; i < item.length; i++) result.push(root[item[i]]); return result; } - } /*else if ( (root.constructor != Object) && (root instanceof Object) ) { - //this section was proposed for calling method on blessed reference in Perl - //not sure how well it is playing with javascript - try { - result = root[item].apply(root,args); - } catch (e) { - var my_class = root.constructor.name; - - if (false) throw "Cant locate method"; - - if (root instanceof Array) { - if (this.list_functions[item]) { - args.unshift(root); - result = this.list_functions[item].apply(this,args); - } else if (typeof item == 'string' && /^-?\d+$/.test(item) || typeof item == 'number' ) { - if (typeof root[item] != 'function') return root[item]; - result = root[item].apply(this, args); - } else if (item instanceof Array) { - for (var i = 0; i < item.length; i++) result.push(root[item[i]]); - return result; - } - } else if (typeof root == 'object') { - if (typeof root[item] != 'undefined' && root[item] != null) {//consider undefined == null - if (typeof root[item] == 'function') { - result = root[item].apply(this,args); - } else { - return root[item]; - } - } else if (this.hash_functions[item]) { - args.unshift(root); - result = this.hash_functions[item].apply(this,args); - } - } else if (this.string_functions[item]) { - args.unshift(root); - result = this.string_functions[item].apply(this, args); - } else if (this.list_functions[item]) { - args.unshift([root]); - result = this.list_functions[item].apply(this,args); - } - - } - }*/ else if (this.string_functions[item] && !lvalue) { + } else if (this.string_functions[item] && !lvalue) { args.unshift(root); result = this.string_functions[item].apply(this, args); } else if (this.list_functions[item] && !lvalue) { @@ -634,10 +584,6 @@ proto._assign = function(root, item, args, value, set_default) { } if (atroot || root.constructor == Object || root == this.data.GLOBAL) { -//this section was for automatic global scope access -// if (atroot && this.__config__.GLOBAL && typeof root[item] == 'undefined' && typeof this.data.GLOBAL[item] != 'undefined' && !set_default) { -// return this.data.GLOBAL[item] = value; -// } if (root == this.LOCAL_ANCHOR) root = this.data; @@ -712,7 +658,7 @@ proto.string_functions.list = function(string) { // match(re) get list of matches proto.string_functions.match = function(string, re, modifiers) { - var regexp = new RegExp(re, modifiers); + var regexp = new RegExp(re, modifiers == undefined ? 'g' : modifiers); var list = string.match(regexp); return list; } @@ -729,7 +675,7 @@ proto.string_functions.repeat = function(string, args) { // replace(re, sub, global) replace instances of re with sub proto.string_functions.replace = function(string, re, sub, modifiers) { - var regexp = new RegExp(re,modifiers); + var regexp = new RegExp(re, modifiers == undefined ? 'g' : modifiers); if (! sub) sub = ''; return string.replace(regexp, sub); @@ -1238,8 +1184,8 @@ sub json_json2_internal { var JSON; /* - json2.js - 2008-03-24 + http://www.JSON.org/json2.js + 2009-04-16 Public Domain. @@ -1247,44 +1193,48 @@ var JSON; See http://www.JSON.org/js.html - This file creates a global JSON object containing three methods: stringify, - parse, and quote. - + This file creates a global JSON object containing two methods: stringify + and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object - values are stringified for objects without a toJSON - method. It can be a function or an array. + values are stringified for objects. It can be a + function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each - level. If it is a string (such as '\t'), it contains the - characters used to indent at each level. + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON - method, its toJSON method with be called and the result will be + method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method will - be passed the key associated with the value, and this will be bound - to the object holding the key. + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the object holding the key. + + For example, this would serialize Dates as ISO strings. - This is the toJSON method added to Dates: + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - function toJSON(key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; - } + }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing @@ -1292,35 +1242,24 @@ var JSON; serialized. If your method returns undefined, then the member will be excluded from the serialization. - If no replacer parameter is provided, then a default replacer - will be used: - - function replacer(key, value) { - return Object.hasOwnProperty.call(this, key) ? - value : undefined; - } - - The default replacer is passed the key and value for each item in - the structure. It excludes inherited members. - - If the replacer parameter is an array, then it will be used to - select the members to be serialized. It filters the results such - that only members with keys listed in the replacer array are + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are stringified. - Values that do not have JSON representaions, such as undefined or + Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. - The optional space parameter produces a stringification of the value - that is filled with line breaks and indentation to make it easier to - read. + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then - then indentation will be that many spaces. + the indentation will be that many spaces. Example: @@ -1331,16 +1270,22 @@ var JSON; text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, and - its return value is used instead of the original value. If it - returns what it received, then structure is not modified. If it - returns undefined then the member is deleted. + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. Example: @@ -1360,45 +1305,57 @@ var JSON; return value; }); - - JSON.quote(text) - This method wraps a string in quotes, escaping some characters - as needed. + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); This is a reference implementation. You are free to copy, modify, or redistribute. - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD THIRD PARTY - CODE INTO YOUR PAGES. + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. */ -/*jslint regexp: true, forin: true, evil: true */ +/*jslint evil: true */ /*global JSON */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, floor, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, length, - parse, propertyIsEnumerable, prototype, push, quote, replace, stringify, - test, toJSON, toString + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf */ -if (!JSON) var JSON; -if (!JSON) { - // Create a JSON object only if one does not already exist. We create the -// object in a closure to avoid global variables. +// methods in a closure to avoid creating global variables. - JSON = function () { +if (!this.JSON) { + this.JSON = {}; +} +(function () { - function f(n) { // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - Date.prototype.toJSON = function () { + if (typeof Date.prototype.toJSON !== 'function') { -// Eventually, this method will be based on the date.toISOString method. + Date.prototype.toJSON = function (key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + @@ -1408,175 +1365,179 @@ if (!JSON) { f(this.getUTCSeconds()) + 'Z'; }; + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } - var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; - function quote(string) { + function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. - return escapeable.test(string) ? - '"' + string.replace(escapeable, function (a) { - var c = meta[a]; - if (typeof c === 'string') { - return c; - } - c = a.charCodeAt(); - return '\\u00' + Math.floor(c / 16).toString(16) + - (c % 16).toString(16); - }) + '"' : - '"' + string + '"'; - } + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } - function str(key, holder) { + function str(key, holder) { // Produce a string from holder[key]. - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } // What happens next depends on the value's type. - switch (typeof value) { - case 'string': - return quote(value); + switch (typeof value) { + case 'string': + return quote(value); - case 'number': + case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) ? String(value) : 'null'; + return isFinite(value) ? String(value) : 'null'; - case 'boolean': - case 'null': + case 'boolean': + case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. - return String(value); + return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. - case 'object': + case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. - if (!value) { - return 'null'; - } + if (!value) { + return 'null'; + } // Make an array to hold the partial results of stringifying this object value. - gap += indent; - partial = []; + gap += indent; + partial = []; -// If the object has a dontEnum length property, we'll treat it as an array. +// Is the value an array? - if (typeof value.length === 'number' && - !(value.propertyIsEnumerable('length'))) { + if (Object.prototype.toString.apply(value) === '[object Array]') { -// The object is an array. Stringify every element. Use null as a placeholder +// The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } // Join all of the elements together, separated with commas, and wrap them in // brackets. - v = partial.length === 0 ? '[]' : - gap ? '[\n' + gap + partial.join(',\n' + gap) + - '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } // If the replacer is an array, use it to select the members to be stringified. - if (typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value, rep); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); } } - } else { + } + } else { // Otherwise, iterate through all of the keys in the object. - for (k in value) { - v = str(k, value, rep); + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } + } // Join all of the member texts together, separated with commas, // and wrap them in braces. - v = partial.length === 0 ? '{}' : - gap ? '{\n' + gap + partial.join(',\n' + gap) + - '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - - return null; + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; } + return ''; + } -// Return the JSON object containing the stringify, parse, and quote methods. +// If the JSON object does not yet have a stringify method, give it one. - return { - stringify: function (value, replacer, space) { + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function @@ -1584,123 +1545,124 @@ if (!JSON) { // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. - var i; - gap = ''; - indent = ''; - if (space) { + var i; + gap = ''; + indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; } -// If there is no replacer parameter, use the default replacer. +// If the space parameter is a string, it will be used as the indent string. - if (!replacer) { - rep = function (key, value) { - if (!Object.hasOwnProperty.call(this, key)) { - return undefined; - } - return value; - }; + } else if (typeof space === 'string') { + indent = space; + } -// The replacer can be a function or an array. Otherwise, throw an error. +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. - } else if (typeof replacer === 'function' || - (typeof replacer === 'object' && - typeof replacer.length === 'number')) { - rep = replacer; - } else { - throw new Error('JSON.stringify'); - } + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. - return str('', {'': value}); - }, + return str('', {'': value}); + }; + } - parse: function (text, reviver) { +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. - var j; + var j; - function walk(holder, key) { + function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; } } } - return reviver.call(holder, key, value); } + return reviver.call(holder, key, value); + } + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. -// Parsing happens in three stages. In the first stage, we run the text against -// regular expressions that look for non-JSON patterns. We are especially -// concerned with '()' and 'new' because they can cause invocation, and '=' -// because it can cause mutation. But just to be safe, we want to reject all -// unexpected forms. + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } -// We split the first stage into 4 regexp operations in order to work around +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace all backslash pairs with '@' (a non-JSON character). Second, we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@'). + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { -// In the second stage we use the eval function to compile the text into a +// In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. - j = eval('(' + text + ')'); + j = eval('(' + text + ')'); -// In the optional third stage, we recursively walk the new structure, passing +// In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } // If the text is not JSON parseable, then a SyntaxError is thrown. - throw new SyntaxError('JSON.parse'); - }, - - quote: quote + throw new SyntaxError('JSON.parse'); }; - }(); -} + } +}()); Jemplate.JSON = { @@ -1744,8 +1706,8 @@ Jemplate.JSON = { sub json2 { <<'...'; /* - json2.js - 2008-03-24 + http://www.JSON.org/json2.js + 2009-04-16 Public Domain. @@ -1753,44 +1715,48 @@ sub json2 { See http://www.JSON.org/js.html - This file creates a global JSON object containing three methods: stringify, - parse, and quote. - + This file creates a global JSON object containing two methods: stringify + and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object - values are stringified for objects without a toJSON - method. It can be a function or an array. + values are stringified for objects. It can be a + function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each - level. If it is a string (such as '\t'), it contains the - characters used to indent at each level. + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON - method, its toJSON method with be called and the result will be + method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method will - be passed the key associated with the value, and this will be bound - to the object holding the key. + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the object holding the key. + + For example, this would serialize Dates as ISO strings. - This is the toJSON method added to Dates: + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - function toJSON(key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; - } + }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing @@ -1798,35 +1764,24 @@ sub json2 { serialized. If your method returns undefined, then the member will be excluded from the serialization. - If no replacer parameter is provided, then a default replacer - will be used: - - function replacer(key, value) { - return Object.hasOwnProperty.call(this, key) ? - value : undefined; - } - - The default replacer is passed the key and value for each item in - the structure. It excludes inherited members. - - If the replacer parameter is an array, then it will be used to - select the members to be serialized. It filters the results such - that only members with keys listed in the replacer array are + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are stringified. - Values that do not have JSON representaions, such as undefined or + Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. - The optional space parameter produces a stringification of the value - that is filled with line breaks and indentation to make it easier to - read. + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then - then indentation will be that many spaces. + the indentation will be that many spaces. Example: @@ -1837,16 +1792,22 @@ sub json2 { text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, and - its return value is used instead of the original value. If it - returns what it received, then structure is not modified. If it - returns undefined then the member is deleted. + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. Example: @@ -1866,45 +1827,57 @@ sub json2 { return value; }); - - JSON.quote(text) - This method wraps a string in quotes, escaping some characters - as needed. + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); This is a reference implementation. You are free to copy, modify, or redistribute. - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD THIRD PARTY - CODE INTO YOUR PAGES. + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. */ -/*jslint regexp: true, forin: true, evil: true */ +/*jslint evil: true */ /*global JSON */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, floor, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, length, - parse, propertyIsEnumerable, prototype, push, quote, replace, stringify, - test, toJSON, toString + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf */ -if (!JSON) var JSON; -if (!JSON) { - // Create a JSON object only if one does not already exist. We create the -// object in a closure to avoid global variables. +// methods in a closure to avoid creating global variables. - JSON = function () { +if (!this.JSON) { + this.JSON = {}; +} +(function () { - function f(n) { // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } - Date.prototype.toJSON = function () { + if (typeof Date.prototype.toJSON !== 'function') { -// Eventually, this method will be based on the date.toISOString method. + Date.prototype.toJSON = function (key) { return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + @@ -1914,175 +1887,179 @@ if (!JSON) { f(this.getUTCSeconds()) + 'Z'; }; + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } - var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; - function quote(string) { + function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. - return escapeable.test(string) ? - '"' + string.replace(escapeable, function (a) { - var c = meta[a]; - if (typeof c === 'string') { - return c; - } - c = a.charCodeAt(); - return '\\u00' + Math.floor(c / 16).toString(16) + - (c % 16).toString(16); - }) + '"' : - '"' + string + '"'; - } + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } - function str(key, holder) { + function str(key, holder) { // Produce a string from holder[key]. - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } // What happens next depends on the value's type. - switch (typeof value) { - case 'string': - return quote(value); + switch (typeof value) { + case 'string': + return quote(value); - case 'number': + case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) ? String(value) : 'null'; + return isFinite(value) ? String(value) : 'null'; - case 'boolean': - case 'null': + case 'boolean': + case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. - return String(value); + return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. - case 'object': + case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. - if (!value) { - return 'null'; - } + if (!value) { + return 'null'; + } // Make an array to hold the partial results of stringifying this object value. - gap += indent; - partial = []; + gap += indent; + partial = []; -// If the object has a dontEnum length property, we'll treat it as an array. +// Is the value an array? - if (typeof value.length === 'number' && - !(value.propertyIsEnumerable('length'))) { + if (Object.prototype.toString.apply(value) === '[object Array]') { -// The object is an array. Stringify every element. Use null as a placeholder +// The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } // Join all of the elements together, separated with commas, and wrap them in // brackets. - v = partial.length === 0 ? '[]' : - gap ? '[\n' + gap + partial.join(',\n' + gap) + - '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } // If the replacer is an array, use it to select the members to be stringified. - if (typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value, rep); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); } } - } else { + } + } else { // Otherwise, iterate through all of the keys in the object. - for (k in value) { - v = str(k, value, rep); + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } + } // Join all of the member texts together, separated with commas, // and wrap them in braces. - v = partial.length === 0 ? '{}' : - gap ? '{\n' + gap + partial.join(',\n' + gap) + - '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - - return null; + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; } + return ''; + } -// Return the JSON object containing the stringify, parse, and quote methods. +// If the JSON object does not yet have a stringify method, give it one. - return { - stringify: function (value, replacer, space) { + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function @@ -2090,123 +2067,124 @@ if (!JSON) { // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. - var i; - gap = ''; - indent = ''; - if (space) { + var i; + gap = ''; + indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; } -// If there is no replacer parameter, use the default replacer. +// If the space parameter is a string, it will be used as the indent string. - if (!replacer) { - rep = function (key, value) { - if (!Object.hasOwnProperty.call(this, key)) { - return undefined; - } - return value; - }; + } else if (typeof space === 'string') { + indent = space; + } -// The replacer can be a function or an array. Otherwise, throw an error. +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. - } else if (typeof replacer === 'function' || - (typeof replacer === 'object' && - typeof replacer.length === 'number')) { - rep = replacer; - } else { - throw new Error('JSON.stringify'); - } + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. - return str('', {'': value}); - }, + return str('', {'': value}); + }; + } - parse: function (text, reviver) { +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. - var j; + var j; - function walk(holder, key) { + function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; } } } - return reviver.call(holder, key, value); } + return reviver.call(holder, key, value); + } + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. -// Parsing happens in three stages. In the first stage, we run the text against -// regular expressions that look for non-JSON patterns. We are especially -// concerned with '()' and 'new' because they can cause invocation, and '=' -// because it can cause mutation. But just to be safe, we want to reject all -// unexpected forms. + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } -// We split the first stage into 4 regexp operations in order to work around +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace all backslash pairs with '@' (a non-JSON character). Second, we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@'). + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { -// In the second stage we use the eval function to compile the text into a +// In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. - j = eval('(' + text + ')'); + j = eval('(' + text + ')'); -// In the optional third stage, we recursively walk the new structure, passing +// In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } // If the text is not JSON parseable, then a SyntaxError is thrown. - throw new SyntaxError('JSON.parse'); - }, - - quote: quote + throw new SyntaxError('JSON.parse'); }; - }(); -} + } +}()); ... } diff --git a/lib/Jemplate/Runtime/Compact.pm b/lib/Jemplate/Runtime/Compact.pm index a35d60f..d022a26 100644 --- a/lib/Jemplate/Runtime/Compact.pm +++ b/lib/Jemplate/Runtime/Compact.pm @@ -5,7 +5,7 @@ use warnings; sub main { return &kernel } sub kernel { <<'...'; -if(typeof Jemplate=="undefined"){var Jemplate=function(){this.init.apply(this,arguments)}}Jemplate.VERSION="0.22";Jemplate.process=function(){var A=new Jemplate(Jemplate.prototype.config);return A.process.apply(A,arguments)};(function(){if(!Jemplate.templateMap){Jemplate.templateMap={}}var proto=Jemplate.prototype={};proto.config={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,FILTERS:{},INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};proto.defaults={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};Jemplate.init=function(config){Jemplate.prototype.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof Jemplate.prototype.config[i]=="undefined"){Jemplate.prototype.config[i]=Jemplate.prototype.defaults[i]}}};proto.init=function(config){this.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof this.config[i]=="undefined"){this.config[i]=Jemplate.prototype.defaults[i]}}};proto.process=function(template,data,output){var context=this.config.CONTEXT||new Jemplate.Context();context.config=this.config;context.stash=new Jemplate.Stash(this.config.STASH,this.config);context.__filter__=new Jemplate.Filter();context.__filter__.config=this.config;context.__plugin__=new Jemplate.Plugin();context.__plugin__.config=this.config;var result;var proc=function(input){try{if(typeof context.config.PRE_PROCESS=="string"){context.config.PRE_PROCESS=[context.config.PRE_PROCESS]}for(var i=0;i/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index Date: Fri, 8 Jan 2010 17:04:21 -0800 Subject: [PATCH 10/29] Added pairs testing stub --- t/pairs.t | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 t/pairs.t diff --git a/t/pairs.t b/t/pairs.t new file mode 100644 index 0000000..1f6e916 --- /dev/null +++ b/t/pairs.t @@ -0,0 +1,19 @@ +use Test::More; + +BEGIN { + plan skip_all => "JavaScript::V8x::TestMoreish not available" unless eval { require JavaScript::V8x::TestMoreish }; +} + +plan qw/no_plan/; + +use JavaScript::V8x::TestMoreish; + +test_js_eval <<'_END_'; +diag( "Yoink!" ); +_END_ + +test_js <<'_END_'; +areEqual( 1, 1 ) +_END_ + +1; From d715e672b78b86e5eabede48ac1733b29eb7e38e Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Sat, 9 Jan 2010 17:10:13 -0800 Subject: [PATCH 11/29] Added .pairs (VMethod) functionality with tests Fixed quoted.t failing test Version bump for testing release --- Changes | 7 + Makefile.PL | 4 + README | 12 +- bin/jemplate | 987 +++++++++++++++++--------------- js | 1 + lib/Jemplate.pm | 8 +- lib/Jemplate/Runtime.pm | 14 + lib/Jemplate/Runtime/Compact.pm | 2 +- src/js/kernel.compact.js | 2 +- src/js/kernel.js | 14 + t/pairs.t | 25 +- t/quoted.t | 2 +- 12 files changed, 597 insertions(+), 481 deletions(-) create mode 120000 js diff --git a/Changes b/Changes index 00d839e..4cdcefc 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,10 @@ +--- +version: 0.24_4 +date: Saturday January 09 17:06:40 PST 2010 +changes: +- added .pairs (VMethod) functionality with tests +- fixed quoted.t failing test + --- version: 0.24_3 date: Mon Jun 8 2009 diff --git a/Makefile.PL b/Makefile.PL index 728ca7f..423d3c4 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -18,6 +18,10 @@ all_from 'lib/Jemplate.pm'; #build_requires 'Directory::Scratch'; +if (-e 'inc/.author') { + my $all_from = join '/', 'lib', split m/-/, name . '.pm'; + `perldoc -tF $all_from > README` if ! -e 'README' || (stat $all_from)[9] > (stat 'README')[9]; +} requires 'Template' => '2.19'; requires 'File::Find::Rule' => '0.30'; diff --git a/README b/README index c4e7eda..0a21aca 100644 --- a/README +++ b/README @@ -1,3 +1,6 @@ +VERSION + Version 0.24_4 + NAME Jemplate - JavaScript Templating with Template Toolkit @@ -161,12 +164,13 @@ CURRENT SUPPORT * [% LAST %] * [% CLEAR %] * [%# this is a comment %] + * [% MACRO name(param1, param2) BLOCK %] ... [% END %] ALL of the string virtual functions are supported. ALL of the array virtual functions are supported: - ALL of the hash virtual functions are supported (except for import): + ALL of the hash virtual functions are supported: MANY of the standard filters are implemented. @@ -185,8 +189,8 @@ BROWSER SUPPORT All tests run 100% successful in the above browsers. DEVELOPMENT - The bleeding edge code is available via Subversion at - http://svn.jemplate.net/repo/trunk/ + The bleeding edge code is available via Git at + git://github.com/ingydotnet/jemplate.git You can run the runtime tests directly from http://svn.jemplate.net/repo/trunk/tests/run/index.html or from the @@ -229,6 +233,8 @@ AUTHORS Robert Krimen + Nickolay Platonov + COPYRIGHT Copyright (c) 2006-2008. Ingy döt Net. diff --git a/bin/jemplate b/bin/jemplate index dcdb0bf..3dd6ed4 100755 --- a/bin/jemplate +++ b/bin/jemplate @@ -182,15 +182,13 @@ BEGIN { package File::Find::Rule; use strict; -use vars qw/$VERSION $AUTOLOAD/; use File::Spec; use Text::Glob 'glob_to_regex'; use Number::Compare; use Carp qw/croak/; use File::Find (); # we're only wrapping for now -use Cwd; # 5.00503s File::Find goes screwy with max_depth == 0 -$VERSION = '0.30'; +our $VERSION = '0.32'; sub import { my $pkg = shift; @@ -245,8 +243,8 @@ sub new { my $referent = shift; my $class = ref $referent || $referent; bless { - rules => [], # [0] - subs => [], # [1] + rules => [], + subs => {}, iterator => [], extras => {}, maxdepth => undef, @@ -349,15 +347,15 @@ use vars qw( @stat_tests ); sub any { my $self = _force_object shift; - my @rulesets = @_; - + # compile all the subrules to code fragments push @{ $self->{rules} }, { - rule => 'any', - code => '(' . join( ' || ', map { - "( " . $_->_compile( $self->{subs} ) . " )" - } @_ ) . ")", + rule => "any", + code => '(' . join( ' || ', map '( ' . $_->_compile . ' )', @_ ). ')', args => \@_, }; + + # merge all the subs hashes of the kids into ourself + %{ $self->{subs} } = map { %{ $_->{subs} } } $self, @_; $self; } @@ -366,15 +364,15 @@ sub any { sub not { my $self = _force_object shift; - my @rulesets = @_; push @{ $self->{rules} }, { rule => 'not', - args => \@rulesets, - code => '(' . join ( ' && ', map { - "!(". $_->_compile( $self->{subs} ) . ")" - } @_ ) . ")", + args => \@_, + code => '(' . join ( ' && ', map { "!(". $_->_compile . ")" } @_ ) . ")", }; + + # merge all the subs hashes into us + %{ $self->{subs} } = map { %{ $_->{subs} } } $self, @_; $self; } @@ -465,6 +463,7 @@ sub relative () { sub DESTROY {} sub AUTOLOAD { + our $AUTOLOAD; $AUTOLOAD =~ /::not_([^:]*)$/ or croak "Can't locate method $AUTOLOAD"; my $method = $1; @@ -485,8 +484,8 @@ sub in { my $self = _force_object shift; my @found; - my $fragment = $self->_compile( $self->{subs} ); - my @subs = @{ $self->{subs} }; + my $fragment = $self->_compile; + my %subs = %{ $self->{subs} }; warn "relative mode handed multiple paths - that's a bit silly\n" if $self->{relative} && @_ > 1; @@ -526,11 +525,10 @@ sub in { }'; #use Data::Dumper; - #print Dumper \@subs; + #print Dumper \%subs; #warn "Compiled sub: '$code'\n"; my $sub = eval "$code" or die "compile error '$code' $@"; - my $cwd = getcwd; for my $path (@_) { # $topdir is used for relative and maxdepth $topdir = $path; @@ -540,7 +538,6 @@ sub in { unless $topdir eq '/'; $self->_call_find( { %{ $self->{extras} }, wanted => $sub }, $path ); } - chdir $cwd; return @found; } @@ -552,19 +549,20 @@ sub _call_find { sub _compile { my $self = shift; - my $subs = shift; # [1] return '1' unless @{ $self->{rules} }; my $code = join " && ", map { if (ref $_->{code}) { - push @$subs, $_->{code}; - "\$subs[$#{$subs}]->(\@args) # $_->{rule}\n"; + my $key = "$_->{code}"; + $self->{subs}{$key} = $_->{code}; + "\$subs{'$key'}->(\@args) # $_->{rule}\n"; } else { "( $_->{code} ) # $_->{rule}\n"; } } @{ $self->{rules} }; + #warn $code; return $code; } @@ -597,15 +595,14 @@ BEGIN { package Template::Constants; require Exporter; - use strict; use warnings; -use base 'Exporter'; - +use Exporter; use vars qw( @EXPORT_OK %EXPORT_TAGS ); -use vars qw( $DEBUG_OPTIONS @STATUS @ERROR @CHOMP @DEBUG); +use vars qw( $DEBUG_OPTIONS @STATUS @ERROR @CHOMP @DEBUG @ISA ); +@ISA = qw( Exporter ); -our $VERSION = 2.74; +our $VERSION = 2.75; @@ -739,7 +736,7 @@ use strict; use warnings; use Template::Constants; -our $VERSION = 2.77; +our $VERSION = 2.78; @@ -747,7 +744,8 @@ sub new { my $class = shift; my ($argnames, @args, $arg, $cfg); - { no strict qw( refs ); + { no strict 'refs'; + no warnings 'once'; $argnames = \@{"$class\::BASEARGS"} || [ ]; } @@ -759,10 +757,10 @@ sub new { } # fold all remaining args into a hash, or use provided hash ref - $cfg = defined $_[0] && UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ }; + $cfg = defined $_[0] && ref($_[0]) eq 'HASH' ? shift : { @_ }; my $self = bless { - map { ($_ => shift @args) } @$argnames, + (map { ($_ => shift @args) } @$argnames), _ERROR => '', DEBUG => 0, }, $class; @@ -797,11 +795,6 @@ sub _init { } -sub DEBUG { - my $self = shift; - print STDERR "DEBUG: ", @_; -} - sub debug { my $self = shift; my $msg = join('', @_); @@ -847,7 +840,7 @@ use vars qw( $VERSION $DEBUG $ERROR $INSTDIR $LATEX_PATH $PDFLATEX_PATH $DVIPS_PATH $STASH $SERVICE $CONTEXT $CONSTANTS @PRELOAD ); -$VERSION = 2.74; +$VERSION = 2.75; $DEBUG = 0 unless defined $DEBUG; $ERROR = ''; $CONTEXT = 'Template::Context'; @@ -883,9 +876,7 @@ sub load { my ($class, $module) = @_; $module =~ s[::][/]g; $module .= '.pm'; - eval { - require $module; - }; + eval { require $module; }; return $@ ? $class->error("failed to load $module: $@") : 1; } @@ -893,20 +884,20 @@ sub load { sub parser { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($PARSER); return $PARSER->new($params) - || $class->error("failed to create parser: ", $PARSER->error); + || $class->error("failed to create parser: ", $PARSER->error); } sub provider { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($PROVIDER); return $PROVIDER->new($params) @@ -918,26 +909,26 @@ sub provider { sub plugins { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($PLUGINS); return $PLUGINS->new($params) - || $class->error("failed to create plugin provider: ", - $PLUGINS->error); + || $class->error("failed to create plugin provider: ", + $PLUGINS->error); } sub filters { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($FILTERS); return $FILTERS->new($params) - || $class->error("failed to create filter provider: ", - $FILTERS->error); + || $class->error("failed to create filter provider: ", + $FILTERS->error); } @@ -948,56 +939,56 @@ sub iterator { return undef unless $class->load($ITERATOR); return $ITERATOR->new($list, @_) - || $class->error("failed to create iterator: ", $ITERATOR->error); + || $class->error("failed to create iterator: ", $ITERATOR->error); } sub stash { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($STASH); return $STASH->new($params) - || $class->error("failed to create stash: ", $STASH->error); + || $class->error("failed to create stash: ", $STASH->error); } sub context { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($CONTEXT); return $CONTEXT->new($params) - || $class->error("failed to create context: ", $CONTEXT->error); + || $class->error("failed to create context: ", $CONTEXT->error); } sub service { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($SERVICE); return $SERVICE->new($params) - || $class->error("failed to create context: ", $SERVICE->error); + || $class->error("failed to create context: ", $SERVICE->error); } sub constants { my $class = shift; - my $params = defined($_[0]) && UNIVERSAL::isa($_[0], 'HASH') - ? shift : { @_ }; + my $params = defined($_[0]) && ref($_[0]) eq 'HASH' + ? shift : { @_ }; return undef unless $class->load($CONSTANTS); return $CONSTANTS->new($params) - || $class->error("failed to create constants namespace: ", - $CONSTANTS->error); + || $class->error("failed to create constants namespace: ", + $CONSTANTS->error); } @@ -1005,7 +996,7 @@ sub constants { sub instdir { my ($class, $dir) = @_; my $inst = $INSTDIR - || return $class->error("no installation directory"); + || return $class->error("no installation directory"); $inst =~ s[/$][]g; $inst .= "/$dir" if $dir; return $inst; @@ -1143,7 +1134,7 @@ sub process { die $context->catch($@) if $@; - + return $output; } @@ -1170,9 +1161,9 @@ sub _dump { $output .= "BLOCK: $self->{ _BLOCK }\nDEFBLOCKS:\n"; if ($dblks = $self->{ _DEFBLOCKS }) { - foreach my $b (keys %$dblks) { - $output .= " $b: $dblks->{ $b }\n"; - } + foreach my $b (keys %$dblks) { + $output .= " $b: $dblks->{ $b }\n"; + } } return $output; @@ -1185,12 +1176,12 @@ sub as_perl { my ($class, $content) = @_; my ($block, $defblocks, $metadata) = @$content{ qw( BLOCK DEFBLOCKS METADATA ) }; - $block =~ s/\n/\n /g; + $block =~ s/\n(?!#line)/\n /g; $block =~ s/\s+$//; $defblocks = join('', map { my $code = $defblocks->{ $_ }; - $code =~ s/\n/\n /g; + $code =~ s/\n(?!#line)/\n /g; $code =~ s/\s*$//; " '$_' => $code,\n"; } keys %$defblocks); @@ -1224,7 +1215,7 @@ sub write_perl_file { my ($fh, $tmpfile); return $class->error("invalid filename: $file") - unless $file =~ /^(.+)$/s; + unless $file =~ /^(.+)$/s; eval { require File::Temp; @@ -1273,7 +1264,7 @@ use constant INFO => 1; use constant TEXT => 2; use overload q|""| => "as_string", fallback => 1; -our $VERSION = 2.69; +our $VERSION = 2.70; @@ -1303,16 +1294,15 @@ sub text { my $textref = $self->[ TEXT ]; if ($newtextref) { - $$newtextref .= $$textref if $textref && $textref ne $newtextref; - $self->[ TEXT ] = $newtextref; - return ''; - + $$newtextref .= $$textref if $textref && $textref ne $newtextref; + $self->[ TEXT ] = $newtextref; + return ''; } elsif ($textref) { - return $$textref; + return $$textref; } else { - return ''; + return ''; } } @@ -1332,11 +1322,11 @@ sub select_handler { @hlut{ @options } = (1) x @options; while ($type) { - return $type if $hlut{ $type }; + return $type if $hlut{ $type }; - # strip .element from the end of the exception type to find a - # more generic handler - $type =~ s/\.?[^\.]*$//; + # strip .element from the end of the exception type to find a + # more generic handler + $type =~ s/\.?[^\.]*$//; } return undef; } @@ -1360,6 +1350,9 @@ use base 'Template::Base'; use Template::Config; use Template::Exception; use Template::Constants; +use Scalar::Util 'blessed'; + +use constant EXCEPTION => 'Template::Exception'; our $VERSION = 2.80; our $DEBUG = 0 unless defined $DEBUG; @@ -1390,6 +1383,7 @@ sub process { # localise the variable stash with any parameters passed # and set the 'template' variable $params ||= { }; + # TODO: change this to C<||=> so we can use a template parameter $params->{ template } = $template unless ref $template eq 'CODE'; $context->localise($params); @@ -1500,7 +1494,7 @@ sub _recover { # point... unless a module like CGI::Carp messes around with the # DIE handler. return undef - unless UNIVERSAL::isa($$error, 'Template::Exception'); + unless blessed($$error) && $$error->isa(EXCEPTION); # a 'stop' exception is thrown by [% STOP %] - we return the output # buffer stored in the exception object @@ -1602,7 +1596,7 @@ use constant LOAD => 3; # mtime of template use constant NEXT => 4; # link to next item in cache linked list use constant STAT => 5; # Time last stat()ed -our $VERSION = 2.93; +our $VERSION = 2.94; our $DEBUG = 0 unless defined $DEBUG; our $ERROR = ''; @@ -1711,7 +1705,7 @@ sub load { || return ($self->error(), Template::Constants::STATUS_ERROR); foreach my $dir (@$paths) { - $path = "$dir/$name"; + $path = File::Spec->catfile($dir, $name); last INCPATH if $self->_template_modified($path); } @@ -1768,7 +1762,7 @@ sub paths { unshift(@ipaths, @$dpaths); next; } - elsif (UNIVERSAL::can($dir, 'paths')) { + elsif (ref($dir) && UNIVERSAL::can($dir, 'paths')) { $dpaths = $dir->paths() || return $self->error($dir->error()); unshift(@ipaths, @$dpaths); @@ -1852,8 +1846,8 @@ sub _init { my $wdir = $dir; $wdir =~ s[:][]g if $^O eq 'MSWin32'; $wdir =~ /(.*)/; # untaint - $wdir = $1; - $wdir = File::Spec->catfile($cdir, $1); + $wdir = "$1"; # quotes work around bug in Strawberry Perl + $wdir = File::Spec->catfile($cdir, $wdir); File::Path::mkpath($wdir) unless -d $wdir; } } @@ -1927,19 +1921,26 @@ sub _fetch { warn($self->error(), "\n"); } - # Now fetch template from source, compile, and cache. + # load template from source my ($template, $error) = $self->_load($name, $t_name); - unless ($error) { - ($template, $error) = $self->_compile($template, $self->_compiled_filename($name) ); - # Store compiled template and return it - return $self->store( $name, $template->{data} ) unless $error; + if ($error) { + # Template could not be fetched. Add to the negative/notfound cache. + $self->{ NOTFOUND }->{ $name } = time; + return ( $template, $error ); } - # Template could not be fetched. Add to the negative/notfound cache. - $self->{ NOTFOUND }->{ $name } = time; + # compile template source + ($template, $error) = $self->_compile($template, $self->_compiled_filename($name) ); - return ( $template, $error ); + if ($error) { + # return any compile time error + return ($template, $error); + } + else { + # Store compiled template and return it + return $self->store($name, $template->{data}) ; + } } @@ -2334,6 +2335,7 @@ sub _template_content { local *FH; if (open(FH, "< $path")) { local $/; + binmode(FH); $data = ; $mod_date = (stat($path))[9]; close(FH); @@ -2428,6 +2430,7 @@ sub _dump_cache { sub _decode_unicode { my $self = shift; my $string = shift; + return undef unless defined $string; use bytes; require Encode; @@ -2471,6 +2474,7 @@ package Template; use strict; use warnings; +use 5.006; use base 'Template::Base'; use Template::Config; @@ -2479,8 +2483,9 @@ use Template::Provider; use Template::Service; use File::Basename; use File::Path; +use Scalar::Util qw(blessed); -our $VERSION = '2.19'; +our $VERSION = '2.22'; our $ERROR = ''; our $DEBUG = 0; our $BINMODE = 0 unless defined $BINMODE; @@ -2493,7 +2498,7 @@ Template::Config->preload() if $ENV{ MOD_PERL }; sub process { my ($self, $template, $vars, $outstream, @opts) = @_; my ($output, $error); - my $options = (@opts == 1) && UNIVERSAL::isa($opts[0], 'HASH') + my $options = (@opts == 1) && ref($opts[0]) eq 'HASH' ? shift(@opts) : { @opts }; $options->{ binmode } = $BINMODE @@ -2510,7 +2515,7 @@ sub process { unless (ref $outstream) { my $outpath = $self->{ OUTPUT_PATH }; $outstream = "$outpath/$outstream" if $outpath; - } + } # send processed template to output stream, checking for error return ($self->error($error)) @@ -2591,7 +2596,7 @@ sub _output { } # call the print() method on an object that implements the method # (e.g. IO::Handle, Apache::Request, etc) - elsif (UNIVERSAL::can($where, 'print')) { + elsif (blessed($where) && $where->can('print')) { $where->print($$textref); } # a simple string is taken as a filename @@ -7952,18 +7957,14 @@ sub [#Rule 39 'atomdir', 1, sub -{ $_[0]->{ INFOR } || $_[0]->{ INWHILE } - ? 'last LOOP;' - : 'last;' } +{ $_[0]->block_label('last ', ';') } ], [#Rule 40 'atomdir', 1, sub -{ $_[0]->{ INFOR } - ? $factory->next() - : ($_[0]->{ INWHILE } - ? 'next LOOP;' - : 'next;') } +{ $_[0]->in_block('FOR') + ? $factory->next($_[0]->block_label) + : $_[0]->block_label('next ', ';') } ], [#Rule 41 'atomdir', 2, @@ -8048,13 +8049,12 @@ sub [#Rule 56 '@1-3', 0, sub -{ $_[0]->{ INFOR }++ } +{ $_[0]->enter_block('FOR') } ], [#Rule 57 'loop', 6, sub -{ $_[0]->{ INFOR }--; - $factory->foreach(@{$_[2]}, $_[5]) } +{ $factory->foreach(@{$_[2]}, $_[5], $_[0]->leave_block) } ], [#Rule 58 'loop', 3, @@ -8064,18 +8064,17 @@ sub [#Rule 59 '@2-3', 0, sub -{ $_[0]->{ INWHILE }++ } +{ $_[0]->enter_block('WHILE') } ], [#Rule 60 'loop', 6, sub -{ $_[0]->{ INWHILE }--; - $factory->while(@_[2, 5]) } +{ $factory->while(@_[2, 5], $_[0]->leave_block) } ], [#Rule 61 'loop', 3, sub -{ $factory->while(@_[3, 1]) } +{ $factory->while(@_[3, 1]) } ], [#Rule 62 'loopvar', 4, @@ -8648,17 +8647,6 @@ sub 1; - - - - - - - - - - - } # # Inline include of Template/Directive.pm @@ -8709,14 +8697,14 @@ sub { my \$context = shift || die "template sub called without context\\n"; my \$stash = \$context->stash; my \$output = ''; - my \$error; + my \$_tt_error; eval { BLOCK: { $block } }; if (\$@) { - \$error = \$context->catch(\$@, \\\$output); - die \$error unless \$error->type eq 'return'; + \$_tt_error = \$context->catch(\$@, \\\$output); + die \$_tt_error unless \$_tt_error->type eq 'return'; } return \$output; @@ -8734,14 +8722,14 @@ sub anon_block { $OUTPUT do { my \$output = ''; - my \$error; + my \$_tt_error; eval { BLOCK: { $block } }; if (\$@) { - \$error = \$context->catch(\$@, \\\$output); - die \$error unless \$error->type eq 'return'; + \$_tt_error = \$context->catch(\$@, \\\$output); + die \$_tt_error unless \$_tt_error->type eq 'return'; } \$output; @@ -8793,13 +8781,13 @@ sub ident { # does the first element of the identifier have a NAMESPACE # handler defined? if (ref $class && @$ident > 2 && ($ns = $class->{ NAMESPACE })) { - my $key = $ident->[0]; - $key =~ s/^'(.+)'$/$1/s; - if ($ns = $ns->{ $key }) { - return $ns->ident($ident); - } + my $key = $ident->[0]; + $key =~ s/^'(.+)'$/$1/s; + if ($ns = $ns->{ $key }) { + return $ns->ident($ident); + } } - + if (scalar @$ident <= 2 && ! $ident->[1]) { $ident = $ident->[0]; } @@ -8959,20 +8947,21 @@ sub if { sub foreach { - my ($class, $target, $list, $args, $block) = @_; + my ($class, $target, $list, $args, $block, $label) = @_; $args = shift @$args; $args = @$args ? ', { ' . join(', ', @$args) . ' }' : ''; + $label ||= 'LOOP'; my ($loop_save, $loop_set, $loop_restore, $setiter); if ($target) { - $loop_save = 'eval { $oldloop = ' . &ident($class, ["'loop'"]) . ' }'; - $loop_set = "\$stash->{'$target'} = \$value"; - $loop_restore = "\$stash->set('loop', \$oldloop)"; + $loop_save = 'eval { $_tt_oldloop = ' . &ident($class, ["'loop'"]) . ' }'; + $loop_set = "\$stash->{'$target'} = \$_tt_value"; + $loop_restore = "\$stash->set('loop', \$_tt_oldloop)"; } else { $loop_save = '$stash = $context->localise()'; - $loop_set = "\$stash->get(['import', [\$value]]) " - . "if ref \$value eq 'HASH'"; + $loop_set = "\$stash->get(['import', [\$_tt_value]]) " + . "if ref \$_tt_value eq 'HASH'"; $loop_restore = '$stash = $context->delocalise()'; } $block = pad($block, 3) if $PRETTY; @@ -8980,37 +8969,39 @@ sub foreach { return <iterator(\$list) + unless (UNIVERSAL::isa(\$_tt_list, 'Template::Iterator')) { + \$_tt_list = Template::Config->iterator(\$_tt_list) || die \$Template::Config::ERROR, "\\n"; } - (\$value, \$error) = \$list->get_first(); + (\$_tt_value, \$_tt_error) = \$_tt_list->get_first(); $loop_save; - \$stash->set('loop', \$list); + \$stash->set('loop', \$_tt_list); eval { -LOOP: while (! \$error) { +$label: while (! \$_tt_error) { $loop_set; $block; - (\$value, \$error) = \$list->get_next(); + (\$_tt_value, \$_tt_error) = \$_tt_list->get_next(); } }; $loop_restore; die \$@ if \$@; - \$error = 0 if \$error && \$error eq Template::Constants::STATUS_DONE; - die \$error if \$error; + \$_tt_error = 0 if \$_tt_error && \$_tt_error eq Template::Constants::STATUS_DONE; + die \$_tt_error if \$_tt_error; }; EOF } sub next { + my ($class, $label) = @_; + $label ||= 'LOOP'; return <get_next(); -next LOOP; +(\$_tt_value, \$_tt_error) = \$_tt_list->get_next(); +next $label; EOF } @@ -9067,19 +9058,20 @@ EOF sub while { - my ($class, $expr, $block) = @_; + my ($class, $expr, $block, $label) = @_; $block = pad($block, 2) if $PRETTY; + $label ||= 'LOOP'; return < $WHILE_MAX iterations)\\n" - unless \$failsafe; + unless \$_tt_failsafe; }; EOF } @@ -9099,9 +9091,9 @@ sub switch { $block = $case->[1]; $block = pad($block, 1) if $PRETTY; $caseblock .= <catch(\$@, \\\$output); - die \$error if \$error->type =~ /^return|stop\$/; - \$stash->set('error', \$error); - \$stash->set('e', \$error); - if (defined (\$handler = \$error->select_handler($handlers))) { + \$_tt_error = \$context->catch(\$@, \\\$output); + die \$_tt_error if \$_tt_error->type =~ /^return|stop\$/; + \$stash->set('error', \$_tt_error); + \$stash->set('e', \$_tt_error); + if (defined (\$_tt_handler = \$_tt_error->select_handler($handlers))) { $catchblock } $default @@ -9226,7 +9218,7 @@ sub clear { } -sub break { +sub OLD_break { return 'last LOOP;'; } @@ -9274,15 +9266,15 @@ sub view { return <get('view'); - my \$view = \$context->view($hash); - \$stash->set($name, \$view); - \$stash->set('view', \$view); + my \$_tt_oldv = \$stash->get('view'); + my \$_tt_view = \$context->view($hash); + \$stash->set($name, \$_tt_view); + \$stash->set('view', \$_tt_view); $block - \$stash->set('view', \$oldv); - \$view->seal(); + \$stash->set('view', \$_tt_oldv); + \$_tt_view->seal(); }; EOF } @@ -9306,14 +9298,14 @@ $block local(\$Template::Perl::context) = \$context; local(\$Template::Perl::stash) = \$stash; - my \$result = ''; - tie *Template::Perl::PERLOUT, 'Template::TieString', \\\$result; - my \$save_stdout = select *Template::Perl::PERLOUT; + my \$_tt_result = ''; + tie *Template::Perl::PERLOUT, 'Template::TieString', \\\$_tt_result; + my \$_tt_save_stdout = select *Template::Perl::PERLOUT; eval \$output; - select \$save_stdout; + select \$_tt_save_stdout; \$context->throw(\$@) if \$@; - \$result; + \$_tt_result; }; EOF } @@ -9358,12 +9350,12 @@ sub filter { $OUTPUT do { my \$output = ''; - my \$filter = \$context->filter($name) + my \$_tt_filter = \$context->filter($name) || \$context->throw(\$context->error); $block - &\$filter(\$output); + &\$_tt_filter(\$output); }; EOF } @@ -9404,20 +9396,20 @@ sub macro { my $nargs = scalar @$args; $args = join(', ', map { "'$_'" } @$args); $args = $nargs > 1 - ? "\@args{ $args } = splice(\@_, 0, $nargs)" - : "\$args{ $args } = shift"; + ? "\@_tt_args{ $args } = splice(\@_, 0, $nargs)" + : "\$_tt_args{ $args } = shift"; return <set('$ident', sub { my \$output = ''; - my (%args, \$params); + my (%_tt_args, \$_tt_params); $args; - \$params = shift; - \$params = { } unless ref(\$params) eq 'HASH'; - \$params = { \%args, %\$params }; + \$_tt_params = shift; + \$_tt_params = { } unless ref(\$_tt_params) eq 'HASH'; + \$_tt_params = { \%_tt_args, %\$_tt_params }; - my \$stash = \$context->localise(\$params); + my \$stash = \$context->localise(\$_tt_params); eval { $block }; @@ -9432,10 +9424,10 @@ EOF return <set('$ident', sub { - my \$params = \$_[0] if ref(\$_[0]) eq 'HASH'; + my \$_tt_params = \$_[0] if ref(\$_[0]) eq 'HASH'; my \$output = ''; - my \$stash = \$context->localise(\$params); + my \$stash = \$context->localise(\$_tt_params); eval { $block }; @@ -9515,9 +9507,9 @@ our $DEFAULT_STYLE = { }; our $QUOTED_ESCAPES = { - n => "\n", - r => "\r", - t => "\t", + n => "\n", + r => "\r", + t => "\t", }; our $CHOMP_FLAGS = qr/[-=~+]/; @@ -9528,7 +9520,7 @@ our $CHOMP_FLAGS = qr/[-=~+]/; sub new { my $class = shift; - my $config = $_[0] && UNIVERSAL::isa($_[0], 'HASH') ? shift(@_) : { @_ }; + my $config = $_[0] && ref($_[0]) eq 'HASH' ? shift(@_) : { @_ }; my ($tagstyle, $debug, $start, $end, $defaults, $grammar, $hash, $key, $udef); my $self = bless { @@ -9544,7 +9536,8 @@ sub new { FILE_INFO => 1, GRAMMAR => undef, _ERROR => '', - FACTORY => 'Template::Directive', + IN_BLOCK => [ ], + FACTORY => $config->{ FACTORY } || 'Template::Directive', }, $class; # update self with any relevant keys in config @@ -9590,11 +9583,41 @@ sub new { $self->new_style($config) || return $class->error($self->error()); - + return $self; } +sub enter_block { + my ($self, $name) = @_; + my $blocks = $self->{ IN_BLOCK }; + push(@{ $self->{ IN_BLOCK } }, $name); +} + +sub leave_block { + my $self = shift; + my $label = $self->block_label; + pop(@{ $self->{ IN_BLOCK } }); + return $label; +} + +sub in_block { + my ($self, $name) = @_; + my $blocks = $self->{ IN_BLOCK }; + return @$blocks && $blocks->[-1] eq $name; +} + +sub block_label { + my ($self, $prefix, $suffix) = @_; + my $blocks = $self->{ IN_BLOCK }; + my $name = @$blocks + ? $blocks->[-1] . scalar @$blocks + : undef; + return join('', grep { defined $_ } $prefix, $name, $suffix); +} + + + sub new_style { my ($self, $config) = @_; @@ -9638,7 +9661,7 @@ sub parse { my ($tokens, $block); $info->{ DEBUG } = $self->{ DEBUG_DIRS } - unless defined $info->{ DEBUG }; + unless defined $info->{ DEBUG }; # store for blocks defined in the template (see define_block()) @@ -9650,7 +9673,7 @@ sub parse { # split file into TEXT/DIRECTIVE chunks $tokens = $self->split_text($text) - || return undef; ## RETURN ## + || return undef; ## RETURN ## push(@{ $self->{ FILEINFO } }, $info); @@ -9659,7 +9682,7 @@ sub parse { pop(@{ $self->{ FILEINFO } }); - return undef unless $block; ## RETURN ## + return undef unless $block; ## RETURN ## $self->debug("compiled main template document block:\n$block") if $self->{ DEBUG } & Template::Constants::DEBUG_PARSER; @@ -9680,12 +9703,13 @@ sub split_text { my $style = $self->{ STYLE }->[-1]; my ($start, $end, $prechomp, $postchomp, $interp ) = @$style{ qw( START_TAG END_TAG PRE_CHOMP POST_CHOMP INTERPOLATE ) }; + my $tags_dir = $self->{ANYCASE} ? qri : qr; my @tokens = (); my $line = 1; - return \@tokens ## RETURN ## - unless defined $text && length $text; + return \@tokens ## RETURN ## + unless defined $text && length $text; # extract all directives from the text while ($text =~ s/ @@ -9718,7 +9742,7 @@ sub split_text { if ($chomp && $pre) { # chomp off whitespace and newline preceding directive if ($chomp == CHOMP_ALL) { - $pre =~ s{ (\n|^) [^\S\n]* \z }{}mx; + $pre =~ s{ (\r?\n|^) [^\S\n]* \z }{}mx; } elsif ($chomp == CHOMP_COLLAPSE) { $pre =~ s{ (\s+) \z }{ }x; @@ -9761,7 +9785,7 @@ sub split_text { # and now the directive, along with line number information if (length $dir) { # the TAGS directive is a compile-time switch - if ($dir =~ /^TAGS\s+(.*)/i) { + if ($dir =~ /^$tags_dir\s+(.*)/) { my @tags = split(/\s+/, $1); if (scalar @tags > 1) { ($start, $end) = map { quotemeta($_) } @tags; @@ -9796,7 +9820,7 @@ sub split_text { : ( 'TEXT', $text) ) if length $text; - return \@tokens; ## RETURN ## + return \@tokens; ## RETURN ## } @@ -9812,32 +9836,32 @@ sub interpolate_text { / ( (?: \\. | [^\$] ){1,3000} ) # escaped or non-'$' character [$1] | - ( \$ (?: # embedded variable [$2] - (?: \{ ([^\}]*) \} ) # ${ ... } [$3] - | - ([\w\.]+) # $word [$4] - ) - ) - /gx) { - - ($pre, $var, $dir) = ($1, $3 || $4, $2); - - # preceding text - if (defined($pre) && length($pre)) { - $line += $pre =~ tr/\n//; - $pre =~ s/\\\$/\$/g; - push(@tokens, 'TEXT', $pre); - } - # $variable reference + ( \$ (?: # embedded variable [$2] + (?: \{ ([^\}]*) \} ) # ${ ... } [$3] + | + ([\w\.]+) # $word [$4] + ) + ) + /gx) { + + ($pre, $var, $dir) = ($1, $3 || $4, $2); + + # preceding text + if (defined($pre) && length($pre)) { + $line += $pre =~ tr/\n//; + $pre =~ s/\\\$/\$/g; + push(@tokens, 'TEXT', $pre); + } + # $variable reference if ($var) { - $line += $dir =~ tr/\n/ /; - push(@tokens, [ $dir, $line, $self->tokenise_directive($var) ]); - } - # other '$' reference - treated as text - elsif ($dir) { - $line += $dir =~ tr/\n//; - push(@tokens, 'TEXT', $dir); - } + $line += $dir =~ tr/\n/ /; + push(@tokens, [ $dir, $line, $self->tokenise_directive($var) ]); + } + # other '$' reference - treated as text + elsif ($dir) { + $line += $dir =~ tr/\n//; + push(@tokens, 'TEXT', $dir); + } } return \@tokens; @@ -9855,108 +9879,114 @@ sub tokenise_directive { my @tokens = ( ); while ($text =~ - / - # strip out any comments - (\#[^\n]*) - | - # a quoted phrase matches in $3 - (["']) # $2 - opening quote, ' or " - ( # $3 - quoted text buffer - (?: # repeat group (no backreference) - \\\\ # an escaped backslash \\ - | # ...or... - \\\2 # an escaped quote \" or \' (match $1) - | # ...or... - . # any other character - | \n - )*? # non-greedy repeat - ) # end of $3 - \2 # match opening quote - | - # an unquoted number matches in $4 - (-?\d+(?:\.\d+)?) # numbers - | - # filename matches in $5 - ( \/?\w+(?:(?:\/|::?)\w*)+ | \/\w+) - | - # an identifier matches in $6 - (\w+) # variable identifier - | - # an unquoted word or symbol matches in $7 - ( [(){}\[\]:;,\/\\] # misc parenthesis and symbols - | [+\-*] # math operations - | \$\{? # dollar with option left brace - | => # like '=' - | [=!<>]?= | [!<>] # eqality tests - | &&? | \|\|? # boolean ops - | \.\.? # n..n sequence - | \S+ # something unquoted - ) # end of $7 - /gmxo) { - - # ignore comments to EOL - next if $1; - - # quoted string - if (defined ($token = $3)) { + / + # strip out any comments + (\#[^\n]*) + | + # a quoted phrase matches in $3 + (["']) # $2 - opening quote, ' or " + ( # $3 - quoted text buffer + (?: # repeat group (no backreference) + \\\\ # an escaped backslash \\ + | # ...or... + \\\2 # an escaped quote \" or \' (match $1) + | # ...or... + . # any other character + | \n + )*? # non-greedy repeat + ) # end of $3 + \2 # match opening quote + | + # an unquoted number matches in $4 + (-?\d+(?:\.\d+)?) # numbers + | + # filename matches in $5 + ( \/?\w+(?:(?:\/|::?)\w*)+ | \/\w+) + | + # an identifier matches in $6 + (\w+) # variable identifier + | + # an unquoted word or symbol matches in $7 + ( [(){}\[\]:;,\/\\] # misc parenthesis and symbols + | [+\-*] # math operations + | \$\{? # dollar with option left brace + | => # like '=' + | [=!<>]?= | [!<>] # eqality tests + | &&? | \|\|? # boolean ops + | \.\.? # n..n sequence + | \S+ # something unquoted + ) # end of $7 + /gmxo) { + + # ignore comments to EOL + next if $1; + + # quoted string + if (defined ($token = $3)) { # double-quoted string may include $variable references - if ($2 eq '"') { - if ($token =~ /[\$\\]/) { - $type = 'QUOTED'; - # unescape " and \ but leave \$ escaped so that - # interpolate_text() doesn't incorrectly treat it - # as a variable reference - for ($token) { - s/\\([^\$nrt])/$1/g; - s/\\([nrt])/$QUOTED_ESCAPES->{ $1 }/ge; - } - push(@tokens, ('"') x 2, - @{ $self->interpolate_text($token) }, - ('"') x 2); - next; - } + if ($2 eq '"') { + if ($token =~ /[\$\\]/) { + $type = 'QUOTED'; + # unescape " and \ but leave \$ escaped so that + # interpolate_text() doesn't incorrectly treat it + # as a variable reference + for ($token) { + s/\\([^\$nrt])/$1/g; + s/\\([nrt])/$QUOTED_ESCAPES->{ $1 }/ge; + } + push(@tokens, ('"') x 2, + @{ $self->interpolate_text($token) }, + ('"') x 2); + next; + } else { - $type = 'LITERAL'; - $token =~ s['][\\']g; - $token = "'$token'"; - } - } - else { - $type = 'LITERAL'; - $token = "'$token'"; - } - } - # number - elsif (defined ($token = $4)) { - $type = 'NUMBER'; - } - elsif (defined($token = $5)) { - $type = 'FILENAME'; - } - elsif (defined($token = $6)) { - # reserved words may be in lower case unless case sensitive - $uctoken = $anycase ? uc $token : $token; - if (defined ($type = $lextable->{ $uctoken })) { - $token = $uctoken; - } - else { - $type = 'IDENT'; - } - } - elsif (defined ($token = $7)) { - # reserved words may be in lower case unless case sensitive - $uctoken = $anycase ? uc $token : $token; - unless (defined ($type = $lextable->{ $uctoken })) { - $type = 'UNQUOTED'; - } - } + $type = 'LITERAL'; + $token =~ s['][\\']g; + $token = "'$token'"; + } + } + else { + $type = 'LITERAL'; + $token = "'$token'"; + } + } + # number + elsif (defined ($token = $4)) { + $type = 'NUMBER'; + } + elsif (defined($token = $5)) { + $type = 'FILENAME'; + } + elsif (defined($token = $6)) { + # Fold potential keywords to UPPER CASE if the ANYCASE option is + # set, unless (we've got some preceeding tokens and) the previous + # token is a DOT op. This prevents the 'last' in 'data.last' + # from being interpreted as the LAST keyword. + $uctoken = + ($anycase && (! @tokens || $tokens[-2] ne 'DOT')) + ? uc $token + : $token; + if (defined ($type = $lextable->{ $uctoken })) { + $token = $uctoken; + } + else { + $type = 'IDENT'; + } + } + elsif (defined ($token = $7)) { + # reserved words may be in lower case unless case sensitive + $uctoken = $anycase ? uc $token : $token; + unless (defined ($type = $lextable->{ $uctoken })) { + $type = 'UNQUOTED'; + } + } - push(@tokens, $type, $token); + push(@tokens, $type, $token); } - return \@tokens; ## RETURN ## + return \@tokens; ## RETURN ## } @@ -9967,7 +9997,7 @@ sub define_block { || return undef; $self->debug("compiled block '$name':\n$block") - if $self->{ DEBUG } & Template::Constants::DEBUG_PARSER; + if $self->{ DEBUG } & Template::Constants::DEBUG_PARSER; $defblock->{ $name } = $block; @@ -10012,6 +10042,7 @@ sub location { my $file = $info->{ path } || $info->{ name } || '(unknown template)'; $line =~ s/\-.*$//; # might be 'n-n' + $line ||= 1; return "#line $line \"$file\"\n"; } @@ -10022,7 +10053,7 @@ sub _parse { my ($self, $tokens, $info) = @_; my ($token, $value, $text, $line, $inperl); my ($state, $stateno, $status, $action, $lookup, $coderet, @codevars); - my ($lhs, $len, $code); # rule contents + my ($lhs, $len, $code); # rule contents my $stack = [ [ 0, undef ] ]; # DFA stack @@ -10041,152 +10072,152 @@ sub _parse { my $in_string = 0; while(1) { - # get state number and state - $stateno = $stack->[-1]->[0]; - $state = $states->[$stateno]; - - # see if any lookaheads exist for the current state - if (exists $state->{'ACTIONS'}) { - - # get next token and expand any directives (i.e. token is an - # array ref) onto the front of the token list - while (! defined $token && @$tokens) { - $token = shift(@$tokens); - if (ref $token) { - ($text, $line, $token) = @$token; - if (ref $token) { - if ($info->{ DEBUG } && ! $in_string) { + # get state number and state + $stateno = $stack->[-1]->[0]; + $state = $states->[$stateno]; + + # see if any lookaheads exist for the current state + if (exists $state->{'ACTIONS'}) { + + # get next token and expand any directives (i.e. token is an + # array ref) onto the front of the token list + while (! defined $token && @$tokens) { + $token = shift(@$tokens); + if (ref $token) { + ($text, $line, $token) = @$token; + if (ref $token) { + if ($info->{ DEBUG } && ! $in_string) { # - - - - - - - - - - - - - - - - - - - - - - - - - - # This is gnarly. Look away now if you're easily + # This is gnarly. Look away now if you're easily # frightened. We're pushing parse tokens onto the # pending list to simulate a DEBUG directive like so: - # [% DEBUG msg line='20' text='INCLUDE foo' %] + # [% DEBUG msg line='20' text='INCLUDE foo' %] # - - - - - - - - - - - - - - - - - - - - - - - - - - my $dtext = $text; - $dtext =~ s[(['\\])][\\$1]g; - unshift(@$tokens, - DEBUG => 'DEBUG', - IDENT => 'msg', - IDENT => 'line', - ASSIGN => '=', - LITERAL => "'$line'", - IDENT => 'text', - ASSIGN => '=', - LITERAL => "'$dtext'", - IDENT => 'file', - ASSIGN => '=', - LITERAL => "'$info->{ name }'", - (';') x 2, - @$token, - (';') x 2); - } - else { - unshift(@$tokens, @$token, (';') x 2); - } - $token = undef; # force redo - } - elsif ($token eq 'ITEXT') { - if ($inperl) { - # don't perform interpolation in PERL blocks - $token = 'TEXT'; - $value = $text; - } - else { - unshift(@$tokens, - @{ $self->interpolate_text($text, $line) }); - $token = undef; # force redo - } - } - } - else { - # toggle string flag to indicate if we're crossing - # a string boundary - $in_string = ! $in_string if $token eq '"'; - $value = shift(@$tokens); - } - }; - # clear undefined token to avoid 'undefined variable blah blah' - # warnings and let the parser logic pick it up in a minute - $token = '' unless defined $token; - - # get the next state for the current lookahead token - $action = defined ($lookup = $state->{'ACTIONS'}->{ $token }) - ? $lookup - : defined ($lookup = $state->{'DEFAULT'}) - ? $lookup - : undef; - } - else { - # no lookahead actions - $action = $state->{'DEFAULT'}; - } + my $dtext = $text; + $dtext =~ s[(['\\])][\\$1]g; + unshift(@$tokens, + DEBUG => 'DEBUG', + IDENT => 'msg', + IDENT => 'line', + ASSIGN => '=', + LITERAL => "'$line'", + IDENT => 'text', + ASSIGN => '=', + LITERAL => "'$dtext'", + IDENT => 'file', + ASSIGN => '=', + LITERAL => "'$info->{ name }'", + (';') x 2, + @$token, + (';') x 2); + } + else { + unshift(@$tokens, @$token, (';') x 2); + } + $token = undef; # force redo + } + elsif ($token eq 'ITEXT') { + if ($inperl) { + # don't perform interpolation in PERL blocks + $token = 'TEXT'; + $value = $text; + } + else { + unshift(@$tokens, + @{ $self->interpolate_text($text, $line) }); + $token = undef; # force redo + } + } + } + else { + # toggle string flag to indicate if we're crossing + # a string boundary + $in_string = ! $in_string if $token eq '"'; + $value = shift(@$tokens); + } + }; + # clear undefined token to avoid 'undefined variable blah blah' + # warnings and let the parser logic pick it up in a minute + $token = '' unless defined $token; + + # get the next state for the current lookahead token + $action = defined ($lookup = $state->{'ACTIONS'}->{ $token }) + ? $lookup + : defined ($lookup = $state->{'DEFAULT'}) + ? $lookup + : undef; + } + else { + # no lookahead actions + $action = $state->{'DEFAULT'}; + } - # ERROR: no ACTION - last unless defined $action; + # ERROR: no ACTION + last unless defined $action; - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # shift (+ive ACTION) - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if ($action > 0) { - push(@$stack, [ $action, $value ]); - $token = $value = undef; - redo; - }; + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # shift (+ive ACTION) + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - + if ($action > 0) { + push(@$stack, [ $action, $value ]); + $token = $value = undef; + redo; + }; - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # reduce (-ive ACTION) - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ($lhs, $len, $code) = @{ $rules->[ -$action ] }; + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # reduce (-ive ACTION) + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ($lhs, $len, $code) = @{ $rules->[ -$action ] }; - # no action imples ACCEPTance - $action - or $status = ACCEPT; + # no action imples ACCEPTance + $action + or $status = ACCEPT; - # use dummy sub if code ref doesn't exist - $code = sub { $_[1] } - unless $code; + # use dummy sub if code ref doesn't exist + $code = sub { $_[1] } + unless $code; - @codevars = $len - ? map { $_->[1] } @$stack[ -$len .. -1 ] - : (); + @codevars = $len + ? map { $_->[1] } @$stack[ -$len .. -1 ] + : (); - eval { - $coderet = &$code( $self, @codevars ); - }; - if ($@) { - my $err = $@; - chomp $err; - return $self->_parse_error($err); - } + eval { + $coderet = &$code( $self, @codevars ); + }; + if ($@) { + my $err = $@; + chomp $err; + return $self->_parse_error($err); + } - # reduce stack by $len - splice(@$stack, -$len, $len); + # reduce stack by $len + splice(@$stack, -$len, $len); - # ACCEPT - return $coderet ## RETURN ## - if $status == ACCEPT; + # ACCEPT + return $coderet ## RETURN ## + if $status == ACCEPT; - # ABORT - return undef ## RETURN ## - if $status == ABORT; + # ABORT + return undef ## RETURN ## + if $status == ABORT; - # ERROR - last - if $status == ERROR; + # ERROR + last + if $status == ERROR; } continue { - push(@$stack, [ $states->[ $stack->[-1][0] ]->{'GOTOS'}->{ $lhs }, - $coderet ]), + push(@$stack, [ $states->[ $stack->[-1][0] ]->{'GOTOS'}->{ $lhs }, + $coderet ]), } - # ERROR ## RETURN ## + # ERROR ## RETURN ## return $self->_parse_error('unexpected end of input') - unless defined $value; + unless defined $value; # munge text of last directive to make it readable return $self->_parse_error("unexpected end of directive", $text) - if $value eq ';'; # end of directive SEPARATOR + if $value eq ';'; # end of directive SEPARATOR return $self->_parse_error("unexpected token ($value)", $text); } @@ -10201,7 +10232,7 @@ sub _parse_error { $line = 'unknown' unless $line; $msg .= "\n [% $text %]" - if defined $text; + if defined $text; return $self->error("line $line: $msg"); } @@ -10215,10 +10246,10 @@ sub _dump { my $key; foreach $key (qw( START_TAG END_TAG TAG_STYLE ANYCASE INTERPOLATE - PRE_CHOMP POST_CHOMP V1DOLLAR )) { - my $val = $self->{ $key }; - $val = '' unless defined $val; - $output .= sprintf($format, $key, $val); + PRE_CHOMP POST_CHOMP V1DOLLAR )) { + my $val = $self->{ $key }; + $val = '' unless defined $val; + $output .= sprintf($format, $key, $val); } $output .= '}'; @@ -17845,6 +17876,20 @@ proto.hash_functions.values = function(hash) { return list; } +proto.hash_functions.pairs = function(hash) { + var list = new Array(); + var keys = new Array(); + for ( var key in hash ) { + keys.push( key ); + } + keys.sort(); + for ( var key in keys ) { + key = keys[key] + list.push( { 'key': key, 'value': hash[key] } ); + } + return list; +} + // delete proto.hash_functions.remove = function(hash, key) { return delete hash[key]; @@ -19703,7 +19748,7 @@ use warnings; sub main { return &kernel } sub kernel { <<'...'; -if(typeof Jemplate=="undefined"){var Jemplate=function(){this.init.apply(this,arguments)}}Jemplate.VERSION="0.22";Jemplate.process=function(){var A=new Jemplate(Jemplate.prototype.config);return A.process.apply(A,arguments)};(function(){if(!Jemplate.templateMap){Jemplate.templateMap={}}var proto=Jemplate.prototype={};proto.config={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,FILTERS:{},INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};proto.defaults={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};Jemplate.init=function(config){Jemplate.prototype.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof Jemplate.prototype.config[i]=="undefined"){Jemplate.prototype.config[i]=Jemplate.prototype.defaults[i]}}};proto.init=function(config){this.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof this.config[i]=="undefined"){this.config[i]=Jemplate.prototype.defaults[i]}}};proto.process=function(template,data,output){var context=this.config.CONTEXT||new Jemplate.Context();context.config=this.config;context.stash=new Jemplate.Stash(this.config.STASH,this.config);context.__filter__=new Jemplate.Filter();context.__filter__.config=this.config;context.__plugin__=new Jemplate.Plugin();context.__plugin__.config=this.config;var result;var proc=function(input){try{if(typeof context.config.PRE_PROCESS=="string"){context.config.PRE_PROCESS=[context.config.PRE_PROCESS]}for(var i=0;i/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=indexnew; +my @templates; +push @templates, $jemplate->compile_template_content( <<_END_, 't0' ); +Hello, World. +_END_ +push @templates, $jemplate->compile_template_content( <<_END_, 't1' ); +[% FOREACH pair = hash.pairs %] +[% pair.key %] = [% pair.value %] +[% END %] _END_ +push @templates, $jemplate->compile_template_content( <<_END_, 't2' ); +[% FOREACH key = hash.keys %] +[% key %] = [% hash.\$key %] +[% END %] +_END_ + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @templates, "1;" ); test_js <<'_END_'; -areEqual( 1, 1 ) +result = Jemplate.process( 't1', { hash: { c: 1, a: 2, b: 3 } } ); +like( result, /a = 2\s+b = 3\s+c = 1/ ) _END_ 1; diff --git a/t/quoted.t b/t/quoted.t index 9387c4f..ac89669 100644 --- a/t/quoted.t +++ b/t/quoted.t @@ -17,7 +17,7 @@ stash.set('foo', 'foo'); //line 1 "test_template" stash.set('bar', 'bar'); output += '\n'; -//line 0 "test_template" +//line 1 "test_template" output += stash.get('foo') + '/' + stash.get('bar'); output += '\n'; From c8b9ad76f7f61ac58332f5ee00a5a9d7f34d891b Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Wed, 21 Apr 2010 13:20:34 -0700 Subject: [PATCH 12/29] Added commandline example to SYNOPSIS --- lib/Jemplate.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index e385d67..d0c8e61 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -428,6 +428,10 @@ or, with jQuery.js: Jemplate.process('my-template.html', data, '#some-div'); }); +From the commandline: + + jemplate --runtime --compile path/to/jemplate/directory/ > jemplate.js + =head1 DESCRIPTION Jemplate is a templating framework for JavaScript that is built over From 55ad4998e6cf3977fb7480292856100340b56a73 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Wed, 21 Apr 2010 13:44:42 -0700 Subject: [PATCH 13/29] Prepare for 0.25 release --- Changes | 12 ++++++++++++ README | 6 +++++- bin/jemplate | 3 ++- lib/Jemplate.pm | 4 ++-- tests/index.html | 32 -------------------------------- 5 files changed, 21 insertions(+), 36 deletions(-) diff --git a/Changes b/Changes index 4cdcefc..bc8f4a1 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,15 @@ +--- +version: 0.25 +date: Wednesday April 21 13:44:19 PDT 2010 +changes: +- release + +--- +version: 0.24_4 +date: Saturday January 09 17:06:40 PST 2010 +changes: +- added .pairs (VMethod) functionality with tests + --- version: 0.24_4 date: Saturday January 09 17:06:40 PST 2010 diff --git a/README b/README index 0a21aca..1e40465 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ VERSION - Version 0.24_4 + Version 0.25 NAME Jemplate - JavaScript Templating with Template Toolkit @@ -25,6 +25,10 @@ SYNOPSIS Jemplate.process('my-template.html', data, '#some-div'); }); + From the commandline: + + jemplate --runtime --compile path/to/jemplate/directory/ > jemplate.js + DESCRIPTION Jemplate is a templating framework for JavaScript that is built over Perl's Template Toolkit (TT2). diff --git a/bin/jemplate b/bin/jemplate index 3dd6ed4..c837b2d 100755 --- a/bin/jemplate +++ b/bin/jemplate @@ -19837,7 +19837,8 @@ use warnings; use Template 2.14; use Getopt::Long; -our $VERSION = '0.24_2'; + +our $VERSION = '0.24_4'; use Jemplate::Parser; diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index d0c8e61..e6ea093 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -7,11 +7,11 @@ use Getopt::Long; =head1 VERSION -Version 0.24_4 +Version 0.25 =cut -our $VERSION = '0.24_4'; +our $VERSION = '0.25'; use Jemplate::Parser; diff --git a/tests/index.html b/tests/index.html index efa87f7..e69de29 100644 --- a/tests/index.html +++ b/tests/index.html @@ -1,32 +0,0 @@ - - - Jemplate Testing - - - - - - - From 4be24e2cf7590286f5a562792a39fad81b562048 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Wed, 21 Apr 2010 16:41:38 -0700 Subject: [PATCH 14/29] Added test case for rt37539 --- t/9bug/37539-intermediate-instantiation.t | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 t/9bug/37539-intermediate-instantiation.t diff --git a/t/9bug/37539-intermediate-instantiation.t b/t/9bug/37539-intermediate-instantiation.t new file mode 100644 index 0000000..7c338b4 --- /dev/null +++ b/t/9bug/37539-intermediate-instantiation.t @@ -0,0 +1,34 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; + +BEGIN { + plan skip_all => "JavaScript::V8x::TestMoreish not available" unless eval { require JavaScript::V8x::TestMoreish }; +} + +plan qw/no_plan/; + +use Jemplate; +use Jemplate::Runtime; + +use JavaScript::V8x::TestMoreish; + +my $jemplate = Jemplate->new; +my @js; + +push @js, $jemplate->compile_template_content( <<_END_, 't0' ); +[% BLOCK t1 %] +[% a.b.c = 1 %] +[% a.b.c %] +[% END %] +_END_ + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @js, "1;" ); +test_js <<'_END_'; +result = Jemplate.process( 't1', {} ) +areEqual( result, 1 ); +_END_ From 4e6425ebbf05a935411c584f40a6c5a92e96697e Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Wed, 21 Apr 2010 16:42:53 -0700 Subject: [PATCH 15/29] Fixed up Changes --- Changes | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index bc8f4a1..9885138 100644 --- a/Changes +++ b/Changes @@ -1,14 +1,12 @@ --- -version: 0.25 -date: Wednesday April 21 13:44:19 PDT 2010 changes: -- release +- Added test case for rt37539 --- -version: 0.24_4 -date: Saturday January 09 17:06:40 PST 2010 +version: 0.25 +date: Wednesday April 21 13:44:19 PDT 2010 changes: -- added .pairs (VMethod) functionality with tests +- release --- version: 0.24_4 From db2f91db7c20e00efeca8a1329c3d1783da2071a Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Wed, 21 Apr 2010 16:48:19 -0700 Subject: [PATCH 16/29] Added .gitignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fabf26c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +tests/tjs/var +Makefile +Makefile.old +Makefile.js +t/a.js +t/b.js +t/check.js +t? From b7592e924aee6f1f30963323833da3112a2c4677 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Wed, 21 Apr 2010 16:56:19 -0700 Subject: [PATCH 17/29] Added test case for rt37540 --- Changes | 1 + t/9bug/37540-iterate-over-empty-object.t | 35 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 t/9bug/37540-iterate-over-empty-object.t diff --git a/Changes b/Changes index 9885138..ec24331 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,7 @@ --- changes: - Added test case for rt37539 +- Added test case for rt37540 --- version: 0.25 diff --git a/t/9bug/37540-iterate-over-empty-object.t b/t/9bug/37540-iterate-over-empty-object.t new file mode 100644 index 0000000..e53d287 --- /dev/null +++ b/t/9bug/37540-iterate-over-empty-object.t @@ -0,0 +1,35 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; + +BEGIN { + plan skip_all => "JavaScript::V8x::TestMoreish not available" unless eval { require JavaScript::V8x::TestMoreish }; +} + +plan qw/no_plan/; + +use Jemplate; +use Jemplate::Runtime; + +use JavaScript::V8x::TestMoreish; + +my $jemplate = Jemplate->new; +my @js; + +push @js, $jemplate->compile_template_content( <<_END_, 't0' ); +[% BLOCK t1 %] +[% FOREACH a = empty %] +[% END %] +1 +[% END %] +_END_ + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @js, "1;" ); +test_js <<'_END_'; +result = Jemplate.process( 't1', { empty: [] } ) +areEqual( result, 1 ); +_END_ From dfdffb495bdc1069dcb8f11790add1a66d2b8e9c Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Wed, 21 Apr 2010 17:23:27 -0700 Subject: [PATCH 18/29] Fixed rt53967 with test case --- Changes | 1 + MANIFEST | 3 +++ lib/Jemplate.pm | 6 ++++-- t/9bug/53967-compiler-double-dot-path-fail.t | 12 ++++++++++++ t/assets/jt/a/.hidden/apple | 0 t/assets/jt/a/b/c/.hidden | 0 t/assets/jt/a/b/cherry | 3 +++ 7 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 t/9bug/53967-compiler-double-dot-path-fail.t create mode 100644 t/assets/jt/a/.hidden/apple create mode 100644 t/assets/jt/a/b/c/.hidden create mode 100644 t/assets/jt/a/b/cherry diff --git a/Changes b/Changes index ec24331..0b8dd3d 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ --- changes: +- Fixed rt53697 with test case - Added test case for rt37539 - Added test case for rt37540 diff --git a/MANIFEST b/MANIFEST index 6bf4059..b0acb52 100644 --- a/MANIFEST +++ b/MANIFEST @@ -54,6 +54,9 @@ META.yml README t/a.js t/assets/jt/hello +t/assets/jt/a/.hidden/apple +t/assets/jt/a/b/c/.hidden +t/assets/jt/a/b/cherry t/b.js t/block.t t/call.t diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index e6ea093..327cf66 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -225,8 +225,10 @@ sub recurse_dir { my $dir = shift; my @files; foreach ( File::Find::Rule->file->in( $dir ) ) { - # don't include .hidden files - unless ($_ =~ '\/\.') { push(@files, $_); } + if ( m{/\.[^\.]+} ) {} # Skip ".hidden" files or directories + else { + push @files, $_; + } } return @files; } diff --git a/t/9bug/53967-compiler-double-dot-path-fail.t b/t/9bug/53967-compiler-double-dot-path-fail.t new file mode 100644 index 0000000..ea3e509 --- /dev/null +++ b/t/9bug/53967-compiler-double-dot-path-fail.t @@ -0,0 +1,12 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; + +plan qw/no_plan/; + +use Jemplate; + +is( scalar Jemplate::recurse_dir( 't/../t/assets/jt/a' ), 1, 'Only find one file, the rest should be hidden' ); diff --git a/t/assets/jt/a/.hidden/apple b/t/assets/jt/a/.hidden/apple new file mode 100644 index 0000000..e69de29 diff --git a/t/assets/jt/a/b/c/.hidden b/t/assets/jt/a/b/c/.hidden new file mode 100644 index 0000000..e69de29 diff --git a/t/assets/jt/a/b/cherry b/t/assets/jt/a/b/cherry new file mode 100644 index 0000000..00b9489 --- /dev/null +++ b/t/assets/jt/a/b/cherry @@ -0,0 +1,3 @@ +[% BLOCK cherry %] +apple +[% END %] From 695fcda17a105c5bbc7063e4c682029a030afbfc Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Thu, 22 Apr 2010 16:30:14 -0700 Subject: [PATCH 19/29] Added test case for rt37895 Inconsistent jemplate version --- Changes | 7 ++++--- t/9bug/37895-inconsistent-jemplate-version.t | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 t/9bug/37895-inconsistent-jemplate-version.t diff --git a/Changes b/Changes index 0b8dd3d..4dc68dd 100644 --- a/Changes +++ b/Changes @@ -1,8 +1,9 @@ --- changes: -- Fixed rt53697 with test case -- Added test case for rt37539 -- Added test case for rt37540 +- Fixed rt53967 with test case - Compiler double-dot path fail +- Added test case for rt37539 - Intermediate instantiation +- Added test case for rt37540 - Iterate over empty object +- Added test case for rt37895 - Inconsistent jemplate version --- version: 0.25 diff --git a/t/9bug/37895-inconsistent-jemplate-version.t b/t/9bug/37895-inconsistent-jemplate-version.t new file mode 100644 index 0000000..598f7ac --- /dev/null +++ b/t/9bug/37895-inconsistent-jemplate-version.t @@ -0,0 +1,12 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; + +plan qw/no_plan/; + +system( $^X, 'jemplate' ); + +ok( ! $? ); From fe885b2040c811752b15e5a5e08145a162a91473 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Thu, 22 Apr 2010 16:35:34 -0700 Subject: [PATCH 20/29] Updated MANIFEST and MANIFEST.SKIP --- MANIFEST | 5 +++++ MANIFEST.SKIP | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/MANIFEST b/MANIFEST index b0acb52..c93f4dc 100644 --- a/MANIFEST +++ b/MANIFEST @@ -88,6 +88,11 @@ t/TestJemplate.pm t/throw.t t/while.t t/wrapper.t +t/pairs.t +t/9bug/37539-intermediate-instantiation.t +t/9bug/37540-iterate-over-empty-object.t +t/9bug/37895-inconsistent-jemplate-version.t +t/9bug/53967-compiler-double-dot-path-fail.t tests/Makefile tests/README tests/bin/daemon diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 4b836f9..f51b661 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -7,3 +7,7 @@ ^t/Jemplate.js$ ^src/ ^tests/tjs/var/ +^tmp.* +^tests/ +Makefile.old +t[0-9] From 4b01b945570920c1504ccb9159ec423a9ee2c3a3 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Thu, 22 Apr 2010 17:08:03 -0700 Subject: [PATCH 21/29] Fixed rt issue 53453 with test Make Jemplate slice like TT slice --- Changes | 1 + Makefile.PL | 2 + bin/jemplate | 17 +++++-- lib/Jemplate/Runtime.pm | 7 ++- lib/Jemplate/Runtime/Compact.pm | 2 +- src/js/kernel.compact.js | 2 +- src/js/kernel.js | 7 ++- .../53453-make-Jemplate-slice-like-TT-slice.t | 51 +++++++++++++++++++ tests/t/list.t.js | 2 +- 9 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 t/9bug/53453-make-Jemplate-slice-like-TT-slice.t diff --git a/Changes b/Changes index 4dc68dd..9c7acc6 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ --- changes: +- Fixed rt53453 with test case - Make Jemplate slice like TT slice - Fixed rt53967 with test case - Compiler double-dot path fail - Added test case for rt37539 - Intermediate instantiation - Added test case for rt37540 - Iterate over empty object diff --git a/Makefile.PL b/Makefile.PL index 423d3c4..70f27cd 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -28,6 +28,8 @@ requires 'File::Find::Rule' => '0.30'; use_test_base; +tests_recursive; + no_index directory => 'examples'; clean_files 't/Jemplate.js'; diff --git a/bin/jemplate b/bin/jemplate index c837b2d..fb2025d 100755 --- a/bin/jemplate +++ b/bin/jemplate @@ -17733,7 +17733,12 @@ proto.list_functions.merge = function(list /*, ... args */) { } proto.list_functions.slice = function(list, start, end) { - return list.slice(start, end); + // To make it like slice in TT + // See rt53453 + if ( end == -1 ) { + return list.slice( start ); + } + return list.slice( start, end + 1 ); } proto.list_functions.splice = function(list /*, ... args */ ) { @@ -19748,7 +19753,7 @@ use warnings; sub main { return &kernel } sub kernel { <<'...'; -if(typeof Jemplate=="undefined"){var Jemplate=function(){this.init.apply(this,arguments)}}Jemplate.VERSION="0.22";Jemplate.process=function(){var A=new Jemplate(Jemplate.prototype.config);return A.process.apply(A,arguments)};(function(){if(!Jemplate.templateMap){Jemplate.templateMap={}}var proto=Jemplate.prototype={};proto.config={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,FILTERS:{},INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};proto.defaults={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};Jemplate.init=function(config){Jemplate.prototype.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof Jemplate.prototype.config[i]=="undefined"){Jemplate.prototype.config[i]=Jemplate.prototype.defaults[i]}}};proto.init=function(config){this.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof this.config[i]=="undefined"){this.config[i]=Jemplate.prototype.defaults[i]}}};proto.process=function(template,data,output){var context=this.config.CONTEXT||new Jemplate.Context();context.config=this.config;context.stash=new Jemplate.Stash(this.config.STASH,this.config);context.__filter__=new Jemplate.Filter();context.__filter__.config=this.config;context.__plugin__=new Jemplate.Plugin();context.__plugin__.config=this.config;var result;var proc=function(input){try{if(typeof context.config.PRE_PROCESS=="string"){context.config.PRE_PROCESS=[context.config.PRE_PROCESS]}for(var i=0;i/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=indexfile->in( $dir ) ) { - # don't include .hidden files - unless ($_ =~ '\/\.') { push(@files, $_); } + if ( m{/\.[^\.]+} ) {} # Skip ".hidden" files or directories + else { + push @files, $_; + } } return @files; } diff --git a/lib/Jemplate/Runtime.pm b/lib/Jemplate/Runtime.pm index 6dc2248..b951f29 100644 --- a/lib/Jemplate/Runtime.pm +++ b/lib/Jemplate/Runtime.pm @@ -792,7 +792,12 @@ proto.list_functions.merge = function(list /*, ... args */) { } proto.list_functions.slice = function(list, start, end) { - return list.slice(start, end); + // To make it like slice in TT + // See rt53453 + if ( end == -1 ) { + return list.slice( start ); + } + return list.slice( start, end + 1 ); } proto.list_functions.splice = function(list /*, ... args */ ) { diff --git a/lib/Jemplate/Runtime/Compact.pm b/lib/Jemplate/Runtime/Compact.pm index 454a77a..e5fc58a 100644 --- a/lib/Jemplate/Runtime/Compact.pm +++ b/lib/Jemplate/Runtime/Compact.pm @@ -5,7 +5,7 @@ use warnings; sub main { return &kernel } sub kernel { <<'...'; -if(typeof Jemplate=="undefined"){var Jemplate=function(){this.init.apply(this,arguments)}}Jemplate.VERSION="0.22";Jemplate.process=function(){var A=new Jemplate(Jemplate.prototype.config);return A.process.apply(A,arguments)};(function(){if(!Jemplate.templateMap){Jemplate.templateMap={}}var proto=Jemplate.prototype={};proto.config={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,FILTERS:{},INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};proto.defaults={AUTO_RESET:true,BLOCKS:{},CONTEXT:null,DEBUG_UNDEF:false,DEFAULT:null,ERROR:null,EVAL_JAVASCRIPT:false,GLOBAL:true,SCOPE:this,INCLUDE_PATH:[""],INTERPOLATE:false,OUTPUT:null,PLUGINS:{},POST_PROCESS:[],PRE_PROCESS:[],PROCESS:null,RECURSION:false,STASH:null,TOLERANT:null,VARIABLES:{},WRAPPER:[]};Jemplate.init=function(config){Jemplate.prototype.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof Jemplate.prototype.config[i]=="undefined"){Jemplate.prototype.config[i]=Jemplate.prototype.defaults[i]}}};proto.init=function(config){this.config=config||{};for(var i in Jemplate.prototype.defaults){if(typeof this.config[i]=="undefined"){this.config[i]=Jemplate.prototype.defaults[i]}}};proto.process=function(template,data,output){var context=this.config.CONTEXT||new Jemplate.Context();context.config=this.config;context.stash=new Jemplate.Stash(this.config.STASH,this.config);context.__filter__=new Jemplate.Filter();context.__filter__.config=this.config;context.__plugin__=new Jemplate.Plugin();context.__plugin__.config=this.config;var result;var proc=function(input){try{if(typeof context.config.PRE_PROCESS=="string"){context.config.PRE_PROCESS=[context.config.PRE_PROCESS]}for(var i=0;i/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index/g,">");text=text.replace(/"/g,""");return text};proto.filters.html_para=function(text){var lines=text.split(/(?:\r?\n){2,}/);return"

\n"+lines.join("\n

\n\n

\n")+"

\n"};proto.filters.html_break=function(text){return text.replace(/(\r?\n){2,}/g,"$1
$1
$1")};proto.filters.html_line_break=function(text){return text.replace(/(\r?\n)/g,"$1
$1")};proto.filters.uri=function(text){return encodeURIComponent(text)};proto.filters.url=function(text){return encodeURI(text)};proto.filters.indent=function(text,args){var pad=args[0];if(!text){return null}if(typeof pad=="undefined"){pad=4}var finalpad="";if(typeof pad=="number"||String(pad).match(/^\d$/)){for(var i=0;i=0;i=i-size){list.unshift(string.substr(i,size))}if(string.length%size){list.unshift(string.substr(0,string.length%size))}}else{for(i=0;i=0)?1:0};proto.string_functions.size=function(string){return 1};proto.string_functions.split=function(string,re){var regexp=new RegExp(re);var list=string.split(regexp);return list};proto.list_functions={};proto.list_functions["typeof"]=function(list){return"array"};proto.list_functions.list=function(list){return list};proto.list_functions.join=function(list,str){return list.join(str)};proto.list_functions.sort=function(list,key){if(typeof (key)!="undefined"&&key!=""){return list.sort(function(a,b){if(a[key]==b[key]){return 0}else{if(a[key]>b[key]){return 1}else{return -1}}})}return list.sort()};proto.list_functions.nsort=function(list){return list.sort(function(a,b){return(a-b)})};proto.list_functions.grep=function(list,re){var regexp=new RegExp(re);var result=[];for(var i=0;i=0;i--){result.push(list[i])}return result};proto.list_functions.merge=function(list){var result=[];var push_all=function(elem){if(elem instanceof Array){for(var j=0;j0?this.object_keys[index-1]:"";this.next=index0?object[index-1]:"";this.next=index "JavaScript::V8x::TestMoreish not available" unless eval { require JavaScript::V8x::TestMoreish }; +} + +plan qw/no_plan/; + +use Jemplate; +use Jemplate::Runtime; + +use JavaScript::V8x::TestMoreish; + +my $jemplate = Jemplate->new; +my @js; + +push @js, $jemplate->compile_template_content( <<_END_, 't0' ); +[% BLOCK t1 %][% apple.slice( 0, 2 ).join(' - ') %][% END %] +[% BLOCK t2 %][% apple.slice( -3, -1 ).join(' - ') %][% END %] +[% BLOCK t3 %][% apple.slice( -3, -2 ).join(' - ') %][% END %] +[% BLOCK t4 %][% apple.slice( 3, -2 ).join(' - ') %][% END %] +[% BLOCK t5 %][% apple.slice( 3, -1 ).join(' - ') %][% END %] +_END_ + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @js, "1;" ); +test_js <<'_END_'; +var result +var apple = [ 1, 2, 3, 4, 5, 6, 7 ] + +result = Jemplate.process( 't1', { apple: apple } ) +areEqual( result, "1 - 2 - 3" ); + +result = Jemplate.process( 't2', { apple: apple } ) +areEqual( result, "5 - 6 - 7" ); + +result = Jemplate.process( 't3', { apple: apple } ) +areEqual( result, "5 - 6" ); + +result = Jemplate.process( 't4', { apple: apple } ) +areEqual( result, "4 - 5 - 6" ); + +result = Jemplate.process( 't5', { apple: apple } ) +areEqual( result, "4 - 5 - 6 - 7" ); +_END_ + diff --git a/tests/t/list.t.js b/tests/t/list.t.js index dfbaee4..7084c7b 100644 --- a/tests/t/list.t.js +++ b/tests/t/list.t.js @@ -19,7 +19,7 @@ list.html [% a1.max() %]+[% a1.size() %] [% SET a2 = a1.reverse -%] [% a2.join('^') %] -[% a2.slice(1, 3).join('*') %] +[% a2.slice(1, 2).join('*') %] [% SET a3 = [ 5, 9, 'x', 17, 9, 33, 12, 'x', 5] -%] [% a3.unique().join(',') %] [% a1.unshift('zero').sort().join('!') %] From 33307a3d7f54d0783a2158d8b12a69a1a8d076b7 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Thu, 22 Apr 2010 17:54:26 -0700 Subject: [PATCH 22/29] Fixed rt53454 with test case Support rudimentary array range operator --- Changes | 1 + MANIFEST | 2 ++ lib/Jemplate/Directive.pm | 14 ++++++++ t/9bug/53454-array-range-operator.t | 52 +++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 t/9bug/53454-array-range-operator.t diff --git a/Changes b/Changes index 9c7acc6..59b2796 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,6 @@ --- changes: +- Fixed rt53454 with test case - Support rudimentary array range operator - Fixed rt53453 with test case - Make Jemplate slice like TT slice - Fixed rt53967 with test case - Compiler double-dot path fail - Added test case for rt37539 - Intermediate instantiation diff --git a/MANIFEST b/MANIFEST index c93f4dc..e578529 100644 --- a/MANIFEST +++ b/MANIFEST @@ -93,6 +93,8 @@ t/9bug/37539-intermediate-instantiation.t t/9bug/37540-iterate-over-empty-object.t t/9bug/37895-inconsistent-jemplate-version.t t/9bug/53967-compiler-double-dot-path-fail.t +t/9bug/53453-make-Jemplate-slice-like-TT-slice.t +t/9bug/53454-array-range-operator.t tests/Makefile tests/README tests/bin/daemon diff --git a/lib/Jemplate/Directive.pm b/lib/Jemplate/Directive.pm index d0fe4c4..7559c57 100644 --- a/lib/Jemplate/Directive.pm +++ b/lib/Jemplate/Directive.pm @@ -33,6 +33,17 @@ $block ... } +# Try to do 1 .. 10 expansions +sub _attempt_range_expand_val ($) { + my $val = shift; + return $val unless + my ( $from, $to ) = $val =~ m/\s*\[\s*(\S+)\s*\.\.\s*(\S+)\s*\]/; + + die "Range expansion is current supported for positive/negative integer values only (e.g. [ 1 .. 10 ])\nCannot expand: $val" unless $from =~ m/^-?\d+$/ && $to =~ m/^-?\d+$/; + + return join '', '[', join( ',', $from .. $to ), ']'; +} + #------------------------------------------------------------------------ # textblock($text) #------------------------------------------------------------------------ @@ -101,6 +112,7 @@ sub assign { $var = '[' . join(', ', @$var) . ']'; } } + $val = _attempt_range_expand_val $val; $val .= ', 1' if $default; return "stash.set($var, $val)"; } @@ -277,6 +289,8 @@ sub foreach { $loop_restore = 'stash = context.delocalise()'; } + $list = _attempt_range_expand_val $list; + return < "JavaScript::V8x::TestMoreish not available" unless eval { require JavaScript::V8x::TestMoreish }; +} + +plan qw/no_plan/; + +use Jemplate; +use Jemplate::Runtime; + +use JavaScript::V8x::TestMoreish; + +my $jemplate = Jemplate->new; +my @js; + +push @js, $jemplate->compile_template_content( <<_END_, 't0' ); +[% BLOCK t1 %][% list = [ 1 .. 10 ] %][% list.join(', ') %][% END %] +[% BLOCK t2 %][% FOREACH ii = [ 1 .. 4 ] %][% ii %] [% END %][% END %] +[% BLOCK t3 %][% FOREACH ii = [ -1 .. -4 ] %][% ii %] [% END %][% END %] +[% BLOCK t4 %][% FOREACH ii = [ -4 .. -1 ] %][% ii %] [% END %][% END %] +_END_ + +eval { +$jemplate->compile_template_content( <<_END_, 't0' ); +[% BLOCK t1 %][% broken = [ 1 .. end ] %][% list.join(', ') %][% END %] +_END_ +}; +like $@, qr{Range expansion is current supported for positive/negative integer values only}; + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @js, "1;" ); +test_js <<'_END_'; +var result + +result = Jemplate.process( 't1', { } ) +areEqual( result, "1, 2, 3, 4, 5, 6, 7, 8, 9, 10" ); + +result = Jemplate.process( 't2', { } ) +areEqual( result, "1 2 3 4 " ); + +result = Jemplate.process( 't3', { } ) +areEqual( result, "" ); + +result = Jemplate.process( 't4', { } ) +areEqual( result, "-4 -3 -2 -1 " ); +_END_ From 25a99809442e2846c37881f2e362059c8b9444e5 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Thu, 22 Apr 2010 17:59:55 -0700 Subject: [PATCH 23/29] Development release: 0.25_1 --- Changes | 2 ++ README | 2 +- lib/Jemplate.pm | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 59b2796..1f774a3 100644 --- a/Changes +++ b/Changes @@ -1,4 +1,6 @@ --- +version: 0.25_1 +date: Thursday April 22 17:58:53 PDT 2010 changes: - Fixed rt53454 with test case - Support rudimentary array range operator - Fixed rt53453 with test case - Make Jemplate slice like TT slice diff --git a/README b/README index 1e40465..c21fe0d 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ VERSION - Version 0.25 + Version 0.25_1 NAME Jemplate - JavaScript Templating with Template Toolkit diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index 327cf66..7b61002 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -7,11 +7,11 @@ use Getopt::Long; =head1 VERSION -Version 0.25 +Version 0.25_1 =cut -our $VERSION = '0.25'; +our $VERSION = '0.25_1'; use Jemplate::Parser; From 7b4b66c3fe7c2e10e14ab3684de184f7de30d8f7 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Thu, 22 Apr 2010 18:29:16 -0700 Subject: [PATCH 24/29] Implemented rt4308 with tests Capture implementation (Tom Molesworth) --- Changes | 4 ++++ lib/Jemplate/Directive.pm | 22 ++++++++++++++++- t/9bug/43809-capture-implementation.t | 34 +++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 t/9bug/43809-capture-implementation.t diff --git a/Changes b/Changes index 1f774a3..2d607ae 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,7 @@ +--- +changes: +- Implemented rt4308 - Capture implementation (Tom Molesworth) + --- version: 0.25_1 date: Thursday April 22 17:58:53 PDT 2010 diff --git a/lib/Jemplate/Directive.pm b/lib/Jemplate/Directive.pm index 7559c57..c576b65 100644 --- a/lib/Jemplate/Directive.pm +++ b/lib/Jemplate/Directive.pm @@ -666,7 +666,27 @@ EOF } sub capture { - return "throw('CAPTURE not yet supported in Jemplate');"; + my ($class, $name, $block) = @_; + + if (ref $name) { + if (scalar @$name == 2 && ! $name->[1]) { + $name = $name->[0]; + } + else { + $name = '[' . join(', ', @$name) . ']'; + } + } + + return < "JavaScript::V8x::TestMoreish not available" unless eval { require JavaScript::V8x::TestMoreish }; +} + +plan qw/no_plan/; + +use Jemplate; +use Jemplate::Runtime; + +use JavaScript::V8x::TestMoreish; + +my $jemplate = Jemplate->new; +my @js; + +push @js, $jemplate->compile_template_content( <<_END_, 't0' ); +[% BLOCK t1 %][% result = BLOCK %]Hello, World![% END %][% result %][% END %] +_END_ + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @js, "1;" ); +test_js <<'_END_'; +var result + +result = Jemplate.process( 't1', { } ) +areEqual( result, "Hello, World!" ) +_END_ + From b82da2b8112157b0a5ed83613483421e734b422c Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Fri, 23 Apr 2010 02:49:06 -0700 Subject: [PATCH 25/29] Reminder to add note about commit 3c4d9 referring to wrong rt, should refer to 23883 --- ToDo | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ToDo b/ToDo index f9410be..f014738 100644 --- a/ToDo +++ b/ToDo @@ -1,3 +1,5 @@ +Add note about commit 3c4d9 referring to wrong rt, should refer to 23883 + TODO: Does use_test_base not work? make test compains that Test::Base is not installed -rokr FUTURE: From 53cc49e9dc61e511c4c4268d1df5b659508da78a Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Sat, 24 Apr 2010 15:15:21 -0700 Subject: [PATCH 26/29] Added test for rt37570 List method for lists (fixed in 5eaf340da...) --- Changes | 1 + MANIFEST | 2 ++ MANIFEST.SKIP | 1 + lib/Jemplate.pm | 4 ++-- t/9bug/37570-list-for-lists.t | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 t/9bug/37570-list-for-lists.t diff --git a/Changes b/Changes index 2d607ae..7e9196c 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,7 @@ --- changes: - Implemented rt4308 - Capture implementation (Tom Molesworth) +- Added test for rt37570 - List method for lists (fixed in 5eaf340da...) --- version: 0.25_1 diff --git a/MANIFEST b/MANIFEST index e578529..b122833 100644 --- a/MANIFEST +++ b/MANIFEST @@ -95,6 +95,8 @@ t/9bug/37895-inconsistent-jemplate-version.t t/9bug/53967-compiler-double-dot-path-fail.t t/9bug/53453-make-Jemplate-slice-like-TT-slice.t t/9bug/53454-array-range-operator.t +t/9bug/37570-list-for-lists.t +t/9bug/43809-capture-implementation.t tests/Makefile tests/README tests/bin/daemon diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index f51b661..1f0d415 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -11,3 +11,4 @@ ^tests/ Makefile.old t[0-9] +Template-TT3 diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index 7b61002..f4e3770 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -7,11 +7,11 @@ use Getopt::Long; =head1 VERSION -Version 0.25_1 +Version 0.25_2 =cut -our $VERSION = '0.25_1'; +our $VERSION = '0.25_2'; use Jemplate::Parser; diff --git a/t/9bug/37570-list-for-lists.t b/t/9bug/37570-list-for-lists.t new file mode 100644 index 0000000..bc1fdd3 --- /dev/null +++ b/t/9bug/37570-list-for-lists.t @@ -0,0 +1,32 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; + +BEGIN { + plan skip_all => "JavaScript::V8x::TestMoreish not available" unless eval { require JavaScript::V8x::TestMoreish }; +} + +plan qw/no_plan/; + +use Jemplate; +use Jemplate::Runtime; + +use JavaScript::V8x::TestMoreish; + +my $jemplate = Jemplate->new; +my @js; + +push @js, $jemplate->compile_template_content( <<_END_, 't0' ); +[% BLOCK t1 %][% apple %] [% apple.list %][% END %] +_END_ + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @js, "1;" ); +test_js <<'_END_'; +apple = [ 1, 2, 3, 4, 5 ] +result = Jemplate.process( 't1', { apple: apple } ) +areEqual( result, "1,2,3,4,5 1,2,3,4,5" ); +_END_ From dbfaf6154c17e48762a6525d44b6ddd66a4e3fea Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Sat, 24 Apr 2010 18:56:16 -0700 Subject: [PATCH 27/29] Version 0.25_2 release --- Changes | 2 ++ README | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 7e9196c..3db0e70 100644 --- a/Changes +++ b/Changes @@ -1,4 +1,6 @@ --- +version: 0.25_2 +date: Saturday April 24 18:48:53 PDT 2010 changes: - Implemented rt4308 - Capture implementation (Tom Molesworth) - Added test for rt37570 - List method for lists (fixed in 5eaf340da...) diff --git a/README b/README index c21fe0d..d7ac1cc 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ VERSION - Version 0.25_1 + Version 0.25_2 NAME Jemplate - JavaScript Templating with Template Toolkit From f8177069ab87f0bfe747a571e9c718b0e753a1a3 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Mon, 26 Apr 2010 12:05:21 -0700 Subject: [PATCH 28/29] Release 0.260 --- Changes | 4 ++++ README | 2 +- lib/Jemplate.pm | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 3db0e70..c893bf3 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,7 @@ +--- +version: 0.260 +date: Monday April 26 12:03:40 PDT 2010 + --- version: 0.25_2 date: Saturday April 24 18:48:53 PDT 2010 diff --git a/README b/README index d7ac1cc..933d647 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ VERSION - Version 0.25_2 + Version 0.260 NAME Jemplate - JavaScript Templating with Template Toolkit diff --git a/lib/Jemplate.pm b/lib/Jemplate.pm index f4e3770..a605289 100644 --- a/lib/Jemplate.pm +++ b/lib/Jemplate.pm @@ -7,11 +7,11 @@ use Getopt::Long; =head1 VERSION -Version 0.25_2 +Version 0.260 =cut -our $VERSION = '0.25_2'; +our $VERSION = '0.260'; use Jemplate::Parser; From 5390bec8596c4e880ed7b1a9e8a1244f6dc81c80 Mon Sep 17 00:00:00 2001 From: robertkrimen Date: Tue, 27 Apr 2010 09:38:13 -0700 Subject: [PATCH 29/29] Fixed rt56956 with test case DIV operator broken (Agent Zhang) --- Changes | 6 +++++ MANIFEST | 1 + README | 2 +- bin/jemplate | 39 +++++++++++++++++++++++++++--- lib/Jemplate.pm | 4 +-- lib/Jemplate/Grammar.pm | 2 +- src/parser/Parser.yp | 2 +- t/9bug/56965-DIV-operator-broken.t | 36 +++++++++++++++++++++++++++ 8 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 t/9bug/56965-DIV-operator-broken.t diff --git a/Changes b/Changes index c893bf3..6de2d40 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,9 @@ +--- +version: 0.261 +date: Tuesday April 27 09:35:06 PDT 2010 +changes: +- Fixed rt56956 with test case - DIV operator broken (Agent Zhang) + --- version: 0.260 date: Monday April 26 12:03:40 PDT 2010 diff --git a/MANIFEST b/MANIFEST index b122833..3ae43b0 100644 --- a/MANIFEST +++ b/MANIFEST @@ -97,6 +97,7 @@ t/9bug/53453-make-Jemplate-slice-like-TT-slice.t t/9bug/53454-array-range-operator.t t/9bug/37570-list-for-lists.t t/9bug/43809-capture-implementation.t +t/9bug/56965-DIV-operator-broken.t tests/Makefile tests/README tests/bin/daemon diff --git a/README b/README index 933d647..aaa9f3b 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ VERSION - Version 0.260 + Version 0.261 NAME Jemplate - JavaScript Templating with Template Toolkit diff --git a/bin/jemplate b/bin/jemplate index fb2025d..56b0bdc 100755 --- a/bin/jemplate +++ b/bin/jemplate @@ -10300,6 +10300,16 @@ $block ... } +sub _attempt_range_expand_val ($) { + my $val = shift; + return $val unless + my ( $from, $to ) = $val =~ m/\s*\[\s*(\S+)\s*\.\.\s*(\S+)\s*\]/; + + die "Range expansion is current supported for positive/negative integer values only (e.g. [ 1 .. 10 ])\nCannot expand: $val" unless $from =~ m/^-?\d+$/ && $to =~ m/^-?\d+$/; + + return join '', '[', join( ',', $from .. $to ), ']'; +} + sub textblock { my ($class, $text) = @_; @@ -10356,6 +10366,7 @@ sub assign { $var = '[' . join(', ', @$var) . ']'; } } + $val = _attempt_range_expand_val $val; $val .= ', 1' if $default; return "stash.set($var, $val)"; } @@ -10494,6 +10505,8 @@ sub foreach { $loop_restore = 'stash = context.delocalise()'; } + $list = _attempt_range_expand_val $list; + return <[1]) { + $name = $name->[0]; + } + else { + $name = '[' . join(', ', @$name) . ']'; + } + } + + return < "JavaScript::V8x::TestMoreish not available" unless eval { require JavaScript::V8x::TestMoreish }; +} + +plan qw/no_plan/; + +use Jemplate; +use Jemplate::Runtime; + +use JavaScript::V8x::TestMoreish; + +my $jemplate = Jemplate->new; +my @js; + +push @js, $jemplate->compile_template_content( <<_END_, 't0' ); +[% BLOCK t1 %][% 3 DIV 2 %][% END %] +[% BLOCK t2 %][% 4 DIV 2 %][% END %] +[% BLOCK t3 %][% 5 DIV 2 %][% END %] +_END_ + +test_js_eval( Jemplate::Runtime->kernel ); +test_js_eval( join "\n", @js, "1;" ); +test_js <<'_END_'; + +areEqual( Jemplate.process( 't1' ), 1 ); +areEqual( Jemplate.process( 't2' ), 2 ); +areEqual( Jemplate.process( 't3' ), 2 ); + +_END_