رفتن به متن راهنما
همه‌ی راهنماها

ابزارهای متن

یکسان‌سازی متن فارسی و عربی، اصلاح نیم‌فاصله، تشخیص خط و جهت متن و ساخت اسلاگ برای نشانی اینترنتی، با رفتار دقیق هر ابزار.

برنامه RTLY-Kit 0.3.0آخرین بررسی زمان خواندن 10 دقیقه

در این صفحه

Normalizer

RtlyKit\Text\Normalizer متن فارسی را برای مقایسه و ذخیره آماده می‌کند. ورودی کاربر معمولاً شکل‌های عربی و فارسی حرف‌ها را قاطی می‌کند (صفحه‌کلید عربی «ي» و «ك» تایپ می‌کند). گاهی اعراب و کشیده و فاصلهٔ اضافه هم دارد. آن‌وقت یک واژه روی صفحه یکسان دیده می‌شود، ولی در مقایسهٔ رشته‌ای یا جست‌وجوی پایگاه داده با هم نمی‌خوانند.

UTF-8 نامعتبر

normalize() و fixHalfSpace() و clean() اول هر بایتی را که جزو یک دنبالهٔ UTF-8 معتبر نیست با U+FFFD (نویسهٔ جایگزین) عوض می‌کنند و بعد همهٔ گام‌هایشان را اجرا می‌کنند. نتیجه همیشه UTF-8 معتبر است و افزونه‌ای مثل mbstring لازم نیست.

normalize()

Normalizer::normalize($text, $removeDiacritics = true) این کارها را می‌کند:

  • حرف‌های عربی را فارسی می‌کند: «ك» به «ک»، «ي» و «ى» و «ے» به «ی»، «ة» و «ۀ» به «ه»، «ؤ» به «و»، «إ» و «أ» و «ٱ» به «ا».
  • رقم‌های عربی-هندی ( ٠-٩ ) را فارسی ( ۰-۹ ) می‌کند. رقم‌های انگلیسی دست نمی‌خورند.
  • کشیده (تطویل، U+0640) را حذف می‌کند.
  • اعراب (U+064B تا U+065F و U+0670) را حذف می‌کند، مگر اینکه آرگومان دوم را false بدهید.
  • هر دنبالهٔ فاصله (فاصله، تب، خط جدید) را به یک فاصله تبدیل می‌کند و دو سر متن را تمیز می‌کند.
PHP
<?php
require 'vendor/autoload.php';

use RtlyKit\Text\Normalizer;
use function RtlyKit\normalize_text;

echo Normalizer::normalize("علي  كتاب  ١٢٣ 456"), "\n";    // علی کتاب ۱۲۳ 456
echo Normalizer::normalize("كــتاب"), "\n";                 // کتاب
echo Normalizer::normalize("  سلام \n\t دنیا  "), "\n";     // سلام دنیا
echo Normalizer::normalize("أحمد إبراهيم مؤمن ة"), "\n";    // احمد ابراهیم مومن ه
echo Normalizer::normalize("ثَبْت  ١٢٣ 456"), "\n";         // ثبت ۱۲۳ 456
echo Normalizer::normalize("ثَبْت", false), "\n";           // ثَبْت (اعراب می‌ماند)
echo normalize_text('كتاب'), "\n";                          // کتاب

به چه دست نمی‌زند. همزهٔ تنهای «ء» و «ئ» عمداً می‌ماند، چون در واژه‌های فارسی و عربی معنا دارد. نیم‌فاصله (ZWNJ) هم می‌ماند: «می‌خواهم» بایت‌به‌بایت همان است. حرف‌های انگلیسی هم تغییر نمی‌کنند.

تابع کمکی normalize_text() همان Normalizer::normalize() با مقدارهای پیش‌فرض است.

fixHalfSpace()

نیم‌فاصله (ZWNJ، U+200C) چیزی است که «می‌خواهم» را درست نشان می‌دهد. کپی و پیست اغلب چند نیم‌فاصلهٔ پشت‌هم، یا نیم‌فاصله کنار فاصله، یا نیم‌فاصله آخر متن به جا می‌گذارد. fixHalfSpace() این‌ها را مرتب می‌کند:

  • خط تیرهٔ نرم (U+00AD) را حذف می‌کند.
  • چند نیم‌فاصلهٔ پشت‌هم را یکی می‌کند.
  • نیم‌فاصله‌ای را که به فاصله چسبیده (قبل یا بعد) حذف می‌کند.
  • نیم‌فاصلهٔ اول و آخر متن را حذف می‌کند.
