Spaces:
Paused
Paused
Upload src/viNumberFix.js
Browse files- src/viNumberFix.js +94 -49
src/viNumberFix.js
CHANGED
|
@@ -29,37 +29,63 @@ var DIGIT = { "khong": 0, "mot": 1, "hai": 2, "ba": 3, "bon": 4, "nam": 5, "sau"
|
|
| 29 |
// Scale words → multiplier
|
| 30 |
var SCALE = { "muoi": 10, "tram": 100, "nghin": 1000, "ngan": 1000, "trieu": 1000000, "ty": 1000000000 };
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
var result = 0;
|
| 34 |
var i = 0;
|
| 35 |
|
| 36 |
while (i < tokens.length) {
|
| 37 |
var base = toBaseForm(tokens[i]);
|
| 38 |
-
var next = i + 1 < tokens.length ? toBaseForm(tokens[i + 1]) :
|
| 39 |
-
var nextNext = i + 2 < tokens.length ? toBaseForm(tokens[i + 2]) : "";
|
| 40 |
|
| 41 |
-
//
|
| 42 |
-
if (
|
| 43 |
-
if (
|
| 44 |
-
|
|
|
|
|
|
|
| 45 |
i++;
|
|
|
|
| 46 |
continue;
|
| 47 |
}
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
// "muoi mot" = 11
|
| 58 |
if (base === "muoi" && (next === "mot" || next === "mơ")) {
|
| 59 |
-
return 11;
|
| 60 |
}
|
| 61 |
|
| 62 |
-
// "X muoi
|
| 63 |
if (next === "muoi" && DIGIT[base] !== undefined) {
|
| 64 |
var val = DIGIT[base] * 10;
|
| 65 |
i += 2;
|
|
@@ -68,20 +94,10 @@ function parseNumberTokens(tokens) {
|
|
| 68 |
if (ones === "mot" || ones === "mơ") val += 1;
|
| 69 |
else if (DIGIT[ones] !== undefined) val += DIGIT[ones];
|
| 70 |
}
|
| 71 |
-
return val;
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
// "X muoi" standalone = X0
|
| 75 |
-
if (next === "muoi" && DIGIT[base] !== undefined) {
|
| 76 |
-
return DIGIT[base] * 10;
|
| 77 |
}
|
| 78 |
|
| 79 |
-
//
|
| 80 |
-
if (base === "muoi") {
|
| 81 |
-
return 10;
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
// "X tram Y" = X00 + Y
|
| 85 |
if (next === "tram" && DIGIT[base] !== undefined) {
|
| 86 |
var val = DIGIT[base] * 100;
|
| 87 |
i += 2;
|
|
@@ -89,18 +105,41 @@ function parseNumberTokens(tokens) {
|
|
| 89 |
val += DIGIT[toBaseForm(tokens[i])];
|
| 90 |
i++;
|
| 91 |
}
|
| 92 |
-
return val;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
}
|
| 94 |
|
| 95 |
// Single digit
|
| 96 |
if (DIGIT[base] !== undefined) {
|
| 97 |
-
return DIGIT[base];
|
| 98 |
}
|
| 99 |
|
| 100 |
i++;
|
| 101 |
}
|
| 102 |
|
| 103 |
-
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
}
|
| 105 |
|
| 106 |
/**
|
|
@@ -119,38 +158,44 @@ export function normalizeVietnameseText(text) {
|
|
| 119 |
|
| 120 |
if (!hasNum) return text.replace(/\s+/g, " ").trim();
|
| 121 |
|
| 122 |
-
|
|
|
|
| 123 |
var output = [];
|
| 124 |
var i = 0;
|
| 125 |
|
| 126 |
while (i < words.length) {
|
| 127 |
var w = words[i].trim().replace(/[,.!?;:)]+$/, "");
|
|
|
|
| 128 |
var base = toBaseForm(w);
|
| 129 |
-
var remaining = words.slice(i).map(function(t) { return t.trim().replace(/[,.!?;:)]+$/, ""); });
|
| 130 |
|
| 131 |
-
//
|
| 132 |
-
var
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
var
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
}
|
| 145 |
-
i += consumed;
|
| 146 |
-
continue;
|
| 147 |
}
|
| 148 |
|
| 149 |
output.push(words[i]);
|
| 150 |
i++;
|
| 151 |
}
|
| 152 |
|
| 153 |
-
return output.join("
|
| 154 |
}
|
| 155 |
|
| 156 |
/**
|
|
|
|
| 29 |
// Scale words → multiplier
|
| 30 |
var SCALE = { "muoi": 10, "tram": 100, "nghin": 1000, "ngan": 1000, "trieu": 1000000, "ty": 1000000000 };
|
| 31 |
|
| 32 |
+
/**
|
| 33 |
+
* Parse Vietnamese number phrases to digits.
|
| 34 |
+
* Handles: mot, hai, ba... (1-9)
|
| 35 |
+
* mot muoi, hai muoi... (10-90)
|
| 36 |
+
* mot muoi mot, hai muoi hai... (11-99)
|
| 37 |
+
* mot tram, hai tram... (100-900)
|
| 38 |
+
* mot nghin, hai nghin... (1000-2000000...)
|
| 39 |
+
*/
|
| 40 |
+
function parseVietnameseNumber(text) {
|
| 41 |
+
var tokens = text.toLowerCase().split(/[\s,]+/).map(function(t) {
|
| 42 |
+
return t.replace(/[,.!?;:)]+$/, "").replace(/^[(\[]+/, "");
|
| 43 |
+
});
|
| 44 |
+
|
| 45 |
var result = 0;
|
| 46 |
var i = 0;
|
| 47 |
|
| 48 |
while (i < tokens.length) {
|
| 49 |
var base = toBaseForm(tokens[i]);
|
| 50 |
+
var next = i + 1 < tokens.length ? toBaseForm(tokens[i + 1]) : null;
|
|
|
|
| 51 |
|
| 52 |
+
// Large scale words (tỉ/ty, triệu, nghìn/ngàn)
|
| 53 |
+
if (base === "ty" || base === "tỉ") {
|
| 54 |
+
if (next === "muoi" || next === "tram" || SCALE[next] !== undefined || DIGIT[next] !== undefined) {
|
| 55 |
+
// "hai trieu" = 2000000, "ba muoi lam trieu" = 3,500,000
|
| 56 |
+
var group = parseGroup(tokens.slice(i + 1));
|
| 57 |
+
result += (group || 1) * SCALE.ty;
|
| 58 |
i++;
|
| 59 |
+
while (i < tokens.length && (SCALE[toBaseForm(tokens[i])] || DIGIT[toBaseForm(tokens[i])])) i++;
|
| 60 |
continue;
|
| 61 |
}
|
| 62 |
+
result += SCALE.ty;
|
| 63 |
+
i++;
|
| 64 |
+
continue;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
if (base === "trieu" || base === "triệu") {
|
| 68 |
+
var group = parseGroup(tokens.slice(i + 1));
|
| 69 |
+
result += (group || 1) * SCALE.trieu;
|
| 70 |
+
i++;
|
| 71 |
+
while (i < tokens.length && SCALE[toBaseForm(tokens[i])] !== undefined && SCALE[toBaseForm(tokens[i])] < SCALE.trieu) i++;
|
| 72 |
+
continue;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
if (base === "nghin" || base === "ngan" || base === "nghìn" || base === "ngàn") {
|
| 76 |
+
var group = parseGroup(tokens.slice(i + 1));
|
| 77 |
+
result += (group || 1) * SCALE.nghin;
|
| 78 |
+
i++;
|
| 79 |
+
while (i < tokens.length && SCALE[toBaseForm(tokens[i])] !== undefined && SCALE[toBaseForm(tokens[i])] < SCALE.nghin) i++;
|
| 80 |
+
continue;
|
| 81 |
}
|
| 82 |
|
| 83 |
// "muoi mot" = 11
|
| 84 |
if (base === "muoi" && (next === "mot" || next === "mơ")) {
|
| 85 |
+
return result + 11;
|
| 86 |
}
|
| 87 |
|
| 88 |
+
// "X muoi" patterns (20-90)
|
| 89 |
if (next === "muoi" && DIGIT[base] !== undefined) {
|
| 90 |
var val = DIGIT[base] * 10;
|
| 91 |
i += 2;
|
|
|
|
| 94 |
if (ones === "mot" || ones === "mơ") val += 1;
|
| 95 |
else if (DIGIT[ones] !== undefined) val += DIGIT[ones];
|
| 96 |
}
|
| 97 |
+
return result + val;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
}
|
| 99 |
|
| 100 |
+
// "X tram" patterns (100-900)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
if (next === "tram" && DIGIT[base] !== undefined) {
|
| 102 |
var val = DIGIT[base] * 100;
|
| 103 |
i += 2;
|
|
|
|
| 105 |
val += DIGIT[toBaseForm(tokens[i])];
|
| 106 |
i++;
|
| 107 |
}
|
| 108 |
+
return result + val;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Just "muoi" = 10
|
| 112 |
+
if (base === "muoi") {
|
| 113 |
+
return result + 10;
|
| 114 |
}
|
| 115 |
|
| 116 |
// Single digit
|
| 117 |
if (DIGIT[base] !== undefined) {
|
| 118 |
+
return result + DIGIT[base];
|
| 119 |
}
|
| 120 |
|
| 121 |
i++;
|
| 122 |
}
|
| 123 |
|
| 124 |
+
return result > 0 ? result : null;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
// Parse a group like "ba muoi" or "hai tram"
|
| 128 |
+
function parseGroup(tokens) {
|
| 129 |
+
if (!tokens || tokens.length === 0) return null;
|
| 130 |
+
var val = 0;
|
| 131 |
+
for (var i = 0; i < tokens.length; i++) {
|
| 132 |
+
var base = toBaseForm(tokens[i]);
|
| 133 |
+
if (SCALE[base] !== undefined && SCALE[base] >= 10) {
|
| 134 |
+
// Scale multipliers accumulate
|
| 135 |
+
if (i > 0 && DIGIT[toBaseForm(tokens[i-1])] !== undefined) {
|
| 136 |
+
val += DIGIT[toBaseForm(tokens[i-1])] * SCALE[base];
|
| 137 |
+
} else if (SCALE[base] === 10) val += 10;
|
| 138 |
+
} else if (DIGIT[base] !== undefined) {
|
| 139 |
+
val += DIGIT[base];
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
return val > 0 ? val : null;
|
| 143 |
}
|
| 144 |
|
| 145 |
/**
|
|
|
|
| 158 |
|
| 159 |
if (!hasNum) return text.replace(/\s+/g, " ").trim();
|
| 160 |
|
| 161 |
+
// Try to find and replace number patterns
|
| 162 |
+
var words = text.split(/([\s,]+)/);
|
| 163 |
var output = [];
|
| 164 |
var i = 0;
|
| 165 |
|
| 166 |
while (i < words.length) {
|
| 167 |
var w = words[i].trim().replace(/[,.!?;:)]+$/, "");
|
| 168 |
+
if (!w) { output.push(words[i]); i++; continue; }
|
| 169 |
var base = toBaseForm(w);
|
|
|
|
| 170 |
|
| 171 |
+
// Check if this starts a number phrase
|
| 172 |
+
var next = i + 1 < words.length ? toBaseForm(words[i + 1].trim().replace(/[,.!?;:)]+$/, "")) : "";
|
| 173 |
+
|
| 174 |
+
// Try to parse from this position
|
| 175 |
+
var remaining = [];
|
| 176 |
+
for (var j = i; j < words.length; j++) {
|
| 177 |
+
var wt = words[j].trim().replace(/[,.!?;:)]+$/, "");
|
| 178 |
+
if (wt && (DIGIT[toBaseForm(wt)] !== undefined || SCALE[toBaseForm(wt)] !== undefined)) {
|
| 179 |
+
remaining.push(wt);
|
| 180 |
+
} else break;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
if (remaining.length >= 1) {
|
| 184 |
+
var parsed = parseVietnameseNumber(remaining.join(" "));
|
| 185 |
+
if (parsed !== null) {
|
| 186 |
+
output.push(String(parsed));
|
| 187 |
+
i += remaining.length;
|
| 188 |
+
// Skip any whitespace tokens
|
| 189 |
+
while (i < words.length && !words[i].trim()) i++;
|
| 190 |
+
continue;
|
| 191 |
}
|
|
|
|
|
|
|
| 192 |
}
|
| 193 |
|
| 194 |
output.push(words[i]);
|
| 195 |
i++;
|
| 196 |
}
|
| 197 |
|
| 198 |
+
return output.join("").replace(/\s+/g, " ").trim();
|
| 199 |
}
|
| 200 |
|
| 201 |
/**
|