Skip to content

Commit d80efa1

Browse files
add tashfeerPannedWords() function
1 parent 34e87c7 commit d80efa1

4 files changed

Lines changed: 251 additions & 31 deletions

File tree

src/constants/panned-words.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const PANNED_WORDS = [
1616
'قدس',
1717
'قصى',
1818
'طبع',
19+
'قتل',
1920
'خان',
2021
'كتائب',
2122
'عز',

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as constants from './constants';
22
import * as script from './scripts';
3+
import * as utils from './utils';
34

45
export const ArabicServices = {
56
constants,
7+
utils,
68
...script,
79
};

src/scripts/scripts.ts

Lines changed: 88 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ARABIC_DOTLESS_DICT, TASHKEEL } from '../constants';
1+
import { ARABIC_DOTLESS_DICT, PANNED_WORDS, TASHKEEL } from '../constants';
22
import {
33
ALEF,
44
ALONE_LETTERS,
@@ -10,7 +10,7 @@ import {
1010
WAW,
1111
YAA,
1212
} from '../constants/arabic-letters';
13-
import { setCharAt } from '../utils';
13+
import { setCharAt, similarityScore } from '../utils';
1414

1515
/**
1616
* Remove all tashkeel from text
@@ -71,6 +71,35 @@ export function removeTatweel(text: string): string {
7171
return text.replace(/ـ/g, '');
7272
}
7373

74+
/**
75+
* Converts a word to its pronounced letter representations based on PRONOUNCED_LETTERS.
76+
* @param {string} word - The word to convert.
77+
* @returns {string} The word with pronounced letters separated by spaces.
78+
*/
79+
export function wordToLetters(word: string): string {
80+
let newWord = '';
81+
82+
// Loop through each character in the input word
83+
for (let i = 0; i < word.length; i++) {
84+
const letter = word[i];
85+
86+
// Check if the current letter has a pronunciation in PRONOUNCED_LETTERS
87+
if (PRONOUNCED_LETTERS.hasOwnProperty(letter)) {
88+
newWord += PRONOUNCED_LETTERS[letter];
89+
90+
// Add a space after the pronounced letter unless it's the last letter in the word
91+
if (i !== word.length - 1) {
92+
newWord += ' ';
93+
}
94+
} else {
95+
// If the letter is not in PRONOUNCED_LETTERS, keep it unchanged
96+
newWord += letter;
97+
}
98+
}
99+
100+
return newWord.trim();
101+
}
102+
74103
/**
75104
* Removes predefined affixes (prefixes and suffixes) from an Arabic word if it starts or ends with those affixes.
76105
* This function is designed specifically for processing Arabic text, where certain affixes might need to be removed
@@ -199,6 +228,23 @@ function tashfeerCharacter(character: string): string {
199228
return replacementCharacter;
200229
}
201230

231+
/**
232+
* Performs tashfeer encryption on a given word.
233+
* @param {string} word - The input word to be encrypted.
234+
* @param {number} [level=0] - The encryption level (default is 0).
235+
* @returns {string} The encrypted word.
236+
*/
237+
function tashfeerHandler(word: string, level: number = 0): string {
238+
const wordLength = word.length;
239+
// Calculate the encryption level based on the input level and word length
240+
const n = calculateEncryptionLevel(level, wordLength);
241+
// Generate a list of random indexes for encryption
242+
const randomIndexes = getRandomIndexes(n, wordLength);
243+
// Process the word for encryption using random indexes
244+
const outputWord = tashfeerWord(word, randomIndexes);
245+
return outputWord;
246+
}
247+
202248
/**
203249
* Performs tashfeer encryption on a given sentence.
204250
* @param {string} sentence - The input sentence to be encrypted.
@@ -207,44 +253,55 @@ function tashfeerCharacter(character: string): string {
207253
export function tashfeer(sentence: string, levelOfTashfeer: number = 1): string {
208254
let new_sentence = '';
209255
for (const word of sentence.split(' ')) {
210-
const wordLength = word.length;
211-
// Calculate the encryption level based on the input level and word length
212-
// encryptionLevel is used to determine the number of characters to be replaced (encrypted)
213-
const encryptionLevel = calculateEncryptionLevel(levelOfTashfeer, wordLength);
214-
// Generate a list of random indexes for encryption
215-
const randomIndexes = getRandomIndexes(encryptionLevel, wordLength);
216-
// Process the word for encryption using random indexes
217-
const outputWord = tashfeerWord(word, randomIndexes);
218-
new_sentence += outputWord + ' ';
256+
new_sentence += tashfeerHandler(word, levelOfTashfeer) + ' ';
219257
}
220258
return new_sentence.trim();
221259
}
222260

223261
/**
224-
* Converts a word to its pronounced letter representations based on PRONOUNCED_LETTERS.
225-
* @param {string} word - The word to convert.
226-
* @returns {string} The word with pronounced letters separated by spaces.
262+
* Calculates a ratio that likely represents the degree of similarity of a given string to elements in a 'panned' array.
263+
*
264+
* @param {string} string - The string to be compared against the elements in the 'panned' array.
265+
* @returns {number} The highest similarity ratio found between the string and elements in 'panned'.
227266
*/
228-
export function wordToLetters(word: string): string {
229-
let newWord = '';
230-
231-
// Loop through each character in the input word
232-
for (let i = 0; i < word.length; i++) {
233-
const letter = word[i];
267+
function pannedSimilarityRatio(string: string): number {
268+
let maximumSimilarity = -1;
269+
for (const i in PANNED_WORDS) {
270+
const calculatedSimilarity = similarityScore(string, PANNED_WORDS[i]);
271+
if (calculatedSimilarity > maximumSimilarity) {
272+
maximumSimilarity = calculatedSimilarity;
273+
}
274+
}
275+
return maximumSimilarity * 100;
276+
}
234277

235-
// Check if the current letter has a pronunciation in PRONOUNCED_LETTERS
236-
if (PRONOUNCED_LETTERS.hasOwnProperty(letter)) {
237-
newWord += PRONOUNCED_LETTERS[letter];
278+
/**
279+
* Checks if a string is similar to any 'panned' words based on a predefined similarity ratio.
280+
*
281+
* @param {string} string - The string to be checked.
282+
* @returns {boolean} True if the string is similar to any 'panned' word, false otherwise.
283+
*/
284+
function checkIfPannedWord(string: string): boolean {
285+
const std_ratio = 70;
286+
return pannedSimilarityRatio(removeArabicAffixes(string)) >= std_ratio;
287+
}
238288

239-
// Add a space after the pronounced letter unless it's the last letter in the word
240-
if (i !== word.length - 1) {
241-
newWord += ' ';
242-
}
289+
/**
290+
* Performs tashfeer encryption on a given sentence, but only for words that are considered "panned" words.
291+
* Panned words are determined based on a predefined similarity ratio.
292+
*
293+
* @param {string} sentence - The input sentence to be encrypted.
294+
* @param {number} [levelOfTashfeer=2] - The encryption level (default is 2).
295+
* @returns {string} The encrypted sentence with tashfeer applied to panned words.
296+
*/
297+
export function tashfeerPannedWords(sentence: string, levelOfTashfeer: number = 2): string {
298+
let new_sentence = '';
299+
for (const word of sentence.split(' ')) {
300+
if (checkIfPannedWord(word)) {
301+
new_sentence += tashfeerHandler(word, levelOfTashfeer) + ' ';
243302
} else {
244-
// If the letter is not in PRONOUNCED_LETTERS, keep it unchanged
245-
newWord += letter;
303+
new_sentence += word + ' ';
246304
}
247305
}
248-
249-
return newWord.trim();
306+
return new_sentence.trim();
250307
}

tests/scripts.test.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,163 @@ describe('#wordToLetters', () => {
102102
expect(result).toEqual('هاء ذال هاء جيم ميم لام تاء_مربوطة ألف خاء راء ألف_لينة');
103103
});
104104
});
105+
106+
describe('#removeArabicAffixes', () => {
107+
it('should remove "أ" prefix from a word', () => {
108+
const word = 'أمل';
109+
const result = ArabicServices.removeArabicAffixes(word);
110+
expect(result).toEqual('مل');
111+
});
112+
113+
it('should remove "ا" prefix & suffix "ة" from a word', () => {
114+
const word = 'امرأة';
115+
const result = ArabicServices.removeArabicAffixes(word);
116+
expect(result).toEqual('مرأ');
117+
});
118+
119+
it('should remove "إ" prefix from a word', () => {
120+
const word = 'إنسان';
121+
const result = ArabicServices.removeArabicAffixes(word);
122+
expect(result).toEqual('نسان');
123+
});
124+
125+
it('should remove "ال" prefix from a word', () => {
126+
const word = 'الكتاب';
127+
const result = ArabicServices.removeArabicAffixes(word);
128+
expect(result).toEqual('كتاب');
129+
});
130+
131+
it('should remove "ي" prefix from a word', () => {
132+
const word = 'يوم';
133+
const result = ArabicServices.removeArabicAffixes(word);
134+
expect(result).toEqual('وم');
135+
});
136+
137+
it('should remove "ت" prefix from a word', () => {
138+
const word = 'تفاح';
139+
const result = ArabicServices.removeArabicAffixes(word);
140+
expect(result).toEqual('فاح');
141+
});
142+
143+
it('should remove "ن" prefix from a word', () => {
144+
const word = 'نجم';
145+
const result = ArabicServices.removeArabicAffixes(word);
146+
expect(result).toEqual('جم');
147+
});
148+
149+
it('should remove "ب" prefix from a word', () => {
150+
const word = 'بيت';
151+
const result = ArabicServices.removeArabicAffixes(word);
152+
expect(result).toEqual('يت');
153+
});
154+
155+
it('should remove "ة" suffix from a word', () => {
156+
const word = 'كتابة';
157+
const result = ArabicServices.removeArabicAffixes(word);
158+
expect(result).toEqual('كتاب');
159+
});
160+
161+
it('should remove "ه" suffix from a word', () => {
162+
const word = 'جديه';
163+
const result = ArabicServices.removeArabicAffixes(word);
164+
expect(result).toEqual('جدي');
165+
});
166+
167+
it('should remove "ي" suffix from a word', () => {
168+
const word = 'ذهبي';
169+
const result = ArabicServices.removeArabicAffixes(word);
170+
expect(result).toEqual('ذهب');
171+
});
172+
173+
it('should remove "ى" suffix from a word', () => {
174+
const word = 'منزلي';
175+
const result = ArabicServices.removeArabicAffixes(word);
176+
expect(result).toEqual('منزل');
177+
});
178+
179+
it('should remove "ية" suffix from a word', () => {
180+
const word = 'علمية';
181+
const result = ArabicServices.removeArabicAffixes(word);
182+
expect(result).toEqual('علم');
183+
});
184+
185+
it('should remove "ين" suffix from a word', () => {
186+
const word = 'موظفين';
187+
const result = ArabicServices.removeArabicAffixes(word);
188+
expect(result).toEqual('موظف');
189+
});
190+
191+
it('should remove "ون" suffix from a word', () => {
192+
const word = 'موظفون';
193+
const result = ArabicServices.removeArabicAffixes(word);
194+
expect(result).toEqual('موظف');
195+
});
196+
197+
it('should remove "هم" suffix from a word', () => {
198+
const word = 'طلابهم';
199+
const result = ArabicServices.removeArabicAffixes(word);
200+
expect(result).toEqual('طلاب');
201+
});
202+
});
203+
204+
describe('#similarityScore', () => {
205+
it('should return 1 for identical strings', () => {
206+
const s1 = 'مرحبا';
207+
const s2 = 'مرحبا';
208+
const result = ArabicServices.utils.similarityScore(s1, s2);
209+
expect(result).toEqual(1.0);
210+
});
211+
212+
it('should return 0 for completely different strings', () => {
213+
const s1 = 'شجرة';
214+
const s2 = 'طفل';
215+
const result = ArabicServices.utils.similarityScore(s1, s2);
216+
expect(result).toEqual(0.0);
217+
});
218+
219+
it('should return a value between 0 and 1 for similar strings', () => {
220+
const s1 = 'مرحبا';
221+
const s2 = 'مرحب';
222+
const result = ArabicServices.utils.similarityScore(s1, s2);
223+
expect(result).toBeGreaterThan(0.0);
224+
expect(result).toBeLessThan(1.0);
225+
});
226+
227+
it('should handle empty strings', () => {
228+
const s1 = '';
229+
const s2 = '';
230+
const result = ArabicServices.utils.similarityScore(s1, s2);
231+
expect(result).toEqual(1.0);
232+
});
233+
234+
it('should handle one empty string', () => {
235+
const s1 = 'مرحبا';
236+
const s2 = '';
237+
const result = ArabicServices.utils.similarityScore(s1, s2);
238+
expect(result).toEqual(0.0);
239+
});
240+
});
241+
242+
describe('#tashfeerPannedWords', () => {
243+
it('should perform tashfeer encryption on panned words only', () => {
244+
const sentence = 'جيش العدو يقتل الأطفال';
245+
const result = ArabicServices.tashfeerPannedWords(sentence);
246+
expect(result).not.toEqual(sentence);
247+
expect(result).toMatch(/الأطفال/);
248+
expect(result).not.toMatch(/جيش/);
249+
expect(result).not.toMatch(/العدو/);
250+
expect(result).not.toMatch(/يقتل/);
251+
});
252+
253+
it('should not perform tashfeer encryption on non-panned words', () => {
254+
const sentence = 'هذه جملة غير مشفرة';
255+
const result = ArabicServices.tashfeerPannedWords(sentence);
256+
expect(result).toEqual(sentence);
257+
});
258+
259+
it('should handle empty input', () => {
260+
const sentence = '';
261+
const result = ArabicServices.tashfeerPannedWords(sentence);
262+
expect(result).toEqual('');
263+
});
264+
});

0 commit comments

Comments
 (0)