PHP
$a = "می\u{200C}\u{200C}\u{200C}خواهم";
echo preg_match_all('/./u', $a), ' ', preg_match_all('/./u', Normalizer::fixHalfSpace($a)), "\n";   // 10 8

$b = "\u{200C}سلام \u{200C}دنیا\u{200C}";
echo preg_match_all('/./u', $b), ' ', preg_match_all('/./u', Normalizer::fixHalfSpace($b)), "\n";   // 12 9
echo Normalizer::fixHalfSpace($b), "\n";                                  // سلام دنیا

نیم‌فاصلهٔ جاافتاده را اضافه نمی‌کند و فاصله‌های معمولی را هم یکی نمی‌کند.

clean()

Normalizer::clean() نسخهٔ کامل برای ذخیره و جست‌وجوست. اول نویسه‌های نامرئی را حذف می‌کند: فاصلهٔ بدون پهنا (U+200B)، اتصال‌دهندهٔ بدون پهنا (U+200D) و نشانهٔ ترتیب بایت (U+FEFF). بعد normalize() و fixHalfSpace() را اجرا می‌کند و یک بار دیگر فاصله‌ها را یکی می‌کند. ZWNJ تنها نویسهٔ بدون پهنایی است که می‌ماند. با این ترتیب clean() تکرارپذیر است: پاک‌سازی متنِ از قبل پاک، چیزی را عوض نمی‌کند.

PHP
$c = "كتاب\u{200B}ي  ١٢٣";
echo Normalizer::clean($c), "\n";   // کتابی ۱۲۳

اگر فقط normalize() را روی همین متن بزنید، فاصلهٔ نامرئی وسط واژه می‌ماند. برای همین clean() هست. متنی را که کاربر وارد کرده، پیش از ذخیره یا نمایه‌سازی، با آن تمیز کنید.

Detector

RtlyKit\Text\Detector دربارهٔ خط، زبان و جهت متن جواب می‌دهد. همهٔ متدها استاتیک‌اند و خطا پرتاب نمی‌کنند. UTF-8 نامعتبر مثل Normalizer پردازش می‌شود: هر بایت بد به U+FFFD تبدیل می‌شود، نویسه‌ای خنثی که نه حرف است و نه راست‌به‌چپ، پس جواب فقط به بخش معتبر متن بستگی دارد. نشانهٔ ترتیب بایت (U+FEFF) نویسهٔ راست‌به‌چپ حساب نمی‌شود.

isPersian() و isArabic()

هر دو روش تقریبی‌اند و برای متنی با خط عربی کار می‌کنند. فارسی از حرف‌هایی شناخته می‌شود که فقط در فارسی هستند (پ چ ژ گ، شکل فارسی ک و ی و رقم‌های فارسی). عربی از ي ك ى ة و رقم‌های عربی-هندی شناخته می‌شود. حرف‌های مشترک، مثل «ه» و شکل‌های همزه، حساب نمی‌شوند. اگر حرف‌های ویژهٔ فارسی دست‌کم به اندازهٔ حرف‌های ویژهٔ عربی باشند، متن فارسی است. پس در حالت مساوی یا وقتی هیچ حرف تمایزی نباشد هم فارسی حساب می‌شود.

PHP
use RtlyKit\Text\Detector;

var_dump(Detector::isPersian('سلام'));        // bool(true)، حرف تمایزی ندارد، پیش‌فرض فارسی است
var_dump(Detector::isPersian('گچپژ'));        // bool(true)
var_dump(Detector::isArabic('مرحبا بكم'));    // bool(true)
var_dump(Detector::isArabic('كتاب ي'));       // bool(true)
var_dump(Detector::isPersian('hello'));       // bool(false)، اصلاً خط عربی ندارد
var_dump(Detector::isPersian('۱۲۳'));         // bool(true)، رقم‌های فارسی
var_dump(Detector::isArabic('٣٤٥'));          // bool(true)، رقم‌های عربی-هندی
var_dump(Detector::isPersian(''));            // bool(false)

