السلام عليكم.
أثناء عملي على مشروع كبير في الشركة لزمني عملية تحويل الأرقام إلى نص و التي تسمى في اللغة العربية ( التفقيط ) و أثناء بحثي على الانترنيت على كود جاهز لهذه العملية, وجدت بعضاً منها و لكن أغليها لم يكن بالجودة الكافية لذلك قررت العمل عليها بشكل كامل بحيث تغطي كافة الاحتمالات و أحببت مشاركتكم بالكود لتعم الفائدة و أيضاً لتقبل الملاحظات و الاقتراحات.
أولاً هذه صورة عن مشروع تجريبي يبين عمل الكلاس:
وهذا لينك المشروع التجريبي: NumberToWord.zip
سأحاول أن أقوم بشرح الكلاس بشكل مبسط.
عندما نقرأ أي رقم باللغة العربية فإننا نقسمه إلى مجموعات: المئات , الآلاف , الملايين , المليارات ... الخ , فالعملية ببساطة تقوم على تقسيم العدد إلى مجموعات مكونة من 3 خانات.
فمثلاً: الرقم: 987,654,321.123 يتم تقسيمه إلى المجموعات التالية:
123: المجموعة -1
321: المجموعة 0
654: المجموعة 1
987: المجموعة 2
و هذا الكود الذي يقوم بذلك:
Byte group = 0;
while (tempNumber >= 1)
{
// seperate number into groups
int numberToProcess = (int)(tempNumber % 1000);
tempNumber = tempNumber / 1000;
// convert group into its text
string groupDescription = ProcessArabicGroup(numberToProcess, group, Math.Floor(tempNumber));
group++;
}الآن يتم تحويل النص لكل مجموعة على حسب مستواها عن طريق التابع: ProcessArabicGroup و من ثم يتم جمع النصوص مع بعضها لتكوين النص الكامل.
اللغة العربية فيها نظام معقد لتفقيط الأرقام, فهناك حالات مختلفة للأرقام فمثلاً الرقم واحد و اثنان يوافقان المعدود في التذكير و التأنيث في حين أن الأرقام من 3 إلى 10 تخالفان المعدود ... الخ. (شرح قواعد الأعداد في اللغة العربية خارج نطاق هذه المقالة).
الآن: ضمن التابع ProcessArabicGroup يتم معالجة المجموعة المكونة من 3 خانات على حسب قواعد اللغة العربية و يتم ذلك عن طريق أوامر :if/else و هناك قواعد عامة و استثناءات لهذه القواعد.
فمثلاً الرقم 200125 يقرأ مئتا ألف و مائة و خمس و عشرون ( لاحظ تم استخدام مئتا عوضاً عن مئتان و هي حالة المضاف ) و هذا هو الكود:
if (hundreds > 0)
{
if (tens == 0 && hundreds == 2) // حالة المضاف
retVal = String.Format("{0}", arabicAppendedTwos[0]);
else // الحالة العادية
retVal = String.Format("{0}", arabicHundreds[hundreds]);
}و كما ستلاحظون عن مراجعة كود الكلاس فستجدون أن معظمه هو أوامر: if/else لتحديد الحالات الاستثنائية.
من الأمور المهمة في الكود أنه يعالج الأرقام في حالة كان المعدود مذكراً أو مؤنثاً و هنا أنا أستخدم العملة كمعدود ( هذه كانت متطلبات مشروع شركتي) و قد واجهتني مشكلة أن برنمجي متعدد الدول, أي أنني أحتاج أن أظهر التفقيط بعدة عملات ( ليرة سورية, درهم إماراتي , ريال سعودي ... ) و أيضاً الكلاس يعالج الأرقام التي بعد الفاصلة و أيضاً يوجد معدود مختلف لها ( قرش , فلس , هللة ....).
يوجد تابع يعالج هذه الحالة و هو GetDigitFeminineStatus وهو يعتمد على مستوى المجموعة و هذا هو الكود:
private string GetDigitFeminineStatus(int digit, int groupLevel)
{
if (groupLevel == -1)
{ // if it is in the decimal part
if (Currency.IsCurrencyPartNameFeminine)
return arabicFeminineOnes[digit]; // use feminine field
else
return arabicOnes[digit];
}
else
if (groupLevel == 0)
{
if (Currency.IsCurrencyNameFeminine)
return arabicFeminineOnes[digit];// use feminine field
else
return arabicOnes[digit];
}
else
return arabicOnes[digit];
}و لتسهيل العملية قمت بإنشاء كلاس للعملة اسمه CurrencyInfo و هذا هو تفصيله:
و بعد تحويل الأرقام إلى نص يتم إضافة اسم العملة و أجزاؤها على حسب قواعد اللغة العربية و أيضاً باستخدام أوامر: if/else كما في الكود التالي:
if (_intergerValue != 0)
{ // here we add currency name depending on _intergerValue : 1 ,2 , 3--->10 , 11--->99
int remaining100 = (int)(_intergerValue % 100);
if (remaining100 == 0)
formattedNumber += Currency.Arabic1CurrencyName;
else
if (remaining100 == 1)
formattedNumber += Currency.Arabic1CurrencyName;
else
if (remaining100 == 2)
{
if (_intergerValue == 2)
formattedNumber += Currency.Arabic2CurrencyName;
else
formattedNumber += Currency.Arabic1CurrencyName;
}
else
if (remaining100 >= 3 && remaining100 <= 10)
formattedNumber += Currency.Arabic310CurrencyName;
else
if (remaining100 >= 11 && remaining100 <= 99)
formattedNumber += Currency.Arabic1199CurrencyName;أرجو أن أكون قد أوضحت النقاط الرئيسية التي يرتكز عليها الكود و أنا أرحب بملاحظاتكم و تعديلاتكم على الكود.
لا تنسونا من دعواتكم.
