الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

مشروع لحل مشكلة الكتابة بالعربية

مغلق
بدأه hasan_aljudy في 2 أبريل 2005 · 9 رد · 5,833 مشاهدة · في مشاريع ومسابقات القسم
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

بسم الله الرحمن الرحيم

السلام عليكم و رحمة الله و بركاته

هذا مشروع اعمل عليه لحل مشكلة دعم اللغة العربية في البرامج اللتي لا تدعمها.

طبعا كلنا يعرف ان ويندوز قد حل هذه المشكلة منذ زمن, و مع وندوز اكس بي اصبح دعم اللغة العربية موجود في كل النسخ, يعني لا يوجد شي اسمه نسخة تدعم العربية و نسخة لا تدعمها.

لكن طبعا كما تعرفون, ليس كل شيء في الدنيا هو وندوز! و هناك برامج تعمل على الوندوز ولا تدعم اللغة العربية بشكل كامل, و هو ما دفعني الى البدء في المشروع من الأساس, حيث اني كنت اعمل على احد تلك "البرامج" ;)

في الحقيقة هو ليس بالظبط برنامج .. هو لعبة! لعبة Half-Life 2, و هي لمن لا يعرف ربما أقوى لعبة صدرت لحد الآن, محركها و محرك Doom 3 في صراع شديد على القمة :D

ما علينا, القصة اني في احد منتديات الألعاب وجدت أحد الأشخاص يقول انه سيحاول ان يدبر حركة عن طريق الـ SDK لدعم الشات باللغة العربية.

الفكرة بصراحة خرطت مشطي, و قصة Half-Life 2 طويلة نوعا ما, هناك SDK للعبة يحتوي على الكود الكامل لملفات server.dll و client.dll يسمح لك بإنشاء mods للعبة.

القصة طويلة, من أراد المزيد يستطيع ان يقرأ هذا الموضوع:

http://maxforums.net/showthread.php?t=46037

عموما, عندما حاولت تطبيق الفكرة, وجدت نفسي في مشكلة: أريد طريقة لأخذ string يحتوي على حروف عربية و انجليزية ثم ينشيء منها string آخر يحتوي على الحروف مرتبة بشكل معين بحيث أنه عند عرض الـ string سيظهر بشكل صحيح.

مثلا, لو أردت كتابة

test تجربة

فإنها تظهر في Half-Life 2 بالشكل التالي:

test ة ب ر ج ت

ما أريد عمله, هو معالجة النص و انتاج نص آخر يكون شكله هكذا:

test تـ   ـجـ   ـر  بـ   ـة

جميع أشكال الحروف موجودة في الـ Unicode, كل ما عليك عمله هو اختيار الشكل المناسب لكل حرف بحيث يظهر في سياقه الصحيح, يعني هنا حرف الجيم "ج" هو حرف متوسط, يتصل بالحرف اللذي قبله و اللذي بعده, لذلك نريد ان نضع مكانه الحرف "ـجـ", و هو من وجهة نظر اليونيكود حرف آخر تماما (من وجهة نظر كود الحرف على الأقل).

هذه الفكرة جائتني عندما أشار أحد الاشخاص الى انك بإمكانك ان تكتب بالعربية في Half-Life 2 عن طريق فتح الـ charmap و اختيار الحروف بالشكل الصحيح ثم نسخها و لصقها. فقلت لنفسي: هذه هي الخطوة الأولى.

طبعا في البداية, كنت احاول البحث في النت عن حل جاهز, لا بد ان حل كهذا موجود!! لكني بصراحة لم أجد الكثير, و ما وجدته لم يعجبني, إما لتعقيده او عدم وضوحه او لأني لم اعرف كيف استخدمه (او لجميع تلك الأسباب). وجدت مشروعا اسمه fribidi, لكني لم استخدمه للأسباب اللتي ذكرتها للتو.

قلت لنفسي لا بد ان هناك طريقة في الوندوز لاستدعاء function يقوم بهذه العملية, عرفت ان وندوز يستخدم مكتبة اسمها uniscribe, لكني لم أفهم كيف من المفروض ان استخدمها ..

في النهاية قررت ان اقوم بكتابة الكود لعمل ذلك بنفسي!

أول نسخة للحل وضعتها في منتدى "المنتدى" لكن طبعا الإقبال هناك كان ضعيف, هذا هو الموضوع:

http://montada.com/showthread.php?t=346661

قد يكون هذا العمل دون فائدة عملية, لأن هناك مكتبات أخرى تقوم بنفس الشيء, لكني أظن ان هذا العمل له فائدة على الأقل تعليمية .. على الأقل بالنسبة لي.

عندما شاهدت المشروع يعمل شعرت بأني أنجزت شيئا عظيما :P

و من يدري, ربما يتطور هذا الموضوع و يصبح شي قياسي مثلا في يوم من الأيام (ههههه الرجال يحلم كثير!!)

هناك في الموضوع تجدون النسخة الأولى من المشروع, و رغم أنني استخدمت بعض الكائنات, لكن الكود كان procedural جدا جدا, و هناك بعض الأشياء اللتي تعلمتها من الكورس اللذي نأخذه حاليا في الجامعة جعلتني أراجع الكود و أعيد طريقة كتابته لجعله object oriented بأفضل قدر ممكن.

لأعيد الهدف من الكود:

أخذ string يحتوي نصوص عربية و انجليزية مرتبة بشكل منطقي (حسب ترتيب ادخالها من الكيبورد) و إنتاج string جديد يحتوي على الحروف مشكلة بترتيب "بصري" بحيث عندما يتم عرضها تظهر بالشكل الصحيح.

آخر نسخة من المشروع تجدونها هنا:

http://pages.cpsc.ucalgary.ca/~abdurahh/bi...alpha-0.2.1.rar

و سأقوم بوضع جميع النسخ القادمة هنا إن شاء الله:

http://pages.cpsc.ucalgary.ca/~abdurahh/bidi/

و هذه أول نسخة اصدرتها

http://pages.cpsc.ucalgary.ca/~abdurahh/bi...c%20version.rar

تستطيعون مقارنتها بالنسخة الجديدة :lol:

المشروع لا يزال قيد العمل, لا يزال هناك الكثير ..

بانتظار ارائكم و اقتراحاتكم :D

تم تعديل هذه المشاركة بواسطة hasan_aljudy في 2 أبريل 2005 في 05:50