خوب است بدانید. این روش بر شمارش حرف‌ها بنا شده و تشخیص‌دهندهٔ کامل زبان نیست. متن خیلی کوتاه، نام‌ها و واژه‌های عربی که فارسی نوشته شده‌اند مبهم‌اند و متنی که هر دو زبان را دارد به هر سو می‌رود. برای انتخاب یک پیش‌فرض معقول خوب است. برای تصمیمی که باید حتماً درست باشد، تنها به آن تکیه نکنید.

جهت و RTL

  • containsRtl($text) : آیا متن نویسه‌ای از یک خط راست‌به‌چپ دارد (عبری، عربی و ضمیمه‌هایش، سریانی، تانا، نکو و غیره، شکل‌های نمایشی عربی) یا یکی از کنترل‌های صریح راست‌به‌چپ: RLM (U+200F)، RLE (U+202B)، RLO (U+202E) و RLI (U+2067). direction() این کنترل‌ها را نمی‌شمارد و فقط به اولین حرف نگاه می‌کند.
  • direction($text) : 'rtl' یا 'ltr' بر اساس اولین حرف قوی ، طبق قاعدهٔ دوسویهٔ یونیکد. رقم، نشانه‌گذاری و فاصله خنثی‌اند. متنی که هیچ حرفی ندارد 'ltr' می‌دهد. همان خط‌هایی را راست‌به‌چپ می‌داند که containsRtl() می‌داند، از جمله خط‌های تاریخی مثل فنیقی، اوستایی و نبطی.
  • isHebrew($text) : آیا متن حرف عبری دارد.
PHP
var_dump(Detector::containsRtl('abc'));           // bool(false)
echo Detector::direction('سلام hello'), "\n";    // rtl
echo Detector::direction('Hello مرحبا'), "\n";   // ltr (اولین حرف لاتین است)
echo Detector::direction('۱۲۳'), "\n";           // ltr (رقم خنثی است و حرف نیست)
var_dump(Detector::isHebrew('שלום'));            // bool(true)

توابع کمکی contains_rtl() و text_direction() همان containsRtl() و direction() هستند. برای صفت dir در HTML، روی متنی که کاربر نوشته از direction() استفاده کنید.

isRtlLocale()

isRtlLocale($locale) می‌گوید یک برچسب زبان راست‌به‌چپ نوشته می‌شود یا نه. برچسب را با - و _ می‌شکند (پسوند .UTF-8 یا @euro نادیده گرفته می‌شود) و به بزرگی و کوچکی حرف و فاصله‌های دو سر توجه نمی‌کند. زیربرچسب خطِ چهارحرفی تعیین‌کننده است: Arab و Hebr و خط‌های راست‌به‌چپ دیگر true می‌دهند و هر خط دیگر false، حتی برای زبان راست‌به‌چپ (fa-Latn و ar-Latn و sd-Deva همه false‌اند). بدون زیربرچسب خط، زبان تعیین می‌کند: fa، ar، he، ur، ps، ug، dv، ckb، azb و ... true می‌دهند.

PHP
var_dump(Detector::isRtlLocale('fa_IR'));     // bool(true)
var_dump(Detector::isRtlLocale('ar-SA'));     // bool(true)
var_dump(Detector::isRtlLocale('ckb-IQ'));    // bool(true)
var_dump(Detector::isRtlLocale('az-Arab'));   // bool(true)، زیربرچسب خط تعیین‌کننده است
var_dump(Detector::isRtlLocale('fa-Latn'));   // bool(false)، زیربرچسب خط تعیین‌کننده است
var_dump(Detector::isRtlLocale('ku'));        // bool(false)، «ku» ساده در فهرست زبان‌های RTL نیست
var_dump(Detector::isRtlLocale('en'));        // bool(false)
var_dump(Detector::isRtlLocale(''));          // bool(false)

Slugify

