Spaces:
Sleeping
Sleeping
File size: 4,794 Bytes
3d9ba5b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | import os
import re
from typing import List, Set
class ArabicPreprocessor:
"""
Arabic NLP Preprocessor with diacritics removal, letter normalization,
kashida removal, light stemming, and stop-word filtering.
"""
TASHKEEL_REGEX = re.compile(r'[\u0617-\u061A\u064B-\u0652]')
TATWEEL_REGEX = re.compile(r'\u0640')
PUNCTUATION_REGEX = re.compile(r'[ุุุยซยป""\'\(\)\[\]\{\}\.,:!ุ\-_/\\|*~`@#$%^&+=<>]')
NON_ARABIC_REGEX = re.compile(r'[^\u0600-\u06FF\s]')
# Affixes for Light Stemmer
PREFIXES = ['ูุงู', 'ูุงู', 'ุจุงู', 'ูุงู', 'ุงู', 'ูู', 'ูุจ', 'ูุจ', 'ูุณ', 'ูุณ', 'ู', 'ู', 'ุจ', 'ู', 'ู', 'ุณ', 'ู', 'ุช', 'ู', 'ุง']
SUFFIXES = ['ูู
ุง', 'ูู
ุง', 'ูู
', 'ูู', 'ูู
', 'ูู', 'ูุง', 'ูุง', 'ุงู', 'ูู', 'ูู', 'ุงุช', 'ุชู
', 'ุชู', 'ูุง', 'ุชุง', 'ุชู', 'ูู', 'ู', 'ู', 'ู', 'ุช', 'ุฉ']
def __init__(self, stopwords_file: str = None):
self.stopwords: Set[str] = set()
if stopwords_file and os.path.exists(stopwords_file):
self.load_stopwords(stopwords_file)
else:
default_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'arabic_stopwords.txt')
if os.path.exists(default_path):
self.load_stopwords(default_path)
def load_stopwords(self, filepath: str):
with open(filepath, 'r', encoding='utf-8') as f:
for line in f:
word = line.strip()
if word:
self.stopwords.add(self.normalize_letters(self.remove_tashkeel(word)))
def remove_tashkeel(self, text: str) -> str:
"""Removes Arabic vowel marks (Harakat/Tashkeel)."""
return self.TASHKEEL_REGEX.sub('', text)
def remove_tatweel(self, text: str) -> str:
"""Removes elongation character (Kashida/Tatweel)."""
return self.TATWEEL_REGEX.sub('', text)
def normalize_letters(self, text: str) -> str:
"""Standardizes varied Arabic letter forms."""
# Normalize Alefs
text = re.sub(r'[ุฅุฃุขูฑ]', 'ุง', text)
# Normalize Yaa / Alef Maksura
text = re.sub(r'ู', 'ู', text)
# Normalize Taa Marbuta
text = re.sub(r'ุฉ', 'ู', text)
# Normalize Hamza variants
text = re.sub(r'ุค', 'ุก', text)
text = re.sub(r'ุฆ', 'ุก', text)
return text
def clean_punctuation(self, text: str) -> str:
"""Removes punctuation symbols while keeping spaces."""
return self.PUNCTUATION_REGEX.sub(' ', text)
def light_stem(self, word: str) -> str:
"""
Light stemming for Arabic words by systematically stripping
common prefixes and suffixes while protecting root lengths.
"""
if len(word) <= 3:
return word
# Strip prefixes
for prefix in sorted(self.PREFIXES, key=len, reverse=True):
if word.startswith(prefix) and len(word) - len(prefix) >= 3:
word = word[len(prefix):]
break
# Strip suffixes
for suffix in sorted(self.SUFFIXES, key=len, reverse=True):
if word.endswith(suffix) and len(word) - len(suffix) >= 3:
word = word[:-len(suffix)]
break
return word
def preprocess_word(self, word: str, stem: bool = False) -> str:
"""Cleans and standardizes a single Arabic word."""
word = self.remove_tashkeel(word)
word = self.remove_tatweel(word)
word = self.normalize_letters(word)
word = self.clean_punctuation(word).strip()
if stem:
word = self.light_stem(word)
return word
def preprocess_text(self, text: str, remove_stopwords: bool = True, stem: bool = False) -> str:
"""Full pipeline for cleaning and standardizing raw Arabic text."""
if not text:
return ""
text = self.remove_tashkeel(text)
text = self.remove_tatweel(text)
text = self.normalize_letters(text)
text = self.clean_punctuation(text)
words = text.split()
processed_words = []
for w in words:
w_norm = self.normalize_letters(w)
if remove_stopwords and (w in self.stopwords or w_norm in self.stopwords):
continue
if stem:
w = self.light_stem(w)
if len(w) > 0:
processed_words.append(w)
return ' '.join(processed_words)
def tokenize_words(self, text: str, remove_stopwords: bool = False, stem: bool = False) -> List[str]:
"""Tokenizes text into a list of cleaned words."""
cleaned = self.preprocess_text(text, remove_stopwords=remove_stopwords, stem=stem)
return cleaned.split()
|