Sanyanggae LogoSanyanggae

💻 الويب وتطوير الواجهات الأمامية

ما الفرق بين Unicode وUTF-8؟ تشوّه النص الكوري والبايتات وطول السلسلة والتطبيع

نقارن نقاط ترميز الحروف الكورية والرموز التعبيرية ببايتات UTF-8 وطول سلاسل JavaScript، ونفصل فك الترميز الخاطئ عن مشكلات الخطوط والتطبيع عن حفظ الأصل.

사냥개 (Sanyanggae)نُشر حُدّث 13 د

هذه ترجمة للنص الكوري الأصلي. الأصل الكوري · مراجعة الترجمة: مراجعة بالذكاء الاصطناعي

مخطط توضيحي بثلاث خانات يربط المحارف ونقاط الترميز وبايتات UTF-8 والعرض والتطبيع
مخطط توضيحي أصلي يفصل أرقام محارف Unicode وبايتات تخزين UTF-8 وقواعد العرض والمقارنة؛ وليس صورة لعطل حقيقي أو نتيجة قياس.

1. رقم المحرف يختلف عن البايتات المخزّنة

لنفترض أنك صدّرت مستندًا كوريًا ثم فتحته فظهر عنوانه بمحارف غريبة. لم تتغير لغة الكاتب، بل ربما اختلفت قواعد قراءة الأرقام المخزّنة. Unicode معيار يمنح المحارف والرموز أرقامًا تسمى نقاط ترميز، بينما UTF-8 ترميز يمثّل القيم المقابلة بتتابع من البايتات. نقطة ترميز المقطع الكوري «가» مثلًا هي U+AC00. الأرقام بعد U+ مكتوبة بالنظام الست عشري، ولا تدل على ترتيب المحرف في الملف أو عنوان في الذاكرة. كما أنها لا تحدد شكل الخط أو موضع البكسلات. قد تُرسم نقطة الترميز نفسها بصورة مختلفة بحسب الخط ونظام تنضيد النص. لذلك ترتبط إتاحة Unicode والحفظ بصيغة UTF-8 وتثبيت خط كوري، لكنها أمور يجب فحص كل منها على حدة. يقدّم شرح Unicode لصيغ UTF مدخلًا للفصل بين المحرف وطريقة تخزينه.

في UTF-8 يصبح «가» ثلاثة بايتات، هي EA B0 80 بالنظام الست عشري. أما الحرف اللاتيني A فنقطته U+0041 ويمثله بايت واحد هو 41. البايت وحدة من ثمانية بِتّات، وقد يختلف عدد البايتات من محرف إلى آخر داخل المستند نفسه. ولا يكشف الامتداد .txt أو .csv وحده الترميز الحقيقي. يمثّل UTF-8 القيم العددية القياسية الصالحة في Unicode ببايت واحد إلى أربعة بايتات، ولذلك لا يمكن حساب حجم الملف بدقة بضرب عدد العلامات المرئية في عدد ثابت. ولا يصح تعميم مثال هذا المقطع على كل الرموز المتعلقة بالكورية أو جميع اللغات. الترميز قاعدة لنقل معلومات المحارف إلى بايتات، وليس ضغطًا أو تشفيرًا لحماية السرية. وقد يتغير الحجم الإجمالي للجملة نفسها بسبب بيانات صيغة الملف الإضافية وفواصل الأسطر وضغط النقل. يساعد فصل التحويل بين البايت والمحرف في معيار الترميز WHATWG عن حجم الملف الكامل وعن السرية على تضييق موضع المشكلة.

2. عدد البايتات ووحدات الترميز والمحارف المرئية

عند قراءة length لسلسلة JavaScript نحصل على عدد وحدات ترميز UTF-16، لا عدد بايتات ملف UTF-8 ولا عدد المحارف كما يدركها الإنسان. الوجه «😀» نقطة ترميز واحدة هي U+1F600، لكنه يحتاج وحدتي UTF-16، لذلك تساوي '😀'.length العدد 2. وتمثيله في UTF-8 هو F0 9F 98 80، أي أربعة بايتات. العدد العشري 128512 الناتج من codePointAt(0) هو الرقم نفسه المكتوب U+1F600، وليس عدد المحارف أو حجم الملف. أما «가» فله نقطة ترميز واحدة ووحدة UTF-16 واحدة وثلاثة بايتات UTF-8. اختلاف الأعداد للسلسلة نفسها ناتج من اختلاف وحدة القياس. يوضّح شرح MDN لطول السلسلة وحدة length. ولا يكفي هذا العدد أيضًا للجزم بعدد البايتات التي يستهلكها محرك JavaScript فعليًا في الذاكرة.