Slugify::make($text, $separator = '-') عنوان را به اسلاگ مناسب نشانی اینترنتی تبدیل می‌کند و حرف‌های فارسی و عربی را همان‌طور خوانا نگه می‌دارد. به لاتین تبدیل نمی‌کند.

  1. متن را با Normalizer::normalize() یکسان می‌کند.
  2. فاصله و نیم‌فاصله را به جداکننده تبدیل می‌کند. نویسهٔ U+0001 که در متن باشد اول حذف می‌شود، پس هیچ‌وقت نقش جداکننده ندارد.
  3. هر چیزی را که حرف (از هر خط)، نشانهٔ ترکیبی، رقم (از هر دسته)، خط تیره یا زیرخط نیست حذف می‌کند. نشانه‌گذاری و اموجی می‌رود.
  4. خط تیره، زیرخط و جداکنندهٔ تکراری را یکی می‌کند و جداکننده را از دو سر برمی‌دارد. این کار پیش از گذاشتن متنِ جداکننده انجام می‌شود، پس متنی که برابر جداکننده است یا آن را دارد می‌ماند: Slugify::make('maxx', 'x') می‌شود maxx.
  5. نتیجه را با UTF-8 به حرف کوچک تبدیل می‌کند.
PHP
use RtlyKit\Text\Slugify;

echo Slugify::make('سلام دنیا'), "\n";               // سلام-دنیا
echo Slugify::make("کتاب\u{200C}خانه ملی"), "\n";    // کتاب-خانه-ملی
echo Slugify::make('Hello, World!'), "\n";           // hello-world
echo Slugify::make('  A--B__C  '), "\n";             // a-b_c
echo Slugify::make('سلام!!! دنیا؟'), "\n";           // سلام-دنیا
echo Slugify::make('۱۲۳ ابر'), "\n";                 // ۱۲۳-ابر
echo Slugify::make('كتاب ي'), "\n";                  // کتاب-ی
echo Slugify::make('سلام 😀 دنیا'), "\n";            // سلام-دنیا
echo Slugify::make('عکس.jpg'), "\n";                 // عکسjpg
var_dump(Slugify::make('$%^&'));                    // string(0) ""

دو نکته. رقم‌های انگلیسی انگلیسی می‌مانند. رقم‌های فارسی و عربی-هندی هر دو فارسی می‌شوند. اگر رقم انگلیسی در نشانی می‌خواهید، اول Digits::toEnglish() را اجرا کنید. نقطه حذف می‌شود، به جداکننده تبدیل نمی‌شود. پس پسوند فایل به نام می‌چسبد (عکسjpg). نام و پسوند را جدا اسلاگ کنید.

جداکننده

هر رشته‌ای را می‌توانید به‌عنوان آرگومان دوم بدهید: '_'، رشتهٔ بلندتر، یا رشتهٔ خالی که واژه‌ها را بدون جداکننده می‌چسباند.

PHP
echo Slugify::make('سلام دنیا', '_'), "\n";        // سلام_دنیا
echo Slugify::make('سلام دنیا', ''), "\n";         // سلامدنیا
echo Slugify::make('Hello World', '--'), "\n";     // hello--world

جداکننده باید حداکثر ۶۴ بایت و UTF-8 معتبر باشد. جداکنندهٔ بلندتر RtlyKitException با کد input_too_long می‌دهد و جداکنندهٔ نامعتبر همان خطا را با invalid_argument:

PHP
try {
    Slugify::make('Hello World', str_repeat('x', 65));
} catch (\RtlyKit\Exceptions\RtlyKitException $e) {
    echo $e->getMessage(), ' [', $e->getErrorCode()->value, "]\n";
    // The slug separator is too long. [input_too_long]
}

اسلاگ خالی و یکتایی. عنوانی که فقط نشانه‌گذاری دارد، رشتهٔ خالی می‌دهد. عنوان‌های متفاوت هم می‌توانند یک اسلاگ بسازند. حالت خالی را در کدتان در نظر بگیرید (مثلاً به شناسه برگردید) و یکتایی را در ذخیره‌سازی خودتان تضمین کنید.

کدگذاری. Slugify هم برای متن و هم برای جداکننده UTF-8 معتبر می‌خواهد. بایت‌های نامعتبر در متن، RtlyKitException با کد invalid_argument می‌دهد. اسلاگ یک شناسه است، برای همین ورودی خراب را قبول نمی‌کند. Normalizer عمداً ملایم‌تر است: تا جایی که بتواند کار می‌کند و آنچه را نتواند پردازش کند همان‌طور برمی‌گرداند. حرف کوچک‌کردن با نگاشت ساده‌ٔ یونیکد انجام می‌شود و افزونهٔ PHP لازم ندارد.