From e9a051f3c47b21be1e27200cce058056ab8952a5 Mon Sep 17 00:00:00 2001 From: thorn0 Date: Tue, 16 Aug 2016 02:42:13 +0300 Subject: [PATCH] Built sffjs from commit 32575c8 on branch master --- LICENSE | 31 ++--- changelog.txt | 90 --------------- cultures/stringformat.sv-SE.js | 2 +- cultures/stringformat.sv.js | 2 +- package.json | 18 +-- sffjs.js | 203 +++++++++++++++++++-------------- sffjs.min.js | 2 +- test/tests.html | 2 +- test/tests.js | 178 +++++++++++++++++++++-------- 9 files changed, 274 insertions(+), 254 deletions(-) delete mode 100644 changelog.txt diff --git a/LICENSE b/LICENSE index 1f36887..38f19e6 100644 --- a/LICENSE +++ b/LICENSE @@ -3,9 +3,10 @@ String.format for JavaScript Copyright (c) 2009-2014 Daniel Mester Pirttijärvi +Fork by Georgii Dolzhykov -The library core (stringformat-1.09.js and stringformat-1.09.min.js) is -licensed under the terms of the zlib license. +The library core (sffjs.js and sffjs.min.js) is +licensed under the terms of the zlib license. * * * @@ -29,29 +30,29 @@ freely, subject to the following restrictions: --------------------------------------------------------------------------- -Most of the culture files contain information extracted using the Mono +Most of the culture files contain information extracted using the Mono class library, licensed under the terms of the MIT X11 license. * * * Copyright (c) -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------------- \ No newline at end of file diff --git a/changelog.txt b/changelog.txt deleted file mode 100644 index 0e79398..0000000 --- a/changelog.txt +++ /dev/null @@ -1,90 +0,0 @@ - -CHANGELOG -String.format for JavaScript - ---------------------------------------------------------------------------- -Version 1.0 -Released 2009-06-05 - -* Initial release ---------------------------------------------------------------------------- -Version 1.01 -Released 2009-12-14 - -* Bug fix: Date formatting bug fix ---------------------------------------------------------------------------- -Version 1.02 -Released 2009-12-20 - -* Ms-PL replaced by the zlib license ---------------------------------------------------------------------------- -Version 1.03 -Released 2012-10-01 - -* Bug fix: Fixed formatting bug when non-placeholder chars were included - after decimal point. ---------------------------------------------------------------------------- -Version 1.04 -Released 2013-04-01 - -* Added localizations for the cultures de (German), es (spanish) and fr - (French). -* Added support for object paths instead of indexes. Supply the object as - first argument, and access its members by using the syntax {member_name}. -* Added support for the date format specifiers 'M', 'Y' and 't'. -* Added support for precision on numeric standard format strings. -* Added support for the standard numeric format strings 'd', 'r', 'p' and - 'e'. -* Standard format string 'g' now supports exponential notation and uses it - under the same circumstances as the .NET implementation. -* Bug fix: escaped end braces (}}) were not unescaped to }. -* Bug fix: values containing escaped braces (}}) was incorrectly unescaped - to }. ---------------------------------------------------------------------------- -Version 1.05 -Released 2013-04-07 - -* Added support for explicit abbreviated month and day names. -* Added public interface for registering cultures after initialization - (msf.registerCulture()). -* Added all cultures available in Mono. -* Added library version to global msf object. -* Bug fix: fallback from region specific culture to neutral culture assumed - the language code was always 2 characters. ---------------------------------------------------------------------------- -Version 1.06 -Released 2013-04-08 - -* Bug fix: the text alignment when specifying the alignment option was - reversed. ---------------------------------------------------------------------------- -Version 1.07 -Released 2013-06-14 - -* Bug fix: no default culture was set on startup. ---------------------------------------------------------------------------- -Version 1.08 -Released 2013-07-16 - -* Bug fix: String.format crashing while processing a format item without a - format string when the value was a Date instance. -* Bug fix: string literals enclosed by double quotation marks and escaped - single characters were not treated as such. ---------------------------------------------------------------------------- -Version 1.09 -Released 2014-01-26 - -* Minor changes. -* Changed name of API namespace from msf to sffjs. msf will remain for some - time for backward compatibility but will be removed in the future. ---------------------------------------------------------------------------- -Version 1.10 (fork) -Not released - -* Bug fix: genitive forms for months (needed for Slavic languages) -* Added support for AMD and CommonJS. The global object and other native - objects such as String aren't modified if the library is loaded - via AMD or CommonJS. -* The sffjs object is a function now. It's the same function that is - assigned to String.format. - diff --git a/cultures/stringformat.sv-SE.js b/cultures/stringformat.sv-SE.js index 46797fb..beb1ab6 100644 --- a/cultures/stringformat.sv-SE.js +++ b/cultures/stringformat.sv-SE.js @@ -1 +1 @@ -sffjs.registerCulture({name:"sv",d:"yyyy-MM-dd",D:"'den 'd MMMM yyyy",M:"d MMMM",Y:"MMMM yyyy",_M:["januari","februari","mars","april","maj","juni","juli","augusti","september","oktober","november","december"],_m:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],_D:["söndag","måndag","tisdag","onsdag","torsdag","fredag","lördag"],_d:["sön","mån","tis","ons","tor","fre","lör"],_r:",",_cr:",",_t:" ",_ct:".",_c:"#,0.00 kr"}); \ No newline at end of file +sffjs.registerCulture({name:"sv",d:"yyyy-MM-dd",D:"'den 'd MMMM yyyy",M:"d MMMM",Y:"MMMM yyyy",_M:["januari","februari","mars","april","maj","juni","juli","augusti","september","oktober","november","december"],_m:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],_D:["söndag","måndag","tisdag","onsdag","torsdag","fredag","lördag"],_d:["sön","mån","tis","ons","tor","fre","lör"],_r:",",_cr:",",_t:" ",_ct:".",_c:"#,0.00 kr"}); \ No newline at end of file diff --git a/cultures/stringformat.sv.js b/cultures/stringformat.sv.js index 46797fb..beb1ab6 100644 --- a/cultures/stringformat.sv.js +++ b/cultures/stringformat.sv.js @@ -1 +1 @@ -sffjs.registerCulture({name:"sv",d:"yyyy-MM-dd",D:"'den 'd MMMM yyyy",M:"d MMMM",Y:"MMMM yyyy",_M:["januari","februari","mars","april","maj","juni","juli","augusti","september","oktober","november","december"],_m:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],_D:["söndag","måndag","tisdag","onsdag","torsdag","fredag","lördag"],_d:["sön","mån","tis","ons","tor","fre","lör"],_r:",",_cr:",",_t:" ",_ct:".",_c:"#,0.00 kr"}); \ No newline at end of file +sffjs.registerCulture({name:"sv",d:"yyyy-MM-dd",D:"'den 'd MMMM yyyy",M:"d MMMM",Y:"MMMM yyyy",_M:["januari","februari","mars","april","maj","juni","juli","augusti","september","oktober","november","december"],_m:["jan","feb","mar","apr","maj","jun","jul","aug","sep","okt","nov","dec"],_D:["söndag","måndag","tisdag","onsdag","torsdag","fredag","lördag"],_d:["sön","mån","tis","ons","tor","fre","lör"],_r:",",_cr:",",_t:" ",_ct:".",_c:"#,0.00 kr"}); \ No newline at end of file diff --git a/package.json b/package.json index 4ee30b4..108a29f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sffjs", - "version": "1.10.0", + "version": "1.11.1", "description": "String.Format for JavaScript", "main": "sffjs.js", "repository": { @@ -22,11 +22,15 @@ }, "homepage": "https://github.com/thorn0/sffjs", "devDependencies": { - "grunt": "^0.4.5", - "grunt-build-control": "^0.6.1", - "grunt-contrib-clean": "^0.6.0", - "grunt-contrib-copy": "^0.7.0", - "grunt-contrib-uglify": "^0.9.2", - "load-grunt-tasks": "^3.3.0" + "grunt": "^1.0.1", + "grunt-build-control": "^0.7.1", + "grunt-contrib-clean": "^1.0.0", + "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-uglify": "^2.0.0", + "load-grunt-tasks": "^3.5.2" + }, + "scripts": { + "test": "node src/stringformat.tests", + "dotnet": "node src/stringformat.tests dotnet" } } diff --git a/sffjs.js b/sffjs.js index f9124c0..d694eb9 100644 --- a/sffjs.js +++ b/sffjs.js @@ -1,5 +1,5 @@ /** - * String.format for JavaScript, version 1.10.0 + * String.format for JavaScript, version 1.11.1 * * Copyright (c) 2009-2014 Daniel Mester Pirttijärvi * http://mstr.se/sffjs @@ -27,7 +27,7 @@ * */ /* global sffjs:true, module, define */ -/* jshint curly:false, eqeqeq:true */ +/* jshint curly:false, eqeqeq:true, eqnull:true, -W053:true */ (function(factory) { if (typeof module !== 'undefined' && module.exports) { @@ -43,6 +43,7 @@ } }).call(this, function() { + "use strict"; // ***** Private Variables ***** @@ -328,52 +329,119 @@ integralDigits = -1, decimals = 0, forcedDecimals = -1, + + thousandsMultiplier = 1, + atDecimals = 0, // Bool unused = 1, // Bool, True until a digit has been written to the output - c, i, f, - format_length = format.length, + + tokens = [], + tokenGroups = [tokens], + + currentToken, + numberIndex, + formatIndex, endIndex, + out = []; - // Analyse format string - // Count number of digits, decimals, forced digits and forced decimals. - for (i = 0; i < format_length; i++) { - c = format.charAt(i); + // Tokenize format string. + // Constants are represented with String instances, while all other tokens are represented with + // string literals. + for (formatIndex = 0; formatIndex < format.length; formatIndex++) { + currentToken = format.charAt(formatIndex); // Check if we have reached a literal - if (c === "'" || c === '"') { + if (currentToken === "'" || currentToken === '"') { - // Search for end of literal - i = format.indexOf(c, i + 1); + // Find end of literal + endIndex = format.indexOf(currentToken, formatIndex + 1); + + // String instances are used to represent constants + tokens.push(new String( + format.substring( + formatIndex + 1, + endIndex < 0 ? undefined : endIndex // assume rest of string if matching quotation mark is missing + ))); // If there is no matching end quotation mark, let's assume the rest of the string is a literal. // This is the way .NET handles things. - if (i < 0) break; + if (endIndex < 0) break; + + formatIndex = endIndex; // Check for single escaped character - } else if (c === "\\") { - i++; + } else if (currentToken === "\\") { + // String instances are used to represent constants + tokens.push(new String(format.charAt(++formatIndex))); + + } else if (currentToken === ";") { + + // Short circuit tokenizer + if (number > 0 || // No need to parse any more groups if the number is positive since the first group is for positive numbers + number < 0 && tokenGroups.length > 1) { // Dito for negative numbers which is specified in the second group + break; + } + + // Begin a new token group + tokenGroups.push(tokens = []); } else { + tokens.push(currentToken); + } + } - // Only 0 and # are digit placeholders, skip other characters in analyzing phase - if (c === "0" || c === "#") { - decimals += atDecimals; + // Determine which token group to be used ( positive; negative; zero, where the two last ones are optional) + if (number < 0 && tokenGroups.length > 1) { + number *= -1; + format = tokenGroups[1]; + } else { + format = tokenGroups[!number && tokenGroups.length > 2 ? 2 : 0]; + } - if (c === "0") { - // 0 is a forced digit - if (atDecimals) { - forcedDecimals = decimals; - } else if (forcedDigits < 0) { - forcedDigits = digits; - } + // Analyse format string + // Count number of digits, decimals, forced digits and forced decimals. + for (formatIndex = 0; formatIndex < format.length; formatIndex++) { + currentToken = format[formatIndex]; + + // Only handle digit placeholders and number multipliers during analysis phase + if (currentToken === "0" || currentToken === "#") { + decimals += atDecimals; + + if (currentToken === "0") { + // 0 is a forced digit + if (atDecimals) { + forcedDecimals = decimals; + } else if (forcedDigits < 0) { + forcedDigits = digits; } + } - digits += !atDecimals; + // If a comma specifier is specified before the last integral digit + // it indicates thousand grouping. + if (thousandsMultiplier !== 1 && !atDecimals) { + // Set thousand separator + out.t = thousandSeparator; + thousandsMultiplier = 1; } - // If the current character is ".", then we have reached the end of the integral part - atDecimals = atDecimals || c === "."; + digits += !atDecimals; + } + + // End of integral part + else if (currentToken === ".") { + atDecimals = 1; + } + + // Comma specifier used for both thousand grouping and scaling. + // It is only effective if specified before the explicit or implicit decimal point. + else if (currentToken === "," && !atDecimals && digits > 0) { + thousandsMultiplier *= 0.001; + } + + // Percent + else if (currentToken === "%") { + number *= 100; } } forcedDigits = forcedDigits < 0 ? 1 : digits - forcedDigits; @@ -384,74 +452,53 @@ } // Round the number value to a specified number of decimals - number = numberToString(number, decimals); + number = numberToString(number * thousandsMultiplier, decimals); // Get integral length integralDigits = numberOfIntegralDigits(number); // Set initial number cursor position - i = integralDigits - digits; + numberIndex = integralDigits - digits; // Initialize thousand grouping out.g = Math.max(integralDigits, forcedDigits); - out.t = thousandSeparator; - for (f = 0; f < format_length; f++) { - c = format.charAt(f); - - // Check if we have reached a literal - if (c === "'" || c === '"') { - - // Find end of literal - endIndex = format.indexOf(c, f + 1); - - out.push( - format.substring( - f + 1, - endIndex < 0 ? format.length : endIndex // assume rest of string if matching quotation mark is missing - )); + for (formatIndex = 0; formatIndex < format.length; formatIndex++) { + currentToken = format[formatIndex]; - if (endIndex < 0) break; - f = endIndex; - - // Single escaped character - } else if (c === "\\") { - out.push(format.charAt(f + 1)); - f++; - - // Digit placeholder - } else if (c === "#" || c === "0") { - if (i < integralDigits) { + // Digit placeholder + if (currentToken === "#" || currentToken === "0") { + if (numberIndex < integralDigits) { // In the integral part - if (i >= 0) { + if (numberIndex >= 0) { if (unused) { - groupedAppend(out, number.substr(0, i)); + groupedAppend(out, number.substr(0, numberIndex)); } - groupedAppend(out, number.charAt(i)); + groupedAppend(out, number.charAt(numberIndex)); // Not yet inside the number number, force a zero? - } else if (i >= integralDigits - forcedDigits) { + } else if (numberIndex >= integralDigits - forcedDigits) { groupedAppend(out, "0"); } unused = 0; - } else if (forcedDecimals-- > 0 || i < number.length) { + } else if (forcedDecimals-- > 0 || numberIndex < number.length) { // In the fractional part - groupedAppend(out, i >= number.length ? "0" : number.charAt(i)); + groupedAppend(out, numberIndex >= number.length ? "0" : number.charAt(numberIndex)); } - i++; + numberIndex++; // Radix point character according to current culture. - } else if (c === ".") { - if (number.length > ++i || forcedDecimals > 0) { + } else if (currentToken === ".") { + if (number.length > ++numberIndex || forcedDecimals > 0) { out.push(radixPoint); } // Other characters are written as they are, except from commas - } else if (c !== ",") { - out.push(c); + } else if (currentToken !== ",") { + out.push(currentToken); } } @@ -631,27 +678,7 @@ } // EVALUATE CUSTOM NUMERIC FORMAT STRING - - // Thousands - if (format.indexOf(",.") !== -1) { - number /= 1000; - } - - // Percent - if (format.indexOf("%") !== -1) { - number *= 100; - } - - // Split groups ( positive; negative; zero, where the two last ones are optional) - var groups = format.split(";"); - if (number < 0 && groups.length > 1) { - number *= -1; - format = groups[1]; - } else { - format = groups[!number && groups.length > 2 ? 2 : 0]; - } - - return customNumberFormatter(number, format, radixPoint, format.match(/^[^\.]*[0#],[0#]/) && thousandSeparator); + return customNumberFormatter(number, format, radixPoint, thousandSeparator); } // ***** Date Formatting ***** @@ -774,7 +801,7 @@ }; /// The version of the library String.Format for JavaScript. - sffjs.version = "1.10.0"; + sffjs.version = "1.11.1"; sffjs.setCulture = function(languageCode) { /// diff --git a/sffjs.min.js b/sffjs.min.js index 5a345d9..14a353f 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 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;b1&&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 c<0?g+k:k+g}function m(a,b,c,d,e,f){var j,l,m=[];for(m.t=f,a<0&&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=1,r=0,s=1,t=[],u=[t],v=[];for(i=0;i0||a<0&&u.length>1)break;u.push(t=[])}else t.push(e);for(a<0&&u.length>1?(a*=-1,b=u[1]):b=u[!a&&u.length>2?2:0],i=0;i0?q*=.001:"%"===e&&(a*=100);for(m=m<0?1:l-m,a<0&&v.push("-"),a=g(a*q,o),n=h(a),f=n-l,v.g=Math.max(n,m),i=0;i=0?(s&&k(v,a.substr(0,f)),k(v,a.charAt(f))):f>=n-m&&k(v,"0"),s=0):(p-- >0||f=a.length?"0":a.charAt(f)),f++):"."===e?(a.length>++f||p>0)&&v.push(c):","!==e&&v.push(e);return v.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(;j<1;)j*=10,i--;var k,l,o=f[1],p=3;if("G"===g){if(i>-5&&(!h||i0?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+="+"),a<0&&(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}}return n(a,b,d,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?h<12?r._am:r._pm:"t"===b?(h<12?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 body { - font: 0.75em Aial, sans-serif; + font: 0.75em Arial, sans-serif; } div.pass, div.fail { diff --git a/test/tests.js b/test/tests.js index ad043e6..556149f 100644 --- a/test/tests.js +++ b/test/tests.js @@ -27,34 +27,47 @@ * 3. This notice may not be removed or altered from any source distribution. * */ -/*global sffjs,require*/ +/*jshint -W045:true, undef:true, browser:true, node:true*/ +/*global sffjs*/ (function() { + "use strict"; + var browser = typeof window !== 'undefined', + dotNetStringFormat; + + if (!browser) { + global.sffjs = require('./stringformat'); + require('./cultures/stringformat.en-US'); + require('./cultures/stringformat.sv'); + require('./cultures/stringformat.uk'); + sffjs.unsafe(); + + if (process.platform === 'win32' && process.argv[2] === 'dotnet') { + dotNetStringFormat = require('../checker/checker'); + } + } + /// /// Performs a series of unit tests and writes the output to the page. /// function runTests(test) { - sffjs.setCulture("en"); + if (browser) { + // In the browser, sffjs tries to get the culture from the properties of the navigator object + sffjs.setCulture('en'); + } var testObject = { a: "b", - authors: [ - { + authors: [{ firstname: "John", lastname: "Doe", - phonenumbers: [ - { - home: "012", - home: "345" - } - ], + phonenumbers: [{ + home: "012" + }], age: 27 - } - ] + }] }; - var undefined; - test.section("Tags"); assert.formatsTo("Test {with} brackets", "Test {{with}} brackets"); assert.formatsTo("{brackets} in args", "{0} in args", "{brackets}"); @@ -68,7 +81,7 @@ 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"); @@ -103,10 +116,10 @@ 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 + // * 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); @@ -195,10 +208,16 @@ assert.formatsTo("pos", "{0:pos;neg}", 5); assert.formatsTo("neg", "{0:pos;neg}", -5); assert.formatsTo("pos", "{0:pos;neg}", 0); + assert.formatsTo("pos;", "{0:pos';';neg';'}", 5); + assert.formatsTo("neg;", "{0:pos';';neg';'}", -5); + assert.formatsTo("pos;", "{0:pos';';neg';'}", 0); assert.formatsTo("pos", "{0:pos;neg;zero}", 5); assert.formatsTo("neg", "{0:pos;neg;zero}", -5); assert.formatsTo("zero", "{0:pos;neg;zero}", 0); + assert.formatsTo("pos", "{0:pos;neg;zero';'}", 5); + assert.formatsTo("neg", "{0:pos;neg;zero';'}", -5); + assert.formatsTo("zero;", "{0:pos;neg;zero';'}", 0); test.section("Simple numeric format strings"); assert.formatsTo("0.42", "{0}", 0.42); @@ -206,8 +225,6 @@ test.section("Custom numeric format strings"); assert.formatsTo("4", "{0:0}", 4.42); - assert.formatsTo("42%", "{0:0%}", 0.42); - assert.formatsTo("42.01%", "{0:0.00%}", 0.42009); assert.formatsTo("42.01d", "{0:0.00d}", 42.009); assert.formatsTo("42.01", "{0:0.0#}", 42.009); assert.formatsTo("42.0", "{0:0.0#}", 42.001); @@ -219,9 +236,26 @@ assert.formatsTo("042.50", "{0:000.#0}", 42.5); assert.formatsTo("042.5", "{0:000.0#}", 42.5); assert.formatsTo("042.5000000000000000000000000", "{0:000.0000000000000000000000000}", 42.5); - assert.formatsTo("1098#234.0", "{0:0'098#'000.0#}", 1234); + test.section("Custom numeric format strings - percent"); + assert.formatsTo("42%", "{0:0%}", 0.42); + assert.formatsTo("4200%%", "{0:0%%}", 0.42); + assert.formatsTo("4%", "{0:0'%'}", 4.2); + assert.formatsTo("4%", "{0:0\\%}", 4.2); + assert.formatsTo("42.01%", "{0:0.00%}", 0.42009); + + test.section("Custom numeric format strings - thousands separator and scaling"); + assert.formatsTo("4200", "{0:,0,}", 4200000); + assert.formatsTo("4,200", "{0:#,0,}", 4200000); + assert.formatsTo("4", "{0:#,0,,}", 4200000); + assert.formatsTo("4.2", "{0:0,,.0}", 4200000); + assert.formatsTo("4.2", "{0:#,0,,.0}", 4200000); + assert.formatsTo("4200.0", "{0:0.,0}", 4200); + assert.formatsTo("4200,", "{0:0\\,.}", 4200); + assert.formatsTo("4.", "{0:0,\\.}", 4200); + assert.formatsTo("4200,.", "{0:0',.'}", 4200); + test.section("Specifier D"); assert.formatsTo("43", "{0:d}", 42.5); assert.formatsTo("43", "{0:D}", 42.5); @@ -318,7 +352,7 @@ sffjs.setCulture("sv-SE"); assert.formatsTo("1,2e+03", "{0:g2}", 1242.55); assert.formatsTo("1242,50", "{0:f}", 1242.5); - assert.formatsTo("1 242,500", "{0:N3}", 1242.5); + assert.formatsTo("1 242,500", "{0:N3}", 1242.5); assert.formatsTo("2353", "{0:R}", 2353); assert.formatsTo("25.3333333", "{0:R}", 25.3333333); @@ -403,11 +437,12 @@ sffjs.setCulture(""); } - + var currentTest; + function Test() { var t = this; - window.currentTest = this; + currentTest = this; this.sections = []; this.section = function(name) { @@ -426,26 +461,32 @@ }; this.print = function() { - var container = document.createElement("div"); - - var numTests = 0; - var numPassedTests = 0; - - var si, ri; + this.numTests = 0; + this.numPassedTests = 0; - for (si in this.sections) { - for (ri in this.sections[si].results) { - numTests++; + for (var si in this.sections) { + for (var ri in this.sections[si].results) { + this.numTests++; if (this.sections[si].results[ri].result) { - numPassedTests++; + this.numPassedTests++; } } } + if (browser) { + this.printInBrowser(); + } else { + this.printInNonBrowserEnv(); + } + }; + + this.printInBrowser = function() { + var container = document.createElement("div"); + var totalResult = document.createElement("div"); - totalResult.className = (numPassedTests == numTests ? "pass" : "fail") + " total-result"; - totalResult.innerHTML = String.format("{0} of {1} tests passed", numPassedTests, numTests); - totalResult.setAttribute("data-percent", Math.round(100 * numPassedTests / numTests)); + totalResult.className = (this.numPassedTests == this.numTests ? "pass" : "fail") + " total-result"; + totalResult.innerHTML = String.format("{0} of {1} tests passed", this.numPassedTests, this.numTests); + totalResult.setAttribute("data-percent", Math.round(100 * this.numPassedTests / this.numTests)); container.appendChild(totalResult); var progressBar = document.createElement("div"); @@ -453,7 +494,7 @@ progressBar.style.width = "200px"; var progress = document.createElement("span"); - progress.style.width = Math.round(100 * numPassedTests / numTests) + "%"; + progress.style.width = Math.round(100 * this.numPassedTests / this.numTests) + "%"; progressBar.appendChild(progress); totalResult.appendChild(progressBar); @@ -467,7 +508,7 @@ return tr; } - for (si in t.sections) { + for (var si in t.sections) { var section = t.sections[si]; tr = createRow(); @@ -476,7 +517,7 @@ td.appendChild(document.createTextNode(section.name)); tr.appendChild(td); - for (ri in section.results) { + for (var ri in section.results) { var result = section.results[ri]; tr = createRow(); @@ -505,11 +546,32 @@ document.body.appendChild(container); }; + + this.printInNonBrowserEnv = function() { + for (var si in t.sections) { + var section = t.sections[si]; + var sectionNamePrinted = false; + for (var ri in section.results) { + var result = section.results[ri]; + if (!result.result) { + if (!sectionNamePrinted) { + console.log(section.name); + sectionNamePrinted = true; + } + console.log(result.result ? "PASS:" : "FAIL:", result.message, '\t', result.errorMessage); + } + } + if (sectionNamePrinted) { + console.log(); + } + } + console.log(String.format("{0} of {1} tests passed", this.numPassedTests, this.numTests)); + }; } function registerTestResult(message, errorMessage) { - if (window.currentTest) { - window.currentTest.result({ + if (currentTest) { + currentTest.result({ message: message, result: !errorMessage, errorMessage: errorMessage @@ -594,17 +656,27 @@ return; } + if (dotNetStringFormat) { + var dotNetActual; + try { + dotNetActual = dotNetStringFormat.apply(null, args); + } catch (e) { + registerTestResult(message, ".NET: " + e); + return; + } + var sameResult = dotNetActual === actual; + if (!sameResult) { + registerTestResult(message, String.format(".NET: {0}, actual: {1}", stringify(dotNetActual), stringify(actual))); + return; + } + } + message = String.format("{0,-25} {1}", formatString, actual); assert.areEqual(expected, actual, message); } }; - if (typeof window === 'undefined') { - // node.js TBD - String.format = require('../src/sffjs'); - } - var s = String.format; var formats = 0; String.format = function() { @@ -617,8 +689,14 @@ runTests(test); var endTime = new Date().valueOf(); test.print(); - var timeResult = document.createElement("div"); - timeResult.innerHTML = String.format("Executed in {0:0} ms, mean {1:0.00} µs/format", endTime - startTime, 1000 * (endTime - startTime) / formats); - document.body.appendChild(timeResult); - document.body.appendChild(timeResult); + + var timeResultString = String.format("Executed in {0:0} ms, mean {1:0.00} µs/format", endTime - startTime, 1000 * (endTime - startTime) / formats); + + if (browser) { + var timeResult = document.createElement("div"); + timeResult.innerHTML = timeResultString; + document.body.appendChild(timeResult); + } else { + console.log(timeResultString); + } })(); \ No newline at end of file