UNPKG

read-vietnamese-number

Version:

Đọc số thành chữ trong Tiếng Việt

157 lines (156 loc) 5.71 kB
import { InvalidNumberError, NotEnoughUnitError, } from './type.js'; import { splitToDigits, trimLeft, trimRight, validateNumber } from './util.js'; export function readLastTwoDigits(config, b, c, readZeroTen) { const output = []; switch (b) { case 0: { if (readZeroTen && c !== 0) { output.push(config.digits[b]); } output.push(config.digits[c]); break; } case 1: { output.push(config.tenText); if (c === 5) { output.push(config.fiveToneText); } else if (c !== 0) { output.push(config.digits[c]); } break; } default: { output.push(config.digits[b], config.tenToneText); if (c === 1) { output.push(config.oneToneText); } else if (c === 4) { output.push(config.fourToneText); } else if (c === 5) { output.push(config.fiveToneText); } else if (c !== 0) { output.push(config.digits[c]); } break; } } return output; } export function readThreeDigits(config, a, b, c, readZeroHundred) { const output = []; const hasHundred = a !== 0 || readZeroHundred; if (hasHundred) { output.push(config.digits[a], config.hundredText); } if (hasHundred && b === 0) { if (c === 0) { return output; } output.push(config.oddText); } output.push(...readLastTwoDigits(config, b, c, false)); return output; } export function removeThousandsSeparators(config, number) { const regex = new RegExp(config.thousandSign, 'g'); return number.replace(regex, ''); } export function trimRedundantZeros(config, number) { return number.includes(config.pointSign) ? trimLeft(trimRight(number, config.filledDigit), config.filledDigit) : trimLeft(number, config.filledDigit); } export function addLeadingZerosToFitPeriod(config, number) { const newLength = Math.ceil(number.length / config.periodSize) * config.periodSize; return number.padStart(newLength, config.filledDigit); } export function zipIntegralPeriods(config, digits) { const output = []; const periodCount = Math.ceil(digits.length / config.periodSize); for (let i = 0; i < periodCount; i++) { const [a, b, c] = digits.slice(i * config.periodSize, (i + 1) * config.periodSize); output.push([a, b, c]); } return output; } export function parseNumberData(config, number) { let numberString = removeThousandsSeparators(config, number); const isNegative = numberString.startsWith(config.negativeSign); numberString = isNegative ? numberString.substring(config.negativeSign.length) : numberString; numberString = trimRedundantZeros(config, numberString); const pointPos = numberString.indexOf(config.pointSign); let integralString = pointPos === -1 ? numberString : numberString.substring(0, pointPos); const fractionalString = pointPos === -1 ? '' : numberString.substring(pointPos + 1); integralString = addLeadingZerosToFitPeriod(config, integralString); const integralDigits = splitToDigits(integralString); const fractionalDigits = splitToDigits(fractionalString); if (integralDigits === null) { throw new InvalidNumberError('Invalid integral part'); } if (fractionalDigits === null) { throw new InvalidNumberError('Invalid fractional part'); } const integralPart = zipIntegralPeriods(config, integralDigits); if (integralPart.length === 0) { integralPart.push([0, 0, 0]); } else if (integralPart.length > config.units.length) { throw new NotEnoughUnitError('Unit not enough'); } const fractionalPart = fractionalDigits; return { isNegative, integralPart, fractionalPart }; } export function readIntegralPart(config, periods) { const output = []; const isSinglePeriod = periods.length === 1; for (const [index, period] of periods.entries()) { const isFirstPeriod = index === 0; const [a, b, c] = period; if (a !== 0 || b !== 0 || c !== 0 || isSinglePeriod) { output.push(...readThreeDigits(config, a, b, c, !isFirstPeriod), ...config.units[periods.length - 1 - index]); } } return output; } export function readFractionalPart(config, digits) { const output = []; switch (digits.length) { case 2: { const [b, c] = digits; output.push(...readLastTwoDigits(config, b, c, true)); break; } case 3: { const [a, b, c] = digits; output.push(...readThreeDigits(config, a, b, c, true)); break; } default: { for (const digit of digits) { output.push(config.digits[digit]); } break; } } return output; } export function readNumber(config, numberData) { const output = []; output.push(...readIntegralPart(config, numberData.integralPart)); if (numberData.fractionalPart.length !== 0) { output.push(config.pointText, ...readFractionalPart(config, numberData.fractionalPart)); } if (numberData.isNegative) { output.unshift(config.negativeText); } output.push(...config.unit); return output.join(config.separator); } export function doReadNumber(config, number) { const validatedNumber = validateNumber(number); const numberData = parseNumberData(config, validatedNumber); return readNumber(config, numberData); }