From 501d4d2c7fd6b623e61851a14d8cfa556019350c Mon Sep 17 00:00:00 2001 From: thorn0 Date: Fri, 16 Oct 2015 23:18:58 +0300 Subject: [PATCH] Built sffjs from commit 6bd9eff on branch master --- package.json | 10 +-- sffjs.js | 164 +++++++++++++++++++++++--------------------------- sffjs.min.js | 2 +- test/tests.js | 63 ++++++++++++------- 4 files changed, 121 insertions(+), 118 deletions(-) diff --git a/package.json b/package.json index b059679..4ee30b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sffjs", - "version": "1.10.0-alpha.8", + "version": "1.10.0", "description": "String.Format for JavaScript", "main": "sffjs.js", "repository": { @@ -16,17 +16,17 @@ "Daniel Mester Pirttijarvi (http://www.masterdata.se)", "Georgii Dolzhykov " ], - "license": "zlib", + "license": "Zlib", "bugs": { "url": "https://github.com/thorn0/sffjs/issues" }, "homepage": "https://github.com/thorn0/sffjs", "devDependencies": { "grunt": "^0.4.5", - "grunt-build-control": "^0.1.3", + "grunt-build-control": "^0.6.1", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-copy": "^0.7.0", - "grunt-contrib-uglify": "^0.6.0", - "load-grunt-tasks": "^1.0.0" + "grunt-contrib-uglify": "^0.9.2", + "load-grunt-tasks": "^3.3.0" } } diff --git a/sffjs.js b/sffjs.js index c0f7ef2..f9124c0 100644 --- a/sffjs.js +++ b/sffjs.js @@ -1,5 +1,5 @@ /** - * String.format for JavaScript, version 1.10.0-alpha.8 + * String.format for JavaScript, version 1.10.0 * * Copyright (c) 2009-2014 Daniel Mester Pirttijärvi * http://mstr.se/sffjs @@ -46,8 +46,8 @@ // ***** Private Variables ***** - // This is the default values of a culture. Any missing format will default to the format in CULTURE_TEMPLATE. - // The invariant culture is generated from these default values. + // This is the default values of a culture. Any missing format will default to the format in CULTURE_TEMPLATE. + // The invariant culture is generated from these default values. var CULTURE_TEMPLATE = { name: "", // Empty on invariant culture d: "MM/dd/yyyy", @@ -63,7 +63,7 @@ _t: ",", // Thounsands separator _c: "¤#,0.00", // Currency format string _ct: ",", // Currency thounsands separator - _cr: ".", // Currency radix point + _cr: ".", // Currency radix point _am: "AM", _pm: "PM" }, @@ -81,7 +81,6 @@ // Holds all registered external cultures, i.e. not the invariant culture cultures = {}; - // ***** Private Methods ***** // General helpers @@ -140,7 +139,6 @@ sffjs.LC = currentCulture = result || INVARIANT_CULTURE; } - // Maths function numberToString(number, decimals) { @@ -164,7 +162,6 @@ return point < 0 ? 0 : numberString.length - point - 1; } - // Formatting helpers function resolvePath(path, value) { @@ -177,8 +174,8 @@ // Parse and evaluate path if (hasValue(value)) { - var followingMembers = /(\.([a-zA-Z_$]\w+)|\[(\d+)\])/g, - match = /^[a-zA-Z_$]\w+/.exec(path); + var followingMembers = /(\.([a-zA-Z_$]\w*)|\[(\d+)\])/g, + match = /^[a-zA-Z_$]\w*/.exec(path); value = value[match[0]]; @@ -215,14 +212,6 @@ } } - function unescapeBraces(braces, consumedBraces) { - /// Replaces escaped brackets ({ and }) with their unescaped representation. - /// A string containing braces of a single type only. - /// The number of braces that should be ignored when unescaping. - /// A string of the unescaped braces. - return braces.substr(0, (braces.length + 1 - (consumedBraces || 0)) / 2); - } - function processFormatItem(pathOrIndex, align, formatString, args) { /// Process a single format item in a composite format string /// The raw argument index or path component of the format item. @@ -361,7 +350,7 @@ // This is the way .NET handles things. if (i < 0) break; - // Check for single escaped character + // Check for single escaped character } else if (c === "\\") { i++; @@ -425,12 +414,12 @@ if (endIndex < 0) break; f = endIndex; - // Single escaped character + // Single escaped character } else if (c === "\\") { out.push(format.charAt(f + 1)); f++; - // Digit placeholder + // Digit placeholder } else if (c === "#" || c === "0") { if (i < integralDigits) { // In the integral part @@ -454,13 +443,13 @@ i++; - // Radix point character according to current culture. + // Radix point character according to current culture. } else if (c === ".") { if (number.length > ++i || forcedDecimals > 0) { out.push(radixPoint); } - // Other characters are written as they are, except from commas + // Other characters are written as they are, except from commas } else if (c !== ",") { out.push(c); } @@ -497,8 +486,7 @@ // http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx var standardFormatStringMatch = format.match(/^([a-zA-Z])(\d*)$/); - if (standardFormatStringMatch) - { + if (standardFormatStringMatch) { var standardFormatStringMatch_UpperCase = standardFormatStringMatch[1].toUpperCase(), precision = parseInt(standardFormatStringMatch[2], 10); // parseInt used to ensure empty string is aprsed to NaN @@ -543,7 +531,8 @@ // Note that we might have fell through from G above! // Determine coefficient and exponent for normalized notation - var exponent = 0, coefficient = Math.abs(number); + var exponent = 0, + coefficient = Math.abs(number); while (coefficient >= 10) { coefficient /= 10; @@ -667,13 +656,13 @@ // ***** Date Formatting ***** function mainDateFormatter(date, format) { - var year = date.getFullYear(), - month = date.getMonth(), - dayOfMonth = date.getDate(), - dayOfWeek = date.getDay(), - hour = date.getHours(), - minute = date.getMinutes(), - second = date.getSeconds(); + var year = date.getFullYear(), + month = date.getMonth(), + dayOfMonth = date.getDate(), + dayOfWeek = date.getDay(), + hour = date.getHours(), + minute = date.getMinutes(), + second = date.getSeconds(); // If no format is specified, default to G format format = format || "G"; @@ -686,48 +675,48 @@ // If the pattern contains 'd' or 'dd', genitive form is used for MMMM var monthNames = currentCulture._Mg && /(^|[^d])d(?!dd)/.test(format) ? currentCulture._Mg : currentCulture._M; - return format.replace(/(\\.|'[^']*'|"[^"]*"|d{1,4}|M{1,4}|yyyy|yy|HH?|hh?|mm?|ss?|tt?)/g, - function (match) { + return format.replace(/(\\.|'[^']*'|"[^"]*"|d{1,4}|M{1,4}|yyyy|yy|HH?|hh?|mm?|ss?|tt?)/g, + function(match) { - // Day + // Day return match === "dddd" ? currentCulture._D[dayOfWeek] : - // Use three first characters from long day name if abbreviations are not specifed - match === "ddd" ? (currentCulture._d ? currentCulture._d[dayOfWeek] : currentCulture._D[dayOfWeek].substr(0, 3)) : - match === "dd" ? numberPair(dayOfMonth) : - match === "d" ? dayOfMonth : - - // Month - match === "MMMM" ? monthNames[month] : - // Use three first characters from long month name if abbreviations are not specifed - match === "MMM" ? (currentCulture._m ? currentCulture._m[month] : currentCulture._M[month].substr(0, 3)) : - match === "MM" ? numberPair(month + 1) : - match === "M" ? month + 1 : - - // Year - match === "yyyy" ? year : - match === "yy" ? ("" + year).substr(2) : - - // Hour - match === "HH" ? numberPair(hour) : - match === "H" ? hour : - match === "hh" ? numberPair((hour - 1) % 12 + 1) : - match === "h" ? (hour - 1) % 12 + 1 : - - // Minute - match === "mm" ? numberPair(minute) : - match === "m" ? minute : - - // Second - match === "ss" ? numberPair(second) : - match === "s" ? second : - - // AM/PM - match === "tt" ? (hour < 12 ? currentCulture._am : currentCulture._pm) : - match === "t" ? (hour < 12 ? currentCulture._am : currentCulture._pm).charAt(0) : - - // String literal => strip quotation marks - match.substr(1, match.length - 1 - (match.charAt(0) !== "\\")); - } + // Use three first characters from long day name if abbreviations are not specifed + match === "ddd" ? (currentCulture._d ? currentCulture._d[dayOfWeek] : currentCulture._D[dayOfWeek].substr(0, 3)) : + match === "dd" ? numberPair(dayOfMonth) : + match === "d" ? dayOfMonth : + + // Month + match === "MMMM" ? monthNames[month] : + // Use three first characters from long month name if abbreviations are not specifed + match === "MMM" ? (currentCulture._m ? currentCulture._m[month] : currentCulture._M[month].substr(0, 3)) : + match === "MM" ? numberPair(month + 1) : + match === "M" ? month + 1 : + + // Year + match === "yyyy" ? year : + match === "yy" ? ("" + year).substr(2) : + + // Hour + match === "HH" ? numberPair(hour) : + match === "H" ? hour : + match === "hh" ? numberPair((hour - 1) % 12 + 1) : + match === "h" ? (hour - 1) % 12 + 1 : + + // Minute + match === "mm" ? numberPair(minute) : + match === "m" ? minute : + + // Second + match === "ss" ? numberPair(second) : + match === "s" ? second : + + // AM/PM + match === "tt" ? (hour < 12 ? currentCulture._am : currentCulture._pm) : + match === "t" ? (hour < 12 ? currentCulture._am : currentCulture._pm).charAt(0) : + + // String literal => strip quotation marks + match.substr(1, match.length - 1 - (match.charAt(0) !== "\\")); + } ); } @@ -744,25 +733,22 @@ var outerArgs = arguments; - return str.replace(/(\{+)((\d+|[a-zA-Z_$]\w+(?:\.[a-zA-Z_$]\w+|\[\d+\])*)(?:\,(-?\d*))?(?:\:([^\}]*))?)(\}+)|(\{+)|(\}+)/g, function () { + return str.replace(/\{((\d+|[a-zA-Z_$]\w*(?:\.[a-zA-Z_$]\w*|\[\d+\])*)(?:\,(-?\d*))?(?:\:([^\}]*(?:(?:\}\})+[^\}]+)*))?)\}|(\{\{)|(\}\})/g, function() { var innerArgs = arguments; // Handle escaped { - return innerArgs[7] ? unescapeBraces(innerArgs[7]) : - - // Handle escaped } - innerArgs[8] ? unescapeBraces(innerArgs[8]) : - - // Handle case when both { and } are present, but one or both of them are escaped - !(innerArgs[1].length % 2 && innerArgs[6].length % 2) ? - unescapeBraces(innerArgs[1]) + - innerArgs[2] + - unescapeBraces(innerArgs[6]) : - - // Valid format item - unescapeBraces(innerArgs[1], 1) + - processFormatItem(innerArgs[3], innerArgs[4], innerArgs[5], outerArgs) + - unescapeBraces(innerArgs[6], 1); + return innerArgs[5] ? "{" : + + // Handle escaped } + innerArgs[6] ? "}" : + + // Valid format item + processFormatItem( + innerArgs[2], + innerArgs[3], + // Format string might contain escaped braces + innerArgs[4] && innerArgs[4].replace(/\}\}/g, "}").replace(/\{\{/g, "{"), + outerArgs); }); } @@ -788,7 +774,7 @@ }; /// The version of the library String.Format for JavaScript. - sffjs.version = "1.10.0-alpha.8"; + sffjs.version = "1.10.0"; sffjs.setCulture = function(languageCode) { /// diff --git a/sffjs.min.js b/sffjs.min.js index 1bc1a08..5a345d9 100644 --- a/sffjs.min.js +++ b/sffjs.min.js @@ -1 +1 @@ -(function(a){"undefined"!=typeof module&&module.exports?module.exports=a():"function"==typeof define&&define.amd?define(a):(sffjs=a(),sffjs.unsafe())}).call(this,function(){function a(a){return 10>a?"0"+a:a}function b(a){return null!=a}function c(a,b){return isNaN(a)?b:a}function d(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function e(a){for(var b in t)null==a[b]&&(a[b]=t[b]);return a.f=a.f||a.D+" "+a.t,a.F=a.F||a.D+" "+a.T,a.g=a.g||a.d+" "+a.t,a.G=a.G||a.d+" "+a.T,a.m=a.M,a.y=a.Y,a}function f(){var a;if(v){var b=v.toUpperCase();a=w[b]||w[b.split("-")[0]]}r.LC=s=a||u}function g(a,b){var c=Math.pow(10,b||0);return""+Math.round(Math.abs(a)*c)/c}function h(a){var b=a.indexOf(".");return 0>b?a.length:b}function i(a){var b=a.indexOf(".");return 0>b?0:a.length-b-1}function j(a,c){if(b(c)){var d=/(\.([a-zA-Z_$]\w+)|\[(\d+)\])/g,e=/^[a-zA-Z_$]\w+/.exec(a);for(c=c[e[0]];b(c)&&(e=d.exec(a));)c=c[e[2]||Number(e[3])]}return c}function k(a,b){for(var c=0,d=b.length;d>c;c++)a.push(b.charAt(c)),a.g>1&&a.g--%3===1&&a.push(a.t)}function l(a,b){return a.substr(0,(a.length+1-(b||0))/2)}function m(a,c,e,f){var g,h,i=parseInt(a,10),k="";if(isNaN(i))g=j(a,f[1]);else{if(i>f.length-2)throw"Missing argument";g=f[i+1]}for(g=b(g)?g.__Format?g.__Format(e):"number"==typeof g?p(g,e):d(g)?q(g,e):""+g:"",c=Number(c)||0,h=Math.abs(c)-g.length;h-->0;)k+=" ";return 0>c?g+k:k+g}function n(a,b,c,d,e,f){var j,l,m=[];for(m.t=f,0>a&&m.push("-"),a=g(a,d),j=m.g=h(a),l=i(a),b-=j;b-->0;)k(m,"0");if(k(m,a.substr(0,j)),c||l)for(m.push(e),k(m,a.substr(j+1)),c-=l;c-->0;)k(m,"0");return m.join("")}function o(a,b,c,d){var e,f,i,j,l=0,m=-1,n=-1,o=0,p=-1,q=0,r=1,s=b.length,t=[];for(f=0;s>f;f++)if(e=b.charAt(f),"'"===e||'"'===e){if(f=b.indexOf(e,f+1),0>f)break}else"\\"===e?f++:(("0"===e||"#"===e)&&(o+=q,"0"===e&&(q?p=o:0>m&&(m=l)),l+=!q),q=q||"."===e);for(m=0>m?1:l-m,0>a&&t.push("-"),a=g(a,o),n=h(a),f=n-l,t.g=Math.max(n,m),t.t=d,i=0;s>i;i++)if(e=b.charAt(i),"'"===e||'"'===e){if(j=b.indexOf(e,i+1),t.push(b.substring(i+1,0>j?b.length:j)),0>j)break;i=j}else"\\"===e?(t.push(b.charAt(i+1)),i++):"#"===e||"0"===e?(n>f?(f>=0?(r&&k(t,a.substr(0,f)),k(t,a.charAt(f))):f>=n-m&&k(t,"0"),r=0):(p-->0||f=a.length?"0":a.charAt(f)),f++):"."===e?(a.length>++f||p>0)&&t.push(c):","!==e&&t.push(e);return t.join("")}function p(a,b){var d=s._r,e=s._t;if(a=Number(a),!isFinite(a))return""+a;if(!b&&"0"!==b)return n(a,0,0,10,d);var f=b.match(/^([a-zA-Z])(\d*)$/);if(f){var g=f[1].toUpperCase(),h=parseInt(f[2],10);switch(h=h>15?15:h,g){case"D":return n(a,c(h,1),0,0);case"F":e="";case"N":return n(a,1,c(h,2),c(h,2),d,e);case"G":case"E":for(var i=0,j=Math.abs(a);j>=10;)j/=10,i++;for(;1>j;)j*=10,i--;var k,l,m=f[1],p=3;if("G"===g){if(i>-5&&(!h||h>i))return k=h?h-(i>0?i+1:1):0,l=h?h-(i>0?i+1:1):10,n(a,1,k,l,d);m="G"===m?"E":"e",p=2,k=(h||1)-1,l=(h||11)-1}else k=l=c(h,6);return i>=0&&(m+="+"),0>a&&(j*=-1),n(""+j,1,k,l,d,e)+m+n(i,p,0);case"P":return n(100*a,1,c(h,2),c(h,2),d,e)+" %";case"X":var q=Math.round(a).toString(16);for("X"===f[1]&&(q=q.toUpperCase()),h-=q.length;h-->0;)q="0"+q;return q;case"C":b=s._c,d=s._cr,e=s._ct;break;case"R":return""+a}}-1!==b.indexOf(",.")&&(a/=1e3),-1!==b.indexOf("%")&&(a*=100);var r=b.split(";");return 0>a&&r.length>1?(a*=-1,b=r[1]):b=r[!a&&r.length>2?2:0],o(a,b,d,b.match(/^[^\.]*[0#],[0#]/)&&e)}function q(b,c){var d=b.getFullYear(),e=b.getMonth(),f=b.getDate(),g=b.getDay(),h=b.getHours(),i=b.getMinutes(),j=b.getSeconds();c=c||"G",1===c.length&&(c=s[c]||c);var k=s._Mg&&/(^|[^d])d(?!dd)/.test(c)?s._Mg:s._M;return c.replace(/(\\.|'[^']*'|"[^"]*"|d{1,4}|M{1,4}|yyyy|yy|HH?|hh?|mm?|ss?|tt?)/g,function(b){return"dddd"===b?s._D[g]:"ddd"===b?s._d?s._d[g]:s._D[g].substr(0,3):"dd"===b?a(f):"d"===b?f:"MMMM"===b?k[e]:"MMM"===b?s._m?s._m[e]:s._M[e].substr(0,3):"MM"===b?a(e+1):"M"===b?e+1:"yyyy"===b?d:"yy"===b?(""+d).substr(2):"HH"===b?a(h):"H"===b?h:"hh"===b?a((h-1)%12+1):"h"===b?(h-1)%12+1:"mm"===b?a(i):"m"===b?i:"ss"===b?a(j):"s"===b?j:"tt"===b?12>h?s._am:s._pm:"t"===b?(12>h?s._am:s._pm).charAt(0):b.substr(1,b.length-1-("\\"!==b.charAt(0)))})}function r(a){var b=arguments;return a.replace(/(\{+)((\d+|[a-zA-Z_$]\w+(?:\.[a-zA-Z_$]\w+|\[\d+\])*)(?:\,(-?\d*))?(?:\:([^\}]*))?)(\}+)|(\{+)|(\}+)/g,function(){var a=arguments;return a[7]?l(a[7]):a[8]?l(a[8]):a[1].length%2&&a[6].length%2?l(a[1],1)+m(a[3],a[4],a[5],b)+l(a[6],1):l(a[1])+a[2]+l(a[6])})}var s,t={name:"",d:"MM/dd/yyyy",D:"dddd, dd MMMM yyyy",t:"HH:mm",T:"HH:mm:ss",M:"MMMM dd",Y:"yyyy MMMM",s:"yyyy-MM-ddTHH:mm:ss",_M:["January","February","March","April","May","June","July","August","September","October","November","December"],_D:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],_r:".",_t:",",_c:"¤#,0.00",_ct:",",_cr:".",_am:"AM",_pm:"PM"},u=e({}),v="undefined"!=typeof navigator&&(navigator.systemLanguage||navigator.language)||"",w={};return r.unsafe=function(){Number.prototype.__Format=function(a){return p(this,a)},Date.prototype.__Format=function(a){return q(this,a)},String.__Format=r;for(var a=[Date.prototype,Number.prototype,String],b=0;ba?"0"+a:a}function b(a){return null!=a}function c(a,b){return isNaN(a)?b:a}function d(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function e(a){for(var b in s)null==a[b]&&(a[b]=s[b]);return a.f=a.f||a.D+" "+a.t,a.F=a.F||a.D+" "+a.T,a.g=a.g||a.d+" "+a.t,a.G=a.G||a.d+" "+a.T,a.m=a.M,a.y=a.Y,a}function f(){var a;if(u){var b=u.toUpperCase();a=v[b]||v[b.split("-")[0]]}q.LC=r=a||t}function g(a,b){var c=Math.pow(10,b||0);return""+Math.round(Math.abs(a)*c)/c}function h(a){var b=a.indexOf(".");return 0>b?a.length:b}function i(a){var b=a.indexOf(".");return 0>b?0:a.length-b-1}function j(a,c){if(b(c)){var d=/(\.([a-zA-Z_$]\w*)|\[(\d+)\])/g,e=/^[a-zA-Z_$]\w*/.exec(a);for(c=c[e[0]];b(c)&&(e=d.exec(a));)c=c[e[2]||Number(e[3])]}return c}function k(a,b){for(var c=0,d=b.length;d>c;c++)a.push(b.charAt(c)),a.g>1&&a.g--%3===1&&a.push(a.t)}function l(a,c,e,f){var g,h,i=parseInt(a,10),k="";if(isNaN(i))g=j(a,f[1]);else{if(i>f.length-2)throw"Missing argument";g=f[i+1]}for(g=b(g)?g.__Format?g.__Format(e):"number"==typeof g?o(g,e):d(g)?p(g,e):""+g:"",c=Number(c)||0,h=Math.abs(c)-g.length;h-->0;)k+=" ";return 0>c?g+k:k+g}function m(a,b,c,d,e,f){var j,l,m=[];for(m.t=f,0>a&&m.push("-"),a=g(a,d),j=m.g=h(a),l=i(a),b-=j;b-->0;)k(m,"0");if(k(m,a.substr(0,j)),c||l)for(m.push(e),k(m,a.substr(j+1)),c-=l;c-->0;)k(m,"0");return m.join("")}function n(a,b,c,d){var e,f,i,j,l=0,m=-1,n=-1,o=0,p=-1,q=0,r=1,s=b.length,t=[];for(f=0;s>f;f++)if(e=b.charAt(f),"'"===e||'"'===e){if(f=b.indexOf(e,f+1),0>f)break}else"\\"===e?f++:(("0"===e||"#"===e)&&(o+=q,"0"===e&&(q?p=o:0>m&&(m=l)),l+=!q),q=q||"."===e);for(m=0>m?1:l-m,0>a&&t.push("-"),a=g(a,o),n=h(a),f=n-l,t.g=Math.max(n,m),t.t=d,i=0;s>i;i++)if(e=b.charAt(i),"'"===e||'"'===e){if(j=b.indexOf(e,i+1),t.push(b.substring(i+1,0>j?b.length:j)),0>j)break;i=j}else"\\"===e?(t.push(b.charAt(i+1)),i++):"#"===e||"0"===e?(n>f?(f>=0?(r&&k(t,a.substr(0,f)),k(t,a.charAt(f))):f>=n-m&&k(t,"0"),r=0):(p-->0||f=a.length?"0":a.charAt(f)),f++):"."===e?(a.length>++f||p>0)&&t.push(c):","!==e&&t.push(e);return t.join("")}function o(a,b){var d=r._r,e=r._t;if(a=Number(a),!isFinite(a))return""+a;if(!b&&"0"!==b)return m(a,0,0,10,d);var f=b.match(/^([a-zA-Z])(\d*)$/);if(f){var g=f[1].toUpperCase(),h=parseInt(f[2],10);switch(h=h>15?15:h,g){case"D":return m(a,c(h,1),0,0);case"F":e="";case"N":return m(a,1,c(h,2),c(h,2),d,e);case"G":case"E":for(var i=0,j=Math.abs(a);j>=10;)j/=10,i++;for(;1>j;)j*=10,i--;var k,l,o=f[1],p=3;if("G"===g){if(i>-5&&(!h||h>i))return k=h?h-(i>0?i+1:1):0,l=h?h-(i>0?i+1:1):10,m(a,1,k,l,d);o="G"===o?"E":"e",p=2,k=(h||1)-1,l=(h||11)-1}else k=l=c(h,6);return i>=0&&(o+="+"),0>a&&(j*=-1),m(""+j,1,k,l,d,e)+o+m(i,p,0);case"P":return m(100*a,1,c(h,2),c(h,2),d,e)+" %";case"X":var q=Math.round(a).toString(16);for("X"===f[1]&&(q=q.toUpperCase()),h-=q.length;h-->0;)q="0"+q;return q;case"C":b=r._c,d=r._cr,e=r._ct;break;case"R":return""+a}}-1!==b.indexOf(",.")&&(a/=1e3),-1!==b.indexOf("%")&&(a*=100);var s=b.split(";");return 0>a&&s.length>1?(a*=-1,b=s[1]):b=s[!a&&s.length>2?2:0],n(a,b,d,b.match(/^[^\.]*[0#],[0#]/)&&e)}function p(b,c){var d=b.getFullYear(),e=b.getMonth(),f=b.getDate(),g=b.getDay(),h=b.getHours(),i=b.getMinutes(),j=b.getSeconds();c=c||"G",1===c.length&&(c=r[c]||c);var k=r._Mg&&/(^|[^d])d(?!dd)/.test(c)?r._Mg:r._M;return c.replace(/(\\.|'[^']*'|"[^"]*"|d{1,4}|M{1,4}|yyyy|yy|HH?|hh?|mm?|ss?|tt?)/g,function(b){return"dddd"===b?r._D[g]:"ddd"===b?r._d?r._d[g]:r._D[g].substr(0,3):"dd"===b?a(f):"d"===b?f:"MMMM"===b?k[e]:"MMM"===b?r._m?r._m[e]:r._M[e].substr(0,3):"MM"===b?a(e+1):"M"===b?e+1:"yyyy"===b?d:"yy"===b?(""+d).substr(2):"HH"===b?a(h):"H"===b?h:"hh"===b?a((h-1)%12+1):"h"===b?(h-1)%12+1:"mm"===b?a(i):"m"===b?i:"ss"===b?a(j):"s"===b?j:"tt"===b?12>h?r._am:r._pm:"t"===b?(12>h?r._am:r._pm).charAt(0):b.substr(1,b.length-1-("\\"!==b.charAt(0)))})}function q(a,b,c,d){var e=arguments;return a.replace(/\{((\d+|[a-zA-Z_$]\w*(?:\.[a-zA-Z_$]\w*|\[\d+\])*)(?:\,(-?\d*))?(?:\:([^\}]*(?:(?:\}\})+[^\}]+)*))?)\}|(\{\{)|(\}\})/g,function(){var a=arguments;return a[5]?"{":a[6]?"}":l(a[2],a[3],a[4]&&a[4].replace(/\}\}/g,"}").replace(/\{\{/g,"{"),e)})}var r,s={name:"",d:"MM/dd/yyyy",D:"dddd, dd MMMM yyyy",t:"HH:mm",T:"HH:mm:ss",M:"MMMM dd",Y:"yyyy MMMM",s:"yyyy-MM-ddTHH:mm:ss",_M:["January","February","March","April","May","June","July","August","September","October","November","December"],_D:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],_r:".",_t:",",_c:"¤#,0.00",_ct:",",_cr:".",_am:"AM",_pm:"PM"},t=e({}),u="undefined"!=typeof navigator&&(navigator.systemLanguage||navigator.language)||"",v={};return q.unsafe=function(){Number.prototype.__Format=function(a){return o(this,a)},Date.prototype.__Format=function(a){return p(this,a)},String.__Format=q;for(var a=[Date.prototype,Number.prototype,String],b=0;b /// Performs a series of unit tests and writes the output to the page. /// - function runTests(test) { sffjs.setCulture("en"); var testObject = { + a: "b", authors: [ { - firstname: "John", - lastname: "Doe", + firstname: "John", + lastname: "Doe", phonenumbers: [ { - home: "345" + home: "012", + home: "345" } ], - age: 27 + age: 27 } ] }; @@ -58,7 +59,7 @@ assert.formatsTo("Test {with} brackets", "Test {{with}} brackets"); assert.formatsTo("{brackets} in args", "{0} in args", "{brackets}"); assert.formatsTo("{{dblbrackets}} in args", "{0} in args", "{{dblbrackets}}"); - assert.formatsTo("Mismatch {{0}", "Mismatch {{{0}}", "{{brackets}"); + assert.formatsTo("Mismatch {{{brackets}}", "Mismatch {{{0}}", "{{brackets}"); assert.formatsTo("Double outer {{{brackets}}", "Double outer {{{0}}}", "{{brackets}"); test.section("Index"); @@ -67,10 +68,9 @@ assert.formatsTo("!true!", "!{0}!", true); assert.formatsTo("null:!!", "null:!{0}!", null); assert.formatsTo("undefined:!!", "undefined:!{0}!", undefined); - assert.doesThrow(function () { String.format("{1}", 42); }, "Missing argument", "Index out of range"); + assert.doesThrow(function () { String.format("{1}", 42) }, "Missing argument", "Index out of range"); assert.formatsTo("Negative index:!{-1}!", "Negative index:!{-1}!", 42); - test.section("Path"); assert.formatsTo("Hi, John!", "Hi, {authors[0].firstname}!", testObject); assert.formatsTo("Hi, !", "Hi, {authors[1].firstname}!", testObject); @@ -82,6 +82,7 @@ assert.formatsTo("Hi, !", "Hi, {authors.fdg}!", testObject); assert.formatsTo("Hi, 1!", "Hi, {authors.length}!", testObject); assert.formatsTo("1.00", "{authors.length:0.00}", testObject); + assert.formatsTo("After a comes b.", "After a comes {a}.", testObject); test.section("Invalid paths"); assert.formatsTo("Hi, {fg$}!", "Hi, {fg$}!", undefined); @@ -91,6 +92,21 @@ assert.formatsTo("Hi, {.fg}!", "Hi, {.fg}!", undefined); assert.formatsTo("Hi, {a..b}!", "Hi, {a..b}!", undefined); + test.section("Escaped braces"); + assert.formatsTo("a { b", "a {{ b", testObject); + assert.formatsTo("a } b", "a }} b", testObject); + assert.formatsTo("a{{a}}", "a{{{{a}}}", testObject); // * + assert.formatsTo("a{{b}", "a{{{{{a}}}", testObject); + assert.formatsTo("a{aba}a", "a{{a{a}a}}a", testObject); + assert.formatsTo("a{{aba", "a{{{a{a}a", testObject); // * + assert.formatsTo("a{bbb{}a", "a{{b{a}{a}{}a", testObject); // * + assert.formatsTo("4}.2", "{0:0}}.0}", 4.2); + assert.formatsTo("4{.2", "{0:0{{.0}", 4.2); + assert.formatsTo("4}{{}.2", "{0:0}}{{{{}}.0}", 4.2); + // * These tests do not produce the same output as in .NET. In .NET these format strings will + // generate a FormatException while the JS implementation makes a best effort to finish processing + // the format string. + var dtam = new Date(1989, 3, 2, 6, 20, 33); var dtpm = new Date(1989, 3, 2, 18, 20, 33); var dt2009 = new Date(2009, 3, 2, 18, 20, 33); @@ -356,8 +372,6 @@ assert.formatsTo("{brackets} in args", "{0} in args", "{brackets}"); assert.formatsTo("{{dblbrackets}} in args", "{0} in args", "{{dblbrackets}}"); - assert.formatsTo("Mismatch {{0}", "Mismatch {{{0}}", "{{brackets}"); - assert.formatsTo("Double outer {{{brackets}}", "Double outer {{{0}}}", "{{brackets}"); test.section("setCulture"); sffjs.registerCulture({ name: "__LANG" }); @@ -389,21 +403,21 @@ sffjs.setCulture(""); } - + function Test() { var t = this; window.currentTest = this; this.sections = []; - this.section = function (name) { + this.section = function(name) { t.sections.push({ name: name, results: [] }); }; - this.result = function (result) { + this.result = function(result) { if (t.sections.length === 0) { t.section("Untitled test section"); } @@ -411,7 +425,7 @@ t.sections[t.sections.length - 1].results.push(result); }; - this.print = function () { + this.print = function() { var container = document.createElement("div"); var numTests = 0; @@ -443,7 +457,6 @@ progressBar.appendChild(progress); totalResult.appendChild(progressBar); - var table = document.createElement("table"); var tr, td; container.appendChild(table); @@ -510,12 +523,16 @@ } switch (typeof value) { - case "number": return value.toString(); + case "number": + return value.toString(); case "string": - if (value.length > 40) { value = value.substr(0, 40) + "..."; } + if (value.length > 40) { + value = value.substr(0, 40) + "..."; + } return "\"" + value + "\""; - case "undefined": return "[undefined]"; + case "undefined": + return "[undefined]"; case "object": if (value instanceof Date) { @@ -543,7 +560,7 @@ } var assert = { - areEqual: function (expected, actual, message) { + areEqual: function(expected, actual, message) { var result = actual === expected; actual = stringify(actual); @@ -552,7 +569,7 @@ registerTestResult(message, result ? null : String.format("Expected: {0}, actual: {1}", expected, actual)); }, - doesThrow: function (fn, expectedError, message) { + doesThrow: function(fn, expectedError, message) { var actualError = "[No exception thrown]"; try { @@ -564,7 +581,7 @@ assert.areEqual(expectedError, actualError, message); }, - formatsTo: function (expected, formatString, obj0) { + formatsTo: function(expected, formatString, obj0) { var args = Array.prototype.slice.call(arguments, 1); var actual; @@ -590,7 +607,7 @@ var s = String.format; var formats = 0; - String.format = function () { + String.format = function() { formats++; return s.apply(null, arguments); };