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

ترميز الحروف

بدأه C++er في 24 أبريل 2014 · 6 رد · 14,815 مشاهدة · في المقالات العلمية و التقنية
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

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


العقل الذى وهبه الله للإنسان يستطيع التفرقة بين الكلمات المكتوبة بلغته و أى لغه أخرى و هذا على خلاف الأله التى تحتاج لوسيله محدده للتفرقة بين حروف اللغات التى يتم الكتابة بها. هذه الوسيلة تسمى بترميز الحروف و هى محور هذا الموضوع[1].

فى هذا الموضوع

مقدمة

اللغات الطبيعية القائمة على حروف، كل حرف بها له 3 خصائص:

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

فى علوم الحاسب العلم الذى يختص بدراسة أصوات الكلام هو Speech Processing و العلم الذى يختص بدراسة شكل الحرف و رسمه هو Typography. أما ترتيب الحرف فالمفهوم داخل الحاسب يختلف على ما هو عليه فى اللغات الطبيعية و المجال الذى يختص بدراسته هو Character encoding.

يعتبر مجال Character encoding من أهم مجالات الحاسب التى لا يهتم بها الكثيرون، فبسببه أمكن رسم الحروف و طباعتهم و التعامل معهم، و قد مر هذا المجال بالعديد من المراحل إلى أن وصل لمرحلة الإستقرار التى نشهدها حاليا.

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

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

بعدها بفترة قام دونالد ميوراى بإجراء بعض التعديلات على شفرة بودت و أضاف إليها بعض الحروف الغير مرئية و سميت شفرة ميوراى و قام بإستخدام الشفرة المعدلة داخل اله كاتبه بسيطه ذات ازرار تكتب بها و عندما تضغط على زر نهاية السطر يتم إرسال محتويات السطر دفعة واحده[2].

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

عندما تم توسيع المساحه المستخدمه للحرف إلى 6 بت تم إبتكار العديد من وسائل لترميز الرسائل و أكثرها إنتشارا وقتها كان من تصميم IBM و إسمه Binary-Coded Decimal حيث تم تقسيم الـ 6 بت إلى جزئين و كل منهم يمثل الأرقام من صفر و حتى 7 و بعدها تم إستخدام هذه الأرقام فى مجموعات ذات معنى متشابه.

بعد فترة من الزمن تم إقتراح إضافة الكود shift للحروف الموجوده و ذلك ليعطى معنى أخر لكل كود موجود و بالتالي يتم مضاعفة الحروف المستخدمه فبدلا من 64 حرف يصبحوا 128 و أصحاب الفكرة أقروا بأن هذه الوسيلة ستسمح بتمثيل الحروف الكبيرة و الصغيرة بالإضافة لأكواد التنسيق و الأرقام و أضافوا أن القيم المرسلة ستكون صغيرة، و لكن بعد التصويت تم رفض هذا الإقتراح و السبب الذى أجمتعوا عليه أنه إذا حدث خطأ فى إرسال البيانات فوجود الكود shift سيجعل الرسالة بأكملها تالفه و لا يمكن إسترجاع أى شئ منها[3].

رفض إضافة الكود shift أدى إلى إضافة بت جديد للمساحه الموجوده لتصبح 7 بت و هذا أدى إلى ميلاد ASCII.

 
 
ترميز ASCII

وجدت العديد من المحاولات لتصميم ترميز بمساحة 7 بت و أنجح هذه المحاولات كان فى عام 1963 حيث قامت منظمة American Standards Association بتصميم ترميز يحتوى على 128 رمز بمساحة 7 بت للرمز الواحد و سمي بـ American Standard Code for Information Interchange و إختصارا ASCII.

السبب فى إستخدام الكلمه رمز و ليس الكلمة حرف عائد إلى أن تصميم ASCII الأصلي كان يحتوى على أكواد لرموز غير مرئية و حروف و علامات ترقيم و أرقام و غيرهم و لهذا فالكلمه رمز تعطي معنى أدق و أعم من غيرها. التصميم الأصلي قام بحجز أول 32 كود للعمليات الغير مرئية و البقية لتمثيل الحروف و الأرقام و علامات الترقيم و غيرهم، و لكن فى ذلك التصميم لم يتم تحديد طبيعة معظم الأكواد الـ 32 الأولى و هذا سبب تضارب بين الأجهزة التى صممتها بعض الشركات حيث بعضهم كان يستخدم هذه الأكواد لتعنى شئ فى حين أخرين يستخدموا نفس الأكواد لتعنى شئ أخر[4]. بالإضافة لهذه المشكلة ظهرت مشكلة إرسال بعض الأكواد الغير مرئية ليتم تنفيذها على الجهاز الهدف بدل من تنفيذها على جهاز المصدر، هذه المشكلة تم حلها بإستخدام الكود ESC و الذى يتم إرساله و بعده سلسلة من الحروف بهيئة معينه و التى عندما يتم إستقبالها على الجهاز الهدف يتم تحويلها لكود و يتم تنفيذه. الكود ESC أظهر سلسلة جديدة من الأكواد عرفت بالإسم ANSI Escape code.

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

التوثيق الذى تم إصداره فى 1967 تم ترك بعض الحروف من ASCII للإستخدام الدولي و الذى أدى لظهور بعض المشاكل منها عدم قيام بعض الدول بتحديد حروف للاماكن المتاحه. من الأكواد التى تركت للإستخدام الدولي رمز القوس المفتوح و المغلق و الذى جعل مصممي لغة الـ C و بعض اللغات الأخرى بإضافة ما يسمى Trigraph Sequence داخل تصميم لغاتهم و ذلك ليتم إستخدامهم بدلا من بعض الرموز الغير مدعومه فى ترميز لغة معينه.

فى عام 1968 تم إستخدام الإسم US-ASCII للإشارة للنسخه التى تحتوى على الحروف الإنجليزية و الرموز التى تم وضعها من قبل ASA و تم تشيجع إستخدام هذا الإسم حيث كان يوجد وقتها بعض النسخ الأخرى من ASCII مثل German-ASCII و French-ASCII و غيرهم.

فى عام 1986 تم الإستقرار على محتوى US-ASCII و الذى يتم إستخدامه حتى يومنا هذا، و هو كالتالي:

