تو پست قبل گفتم که به نظر من تبدیل عددها به فارسی کار جالبی (حداقل در وب) نیست؛ ولی دلایل‌ام رو نگفتم:


  1. متاسفانه ارقام فارسی در فونت‌هایی که معمولا در سایت‌های فارسی زبان استفاده می‌شه بسیار ناخوانا هستن و تشخیص ارقام ۲ و ۳ و ۴ از هم در اندازه‌های معمول خیلی سخته. بارها شده برای درست خوندن یک شماره تلفن در وب مجبور شدم صفحه رو چندین برابر بزرگ کنم! شاید مشکل از چشم من باشه ولی مساله فقط این نیست.
  2. از اونجایی که صفحه کلید پیش فرض فارسی در ویندوز هیچ امکانی برای وارد کردن ارقام فارسی نداره، با فارسی کردن رقم‌ها در یک سایت رسما امکان جستجوی هرگونه عدد و شماره گرفته می‌شه. فرض کنید در سایت یک ISP دارید صفحه مربوط به پیش‌شماره‌های پشتیبانی شده رو مشاهده می‌کنید ولی چون همه شماره‌ها به فارسی هستند نمی‌تونین جستجو کنید و مجبور هستین که خودتون چشمی دنبال پیش‌شماره‌تون بگردین (برگردین به مورد 1) و یا دست به دامن Character Map در ویندوز بشین!
  3. با فارسی کردن رقم‌ها در یک سایت امکان کپی کردن هم تقریبا گرفته می‌شه. فرض کنید که می‌خواین نرخ امروز دلار به ریال رو از سایت بانک مرکزی کپی کنین و در یک برنامه مالی ساخت وطن استفاده کنین!

مورد اول به خودمون بستگی داره و این که بالاخره کی می‌خواهیم یک فکری به حال فونت‌های فارسی بکنیم. ولی دو مورد آخر مشکلات بنیادی نیستند و به مرور زمان و با پیاده سازی بهتر یونیکد در نرم‌افزارها حل خواهند شد.


یه چیز دیگه: اگه در CSS یک استایل بود که با کمک اون می‌شد مشخص کرد که اعداد به چه شکلی نشون داده بشن جالب بود. یه چیزی تو این مایه‌ها:


p {digits: extended-arabic-indic}

Vote on iDevCenter

حامد یک مطلبی نوشته و لینکش رو ارسال کرده با عنوان تبدیل اعداد انگلیسی به فارسی. با این که به نظر من تبدیل عددها به فارسی کار جالبی (حداقل در وب) نیست، کنجکاو شدم ببینم که واقعا سریعترین روش برای انجام این کار چیه. این هم نتیجه‌اش:


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


void Benchmark(Func<string, string> func) {

    string input = File.ReadAllText(@"D:\WINDOWS\updspapi.log");
    int times = 1000;

    Stopwatch stopwatch = new Stopwatch();

    stopwatch.Start();

    for (int i = 0; i < times; i++)
        func(input);

    stopwatch.Stop();

    Console.WriteLine("{0,10:#,###} Ticks", stopwatch.ElapsedTicks / times);
}

اول از همه میریم سراغ Regular Expressions. در این روش از متد Replace از کلاس Regex استفاده می‌شه تا تمام رقم‌های انگلیسی (با الگوی [9-0]) با معادل فارسی‌شون جایگزین بشن.


// Regex
Regex regex = new Regex(@"[0-9]");

Benchmark(str => {

    return regex.Replace(str, match => ((char)(match.Value[0] + 1728)).ToString());
});

نتیجه: 2,100 تیک برای هر اجرا. کند بودن این روش از همون اول کار کاملا مشخص بود ولی به خاطر کامل شدن مطلب حدفش نکردم. RegExها برای پیدا کردن الگوهای پیچیده در متن‌ها مناسب هستند و برای ما که فقط با ده رقم 0 تا 9 به طور مستقل کار داریم گزینه خوبی نیستند.


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


// String Block
char[] digits = Enumerable.Range(48, 10).Select(i => (char)i).ToArray();

Benchmark(str => {

    while (true) {

        int index = str.IndexOfAny(digits);

        if (index == -1)
            break;

        str = str.Substring(0, index) + ((char)(str[index] + 1728)).ToString() + str.Substring(index + 1);
    }

    return str;
});

نتیجه: 48,700 تیک برای هر اجرا. خیلی بدتر شد! علت اصلی کندی این روش به غیر از جستجوی چند باره متن توسط تابع IndexOfAny) اینه که به ازای هر رقم پیدا شده، 4 شیئ از نوع String ساخته و دور ریخته می‌شه. هر چی هم متن برزگتر باشه تاثیر منفی این کار بیشتره.


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


// String.Replace
Benchmark(str => {

    for (char i = (char)48; i <= 57; i++)
        str = str.Replace(i, (char)(i + 1728));

    return str;
});

نتیجه: 740 تیک برای هر اجرا. بد نیست ولی توجه داشته باشید که باز هم در این روش 9 رشته موقت ساخته می‌شه و تابع Replace مجبوره در هر اجرا یکبار کل متن رو جستجو کنه.


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


// StringBuilder
Benchmark(str => {
    
    StringBuilder sb = new StringBuilder(str.Length);

    for (int i = 0; i < str.Length; i++)
        if (str[i] >= 48 && str[i] <= 57)
            sb.Append((char)(str[i] + 1728));
        else
            sb.Append(str[i]);

    return sb.ToString();
});

