urdu-text-utils 0.1.8 → 0.1.9
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/CHANGELOG.md +7 -0
- package/README.md +27 -0
- package/dist/index.cjs +243 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -1
- package/dist/index.d.ts +86 -1
- package/dist/index.iife.js +1 -1
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +239 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.9
|
|
4
|
+
|
|
5
|
+
- Add rule-based Urdu Stemmer module (`stemUrdu`, `stemUrduText`, `getAffixes`).
|
|
6
|
+
- Add morphological restorations for feminine plurals (*لڑکیاں* → *لڑکی*), hamza plurals (*دعاؤں* → *دعا*), and Arabic/sound plurals (*تعلیمات* → *تعلیم*).
|
|
7
|
+
- Add canonical prefix and suffix stripping with short root protection and exception handling.
|
|
8
|
+
- Add canonical constants `URDU_PREFIXES` and `URDU_SUFFIXES`.
|
|
9
|
+
|
|
3
10
|
## 0.1.8
|
|
4
11
|
|
|
5
12
|
- Fix jsDelivr monthly hit badge in README to use shields.io SVG endpoint.
|
package/README.md
CHANGED
|
@@ -53,6 +53,8 @@ import {
|
|
|
53
53
|
removeStopWords,
|
|
54
54
|
formatUrduDate,
|
|
55
55
|
timeAgoUrdu,
|
|
56
|
+
stemUrdu,
|
|
57
|
+
stemUrduText,
|
|
56
58
|
isUrdu,
|
|
57
59
|
countWords,
|
|
58
60
|
splitSentences,
|
|
@@ -172,6 +174,31 @@ timeAgoUrdu(Date.now() - 2 * 86400 * 1000);// "پرسوں"
|
|
|
172
174
|
timeAgoUrdu(Date.now() + 10 * 60 * 1000); // "۱۰ منٹ بعد"
|
|
173
175
|
```
|
|
174
176
|
|
|
177
|
+
## Urdu stemming
|
|
178
|
+
|
|
179
|
+
Rule-based stemmer and affix stripper with morphological vowel/letter restorations:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { stemUrdu, stemUrduText } from "urdu-text-utils";
|
|
183
|
+
|
|
184
|
+
// Plurals & morphological restoration
|
|
185
|
+
stemUrdu("کتابیں"); // "کتاب"
|
|
186
|
+
stemUrdu("لڑکیاں"); // "لڑکی" (restores final ی)
|
|
187
|
+
stemUrdu("کہانیاں"); // "کہانی"
|
|
188
|
+
stemUrdu("دعاؤں"); // "دعا"
|
|
189
|
+
stemUrdu("خوشبوئیں"); // "خوشبو"
|
|
190
|
+
|
|
191
|
+
// Prefixes & derivational suffixes
|
|
192
|
+
stemUrdu("بےوقوف"); // "وقوف"
|
|
193
|
+
stemUrdu("نااہل"); // "اہل"
|
|
194
|
+
stemUrdu("دکاندار"); // "دکان"
|
|
195
|
+
stemUrdu("مددگار"); // "مدد"
|
|
196
|
+
|
|
197
|
+
// Stems full text while preserving layout & punctuation
|
|
198
|
+
stemUrduText("طلباء کتابیں پڑھتے ہیں اور کہانیاں سنتے ہیں۔");
|
|
199
|
+
// "طلباء کتاب پڑھ ہیں اور کہانی سن ہیں۔"
|
|
200
|
+
```
|
|
201
|
+
|
|
175
202
|
## Diacritics
|
|
176
203
|
|
|
177
204
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -3703,9 +3703,249 @@ function timeAgoUrdu(date, relativeTo = /* @__PURE__ */ new Date(), options = {}
|
|
|
3703
3703
|
return isPast ? `${count} \u0633\u0627\u0644 \u067E\u06C1\u0644\u06D2` : `${count} \u0633\u0627\u0644 \u0628\u0639\u062F`;
|
|
3704
3704
|
}
|
|
3705
3705
|
|
|
3706
|
+
// src/stemmer.ts
|
|
3707
|
+
var URDU_PREFIXES = [
|
|
3708
|
+
"\u063A\u06CC\u0631",
|
|
3709
|
+
"\u062E\u0648\u062F",
|
|
3710
|
+
"\u0627\u06C1\u0644",
|
|
3711
|
+
"\u0628\u06D2",
|
|
3712
|
+
"\u0646\u0627",
|
|
3713
|
+
"\u0644\u0627",
|
|
3714
|
+
"\u0628\u062F",
|
|
3715
|
+
"\u06A9\u0645",
|
|
3716
|
+
"\u0627\u0646",
|
|
3717
|
+
"\u06C1\u0645",
|
|
3718
|
+
"\u0628\u0627",
|
|
3719
|
+
"\u067E\u064F\u0631"
|
|
3720
|
+
];
|
|
3721
|
+
var URDU_SUFFIXES = [
|
|
3722
|
+
// Compound / multi-syllable suffixes
|
|
3723
|
+
"\u06CC\u06BA\u06AF\u06D2",
|
|
3724
|
+
"\u06CC\u0646\u06AF\u06D2",
|
|
3725
|
+
"\u06D2\u06AF\u0627",
|
|
3726
|
+
"\u06D2\u06AF\u06CC",
|
|
3727
|
+
"\u06CC\u0646\u06AF\u06CC",
|
|
3728
|
+
"\u062F\u0627\u0631\u06CC",
|
|
3729
|
+
"\u06AF\u0627\u0631\u06CC",
|
|
3730
|
+
"\u06A9\u0627\u0631\u06CC",
|
|
3731
|
+
"\u0633\u0627\u0632\u06CC",
|
|
3732
|
+
"\u0628\u0627\u0632\u06CC",
|
|
3733
|
+
"\u0645\u0646\u062F\u06CC",
|
|
3734
|
+
"\u062A\u0631\u06CC\u0646",
|
|
3735
|
+
"\u0633\u062A\u0627\u0646",
|
|
3736
|
+
"\u062E\u0627\u0646\u06C1",
|
|
3737
|
+
"\u0646\u0627\u0645\u06C1",
|
|
3738
|
+
"\u062C\u0627\u062A",
|
|
3739
|
+
"\u06AF\u0627\u0646",
|
|
3740
|
+
// Adjectival / nominal / agentive suffixes
|
|
3741
|
+
"\u062F\u0627\u0631",
|
|
3742
|
+
"\u06AF\u0627\u0631",
|
|
3743
|
+
"\u06A9\u0627\u0631",
|
|
3744
|
+
"\u0633\u0627\u0632",
|
|
3745
|
+
"\u0628\u0627\u0632",
|
|
3746
|
+
"\u0645\u0646\u062F",
|
|
3747
|
+
"\u0646\u0627\u06A9",
|
|
3748
|
+
"\u0648\u0627\u0631",
|
|
3749
|
+
"\u062F\u0627\u0646",
|
|
3750
|
+
"\u0632\u0627\u0631",
|
|
3751
|
+
"\u06CC\u062A",
|
|
3752
|
+
"\u067E\u0646",
|
|
3753
|
+
"\u0627\u0626\u06CC",
|
|
3754
|
+
"\u062A\u0631",
|
|
3755
|
+
// Plural & verbal inflections
|
|
3756
|
+
"\u06CC\u0627\u06BA",
|
|
3757
|
+
"\u0626\u06CC\u06BA",
|
|
3758
|
+
"\u0624\u06BA",
|
|
3759
|
+
"\u06CC\u0648\u06BA",
|
|
3760
|
+
"\u0648\u06BA",
|
|
3761
|
+
"\u06CC\u06BA",
|
|
3762
|
+
"\u0627\u062A",
|
|
3763
|
+
"\u06C1\u0627",
|
|
3764
|
+
"\u06CC\u0646",
|
|
3765
|
+
"\u062A\u06CC\u06BA",
|
|
3766
|
+
"\u062A\u0627",
|
|
3767
|
+
"\u062A\u06CC",
|
|
3768
|
+
"\u062A\u06D2",
|
|
3769
|
+
"\u0646\u0627"
|
|
3770
|
+
];
|
|
3771
|
+
var PROTECTED_WORDS = /* @__PURE__ */ new Set([
|
|
3772
|
+
"\u06C1\u0645",
|
|
3773
|
+
"\u0646\u0627",
|
|
3774
|
+
"\u0644\u0627",
|
|
3775
|
+
"\u0628\u06D2",
|
|
3776
|
+
"\u06CC\u0627",
|
|
3777
|
+
"\u062A\u0648",
|
|
3778
|
+
"\u067E\u0631",
|
|
3779
|
+
"\u0633\u06D2",
|
|
3780
|
+
"\u06A9\u0648",
|
|
3781
|
+
"\u06A9\u0627",
|
|
3782
|
+
"\u06A9\u06CC",
|
|
3783
|
+
"\u06A9\u06D2",
|
|
3784
|
+
"\u0645\u06CC\u06BA",
|
|
3785
|
+
"\u0646\u06D2",
|
|
3786
|
+
"\u06C1\u06D2",
|
|
3787
|
+
"\u06C1\u06CC\u06BA",
|
|
3788
|
+
"\u06C1\u0648",
|
|
3789
|
+
"\u062A\u06BE\u0627",
|
|
3790
|
+
"\u062A\u06BE\u06CC",
|
|
3791
|
+
"\u062A\u06BE\u06D2",
|
|
3792
|
+
"\u062A\u06BE\u06CC\u06BA",
|
|
3793
|
+
"\u0628\u0627\u063A",
|
|
3794
|
+
"\u0646\u0627\u0645",
|
|
3795
|
+
"\u0633\u0641\u0631",
|
|
3796
|
+
"\u06C1\u0648\u0627",
|
|
3797
|
+
"\u062F\u0644",
|
|
3798
|
+
"\u0633\u0631",
|
|
3799
|
+
"\u0644\u0628",
|
|
3800
|
+
"\u062F\u0633\u062A",
|
|
3801
|
+
"\u06A9\u0627\u0631",
|
|
3802
|
+
"\u0628\u0627\u0631",
|
|
3803
|
+
"\u0628\u0627\u0644",
|
|
3804
|
+
"\u0628\u0627\u0646",
|
|
3805
|
+
"\u0634\u0627\u0645",
|
|
3806
|
+
"\u06A9\u0627\u0645",
|
|
3807
|
+
"\u062F\u0627\u0645",
|
|
3808
|
+
"\u062C\u0627\u0645",
|
|
3809
|
+
"\u0639\u0627\u0645",
|
|
3810
|
+
"\u0631\u0627\u0645",
|
|
3811
|
+
"\u06AF\u0627\u0645",
|
|
3812
|
+
"\u062A\u0627\u0631",
|
|
3813
|
+
"\u06CC\u0627\u0631",
|
|
3814
|
+
"\u0645\u0627\u0631",
|
|
3815
|
+
"\u0646\u0627\u0631",
|
|
3816
|
+
"\u062F\u0627\u0631",
|
|
3817
|
+
"\u06C1\u0627\u0631",
|
|
3818
|
+
"\u0632\u0627\u0631",
|
|
3819
|
+
"\u063A\u0627\u0631",
|
|
3820
|
+
"\u0631\u0627\u062A",
|
|
3821
|
+
"\u0628\u0627\u062A",
|
|
3822
|
+
"\u0645\u0627\u062A",
|
|
3823
|
+
"\u0630\u0627\u062A",
|
|
3824
|
+
"\u06C1\u0627\u062A\u06BE",
|
|
3825
|
+
"\u0633\u0627\u062A\u06BE",
|
|
3826
|
+
"\u062F\u06CC\u0646",
|
|
3827
|
+
"\u062A\u06CC\u0646",
|
|
3828
|
+
"\u0686\u06CC\u0646",
|
|
3829
|
+
"\u0628\u06CC\u0646",
|
|
3830
|
+
"\u0632\u0645\u06CC\u0646",
|
|
3831
|
+
"\u0627\u0646\u0633\u0627\u0646",
|
|
3832
|
+
"\u0627\u062D\u0633\u0627\u0646",
|
|
3833
|
+
"\u0627\u06CC\u0645\u0627\u0646",
|
|
3834
|
+
"\u0627\u0633\u0644\u0627\u0645",
|
|
3835
|
+
"\u0627\u0639\u0644\u0627\u0646",
|
|
3836
|
+
"\u0627\u0645\u06A9\u0627\u0646",
|
|
3837
|
+
"\u0627\u0631\u0645\u0627\u0646",
|
|
3838
|
+
"\u0627\u0641\u063A\u0627\u0646",
|
|
3839
|
+
"\u0627\u06CC\u0631\u0627\u0646",
|
|
3840
|
+
"\u0639\u0645\u0631\u0627\u0646",
|
|
3841
|
+
"\u0639\u062B\u0645\u0627\u0646",
|
|
3842
|
+
"\u0633\u0644\u0645\u0627\u0646",
|
|
3843
|
+
"\u0639\u0631\u0641\u0627\u0646",
|
|
3844
|
+
"\u0642\u0631\u0622\u0646",
|
|
3845
|
+
"\u0637\u0648\u0641\u0627\u0646",
|
|
3846
|
+
"\u067E\u06C1\u0686\u0627\u0646",
|
|
3847
|
+
"\u062F\u06A9\u0627\u0646",
|
|
3848
|
+
"\u0645\u06A9\u0627\u0646",
|
|
3849
|
+
"\u0627\u0633\u0645\u0627\u0646",
|
|
3850
|
+
"\u0622\u0633\u0645\u0627\u0646",
|
|
3851
|
+
"\u0645\u06C1\u0645\u0627\u0646",
|
|
3852
|
+
"\u0646\u0642\u0635\u0627\u0646"
|
|
3853
|
+
]);
|
|
3854
|
+
function applySuffixStripping(word, suffixes, minLen) {
|
|
3855
|
+
if (word.length <= minLen) return { stem: word };
|
|
3856
|
+
if (word.endsWith("\u06CC\u0627\u06BA") && word.length - 3 >= minLen) {
|
|
3857
|
+
const base = word.slice(0, -3);
|
|
3858
|
+
return { stem: `${base}\u06CC`, suffix: "\u06CC\u0627\u06BA" };
|
|
3859
|
+
}
|
|
3860
|
+
if (word.endsWith("\u06CC\u0648\u06BA") && word.length - 3 >= minLen) {
|
|
3861
|
+
const base = word.slice(0, -3);
|
|
3862
|
+
return { stem: `${base}\u06CC`, suffix: "\u06CC\u0648\u06BA" };
|
|
3863
|
+
}
|
|
3864
|
+
if (word.endsWith("\u0626\u06CC\u06BA") && word.length - 3 >= minLen) {
|
|
3865
|
+
const base = word.slice(0, -3);
|
|
3866
|
+
return { stem: base, suffix: "\u0626\u06CC\u06BA" };
|
|
3867
|
+
}
|
|
3868
|
+
if (word.endsWith("\u0624\u06BA") && word.length - 2 >= minLen) {
|
|
3869
|
+
const base = word.slice(0, -2);
|
|
3870
|
+
return { stem: base, suffix: "\u0624\u06BA" };
|
|
3871
|
+
}
|
|
3872
|
+
for (const suf of suffixes) {
|
|
3873
|
+
if (word.endsWith(suf)) {
|
|
3874
|
+
const remainder = word.slice(0, -suf.length).replace(/[\s\u200C\u200D]+$/, "");
|
|
3875
|
+
if (remainder.length >= minLen && !PROTECTED_WORDS.has(word)) {
|
|
3876
|
+
return { stem: remainder, suffix: suf };
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
return { stem: word };
|
|
3881
|
+
}
|
|
3882
|
+
function applyPrefixStripping(word, prefixes, minLen) {
|
|
3883
|
+
if (word.length <= minLen) return { stem: word };
|
|
3884
|
+
for (const pref of prefixes) {
|
|
3885
|
+
if (word.startsWith(pref)) {
|
|
3886
|
+
const remainder = word.slice(pref.length).replace(/^[\s\u200C\u200D]+/, "");
|
|
3887
|
+
if (remainder.length >= minLen && !PROTECTED_WORDS.has(word)) {
|
|
3888
|
+
return { stem: remainder, prefix: pref };
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
return { stem: word };
|
|
3893
|
+
}
|
|
3894
|
+
function getAffixes(input, options = {}) {
|
|
3895
|
+
if (!input || typeof input !== "string") {
|
|
3896
|
+
return { stem: "" };
|
|
3897
|
+
}
|
|
3898
|
+
const clean = removeDiacritics(normalizeUrdu(input.trim()));
|
|
3899
|
+
if (!clean) return { stem: "" };
|
|
3900
|
+
if (options.exceptions && options.exceptions[clean]) {
|
|
3901
|
+
return { stem: options.exceptions[clean] };
|
|
3902
|
+
}
|
|
3903
|
+
if (PROTECTED_WORDS.has(clean)) {
|
|
3904
|
+
return { stem: clean };
|
|
3905
|
+
}
|
|
3906
|
+
const minLen = options.minStemLength ?? 2;
|
|
3907
|
+
const doSuffixes = options.stripSuffixes ?? true;
|
|
3908
|
+
const doPrefixes = options.stripPrefixes ?? true;
|
|
3909
|
+
const suffixes = options.customSuffixes ?? URDU_SUFFIXES;
|
|
3910
|
+
const prefixes = options.customPrefixes ?? URDU_PREFIXES;
|
|
3911
|
+
let current = clean;
|
|
3912
|
+
let detectedSuffix;
|
|
3913
|
+
let detectedPrefix;
|
|
3914
|
+
if (doSuffixes) {
|
|
3915
|
+
const sRes = applySuffixStripping(current, suffixes, minLen);
|
|
3916
|
+
current = sRes.stem;
|
|
3917
|
+
detectedSuffix = sRes.suffix;
|
|
3918
|
+
}
|
|
3919
|
+
if (doPrefixes) {
|
|
3920
|
+
const pRes = applyPrefixStripping(current, prefixes, minLen);
|
|
3921
|
+
current = pRes.stem;
|
|
3922
|
+
detectedPrefix = pRes.prefix;
|
|
3923
|
+
}
|
|
3924
|
+
return {
|
|
3925
|
+
prefix: detectedPrefix,
|
|
3926
|
+
stem: current,
|
|
3927
|
+
suffix: detectedSuffix
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
function stemUrdu(word, options = {}) {
|
|
3931
|
+
return getAffixes(word, options).stem;
|
|
3932
|
+
}
|
|
3933
|
+
function stemUrduText(text, options = {}) {
|
|
3934
|
+
if (!text || typeof text !== "string") return "";
|
|
3935
|
+
const tokens = text.split(/([\s\p{P}\p{S}]+)/u);
|
|
3936
|
+
return tokens.map((token) => {
|
|
3937
|
+
if (/[\u0600-\u06FF\u0750-\u077F\uFB50-\uFDFF\uFE70-\uFEFF]/.test(token)) {
|
|
3938
|
+
return stemUrdu(token, options);
|
|
3939
|
+
}
|
|
3940
|
+
return token;
|
|
3941
|
+
}).join("");
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3706
3944
|
exports.URDU_MONTHS_GREGORIAN = URDU_MONTHS_GREGORIAN;
|
|
3707
3945
|
exports.URDU_MONTHS_HIJRI = URDU_MONTHS_HIJRI;
|
|
3946
|
+
exports.URDU_PREFIXES = URDU_PREFIXES;
|
|
3708
3947
|
exports.URDU_STOP_WORDS = URDU_STOP_WORDS;
|
|
3948
|
+
exports.URDU_SUFFIXES = URDU_SUFFIXES;
|
|
3709
3949
|
exports.URDU_WEEKDAYS = URDU_WEEKDAYS;
|
|
3710
3950
|
exports.analyzeUrdu = analyzeUrdu;
|
|
3711
3951
|
exports.compareUrdu = compareUrdu;
|
|
@@ -3716,6 +3956,7 @@ exports.editDistance = editDistance;
|
|
|
3716
3956
|
exports.filterStopWords = filterStopWords;
|
|
3717
3957
|
exports.foldUrdu = foldUrdu;
|
|
3718
3958
|
exports.formatUrduDate = formatUrduDate;
|
|
3959
|
+
exports.getAffixes = getAffixes;
|
|
3719
3960
|
exports.getUrduMonthName = getUrduMonthName;
|
|
3720
3961
|
exports.getUrduWeekdayName = getUrduWeekdayName;
|
|
3721
3962
|
exports.hasUrduSpecificLetters = hasUrduSpecificLetters;
|
|
@@ -3734,6 +3975,8 @@ exports.searchUrduRanked = searchUrduRanked;
|
|
|
3734
3975
|
exports.sortUrdu = sortUrdu;
|
|
3735
3976
|
exports.splitSentences = splitSentences;
|
|
3736
3977
|
exports.splitWords = splitWords;
|
|
3978
|
+
exports.stemUrdu = stemUrdu;
|
|
3979
|
+
exports.stemUrduText = stemUrduText;
|
|
3737
3980
|
exports.timeAgoUrdu = timeAgoUrdu;
|
|
3738
3981
|
exports.toArabicIndicDigits = toArabicIndicDigits;
|
|
3739
3982
|
exports.toEnglishDigits = toEnglishDigits;
|