1
#3

يعطيك الف عااافية

والله جهد جبار تقوم به اخوي الغالي

لا حرمناك

وننتظر المزيد منك

52gv0m.gif
#4

ما ادري, يمكن الموضوع غير واضح تماما. أعتقد من واجبي اقدم شرح علمي شوية للفكرة.

الفكرة تقوم على أخذ نص يحتوي حروف انجليزية و عربية معا, المشكلة تكمن في ان النص في حالة إظهاره في نظام لا يدعم العربية فستظهر الحروف العربية منفصلة و معكوسة الاتجاه. لذلك الفكرة هي ربط الحروف و عكسها.

هناك شقين للمشكلة: اولا شكل الحروف, ثانيا ترتيبها في السياق ذو الاتجاه المعاكس.

شكل الحروف:

بشكل عام, حروف اللغة العربية لها أربع أشكال, و هذه الأشكال تعتمد على موقع الحرف في الكلمة:

منفصل: عندما يكون الحرف مكتوبا لوحده, مثلا: حرف العين شكله المنفصل هو: ع

ابتدائي: عندما يكون الحرف في أول الكلمة, مثلا: عـ

منتصف: عندما يكون الحرف في منتصف الكلمة, مثال: ـعـ

انتهائي: عندما يكون الحرف في آخر الكلمة, مثال: ـع

و لكن, هذا ليس كل شيء, هناك حالات يكون فيها الحرف في منتصف الكلمة دون أن يأخذ الشكل الانتصافي مثل حرف العين في كلمة "دعاء", حرف العين هنا يأخذ الشكل الابتدائي بالرغم من أنه في منتصف الكلمة! لماذا؟ لأنه مسبوق بحرف الدال, و حرف الدال لا يتصل بالحرف اللذي يليه, لذلك فإن أي حرف يلي حرف الدال لن يتصل به.

فشكل الحرف إذا ليس محددا فقط حسب موضعه في الكلمة, بل بخاصيتين أخريين: هل يستطيع هذا الحرف الاتصال بالحرف السابق؟ و هل يستطيع الاتصال بالحرف اللاحق؟

هناك مشكلة أخرى, قد يكون الحرف مسبوقا أو ملحوقا بحرف أجنبي (أو رقم), ففي هذه الحالة فإنه لا يتصل بأي منهم. و كذلك نحتاج الى تعريف "أول الكلمة" و "آخر الكلمة", على الغالب سيكون الحرف في أول الكلمة مسبوقا بمسافة أو اي مساحة بيضاء white space, و لكن المسافة هي نفسها حرف (رمز) و هي حرف لا يتصل بالحرف الذي قبله و لا باللذي بعده, و هكذا كل الحروف الغير عربية.

فكل حرف عنده هذه الخصائص:

أربعة أشكال: منفصل, مبتدئ, منتصف, منتهي

و خاصيتان: يتصل باللذي قبه (أو لا), يتصل باللذي بعده (أو لا)

هناك في اليونيكود مجموعتان للحروف العربية:

الأولى تعنى بالأشكال القياسية للحروف:

http://www.unicode.org/charts/PDF/U0600.pdf

والثانية تضم الأشكال الأخرى لكل حرف:

http://www.unicode.org/charts/PDF/UFE70.pdf

عند دراسة هذين الجدولين نلاحظ أولا ان الجدول الثاني يشير الى كل شكل بأنه شكل فرعي من حرف معين في الجدول الأول, مثلا: الشكل الانتهائي لحرف الباء رمزه FE90 و نلاحظ انه معرف كالتالي:

ARABC LETTER BEH FINAL FORM
<final> 0628

و الرقم 0628 هو كود الشكل القياسي لحرف الباء.

الملاحظة الأخرى هي أن الشكل القياسي لكل حرف (في الجدول الأول) هو عادة شكل الحرف عندما يكون منفصلا ( ع مثلا ) و لكن رغم ذلك, هناك شكل في الجدول الثاني يمثل الشكل المنفصل لكل حرف (رغم أنه نفس الشكل القياسي) فالشكل المنفصل لحرف الباء هو FE8E و هو معرف على أنه

<ioslated> 0268

لذلك سنضيف خاصية جديدة الى الحروف, و هي الشكل القياسي, مع الإبقاء على الشكل المنفصل أيضا.

هذا هو الدافع للـ class CHarf ( ملاحظة: قد يتغير اسم الكلاس الى Harf لأني عندما كتبته كنت اتبع الـ Hungarian notation, لكنني تركتها الآن!)

الغرض من هذا الصنف هو إعطاء مرونة أكثر لعملية ربط الحروف ببعضها, و استخدامه يكون كالتالي:

نقوم بإنشاء كائنات من هذا الصنف, كل واحد من هذه الكائنات يمثل حرفا من حروف الأبجدية يحمل كل خصائصه: يحتوي الأكواد التي تمثل الأشكال المختلفة له, و متغيرات منطقية تمثل قابلية الحرف لاتصال بالحرف اللذي قبله و اللذي بعده.

و هناك أيضا كائن يمثل الحروف الغير عربية, هذا الكائن لا يحتوي على شيء سوى أصفار: اكواد الأشكال كلها أصفار, و المتغيرات المنطقية أصفار أيضا (في السي بلص بلص, الصفر هو false) (لا يتصل لا باللذي قبله و لا باللذي بعده)

ثم نأخذ هذه الكائنات و نضعها في جدول, و نستخدم آلية معينه تمكننا من الحصول على مؤشر يشير الى الكائن اللذي يمثل هذا الحرف: فلو وصلنا حرف الذال, نريد مؤشرا الى الكائن اللذي يمثل هذا الحرف و يحتوي كل خصائصه.

في السابق كنت استخدم linked list مبنية ضمن الـ CHarf, لكني غيرت هذه الآلية و قمت باستخدام الـ STL map, و السبب في ذلك هو أني أردت فصل آلية استخراج الحرف عن كائن الحرف نفسه و السبب هو شيء درسته هذا الفصل و الفكرة هي (reduce coupling and increase cohesion) يعني كل صنف يكون له مسؤلية ميعنة وواضحة. ما علينا.

لوضع الحروف في جدول, استخدمت صنفا أسميته HarfMap و هو عبارة عن wrapper لتغليف و احتواء std::map لأنه يستخدم templates و كما تعلمون .. the template syntax is ugly!!!!!!!

