نرمالسازی متن فارسی
کد پایتون برای پاکسازی متن فارسی: یکسانسازی «ی» و «ک»، اصلاح نیمفاصله و رقمها و حذف اعراب، همراه با آزمونهای کوتاه.
کد · ۱۳ آبان ۱۴۰۵
python
import re
from typing import Literal, TypedDict
class NormalizeOptions(TypedDict, total=False):
digits: Literal["persian", "arabic", "latin", "keep"]
remove_diacritics: bool
normalize_spacing: bool
normalize_zwnj: bool
_DIGIT_SETS = {
"persian": "۰۱۲۳۴۵۶۷۸۹",
"arabic": "٠١٢٣٤٥٦٧٨٩",
"latin": "0123456789",
}
_DIACRITICS = re.compile(r"[\u0610-\u061a\u064b-\u065f\u0670\u06d6-\u06ed]")
def normalize_persian(text: str, options: NormalizeOptions | None = None) -> str:
"""متن فارسی را با گزینههای قابل تنظیم یکدست کند.
نویسههای عربی «ي» و «ك» همیشه به شکل فارسی تبدیل میشوند.
گزینهٔ digits مقصد رقمها را مشخص میکند: persian، arabic، latin یا
keep؛ مقدار پیشفرض persian است. گزینههای remove_diacritics،
normalize_spacing و normalize_zwnj همگی بهطور پیشفرض فعالاند.
اصلاح نیمفاصله فقط برای الگوهای رایج و با یک قاعدهٔ ساده انجام
میشود و جایگزین تحلیل زبانی کامل نیست.
"""
opts = options or {}
digit_target = opts.get("digits", "persian")
if digit_target not in (*_DIGIT_SETS, "keep"):
raise ValueError("مقدار گزینهٔ digits معتبر نیست")
result = text.translate(str.maketrans({"ي": "ی", "ك": "ک"}))
if digit_target != "keep":
target = _DIGIT_SETS[digit_target]
for source in _DIGIT_SETS.values():
result = result.translate(str.maketrans(source, target))
if opts.get("remove_diacritics", True):
result = _DIACRITICS.sub("", result)
if opts.get("normalize_spacing", True):
# فاصلههای افقی یکدست میشوند؛ مرز خطها حفظ میشود.
result = re.sub(r"[^\S\r\n]+", " ", result)
result = re.sub(r" *\n *", "\n", result)
result = re.sub(r" *\u200c *", "\u200c", result)
result = result.strip()
if opts.get("normalize_zwnj", True):
# برای پرهیز از تغییر واژهٔ مستقل «می»، فقط فعلهای متداول هدفاند.
verbs = r"(?:شود|شوند|شد|شدم|شدیم|شدی|شدید|کنم|کنی|کند|کنیم|کنید|کنند|کرد|کردم|کردیم|رود|روم|روی|رویم|روید|روند|آید|آیند|تواند|توانند|خواهم|خواهد|دانم|داند|دانند|بینم|بیند|بینند|گوید|گویند)"
result = re.sub(r"(?<![\w\u200c])(ن?می)[ \t]+(" + verbs + r")(?![\w\u200c])", r"\1\u200c\2".replace(r"\u200c", "\u200c"), result)
# «ها» به واژهٔ پیشین میچسبد؛ ادامههای رایج جمع نیز پشتیبانی میشوند.
result = re.sub(r"([\u0621-\u063a\u0641-\u064a\u0671-\u06d3])[ \t]+(ها(?:ی(?:م|ت|ش|مان|تان|شان)?|یم|یت|یش|مان|تان|شان)?)(?![\w\u200c])", r"\1\u200c\2".replace(r"\u200c", "\u200c"), result)
return result
if __name__ == "__main__":
assert normalize_persian(" كتاب ها 123 ٤٥٦ ") == "کتابها ۱۲۳ ۴۵۶"
assert normalize_persian("او می شود و آنها نمی کنند") == "او میشود و آنها نمیکنند"
assert normalize_persian("خانه های زیبا") == "خانههای زیبا"
assert normalize_persian("عَلِی", {"remove_diacritics": False}) == "عَلِی"
assert normalize_persian("عَلِی") == "علی"
assert normalize_persian("۱۲٣4", {"digits": "latin"}) == "1234"
assert normalize_persian("۱۲3", {"digits": "arabic"}) == "١٢٣"
assert normalize_persian("۱۲3", {"digits": "keep"}) == "۱۲3"
assert normalize_persian("می شود", {"normalize_zwnj": False}) == "می شود"
assert normalize_persian("الف ب", {"normalize_spacing": False}) == "الف ب"
assert normalize_persian("می\u200c شود\n کتاب ها") == "میشود\nکتابها"
assert normalize_persian(normalize_persian("كتاب ها می شود")) == "کتابها میشود"
print("همهٔ آزمونها با موفقیت گذشتند.")