نتیجه: 360 تیک برای هر اجرا. خیلی بهتر شد ولی هنوز جای کار داره. از اونجایی که ما تنها لازم داریم یک کاراکتر رو با فقط و فقط یک کاراکتر دیگه جایگزین کنیم، پس می‌شه خیلی بهینه‌تر عمل کرد. در روش بعدی ابتدا یک آرایه موقت از نوع char و به طول رشته اصلی درست می‌کنیم و بعد مثل روش قبل کاراکترها رو تک تک رو بررسی می‌کنیم و حاصل رو توی آرایه موقت کپی می‌کنیم. در آخر هم نتیجه رو به صورت String بر می‌گردونیم. // Char[]
Benchmark(str => {

    char[] temp = new char[str.Length];

    for (int i = 0; i < str.Length; i++) {

        char value = str[i];

        if (value >= 48 && value <= 57)
            temp[i] = (char)(value + 1728);
        else
            temp[i] = value;
    }

    return new String(temp);
});

نتیجه: 150 تیک برای هر اجرا. کمتر از نصف روش قبل. در واقع این روش ساده‌ترین و کاراترین روشی‌ هستش که من بهش رسیدم. برای بهینه‌تر کردن این روش می‌شه از اشاره‌گرها هم استفاده کرد:


// Char[] *Unsafe
Benchmark(str => {

    unsafe {
        fixed (char* src = str) {
            fixed (char* temp = new char[str.Length]) {

                for (int i = 0; i < str.Length; i++) {

                    char value = *(src + i);

                    if (value >= 48 && value <= 57)
                        *(temp + i) = (char)(value + 1728);
                    else
                        *(temp + i) = value;
                }

                return new String(temp);
            }
        }
    }
});

نتیجه: 145 تیک برای هر اجرا. اختلافش با روش قبلی خیلی کمه و به نظر من اصلا به دردسرش نمی‌ارزه. یک نکته‌ای هم که هست اینه که این روش در بعضی حالت‌ها کندتر از روش قبلی عمل می‌کنه! کلا بهتره اجازه بدیم خود NET. حافظه رو مدیریت کنه و ما دخالت نکنیم.


یک کاره دیگه هم که می‌شه کرد اینه که به جای این که کاراکترهای غیر-رقمی رو تک تک کپی کنیم، همه رو نگه داریم و با هم کپی کنیم:


// Char[] Block
Benchmark(str => {

    char[] src = str.ToCharArray();
    char[] temp = new char[str.Length];

    int lastIndex = 0;

    for (int i = 0; i < src.Length; i++) {

        char val = src[i];

        if (val < 48 || val > 57)
            continue;

        Array.Copy(src, lastIndex, temp, lastIndex, i - lastIndex);
        temp[i] = (char)(val + 1728);
        
        lastIndex = i + 1;
    }

    Array.Copy(src, lastIndex, temp, lastIndex, src.Length - lastIndex);

    return new String(temp);
});

نتیجه: 280 تیک برای هر اجرا. برخلاف انتظار، کندتر از دو روش قبلی شد؛ دلیل اصلیش هم استفاده از متد Array.Copy هست. Buffer.BlockCopy هم فرق چندانی نداره.


به روز رسانی: در روش RegEx الگوی d\ رو با [9-0] عوض کردم تا فقط شامل ارقام انگلیسی بشه.


Vote on iDevCenter

چند وقتیه دارم در مورد یک ادیتور خوب برای بخش ویکی‌ها (و همینطور یک بخش جدید دیگه که تو فکرشم) نحقیق می‌کنم ولی هنوز به نتیجه‌ای نرسیدم. مشکل که هست اینه که برای فرمت کردن مطالب سایت از HTML استفاده می‌شه و نوشتن کدهای HTML (و در کل هر چیز چپ به راست) بین متن‌های راست به چپ فارسی کار واقعا سختیه.

از نظر من یک ادیتور مناسب برای سایت باید این دو تا ویژگی‌ رو حتما داشته باشه:

  • کدهای HTML کاملا معتبر و مطابق با اصول فرمت‌دهی سایت تولید کنه؛ چون به دلیل مشکلات امنیتی مجبورم در مورد کدهایی که کاربران وارد می‌کنند خیلی سخت‌گیر باشم.
  • قابلیت نمایش بخشی از متن به صورت LTR و بخشی به صورت RTL رو داشته باشه (برای مواردی که لازمه بین دو پاراگراف فارسی یک نمونه کد نوشته بشه)

حالا راه‌حل هایی که وجود داره:

  • در کد‌های HTML به جای <> از () و نام‌های معادل فارسی استفاده بشه مثل (توپر)متن(توپر/): مشکل LTR و RTL تا حدی برطرف می‌شه و پیاده سازیش هم سریع و آسونه.
  • به جای HTML از یه فرمت دیگه مثل Markdown استفاده بشه: برای مواردی مثل عنوان‌ها و لیست‌ها گزینه مناسبیه ولی برای موارد پیچیده‌تری مثل جدول‌ها و نمونه کدها نه.
  • یک Add-on برای MS Word نوشته بشه تا متون نوشته شده در این برنامه رو به فرمت معتبر برای سایت تبدیل کنه: نمی‌دونم چقدر قابل انجامه.
  • یک ادیتور کامل با ویژگی‌های گفته شد توسط Silverlight و یا به صورت یک برنامه مستقل نوشته بشه: کار بسیار سخت و وقت گیریه ولی در عین حال جالبه و مفید برای جامعه فارسی زبانان.

نظر شما چیه؟