و كذلك أردت إبعاد تفاصيل الجدول عن المستخدم .. ما دخلك أنت كيف أخرن الحروف؟ كل ما تريد فعله هو إضافة كائن حرف جديد الى الجدول, او الحصول على الكائن اللذي يمثل حرفا معينا. و طبعا لو لم يكن هذا الحرف موجودا في الجدول فسنفترض انه حرف غير عربي, و نعطي مؤشرا للكائن اللذي يمثل كل الحروف الغير عربية.

بالمناسبة, حروف اليونيكود نمثلها بنوع wchar_t و هو نوع جديد في السي بلص بلص (لا أعرف متى تمت إضافته و ليست لدي أي فكرة).

و الآن نريد معرفة شكل حرف معين بين حرفين معينيين, كيف نفعل ذلك؟

نأخذ مؤشر للكائن اللذي يمثل الحرف المراد معرفة شكله و مؤشرين للكائنين اللذين يمثلان الحرفين السابق و اللاحق, ثم نرى, هل هناك اتصال بين هذا الحرف و الحرف السابق؟ و هل هناك اتصال بين هذا الحرف و الحرف اللاحق؟ إذا عرفنا هذه الخصائص فسنستطيع تحديد الشكل اللذي يجب ان يكون عليه الحرف. و هذا كله سهل لأن لدينا المؤشرات لهذه الكائنات و نستطيع استخدامها لمعرفة هذه الخصائص.

هذا تقريبا أغلب ما يخص الـ shaping

بالنسبة للـ HarfMap فقط أضفته بالأمس فقط آخر نسخة وضعتها أثناء كتابة هذا المقال هي alpha 0.2.3 و يمكن الحصول عليها من هنا:

http://pages.cpsc.ucalgary.ca/~abdurahh/bidi/

و هي تحتوي الـ source code فقط (من أجل تصغير حجمها)

سأكمل لاحقا إن شاء الله فيما يخص باقي أجزاء المشروع

#5

السلام عليكم

أهلين بأخي المبدع حسن - ماشاء الله عليك الله لايضرك - فهمت الفكرة الاساسية لللمشروع وحملت ملفات المشروع بشوفها واذا كان عندي اسئلة جهز الاجبات يابطل.

الله يوفقنا وأياك وكل مسلم لكل خير.

على فكرة فيه سؤال اذا مايضايققك هل انت خليجي؟ومن أي دوله؟

اللهم علمنا ما ينفعنا وأنفعنا بما علمتنا أنك أنت العليم الحكيم

سبحان الله وبحمده سبحان الله العظيم

لاحول ولاقوة الا بالله( كنز من كنوز الجنة ).

#6

أنا عراقي :)

طيب,تحدثنا عن توصيل الحروف ,في النسخة الأولى كان هذا يتم في harf.cpp و بعضه في container.cpp حيث كانت عملية التوصيل تتم في CBilStringContainer::ShapeContextual, أما الآن فالعملية تتم في harf.cpp و harfmap.cpp و shaping.cpp

طبعا كل واحد من ملفات الـ .cpp هذه يقابله ملف .h

المهم الآن ..

المشكلة الثانية هي ترتيب الحروف, فالحروف العربية تكتب من اليمين الى اليسار, أما الحروف الانجليزية (و كذلك الأرقام) تكتب من اليسار إلى اليمين, لو كتبنا مثلا كلمة "عمر" فسوف ندخل اول حرف العين, ثم الميم, ثم الراء, و نضع العين في أقصى اليمين, ثم نضع الحرف اللذي يليه على يساره .. و هكذا. بينما في الانجليزية, فإننا نضع الحرف الأول في أقصى اليسار, ثم نضع اللذي يليه على يمينه, و هكذا.

فعندما نكون في بيئة من المفترض انها تدعم العربية, فأين سيوضع الحرف الأول؟ على اليمين ام على اليسار؟و أين ستوضع الحروف الباقية؟ الى يسار الحرف السابق أم الى يمينه؟!؟!

هنا يجب تحديد فيما إذا كان الاتجاه العام للنص يتجه من اليمين الى اليسار أم من اليسار الى اليمين؟

لنبسط الأمور, و نفرض ان البيئة اللتي نعمل فيها هي انجليزية في الأساس مع بعض الدعم للغة العربية, بمعنى أننا نريد عرض الحروف العربية بالطريقة الصحيحة حين تكتب في سياق نص انجليزي.

هنا الاتجاه العام للكتابة سيكون من اليسار الى اليمين, لكن لو كتبنا حروف عربية, سجب ان يكون اتجاه هذه الحروف من اليمين الى اليسار, لذلك, من اجل عرض الحروف بالشكل الصحيح, فإننا نقلب ترتيب الحروف العربية ضمن النص.

بمعني, لو كان لدينا نص هكذا:

LLLL-RRR-LLL

حيث LLL هي حروف تتجه من اليسار الى اليمين, و RRR هي حروف تتجه من اليمين الى اليسار ..

في هذا النص, نقوم بعكس اتجاه المقطع اللذي يحتوي على RRR

كنت مرة أقرأ في مجلة بي سي مجازين و قرأت كلمة mirroring technology !!! فقلت في نفسي يبدوا أنها تكنلوجيا معقدة!!! و لكن بعد ان قمت بعملية قلب الحروف هذه .. اكتشفت ان هذه الـ mirroring technology ليست سوى عملية قلب الحروف!!!

بصراحة بعض الناس يحب اختراع كلمات رنانة لإبهار الآخرين!!!

(هذا كان في عدد صادر عام 98 .. في الحقيقة ليس لدي سوى ثلاثة أعداد من تلك المجلة, كلها من تلك السنة .. و وقتها لم يكن لدي حاسوب أصلا!)

السبب في اختياري البدء بنص اتجاهه الأصلي من اليسار الى اليمين هو أنني اول ما حاولت تطبيق هذا الحل كان على لعبة و لم اكن ابالي كثيرا باتجاه النص و ما الى غيره .. اصلا لم تأتي على بالي تلك التعقيدات إلا لاحقا, و اللعبة طبعا انجليزية لذلك من الطبيعي ان يكون اتجاه النص هو من اليسار الى اليمين, فكل ما فكرت فيه وقتها هو عكس ترتيب الحروف العربية! و لم اكن افكر في هذه المصطلحات (يمين و يسار!).

