diff --git a/package.json b/package.json index edff167..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": { diff --git a/src/stringformat.js b/src/stringformat.js index 99a3932..13bd1ce 100644 --- a/src/stringformat.js +++ b/src/stringformat.js @@ -174,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]]; @@ -212,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. @@ -741,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]) : + return innerArgs[5] ? "{" : // 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]) : + innerArgs[6] ? "}" : // Valid format item - unescapeBraces(innerArgs[1], 1) + - processFormatItem(innerArgs[3], innerArgs[4], innerArgs[5], outerArgs) + - unescapeBraces(innerArgs[6], 1); + processFormatItem( + innerArgs[2], + innerArgs[3], + // Format string might contain escaped braces + innerArgs[4] && innerArgs[4].replace(/\}\}/g, "}").replace(/\{\{/g, "{"), + outerArgs); }); } diff --git a/src/stringformat.tests.js b/src/stringformat.tests.js index 08ba203..ad043e6 100644 --- a/src/stringformat.tests.js +++ b/src/stringformat.tests.js @@ -37,14 +37,20 @@ sffjs.setCulture("en"); var testObject = { - authors: [{ + a: "b", + authors: [ + { firstname: "John", lastname: "Doe", - phonenumbers: [{ + phonenumbers: [ + { + home: "012", home: "345" - }], + } + ], age: 27 - }] + } + ] }; var undefined; @@ -53,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"); @@ -62,9 +68,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"); @@ -78,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); @@ -87,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); @@ -352,22 +372,12 @@ 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" - }); - sffjs.registerCulture({ - name: "__LANG-REGION" - }); - sffjs.registerCulture({ - name: "__LANG2" - }); - sffjs.registerCulture({ - name: "__LANG3-region" - }); + sffjs.registerCulture({ name: "__LANG" }); + sffjs.registerCulture({ name: "__LANG-REGION" }); + sffjs.registerCulture({ name: "__LANG2" }); + sffjs.registerCulture({ name: "__LANG3-region" }); sffjs.setCulture(""); assert.areEqual("", sffjs.LC.name, "Invariant culture"); @@ -387,14 +397,13 @@ sffjs.setCulture("__LANG3"); assert.areEqual("", sffjs.LC.name, "Non-existing neutral"); - sffjs.registerCulture({ - name: "__Lang3" - }); + sffjs.registerCulture({ name: "__Lang3" }); assert.areEqual("__Lang3", sffjs.LC.name, "Delayed neutral"); sffjs.setCulture(""); } + function Test() { var t = this;