Skip to content

Commit

Permalink
chore(i18n): fix parser for currency patterns without fraction digits
Browse files Browse the repository at this point in the history
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]: google/closure-library@b9155d5#diff-02793124214ad0470ccea6f86b90d786R711
  • Loading branch information
gkalpak committed Jun 6, 2017
1 parent 7fbbacc commit 5d5fd62
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 7 deletions.
50 changes: 49 additions & 1 deletion i18n/spec/parserSpec.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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() {
Expand All @@ -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);
});
});
34 changes: 28 additions & 6 deletions i18n/src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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];

Expand Down

0 comments on commit 5d5fd62

Please sign in to comment.