المهم, فالفكرة اننا عندما ندخل كلمة مثل:

hello مرحبا

(و لنتغاضى هنا عن توصيل الحروف).. فإنها ستظهر على هذا الشكل:

hello ابحرم

و هو نفس التريب اللذي ضغطنا به الأزرار على الكيبورد.

فالمطلوب هو أخذ جزء من النص, تحديدا "ابحرم" و قلب ترتيب الحروف فيه بحيث تصبح "مرحبا", و هنا على فرض اننا نتعامل مع array, فإن ترتيب الحروف سيكون هكذا:

م

ر

ح

ب

ا

و هو ترتيب صحيح من الناحية المنطقية, لكنه لا يظهر بشكل صحيح, بسبب اتجاه الكتابة كما قلنا مسبقا.

فالترتيب المطلوب للحروف في الـ array سيكون:

ا

ب

ح

ر

م

و عندها سيصبح شكل النص ككل:

h

e

l

l

o

ا

ب

ح

ر

م

و عند وضعه بشكل افقي من اليسار الى اليمين (دون وصل الحروف) سيكون:

h e l l o م ر ح ب ا

و هذه هي الفكرة اللتي يقوم عليها الحل في النسخة الأولية, حيث كان كل شيء موضوعا في CBilStringContainer::SortRtl و قد استخدمت فيه أساليب إجرائية procedural, فكما قد تلاحظون, الكود معقد جدا و "داخل في بعضه" و متابعته صعبة, و صيانته أصعب! يعني لو اكتشفت خطأ بسيط سيجب على أن انظر الى الكود لفترة طويلة لأحاول تذكر كيف كان يعمل, ثم محاولة اكتشاف المكان اللذي يحدث فيه الخطأ (على فرض أنني فهمت كيف يعمل الكود) ثم محاولة إصلاح الخطأ, و هذا قد يتضمن إعادة كتابة أجزاء منه .. و هي عملية أيضا معقدة, لأنني سيجب على التركيز جيدا لكي لا أنسى ما اللذي يحدث ضمن كل هذه الفوضى و أن احرص على أن ما أضيفه لا يعكر سير البرنامج .. الخ.

هنا كنت استخدم ثلاث مصفوفات "متوازية" parallel arrays (لا تخلوا المصطلح يرعبكم!) يعني array يحمل الحروف, و array آخر يحمل اتجاهات هذه الحروف, و array آخر يحمل رقم الـ index المرتب لهذه الحروف.

نأتي الآن الى المشكلة, كيف نعرف اتجاه الحرف؟ يعني هل هذا الحرف L أم R؟! الحل السهل انه إذا كان حرفا عربيا فإنه R, و كل الحروف من عدا ذلك هي L ... و لكن هذا الحل لا يرضيني.

تخيل مثلا, لو أردت الآن كتابة C++ ضمن سطر اتجاهه العام من اليمين اليسار(يعني في هذا المنتدى مثلا, و افترض انك عندما تقرأه تستخدم internet explorer), مااللذي سيحدث هنا؟ الـ ++ جائت على يسار الـ C و هذا أعطى نتيجة غير مرغوبة .. فنحن عندما كتبنا ++ أردناها أن تكون تابعة للـ C يعني هي جزء من المقطع المتجه من اليسار الى اليمين.

طيب, كيف نعرف ذلك؟ عندما فكرت في الأمر, وجدت ان هناك في الحقيقة ثلاث حالات للحروف: إما ان اتجاهه الأصلي (الافتراضي, default) هو R و هذا إذا كان حرفا عربيا, أو L إذا كان حرفا انجليزيا أو رقما, أو N إذا كان اي شيء غير ذلك, و هذا يتضمن طبعا الرموز و علامات التعجب و الاستفهام .. و كل شيء آخر. ( N تعني neutral أي محايد).

و لنقل بأن أي حرف اتجاهه R هو حرف يميني, و أي حرف اتجاهه الأصلي L فهو حرف يساري,و أي حرف ليس لديه اتجاه محدد, فهو حرف محايد, أو حيادي.

الـ + حرف محايد, لذلك يجب علينا ان ننظر الى سياقه قبل ان نعطيه اتجاها معينا. لذلك سنمر على الحروف مرتين: المرة الأولى ننظر فيها الى كل الحروف بشكل عام (نمر على النص حرفا حرفا, دون النظر الى السياق) و نحدد الاتجاهات الأصلية للحروف, مع إعطاء الحروف المحايدة اتجاها محاديا. ثم في المرة الثانية ننظر الى سياق الحروف المحايدة و نحدد بناء عليه ما هو الاتجاه اللذي يجب ان يعطى لها. (في النسخة الأولى من الكود استخدمت طريقة مختلفة قليلا, و لكن نفس الفكرة)

كيف سنستخدم السياق لتحديد ذلك؟ حسنا لننظر الى حالة الـ C++ مثلا .. محررات النصوص العادية تضع الـ C++ بشكل صحيح إذا أتبعناها بحرف انجليزي .. لاحظوا مثلا: C++ r هنا الظاهر ان البرنامج رأى ان الرموز الحيادية تقع ضمن نطاق عام اتجاهه يساري (من اليسار الى اليمين), لذلك قام بقلب الـ++ الى حروف يسارية ..

و لكن لحظة .. هناك مسافة بعد الـ + و قبل الـ r, و المسافة حرف حيادي أيضا .. أها .. إذا هناك مجموعة حروف حيادية هنا, هي "++ " (زائدين و مسافة) تم قلبها الى حروف يسارية ... أما عندما لم يكن هناك حرف الـ r و كانت الـ C++ محاطة بسياق يميني, فهنا نظر البرنامج و رأى حرف الـ C كأنه حرف يساري و لكنه يتيم! فهو وحده هنا!

هممم .. و لكن هذا غباء بعض الشيء .. أنا عندما وضعت المسافة أردتها أن تكون فاصلا بين الكلمة الانجليزية و الكلمة العربية, لذلك من المفترض ان تعتبر C++ كلمة انجليزية و ما يليها بعد المسافة هو كلمات عربية. خصوصا و أنني ألصقت الـ++ بالـ C يعني لم أضع مسافة بينهما, فالمفروض ان يكونوا نفس الكلمة!! أليس كذلك؟