╔═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╦═════╗
║ NUL ║ SOH ║ STX ║ ETX ║ EOT ║ ENQ ║ ACK ║ BEL ║  BS ║  HT ║  LF ║  VT ║  FF ║  CR ║  SO ║  SI ║
║ 001 ║ 001 ║ 002 ║ 003 ║ 004 ║ 005 ║ 006 ║ 007 ║ 008 ║ 009 ║ 00A ║ 00B ║ 00C ║ 00D ║ 00E ║ 00F ║
╠═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╣
║ DLE ║ DC1 ║ DC2 ║ DC3 ║ DC4 ║ NAK ║ SYN ║ ETB ║ CAN ║  EM ║ SUB ║ ESC ║  FS ║  GC ║  RS ║  US ║
║ 010 ║ 011 ║ 012 ║ 013 ║ 014 ║ 015 ║ 016 ║ 017 ║ 018 ║ 019 ║ 01A ║ 01B ║ 01C ║ 01D ║ 01E ║ 01F ║
╠═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╣
║ SPC ║  !  ║  "  ║  #  ║  $  ║  %  ║  &  ║  '  ║  (  ║  )  ║  *  ║  +  ║  ,  ║  -  ║  .  ║  /  ║
║ 020 ║ 021 ║ 022 ║ 023 ║ 024 ║ 025 ║ 026 ║ 027 ║ 028 ║ 029 ║ 02A ║ 02B ║ 02C ║ 02D ║ 02E ║ 02F ║
╠═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╣
║  0  ║  1  ║  2  ║  3  ║  4  ║  5  ║  6  ║  7  ║  8  ║  9  ║  :  ║  ;  ║  <  ║  =  ║  >  ║  ?  ║
║ 030 ║ 031 ║ 032 ║ 033 ║ 034 ║ 035 ║ 036 ║ 037 ║ 038 ║ 039 ║ 03A ║ 03B ║ 03C ║ 03D ║ 03E ║ 03F ║
╠═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╣
║  @  ║  A  ║  B  ║  C  ║  D  ║  E  ║  F  ║  G  ║  H  ║  I  ║  J  ║  K  ║  L  ║  M  ║  N  ║  O  ║
║ 040 ║ 041 ║ 042 ║ 043 ║ 044 ║ 045 ║ 046 ║ 047 ║ 048 ║ 049 ║ 04A ║ 04B ║ 04C ║ 04D ║ 04E ║ 04F ║
╠═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╣
║  P  ║  Q  ║  R  ║  S  ║  T  ║  U  ║  V  ║  W  ║  X  ║  Y  ║  Z  ║  [  ║  \  ║  ]  ║  ^  ║  _  ║
║ 050 ║ 051 ║ 052 ║ 053 ║ 054 ║ 055 ║ 056 ║ 057 ║ 058 ║ 059 ║ 05A ║ 05B ║ 05C ║ 05D ║ 05E ║ 05F ║
╠═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╣
║  `  ║  a  ║  b  ║  c  ║  d  ║  e  ║  f  ║  g  ║  h  ║  i  ║  j  ║  k  ║  l  ║  m  ║  n  ║  o  ║
║ 060 ║ 061 ║ 062 ║ 063 ║ 064 ║ 065 ║ 066 ║ 067 ║ 068 ║ 069 ║ 06A ║ 06B ║ 06C ║ 06D ║ 06E ║ 06F ║
╠═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╬═════╣
║  p  ║  q  ║  r  ║  s  ║  t  ║  u  ║  v  ║  w  ║  x  ║  y  ║  z  ║  {  ║  |  ║  }  ║  ~  ║ DEL ║
║ 070 ║ 071 ║ 072 ║ 073 ║ 074 ║ 075 ║ 076 ║ 077 ║ 078 ║ 079 ║ 07A ║ 07B ║ 07C ║ 07D ║ 07E ║ 07F ║
╚═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╩═════╝

و الـ Control Characters هم:

00 = NUL = Null character
01 = SOH = Start of Header
02 = STX = Start of Text
03 = ETX = End of Text
04 = EOT = End of Transmission
05 = ENQ = Enquiry
06 = ACK = Acknowledgment
07 = BEL = Bell
08 = BS  = Backspace
09 = HT  = Horizontal Tab
0A = LF  = Line feed
0B = VT  = Vertical Tab
0C = FF  = Form feed
0D = CR  = Carriage return
0E = SO  = Shift Out
0F = SI  = Shift In
10 = DLE = Data Link Escape
11 = DC1 = Device Control 1
12 = DC2 = Device Control 2
13 = DC3 = Device Control 3
14 = DC4 = Device Control 4
15 = NAK = Negative Acknowledgment
16 = SYN = Synchronous idle
17 = ETB = End of Transmission Block
18 = CAN = Cancel
19 = EM  = End of Medium
1A = SUB = Substitute
1B = ESC = Escape
1C = FS  = File Separator
1D = GS  = Group Separator
1E = RS  = Record Separator
1F = US  = Unit Separator
7f = DEL = Delete

 
 
 
صفحة المحارف Codepage

كانت أحد المشاكل فى ASCII انه لا يوجد توثيق للنسخ المعدله حيث كل دوله كان لها نسختها الخاصه من ASCII التى تطورها و ترعاها، هذا التشتت جعل ISO تصدر اول توثيق لها فى مجال تبادل المعلومات و أسمته ISO 646

توثيق ISO 646 كان يماثل توثيق ASCII مع ترك بعض الأماكن المحدده للإستخدام الدولي، و كل من استخدم هذا التوثيق و تقيد بقيوده تم تثبيت نسخه تماثل التى قام بتحديدها.

مع العلم أن IBM كانت من المشتركين فى تصميم ASCII إلا أن تصميم أجهزتها كان يستخدم 6bit BCD و الذى كان من الصعب إستبداله بـ ASCII لذا قامت IBM بمد المساحه من 6bit إلى 8 بت و أقترحت توسيع ASCII لتصبح 8 بت و لكن تم رفض هذا الإقتراح.

قامت IBM بتطوير نظام BCD إلى 8 بت و هذا ادى إلى توسيع كل خانة لتشمل الأرقام من 0 إلى 9 و قامت بتطوير أجهزتها للعمل به. هذا التصرف من IBM أدى لتطوير العديد من المنشئات لخطوط أنتاجهم لجعلها 8 بت بدلا من 7 بت و تم جعل البت الثامن بصفر[5] و ذلك لإتمام التوافق مع ASCII.

مع وجود معالجات تقوم بنقل 8 بت دفعه واحدة قامت العديد من المنشئات بإستغلال البت الثامن لأغراضهم الخاصه فى منتجاتهم لتوسيع المساحه المستخدمه لترميز الحروف، مع إنتشار هذا الإسلوب ظهر المسمى Extended ASCII و الذى يعنى إستخدام البت الثامن لترميز عناصر أخرى مع وجود ASCII داخل المدى 0 إلى 127.

المسمى Extended ASCII أدى البعض للإعتقاد أن ASCII تم تطويرها لتصبح 8 بت لتشمل بعض الرموز الجديده و هذا غير صحيح.

فى الماضي تم إستخدام المصطلح Page للإشارة إلى مساحه ثابته من الذاكرة، و حيث أن الـ 8 بت تنقسم لجزئين متساويين كل منهم يستطيع تمثيل 128 قيمة فتم ترك الجزء العلوى فى المدى 0 إلى 127 لـ ASCII و تم تسمية الجزء الأخير بالإسم Code page.

كانت IBM هى من أبتكر المصطلح[6] Code page و أول إستخدام له كان مع تطوير ترميز BCD إلى EBCDIC و بعدها تم إستخدامه داخل نظام التشغيل PC DOS و من وقتها و أصبح المصطلح شائع من شركات كبرى مثل مايكروسوفت و غيرها.

مع أن EBCDIC لم يكن ذات شهره عالية إلا أن الـ Code pages التى تم إستخدامها داخل نظام PC DOS أصبح لها شهره عالية لأنها قامت بتمثيل العديد من اللغات و أولهم و أشهرهم هى Code page 437، هذه الـ code pages كان لها العديد من الأسماء مثل CP-n و OEM-n و PC-n حيث n هو مجرد رقم و مع العلم ان الثلاث مسميات قد يشيروا إلى صفحة محارف واحدة إلا أن الرقم قد يكون مختلف و هذا عائد إلى انه توجد العديد من نسخ DOS و كل منهم موجه لبيئة محدده و من هنا جاء إختلاف المسميات.

قامت منظمة ISO بإعادة النظر فى توثيقها القديم ISO 646 و قامت بإصدار توثيق جديد بالإسم ISO 8859 ليواكب التطورات و قامت فيه بإستخدام البت الثامن لبعض من اللغات الأخرى و أعطتها الإسم ISO 8859-n حيث n هو رقم من 1 إلى 16 ماعدا 12 فلم يتم كتابة صفحة محارف له.

عندما قامت ميكروسوفت بتصميم نظام تشغيل ويندوز قامت بكتابة صفحات محارف خاصه بنظامها و تأخذ الإسم Windows-125n حيث n هو رقم الصفحة فى المدى من 0 إلى 8. أيضا داخل نظام ويندوز ستجد صفحات المحارف التى صممت لـ DOS و لكنها بالإسم OEM-n بدلا من الإسم CP-n.

و لكن حتى مع كل هذه التطورات وجدت لغات تحتوى على رموز بالألاف و التى كان من المستحيل ترميزها داخل 8 بت و هذا أدى إلى ظهور مصطلح جديد يسمى Multi-byte Codepage.
 
 

صفحة المحارف ذات الحجم المتغير Multi-byte Codepage

الحل المقترح كان بجعل ترميز الحروف على قسمين كالتالي:

  • ترميز بحجم ثابت: و أى رمز به يتم تمثيله بمساحه ثابته. هذا الترميز يسمى بـ Fixed-width Character Encoding، مثال على هذا الترميز ASCII.
  • ترميز بحجم متغير: و أى رمز به يتم تمثيله فى مساحه ثابته و يمكن تكرار هذه المساحه عدد معين من المرات. هذا الترميز يسمى بـ Variable-width Character Encoding.

لحل مشكلة اللغات التى تحتوى على أكثر من 128 رمز تم إستخدام إسلوب الترميز بحجم متغير و تطبيقه على المدى FF-80 حيث تم تقسيم هذا المدى إلى ثلاثة أقسام:

  • singleton: وهى أكواد قيمتها تمثل رمز محدد، مثل أن الكود 5916 يمثل الحرف Y داخل ASCII.
  • lead-units: و هو كود أو أكثر يمثل بداية سلسلة من عدة أكواد و هم مجتمعين يمثلوا الرمز. (سيتم تفصيل هذه النقطه لاحقا).
  • trail-units: و هو كود أو أكثر يمثل نهاية السلسلة التى بدءت بالـ lead-units.

مع العلم أن الـ lead-units قد تكون كود واحد أو أكثر إلا أن أغلب صفحات المحارف ذات الحجم المتغير إن لم يكن جميعهم يستخدم كود واحد فقط لتيسير عملية التكويد.

لنأخذ مثال لتقريب الفكرة، لنفترض ان لدينا الحروف (أ ت ج ح م ي لأ) و نريد إستخدام صفحة محارف ذات حجم متغير، حيث الأكواد بها لابد أن تكون عبارة عن سلسلة من lead-units و trail-units لذا قيمة هذه الحروف ستكون كالتالي:

  • أ : EE 80 01 FF
  • ت : EE 80 0E EF
  • ج : EE 80 11 99
  • ح : EE 80 11 9F
  • م : EE 80 1F 6A
  • ي : EE 80 2A 3B
  • لأ : EE 83 05 0A

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

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

مع أن هذا الإسلوب نجح فى تمثيل كافة رموز اللغات إلا أن له عيوب كثيره منها:

  • الأكواد لا تمثل قيمة رقمية صحيحه يمكن الوصول بها للرمز المطلوب، فمثلا داخل ASCII الكود 5916 هو رقم صحيح يمثل الحرف Y، أما الأكواد المستخدمه فى هذا الترميز فلا معنى لها.
  • الوسيلة الوحيده للبحث داخل نص يحتوى على هذه الأكواد هو بقرائتهم بالكامل لإنك لا تعرف طول السلسلة و هذا يسبب بطء كبير فى عمليات معالجة النصوص.
  • وجود خطأ فى أحد الأكواد يتلف النص بأكمله لأنه كما ترى الـ trail-units قد تحتوى على أكواد إذا تم قرائتهم وحدهم فإنهم يمثلوا حروف موجوده أو أكواد بداية لسلسلة أخرى.

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

ترميز Unicode

فى عام 1988 قام جو بيكر بطياعة توثيق مبدئي و أطلق عليه الإسم Unicode و الذى محتواه كان يهدف إلى إيجاد ترميز موحد لكل الرموز بكل لغات العالم و ذلك بمد المساحه المستخدمه لـ ASCII من 8 بت إلى 16 بت و أطلق إسم مجازى على المساحه الموسعه و هو "wide ASCII" و أوضح أن إسم التوثيق يعني "Unique, Unified, Universal encoding".

فى عام 1990 تم إنشاء مجموعة بالإسم Unicode group و قام أعضائها بالإضافه لأعضاء من ISO بالعمل على إعطاء كافة حروف لغات العالم وصف و كود محدد داخل Unicode.

فى عام 1991 تم إنشاء منظمة غير ربحية بالإسم Unicode Consortium و إختصارا Unicode، و تم إصدار أول توثيق لها فى نفس العام و تم تحديد به أن المساحه التى يستهلكها الحرف الواحد هى 16 بت.

و فى الإصدار الثانى تم إضافة كافة حروف Han ليكتمل توثيق كافة الحروف المستخدمه عالميا.

العمل على تطوير الإصدار الثاني تم بالتعاون بين Unicode و ISO، و بعد إصدار ذلك التوثيق من Unicode قامت ISO بإصدار توثيق خاص بها بالإسم Universal Character Set و له الكود ISO/IEC 10646 و كلاهما يتماثل فى المضمون مع إختلاف بعض المصطلاحات، أيضا كلاهما له نفس قاعدة بيانات الحروف زاد الأول أو نقص عن الأخير ببعضهم.

قاعدة بيانات الحروف لها الإسم UCD و هذا الإسم مع توثيق Unicode يعنى Unicode Character Database و مع ISO فإنه يعني Universal Character set Database و مع أن مصطلح الإختصار مختلف إلا أن كلاهما يشير إلى نفس البيانات.

داخل قاعدة بيانات الحروف كل حرف له رقم فريد لا يتكرر فى المدى من 016 إلى 10FFFF16، هذا الرقم يسمى Unicode scalar value أو character value طبقا لـ ISO أى القيمة الصحيحه للحرف، و كل حرف يتم إعطائه وصف محدد غير مرتبط بشكل حيث أن الحرف الواحد داخل لغه معينه قد يكون له أكثر من شكل و لكن له وصف واحد فريد مرتبط به. تبعا لذلك التعريف فإنك ستجد بداخل قاعدة البيانات حرفين قد يكون لهم نفس الشكل و لكن لأن كل منهم فى لغه مختلفه فإن وصف الحرف مختلف و بالتالي كل منهم هو حرف مختلف.
 
فى التوثيق الأول من Unicode تم إستخدام ترميز بمساحة 2 بايت و تم إعطائه الإسم UCS-2 و أستطاع تمثيل كافة القيم الصحيحه للحروف فى المدى من 016 و حتى FFFF16 و تم نبذ هذا الترميز عندما تم توسيع قاعدة البيانات من FFFF16 إلى 10FFFF16.
القيمة الصحيحه للحرف تستخدم للوصول إليه داخل قاعدة البيانات و حيث أن أكبر قيمة يتم حفظها داخل 4 بايت فإن التعامل المباشر مع هذه القيمه فى الحفظ و الإسترجاع و المعالجه داخل الذاكرة غير عملى و لهذا تم إيجاد طريق لترميز تلك القيمه و التى من شأنها جعل عملية الحفظ و الإسترجاع تتم لمساحه أصغر و ايضا عملية إستخلاص قيمة الحرف من الترميز يسيرة و سريعة، الترميز الإفتراضي المستخدم فى عملية الترميز فى كلا التوثيقان هو UTF-16 أو Unicode Transformation Format 16-bit.
 
داخل توثيق ISO تم تحديد ترميزان و هم UCS-2 و UCS-4، أما الأول فقد ذكرت أنه تم نبذه و الأخير يستخدم لحفظ القيمة الصحيحه للحرف داخل 4 بايت.

داخل توثيق Unicode تم تحديد الترميز UTF-16 كبديل لـ UCS-2 و أيضا ترميز UTF-32 كتضمين لـ UCS-4، بالإضافة لهؤلاء تم إضافة ترميز أخر و هو UTF-8 و الذى تم إبتكاره كبديل لـ ASCII.

فى الوقت الحالي توثيق ISO يستخدم UTF-8 -16 -32 لترميز القيمة الصحيحه للحرف و داخل التوثيق تم إستخدام المصطلح code-point ليعنى القيمة الصحيحه للحرف فى المدى المسموح به داخل الترميز.

كل ما ذكرناه ينطبق على التعامل مع قاعدة بيانات الحروف و ترميز القيمة الصحيحه لكل حرف بها و إلى هنا هذا هو أقصى ما يصل إليه توثيق ISO، أما توثيق Unicode فيستمر إلى أبعد من ذلك حيث يحتوى على وسائل للبحث داخل النصوص التى تحتوى على أكثر من لغه بالإضافة لقواعد كتابة أكثر من لغة معا و غيرها[7].

داخل Unicode الترميز UTF-X - حيث X هو عدد البتات - يتم الإشارة إليه بعدد البتات التى يتم إستخدامها داخل الترميز كوحدة بيانات واحدة و تسمى code-unit، أيضا يمكن ان يتم حفظ قيمة الـ code-point داخل وحدة واحدة أو أكثر من الـ code-unit. مع العلم أن المساحات المستخدمه هى عدد البتات بكل من 1 و 2 و 4 بايت إلا أن إستخدام عدد البتات يرجع إلى أن العمليات فى كل ترميز تتم على مستوى البت.

التصميم الأصلى لـ Unicode كان يحتوى على 65536 حرف داخل قاعدة بياناته، و عندما تم زيادة هذا المدى إلى 10FFFF16 حينها تم تسمية المدى الأصلي بالإسم Basic Multilingual Plane و إختصارا BMP، ذلك المدى يأخذ الترتيب صفر و يحتوى على أغلب حروف العالم، و باقي المساحه تم تقسيمها 16 جزء كل منهم بمساحة 65536 حرف و هم كالتالي[8]:

Plane     0: Basic Multilingual Plane 0000-FFFF
Plane     1: Supplementary Multilingual Plane 10000-1FFFF
Plane     2: Supplementary Ideographic Plane 20000-2FFFF
Plane  3-13: Unassigned 30000-DFFFF
Plane    14: Supplementary Special-purpose Plane E0000-EFFFF
Plane 15-16: Supplementary Private Use Area F0000-10FFFF

 
 
 
ترميز UTF-8

الهدف من وراء تصميم هذا الترميز[9] هو أن يكون بديل لـ ASCII، حيث من خلاله يمكن إستخدام ASCII و باقي حروف Unicode بدون مشاكل، هذا الترميز يستهلك 8 بت أو one-octet للـ code-unit الواحده و هو ديناميكي حيث عدد الـ code-unit المستخدم يكون فى المدى من 1-4 و ذلك حسب قيمة الـ code-point تبعا للجدول التالي:
 

+---------------------+-------------------------------------+
| character code point| UTF-8 octet sequence                |
+---------------------+-------------------------------------+
| 0000 0000-0000 007F | 0xxxxxxx                            |
+---------------------+-------------------------------------+
| 0000 0080-0000 07FF | 110xxxxx 10xxxxxx                   |
+---------------------+-------------------------------------+
| 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx          |
+---------------------+-------------------------------------+
| 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
+---------------------+-------------------------------------+

من الجدول السابق تجد أنه إذا كان الحروف المستخدم فى المدى من 016 إلى 7F16 - و هو المدى الخاص بـ ASCII - فإن الحرف يتم ترميزه داخل code-unit واحده و عندما تكون قيمة الحرف فى المدى من 8016 إلى 10FFFF16 فإن البتات الخاصه بتلك القيمه يتم توزيعها بناءا على الجدول السابق و أن أول code-unit يحتوى داخل الـ Most significant bits الخاصه به على عدد code-units التى تمثل code-point فتجد ان المدى 8016-7FF16 أول بايت به يحتوى على 2 بت و هذا يعنى وجود two code-units و نفس الامر ينطبق على كل مدى تم تحديده بالجدول.

عملية توزيع البتات الخاصه بالـ code point داخل Code Units لها ترتيب محدد و يتم كالتالي:

  • قم بمعرفة عدد الـ Code Units التى تحتاجها لترميز الـ code point.
  • قم بإعداد قيمة البتات الخاصه بالـ Code Units كما هو موجود بالجدول.
  • داخل قيمة الـ Code point قم بأخذ البتات الموجوده بـ lowest-order و ضعها فى المكان المقابل لها داخل lowest-order bits الموجوده بأخر code unit و بعد ان تملء الـ code-unit قم بالذهاب للتي تسبقها مباشرة و قم بتكرار هذه العملية حتى تمتلء كافة الـ x.

مثال الحرف "م" يأخذ القيمه U+0645 و لتحويلها لـ UTF-8 نعلم أن هذه القيمة تقع فى المدى الثاني و هذا يعنى أننا نحتاج two code units:

110xxxxx 10xxxxxx

الخطوه التالية هي بتوزيع البتات الخاصه بقيمة الحرف و تتم كالتالي:

06 45

00000110 01000101
           ------
-> 110xxxxxx 10000101

00000110 01
     --- --
-> 11011001 10000101

D9 85

كما ترى قيمة الحرف "م" داخل UTF-8 هى D916 8516.

للتحويل من UTF-8 لقيمة الحرف code point نحتاج لمساحه أقصاها 21 بت (و هم عدد البتات التى يتم توزيعهم على كافة code units)، الخطوات تتم كالتالي:

  • نقوم بحجز مساحه بحد أقصى 21 بت و نجعل قيمتهم صفر.
  • نقوم بمعرفة عدد الـ code units التى تمثل الـ code point.
  • نقوم بإستخلاص البتات من الـ code units من أقصي اليمين إلى اليسار و يتم و ضعهم داخل البتات (من الخطوه 1) بدءا من lowest-order bit و صلا لـ highest-order bit.

الأن لنقوم بإستخلاص الـ code point من القيمه EF16 B716 BD16:

EF B7 BD

0xEF starts with 1110 which result 3 code-units which means we need 16-bits for target code-point

cp = 0000000000000000

-> 11101111  10110111  10111101
                         ------
cp = 0000000000111101
               ------

-> 11101111  10110111  10111101
               ------
cp = 0000110111111101
         ------

-> 11101111  10110111  10111101
       ----
cp = 1111110111111101
     ----

cp = 11111101 11111101 = FD FD

الكود FDFD16 هو قيمة الحرف الذى يمثل البسلمة.

عندما تكون الـ code units أكثر من واحده حينها تسمى الـ code-unit الأولي بالإسم lead-unit و الباقيين trail-unit و فى هذه الحالة توجد بعض الـ lead-unit التى إذا تم إستخدامها فهذا يعني invalid UTF-8 sequence و هم:

C0, C1, F5-FF

حيث الأولى و الثانية تسمح بتكويد code point فى المدى 0016-7F16 داخل وحدتين و الأكواد فى المدى F516-FF16 تسمح بتكويد code points فى مدى أكبر من 10FFFF16، أيضا الكود F416 يسمح بتكويد قيم أكبر من المدى المسموح به و لابد من التحقق من القيمه النهائية لتحديد إذا كانت صحيحه أو خاطئة.

أحد المزايا الموجوده فى UTF-8 هو انه دائما يمكن معرفة بداية السلسلة و بالتالي إذا وجدت وحدة بداية بقيمه غير صحيحة يمكن بعدها تخطى البقية حتى الوصول لوحدة بداية أخرى (لاحظ ان الوحدات التى ليست بوحدات بداية تبدء دائما بـ 0b10).
 
 

ترميز UTF-16

عندما تم تصميم Unicode 1.0 تم إستخدام مساحة مقدارها 16 بت ليتم من خلالها ترميز كافة الحروف و سميت UCS-2، و مع الإصدار الثاني من Unicode تم توسيع مدى الاكواد و معه تم نبذ ذلك الترميز و تم إبتكار ترميز أخر[10] ليكون له بديلا.

عند وضع تصميم Unicode 2.0 تم الأخذ فى الحسبان إمكانية إستخدام الترميز UTF-16 مع قيم أكبر من FFFF16، لهذا السبب و لجعل عملية ترميز أكواد الحروف من و إلى ذلك الترميز يسيرة تم حجز مساحه محدوده من أكواد الحروف ليتم إستخدامها بشكل خاص مع هذا الترميز للتعامل مع الحروف التى تحتاج مساحه أكبر من FFFF16.

داخل UTF-16 أى code-point يمكن ترميزها داخل عدد two code-units بحد أقصى حيث إذا كانت قيمة الـ code-point أقل من أو تساوى FFFF16 حينها يتم حفظ قيمة الـ code-point داخل وحدة واحده - أى code-unit واحده - و إذا كانت قيمة الـ code-point أكبر من FFFF16 حينها يتم إستخدام وحدتان لترميز الـ code-point، هاتان الوحدتان يطلق على يطلق على الوحدة الأولي - التى توضع يسارا - الإسم High Surrogates، و يطلق على إسم الوحده الأخيره - التى توضع يمينا - الإسم Low Surrogates.

بهذا أصبحت أكواد الحروف داخل Unicode تأخذ المساحات التالية:

  • المدى 00DB7F16 - 00000016: يستخدم لتكويد الحروف.
  • المدى 00DBFF16 - 00D80016: يستخدم مع ترميز UTF-16 فى مدى أكبر من FFFF16 داخل الوحده الأولى.
  • المدى 00DFFF16 - 00DC0016: يستخدم مع ترميز UTF-16 فى مدى أكبر من FFFF16 داخل الوحده الأخيره.
  • المدى 10FFFF16 - 00E00016: يستخدم لتكويد الحروف.

لتحويل كود فى المدى من 10FFFF16 - 1FFFF16 نقوم بالأتي:

  • يتم طرح القيمه 1000016 من قيمة الكود المدخل.
  • القيمه الباقيه تتكون من 20 بت يتم تقسيمها إلى 10 بت و 10 بت.
  • الـ 10 بت الموجودين على اليسار يتم إضافتهم على القيمه D80016 و القيمه الناتجه ستكون فى المدى من D80016 إلى DBFF16 و هذه قيمة الوحده الاولى.
  • الـ 10 بت الموجودين على اليمين يتم إضافتهم على القيمه DC0016 و القيمه الناتجه ستكون فى المدى من DFFF16 إلى DC0016 و هذه قيمة الوحده الثانية.

الخطوات بإسلوب أخر:

If (Codepoint <= FFFF)
	return Codepoint

U = Codepoint - 10000
U = xxxxxxxxxxyyyyyyyyyy
A = xxxxxxxxxx
B = yyyyyyyyyy

CU1 = D800 + A
CU2 = DC00 + B

return CU1, CU2

للحصول على قيمة الـ code-point من UTF-16 code-units:

  • إذا كانت قيمة الوحده ليست فى المدى DFFF16 - D80016 حينها قيمة code-unit هى قيمة الـ code-point.
  • إذا كانت قيمة الوحده الاولى ليست فى المدى DBFF16 - D80016 فالترميز خاطئ، و إذا كانت قيمة الوحدة الثانية ليست فى المدى DFFF16 - DC0016 فالترميز خاطئ.
  • قم بطرح القيمه D80016 من الوحده الأولى و القيمه DC0016 من الوحده الثانية و قم بحجز مساحة 20 بت و ضع الناتج من الوحده الأولى فى الـ 10 بت الموجودين يسار الـ 20 بت و الناتج من الوحده الثانية فى الـ 10 بت الموجودين يمين الـ 20 بت.
  • أضف القيمه 1000016 لقيمة الـ 20 بت و الناتج سيكون بحد أقصي 21 بت و هو قيمة الـ code-point.

إذا كنت تتسائل لماذا تم تحديد القيمه 10FFFF16 لتكون أقصي قيمة لكود حرف فها هي الإجابه امامك، لأن حينها ستصبح الـ 20 بت تحتوى القيمة FFFFF16 و التى عندما يتم توزيعها ستعطينا الوحدتان DBFF-DFFF16.
الإجابه بإسلوب أخر: أقصي قيمة يمكن ترميزها داخل UTF-16 هى القيمه 10FFFF16 و لهذا جعلت أقصى قيمة لأى كود داخل Unicode و Universal Character Set.

عملية حفظ الـ 16 بت داخل الذاكره تختلف بإختلاف المعمارية المستخدمه حيث ترتيب الـ 2 بايت الممثلين للـ 16 بت يختلف بين Little-Endian و Big-Endian لذا إستخدام Byte Order Mark مع ترميز UTF-16 أمر أساسي
 
 

ترميز UTF-32

يمكنك اعتبار هذا الترميز أبسط ترميز حيث يستطيع تمثيل كافة أكواد Unicode داخل وحدة واحدة بمساحة 32 بت، و هذا يجعل عملية ربط قيمة الحرف و الكود لا تحتاج لأى عمليات.

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

علامة BOM

هى إختصار لـ Byte Order Mark و هى إسم للقيمة 0000FEFF32 و تستخدم كأول كود فى سلسلة أكواد بترميز محدد[11] لتقوم بتحديد الترميز المستخدم.

  • UTF-8
    عندما يتم إستخدام هذا الترميز فمن الأفضل عدم إستخدام BOM لأن هذا الترميز تم تصميمه ليحوى ASCII بالإضافه لباقي أكواد Unicode و إستخدامك للـ BOM سيجعل البرامج القديمه (desktop - web) تحتاج للتعديل و هذا قد يكون غير متاح، بالإضافه إلى انه إذا تم تمرير سلسلة من الأكواد بهذا الترميز لأى برنامج يدعم UTF-8 فسيستطيع التعرف عليه مباشرة لأن هذا الترميز يسهل التعرف على وحداته بسهوله كما ذكر من قبل.
    و لكن إذا دعت الحاجه لوجود BOM حينها سيتم تحويل الكود المذكور لـ UTF-8 ليصبح EF BB BF، لاحظ ان هذا الترميز مبني على وحدة مساحتها 8 بت و لهذا فمشاكل ترتيب الوحدات Endianness غير موجوده بهذا الترميز.
  • UTF-16
    الوحده الواحدة داخل هذا الترميز تحفظ داخل وحدتين بمساحة 8 بت لكل منهم، و هنا تظهر مشكلة ترتيب الوحدات عند قراءة سلسلة أكواد لأنك إن حفظتهم من ألة تعمل بنمط Big-Endian فإنك لابد من أن تقرأهم بهذا النمط بغض النظر عن طبيعة الأله التى تقوم بالقرأه منها.
    حل هذه المشكلة يتم بوضع القيمه FEFF16 بأى سلسلة يتم حفظها بهذا الترميز، و عندما تكون السلسلة بنمط Big-Endian فترتيب وحدات هذه القيمه سيكون FE FF - تقرأ من اليسار لليمين - و عندما تكون السلسلة بنمط Little-Endian فترتيب وحدات هذه القيمه سيكون FF FE - تقرأ من اليسار لليمين - و السؤال الذى يتبادر إلى الذهن هو: ماذا يحدث إذا تم حفظ السلسلة من BE و تم قرائتها من LE بترتيب وحدات LE؟
    فى هذه الحالة القيمه التى سيتم قرائتها هى FFFE16 و هى تقابل حرف من الحروف الممنوع إستخدامها فى تبادل المعلومات و ايضا احد اوصافه هو انه يمكن استخدامه لمعرفة ترتيب الوحدات مع FEFF16، و هذا يعنى ايا كان الأله التى تقرأ منها فإنه يوجد لديك إحتمالين و هم قراءة FEFF16 أو FFFE16 و إحداهما تدل على ان السلسلة بترميز LE و الأخرى تدل على ان السلسلة بترميز BE.
  • UTF-32
    بالمثل مع UTF-16 و لكن فى هذه الحالة سيتم كتابة الأصفار، و بالتالي عند كتابة BOM بنمط Little-Endian فترتيب الأكواد سيكون FF FE 00 00 - تقرأ من اليسار لليمين - و عندما يكون النمط Big-Endian فترتيب الأكواد سيكون 00 00 FE FF - تقرأ من اليمين لليسار.

مكتبة charset

مكتبة charset قمت بكتابتها بلغة ++C و لها ثلاثة إستخدامات:

  • التحويل code-unit بين من ترميز لأخر.
  • تحويل حرف من character-set إلى Unicode و العكس.
  • تحويل stream كاملة من character-set إلى Unicode و العكس.
  • تحويل stream كامله من character-set لأخرى أو من ترميز Unicode لترميز Unicode أخر.

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

خاتمة

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

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


و الله ولي التوفيق

[1]مع العلم أن عنوان الموضوع هو ترميز الحروف إلا أن الهدف منه هو تعريفك بترميز Unicode و كيفية إستخدامه و الإستفاده منه و لهذا سيتم إهمال العديد من الاحداث التاريخية و ذكر أخرين بشكل مجمل حتى لا نقوم بالتركيز على نقاط جانبية اكثر مما ينبغي.
[2]هذا السلوك موجود حتى يومنا هذا فى مكتبة الإدخال و الإخراج بلغة السي و يعتبر هو السلوك الإفتراضي حيث لا يتم حفظ المحتويات التى يتم كتابتها داخل stream إلا بعد كتابة علامة السطر الجديد.
[3]مع العلم انه تم رفض الكود shift إلا أن بعض المنشئات قامت بتصميم ترميز خاص بها و أستخدمته و لكن ليس لقترة طويلة.
[4]راجع جزء ASCII Control characters من مقال Wikipedia.
[5]بالإضافة إلى أن IBM هى أول من صنع أجهزة بمعالجات 8 بت فإنها أيضا أول من طبق مفهوم Byte Address بدلا من Bit Address (و يعنى التعامل مع الذاكرة على مستوى البت).
[6]المصطلح Code Page يقابل الكلمه صفحة محارف باللغه العربية.
[7]يمكنك معرفة كافة هذه الإختلافات من Appendix C بتوثيق Unicode أو بعضها من Wikipedia.
[8]يمكنك معرفة تفاصيل أكثر عن هذه المساحات و طبيعة محتواها من Wikipedia أو من توثيق Unicode أو من توثيق ISO.
[9]راجع المقالة التى تتحدث عن UTF-8 داخل Wikipedia، أو أقرأ عن هذا الترميز بالتفصيل فى وثيقة من RFC.
[10]راجع المقالة التى تتحدث عن UTF-16 داخل Wikipedia، أو أقرأ عن هذا الترميز بالتفصيل فى وثيقة من RFC.
[11]يوجد أكثر من ترميز غير المذكورين هنا و لكنهم غير مدعومين من Unicode.

تم تعديل هذه المشاركة بواسطة C++er في 24 أبريل 2014 في 21:47

11

مدونتي: C++ Tips and Tricks

#2

السلام  عليكم 

 

موضوع  شامل و  أكثر  من  رائع  يستحق  التقييم   و فقك  الله

 

لكن  هل  الدول  العربية  في  ذلك  الوقت  صنعت  لنفسها  ترميز  ام  اننا  اكتفينا  بالمشاهدة  فقط  ؟!! 

للإطلاع  على جديد  الكتب من تأليفي


 


الطريق لاحتراف تطوير المواقع   ج1 | ج2 | ج3 | ج4


 


                    4Colors | Break Out | Classic Snake | لعبة السودوكو - 2014  |  لعبة  XO                      


 


Help Dodo


 


^_^


 

#3
اقتباس
لكن  هل  الدول  العربية  في  ذلك  الوقت  صنعت  لنفسها  ترميز  ام  اننا  اكتفينا  بالمشاهدة  فقط  ؟!!

من قرائتي لم يرد ذكر أى من الدول العربية.

 

 

و الله ولي التوفيق

مدونتي: C++ Tips and Tricks

#4

شكراً لك أخي محمد على المقالة الرائعة.

 

معلومات بسيطة عند التعامل مع العربية، قد يكون لديك ملفين فيهم كلمة معينة إلا أن محتويات كل ملف ستختلف كلياً عن الآخر إذا كان الترميز مختلف. أيضاً عند ظهور الكلام بلغة أخرى أو يظهر رموز فالأرجح أنك تقرأها بالترميز الخطأ.

 

اللغة العربية غالباً نخزن بأحد الترميزين windows-1256 أو utf-8 كلهم مختلفين:

 

ترميز windows-1256 شائع الإستخدام، يمكنك تمييزه بأن حجم الملف بالبايت يساوي عدد الحروف، كلمة "أبجد" ستخزن 0xc3 0xc8 0xcc 0xcf (يفترض أن يكون عندك محرر هكس):

00000000 C3 C8 CC CF                                        ....

التعامل مع الملف ببايثون، لاحظ أني فتحت الملف كـrb (قراءة كبيانات read-binary) كي لاتتدخل اللغة وتعدل في الحروف:

> python -c "print(open('windows-1256.txt', 'rb').read())"
b'\xc3\xc8\xcc\xcf'

> python -c "print(open('windows-1256.txt', 'rb').read().decode('windows-1256'))"
أبجد

>

عند فتح ملف بهذا الترميز وكنت تستخدم ترميز اللغات الاتينية (windows-1252, ISO-8859-1, Latin-1) فسيظهر لك ÃÈÌÏ، أحرف أو أرقام إنجليزية أو أحرف مع علامات.

 

ترميز utf-8 يمكنك تمييزة بعلامة BOM 0xef 0xbb 0xbf (لن تجدها دائماً ولا ينصح باستخدامها)، حجم الملف بالبايت يساوي ضعف عدد الأحرف إذا كانت الأحرف فقط عربية (بايتين لكل حرف عربي و بايت لحروف الآسكي، و 3 بايتات إضافية إن كان علامة BOM موجودة) .

 

ملف أبجد مع علامة BOM:

00000000 EF BB BF D8 A3 D8 A8 D8 AC D8 AF                   ...........

القراءة من بايثون، حذفت علامة BOM قبل فك ترميز الملف، وإلا فستعطيني خطأ عند فك الترميز:

> python -c "print(open('utf-8.txt', 'rb').read())"
b'\xef\xbb\xbf\xd8\xa3\xd8\xa8\xd8\xac\xd8\xaf'

> python -c "print(open('utf-8.txt', 'rb').read()[3:].decode('utf-8'))"
أبجد

>

إذا قرأته بترميز اللغات الغربية فسيظهر أبجد. إذا قرأته بترميز العربية windows-1256 فسيظهر ط£ط¨ط¬ط¯.

 

====================================================

 

بالنسبة لمبرمجين ويندوز، أغلب المصادر والكتب تشير لأن ويندوز يستخدم داخلياً اليونيكود، النظام تحديداً يستنخدم ترميز utf-16le، ترميز utf-16 مع بايتات مقلوبة little endian. ملف أبجد سيحتوي عند ترميزه بـutf-16le:

00000000 23 06 28 06 2C 06 2F 06                            #.(.,./.

محتويات الملف:

> python -c "print(open('utf-16.txt', 'rb').read())"
b'#\x06(\x06,\x06/\x06'

> python -c "print(open('utf-16.txt', 'rb').read().decode('utf-16le'))"
أبجد

>

لينكس يستخدم داخلياً ترميز utf-8.

3
#5

ملحوظة: كل الأكواد  التالية تهدف للتوضيح و إن اردت إستخدامها داخل برامجك يمكنك إجراء تحسينات كثيرة عليها.

 

قد يتسائل أحدكم:

ما هى الطريقه التى يمكنني بها التحويل بين الـ code-pages بدون إستخدام أى مكتبات خارجية أو المكتبة الإفتراضية التى تأتى مع لغتي؟

 

الإجابه: بمعرفة أن أى صفحة محارف تحتوى فقط على 256 كود و الـ 128 الأوليين هما لـ ASCII، يمكننا كتابة مصفوفة من 128 عنصر و قيمة كل عنصر يساوى كود Unicode لذلك العنصر، بمعني الحرف 'م' من صفحة محارف 'windows-1526' له الكود 227 و بالتالي داخل مصفوفتنا سيأخذ الكود 99 (227-128) و قيمة ذلك العنصر ستكون قيمة الحرف 'م' داخل Unicode و هو U+0645 (و هذا يجعل نوع هذه المصفوفه Unisgned 16-bit Integer)، و شكلها فى لغة C++/C كالتالي:

const unsigned short windows1256[] =
{
	0x20AC, 0x067E, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0679, 0x2039, 0x0152, 0x0686, 0x0698, 0x0688,
	0x06AF, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x06A9, 0x2122, 0x0691, 0x203A, 0x0153, 0x200C, 0x200D, 0x06BA,
	0x00A0, 0x060C, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x06BE, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
	0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x061B, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x061F,
	0x06C1, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F,
	0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x00D7, 0x0637, 0x0638, 0x0639, 0x063A, 0x0640, 0x0641, 0x0642, 0x0643,
	0x00E0, 0x0644, 0x00E2, 0x0645, 0x0646, 0x0647, 0x0648, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x0649, 0x064A, 0x00EE, 0x00EF,
	0x064B, 0x064C, 0x064D, 0x064E, 0x00F4, 0x064F, 0x0650, 0x00F7, 0x0651, 0x00F9, 0x0652, 0x00FB, 0x00FC, 0x200E, 0x200F, 0x06D2
};

هذه المصفوفة تحتوى على كود UTF-32 للحروف، و حيث أن حروف صفحة المحارف هذه يمكن حفظه داخل 16 بت دون مشاكل فكانت هذه المساحة التى أستخدمتها.

 

لتحويل محتوى نص من صفحة المحارف windows-1256 ليونيكود كل ما تحتاجه هو الحصول على موقع كود الحرف داخل المصفوفة ثم العودة به: (الكود التالي بفترض ان النص قد يحتوى على حروف ASCII أيضا)

unsigned short to_unicode (char c)
{
	unsigned char uc = (unsigned char)c ;

	if (uc < 128) return uc;
	
	return windows1256[uc-128];
}

الكود السابق يقوم بتحويل حرف بصفحة محارف windows-1256 ليونيكود و يمكنك كتابة حلقو تكرار لإستدعاء الدالة و الحصول على محتوى النص بأكمله داخل ترميز يونيكود.

 

قبل حفظ قيمة الحرف يمكنك كتابة دوال لتحويل قيمة الحرف من UTF-32 إلى UTF-16 أو UTF-8 كما تريد.

 

سؤال أخر: ماذا يحدث لو أردت التحويل بين صفحتان محارف مثل windows-1256 و oem720 حيث كلاهما يحتوى على حروف العربية؟

الإجابه: الحل يكون فى كتابتك مصفوفتين كل منهم يحتوى على 128 عنصر، أما الأولي فستكتبها بنفسك، و الأخرى ستقوم بتوليدها تلقائيا:

unsigned short oem720[] =
{
	0xffff, 0xffff, 0x00E9, 0x00E2, 0xffff, 0x00E0, 0xffff, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0xffff, 0xffff, 0xffff, 
	0xffff, 0x0651, 0x0652, 0x00F4, 0x00A4, 0x0640, 0x00FB, 0x00F9, 0x0621, 0x0622, 0x0623, 0x0624, 0x00A3, 0x0625, 0x0626, 0x0627, 
	0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x00AB, 0x00BB, 
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, 
	0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, 
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, 
	0x0636, 0x0637, 0x0638, 0x0639, 0x063A, 0x0641, 0x00B5, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 
	0x2261, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, 0x0650, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
};

المصفوفه oem720 تم تحديد محتوياتها كما تم تحديد محتويات المصفوفة windows1256 و يوجد هنا أمر جديد و هو القيمة 0xffff حيث هذه القيمة انا استخدمتها خصيصا لسبيبن:

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

المصفوفة الثانية التى سنقوم بتوليدها سنعطيها الإسم WinToOem حيث موقع كل عنصر بها هو موقع حرف داخل صفحة المحارف windows-1256 و قيمة ذلك الحرف هى موقع العنصر داخل صفحة محارف oem720، و بالتالي إذا اردنا تحويل نص من صفحة محارف windows1256 إلى oem720 كل ما سنفعله هو تمرير الكود للمصفوفة WinToOem و الناتج سيكون قيمة الحرف داخل صفحة محارف oem720.

char WinToOem[128];int find(unsigned short* array, unsigned short value){    for(int i=0; i<128; ++i)    {        if (array == value) return i;    }    return -1;}void gen_array(){    for(int i=0; i<128; ++i)    {        int r = find(oem720, windows1256);                if (r == -1)            WinToOem = 0;        else            WinToOem = i + 128;    }}

كل عنصر بالمصفوفة WinToOem يحتوى على صفر و هذا يعنى انه الحرف لا يوجد له مثيل فى oem720 (تذكر المصفوفة لا تحتوى على أكواد ASCII و الكود صفر لا يتم إستخدامه أبدا داخل اى صفحة محارف ماعدا للحرف NULL و هذا مؤكد) أو كود الحرف داخل oem720.

 

الإسلوب السابق هو ما اتبعته داخل مكتبة charset مع بعض التعميم لتطبيق الطريقة على العديد من صفحات المحارف.

 

 

و الله ولي التوفيق

تم تعديل هذه المشاركة بواسطة C++er في 27 أبريل 2014 في 14:27

4

مدونتي: C++ Tips and Tricks

#6

موضوع جميل جداً و أشكرك على طرحه :)

 

بالنسبة للعربي فالمعروف هو استخدامنا لـ Windows-1256 على نظام الوندوز و UTF-8 عموماً و لكن هناك أيضاً ISO و تم تخصيص رقم 6 للغة العربية ليصبح اسمه ISO 8859-6 و لكنه غير مستخدم أو لا أراه مستخدم في برامج مهمه و لكن هناك برامج تتعامل معه مثل برنامج Kate و KWrite  و ما يشتق منهم و ذلك على اللينوكس.

forum-signature-01.gif
#7

انا شاكر لحضرتك جدا على الشرح

 

كنت محتاجه جدا

 

بارك الله فيك

تم تعديل هذه المشاركة بواسطة مصطفى 36a2 في 5 أغسطس 2014 في 23:25

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