urdu-text-utils 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/index.cjs +804 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +245 -0
- package/dist/index.d.ts +245 -0
- package/dist/index.js +777 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/chars.ts
|
|
4
|
+
var DIACRITIC_RANGES = "\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06ED";
|
|
5
|
+
var DIACRITICS_RE = new RegExp(`[${DIACRITIC_RANGES}]`, "gu");
|
|
6
|
+
var TATWEEL_RE = /ـ/gu;
|
|
7
|
+
var INVISIBLE_RE = /[----]/gu;
|
|
8
|
+
var ZWNJ_RE = //gu;
|
|
9
|
+
var ARABIC_LETTER_RANGES = "\\u0620-\\u063F\\u0641-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06EE\\u06EF\\u06FA-\\u06FF\\u0750-\\u077F\\u08A0-\\u08FF\\uFB50-\\uFDFF\\uFE70-\\uFEFC";
|
|
10
|
+
var ARABIC_LETTER_RE = new RegExp(`[${ARABIC_LETTER_RANGES}]`, "u");
|
|
11
|
+
var ANY_LETTER_RE_G = /\p{L}/gu;
|
|
12
|
+
var URDU_DIGITS = "\u06F0\u06F1\u06F2\u06F3\u06F4\u06F5\u06F6\u06F7\u06F8\u06F9";
|
|
13
|
+
var ARABIC_INDIC_DIGITS = "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669";
|
|
14
|
+
var ASCII_DIGITS = "0123456789";
|
|
15
|
+
var ANY_DIGIT_RE_G = new RegExp(`[${ASCII_DIGITS}${URDU_DIGITS}${ARABIC_INDIC_DIGITS}]`, "gu");
|
|
16
|
+
var SENTENCE_SPLIT_RE = /[۔؟?!.…]+/u;
|
|
17
|
+
var WORD_SPLIT_RE = /[\s،؛؟۔٫٬…!-\/:-@\[-`{-~‐-‧«»‘’“”]+/u;
|
|
18
|
+
|
|
19
|
+
// src/normalize.ts
|
|
20
|
+
var URDU_YEH = "\u06CC";
|
|
21
|
+
var URDU_KEHEH = "\u06A9";
|
|
22
|
+
var URDU_HEH_GOAL = "\u06C1";
|
|
23
|
+
var ALEF = "\u0627";
|
|
24
|
+
var WAW = "\u0648";
|
|
25
|
+
var LETTER_MAP = {
|
|
26
|
+
// yeh family -> ی. Note ے (U+06D2 bari ye) is a distinct letter and is preserved.
|
|
27
|
+
"\u064A": URDU_YEH,
|
|
28
|
+
// ي arabic yeh
|
|
29
|
+
"\u0649": URDU_YEH,
|
|
30
|
+
// ى alef maksura
|
|
31
|
+
"\u06CD": URDU_YEH,
|
|
32
|
+
// ۍ yeh with tail
|
|
33
|
+
"\u06D0": URDU_YEH,
|
|
34
|
+
// ې pashto e
|
|
35
|
+
"\u0620": URDU_YEH,
|
|
36
|
+
// ؠ kashmiri yeh
|
|
37
|
+
// kaf family -> ک
|
|
38
|
+
"\u0643": URDU_KEHEH,
|
|
39
|
+
// ك arabic kaf
|
|
40
|
+
"\u06AA": URDU_KEHEH,
|
|
41
|
+
// ڪ swash kaf
|
|
42
|
+
// heh family -> ہ. ھ (U+06BE do-chashmi heh) is a distinct letter and is preserved.
|
|
43
|
+
"\u0647": URDU_HEH_GOAL,
|
|
44
|
+
// ه arabic heh
|
|
45
|
+
"\u06C0": URDU_HEH_GOAL,
|
|
46
|
+
// ۀ heh with yeh above
|
|
47
|
+
"\u0629": URDU_HEH_GOAL,
|
|
48
|
+
// ة teh marbuta
|
|
49
|
+
"\u06C3": URDU_HEH_GOAL,
|
|
50
|
+
// ۃ teh marbuta goal
|
|
51
|
+
"\u06D5": URDU_HEH_GOAL,
|
|
52
|
+
// ە ae
|
|
53
|
+
// alef family -> ا. آ (U+0622 alef madda) is a real Urdu letter and is preserved.
|
|
54
|
+
"\u0623": ALEF,
|
|
55
|
+
// أ alef with hamza above
|
|
56
|
+
"\u0625": ALEF,
|
|
57
|
+
// إ alef with hamza below
|
|
58
|
+
"\u0671": ALEF,
|
|
59
|
+
// ٱ alef wasla
|
|
60
|
+
"\u0672": ALEF,
|
|
61
|
+
// ٲ alef with wavy hamza above
|
|
62
|
+
"\u0673": ALEF,
|
|
63
|
+
// ٳ alef with wavy hamza below
|
|
64
|
+
// waw family -> و. ؤ (U+0624) is preserved: it carries hamza meaningfully in Urdu.
|
|
65
|
+
"\u06CB": WAW,
|
|
66
|
+
// ۋ ve
|
|
67
|
+
"\u06C6": WAW,
|
|
68
|
+
// ۆ oe
|
|
69
|
+
"\u06C7": WAW
|
|
70
|
+
// ۇ u
|
|
71
|
+
};
|
|
72
|
+
var digitTargets = {
|
|
73
|
+
urdu: URDU_DIGITS,
|
|
74
|
+
english: ASCII_DIGITS,
|
|
75
|
+
arabic: ARABIC_INDIC_DIGITS
|
|
76
|
+
};
|
|
77
|
+
var PUNCTUATION_MAP = {
|
|
78
|
+
",": "\u060C",
|
|
79
|
+
// ،
|
|
80
|
+
";": "\u061B",
|
|
81
|
+
// ؛
|
|
82
|
+
"?": "\u061F"
|
|
83
|
+
// ؟
|
|
84
|
+
};
|
|
85
|
+
var DIGIT_LOOKUP = /* @__PURE__ */ new Map();
|
|
86
|
+
for (const set of [ASCII_DIGITS, URDU_DIGITS, ARABIC_INDIC_DIGITS]) {
|
|
87
|
+
for (let i = 0; i < 10; i++) DIGIT_LOOKUP.set(set[i], i);
|
|
88
|
+
}
|
|
89
|
+
function normalizeUrdu(input, options = {}) {
|
|
90
|
+
if (!input) return "";
|
|
91
|
+
const {
|
|
92
|
+
compatibility = true,
|
|
93
|
+
stripDiacritics = false,
|
|
94
|
+
stripTatweel = true,
|
|
95
|
+
stripZwnj = false,
|
|
96
|
+
collapseWhitespace = true,
|
|
97
|
+
digits = "preserve",
|
|
98
|
+
urduPunctuation = false
|
|
99
|
+
} = options;
|
|
100
|
+
let text = compatibility ? input.normalize("NFKC") : input.normalize("NFC");
|
|
101
|
+
text = text.replace(INVISIBLE_RE, "");
|
|
102
|
+
if (stripZwnj) text = text.replace(ZWNJ_RE, "");
|
|
103
|
+
if (stripTatweel) text = text.replace(TATWEEL_RE, "");
|
|
104
|
+
if (stripDiacritics) text = text.replace(DIACRITICS_RE, "");
|
|
105
|
+
const digitTarget = digits === "preserve" ? null : digitTargets[digits];
|
|
106
|
+
let out = "";
|
|
107
|
+
for (const ch of text) {
|
|
108
|
+
const mapped = LETTER_MAP[ch];
|
|
109
|
+
if (mapped !== void 0) {
|
|
110
|
+
out += mapped;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (digitTarget) {
|
|
114
|
+
const value = DIGIT_LOOKUP.get(ch);
|
|
115
|
+
if (value !== void 0) {
|
|
116
|
+
out += digitTarget[value];
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (urduPunctuation) {
|
|
121
|
+
const punct = PUNCTUATION_MAP[ch];
|
|
122
|
+
if (punct !== void 0) {
|
|
123
|
+
out += punct;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
out += ch;
|
|
128
|
+
}
|
|
129
|
+
if (collapseWhitespace) out = out.replace(/\s+/gu, " ").trim();
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function removeDiacritics(input) {
|
|
133
|
+
if (!input) return "";
|
|
134
|
+
return input.normalize("NFC").replace(DIACRITICS_RE, "");
|
|
135
|
+
}
|
|
136
|
+
function foldUrdu(input) {
|
|
137
|
+
return normalizeUrdu(input, {
|
|
138
|
+
stripDiacritics: true,
|
|
139
|
+
stripZwnj: true,
|
|
140
|
+
collapseWhitespace: true
|
|
141
|
+
}).toLowerCase();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/numbers.ts
|
|
145
|
+
var SETS = {
|
|
146
|
+
urdu: URDU_DIGITS,
|
|
147
|
+
english: ASCII_DIGITS,
|
|
148
|
+
arabic: ARABIC_INDIC_DIGITS
|
|
149
|
+
};
|
|
150
|
+
var VALUE_OF = /* @__PURE__ */ new Map();
|
|
151
|
+
for (const set of Object.values(SETS)) {
|
|
152
|
+
for (let i = 0; i < 10; i++) VALUE_OF.set(set[i], i);
|
|
153
|
+
}
|
|
154
|
+
function convert(input, target) {
|
|
155
|
+
if (!input) return "";
|
|
156
|
+
const digits = SETS[target];
|
|
157
|
+
let out = "";
|
|
158
|
+
for (const ch of input) {
|
|
159
|
+
const value = VALUE_OF.get(ch);
|
|
160
|
+
out += value === void 0 ? ch : digits[value];
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
function toUrduDigits(input) {
|
|
165
|
+
return convert(input, "urdu");
|
|
166
|
+
}
|
|
167
|
+
function toEnglishDigits(input) {
|
|
168
|
+
return convert(input, "english");
|
|
169
|
+
}
|
|
170
|
+
function toArabicIndicDigits(input) {
|
|
171
|
+
return convert(input, "arabic");
|
|
172
|
+
}
|
|
173
|
+
function convertNumbers(input, to = "urdu") {
|
|
174
|
+
return convert(input, to);
|
|
175
|
+
}
|
|
176
|
+
function parseUrduNumber(input) {
|
|
177
|
+
if (!input) return NaN;
|
|
178
|
+
const ascii = toEnglishDigits(input).replace(/[٬,\s]/gu, "").replace(/٫/gu, ".");
|
|
179
|
+
if (!/^[+-]?(\d+(\.\d*)?|\.\d+)$/u.test(ascii)) return NaN;
|
|
180
|
+
return Number(ascii);
|
|
181
|
+
}
|
|
182
|
+
var ONES = ["", "\u0627\u06CC\u06A9", "\u062F\u0648", "\u062A\u06CC\u0646", "\u0686\u0627\u0631", "\u067E\u0627\u0646\u0686", "\u0686\u06BE", "\u0633\u0627\u062A", "\u0622\u0679\u06BE", "\u0646\u0648"];
|
|
183
|
+
var TEENS_AND_TENS = {
|
|
184
|
+
10: "\u062F\u0633",
|
|
185
|
+
11: "\u06AF\u06CC\u0627\u0631\u06C1",
|
|
186
|
+
12: "\u0628\u0627\u0631\u06C1",
|
|
187
|
+
13: "\u062A\u06CC\u0631\u06C1",
|
|
188
|
+
14: "\u0686\u0648\u062F\u06C1",
|
|
189
|
+
15: "\u067E\u0646\u062F\u0631\u06C1",
|
|
190
|
+
16: "\u0633\u0648\u0644\u06C1",
|
|
191
|
+
17: "\u0633\u062A\u0631\u06C1",
|
|
192
|
+
18: "\u0627\u0679\u06BE\u0627\u0631\u06C1",
|
|
193
|
+
19: "\u0627\u0646\u06CC\u0633",
|
|
194
|
+
20: "\u0628\u06CC\u0633",
|
|
195
|
+
30: "\u062A\u06CC\u0633",
|
|
196
|
+
40: "\u0686\u0627\u0644\u06CC\u0633",
|
|
197
|
+
50: "\u067E\u0686\u0627\u0633",
|
|
198
|
+
60: "\u0633\u0627\u0679\u06BE",
|
|
199
|
+
70: "\u0633\u062A\u0631",
|
|
200
|
+
80: "\u0627\u0633\u06CC",
|
|
201
|
+
90: "\u0646\u0648\u06D2",
|
|
202
|
+
100: "\u0633\u0648"
|
|
203
|
+
};
|
|
204
|
+
function numberToUrduWords(value) {
|
|
205
|
+
if (!Number.isFinite(value)) return "";
|
|
206
|
+
if (!Number.isInteger(value)) throw new TypeError("numberToUrduWords expects an integer");
|
|
207
|
+
if (value < 0) return `\u0645\u0646\u0641\u06CC ${numberToUrduWords(-value)}`;
|
|
208
|
+
if (value === 0) return "\u0635\u0641\u0631";
|
|
209
|
+
const scales = [
|
|
210
|
+
[1e7, "\u06A9\u0631\u0648\u0691"],
|
|
211
|
+
[1e5, "\u0644\u0627\u06A9\u06BE"],
|
|
212
|
+
[1e3, "\u06C1\u0632\u0627\u0631"],
|
|
213
|
+
[100, "\u0633\u0648"]
|
|
214
|
+
];
|
|
215
|
+
const parts = [];
|
|
216
|
+
let rest = value;
|
|
217
|
+
for (const [size, name] of scales) {
|
|
218
|
+
if (rest >= size) {
|
|
219
|
+
const count = Math.floor(rest / size);
|
|
220
|
+
rest %= size;
|
|
221
|
+
parts.push(`${numberToUrduWords(count)} ${name}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (rest > 0) {
|
|
225
|
+
if (TEENS_AND_TENS[rest]) {
|
|
226
|
+
parts.push(TEENS_AND_TENS[rest]);
|
|
227
|
+
} else if (rest < 10) {
|
|
228
|
+
parts.push(ONES[rest]);
|
|
229
|
+
} else {
|
|
230
|
+
const tens = Math.floor(rest / 10) * 10;
|
|
231
|
+
parts.push(`${TEENS_AND_TENS[tens] ?? ""} ${ONES[rest % 10] ?? ""}`.trim());
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return parts.join(" ").replace(/\s+/gu, " ").trim();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/detect.ts
|
|
238
|
+
function urduRatio(input) {
|
|
239
|
+
if (!input) return 0;
|
|
240
|
+
let letters = 0;
|
|
241
|
+
let urdu = 0;
|
|
242
|
+
for (const ch of input.match(ANY_LETTER_RE_G) ?? []) {
|
|
243
|
+
letters++;
|
|
244
|
+
if (ARABIC_LETTER_RE.test(ch)) urdu++;
|
|
245
|
+
}
|
|
246
|
+
return letters === 0 ? 0 : urdu / letters;
|
|
247
|
+
}
|
|
248
|
+
function isUrdu(input, options = {}) {
|
|
249
|
+
const { threshold = 0.5, minLetters = 1 } = options;
|
|
250
|
+
if (!input) return false;
|
|
251
|
+
let letters = 0;
|
|
252
|
+
let urdu = 0;
|
|
253
|
+
for (const ch of input.match(ANY_LETTER_RE_G) ?? []) {
|
|
254
|
+
letters++;
|
|
255
|
+
if (ARABIC_LETTER_RE.test(ch)) urdu++;
|
|
256
|
+
}
|
|
257
|
+
if (urdu < minLetters) return false;
|
|
258
|
+
return letters > 0 && urdu / letters >= threshold;
|
|
259
|
+
}
|
|
260
|
+
var URDU_ONLY = /* @__PURE__ */ new Set([
|
|
261
|
+
"\u0679",
|
|
262
|
+
// ٹ tteh
|
|
263
|
+
"\u0688",
|
|
264
|
+
// ڈ ddal
|
|
265
|
+
"\u0691",
|
|
266
|
+
// ڑ rreh
|
|
267
|
+
"\u06BA",
|
|
268
|
+
// ں noon ghunna
|
|
269
|
+
"\u06D2",
|
|
270
|
+
// ے bari ye
|
|
271
|
+
"\u06D3",
|
|
272
|
+
// ۓ bari ye with hamza
|
|
273
|
+
"\u06C1",
|
|
274
|
+
// ہ heh goal
|
|
275
|
+
"\u06BE",
|
|
276
|
+
// ھ do-chashmi heh
|
|
277
|
+
"\u06A9",
|
|
278
|
+
// ک keheh
|
|
279
|
+
"\u06AF",
|
|
280
|
+
// گ gaf
|
|
281
|
+
"\u0686",
|
|
282
|
+
// چ tcheh
|
|
283
|
+
"\u067E",
|
|
284
|
+
// پ peh
|
|
285
|
+
"\u0698",
|
|
286
|
+
// ژ jeh
|
|
287
|
+
"\u06CC"
|
|
288
|
+
// ی farsi yeh
|
|
289
|
+
]);
|
|
290
|
+
function hasUrduSpecificLetters(input) {
|
|
291
|
+
for (const ch of input) if (URDU_ONLY.has(ch)) return true;
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/stats.ts
|
|
296
|
+
function countWords(input) {
|
|
297
|
+
return splitWords(input).length;
|
|
298
|
+
}
|
|
299
|
+
function splitWords(input) {
|
|
300
|
+
if (!input) return [];
|
|
301
|
+
return input.split(WORD_SPLIT_RE).map((w) => w.trim()).filter((w) => w.length > 0);
|
|
302
|
+
}
|
|
303
|
+
function countSentences(input) {
|
|
304
|
+
return splitSentences(input).length;
|
|
305
|
+
}
|
|
306
|
+
function splitSentences(input) {
|
|
307
|
+
if (!input) return [];
|
|
308
|
+
return input.split(SENTENCE_SPLIT_RE).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
309
|
+
}
|
|
310
|
+
function analyzeUrdu(input) {
|
|
311
|
+
const text = input ?? "";
|
|
312
|
+
const characters = [...text].length;
|
|
313
|
+
const charactersNoSpaces = [...text.replace(/\s/gu, "")].length;
|
|
314
|
+
const words = countWords(text);
|
|
315
|
+
const sentences = countSentences(text);
|
|
316
|
+
const paragraphs = text.split(/\n\s*\n/u).map((p) => p.trim()).filter((p) => p.length > 0).length;
|
|
317
|
+
return {
|
|
318
|
+
characters,
|
|
319
|
+
charactersNoSpaces,
|
|
320
|
+
words,
|
|
321
|
+
sentences,
|
|
322
|
+
paragraphs,
|
|
323
|
+
urduPercentage: Math.round(urduRatio(text) * 100),
|
|
324
|
+
diacritics: (text.match(DIACRITICS_RE) ?? []).length,
|
|
325
|
+
digits: (text.match(ANY_DIGIT_RE_G) ?? []).length,
|
|
326
|
+
averageWordsPerSentence: sentences === 0 ? 0 : Math.round(words / sentences * 10) / 10,
|
|
327
|
+
readingTimeMinutes: Math.round(words / 180 * 10) / 10
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/collate.ts
|
|
332
|
+
var ALPHABET = [
|
|
333
|
+
"\u0627",
|
|
334
|
+
"\u0622",
|
|
335
|
+
"\u0628",
|
|
336
|
+
"\u067E",
|
|
337
|
+
"\u062A",
|
|
338
|
+
"\u0679",
|
|
339
|
+
"\u062B",
|
|
340
|
+
"\u062C",
|
|
341
|
+
"\u0686",
|
|
342
|
+
"\u062D",
|
|
343
|
+
"\u062E",
|
|
344
|
+
"\u062F",
|
|
345
|
+
"\u0688",
|
|
346
|
+
"\u0630",
|
|
347
|
+
"\u0631",
|
|
348
|
+
"\u0691",
|
|
349
|
+
"\u0632",
|
|
350
|
+
"\u0698",
|
|
351
|
+
"\u0633",
|
|
352
|
+
"\u0634",
|
|
353
|
+
"\u0635",
|
|
354
|
+
"\u0636",
|
|
355
|
+
"\u0637",
|
|
356
|
+
"\u0638",
|
|
357
|
+
"\u0639",
|
|
358
|
+
"\u063A",
|
|
359
|
+
"\u0641",
|
|
360
|
+
"\u0642",
|
|
361
|
+
"\u06A9",
|
|
362
|
+
"\u06AF",
|
|
363
|
+
"\u0644",
|
|
364
|
+
"\u0645",
|
|
365
|
+
"\u0646",
|
|
366
|
+
"\u06BA",
|
|
367
|
+
"\u0648",
|
|
368
|
+
"\u06C1",
|
|
369
|
+
"\u06BE",
|
|
370
|
+
"\u0621",
|
|
371
|
+
"\u06CC",
|
|
372
|
+
"\u06D2"
|
|
373
|
+
];
|
|
374
|
+
var VARIANTS = {
|
|
375
|
+
"\u0624": ["\u0648", 1],
|
|
376
|
+
// ؤ waw with hamza
|
|
377
|
+
"\u0626": ["\u06CC", 1],
|
|
378
|
+
// ئ yeh with hamza
|
|
379
|
+
"\u06C2": ["\u06C1", 1],
|
|
380
|
+
// ۂ heh goal with hamza
|
|
381
|
+
"\u06D3": ["\u06D2", 1]
|
|
382
|
+
// ۓ bari ye with hamza
|
|
383
|
+
};
|
|
384
|
+
var WEIGHTS = /* @__PURE__ */ new Map();
|
|
385
|
+
ALPHABET.forEach((ch, index) => WEIGHTS.set(ch, [(index + 1) * 10, 0]));
|
|
386
|
+
for (const [ch, [base, secondary]] of Object.entries(VARIANTS)) {
|
|
387
|
+
const baseWeight = WEIGHTS.get(base);
|
|
388
|
+
if (baseWeight) WEIGHTS.set(ch, [baseWeight[0], secondary]);
|
|
389
|
+
}
|
|
390
|
+
var NON_LETTER_BASE = (ALPHABET.length + 1) * 10;
|
|
391
|
+
function weightsOf(text) {
|
|
392
|
+
const out = [];
|
|
393
|
+
for (const ch of text) {
|
|
394
|
+
const weight = WEIGHTS.get(ch);
|
|
395
|
+
if (weight) {
|
|
396
|
+
out.push(weight[0], weight[1]);
|
|
397
|
+
} else if (ch === " ") {
|
|
398
|
+
out.push(1, 0);
|
|
399
|
+
} else {
|
|
400
|
+
out.push(NON_LETTER_BASE + (ch.codePointAt(0) ?? 0), 0);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
function compareUrdu(a, b) {
|
|
406
|
+
const left = weightsOf(foldUrdu(a));
|
|
407
|
+
const right = weightsOf(foldUrdu(b));
|
|
408
|
+
const length = Math.min(left.length, right.length);
|
|
409
|
+
for (let i = 0; i < length; i++) {
|
|
410
|
+
const diff = left[i] - right[i];
|
|
411
|
+
if (diff !== 0) return diff;
|
|
412
|
+
}
|
|
413
|
+
return left.length - right.length;
|
|
414
|
+
}
|
|
415
|
+
function sortUrdu(items, options = {}) {
|
|
416
|
+
const { descending = false, getText } = options;
|
|
417
|
+
const key = getText ?? ((item) => String(item));
|
|
418
|
+
const sorted = [...items].sort((a, b) => compareUrdu(key(a), key(b)));
|
|
419
|
+
return descending ? sorted.reverse() : sorted;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/search.ts
|
|
423
|
+
function editDistance(a, b, limit = Infinity) {
|
|
424
|
+
if (a === b) return 0;
|
|
425
|
+
if (Math.abs(a.length - b.length) > limit) return limit + 1;
|
|
426
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
427
|
+
let current = new Array(b.length + 1);
|
|
428
|
+
for (let i = 1; i <= a.length; i++) {
|
|
429
|
+
current[0] = i;
|
|
430
|
+
let rowMin = current[0];
|
|
431
|
+
for (let j = 1; j <= b.length; j++) {
|
|
432
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
433
|
+
current[j] = Math.min(current[j - 1] + 1, previous[j] + 1, previous[j - 1] + cost);
|
|
434
|
+
rowMin = Math.min(rowMin, current[j]);
|
|
435
|
+
}
|
|
436
|
+
if (rowMin > limit) return limit + 1;
|
|
437
|
+
[previous, current] = [current, previous];
|
|
438
|
+
}
|
|
439
|
+
return previous[b.length];
|
|
440
|
+
}
|
|
441
|
+
function scoreOf(query, text, options) {
|
|
442
|
+
if (!query) return 0;
|
|
443
|
+
if (text === query) return 1;
|
|
444
|
+
if (text.startsWith(query)) return 0.9;
|
|
445
|
+
if (text.includes(query)) return 0.8;
|
|
446
|
+
if (!options.fuzzy) return 0;
|
|
447
|
+
const queryWords = splitWords(query);
|
|
448
|
+
const textWords = splitWords(text);
|
|
449
|
+
if (queryWords.length === 0 || textWords.length === 0) return 0;
|
|
450
|
+
let matched = 0;
|
|
451
|
+
for (const qw of queryWords) {
|
|
452
|
+
const limit = Math.min(options.maxDistance, Math.max(1, Math.floor(qw.length / 3)));
|
|
453
|
+
const hit = textWords.some(
|
|
454
|
+
(tw) => tw.includes(qw) || editDistance(qw, tw, limit) <= limit
|
|
455
|
+
);
|
|
456
|
+
if (hit) matched++;
|
|
457
|
+
}
|
|
458
|
+
if (matched === 0) return 0;
|
|
459
|
+
return matched / queryWords.length * 0.7;
|
|
460
|
+
}
|
|
461
|
+
function searchUrdu(query, items, options = {}) {
|
|
462
|
+
return searchUrduRanked(query, items, options).map((r) => r.item);
|
|
463
|
+
}
|
|
464
|
+
function searchUrduRanked(query, items, options = {}) {
|
|
465
|
+
const { getText, fuzzy = false, maxDistance = 1, limit, sortByScore = true } = options;
|
|
466
|
+
const read = getText ?? ((item) => String(item));
|
|
467
|
+
const folded = foldUrdu(query);
|
|
468
|
+
if (!folded) return [];
|
|
469
|
+
const results = [];
|
|
470
|
+
for (const item of items) {
|
|
471
|
+
const score = scoreOf(folded, foldUrdu(read(item)), { fuzzy, maxDistance });
|
|
472
|
+
if (score > 0) results.push({ item, score });
|
|
473
|
+
}
|
|
474
|
+
if (sortByScore) results.sort((a, b) => b.score - a.score);
|
|
475
|
+
return limit === void 0 ? results : results.slice(0, limit);
|
|
476
|
+
}
|
|
477
|
+
function highlightUrdu(text, query, wrap = (m) => `<mark>${m}</mark>`) {
|
|
478
|
+
const foldedQuery = foldUrdu(query);
|
|
479
|
+
if (!foldedQuery || !text) return text;
|
|
480
|
+
const sourceChars = [...text];
|
|
481
|
+
const foldedChars = [];
|
|
482
|
+
const sourceIndexOf = [];
|
|
483
|
+
sourceChars.forEach((ch, i) => {
|
|
484
|
+
const folded = normalizeUrdu(ch, {
|
|
485
|
+
stripDiacritics: true,
|
|
486
|
+
stripZwnj: true,
|
|
487
|
+
collapseWhitespace: false
|
|
488
|
+
}).toLowerCase();
|
|
489
|
+
for (const f of folded) {
|
|
490
|
+
foldedChars.push(f);
|
|
491
|
+
sourceIndexOf.push(i);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
const haystack = foldedChars.join("");
|
|
495
|
+
let out = "";
|
|
496
|
+
let cursor = 0;
|
|
497
|
+
let from = 0;
|
|
498
|
+
for (; ; ) {
|
|
499
|
+
const hit = haystack.indexOf(foldedQuery, from);
|
|
500
|
+
if (hit === -1) break;
|
|
501
|
+
const startSource = sourceIndexOf[hit];
|
|
502
|
+
const endSource = (sourceIndexOf[hit + foldedQuery.length - 1] ?? startSource) + 1;
|
|
503
|
+
out += sourceChars.slice(cursor, startSource).join("");
|
|
504
|
+
out += wrap(sourceChars.slice(startSource, endSource).join(""));
|
|
505
|
+
cursor = endSource;
|
|
506
|
+
from = hit + foldedQuery.length;
|
|
507
|
+
}
|
|
508
|
+
out += sourceChars.slice(cursor).join("");
|
|
509
|
+
return out;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/transliterate.ts
|
|
513
|
+
var WORD_DICTIONARY = {
|
|
514
|
+
"\u0622\u067E": "aap",
|
|
515
|
+
"\u0622\u067E\u06A9\u0627": "aapka",
|
|
516
|
+
"\u0627\u0648\u0631": "aur",
|
|
517
|
+
"\u0627\u06CC\u06A9": "aik",
|
|
518
|
+
"\u0627\u062D\u0645\u062F": "ahmed",
|
|
519
|
+
"\u0627\u0686\u06BE\u0627": "acha",
|
|
520
|
+
"\u0628\u06C1\u062A": "bohat",
|
|
521
|
+
"\u067E\u0627\u06A9\u0633\u062A\u0627\u0646": "pakistan",
|
|
522
|
+
"\u067E\u0627\u0646\u06CC": "paani",
|
|
523
|
+
"\u067E\u0631": "par",
|
|
524
|
+
"\u067E\u06C1\u0644\u0627": "pehla",
|
|
525
|
+
"\u062A\u0645": "tum",
|
|
526
|
+
"\u062A\u06BE\u0627": "tha",
|
|
527
|
+
"\u062A\u06BE\u06CC": "thi",
|
|
528
|
+
"\u062D\u0627\u0644": "haal",
|
|
529
|
+
"\u062E\u0627\u0646": "khan",
|
|
530
|
+
"\u062E\u0648\u0628\u0635\u0648\u0631\u062A": "khoobsurat",
|
|
531
|
+
"\u062F\u0646": "din",
|
|
532
|
+
"\u0631\u0627\u062A": "raat",
|
|
533
|
+
"\u0632\u06CC\u062F": "zaid",
|
|
534
|
+
"\u0633\u06D2": "se",
|
|
535
|
+
"\u0634\u06C1\u0631": "shehar",
|
|
536
|
+
"\u0639\u0644\u06CC": "ali",
|
|
537
|
+
"\u06A9\u0627": "ka",
|
|
538
|
+
"\u06A9\u062A\u0627\u0628": "kitaab",
|
|
539
|
+
"\u06A9\u0631": "kar",
|
|
540
|
+
"\u06A9\u0631\u0646\u0627": "karna",
|
|
541
|
+
"\u06A9\u06D2": "ke",
|
|
542
|
+
"\u06A9\u06CC": "ki",
|
|
543
|
+
"\u06A9\u06CC\u0627": "kya",
|
|
544
|
+
"\u06A9\u06CC\u0633\u06D2": "kaisay",
|
|
545
|
+
"\u0644\u0691\u06A9\u0627": "larka",
|
|
546
|
+
"\u0644\u0691\u06A9\u06CC": "larki",
|
|
547
|
+
"\u0644\u06CC\u06D2": "liye",
|
|
548
|
+
"\u0645\u062D\u0645\u062F": "muhammad",
|
|
549
|
+
"\u0645\u0636\u0645\u0648\u0646": "mazmoon",
|
|
550
|
+
"\u0645\u0644\u06A9": "mulk",
|
|
551
|
+
"\u0645\u06CC\u06BA": "mein",
|
|
552
|
+
"\u0645\u06CC\u0631\u0627": "mera",
|
|
553
|
+
"\u0645\u06CC\u0631\u06CC": "meri",
|
|
554
|
+
"\u0646\u0627\u0645": "naam",
|
|
555
|
+
"\u0646\u06C1\u06CC\u06BA": "nahi",
|
|
556
|
+
"\u0648\u06C1": "woh",
|
|
557
|
+
"\u06C1\u06D2": "hai",
|
|
558
|
+
"\u06C1\u06CC\u06BA": "hain",
|
|
559
|
+
"\u06C1\u0648": "ho",
|
|
560
|
+
"\u06C1\u0648\u06BA": "hoon",
|
|
561
|
+
"\u06CC\u06C1": "yeh"
|
|
562
|
+
};
|
|
563
|
+
var DIGRAPHS = {
|
|
564
|
+
"\u0628\u06BE": "bh",
|
|
565
|
+
"\u067E\u06BE": "ph",
|
|
566
|
+
"\u062A\u06BE": "th",
|
|
567
|
+
"\u0679\u06BE": "th",
|
|
568
|
+
"\u062C\u06BE": "jh",
|
|
569
|
+
"\u0686\u06BE": "chh",
|
|
570
|
+
"\u062F\u06BE": "dh",
|
|
571
|
+
"\u0688\u06BE": "dh",
|
|
572
|
+
"\u0691\u06BE": "rh",
|
|
573
|
+
"\u06A9\u06BE": "kh",
|
|
574
|
+
"\u06AF\u06BE": "gh",
|
|
575
|
+
"\u0644\u06BE": "lh",
|
|
576
|
+
"\u0645\u06BE": "mh",
|
|
577
|
+
"\u0646\u06BE": "nh",
|
|
578
|
+
"\u0631\u06BE": "rh"
|
|
579
|
+
};
|
|
580
|
+
var LETTERS = {
|
|
581
|
+
"\u0627": "a",
|
|
582
|
+
"\u0622": "aa",
|
|
583
|
+
"\u0628": "b",
|
|
584
|
+
"\u067E": "p",
|
|
585
|
+
"\u062A": "t",
|
|
586
|
+
"\u0679": "t",
|
|
587
|
+
"\u062B": "s",
|
|
588
|
+
"\u062C": "j",
|
|
589
|
+
"\u0686": "ch",
|
|
590
|
+
"\u062D": "h",
|
|
591
|
+
"\u062E": "kh",
|
|
592
|
+
"\u062F": "d",
|
|
593
|
+
"\u0688": "d",
|
|
594
|
+
"\u0630": "z",
|
|
595
|
+
"\u0631": "r",
|
|
596
|
+
"\u0691": "r",
|
|
597
|
+
"\u0632": "z",
|
|
598
|
+
"\u0698": "zh",
|
|
599
|
+
"\u0633": "s",
|
|
600
|
+
"\u0634": "sh",
|
|
601
|
+
"\u0635": "s",
|
|
602
|
+
"\u0636": "z",
|
|
603
|
+
"\u0637": "t",
|
|
604
|
+
"\u0638": "z",
|
|
605
|
+
"\u0639": "a",
|
|
606
|
+
"\u063A": "gh",
|
|
607
|
+
"\u0641": "f",
|
|
608
|
+
"\u0642": "q",
|
|
609
|
+
"\u06A9": "k",
|
|
610
|
+
"\u06AF": "g",
|
|
611
|
+
"\u0644": "l",
|
|
612
|
+
"\u0645": "m",
|
|
613
|
+
"\u0646": "n",
|
|
614
|
+
"\u06BA": "n",
|
|
615
|
+
"\u0648": "o",
|
|
616
|
+
"\u06C1": "h",
|
|
617
|
+
"\u06BE": "h",
|
|
618
|
+
"\u0621": "",
|
|
619
|
+
"\u06CC": "i",
|
|
620
|
+
"\u0626": "i",
|
|
621
|
+
"\u06D2": "e",
|
|
622
|
+
"\u06D3": "e",
|
|
623
|
+
"\u0624": "o",
|
|
624
|
+
"\u06C2": "h"
|
|
625
|
+
};
|
|
626
|
+
function romanizeWord(word) {
|
|
627
|
+
const dictionary = WORD_DICTIONARY[word];
|
|
628
|
+
if (dictionary) return dictionary;
|
|
629
|
+
const chars = [...word];
|
|
630
|
+
let out = "";
|
|
631
|
+
for (let i = 0; i < chars.length; i++) {
|
|
632
|
+
const pair = chars[i] + (chars[i + 1] ?? "");
|
|
633
|
+
const digraph = DIGRAPHS[pair];
|
|
634
|
+
if (digraph) {
|
|
635
|
+
out += digraph;
|
|
636
|
+
i++;
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const ch = chars[i];
|
|
640
|
+
if (i === 0 && ch === "\u0627") {
|
|
641
|
+
const next = chars[1];
|
|
642
|
+
if (next === "\u0648") {
|
|
643
|
+
out += "o";
|
|
644
|
+
i++;
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (next === "\u06CC") {
|
|
648
|
+
out += "i";
|
|
649
|
+
i++;
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
out += "a";
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
out += LETTERS[ch] ?? ch;
|
|
656
|
+
}
|
|
657
|
+
return out;
|
|
658
|
+
}
|
|
659
|
+
function romanize(input, options = {}) {
|
|
660
|
+
if (!input) return "";
|
|
661
|
+
const clean = removeDiacritics(normalizeUrdu(input));
|
|
662
|
+
const roman = clean.split(/(\s+)/u).map((chunk) => /^\s+$/u.test(chunk) ? chunk : romanizeWord(chunk)).join("");
|
|
663
|
+
if (!options.capitalize) return roman;
|
|
664
|
+
return roman.charAt(0).toUpperCase() + roman.slice(1);
|
|
665
|
+
}
|
|
666
|
+
var ROMAN_DICTIONARY = {};
|
|
667
|
+
for (const [urduWord, roman] of Object.entries(WORD_DICTIONARY)) {
|
|
668
|
+
ROMAN_DICTIONARY[roman] = urduWord;
|
|
669
|
+
}
|
|
670
|
+
Object.assign(ROMAN_DICTIONARY, {
|
|
671
|
+
hai: "\u06C1\u06D2",
|
|
672
|
+
hay: "\u06C1\u06D2",
|
|
673
|
+
he: "\u06C1\u06D2",
|
|
674
|
+
hein: "\u06C1\u06CC\u06BA",
|
|
675
|
+
hoon: "\u06C1\u0648\u06BA",
|
|
676
|
+
hun: "\u06C1\u0648\u06BA",
|
|
677
|
+
ap: "\u0622\u067E",
|
|
678
|
+
kaise: "\u06A9\u06CC\u0633\u06D2",
|
|
679
|
+
kaisa: "\u06A9\u06CC\u0633\u0627",
|
|
680
|
+
nam: "\u0646\u0627\u0645",
|
|
681
|
+
naam: "\u0646\u0627\u0645",
|
|
682
|
+
mai: "\u0645\u06CC\u06BA",
|
|
683
|
+
main: "\u0645\u06CC\u06BA",
|
|
684
|
+
nahin: "\u0646\u06C1\u06CC\u06BA",
|
|
685
|
+
kia: "\u06A9\u06CC\u0627",
|
|
686
|
+
bahut: "\u0628\u06C1\u062A",
|
|
687
|
+
bhot: "\u0628\u06C1\u062A",
|
|
688
|
+
acha: "\u0627\u0686\u06BE\u0627",
|
|
689
|
+
achha: "\u0627\u0686\u06BE\u0627",
|
|
690
|
+
shukriya: "\u0634\u06A9\u0631\u06CC\u06C1",
|
|
691
|
+
salam: "\u0633\u0644\u0627\u0645"
|
|
692
|
+
});
|
|
693
|
+
var ROMAN_RULES = [
|
|
694
|
+
[/^kh/u, "\u06A9\u06BE"],
|
|
695
|
+
[/^gh/u, "\u06AF\u06BE"],
|
|
696
|
+
[/^chh/u, "\u0686\u06BE"],
|
|
697
|
+
[/^ch/u, "\u0686"],
|
|
698
|
+
[/^sh/u, "\u0634"],
|
|
699
|
+
[/^th/u, "\u062A\u06BE"],
|
|
700
|
+
[/^ph/u, "\u067E\u06BE"],
|
|
701
|
+
[/^bh/u, "\u0628\u06BE"],
|
|
702
|
+
[/^aa/u, "\u0622"],
|
|
703
|
+
[/^oo/u, "\u0648"],
|
|
704
|
+
[/^ee/u, "\u06CC"],
|
|
705
|
+
[/^ai/u, "\u06CC"],
|
|
706
|
+
[/^b/u, "\u0628"],
|
|
707
|
+
[/^p/u, "\u067E"],
|
|
708
|
+
[/^t/u, "\u062A"],
|
|
709
|
+
[/^j/u, "\u062C"],
|
|
710
|
+
[/^d/u, "\u062F"],
|
|
711
|
+
[/^r/u, "\u0631"],
|
|
712
|
+
[/^z/u, "\u0632"],
|
|
713
|
+
[/^s/u, "\u0633"],
|
|
714
|
+
[/^f/u, "\u0641"],
|
|
715
|
+
[/^q/u, "\u0642"],
|
|
716
|
+
[/^k/u, "\u06A9"],
|
|
717
|
+
[/^g/u, "\u06AF"],
|
|
718
|
+
[/^l/u, "\u0644"],
|
|
719
|
+
[/^m/u, "\u0645"],
|
|
720
|
+
[/^n/u, "\u0646"],
|
|
721
|
+
[/^v|^w/u, "\u0648"],
|
|
722
|
+
[/^h/u, "\u06C1"],
|
|
723
|
+
[/^y/u, "\u06CC"],
|
|
724
|
+
[/^a/u, "\u0627"],
|
|
725
|
+
[/^e/u, "\u06CC"],
|
|
726
|
+
[/^i/u, "\u06CC"],
|
|
727
|
+
[/^o/u, "\u0648"],
|
|
728
|
+
[/^u/u, "\u0648"]
|
|
729
|
+
];
|
|
730
|
+
function romanWordToUrdu(word) {
|
|
731
|
+
const lower = word.toLowerCase();
|
|
732
|
+
const dictionary = ROMAN_DICTIONARY[lower];
|
|
733
|
+
if (dictionary) return dictionary;
|
|
734
|
+
let rest = lower;
|
|
735
|
+
let out = "";
|
|
736
|
+
let position = 0;
|
|
737
|
+
while (rest.length > 0) {
|
|
738
|
+
let matched = false;
|
|
739
|
+
for (const [pattern, replacement] of ROMAN_RULES) {
|
|
740
|
+
const hit = pattern.exec(rest);
|
|
741
|
+
if (!hit) continue;
|
|
742
|
+
const isShortVowel = /^[aeiou]$/u.test(hit[0]);
|
|
743
|
+
const skip = isShortVowel && position > 0 && rest.length > 1;
|
|
744
|
+
if (!skip) out += replacement;
|
|
745
|
+
rest = rest.slice(hit[0].length);
|
|
746
|
+
position++;
|
|
747
|
+
matched = true;
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
if (!matched) {
|
|
751
|
+
out += rest[0];
|
|
752
|
+
rest = rest.slice(1);
|
|
753
|
+
position++;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return out;
|
|
757
|
+
}
|
|
758
|
+
function romanToUrdu(input) {
|
|
759
|
+
if (!input) return "";
|
|
760
|
+
return input.split(/(\s+)/u).map((chunk) => /^\s+$/u.test(chunk) ? chunk : romanWordToUrdu(chunk)).join("");
|
|
761
|
+
}
|
|
762
|
+
function urduSlug(input, options = {}) {
|
|
763
|
+
const { separator = "-", maxLength, preserveUrdu = false } = options;
|
|
764
|
+
if (!input) return "";
|
|
765
|
+
const words = splitWords(removeDiacritics(normalizeUrdu(input)));
|
|
766
|
+
const parts = preserveUrdu ? words : words.map(romanizeWord);
|
|
767
|
+
const allowed = preserveUrdu ? /[^\p{L}\p{N}]+/gu : /[^a-z0-9]+/gu;
|
|
768
|
+
let slug = parts.map((part) => part.toLowerCase().replace(allowed, " ").trim()).flatMap((part) => part.split(/\s+/u)).filter((part) => part.length > 0).join(separator);
|
|
769
|
+
if (maxLength && slug.length > maxLength) {
|
|
770
|
+
slug = slug.slice(0, maxLength);
|
|
771
|
+
const lastSeparator = slug.lastIndexOf(separator);
|
|
772
|
+
if (lastSeparator > 0) slug = slug.slice(0, lastSeparator);
|
|
773
|
+
}
|
|
774
|
+
return slug;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
exports.analyzeUrdu = analyzeUrdu;
|
|
778
|
+
exports.compareUrdu = compareUrdu;
|
|
779
|
+
exports.convertNumbers = convertNumbers;
|
|
780
|
+
exports.countSentences = countSentences;
|
|
781
|
+
exports.countWords = countWords;
|
|
782
|
+
exports.editDistance = editDistance;
|
|
783
|
+
exports.foldUrdu = foldUrdu;
|
|
784
|
+
exports.hasUrduSpecificLetters = hasUrduSpecificLetters;
|
|
785
|
+
exports.highlightUrdu = highlightUrdu;
|
|
786
|
+
exports.isUrdu = isUrdu;
|
|
787
|
+
exports.normalizeUrdu = normalizeUrdu;
|
|
788
|
+
exports.numberToUrduWords = numberToUrduWords;
|
|
789
|
+
exports.parseUrduNumber = parseUrduNumber;
|
|
790
|
+
exports.removeDiacritics = removeDiacritics;
|
|
791
|
+
exports.romanToUrdu = romanToUrdu;
|
|
792
|
+
exports.romanize = romanize;
|
|
793
|
+
exports.searchUrdu = searchUrdu;
|
|
794
|
+
exports.searchUrduRanked = searchUrduRanked;
|
|
795
|
+
exports.sortUrdu = sortUrdu;
|
|
796
|
+
exports.splitSentences = splitSentences;
|
|
797
|
+
exports.splitWords = splitWords;
|
|
798
|
+
exports.toArabicIndicDigits = toArabicIndicDigits;
|
|
799
|
+
exports.toEnglishDigits = toEnglishDigits;
|
|
800
|
+
exports.toUrduDigits = toUrduDigits;
|
|
801
|
+
exports.urduRatio = urduRatio;
|
|
802
|
+
exports.urduSlug = urduSlug;
|
|
803
|
+
//# sourceMappingURL=index.cjs.map
|
|
804
|
+
//# sourceMappingURL=index.cjs.map
|