فالـ + حرف محايد, لكنه جاء مباشرة بعد حرف يساري, كما أن الحرف اللذي يليه أيضا حرف محايد, لذلك لا يحق لنا أن نفترض ان هذا الزائد يميني لمجرد ان السياق العام يميني. مادام قد جاء بعد حرف يساري فهناك احتمال أن يكون جزءا من الكلمة اليسارية.

أقول هناك احتمال, لأنه قد لا يكون الوضع دائما هكذا ..

تذكروا ان المسافة حرف محايد, و لكن عندما نضعها بين كلمة انجليزية و أخرى عربية, فنحن هنا قد وضعنا المسافة مباشرة بعد الكلمة الانجليزية, و لكنه ليست جزءا من الكلمة!!!

همم .. لماذا؟ لأننا وضعنا بعدها كلمة عربية!

أها, لنعيد الخطة: لنفرض ان الاتجاه العام للنص هو اتجاه يميني, لو وجدنا حرفا محايدا جاء بعد حرف يساري, و لم يكن الحرف اللذي يلي هذا الحرف المحايد يمينيا, فسنعتبر ان هذا الحرف المحايد هو يساري (في هذا السياق).

فبعد شيء من التفكير, توصلت الى هذه الطريقة (و هي من اختراعي) لتحديد اتجاه للحروف المحايدة في سياق معين (عندما فكرت في هذه الطريقة, كنت افترض ان سياق النص العام هو يساري, و لكن نستطيع بكل سهولة اشتقاق طريقة عامة بغض النظر عن سياق النص)

لنفرض ان لدينا النص "ن" و هو عبارة عن جملة تحتوي حروفا معينة. بعض هذه الحروف سيكون اتجاهه الافتراضي "أ", و البعض الآخر سيكون اتجاهه الإفتراضي "ب", و بعضها لن يكون لديه اي اتجاه افتراضي, فنعطيه اتجاها "م".

على فرض ان الاتجاه العام في السياق "ن" هو "أ", فإن أي حرف "س" اتجاهه "م" سيعطى الاتجاه "ب" إذا (و فقط إذا) كان الاتجاه الافتراضي للحرف الذي يسبق "س" هو "ب" و لم يكن الاتجاه الافتراضي للحرف الذي يلي "س" هو "أ".

فإذا لم يكن الوضع كذلك, فإن الحرف "س" سيأخذ الإتجاه "أ" (اللذي هو الاتجاه العام للنص).

هنا الاتجاه "أ" هو الاتجاه الرئيسي للنص (الاتجاه العام, مثلا في هذا المنتدى, الاتجاه العام للكتابة هو من اليمين الى اليسار, يعني حسب مصطلحاتي, اتجاه يميني) و "ب" هو الاتجاه الفرعي للنص.

تستطيع استبدال "أ" و "ب" بأي من "يميني" و "يساري" و تطبق الفكرة عليهما.

في النسخة الأولى من البرنامج كنت أفترض دائما ان الاتجاه الرئيسي (و اللذي رمزت له بـ"أ") هو الاتجاه اليساري, لذلك فكنت أمر على الحروف على مرحلتين بهذه الطريقة:

المرحلة الأولى: حدد الاتجاهات الأصلية لكل الحروف, إذا وجدت حرفا محايدا, أعطه الاتجاه الرئيسي L.

المرحلة الثانية: مر على كل الحروف, إذا وجدت حرفا اتجاهه L لكن اتجاهه الافتراضي ليس L (معنى هذا ان هذا الحرف في الأصل كان محايدا) فانظر الى الاتجاه الأصلي للحرف اللذي يليه, فإذا لم يكن هذا الاتجاه هو الاتجاه الأساسي, معنى هذا أننا يجب ان نحول اتجاه هذا الحرف إلى R. أما إذا لم يتحقق هذا الشرط, فمعنى هذا ان اتجاه هذا الحرف المحايد يجب أن يكون L, و لكن لا نحتاج لتغييره لأننا قد اعطيناه هذا الإتجاه في المرحلة السابقة.

طبعا, قبل تحديد الإتجاهات قد يكون لدينا نص شكل اتجاهاته هكذا:

LLLNNRLLNRLLNLRN

أما بعد ذلك, فستخفي كل الـ Ns و تتحول إما الى R أو L يعني سنحصل على نص شكل اتجاهاته العام يشبه:

LLLRRRLLLLLLLRRLL

كمثال فقط ..

هنا نستطيع البدء بقلب المقاطع RRR و هي عملية رياضية .. قمت بالجلوس لمدة ربع ساعة مع ورقة و قلم و استنتجت آلية لعمل ذلك, أعتقد اني وجدت بها بعض الأخطاء فقمت بتصحيحها عدة مرات ..

المهم انا عملت في النهاية! و الحمد لله.

هذا في النسخة الأولى, حيث كان كل شيء في CBilStringContainer::SortRtl , و كنت قد كتبت ملاحظة فوقه ان الكود هنا معقدة و داخلة في بعضها يعني ..

و في الحقيقة كل البرنامج كان مختزلا في ذلك الـ function, باستثناء الـ CHarf طبعا, فكان الحل اللذي ابتكرته في الحقيقة procedural رغم انني استخدمت classes, فالبرمجة الشيئية OOP ليست مجرد كتابة فئات و إنشاء كائنات منها .. بل البرمجة الشيئية تكون في التصميم, فإذا كان هناك عدة فئات لكن الكود الموجود بها معقد و داخل في بعضه .. فما الفائدة إذا؟ لم نستفد شيئا من التوجه نحو الكائنات.

الصحيح هو فصل كل شيء الى كائن. و هذا ما قمت به في النسخ اللاحقة, حيث حاولت القيام بعدة تطويرات مهمة لإعادة هيكلة البرنامج و فصل كل شيء على حدة, و هذا ما سآتي إليه في المرة القادمة إن شاء الله.

#7

السلام عليكم أخي حسان

بالنسبة للنقطة الأخيرة التي ذكرتها

إذهب إلى موقع عرب أيز سوف تجد فيه السورس كود لمحرر نصوص يدعم العربية ويدعم LLLL-RRR-LLL قد يفيدك في تحقيق ما تريد

أحببت أن أشارك بما أعرف وشكراً

#8

أعتذر عن ترك الموضوع .. واضح انه لا يوجد اهتمام, و لكن من واجبي شرح الموضوع على كل حال.

