From 5d5fd6292a1383b6eb3916ec6e7f961de02423d8 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Fri, 19 May 2017 20:00:19 +0300 Subject: [PATCH] chore(i18n): fix parser for currency patterns without fraction digits Previously, it was assumed that all currency pattern would have fraction digits. However, in [closure-library@b9155d5][1] the `agq_CM` locale was modified to have such a pattern (namely `#,##0\u00A4`). This commit modifies the parser implementation to account for pattern without a decimal point (and thus no fraction digits). [1]: https://github.com/google/closure-library/commit/b9155d5966a228cb33f367c30c275c833b30e3ff#diff-02793124214ad0470ccea6f86b90d786R711 --- i18n/spec/parserSpec.js | 50 ++++++++++++++++++++++++++++++++++++++++- i18n/src/parser.js | 34 +++++++++++++++++++++++----- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/i18n/spec/parserSpec.js b/i18n/spec/parserSpec.js index f383122a057a..c26e49e9b85c 100644 --- a/i18n/spec/parserSpec.js +++ b/i18n/spec/parserSpec.js @@ -1,6 +1,43 @@ 'use strict'; -var parsePattern = require('../src/parser.js').parsePattern; +var parser = require('../src/parser'); +var ensureDecimalSep = parser.ensureDecimalSep; +var parsePattern = parser.parsePattern; + +describe('ensureDecimalSep', function() { + it('should leave patterns with DECIMAL_SEP untouched', function() { + [ + '#,##0.00', + '$#,##0.00', + '#,##0.00$', + '$0.00', + '0.00$', + '0.0', + '#,##0.', + '0.' + ].forEach(function(pattern) { + expect(ensureDecimalSep(pattern)).toBe(pattern); + }); + }); + + it('should add a DECIMAL_SEP in patterns that don\'t have one (after the last ZERO)', function() { + var patterns = { + '#,##000': '#,##000.', + '$#,#0#00': '$#,#0#00.', + '#,##000$': '#,##000.$', + '$000': '$000.', + '000$': '000.$', + '00': '00.', + '#,##0': '#,##0.', + '0': '0.' + }; + + Object.keys(patterns).forEach(function(input) { + var output = patterns[input]; + expect(ensureDecimalSep(input)).toBe(output); + }); + }); +}); describe('parsePattern', function() { function parseAndExpect(pattern, pp, np, ps, ns, mii, mif, maf, g, lg) { @@ -28,6 +65,11 @@ describe('parsePattern', function() { '', '\u202A-', '', '\u202C', 1, 0, 3, 3, 3); parseAndExpect('#0.###;#0.###-', '', '', '', '-', 1, 0, 3, 0, 0); + // Even patterns without a DECIMAL_SEP + parseAndExpect('#,##0', '', '-', '', '', 1, 0, 0, 3, 3); + parseAndExpect('+#,##0', '+', '-+', '', '', 1, 0, 0, 3, 3); + parseAndExpect('#,#0;+#,#0', '', '+', '', '', 1, 0, 0, 2, 2); + parseAndExpect('#,##,##0+;(#,##,##0)', '', '(', '+', ')', 1, 0, 0, 2, 3); }); it('should parse CURRENCY patterns', function() { @@ -51,5 +93,11 @@ describe('parsePattern', function() { parseAndExpect('\u00A4 #,##0.00;\u00A4 #,##0.00-', '\u00A4 ', '\u00A4 ', '', '-', 1, 2, 2, 3, 3); parseAndExpect('\u00A4 #,##,##0.00', '\u00A4 ', '-\u00A4 ', '', '', 1, 2, 2, 2, 3); + + // Even patterns without a DECIMAL_SEP + parseAndExpect('#,##0 \u00A4', '', '-', ' \u00A4', ' \u00A4', 1, 0, 0, 3, 3); + parseAndExpect('\u00A4 #,##0', '\u00A4 ', '-\u00A4 ', '', '', 1, 0, 0, 3, 3); + parseAndExpect('#,#0 \u00A4;+#,#0\u00A4', '', '+', ' \u00A4', '\u00A4', 1, 0, 0, 2, 2); + parseAndExpect('\u00A4 #,##,##0;(\u00A4 #,##,##0)', '\u00A4 ', '(\u00A4 ', '', ')', 1, 0, 0, 2, 3); }); }); diff --git a/i18n/src/parser.js b/i18n/src/parser.js index a33a67c39ee3..2c289be2550c 100644 --- a/i18n/src/parser.js +++ b/i18n/src/parser.js @@ -4,13 +4,26 @@ * A simple parser to parse a number format into a pattern object */ +exports.ensureDecimalSep = ensureDecimalSep; exports.parsePattern = parsePattern; -var PATTERN_SEP = ';', - DECIMAL_SEP = '.', - GROUP_SEP = ',', - ZERO = '0', - DIGIT = '#'; +var PATTERN_SEP = ';', + DECIMAL_SEP = '.', + GROUP_SEP = ',', + DIGIT = '#', + ZERO = '0', + LAST_ZERO_RE = /^(.*0)(?!0)(.*)$/; + +/** + * Helper function for parser. + * Ensures that `pattern` (e.g #,##0.###) contains a DECIMAL_SEP, which is necessary for further + * parsing. If a pattern does not include one, it is added after the last ZERO (which is the last + * thing before the `posSuf` - if any). + */ +function ensureDecimalSep(pattern) { + return (pattern.indexOf(DECIMAL_SEP) !== -1) + ? pattern : pattern.replace(LAST_ZERO_RE, '$1' + DECIMAL_SEP + '$2'); +} /** * main function for parser @@ -33,7 +46,16 @@ function parsePattern(pattern) { positive = patternParts[0], negative = patternParts[1]; - var positiveParts = positive.split(DECIMAL_SEP), + // The parsing logic further below assumes that there will always be a DECIMAL_SEP in the pattern. + // However, some locales (e.g. agq_CM) do not have one, thus we add one after the last ZERO + // (which is the last thing before the `posSuf` - if any). Since there will be no ZEROs or DIGITs + // after DECIMAL_SEP, `min/maxFrac` will remain 0 (which is accurate - no fraction digits) and + // `posSuf` will be processed correctly. + // For example `#,##0$` would be converted to `#,##0.$`, which would (correctly) result in: + // `minFrac: 0`, `maxFrac: 0`, `posSuf: '$'` + // Note: We shouldn't modify `positive` directly, because it is used to parse the negative part.) + var positiveWithDecimalSep = ensureDecimalSep(positive), + positiveParts = positiveWithDecimalSep.split(DECIMAL_SEP), integer = positiveParts[0], fraction = positiveParts[1];