لكن في بداية حديثي ، ذكرت أن هذا لن يكون عرضًا آخر من سلسلة "المفاهيم الخاطئة حول X ، التي يؤمن بها المبرمجون". يمكنك أن تجد أي عدد من هذه الاكتشافات. ومع ذلك ، لا أحب هذه المقالات. يسردون أشياء مختلفة يُفترض أنها خاطئة ، لكن نادرًا ما يشرحون سبب ذلك وما يجب فعله بدلاً من ذلك. أظن أن الناس سيقرأون فقط مقالات مثل هذه ، ويهنئون أنفسهم على هذا الإنجاز ، ثم يذهبون للبحث عن طرق جديدة مثيرة لارتكاب أخطاء غير مذكورة في هذه المقالات. هذا لأنهم لم يفهموا حقًا المشكلات التي تسبب هذه الأخطاء.
لذلك ، في تقريري ، حاولت شرح بعض المشاكل على أفضل وجه ممكن وشرح كيفية حلها - أحب هذا النهج أكثر من ذلك بكثير . أحد الموضوعات التي تناولتها بشكل عابر (كانت مجرد شريحة واحدة واثنين من الإشارات في شرائح أخرى) هي التعقيدات التي يمكن ربطها بحالة الشخصيات. هناك Correct Answer ™ رسمي للمشكلة التي ناقشتها - مقارنة معرّف غير حساس لحالة الأحرف - وفي حديثي قدمت أفضل حل أعرفه ، باستخدام مكتبة Python القياسية فقط.
ومع ذلك ، فقد ذكرت بإيجاز التعقيدات الأعمق لحالة أحرف Unicode ، وأريد تخصيص بعض الوقت لوصف التفاصيل. إنه أمر مثير للاهتمام ، ويمكن أن يساعدك فهمه في اتخاذ القرارات عند تصميم وكتابة كود معالجة النصوص. لذلك ، أقدم لكم عكس المقالات "المفاهيم الخاطئة حول X التي يعتقد المبرمجون بها" - "الحقيقة التي يجب أن يعرفها المبرمجون."
وشيء آخر: Unicode مليء بالمصطلحات. في هذه المقالة ، سأستخدم تعريفات "الأحرف الكبيرة" و "الأحرف الصغيرة" بشكل أساسي ، منذ معيار Unicodeيستخدم هذه الشروط. إذا كنت تحب مصطلحات أخرى مثل الأحرف الصغيرة / الكبيرة ، فلا بأس بذلك. أيضًا ، غالبًا ما أستخدم مصطلح "رمز" ، والذي قد يراه البعض غير صحيح. نعم ، في Unicode ، ليس مفهوم "الشخصية" دائمًا ما يتوقعه الناس ، لذلك من الأفضل غالبًا تجنبه باستخدام مصطلحات أخرى. ومع ذلك ، في هذه المقالة ، سأستخدم المصطلح كما هو مستخدم في Unicode - لوصف كيان مجرد يمكن المطالبة به. كلما كان ذلك مهمًا ، سأستخدم مصطلحات أكثر تحديدًا مثل رمز نقطة للتوضيح.
يوجد أكثر من سجلين
يتم استخدام المتحدثين الأصليين للغات الأوروبية لحقيقة أن لغاتهم تستخدم أحرف الحالة للإشارة إلى أشياء محددة. على سبيل المثال ، في اللغات الإنجليزية [والروسية] ، نبدأ عادةً الجمل بحرف كبير ونستمر في أغلب الأحيان بأحرف صغيرة. أيضًا ، تبدأ الأسماء بأحرف كبيرة ، والعديد من الاختصارات مكتوبة بأحرف كبيرة.
ونعتقد عادة أن هناك سجلين فقط. يوجد الحرف "A" ويوجد الحرف "a". واحد بالأحرف الكبيرة والآخر بالأحرف الصغيرة - أليس كذلك؟
ومع ذلك ، هناك ثلاثة سجلات في Unicode. هناك حالة كبيرة ، وهناك حالة صغيرة ، وهناك حالة عنوان [titlecase]. في اللغة الإنجليزية ، تتم كتابة الأسماء بهذه الطريقة. على سبيل المثال ، "Avengers: Infinity War". عادة ، لهذا ، يتم كتابة الحرف الأول من كل كلمة ببساطة بأحرف كبيرة (واعتمادًا على القواعد والأنماط المختلفة ، لا يتم كتابة بعض الكلمات ، مثل المقالات ، بأحرف كبيرة).
يعطي معيار Unicode مثالاً على حرف في حالة كبيرة: U + 01F2 LATIN CAPITAL LETTER D WITH SMALL Z. يبدو مثل هذا: Dz.
أحيانًا ما تكون هذه الأحرف مطلوبة للتعامل مع النتائج السلبية لأحد الحلول المبكرة لمعيار Unicode: التوافق مع الإصدارات السابقة مع ترميزات النص الحالية. سيكون أكثر ملاءمة لـ Unicode أن يؤلف تسلسلات باستخدام مجموعة الأحرف القياسية. ومع ذلك ، في العديد من الأنظمة الحالية ، تم بالفعل تخصيص مساحة للتسلسلات الجاهزة. على سبيل المثال ، في ISO-8859-1 ("latin-1") ، يحتوي الحرف "é" على نموذج جاهز مرقم 0xe9. في Unicode ، يُفضل كتابة هذا الحرف بعلامة منفصلة "e" وعلامة تشكيل. ولكن لضمان التوافق الكامل مع الإصدارات السابقة مع الترميزات الحالية مثل latin-1 ، يقوم Unicode أيضًا بتعيين نقاط رمز للأحرف الجاهزة. على سبيل المثال ، U + 00E9 LATIN SMALL LETTER E WITH ACUTE.
على الرغم من أن موضع الكود الخاص بهذا الحرف هو نفس قيمة البايت اللاتيني 1 ، فلا يجب أن تعتمد عليه. من غير المحتمل أن يحافظ ترميز الأحرف في Unicode على هذه المواضع. على سبيل المثال ، في UTF-8 ، تتم كتابة موضع الكود U + 00E9 كتسلسل البايت 0xc3 0xa9.
وبالطبع ، هناك أحرف في الترميزات الحالية تحتاج إلى معالجة خاصة عند استخدام الأحرف الكبيرة ، ولهذا السبب تم تضمينها في Unicode "كما هي". إذا كنت تريد إلقاء نظرة عليها ، فابحث في قاعدة بيانات Unicode المفضلة لديك عن الأحرف من فئة Lt ("حرف ، حرف العنوان").
هناك عدة طرق لتحديد الحالة
يسرد معيار Unicode (§4.2) ثلاثة تعريفات مختلفة للحالة. ربما يتم اختيار أحد الثلاثة من خلال لغة البرمجة الخاصة بك ؛ خلاف ذلك ، سيعتمد اختيارك على هدفك المحدد. هذه التعريفات هي:
- يكون الحرف بأحرف كبيرة إذا كان في فئة Lu ("حرف ، أحرف كبيرة") ، وفي حالة صغيرة إذا كان في فئة Ll ("حرف ، أحرف صغيرة"). يعترف المعيار بحدود هذا التعريف: يجب أن يُنسب كل رمز محدد إلى فئة واحدة فقط. ولهذا السبب ، فإن العديد من الأحرف "يجب أن تكون" في الأحرف الكبيرة أو الصغيرة لن تفي بهذا المطلب لأنها تنتمي إلى فئة أخرى.
- يكون الحرف بأحرف كبيرة إذا كان يرث خاصية الأحرف الكبيرة ، وفي حالة الأحرف الصغيرة إذا كان يرث خاصية الأحرف الصغيرة. إنه مزيج من تعريف واحد مع خصائص شخصية أخرى ، من بينها الحالة.
- يكون الحرف بأحرف كبيرة إذا لم يتغير بعد تعيينه إلى أحرف كبيرة. يكون الحرف بأحرف صغيرة إذا لم يتغير بعد تعيينه إلى الأحرف الصغيرة. هذا تعريف عام إلى حد ما ، ولكن يمكن أن يتصرف أيضًا بشكل غير حدسي.
إذا كنت تعمل بمجموعة فرعية محدودة من الرموز (على وجه التحديد ، مع الأحرف) ، فقد يكون تعريفًا واحدًا كافيًا لك. إذا كانت ذخيرتك الموسيقية أوسع - فهي تتضمن رموزًا تشبه الحروف وليست أحرفًا ، فقد يناسبك التعريف الثاني. يوصى به وفقًا لمعيار Unicode ، §4.2:
يجب أن يعمل المبرمجون الذين يتلاعبون بسلاسل Unicode مع وظائف سلسلة مثل isLowerCase (وابن عمها الوظيفي toLowerCase) إذا لم يعملوا مباشرة مع خصائص الأحرف.
الوظيفة المذكورة هنا محددة في § 3.13 من معيار Unicode. بشكل رسمي ، يستخدم التعريف 3 الدالتين isLowerCase و isUpperCase من §3.13 ، المحددة من حيث المواضع الثابتة في toLowerCase و toUpperCase ، على التوالي.
إذا كانت لغة البرمجة الخاصة بك تحتوي على وظائف للتحقق من حالة السلاسل أو الأحرف الفردية أو تحويلها ، فمن المفيد التحقق من أي من التعريفات المذكورة يتم استخدامها في التنفيذ. إذا كنت مهتمًا ، فإن أساليب isupper () و islower () في Python تستخدم التعريف الثاني.
من المستحيل فهم حالة الشخصية من خلال مظهرها أو اسمها
من خلال ظهور العديد من الشخصيات ، يمكنك معرفة حالتها. على سبيل المثال ، "أ" بأحرف كبيرة. هذا واضح أيضًا من اسم الرمز: "LATIN CAPITAL LETTER A". ومع ذلك ، في بعض الأحيان لا تعمل هذه الطريقة. خذ نقطة الكود U + 1D34. يبدو مثل هذا: ᴴ. في Unicode ، يتم تعيين الاسم له: MODIFIER LETTER CAPITAL H. لذا فهو حرف كبير ، أليس كذلك؟
في الواقع ، ترث خاصية الأحرف الصغيرة ، لذلك حسب التعريف رقم 2 فهي في أحرف صغيرة ، على الرغم من أنها تشبه بصريًا حرف H كبير ، والاسم يحتوي على كلمة "CAPITAL".
بعض الشخصيات ليس لها حالة على الإطلاق
ينص التعريف 135 في §3.13 من معيار Unicode على ما يلي:
تعتبر C حساسة لحالة الأحرف إذا وفقط إذا كانت C تحتوي على خاصية الأحرف الصغيرة أو الأحرف الكبيرة ، أو كانت الفئة العامة هي Titlecase_Letter.
هذا يعني أن الكثير من أحرف Unicode - في الواقع ، معظمها - بلا غلاف. الأسئلة المتعلقة بقضيتهم لا معنى لها ، ولا تؤثر عليهم تغييرات الحالة. ومع ذلك ، يمكننا الحصول على إجابة لهذا السؤال من خلال التعريف رقم 3.
تتصرف بعض الشخصيات وكأن لديها سجلات متعددة
المعنى الضمني هو أنك إذا استخدمت التعريف رقم 3 وسألت عما إذا كان الحرف غير المسجّل هو أحرف كبيرة أم صغيرة ، فستحصل على نعم.
يعطي معيار Unicode مثالاً (الجدول 4-1 ، السطر 7) للحرف U + 02BD MODIFIER LETTER COMMA (الذي يشبه هذا: ʽ). لا يحتوي على خصائص الأحرف الصغيرة أو الأحرف الكبيرة الموروثة ، ولا ينتمي إلى الفئة Lt ، لذلك ليس له حالة. في الوقت نفسه ، لا يؤدي التحويل إلى أحرف كبيرة إلى تغييره ، ولا يؤدي التحويل إلى أحرف صغيرة إلى تغييره ، لذلك من خلال التعريف الثالث يجيب بـ "نعم" على كلا السؤالين: "هل أنت حرف كبير؟" و "هل أنت صغير؟"
يبدو أن هذا يمكن أن يسبب ارتباكًا غير ضروري ، ولكن النقطة هي أن التعريف رقم 3 يعمل مع أي تسلسل من أحرف Unicode ، ويسمح لك بتبسيط خوارزميات تحويل الحالة (الأحرف التي لا تحتوي على أحرف تتحول إلى نفسها فقط).
الحالة حساسة للسياق
قد تعتقد أنه إذا كانت جداول تحويل حالة Unicode تغطي جميع الأحرف ، فإن هذا التحويل يتعلق ببساطة بإيجاد المكان المناسب في الجدول. على سبيل المثال ، تشير قاعدة بيانات Unicode إلى أن U + 0041 LATIN CAPITAL LETTER A هو حرف صغير U + 0061 LATIN SMALL LETTER A. بسيط ، أليس كذلك؟
أحد الأمثلة التي لا يعمل فيها هذا النهج هو اليونانية. الحرف Σ - أي U + 03A3 GREEK CAPITAL LETTER SIGMA - يتم تعيينه إلى حرفين مختلفين عند تحويله إلى أحرف صغيرة ، اعتمادًا على مكانه في الكلمة. إذا كانت في نهاية الكلمة ، فستكون صغيرة ς (U + 03C2 GREEK SMALL LETTER FINAL SIGMA). في مكان آخر سيكون σ (U + 03C3 GREEK SMALL LETTER SIGMA).
هذا يعني أن السجل ليس فردي أو متعد. مثال آخر هو ß (U + 00DF LATIN SMALL LETTER SHARP S ، أو escet ). سيكون "SS" بأحرف كبيرة ، على الرغم من وجود الآن شكل كبير آخر (ẞ، U + 1E9E LATIN CAPITAL LETTER SHARP S). ويؤدي تحويل "SS" إلى أحرف صغيرة إلى "ss" ، لذلك (باستخدام مصطلحات Unicode لتحويل الحالة): toLowerCase (toUpperCase (ß))! = Ss.
الحالة تعتمد على اللغة
اللغات المختلفة لها قواعد تحويل حالة مختلفة. المثال الأكثر شيوعًا: i (U + 0069 LATIN SMALL LETTER I) و I (U + 0049 LATIN CAPITAL LETTER I) يتم تحويلهما إلى بعضهما البعض في معظم المناطق - معظمها ، ولكن ليس كلها. في اللغتين az و tr (اللغات التركية) ، ستكون الأحرف الكبيرة I (U + 0130 LATIN CAPITAL LETTER I WITH DOT ABOVE) ، وسأكون الحرف الصغير I (U + 0131 LATIN SMALL LETTER I). في بعض الأحيان ، يعني تصحيح الأمر حقًا الفرق بين الحياة والموت.
لا يتعامل Unicode نفسه مع جميع قواعد تحويل الحالة الممكنة لجميع اللغات. تحتوي قاعدة بيانات Unicode على قواعد عامة فقط لتحويل جميع الأحرف ، وليست خاصة باللغة المحلية. هناك أيضًا قواعد خاصة لبعض اللغات والأشكال المركبة - اللغات الليتوانية والتركية وبعض ميزات اليونانية. كل شيء آخر غير موجود. تنص الفقرة 3.13 من المعيار على ذلك وتوصي بإدخال قواعد الترجمة الخاصة بالمكان إذا لزم الأمر.
قد يكون أحد الأمثلة على ذلك هو علامة ناطقة باللغة الإنجليزية - هذه هي حالة العنوان لأسماء معينة. يجب تحويل "O'brian" إلى "O'Brian" (وليس "O'brian"). ومع ذلك ، عند القيام بذلك ، يجب تحويل "it's" إلى "it's" وليس إلى "It'S". مثال آخر لم يتم التعامل معه في Unicode هو تركيبة الأحرف الهولندية "" ، والتي ، عند تحويلها إلى حالة أحرف كبيرة ، يجب تحويلها إلى جميع الأحرف الكبيرة إذا ظهرت في بداية الكلمة. وبالتالي ، فإن أكبر خليج في هولندا في سجل الملكية سيكون "IJsselmeer" بدلاً من "Ijsselmeer". يحتوي Unicode على الأحرف IJ U + 0132 LATIN CAPITAL LIGATURE IJ و ij U + 0133 LATIN SMALL LIGATURE IJ إذا كنت بحاجة إليها. بشكل افتراضي ، يحولها تحويل الحالة إلى بعضها البعض (على الرغم من أن أشكال تسوية Unicode باستخدام تكافؤ التوافق ستقسمهم إلى حرفين منفصلين)
العودة إلى المواد المعروضة في التقرير. تعقيد إدارة حالة Unicode يعني أن المقارنات غير الحساسة لحالة الأحرف لا يمكن إجراؤها باستخدام وظائف التحويل القياسية الصغيرة أو الكبيرة الموجودة في العديد من لغات البرمجة. لمثل هذه المقارنات ، يحتوي Unicode على مفهوم طي الحالة ، ويحدد البند 3.13 من المعيار دالتي toCaseFold و isCaseFolded.
قد تعتقد أن الصب على الحالة المطوية يشبه الصب إلى الأحرف الصغيرة - لكنه ليس كذلك. يحذر معيار Unicode من أن السلسلة ذات الحالة المطوية لا يجب أن تكون صغيرة. على سبيل المثال ، يتم إعطاء لغة الشيروكي - هناك ، في سلسلة مطوية ، ستظهر أيضًا الأحرف الكبيرة.
في إحدى الشرائح في حديثي ، تم تنفيذ Unicode Technical Report # 36 بشكل كامل في Python قدر الإمكان. يتم تنفيذ تسوية NFKC ثم يتم استدعاء طريقة casefold (متوفرة فقط في Python 3+) للسلسلة الناتجة. ومع ذلك ، تسقط بعض حالات الحافة ، وهذا ليس حقًا ما يوصى به لمقارنة الهوية. الأخبار السيئة أولاً: لا تكشف Python عن خصائص Unicode كافية لتصفية الأحرف غير الموجودة في XID_Start أو XID_Continue ، أو الأحرف التي لها خاصية Default_Ignorable_Code_Point. بقدر ما أعرف ، فإنه لا يدعم تعيين NFKC_Casefold. لا توجد أيضًا طريقة سهلة لاستخدام NFKC UAX # 31§5.1.
والخبر السار هو أن معظم هذه الحالات المتطورة لا تنطوي على أي مخاطر أمنية حقيقية تشكلها الرموز المعنية. ولا يتم تعريف طي العلبة من حيث المبدأ على أنه عملية الحفاظ على التطبيع (ومن هنا جاء تعيين NFKC_Casefold ، والذي تمت إعادة تطبيعه إلى NFC بعد طي الحالة). بشكل عام ، عند المقارنة ، لا تهتم إذا تم تطبيع كلا الخيطين بعد المعالجة المسبقة. أنت تهتم إذا لم تكن المعالجة المسبقة غير متسقة وتضمن أن الخطوط التي "يجب" أن تختلف لاحقًا فقط ستكون مختلفة بعد ذلك. إذا كنت قلقًا بشأن هذا الأمر ، فيمكنك إعادة التطبيع يدويًا بعد إضافة التسجيل.
يكفي حاليا
هذه المقالة ، مثل التقرير السابق ، ليست شاملة ، ولا يكاد يكون من الممكن تضمين كل هذه المواد في منشور واحد. نأمل أن تكون هذه نظرة عامة مفيدة حول تعقيدات هذا الموضوع ، وتوفر نقاط انطلاق كافية للبحث عن مزيد من المعلومات. لذلك ، من حيث المبدأ ، يمكنك التوقف هنا.
ألن يكون من السذاجة أن نأمل أن يتوقف الآخرون عن كتابة تعريضات من سلسلة "المفاهيم الخاطئة حول X التي يؤمن بها المبرمجون" ويبدأون في كتابة مقالات مثل "الحقيقة التي يجب أن يعرفها المبرمجون"؟