بالنسبة لـ Arab Eyes فهم يستخدمون fribidi و هو على ما أظن يأتي أيضا مع توزيعات اللينكس اللتي فيها دعم للعربية.عموما ذكرت في وقتها أنني لم أفهم fribidi كما أنني قررت كتابة الكود بنفسي على كل حال لكي أستفيد من الخبرة اللتي اكتسبها من ذلك.

المهم ..

احنة عدنة استاذ لمادة البرمجة .. و الكورس هو عن البرمجة الشيئية oop و استاذنا واحد معقد, و لكنه معقد بشكل جيد, فهو يركز بشكل كبير جدا على أهمية الـ design او التصميم في البرنامج و التفكير فيه مليا قبل البدء في كتابة الكود, و كان مما أخبرنا به أيضا ان كثيرا من الناس يظنون أنهم يبرمجون بطريقة كائنية, لكنهم مخطئون, فبرامجهم حقيقة هي برامج إجرائية.

و بدأت بعد فترة قصيرة بالتفكير فيما يقوله الاستاذ و إعادة النظر في البرامج اللتي أكتبها و البرامج التي كتبها غيري و اللتي كنت أظن أنها object oriented, بدأت أنظر إليها و بدأ ينتابني شعور انها مما يصنفه الاستاذ بالبرامج غير الكائنية.

قمت بإرسال رسالة إليه و عرضت عليه مقطعا من كود لعبة Half-Life 2, فقال أنها أبعد ما تكون عن الـ object oriented و أنها في الحقيقة إجرائية.

و درسنا أيضا في هذا الكورس, العديد من المفاهيم اللتي في الحقيقة لم أجدها أبدا في الانترنت طوال الفترة اللتي كنت أتعلم فيها البرمجة لوحدي... لكنها مفاهيم مهمة جدا, لا أدري كيف يمكن للناس إهمالها بهذا الشكل!!

cohesion و coupling, أعتقد ان علي ان أكتب عنهما قليلا (فيما بعد إن شاء الله) لكنها مفاهيم مهمة جدا, و هي مرتبطة ارتباط كبير جدا بالبرمجة الكائنية, و هي تفسر العديد من الأمور اللتي قد تبدو غامضة: لماذا الـ private و الـ public؟؟! و لماذا لا يستطيع الكلاس الإبن الوصول الى المتغيرات الـ private في الكلاس الأب؟ و كثير من الأشياء مثلا .. listener design pattern من سمع به ربما يتسائل عن فائدته او لماذا تم التفكير فيه أصلا, كثير من هذه الأسئلة أجوبتها تصل في النهاية الى الـ cohesion هذا .. المهم ..

ما تعلمته في الكورس دفعني إلى إعادة التفكير في هيكل الكود اللذي قمت بكتابته,و بعد فترة من التفكير كانت النتيجة هي ما توصلت إليه (ما وضعته في هذا الموضوع),و لا أريد أن أدعي ان الكود الآن أصبح ممتازا ولا شي من هذا, و لكن هذا ما استطعت الوصول إليه.

فكرت أولا في كيفية تمثيل نص "ثنائي الاتجاه" bidirectional, و كنت في السابق أستخدم ثلاث مصفوفات "متوازية", قررت استبدلها بمصفوفة واحد من struct يحتوي على ثلاث عناصر: الشكل الأصلي للحرف, شكل الحرف بعد اختيار شكله المناسب في السياق (خاص بالحروف العربية فقط) و العنصر الثالث هو اتجاه الحرف. طبعا فيما بعد قمت بتطوير الـ struct الى كلاس و الغيت الـ public access للمتغيرات و وضعت accessor methods .. الخ.

و في الحقيقة أيضا, لم استخدم مصفوفة array بل حاولت استخدام linked list و لقد قمت في البداية بمحاولة كتابة كودي الخاص للنكد لست, لكن اكتشفت في النهاية أنني سأوفر على نفسي الكثير من المشاكل إن استخدمت واحد من الـ containers في الـ STL و بدلا من اللنكد لست, استخدمت vector و هو مصفوفة ديناميكية قابلة لتغيير الحجم الخ.

طبعا السنتاكس الخاص بالـ templates مزعج جدا و صعب القراءة, لذلك فضلت كتابة كلاس صغير لتغليف الـ vector و لتغليف الـ iterator الخاص به أيضا ..و هذا في char_vector.cpp و char_vector.h حيث هذه الملفات مجرد تغليف للـ std::vector و الـ iterator الخاص به.

الكلاس CharVector هو اللذي يتحمل مسئولية تمثيل سلسلة من العناصر من نوع Char, أو لنقل, تمثيل نص ثنائي الاتجاه. و لكنه لا يفعل شيئا غير ذلك.

بعد ذلك فكرت في كيفية معالجة هذا النص .. هناك ناحيتان للمعالجة: توصيل الحروف, و ترتيب الاتجاهات.

بالنسبة لترتيب الاتجاهات, خطرت في بالي فكرة, و هي تقسيم النص الى مقاطع صغيرة حسب الاتجاهات, يعني مقطع كله يمين, مقطع كله يسار, الخ. ثم نستطيع النظر الى هذه المقاطع بشكل منفصل, و تحديد ما إذا كنا نريد عكس اتجاه أي مقطع, و بعد ذلك نقوم بإعادة تجميع المقاطع مع بعضها (بعد ان عكسنا المقاطع اللتي تحتاج ان تعكس او تقلب) و بهذا لا احتاج حتى ايجاد خوارزمية لقلب تسلسل سلسلة معينة من الحروف (مع أني كتبتها أصلا في الكود القديم)

المهم, قمت بعدة محاولات لكتابة الكود بحيث ان النص عبارة عن مجموعة من المقاطع, لكني لم أنجح, كما انني فكرت في أن هذه الفكرة ليست جيدة تماما ..

ما كنت أفكر فيه هو تمثيل النص كمجموعة مقاطع, أي بناء هذه الفكرة في الكلاس اللذي يمثل النص .. لكنني قررت تغيير هذه الشيء, مع الإبقاء على الفكرة بشكل عام.