حتى عدّ نقاط الترميز واحدة واحدة لا يساوي دائمًا عدد العلامات التي يراها المستخدم. فقد تلحق بالمحرف الأساسي علامات دمج، وقد يتكون رمز تعبيري واحد من عدة نقاط. تشرح قواعد تقسيم النص في Unicode حدود العناقيد الكتابية الممتدة. وطريقة مثل Array.from(text) التي تفك السلسلة بحسب نقاط الترميز لا تعدّ تلقائيًا المجموعات المكوّنة من عدة نقاط. يمكن في المتصفح، بعد التأكد من الدعم، النظر في استخدام Intl.Segmenter مع granularity مضبوطة على 'grapheme'. لكن العنقود لا يطابق بالضرورة شكلًا رسوميًا واحدًا في الخط، وقد تختلف توقعات التحرير باختلاف اللغة. ينبغي أن يعرض حقل التعريف عددًا مفهومًا للمستخدم، بينما تُطبّق حدود البايتات بصورة منفصلة على التخزين والاتصال. وإذا كتبنا «20 محرفًا» ثم قطعنا وحدات UTF-16 فقط فقد نشطر رمزًا تعبيريًا أو محرفًا مركبًا من الوسط.

3. عند تشوّه الكورية افصل التخزين والنقل والعرض

قد يتشوه النص إذا اختلف الترميز المستخدم عند كتابة البايتات عن فك الترميز عند قراءتها. فقراءة ملف UTF-8 بترميز آخر قد تُظهر محارف خاطئة حتى إن لم يتغير الملف نفسه. في HTML نفحص معًا صيغة التخزين الفعلية وcharset في ترويسة HTTP المسماة Content-Type وتصريح meta charset في مقدمة المستند. السمة lang='ko' تعلن لغة المحتوى، ولا تختار كيفية قراءة بايتات الكورية كما يفعل charset. يشرح دليل W3C لتصريحات الترميز ضرورة حفظ الملف نفسه بصيغة UTF-8 ومطابقة تصريح الخادم والمستند، بدل تغيير التصريح وحده. كما قد تؤثر علامات تعريف الترميز في أولوية الاختيار؛ فلا تفترض أن meta تتغلب دائمًا على كل المعلومات الأخرى. وإذا اختلفت النتيجة بين فتح الملف مباشرة وتلقيه من الخادم، تصبح إعدادات مرحلة النقل قرينة مهمة.

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

4. فحص القيم نفسها باستخدام TextEncoder وTextDecoder

ابدأ التجربة البسيطة بسلسلة قصيرة أنشأتها بنفسك. يحوّل TextEncoder سلسلة JavaScript إلى مصفوفة بايتات UTF-8. عند قراءة ناتج new TextEncoder().encode('가') بالنظام الست عشري نجد EA B0 80، وتساوي length للمصفوفة 3. وقد تعرض أدوات التطوير القيم نفسها بالأعداد العشرية 234 و176 و128؛ فهي ليست بيانات أخرى بل اختلاف في نظام كتابة العدد. وتبيّن المقارنة بطول السلسلة العادي، الذي يساوي 1، اختلاف الوحدات. وعند قراءة المصفوفة بواسطة new TextDecoder('utf-8').decode(bytes) نحصل على «가»؛ وbytes هنا هي المصفوفة المنشأة سابقًا. هذا المُرمِّز ينتج UTF-8، وليس محوّلًا عامًا يحفظ بأي ترميز قديم تختاره. وعند فحص ملف حقيقي استخدم أولًا بايتاته الأصلية المحفوظة. فإعادة ترميز عبارة ظهرت خطأ على الشاشة إلى UTF-8 لا تثبت بايتات الملف الأول.

بتحديد 'utf-8' و{ fatal: true } في مُنشئ TextDecoder يمكن جعل المدخل غير المطابق يؤدي إلى خطأ بدل استبداله بصمت. مثلًا، البايت FF وحده غير صالح في UTF-8، فتفشل قراءته بهذا الوضع. وفي وضع الاستبدال الافتراضي قد يظهر U+FFFD مكان الجزء غير الصحيح. لكن غياب الخطأ لا يعني اكتشاف الترميز الأصلي أو التأكد من صحة المعنى. فبعض البايتات مقبولة في أكثر من ترميز، وقد تكون نتيجة قراءة خاطئة سابقة قد حُفظت مجددًا بصيغة UTF-8 صالحة. يتحقق الفحص من قبول المُفكِّك المختار للمدخل فقط؛ ولا يثبت تلقائيًا مصدر المستند أو مطابقته للأصل أو عدم العبث به أثناء النقل بين المراحل.

