# دستورالعمل جامع نرمال‌سازی جستجو برای متون عربی و اسلامی ### (Comprehensive Arabic & Persian Search Normalization Specification) --- ## ۱. مقدمه و بیان مسئله (Executive Summary & Problem Statement) در پایگاه‌های داده و سامانه‌های متنی اسلامی و حدیثی، جستجوی متنی با چالش‌های بنیادین زبان‌شناختی و فنی مواجه است: 1. **گوناگونی رسم‌الخط و کدگذاری یونیکد (Unicode Homoglyphs):** نویسه‌هایی مانند الف همزه‌دار (`أ`، `إ`)، الف ممدوده (`آ`)، الف وصل (`ٱ`) و الف ساده (`ا`) دارای کدهای یونیکد کاملاً متفاوتی هستند. 2. **اعراب و اعجام (Harakat / Tashkeel):** متون احادیث و اسناد دینی معمولاً با حرکت‌گذاری کامل (مانند `إِنَّمَا الأَعْمَالُ`) در پایگاه داده ذخیره شده‌اند، در حالی که کاربران جستجوهای خود را بدون اعراب (`انما الاعمال` یا `انما`) تایپ می‌کنند. در مقایسه‌های متنی پیش‌فرض دیتابیس (`LIKE` یا `ILIKE` و `icontains`)، وجود اعراب در میان حروف کلمه باعث می‌شود کلمه کاربر تطبیق داده نشود و نتیجه «هیچ رکوردی یافت نشد» برگردد. 3. **تداخل کاراکترهای فارسی و عربی:** در کیبوردهای کاربران (موبایل و دسکتاپ)، حروفی چون «ی» و «ي»، «ک» و «ك»، «ه» و «ة» مدام جابه‌جا می‌شوند. 4. **علائم قرآنی و وقوف:** نمادهایی مانند «الف خنجری» (`ٰ` مانند `رَحْمٰنِ`)، علائم وقف و نمادهای تعظیم اگر نرمال نشوند، جستجو را مسدود می‌کنند. > **هدف:** هر متنی که کاربر با هر شکلی از کیبورد (با اعراب، بدون اعراب، با همزه یا بدون همزه، فارسی یا عربی) جستجو کرد، سیستم باید دقیقاً مفهوم آن را درک کرده و تمامی رکوردهای منطبق را بدون وابستگی به شکل ظاهری نگارش پیدا کند. --- ## ۲. دسته‌بندی جامع کاراکترها و حالات نرمال‌سازی (Normalization Taxonomy) ### ۲.۱. خانواده الف‌ها و همزه‌ها (Alef & Hamza Variants) الف در رسم‌الخط عربی دارای حالت‌های متعددی است که در جستجو باید همگی یکپارچه شوند: | نویسه اصلی | نام یونیکد | کد یونیکد | هدف نرمال‌سازی | مثال جستجو | مثال در دیتابیس | | :--- | :--- | :--- | :--- | :--- | :--- | | **أ** | Arabic Letter Alef With Hamza Above | `U+0623` | **ا** (`U+0627`) | أنما | انما / إِنَّمَا | | **إ** | Arabic Letter Alef With Hamza Below | `U+0625` | **ا** (`U+0627`) | إسناد | اسناد | | **آ** | Arabic Letter Alef With Madda Above | `U+0622` | **ا** (`U+0627`) | آثار | اثار | | **ٱ** | Arabic Letter Alef Wasla | `U+0671` | **ا** (`U+0627`) | ٱمرؤ | امرؤ | | **ا** | Arabic Letter Alef (Bare) | `U+0627` | **ا** (`U+0627`) | اعمال | أعمال | | **ٴ** | Arabic Letter High Hamza | `U+0674` | *حذف* یا **ا** | — | — | #### حالات دیگر همزه‌ها (Other Hamza Forms): - **همزه روی واو (`ؤ` - `U+0624`):** در جستجوی آسان‌گیر (Lenient)، همزه روی واو به `و` (`U+0648`) نرمال می‌شود تا کاربر با سرچ «مومن» بتواند «مؤمن» را پیدا کند یا بالعکس («مسؤول» / «مسئول»). - **همزه روی یاء / نبره (`ئ` - `U+0626`):** به `ی` / `ي` تبدیل می‌شود تا کلماتی چون «قائل»، «قایل»، «هیئة»، «هيئة» هم‌ارز شوند. - **همزه تنها روی خط (`ء` - `U+0621`):** حذف یا تبدیل به فاصله در صورت نیاز. --- ### ۲.۲. خانواده یاء و الف مقصوره (Yeh & Alef Maksura) یکی از پرتکرارترین خطاها در جستجوی عربی و فارسی مربوط به حرف «ی» است: | نویسه اصلی | نام | کد یونیکد | رفتار نرمال‌سازی | مثال | | :--- | :--- | :--- | :--- | :--- | | **ي** | یاء عربی دو نقطه | `U+064A` | تبدیل به نویسه واحد مبنا (مثلاً `ی` یا `ي`) | علي / علی | | **ى** | الف مقصوره عربی (بی‌نقطه) | `U+0649` | تبدیل به `ی` یا `ا` بر اساس سیاست پروژه | موسی / موسي / موسى | | **ی** | یای فارسی بدون نقطه | `U+06CC` | تبدیل به نویسه واحد مبنا | حدیث / حديث | | **ئ** | یاء با همزه | `U+0626` | تبدیل به نویسه واحد مبنا | بئر / بیر | > **نکته تخصصی در متون دینی:** الف مقصوره (`ى` در انتهای کلماتی چون «حتى»، «إلى»، «موسى») در کیبورد کاربران گاهی با `ی`، گاهی با `ي` و حتی گاهی به اشتباه با `ا` («حتا») نوشته می‌شود. نرمال‌سازی `[ي ى ی ئ]` به یک نویسه پایدار، پوشش جستجو را به ۱۰۰٪ می‌رساند. --- ### ۲.۳. کاف عربی و فارسی (Kaf Normalization) - **ك** (کاف عربی با نشان همزه/کاف کوچک: `U+0643`) - **ک** (کاف فارسی سرکش‌دار: `U+06A9`) - **قاعده:** تبدیل هر دو به یک فرم استاندارد (مثلاً `ك` برای متون عربی یا `ک`). --- ### ۲.۴. تاء مربوطه و هاء (Teh Marbuta & Heh) - **ة** (تاء مربوطه: `U+0629`) - **ه** (هاء: `U+0647`) - **ۀ** (هاء با همزه: `U+06C0`) - **تحلیل رفتاری:** کاربران در تایپ سریع اسامی یا اصطلاحات اغلب تاء مربوطه را با هاء جابه‌جا می‌زنند: - «معاویه» ↔ «معاوية» - «فاطمه» ↔ «فاطمة» - «صحابه» ↔ «صحابة» - «رواة» ↔ «رواه» - **قاعده سرچ نرمال:** برای جستجوی متنی، `ة` به `ه` تبدیل می‌شود تا تفاوت نگارشی کاربر باعث حذف رکورد نشود. --- ### ۲.۵. حرکات، اعراب و تنوین‌ها (Tashkeel / Harakat / Diacritics) - حیاتی‌ترین بخش تمام حرکات زیر باید از متن ورودی و از نسخه ایندکس‌شده جستجو **کاملاً حذف شوند**: | نام حرکت | علامت | کد یونیکد | اثر در جستجوی خام دیتابیس | | :--- | :---: | :---: | :--- | | **فتحه (Fatha)** | َ | `U+064E` | مانع تطابق متن ساده می‌شود | | **ضمه (Damma)** | ُ | `U+064F` | مانع تطابق متن ساده می‌شود | | **کسره (Kasra)** | ِ | `U+0650` | مانع تطابق متن ساده می‌شود | | **تنوین نصب (Fathatan)** | ً | `U+064B` | مانع تطابق | | **تنوین رفع (Dammatan)** | ٌ | `U+064C` | مانع تطابق | | **تنوین جر (Kasratan)** | ٍ | `U+064D` | مانع تطابق | | **سکون (Sukun)** | ْ | `U+0652` | مانع تطابق | | **تشدید (Shadda)** | ّ | `U+0651` | مانع تطابق کلمات دارای تشدید | | **الف خنجری (Dagger Alef)** | ٰ | `U+0670` | **بسیار خطرناک:** در «رَحْمٰنِ»، «إِلٰهَ»، «هٰذَا»، «إِسْمٰعِيل» اگر حذف نشود، سرچ «رحمن» هرگز «رحمٰن» را پیدا نمی‌کند! | | **مده (Maddah)** | ٓ | `U+0653` | مانع تطابق | | **همزه فوقانی اعرابی** | ٔ | `U+0654` | مانع تطابق | | **همزه تحتانی اعرابی** | ٕ | `U+0655` | مانع تطابق | --- ### ۲.۶. کشیدگی، تطویل و نشانه‌های نامرئی (Tatweel & Invisible Characters) - **تطویل / کشیده (`ـ` - `U+0640`):** برای تنظیم طول خطوط در متون کهن یا زیبایی متنی به کار می‌رود (مثل `صـــــراط` یا `رســــول`). این کاراکتر باید به کلی حذف شود. - **نیم‌فاصله (Zero-Width Non-Joiner - `U+200C`):** در متون فارسی و اسامی ترکیبی وجود دارد؛ باید به فاصله عادی یا حذف کامل تبدیل شود. - **اتصال‌دهنده مجازی (ZWJ - `U+200D`):** باید حذف شود. - **نشانگرهای جهت یونیکد (LRM `U+200E` و RLM `U+200F`):** باید کاملاً حذف شوند. --- ### ۲.۷. نشانه‌ها و نمادهای وقوف قرآنی و مذهبی (Quranic Symbols & Waqf Marks) در متون روایی و قرآنی نمادهای ویژه‌ای در یونیکد ذخیره می‌شوند که باید در لایه سرچ پالایش گردند: - علائم وقف قرآنی: `ۖ` (`U+06D6`), `ۗ` (`U+06D7`), `ۘ` (`U+06D8`), `ۙ` (`U+06D9`), `ۚ` (`U+06DA`), `ۛ` (`U+06DB`), `ۜ` (`U+06DC`), `۝` (`U+06DD`), `۞` (`U+06DE`), `۟` (`U+06DF`), `۠` (`U+06E0`), `ۡ` (`U+06E1`), `ۢ` (`U+06E2`), `ۣ` (`U+06E3`), `ۤ` (`U+06E4`). - نمادهای لیگچر مذهبی: `ﷺ` (`U+FDFA`), `ﷻ` (`U+FDFB`), `﷽` (`U+FDFD`), `ؑ` (`U+0611`). - پرانتزها و براکت‌های قرآنی و نقل‌قول: `﴿`، `﴾`، `«`، `»`، `[`، `]`، `(`، `)`. --- ### ۲.۸. ارقام و اعداد (Digits) - ارقام عربی-مشرقی: `[٠, ١, ٢, ٣, ٤, ٥, ٦, ٧, ٨, ٩]` - ارقام فارسی: `[۰, ۱, ۲, ۳, ۴, ۵, ۶, ۷, ۸, ۹]` - ارقام استاندارد لاتین: `[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]` - **قاعده:** تبدیل تمام ارقام به ارقام استاندارد (0-9) تا سرچ شماره حدیث یا جلد و صفحه فارغ از نوع کیبورد عدد را پیدا کند. --- ## ۳. ماتریس مقایسه‌ای سناریوهای سرچ (Search Scenario Matrix) | ورودی کاربر در سرچ | متن در دیتابیس | وضعیت جستجوی فعلی (خام) | وضعیت پس از نرمال‌سازی | | :--- | :--- | :---: | :---: | | `انما` | `أنما الأعمال بالنيات` | ❌ No result (تفاوت `ا` و `أ`) | ✅ منطبق و پیدا می‌شود | | `انما` | `إِنَّمَا الأَعْمَالُ بِالنِّيَّاتِ` | ❌ No result (وجود کسره، تشدید، فتحه) | ✅ منطبق و پیدا می‌شود | | `أبو هريرة` | `ابو هريره` | ❌ No result (تفاوت `أ/ا` و `ة/ه`) | ✅ منطبق و پیدا می‌شود | | `صحیح بخاری` | `صَحِيحُ الْبُخَارِيِّ` | ❌ No result (تفاوت اعراب، `ی/ي`) | ✅ منطبق و پیدا می‌شود | | `رحمن` | `الرَّحْمٰنِ الرَّحِيمِ` | ❌ No result (وجود الف خنجری `ٰ`) | ✅ منطبق و پیدا می‌شود | | `مومن` | `إِنَّمَا الْمُؤْمِنُونَ إِخْوَةٌ` | ❌ No result (تفاوت `و` با `ؤ`) | ✅ منطبق و پیدا می‌شود | | `صراط` | `صــــراط الذين` | ❌ No result (وجود کشیده `ـ`) | ✅ منطبق و پیدا می‌شود | | `حدیث ۱۱۰` | `حديث 110` یا `حديث ١١٠` | ❌ No result (تفاوت ارقام) | ✅ منطبق و پیدا می‌شود | --- ## ۴. گزینه‌ها و معماری پیاده‌سازی فنی در Django و PostgreSQL برای اعمال این نرمال‌سازی در سیستم، سه رویکرد معماری وجود دارد: ### 🟢 گزینه اول: الگوی ستون جستجوی نرمال‌شده (Normalized Shadow Column / Search Column) — **[رویکرد پیشنهادی و استاندارد]** در این الگو، متن اصلی برای نمایش دست‌نخورده باقی می‌ماند (تا اعراب و زیبایی اصیل آن در UI حفظ شود)، اما یک ستون متنی نرمال‌شده در کنار آن ایجاد و ایندکس‌گذاری می‌شود: 1. **در مدل‌ها (`Hadis`، `HadisCategory`، `Transmitter` و ...):** - افزودن فیلد `normalized_text = models.TextField(blank=True, db_index=True)` یا استفاده از `django.contrib.postgres.search.SearchVector`. - در متد `save()` مدل، متن اصلی از تابع نرمال‌ساز عبور کرده و فیلد نرمال‌شده به صورت خودکار پر می‌شود. - ایجاد یک اسکریپت ساده migration برای پر کردن یک‌باره مقادیر رکوردهای موجود. 2. **در لایه Queryset / View:** - عبارت سرچ کاربر (`search_query`) توسط همان تابع پایتون نرمال‌سازی می‌شود: `normalized_q = normalize_text(search_query)`. - جستجو روی ستون `normalized_text__icontains=normalized_q` انجام می‌شود. 3. **مزایا:** - **فوق‌العاده سریع (High Performance):** دیتابیس مستقیماً روی ستون ایندکس‌شده کوئری می‌زند بدون اینکه در هر ریکوئست تابع یا رجکس سنگین روی میلیون‌ها کاراکتر اجرا شود. - **سادگی و پایداری:** سازگاری کامل با معماری فعلی Django بدون نیاز به نصب اکستنشن‌های پیچیده C در دیتابیس سرور. - **دقت ۱۰۰٪:** تضمین می‌کند که منطق سمت پایتون در هر دو طرف ذخیره و جستجو دقیقاً یکی است. --- ### 🟡 گزینه دوم: تابع پایگاه داده در سطح PostgreSQL (Database-Level Stored Function & Functional Index) 1. ایجاد یک تابع PL/pgSQL در PostgreSQL (مثلاً `fn_normalize_arabic(text)`). 2. ساخت ایندکس تابعی: ```sql CREATE INDEX idx_hadis_normalized_text ON hadis_hadis (fn_normalize_arabic(text)); ``` 3. در جنگو با استفاده از `Func` یا Raw SQL: ```python queryset.filter(Q(normalized_text_func__icontains=normalize_text(query))) ``` 4. **مزایا:** عدم نیاز به ذخیره دیتای مضاعف در ستون جداگانه. 5. **معایب:** وابستگی شدید به دیتابیس، سختی مایگریشن در محیط‌های توسعه و تست SQLite/Docker، و پیچیدگی نگهداری لاجیک در SQL. --- ### 🔴 گزینه سوم: استفاده از Regex در زمان کوئری (Query-time Regex) 1. تبدیل هر حرف از کلمه سرچ به یک گروه رجکس؛ مثلاً تبدیل `انما` به: `[اأإآٱ][ًٌٍَُِّْٰ]*ن[ًٌٍَُِّْٰ]*م[ًٌٍَُِّْٰ]*[اأإآٱ]` 2. ارسال به دیتابیس با `text__iregex=pattern`. 3. **معایب:** - **بسیار کند:** دیتابیس نمی‌تواند از هیچ ایندکسی استفاده کند (Full Table Scan با Regex Engine). - با افزایش تعداد احادیث و اسناد، پاسخ سرور از چند میلی‌ثانیه به چند ثانیه افزایش می‌یابد و بار سرور را به شدت بالا می‌برد. --- ## ۵. کد مرجع پایتون برای تابع نرمال‌سازی (Python Reference Implementation) این تابع کامل‌ترین و بهینه‌ترین پیاده‌سازی منطبق با استاندارد Unicode Consortium برای متون عربی و فارسی است: ```python import re import unicodedata # 1. حرکات، اعراب، تنوین‌ها، تشدید، سکون و الف خنجری # شامل بازه U+064B تا U+065F و الف مقصوره بالایی U+0670 ARABIC_DIACRITICS_REGEX = re.compile(r'[\u064B-\u065F\u0670\u06D6-\u06ED]') # 2. کاراکتر کشیدگی / تطویل TATWEEL_REGEX = re.compile(r'\u0640') # 3. جدول نگاشت الف‌ها و کاراکترهای چندشکلی ARABIC_NORMALIZATION_MAP = str.maketrans({ # انواع الف به الف ساده 'أ': 'ا', 'إ': 'ا', 'آ': 'ا', 'ٱ': 'ا', # انواع یاء و الف مقصوره به یای استاندارد 'ي': 'ی', 'ى': 'ی', 'ئ': 'ی', # کاف عربی به کاف یکسان 'ك': 'ک', # تاء مربوطه و هاء 'ة': 'ه', 'ۀ': 'ه', # واو همزه‌دار 'ؤ': 'و', # ارقام عربی مشرقی و فارسی به ارقام استاندارد '٠': '0', '١': '1', '٢': '2', '٣': '3', '٤': '4', '٥': '5', '٦': '6', '٧': '7', '٨': '8', '٩': '9', '۰': '0', '۱': '1', '۲': '2', '۳': '3', '۴': '4', '۵': '5', '۶': '6', '۷': '7', '۸': '8', '۹': '9', }) # 4. کاراکترهای کنترلی و نامرئی (Zero-width spaces, LRM, RLM) ZERO_WIDTH_REGEX = re.compile(r'[\u200B-\u200F\u202A-\u202E\uFEFF]') def normalize_for_search(text: str) -> str: """ متن ورودی را بر اساس قواعد استاندارد جستجوی متون عربی و اسلامی نرمال‌سازی می‌کند: 1. حذف کاراکترهای کنترلی پنهان و نیم‌فاصله‌های نامتعارف 2. حذف کامل تمام اعراب‌ها، حرکات، تشدید، تنوین‌ها و الف خنجری (Tashkeel) 3. حذف علامت کشیدگی (تطویل / کشیده) 4. یکسان‌سازی الف‌ها (أ، إ، آ، ٱ -> ا) 5. یکسان‌سازی یاء و الف مقصوره (ي، ى، ئ -> ی) 6. یکسان‌سازی کاف (ك -> ک) 7. یکسان‌سازی تاء مربوطه (ة -> ه) 8. یکسان‌سازی واو همزه‌دار (ؤ -> و) 9. تبدیل ارقام عربی و فارسی به ارقام استاندارد 10. یکپارچه‌سازی فاصله‌های خالی چندگانه """ if not text or not isinstance(text, str): return "" # ۱. نرمال‌سازی فرم یونیکد (NFKC) text = unicodedata.normalize('NFKC', text) # ۲. حذف کاراکترهای نامرئی text = ZERO_WIDTH_REGEX.sub('', text) # ۳. حذف حرکات و اعراب text = ARABIC_DIACRITICS_REGEX.sub('', text) # ۴. حذف تطویل text = TATWEEL_REGEX.sub('', text) # ۵. نگاشت الف‌ها و کاراکترهای هم‌ارز text = text.translate(ARABIC_NORMALIZATION_MAP) # ۶. حذف فاصله‌های اضافی مکرر text = re.sub(r'\s+', ' ', text).strip() return text ``` --- ## ۶. نقشه راه اجرایی پیشنهادی (Recommended Implementation Roadmap) 1. **فاز ۱ — بررسی و تأیید نهایی:** - تأیید قوانین نرمال‌سازی فوق توسط کارفرما و تیم فنی (به‌ویژه در خصوص تبدیل `ة` به `ه` و `ؤ` به `و`). 2. **فاز ۲ — اضافه کردن ماژول Utility:** - افزودن فایل `backend/utils/text_normalizer.py` شامل تابع `normalize_for_search`. 3. **فاز ۳ — پیاده‌سازی پایگاه داده:** - اضافه کردن فیلدهای `search_text` یا `normalized_text` در مدل‌های کلیدی (`Hadis`، `HadisCategory`، `Transmitter`، `HadisCorrection`، `ReferenceBook`). - تنظیم پر شدن خودکار در `save()`. - اجرای یک Management Command برای نرمال‌سازی داده‌های قبلی. 4. **فاز ۴ — به‌روزرسانی Queryset های جستجو:** - اصلاح متدهای `apply_search_filter` در Viewها تا ورودی کاربر را پیش از جستجو نرمال کند. 5. **فاز ۵ — تست و اعتبارسنجی:** - نوشتن تست‌های خودکار (Unit Tests) برای کلمات چالش‌برانگیز مثل «أنما»، «إِنَّمَا»، «الرَّحْمٰنِ»، «أبو هريرة»، «مسؤول» و اطمینان از نتیجه مثبت در تمامی حالات.