في النهاية قررت وضع كود توصيل الحروف و كود قلب مقاطع الاتجاهات كل في كلاس خاص بها, و بالنسبة لقلب المقاطع (سأسميها المقاطع الاتجاهية!) وضعت عدة درجات من التسلسل الطبقي (من ناحية الوراثة) لأن هناك عدة طرق لتحديد منهج قلب المقاطع, فأردت وضع الكود العام في الكلاس الأب ثم وضع بعض التفاصيل في عدة كلاسات مشتقة لأنه في الغالب هناك نوعين: إما ان الاتجاه العام للنص هو من يميني, ففي هذه الحالة نريد قلب المقاطع اليسارية فقط, او بالعكس, النص اتجاهه يساري, فنقلب المقاطع اليمينية فقط .. و ربما هناك طرق أخرى يتقرر إضافتها لاحقا.

الفكرة بقيت كما هي: طريقة قلب النصوص بشكل عام تكون هكذا: ناخذ النص الكامل, نمر عليه عدة مرات الى أن نصل الى النهاية, في كل مرة نستخرج مقطعا معينا من النص (مقطع ذو اتجاه معين) و وضع المؤشر في بداية المقطع القادم (المؤشر هنا هو الـ iterator) نقوم بالتأكد من اتجاه المقطع (لكل مقطع) و نحدد ما إذا كنا نريد عكس اتجاهه أم لا, بعد ذلك, نضيف هذا المقطع الى نص آخر

بعد الانتهاء من كل المقاطع, سيكون لدينا نص جديد مؤلف من هذه المقاطع بعد ان تمت معالجتها.

الآن نقوم بنسخ النص الجديد الى النص القديم. و هكذا تكون كل المقاطع اللتي تحتاج الى قلب قد قلبت.

بالنسبة لتوصيل الحروف, فلا يوجد اختلاف كبير عن المرة السابق, كل ما قمت به هو فصل المهمات الى عدة كلاسات, فبعد أن كان كل شيء في CHarf وزعت الأمور على Harf و HarfMap و HarfShaper

و العملية أيضا تمر على ثلاث مراحل, لكنها الآن في BidiString::process مع العلم أن BidiString يرث من CharString و اللذي يرث من CharVector و السبب في هذه التقسيمات هو احتمال إضافة طرق جديدة لمعالجة النص, لأن الطريقة اللتي أقوم بها ليست هي الطريقة الرسمية... فالطريقة الرسمية هي هنا http://www.unicode.org/reports/tr9/ و الفرق هو أن هذه الطريقة تأخذ في نظر الاعتبار احتواء النص على رموز خاصة تدل (حسب ما أظن) على اتجاهات النص لإعطاء مزيد من المرونة في التعامل مع النصوص .. و لو تجربون ضغط رايت كلك على حقل نصي في الويندوز اكس بي ستلاحظون وجود خيار Insert unicode control character و من ضمنها أشياء مثل LRM, RLM, LRE, RLE و غيرها, و هي أشياء لم أدرسها بعد, فهي تحتاج الى شيء من الدراسة و معرفة متى يقوم النظام بإدخالها وسط الجملة (إن كان يقوم بذلك أصلا)

المهم, كنت أقول ان عملية المعالجة تجري في BidiString::process في bidirectional_string.cpp و لاحظوا أنه يستدعي الـ methods بطريقة "بوليمورفية" حيث ان هناك كلاسين يرثان منه: واحد لنص عام اتجاهه يميني, و الآخر يمثل نصا عاما اتجاهه يساري.

و العملية بكل بساطة هي استدعاء الكائن اللذي يقوم بتوصيل حروف هذا النص و ارسال رسالة له مفادها ان "أوصل حروفي ببعضها!” و إرسال رسالة الى الكائن اللذي يتولي تحديد اتجاهات الحروف مفادها ان "حدد اتجاهات حروفي" و رسالة الى الكائن اللذي يقلب "المقاطع الاتجاهية" مفادها ان اقلب المقاطع اللتي تحتاج قلبا في النص.و بعد ذلك تكون عملية المعالجة قد تمت.

ما هي هذه الكائنات؟ هنا تأتي البوليمورفزم .. فكل واحد من الكلاسات امشتقة يستخدم كائنا خاصا به. مثلا RightToLeftString يستخدم كائنا من نوع LeftMirror (أي ان الكائن اللذي يمثل نصا اتجاهه العام يميني, يستخدم كائنا يقلب المقاطع اليسارية فقط) و هكذا ..

في هذه المرحلة, فإن هذا المستوى اللذي وصلت إليه, و اللذي أنا أفتخر بنجازه رغم تواضعه, حيث فلقد كنت أشك حتى في الوصول إليه, لكن بفضل الله وصلت اليه.

كنت أقول, إن هذا المستوى دون المطلوب.

لا يزال هناك على الأقل أمرين او ثلاثة يحتاجان للإهتمام:

الأول, اتباع الخوارزمية الرسمية و الاهتمام بالرموز الخاصة control characters

و الثاني, التفكير في طريقة لحل مشكلة الحركات (التشكيل .. الفتحة و الضمة و الكسرة .. الخ)

و الثالث, (و هو الأقل أهمية بالنسبة لي في هذه المرحلة) هو اكتشاف الحروف اللتي تغير شكلها حين تتصل ببعضها, في المقام الأول فإن الحرفين الوحيدين هما الألف و اللام, “لا" و لكن هناك حروف أخرى, رغم أنها ليست ضرورية, لكنها تعطي جمالا للنص, مثل الميم إذا جائت بعد اللام (مثل الخط traditional arabic في ويندوز) و غيرها .. و لكن هذا أقل شيء في سلم الأولويات, لأني أعتقد أصلا أن هذه الأشياء خاصة بالخط نفسه, و ليست جزءا من اليونيكود و الله أعلم, قد أكون مخطئا.

على فكرة, أظن ان الـ fribidi لا تقدم حلا جيدا لمشكلة الحركات, ففي لينكس, إذا كان هناك نص يحتوي على حركات, فإن الحركات تقف فيما بين الحروف (أي تفصل بينها) و لا تقع عليها مباشرة.

المشكلة هنا أننا يجب ان ندخل في تفاصيل كيفية رسم الحروف, أي لا يكفي تغيير رموز اليونيكود في النص, و لكن برأيي يمكن حل هذه المشكلة بإيجاد مفهوم قياسي معتمد لرسم الحركات, و من ثم ما علينا سوى إنتاج معلومات بالهيئة المطلوبة لتعمل مع هذا لمفهوم القياسي. و في الأنظمةالمفتوحة المصدر قد يكون الأمر أكثر سهولة إن شاء الله.