5. التطبيع عملية مستقلة لتوحيد التمثيلات

قد تختلف سلسلة نقاط الترميز في نصين قُرئا بصورة صحيحة. يمكن تمثيل «가» بنقطة U+AC00 واحدة، أو بتتابع الحرف الساكن الابتدائي القابل للتركيب U+1100 وحرف العلة الأوسط U+1161. وهاتان القيمتان تختلفان عن مجرد صفّ محرفي التوافق الكوريين «ㄱ» و«ㅏ»، لذا يجب مراجعة الأرقام أيضًا. يُرسم التمثيلان عادة كمقطع واحد، لكن مقارنة سلاسل JavaScript الخام قد تعتبرهما مختلفين. يؤدي تطبيق NFC على الطرفين وفق قواعد التطبيع في Unicode إلى تمثيل واحد لهذا المثال المتكافئ تكافؤًا كانونيًا (معياريًا). ويستخدم NFD التفكيك الكانوني. في المثال يشغل تمثيل NFC ثلاثة بايتات UTF-8، وتشغل علامتا NFD ستة؛ فقد يختلف حجم التخزين رغم تطابق المظهر. ويمكن بحث هذا الاختلاف عندما يبدو استعلام البحث والاسم المحفوظ متساويين لكنهما لا يتطابقان؛ وهو مختلف عن تشوّه الحروف بسبب تفسير البايتات على نحو خاطئ.

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

6. احتفظ بالأصل وسجّل حدود التحويل

قبل الكتابة فوق الملف الأول أثناء تحديد سبب الضرر، احتفظ ببايتاته الأصلية وطريق الحصول عليه. اقرأ نسخة بالترميز المتوقع، ثم تأكد من بقاء المحتوى نفسه عند حفظ النتيجة التي تبدو صحيحة مجددًا. يذكر شرح W3C لعلامة BOM أن توقيع UTF-8 الأولي هو EF BB BF، وأن هذه البايتات ليست إلزامية. لا يحتاج UTF-8 إلى اختيار ترتيب البايتات؛ فلا تفهم هذه العلامة على أنها تؤدي وظيفة اختيار الترتيب في UTF-16. وإضافة BOM أو حذفها يختلف عن تحويل بايتات المتن إلى ترميز آخر. وإذا حُفظت القيم غير المقروءة بعد تحويلها إلى علامات استبدال أو استفهام، فقد تكون عدة قيم أصلية قد اندمجت في قيمة واحدة، فلا يمكن ضمان الاستعادة الآلية. ولا يُبحث احتمال العكس إلا عند معرفة الترميز الأصلي وتاريخ التحويل؛ فالتطبيع لا يعيد بايتات ضاعت.

لا تكتفِ بسطر إنجليزي واحد لاختبار وظيفة متعددة اللغات. ضمّن مجموعة قصيرة من الأمثلة مقطعًا كوريًا وحرفًا لاتينيًا بعلامة دمج ورمزًا تعبيريًا من مستوى إضافي، ونصًا من اليمين إلى اليسار كالعربية أو العبرية؛ ثم قارن الإدخال والتخزين وإعادة القراءة والتصدير. افحص في كل مرحلة المظهر ونقاط الترميز وعدد بايتات UTF-8 ووحدة عدّ المحارف التي يعتمدها المنتج. لا يُعالَج اتجاه القراءة بتغيير UTF-8 إلى ترميز آخر؛ فإعداد لغة الشاشة واتجاهها يحتاج إلى فحص مستقل. واحفظ الأصل واشرح المعيار الجديد أيضًا عند تغيير حد الإدخال أو مقارنة البحث. تأكد من أن عداد المتصفح وحد قبول الخادم يستخدمان الوحدة نفسها، وإن لزم حدّان مختلفان فاعرض كليهما. الأرقام والأمثلة البرمجية هنا تُظهر تحويل سلاسل قصيرة، وليست قياسات لعطل في خدمة بعينها. وبفحص رقم المحرف وبايتات التخزين ووحدات السلسلة والعرض وقواعد المقارنة بالترتيب يمكن إعادة إنتاج ما تغيّر عند كل حد.

الأدلة والمراجع

يشرح المقال معلومات من مصادر عامة. ولا تعني الإحالة مراجعة الجهة المذكورة للمحتوى أو اعتمادها له.

عن الناشر ووسائل التواصل

مقالات أخرى في الموضوع