urdu-text-utils 0.1.7 → 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 CHANGED
@@ -1,5 +1,17 @@
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
+
10
+ ## 0.1.8
11
+
12
+ - Fix jsDelivr monthly hit badge in README to use shields.io SVG endpoint.
13
+ - Update CDN documentation and package metadata.
14
+
3
15
  ## 0.1.7
4
16
 
5
17
  - Add Urdu date & time formatting module (`formatUrduDate`, `timeAgoUrdu`, `getUrduMonthName`, `getUrduWeekdayName`).
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # urdu-text-utils — Urdu Text Processing Toolkit for JavaScript & TypeScript
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/urdu-text-utils?style=flat-square&color=157f52&label=npm)](https://www.npmjs.com/package/urdu-text-utils)
4
- [![jsDelivr](https://data.jsdelivr.com/v1/package/npm/urdu-text-utils/badge?style=flat-square)](https://www.jsdelivr.com/package/npm/urdu-text-utils)
4
+ [![jsDelivr](https://img.shields.io/jsdelivr/npm/hm/urdu-text-utils?style=flat-square&label=jsDelivr)](https://www.jsdelivr.com/package/npm/urdu-text-utils)
5
5
  [![unpkg](https://img.shields.io/badge/unpkg-urdu--text--utils-blue?style=flat-square)](https://unpkg.com/urdu-text-utils/)
6
6
  [![CI](https://img.shields.io/github/actions/workflow/status/Zaid-maker/urdu-text-utils/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/Zaid-maker/urdu-text-utils/actions/workflows/ci.yml)
7
7
  [![minzipped size](https://img.shields.io/bundlejs/size/urdu-text-utils?style=flat-square&label=min%2Bgzip)](https://bundlejs.com/?q=urdu-text-utils)
@@ -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;