ما أقصده بالمفهوم القياسي هو مثلا اقتراح طريقة لرسم الجملة أو الـ string على عدة مراحل: أول مرحلة هي رسم الحروف, ثم رسم المستوى الأول من الحركات, ثم المستوى اللذي يليه (إن وجد) لأنه من الممكن وضع عدة حركات في نفس المكان (شدة فوقحها فتحة مثلا .. ) و لكن هذه الحركات ستكون فوق بعضها, فنقول ان هناك مستويين من الحركات: المستوى الأول هو الشدة و المستوى الثاني هو الفتحة. و نقول مثلا أن هناك ثلاث مستويات للنص (هذه مجرد فكرة) المستوى الأول هو الحروف, و المستوى الثاني هو الحركات(الشدة), و المتسوى اللذي يليه هو المستوى التالي منالحركات, اي الحركات اللتي تقف فوق حركات أخرى (الفتحة). فالبرنامج اللذي سيقوم برسم الحروف, عليه ان يرسم جميع المستويات (فوق بعضها) قبل أن يقول لنفسه انه انتهي من رسم هذا النص.

مرة أخرى, هذه مجرد فكرة او اقتراح.

من كان يريد النظر الى تطبيق عملي لهذا البرنامج اللذي قمت بكتابته, فلينظر إلى main و لكن ليستخدم debugger و لينظر الى الأسطر:

	leftString.put( d );	//before
	leftString.process();
	leftString.put( d );	//after

حيث إني في المرة الأولى أقوم بوضع النص في مصفوفة حروف عادية c-style string و بالنظر الى عناصر المصفوفة في الديبجر, سنرى حالة الحروف قبل المعالجة.

في السطر الثاني أقوم بمعالجة النص, ثم في السطر الثالث أقوم بوضع محتويات النص في المصفوفة السالفة الذكر من جديد, و لكن هذه المرة بعد أن تمت معالجتها.

طبعا في حال استخدام VC++ فإن الديبجر فيه دعم للعربية و يظهر النص بشكل مرتب من المرة الأولى, و لكن بالنظر الى عناصر المصفوفة منفصلة, سنرى أنها في الحقيقة غير مرتبة.

و شاهدوا الصورة إن أردتم:

http://pages.cpsc.ucalgary.ca/~abdurahh/bi...i-in-action.png

طبعا أول سطر (اللذي يظهر بالأحمر) يظهر النص بشكل مرتب (حتى لو لم يكن مرتبا من الأصل) فقبل المعالجة يظهر النص بالطريقة اللتي عالجه بها الويندوز. لاحظوا أن هناك اختلاف بسيط بين قبل المعالجة و بعد المعالجة, قبل المعالجة تظهر طريقة ويندوز في الترتيب, اما بعد المعالجة تظهر طريقتي في الترتيب.

لاحظوا أن طريقة ترتيبي تختلف عن طريقة ويندوز, في طريقة ويندوز, علامة التعجب جائت على يمين "ابجد", مع انها تتبعها مباشرة منطقيا,لكن في رسمها بدت كأنها جزء من النص الانجليزي. أما في طريقتي, فهي تتبع الكتابة العربية فجائت بعدها مباشرة.

طبعا هذا الاختلاف بسيط, لكنه من المفروض ان يحل الـ C++ حين تظهر الـ ++ بعكس الاتجاه المطلوب!!

على فكرة, برنامج Word في مايكروسوفت يرتب الحروف بطريقة أفضل, فلو كتبت فيه C++ ستظهر بالشكل الصحيح, و أظن ان السبب في ذلك هو استخدام الحروف الأخرى الخاصة control characters او ربما الورد يستخدم خوارزمية أخرى خاصة به. و الله أعلم

#9

السلام عليكم

ربنا يوفقك وبارك الله فيك على هذا المجهود

/index.php/topic/264448-%D8%A7%D9%84%D8%A8%D8%AF%D8%A7%D9%8A%D8%A9-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D9%86%D8%AF%D8%B1%D9%88%D9%8A%D8%AF/

 

اني وان كنت الاخير زمانه ---- لأتِ بما لم تستطعه الاوائلُ

#10

Up

عذراً لعدم وضع رد علمي مؤقتاً ...

لسه ما قرأت الموضوع بتمعن ...

"First they ignore you, then they laugh at you, then they attack you, then you win"

===

اقتباس

حُكَّامُـنَا إِنْ تَصَـدّوا لِلْحِمَــى اقْتَحَمُـوا ***** وَإِنْ تَصَدَّى لَـهُ المُسْتَعْمِـرُ انْسَحَبُـوا

هُمْ يَفْرشـُونَ لِجَيْـشِ الغَــزْوِ أَعْيُنَـهُـمْ ***** وَيَدَّعُــونَ وُثُـوبَـاً قَـبْـلَ أَنْ يَثِـبُــوا

الحَاكِمُـونَ و«وَاشُنْـطُـنْ» حُكُومَتُـهُـمْ ***** وَاللامِعُــونَ وَمَـا شَعَّـوا وَلا غَرَبُــوا

القَاتِلُـــونَ نُبُــوغَ الشَّـعْــبِ تَرْضِـيَـــةً ***** لِلْمُعْتَدِيــنَ وَمَـا أَجْـدَتْـهُـمُ الـقُــرَبُ

لَهُمْ شُمُـوخُ «المُثَنَّـى» ظَاهِـرَاً وَلَهُـمْ ***** هَـوَىً إِلَـى بَابَـك الخَرْمِـيّ يُنْتَسَـبُ

البردوني "أبو تمام وعروبة اليوم" 1971

===

اقتباس
عبيد الهوى يحكمون البـلاد ***** ويحكمهــم كلّهـــم درهــــــم

و تقتـادهـم شهـوة لا تنــام ***** وهـــم فـي جـهالتهــم نــــوّم

ففــي كـــلّ ناحيــة ظـالـــم ***** غبــــــيّ يسـلـّطــــه أظلـــم

أيا من شبعتم على جوعــنا ***** وجـــوع بنينـا ألـم تتخمـــوا ؟

ألم تفهموا غضبة الكادحين ***** على الظلـم ؟ لا بدّ أن تفهموا

البردوني "نحن و الحاكمون "

هذا الموضوع مغلق.

مواضيع مشابهة