messageformat-number-skeleton
Version:
A parser & formatter for ICU NumberFormat skeleton strings & patterns
129 lines (128 loc) • 4.25 kB
JavaScript
import { parseTokens } from './pattern-parser/parse-tokens.js';
import { parseNumberAsSkeleton } from './pattern-parser/number-as-skeleton.js';
import { PatternError } from './errors.js';
function handleAffix(affixTokens, res, currency, onError, isPrefix) {
let inFmt = false;
let str = '';
for (const token of affixTokens) {
switch (token.char) {
case '%':
res.unit = { style: token.style };
if (isPrefix)
inFmt = true;
else
str = '';
break;
case '¤':
if (!currency) {
const msg = `The ¤ pattern requires a currency`;
onError(new PatternError('¤', msg));
break;
}
res.unit = { style: 'currency', currency };
switch (token.currency) {
case 'iso-code':
res.unitWidth = 'unit-width-iso-code';
break;
case 'full-name':
res.unitWidth = 'unit-width-full-name';
break;
case 'narrow':
res.unitWidth = 'unit-width-narrow';
break;
}
if (isPrefix)
inFmt = true;
else
str = '';
break;
case '*':
// TODO
break;
case '+':
if (!inFmt)
str += '+';
break;
case "'":
if (!inFmt)
str += token.str;
break;
}
}
return str;
}
function getNegativeAffix(affixTokens, isPrefix) {
let inFmt = false;
let str = '';
for (const token of affixTokens) {
switch (token.char) {
case '%':
case '¤':
if (isPrefix)
inFmt = true;
else
str = '';
break;
case '-':
if (!inFmt)
str += '-';
break;
case "'":
if (!inFmt)
str += token.str;
break;
}
}
return str;
}
/**
* Parse an {@link
* http://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns |
* ICU NumberFormatter pattern} string into a {@link Skeleton} structure.
*
* @public
* @param src - The pattern string
* @param currency - If the pattern includes ¤ tokens, their skeleton
* representation requires a three-letter currency code.
* @param onError - Called when the parser encounters a syntax error. The
* function will still return a {@link Skeleton}, but it will be incomplete
* and/or inaccurate. If not defined, the error will be thrown instead.
*
* @remarks
* Unlike the skeleton parser, the pattern parser is not able to return partial
* results on error, and will instead throw. Output padding is not supported.
*
* @example
* ```js
* import { parseNumberPattern } from 'messageformat-number-skeleton'
*
* parseNumberPattern('#,##0.00 ¤', 'EUR', console.error)
* // {
* // group: 'group-auto',
* // precision: {
* // style: 'precision-fraction',
* // minFraction: 2,
* // maxFraction: 2
* // },
* // unit: { style: 'currency', currency: 'EUR' }
* // }
* ```
*/
export function parseNumberPattern(src, currency, onError = error => {
throw error;
}) {
const { tokens, negative } = parseTokens(src, onError);
const res = parseNumberAsSkeleton(tokens.number, onError);
const prefix = handleAffix(tokens.prefix, res, currency, onError, true);
const suffix = handleAffix(tokens.suffix, res, currency, onError, false);
if (negative) {
const negPrefix = getNegativeAffix(negative.prefix, true);
const negSuffix = getNegativeAffix(negative.suffix, false);
res.affix = { pos: [prefix, suffix], neg: [negPrefix, negSuffix] };
res.sign = 'sign-never';
}
else if (prefix || suffix) {
res.affix = { pos: [prefix, suffix] };
}
return res;
}