محوّل حالة الأحرف
حوّل النص بين camelCase و snake_case و kebab-case و Title Case وغيرها، مع تحويل حالة يراعي اللغة وأدوات للأسطر. يعمل داخل متصفحك.
صيغ الحالة
- UPPERCASE
XMLHTTPREQUEST HANDLER FOR ISTANBUL CITY GUIDE - lowercase
xmlhttprequest handler for istanbul city guide - Title Case
Xmlhttprequest Handler For Istanbul City Guide - Sentence case
Xmlhttprequest handler for istanbul city guide - camelCase
xmlHttpRequestHandlerForIstanbulCityGuide - PascalCase
XmlHttpRequestHandlerForIstanbulCityGuide - snake_case
xml_http_request_handler_for_istanbul_city_guide - kebab-case
xml-http-request-handler-for-istanbul-city-guide - CONSTANT_CASE
XML_HTTP_REQUEST_HANDLER_FOR_ISTANBUL_CITY_GUIDE - dot.case
xml.http.request.handler.for.istanbul.city.guide
عمليات الأسطر
XMLHttpRequest handler for istanbul city guideالأسطر: 1
تقسيم واحد للكلمات، عشر صيغ للحالة
يبدو تغيير حالة الأحرف أبسط ما يمكن أن يفعله برنامج، وهو مصدر عدد مفاجئ من الأخطاء الحقيقية. أحد الأسباب أن «الحالة» في الواقع مسألتان منفصلتان تحملان اسمًا واحدًا. تحويل النص إلى UPPERCASE أو lowercase تخطيط حرفًا بحرف يعرّفه Unicode. أما تحويله إلى camelCase أو snake_case فشيء مختلف تمامًا: عليك أولًا أن تقرر أين تقع الكلمات، وبعد ذلك فقط تعيد تشكيل الحالة وتربط. هذه الأداة تفعل الأمرين، وتعرض كل النتائج دفعة واحدة، والأهم أنها تخبرك أي الإجابات يعتمد على إعداد اللغة.
صيغ المبرمجين الست تنبع كلها من التقسيم نفسه، فلا يمكن أن تتناقض فيما بينها. وهذه حدوده:
- حرف صغير يليه حرف كبير يبدأ كلمة جديدة، وهذا ما يجعل fooBar يصبح foo و Bar.
- سلسلة الأحرف الكبيرة تنتهي حيث تبدأ الكلمة التالية، أي عند آخر حرف كبير قبل حرف صغير. فيصبح XMLHttpRequest هو XML و Http و Request، ويصبح IOError هو IO و Error.
- رقم يليه حرف كبير يبدأ كلمة جديدة، فيصبح html5Parser هو html5 و Parser.
- كل ما ليس حرفًا أو رقمًا أو علامة تشكيل يفصل بين الكلمات: المسافات والشرطات السفلية والشرطات والنقاط وعلامات الترقيم.
وفيما عدا ذلك تبقى الأرقام ملتصقة بالكلمة التي تسبقها، ولهذا يمر utf8 و address2 كاملين. هذا اختلاف مقصود عن lodash، الذي يعامل كل سلسلة أرقام ككلمة مستقلة ويحوّل utf8 إلى utf_8 — متسق تقنيًا، لكنه ليس ما قصده أحد وهو يسمّي متغيرًا.
التقسيم مكتوب بخصائص Unicode لا بفئة أحرف ASCII، لذا لا يقتصر على الإنجليزية. المعرّف المكتوب بالسيريلية ينقسم عند حدود حالته تمامًا كما ينقسم المعرّف اللاتيني. أما الكتابة التي لا حالة فيها أصلًا — العربية والعبرية والصينية واليابانية والكورية — فلا حدود حالة فيها ليُعثر عليها، ولذلك تنقسم كلماتها عند الفواصل فقط، وهذه هي الإجابة الصحيحة وليست قصورًا.
الاختصارات: إجابتان صحيحتان
بعد أن يُقسَّم XMLHttpRequest إلى XML و Http و Request، تطرح إعادة تركيبه بصيغة PascalCase سؤالًا بلا إجابة وحيدة صحيحة. إن أعدت تشكيل كل كلمة حصلت على XmlHttpRequest، وهو ما ينتجه lodash وما يطلبه دليل أسلوب Google للغة Java: عامِل الاختصار ككلمة عادية كي تبقى الحدود مرئية في أسماء مثل ParseXmlDocument. وإن أبقيت سلسلة الأحرف الكبيرة حصلت على XMLHttpRequest، وهو الاسم الفعلي لواجهة المتصفح والعُرف الذي يتبعه معظم DOM وكثير من .NET.
كلتا الصيغتين مستعملة في قواعد شيفرة حقيقية، لذا تقدّم الأداة كلتيهما وتجعل إعادة التشكيل هي الافتراضي. والخيار يؤثر في camelCase و PascalCase فقط، لأن الصيغ الأخرى تعيد تشكيل الكلمة كاملة على أي حال، فيبقى XMLHttpRequest هو xml_http_request و XML_HTTP_REQUEST في الحالتين.
وثمة تفصيل ثابت لا خيار فيه: الكلمة الأولى في اسم camelCase تُصغَّر بالكامل دائمًا، حتى لو كانت اختصارًا. فالإبقاء عليها ينتج PascalCase، والإبقاء الجزئي ينتج xMLHttpRequest، وهي صيغة لا يوصي بها أي دليل أسلوب ويتعثر بها كل قارئ. لذلك يصبح XMLHttpRequest هو xmlHttpRequest في الحالتين، بينما يحتفظ parseXMLDocument باختصاره في الوسط عندما تطلب ذلك.
حرف الـ i التركي، ولماذا اللغة خيار
في JavaScript طريقتان للتحويل إلى الأحرف الكبيرة، والفرق بينهما أوقع أنظمة إنتاج. الطريقة toUpperCase تطبّق تخطيط Unicode المستقل عن اللغة: يصبح الحرف i هو I. أما toLocaleUpperCase فتطبّق قواعد لغة بعينها، وفي التركية والأذربيجانية يكون الحرف الكبير لـ i هو İ بنقطة، لأن في هاتين الأبجديتين أيضًا حرف ı بلا نقطة حرفه الكبير هو I. حوّل مُدخل مستخدم تركي إلى الأحرف الكبيرة بلغته وقارنه بكلمة مفتاحية في بروتوكول، وستفشل المقارنة عند حرف يكاد يبدو مطابقًا.
هذا أشهر خطأ تدويل في معالجة النصوص، وهو غير مرئي إلى أن يقع لك أنت. لذلك لا تخمّن هذه الأداة أبدًا. افتراضها هو التخطيط المستقل عن اللغة، وهو ما تحتاجه المعرّفات ورموز البروتوكولات وترويسات HTTP ومفاتيح قواعد البيانات. واختيار لغة ينقلك إلى قواعد تلك اللغة — وكلما غيّر الاختيار نتيجةً عرضت الأداة إلى جانبها الإجابة المستقلة عن اللغة، لترى بالضبط ما فعلته اللغة.
'i'.toUpperCase() // 'I'
'i'.toLocaleUpperCase('tr') // 'İ' dotted capital
'I'.toLocaleLowerCase('tr') // 'ı' dotless lowercase
'ISTANBUL'.toLocaleLowerCase('tr') // 'ıstanbul'وثلاث لغات أخرى تغيّر الإجابة، والأداة تقدّمها جميعًا. الليتوانية تُبقي النقطة فوق الـ i عند تصغير حرف كبير يحمل علامة أخرى، فيصبح Ì هو i متبوعًا بنقطة مركّبة وعلامة مركّبة، لا الحرف المؤلَّف الواحد ì. واليونانية تُسقط علامة التونوس عند التكبير، فيصبح άνθρωπος هو ΑΝΘΡΩΠΟΣ لا ΆΝΘΡΩΠΟΣ، لأن اليونانية لا تكتب العلامة على الأحرف الكبيرة. والأذربيجانية تشارك التركية في الـ i بنقطة وبدونها.
وحالتان مشهورتان ليستا قاعدتي لغة، ويجدر تمييز إحداهما من الأخرى. الحرف الألماني ß يتحول إلى SS في كل اللغات، لأن ذلك قاعدة SpecialCasing في Unicode لا قاعدة ألمانية — ولهذا أيضًا لا يمكن عكس التخطيط، فـ SS يصغّر إلى ss. وحرف السيغما اليوناني في آخر الكلمة يصغّر إلى الصيغة النهائية ς لا σ، في كل مكان: هذا شرط سياقي على موضع الحرف، لا إعداد لغة. وكلاهما ظاهر في هذه الأداة دون اختيار أي لغة.
صيغتا Title case و Sentence case هنا آليّتان
تجعل صيغة Title Case في هذه الأداة أول حرف من كل كلمة كبيرًا وبقية الحروف صغيرة. وتجعل صيغة Sentence case أول حرف من النص وأول حرف من كل جملة بعده كبيرًا، حيث تنتهي الجملة عند نقطة أو علامة تعجب أو استفهام أو ثلاث نقاط تتبعها مسافة. وكلتاهما تصغّر المُدخل أولًا، وهذا ما يجعلهما مفيدتين مع نص وصل بالصياح.
هاتان قاعدتان آليّتان عن قصد، ويستحق البديل شرحًا. تعرّف أدلة الأسلوب الإنجليزية صيغة العنوان بالكلمات القصيرة التي تبقى صغيرة — وهي مختلفة فيما بينها على القائمة، وعلى تكبير الكلمة بعد النقطتين، وعلى حروف الجر المكوّنة من أربعة أحرف فأكثر. وتطبيق واحد منها هنا يعني شحن أعراف الصحافة الأمريكية إلى قرّاء يكتبون بالعربية أو اليابانية أو التركية، حيث لا ينتقل المفهوم أصلًا. أما تكبير كل كلمة فمتوقَّع وقابل للشرح بكل لغة وسهل التعديل يدويًا بعده.
شرط المسافة في صيغة الجملة هو ما يمنع 3.5 metres من أن تصير 3.5 Metres، لأن النقطة هناك يتبعها رقم. لكنه لا يحل مسألة الاختصارات: فـ e.g. this ما زال يبدأ جملة زائفة، لأن معرفة أن «e.g.» ليست نهاية جملة تتطلب قائمة باختصارات تلك اللغة. كذلك لا تبدأ الفاصلة العليا داخل الكلمة كلمةً جديدة، فـ it's يُكبَّر مرة واحدة ويعطي It's لا It'S.
الفرز: ترتيب أبجدي أم ترتيب بايتات
تقصّ أدوات الأسطر المسافات وتحذف المكرر وتفرز وتعكس وترقّم، ويطرح الفرز مسألة اللغة مرة ثانية. فالترتيب الأبجدي خاصية لغة لا خاصية Unicode. الألمانية تضع ä بجوار a، فيكون الترتيب a ثم ä ثم z. والسويدية تعدّها حرفًا مستقلًا في آخر الأبجدية، فيكون a ثم z ثم ä. وكلاهما صحيح في بلده، والأداة تستخدم اللغة التي اخترتها أعلى الصفحة.
والخيار الآخر هو ترتيب نقاط الترميز، وهو ما تريده حين يجب أن تطابق الإجابة شيئًا آخر. فالمقارنة بنقطة الترميز هي ترتيب البايتات في UTF-8، ولذلك تعيد بالضبط ما ينتجه LC_ALL=C sort و git ومقارنة السلاسل الافتراضية في معظم لغات البرمجة: كل حرف كبير قبل كل حرف صغير، فيسبق B الحرف a. اخترها حين تقارن مخرجات أداة أخرى، واختر الترتيب الأبجدي حين يقرأ إنسان القائمة.
أما الترتيب الرقمي الطبيعي — item2 قبل item10 لا بعده — فخاصية من خصائص المقارنة اللغوية، ولذلك يتاح مع القاعدة الأبجدية فقط. ترتيب البايتات لا يعرف مفهوم الرقم داخل السلسلة، ولا يعرفه الفرز الذي يحاكيه، فتقديم الاثنين معًا وعد لا تستطيع المخرجات الوفاء به.
تُنفَّذ العمليات بترتيب ثابت، والترتيب مهم. القصّ يسبق حذف المكرر، فتُعرَف السطران اللذان اختلفا في مسافة أخيرة فقط على أنهما سطر واحد. والفرز يسبق العكس، فيعني العكس دائمًا «اعكس ما أراه». ويُذكر عدد الأسطر الفارغة وعدد المكررات المحذوفة معًا، لأن العدّ الصامت هو الطريقة التي لا تنتبه بها إلى أن قائمتك لم تكن كما ظننت.
أين يعمل هذا
كل شيء يجري في متصفحك. تخطيطات الحالة تأتي من تطبيق Unicode في محرك JavaScript نفسه، والمقارنة اللغوية من Intl.Collator، فتتفق الإجابات مع بيئة التشغيل التي ستعالج سلاسلك فعلًا لا مع جدول نسخه أحدهم قبل سنوات. ولا يُرفع شيء مما تلصقه ولا يُخزَّن ولا يُسجَّل، ما يجعل الأداة آمنة على الأسماء وأسطر السجلات وكل ما تفضّل ألا ترسله إلى خادم لمجرد تغيير حالة أحرفه.
الأسئلة الشائعة
- هل يُرسل نصي إلى خادم؟
- لا. كل تحويل يجري في متصفحك باستخدام تخطيطات حالة Unicode في محرك JavaScript نفسه، ولا يُرفع شيء مما تلصقه ولا يُسجَّل.
- لماذا يظهر لي اختيار التركية نتيجتين مختلفتين؟
- لأن التركية تغيّر الإجابة. ففي أبجديتها حرف i بنقطة وحرف ı بلا نقطة، ولذلك يكون الحرف الكبير لـ i هو İ لا I. وكلما غيّرت لغةٌ نتيجةً عرضت الأداة إلى جانبها الإجابة المستقلة عن اللغة، لأنها ما تحتاجه المعرّفات ورموز البروتوكولات.
- هل أستخدم التخطيط المستقل عن اللغة أم تخطيط لغة؟
- استخدم التخطيط المستقل عن اللغة لكل ما تقرؤه آلة: المعرّفات وترويسات HTTP ومفاتيح قواعد البيانات ورموز البروتوكولات وأسماء الملفات. واستخدم تخطيط لغة للنص الذي يقرؤه إنسان بتلك اللغة. والخلط بينهما هو خطأ الـ i التركي الكلاسيكي.
- لماذا يتحول XMLHttpRequest إلى XmlHttpRequest؟
- لأن الاختصارات يُعاد تشكيلها افتراضيًا، وهو ما يفعله lodash وعدة أدلة أسلوب كبرى. فعّل خيار الاختصارات للإبقاء على سلسلة الأحرف الكبيرة واستعادة XMLHttpRequest. والخيار يؤثر في camelCase و PascalCase فقط، لأن الصيغ الأخرى تعيد تشكيل كل كلمة على أي حال.
- لماذا يبقى utf8 كلمة واحدة بينما يقسمه lodash؟
- لأن الأرقام هنا تلتصق بالكلمة التي تسبقها، فيمر utf8 و address2 كاملين. أما الرقم الذي يليه حرف كبير فيبدأ كلمة جديدة، وهذا ما يقسم html5Parser إلى html5 و Parser.
- لماذا تُكبّر صيغة Title Case كلمات قصيرة مثل «of» و«the»؟
- لأن القاعدة آليّة عن قصد. أدلة الأسلوب الإنجليزية مختلفة فيما بينها على الكلمات القصيرة التي تبقى صغيرة، وأعرافها لا تنتقل إلى بقية اللغات التي يخدمها هذا الموقع. وتكبير كل كلمة متوقَّع وسهل التعديل يدويًا.
- لماذا لا أستطيع استخدام الترتيب الرقمي الطبيعي مع ترتيب نقاط الترميز؟
- الترتيب الطبيعي خاصية من خصائص المقارنة اللغوية. أما ترتيب نقاط الترميز فهو ترتيب بايتات لا مفهوم فيه لرقم داخل سلسلة — ولا في LC_ALL=C sort الذي يحاكيه. وتقديم الاثنين معًا ينتج مخرجات لم تعد تطابق الأداة التي يحاكيها.
- لماذا يصبح ß هو SS حتى دون اختيار لغة؟
- لأن ذلك التخطيط قاعدة SpecialCasing في Unicode لا قاعدة في اللغة الألمانية، فينطبق في كل مكان. وهو أيضًا غير قابل للعكس: فـ SS يصغّر إلى ss لا إلى ß. وحرف السيغما النهائي في اليونانية يعمل بالطريقة نفسها — فهو يعتمد على موضع الحرف لا على لغة.