chinese-numbers-to-arabic
Version:
Convert Chinese numbers into their Arabic Format
268 lines (260 loc) • 7.71 kB
JavaScript
// src/utils/chars.ts
var cnNumbers = {
\u96F6: 0,
"\u3007": 0,
"\uFF10": 0,
\u6D1E: 0,
\u58F9: 1,
\u4E00: 1,
\u5E7A: 1,
"\uFF11": 1,
\u8CB3: 2,
\u8D30: 2,
\u4E8C: 2,
\u4E24: 2,
\u5169: 2,
\u5006: 2,
\u4FE9: 2,
"\uFF12": 2,
// Outdated financial number variant which is commonly used as a normal word,
// which causes false positives:
// '參': 3,
\u53C1: 3,
\u4E09: 3,
\u4EE8: 3,
"\uFF13": 3,
\u8086: 4,
\u56DB: 4,
"\uFF14": 4,
\u4F0D: 5,
\u4E94: 5,
"\uFF15": 5,
\u9678: 6,
\u9646: 6,
\u516D: 6,
"\uFF16": 6,
\u67D2: 7,
\u4E03: 7,
\u62D0: 7,
"\uFF17": 7,
\u634C: 8,
\u516B: 8,
"\uFF18": 8,
\u7396: 9,
\u4E5D: 9,
\u52FE: 9,
"\uFF19": 9,
\u62FE: "*10",
\u5341: "*10",
\u5EFF: "*20",
\u5345: "*30",
\u534C: "*40",
\u4F70: "*100",
\u767E: "*100",
\u7695: "*200",
\u4EDF: "*1000",
\u5343: "*1000",
\u842C: "*10000",
\u4E07: "*10000",
\u5104: "*100000000",
\u4EBF: "*100000000"
};
var characters = Object.keys(cnNumbers);
var characterList = characters.join("");
var afterManMultipliers = ["\u842C", "\u4E07", "\u5104", "\u4EBF"];
var NUMBER_IN_STRING_REGEX = new RegExp(
`(?![0]+)(?:(?:\\d+(?:[.,\\s]\\d+)*)*)(?:[\\d${characterList}]+)`,
"gu"
);
var reverseMultipliers = {
"10": "\u5341",
"100": "\u767E",
"1000": "\u5343",
"10000": "\u842C"
};
var SINGLE_ARABIC_NUMBER_REGEX = /\d/gu;
// src/utils/addMissingUnits.ts
var addMissingUnits = (str) => {
const characters2 = [...str];
let result = "";
const reverse = reverseMultipliers;
characters2.forEach((character, i) => {
if (i === 0) {
result += character;
} else {
const arabic = Number.isNaN(character) ? cnNumbers[character] : Number.parseInt(character);
const previousNumber = cnNumbers[characters2[i - 1]] || characters2[i - 1];
const previousCharacterAsMultiplier = reverse[previousNumber.toString().replace("*", "")] ? previousNumber.toString().replace("*", "") : void 0;
const nextCharacterArabic = (cnNumbers[characters2[i + 1]] || 0).toString().replace("*", "");
if (
// not a multiplier like '*100':
typeof arabic === "number" && // in the 1-9 range:
arabic > 0 && arabic < 10 && // previous character is 10, 100, 1000 or 10000:
previousCharacterAsMultiplier !== void 0 && // e.g. 1000 < 10000 for 8千3萬, or it's the last character in string:
(Number.parseInt(previousCharacterAsMultiplier) < Number.parseInt(nextCharacterArabic) || characters2[i + 1] === void 0) && // For numbers like 十五, there are no other units to be appended at the end
previousCharacterAsMultiplier !== "10"
) {
const oneOrderSmaller = (Number.parseInt(previousCharacterAsMultiplier) / 10).toString();
const missingMultiplier = reverse[oneOrderSmaller];
result += character + missingMultiplier;
} else {
result += character;
}
}
});
return result;
};
// src/utils/isArabicNumber.ts
var isArabicNumber = (character) => {
if (!character || character.length !== 1) {
throw new Error("Function isArabicNumber expects exactly one character.");
}
const arabicNumbers = [
"0123456789\uFF10",
"\uFF11",
"\uFF12",
"\uFF13",
"\uFF14",
"\uFF15",
"\uFF16",
"\uFF17",
"\uFF18",
"\uFF19"
];
return arabicNumbers.includes(character);
};
// src/utils/isChineseNumber.ts
var isChineseNumber = (character) => {
if (!character || character.length !== 1) {
throw new Error("Function isChineseNumber expects exactly one character.");
}
return !!cnNumbers[character];
};
// src/utils/sourceStringEndsWithAfterManNumber.ts
var sourceStringEndsWithAfterManNumber = (str) => {
if (!str) {
return str;
}
const characters2 = [...str].reverse();
for (const character of characters2) {
if (SINGLE_ARABIC_NUMBER_REGEX.test(character)) ;
if (afterManMultipliers.includes(character)) {
return character;
}
}
return null;
};
// src/modules/toInteger.ts
var toInteger = (source) => {
let result = 0;
let pairs = [];
let str = source.toString();
let currentPair = [];
let leadingNumber = Number.parseFloat(str);
if (!str || str === "") {
throw new Error("Empty strings cannot be converted.");
}
if (Number.parseFloat(str).toString() === str.trim()) {
return Number.parseFloat(str) || 0;
}
let atLeastOneChineseNumber = false;
for (const character of str) {
if (characters.includes(character)) {
atLeastOneChineseNumber = true;
break;
}
}
if (!atLeastOneChineseNumber) {
return Number.parseFloat(str) || 0;
}
str = str.replace(/[,\s]/gu, "");
str = addMissingUnits(str);
const maanLikeCharacterAtTheEnd = sourceStringEndsWithAfterManNumber(str);
if (maanLikeCharacterAtTheEnd) {
const maanLocation = str.lastIndexOf(maanLikeCharacterAtTheEnd);
const stringBeforeMaan = str.slice(0, Math.max(0, maanLocation));
let convertedNumberBeforeMaan;
if (stringBeforeMaan == null ? void 0 : stringBeforeMaan.trim()) {
convertedNumberBeforeMaan = toInteger(stringBeforeMaan);
} else {
convertedNumberBeforeMaan = 1;
}
str = convertedNumberBeforeMaan.toString() + str.slice(maanLocation);
leadingNumber = Number.parseFloat(str);
if (!Number.isNaN(leadingNumber)) {
str = str.replace(leadingNumber.toString(), "");
}
}
const len = str.length;
for (let i = 0; i < len; i++) {
if (SINGLE_ARABIC_NUMBER_REGEX.test(str[i])) {
currentPair.push(Number.parseInt(str[i]));
}
if (cnNumbers[str[i]]) {
let arabic = cnNumbers[str[i]];
if (typeof arabic === "number") {
if (currentPair.length > 0) {
const string = `${currentPair[0].toString()}${arabic}`.toString();
currentPair[0] = Number.parseInt(string);
} else {
currentPair.push(arabic);
}
} else {
const [action] = arabic;
arabic = Number.parseInt(arabic.replace("*", ""));
currentPair.push(arabic);
if (i === 0 && action === "*") {
currentPair.push(1);
pairs.push(currentPair);
currentPair = [];
} else {
if (currentPair.length === 2) {
pairs.push(currentPair);
currentPair = [];
} else {
if (afterManMultipliers.includes(str[i])) {
let numbersSoFar = 0;
pairs.forEach((pair) => {
numbersSoFar += pair[0] * pair[1];
});
if (!Number.isNaN(leadingNumber)) {
numbersSoFar *= leadingNumber;
leadingNumber = Number.NaN;
}
pairs = [[numbersSoFar, arabic]];
currentPair = [];
} else {
currentPair.push(1);
pairs.push(currentPair);
currentPair = [];
}
}
}
}
}
}
if (currentPair.length === 1) {
currentPair.push(1);
pairs.push(currentPair);
}
if (pairs.length > 0 && !Number.isNaN(leadingNumber)) {
pairs[0][0] *= leadingNumber;
}
pairs.forEach((pair) => {
result += pair[0] * pair[1];
});
return result;
};
// src/modules/toArabicString.ts
var toArabicString = (source, minimumCharactersInNumber = 1) => {
if (typeof source !== "string") {
return source;
}
return source.replace(NUMBER_IN_STRING_REGEX, (match) => {
if (match.length >= minimumCharactersInNumber) {
return toInteger(match.replace(/[\s,_]/gu, "")).toString();
}
return match;
});
};
export { addMissingUnits, isArabicNumber, isChineseNumber, toArabicString, toInteger };