Заглавные и строчные буквы

    Я собрал здесь некоторые не очень очевидные факты о заглавных и строчных буквах, с которыми может столкнуться программист в работе. Многие из вас переводили строки во «все заглавные» (uppercase), «все строчные» (lowercase), «первую заглавную, а остальные строчные» (titlecase). Ещё более популярна операция сравнения без учёта регистра. В мировом масштабе такие операции могут быть весьма нетривиальны. Пост построен в виде «сборника заблуждений» с контрпримерами.

    1. Если я переведу строку в uppercase или lowercase, число Unicode-символов не изменится.

    Нет. В тексте могут попасться строчные лигатуры, которым не соответствует один символ в верхнем регистре. Например, при переводе в uppercase: fi (U+FB00) -> FI (U+0046, U+0049)

    2. Лигатуры — изврат, ими никто не пользуется. Если их не учитывать, то я прав.

    Нет. Некоторым буквам с диакритикой нет точного соответствия в другом регистре, поэтому приходится использовать комбинированный символ. Скажем, в языке африкаанс есть буква ʼn (U+0149). В верхнем регистре ей соответствует комбинация из двух символов: ʼN (U+02BC, U+004E). Если вам попадётся транслитерация арабского текста, вы можете столкнуться с (U+1E96), которой в верхнем регистре также нет односимвольного соответствия, поэтому придётся заменять на (U+0048, U+0331). В ваханском языке есть буква (U+01F0) с аналогичной проблемой. Вы можете возразить, что это экзотика, однако на африкаанс в википедии 23000 статей.

    3. Ну хорошо, но давайте считать комбинированный символ (с участием modifying или combining code points) одним символом. Тогда длина всё же сохранится.

    Нет. Есть, например, в немецком языке буква «эсцет» ß (U+00DF). При переводе в верхний регистр, она превращается в два символа SS (U+0053, U+0053).

    4. Ладно-ладно, понял. Будем считать, что количество Unicode-символов может увеличиться, но не больше, чем вдвое.

    Нет. Бывают специфические греческие буквы, например, (U+0390), которые превращаются в три Unicode-символа (U+0399, U+0308, U+0301)

    5. Давайте про titlecase. Здесь всё просто: взял первый символ из слова, перевёл в uppercase, взял все последующие, перевёл в lowercase.

    Нет. Вспомним те же лигатуры. Если слово в lowercase начинается с fl (U+FB02), то в uppercase лигатура превратится в FL (U+0046, U+004C), но в titlecase — в Fl (U+0046, U+006C). То же самое с ß, но с неё, теоретически, слова начинаться не могут.

    6. Опять эти лигатуры! Хорошо, берём первый символ из слова, переводим в uppercase, если получилось больше одного символа, то первый оставляем, а остальные назад в lowercase. Тогда точно сработает.

    Не сработает. Есть, например, диграф dz (U+01F3), который может использоваться в тексте на польском, словацком, македонском или венгерском языке. В uppercase ему соответствует диграф DZ (U+01F1), а в titlecase — диграф Dz (U+01F2). Есть ещё разные диграфы. Греческий же язык порадует вас приколами с ипогеграммени и просгеграммени (к счастью, в современных текстах такое редко встречается). В общем случае варианты uppercase и titlecase для символа могут быть различны, для них есть отдельные записи в стандарте Unicode.

    7. Хорошо, но, по крайней мере, результат преобразования регистра символа в uppercase или lowercase не зависит от его позиции в слове.

    Нет. К примеру, греческая заглавная сигма Σ (U+03A3) на конце слова превращается в строчную ς (U+03C2), а в середине — в σ (U+03C3).

    8. Ох, ну ладно, греческую сигму обработаем отдельно. Но во всяком случае, один и тот же символ в одной и той же позиции в тексте преобразуется одинаково.

    Нет. Скажем, в большинстве языков с латиницей строчный вариант для I (U+0049) — это i (U+0069), но не в турецком и азербайджанском. Там строчный вариант для I — это ı (U+0131), а заглавный вариант для i — это İ (U+0130). В Турции из-за этого в разнообразном софте порой наблюдаются фееричные баги. А если вам попадётся текст на литовском с проставленными ударениями, то, к примеру, заглавная Ì (U+00CC), которая превратится не в ì (U+00EC), а в (U+0069, U+0307, U+0300). В общем, результат преобразования зависит ещё и от языка. Большинство сложных случаев описано здесь.

    9. Какой ужас! Ну хорошо, пусть мы теперь правильно преобразуем в uppercase и lowercase. Сравнить два слова без учёта регистра не представляет проблем: переводим оба в lowercase и сравниваем.

    Здесь тоже немало подводных камней, которые вытекают из вышесказанного. Например, не сработает с немецкими straße и STRASSE (первое не изменится, второе превратится в strasse). Со многими другими описанными выше буквами тоже будут проблемы.

    10. М-да… Может, тогда всё в uppercase?

    И это не всегда сработает (хотя и гораздо чаще). Но, скажем, если вам попадётся запись STRAE (да, большая эсцет тоже есть в немецком и в Unicode), она не совпадёт со straße. Для сравнений буквы преобразуются по специальной таблице Unicode — CaseFolding, по ней и обе ß, и SS превратятся в ss.

    11. А-а-а, это ж капец какой-то!

    Вот тут согласен.

    Если у кого-то не отображаются какие-то символы, напишите мне личное сообщение, я заменю на картинку.
    Share post

    Comments 66

      0
      Сказав как неправильно, надо сказать и как правильно.
      Желательно сразу с рецептами на разных языках (программирования).
        +10
        Я сказал. Для titlecase не использовать uppercase, а взять соответсвующее поле Unicode. Например, здесь (titlecase — последняя колонка). Для сравнения не использовать не uppercase, не lowercase, а использовать CaseFolding. Не закладываться на длину строки после изменения регистра. А разные языки программирования — отдельная тема, всё сразу не влазит :-)
          0
          Для сравнения вроде можно нормализовать обе строки и сравнивать их хоть по байтам. Или нет? Я уже во всём начал сомневаться после вашей статьи.
            0
            Нормализация — это немного другое (например, разбор скомбинированного символа на компоненты или разбор лигатур). Она не для регистронезависимого сравнения, а для сравнения строк «по существу». В нормализованном виде заглавные и строчные буквы всё же отличаются.
        +5
        Моя любимая ссылка про Unicode (чтобы прочитать её как можно более полно я специально ставил шрифты):

        stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default

        Там очень много интересного, включая цитату «Code that assumes that ASCII is good enough for writing English properly is stupid, shortsighted, illiterate, broken, evil, and wrong. Off with their heads! If that seems too extreme, we can compromise: henceforth they may type only with their big toe from one foot (the rest still be ducktaped)».

          +5
          Парсер выкусил немного нетрадиционного юникода из коммента выше… На хабре тоже не любят unicode?

          ¡ƨdləɥ ƨᴉɥʇ ədoɥ puɐ ʻλɐp əɔᴉu ɐ əʌɐɥ ʻʞɔnl poo⅁
          –12
          Лучше бы с утечками памяти боролись)
            +15
            И не говорите, мерзкое население досталось планете Земля, навыдумывали тут всякого!
              +1
              Такова жизнь. Язык усложняется сам по себе — хотя бы из-за подражаний не то немцам, не то французам (например, «кронштейн» с его [э]). А вот в «шинели» или «Одессе» этого [э] никогда не было.

              Впрочем, язык умеет и упрощаться. Но усложнения, видимо, перевешивают. Приходится упрощать «по указке» (от упрощений страдают в первую очередь образованные люди). И последнее крупное изменение русской письменности произошло в удачнейший момент: наиболее реакционная часть народа смылась в Парижи, а ликбез дал стране новых грамотных.

              Приведу аналогию. Многие из нас программисты. Все мы ругаемся на плохой код, и любая действующая программа размером в несколько десятков тысяч строк будет смесью хорошего и плохого кода. Почему плохой не переписываем? А потому что в программе бывает сложная логика, которую написать и проверить тяжело, и, возможно, это время лучше потратить на другие фичи.

              Есть одно показательное упрощение, связанное с компами. Украина, 1994 год, надднепрянские диалекты в немилости. И вдруг объявляют: последовательность букв в украинском алфавите теперь как в русском, для упрощения компьютерной обработки.
              (Для тех, кто не знает: литературный украинский основан на надднепрянском диалекте. А примерно в это время начали появляться персонажи изх канадской диаспоры и заменять «аеропорт» на «летовище», хотя второе слово больше ассоциируется с травяным полем и «колбасой» на столбе, чем с той махиной, которой стал современный аэропорт.)
                0
                …А потому что в программе бывает сложная логика, которую написать и проверить тяжело, и, возможно, это время лучше потратить на другие фичи. Точно так же и в языке переходные процессы (если изменение крупное) продлятся очень долго, а в краткосрочной перспективе — только негатив.
                  +1
                  …точно так же «плохим кодом» будет «блоггер». Но это слово пока на правах неологизма, так что бери и исправляй, пока горячо. Правило таково: если суффикс «ер» можно оторвать, одна буква (есть «блог», поэтому в словари вошло «блогер»). Если оторвать нельзя, две буквы («роллер» есть, а «рола» нет). А «стример» с одной буквой потому, что и в английском одна буква.
              +2
              Век живи, век учись. Автор, огромное спасибо!
                +3
                Вывод: никогда не пишите свой собственный текстовый редактор, пусть мучаются программисты Майкрософт:)
                  +4
                  Titlecase не во всех языках — прямолинейное «первая буква заглавная, остальные строчные»: как минимум, в голландском диграф IJ (два символа, не лигатура) набирается всегда в одном регистре, и название Исландии пишется IJsland.

                  Подробнее: ru.wikipedia.org/wiki/IJ
                    0
                    Зато если использовать лигатуру (U+0133) и пользоваться полем titlecase, то всё нормально:
                    0133;LATIN SMALL LIGATURE IJ;Ll;0;L; 0069 006A;;;;N;LATIN SMALL LETTER I J;;0132;;0132
                    Для этого диграфа uppercase и titlecase совпадают (в отличие от dz), поэтому IJsland вполне получится.
                      +2
                      И останется только заставить пользователей набирать IJ спецсимволом, отсутствующим на клавиатуре, а не двумя отдельными символами, к которым все давно привыкли.
                        0
                        Неудобно, факт.
                          +3
                          Ещё замечу о немеханических, языкозависимых правилах капитализации: бывает, что служебные слова в начале предложения при переводе в titlecase игнорируются, как например в том же голландском (пример взят из Википедии): 's Avonds is zij nooit thuis. — cочетание 's avonds «вечером» в начале предложения записывается как 's Avonds.
                    0
                    Справедливости ради, как минимум поляки не пользуются диграфами для записи любых символов, в том числе и dz. Собственно, как в английском языке не употребляют отдельный знак для диграфа «ch» (даже не уверен, есть ли он в юникод). Правильнее сказать, что символ "dz" может встречаться в фонетическом алфавите, но там речи о uppercase/titlecase как раз никогда не идёт. Сомневаюсь, что в других перечисленных языках, кроме македонского (потому что у македонцев кириллица, и им один символ был бы удобнее) ситуация иная.
                      +1
                      Да, я понимаю, что лигатурами и диграфами мало кто пользуется, просто так набирая текст. Но они вполне могут встретиться в текстах онлайн-библиотек. Некоторые сайты могут принципиально заменять все dz на лигатуру из соображений визуальной красоты. Если вы, к примеру, занимаетесь индексацией контента такого сайта или библиотеки, вам об этом надо помнить. Конечно, всё это очень частные случаи, но с ними можно реально столкнуться.
                        0
                        Так-то оно могло бы быть и так, но статья про диграф dz в польской википедии (а википедисты — самые ярые поборники «правильных» юникодных символов, по-моему) употребляет сам диграф только как раз в фонетическом обозначении)
                          0
                          Это неудобно :) Да, в белорусской Википедии тарашкевицей, например, принят правильный типографский апостроф ’ вместо ', для чего мне, например, пришлось создавать свою раскладку клавиатуры с доп. знаками.
                            0
                            Я понимаю. Я просто хотел сказать, что если бы этот диграф имел хоть какое-нибудь употребление, уж в википедии, да ещё и в статье про него самого он был бы использован.
                              0
                              Та же проблема с украинским языком, причем в Юникоде существует несколько символов апострофа: U+02BC и U+2019, первый используется в доменах типа мʼякий.com.ua а второй — в типографских раскладках (у него проблемы с отображением и проверкой правописания).
                                0
                                … у первого символа проблемы …
                          +1
                          В белорусском тоже есть диграфы «дж» и «дз» (а в латинке ещё dž и dź соотв.), но, если мне не изменяет память, в киррилической литературе за последние 70-80 лет я эти диграфы, отпечатанные/набранные одним глифом, не встречал.

                          ru.wikipedia.org/wiki/%D0%91%D0%B5%D0%BB%D0%BE%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9_%D0%B0%D0%BB%D1%84%D0%B0%D0%B2%D0%B8%D1%82
                          +2
                          Нет. Скажем, в большинстве языков с латиницей строчный вариант для I (U+0049) — это i (U+0069), но не в турецком и азербайджанском. Там строчный вариант для I — это ı (U+0131), а заглавный вариант для i — это İ (U+0130). В Турции из-за этого в разнообразном софте порой наблюдаются фееричные баги.…
                          Не только в Турции, но в PHP, начиная с версии 5.3, когда используется местная локаль. Имена функций переводя в lowecase и некоторые функции, которые имеют заглавную «I» в названии, перестают находиться. Было весело, когда мы с этим столкнулись.
                            0
                            PHP в данном случае как раз и есть «разнообразный софт».

                            А какая у вас местная локаль?
                              0
                              Кажется соответствовала языку, выбранным пользователем.
                            0
                            У венгров, например, букв, основанных на одной только графеме «O», целых 4: без диакритики, с одинарным ауктом, с двойным акутом, с умлаутом. Каково вот им? :-)

                            Здесь пример предложения на венгерском, в котором представлено 3 таких из 4. Над тем примером есть ещё десятка три рукописных образцов письменности из разных стран мира (одни из самых причудливых по форме — из Бангладеш).

                            У Китая, Кореи, Эфиопии, в арабских странах, в индийской/бенгальской/мьянмарской/тайской/кхмерской письменности и т.п., в иврите и ещё ряде других письменностей не различаются строчные и заглавные символы, зато огромное количество своих нюансов и особенностей.

                            Так что латиница и кириллица — это ещё не самый капец :)
                              +2
                              > в ряде других письменностей не различаются строчные и заглавные символы
                              Собственно, у двух третей населения планеты в письменности нет заглавных и строчных букв.
                              Так что поднятая в топике проблема — чисто наша местечковая :)
                                0
                                Полноширинные и полуширинные символы катаканы тоже не очень способствую упрощению. Да и честно, даже не знаю как в CJK решаются проблемы такого вида:

                                vs
                                亻乃
                                Одно и то же это или нет?
                                  +3
                                  Полноширинные и полуширинные символы (не только каны) мэпятся друг на друга через поле декомпозиции. К примеру:
                                  FF71;HALFWIDTH KATAKANA LETTER A;Lo;0;L;<narrow> 30A2;;;;N;;;;;
                                  FF71 (ア) — узкий вариант символа 30A2 (ア).

                                  Декомпозиция иероглифов вряд ли входит в спектр задач Unicode.
                                    +4
                                    Как японцы, так и китайцы считают это совершенно разными вещами (не считая литспика, но он не слишком распространён: набирать так длинный текст — ад). Разницу иногда обыгрывают, например 糸色望 vs 絶望.

                                    Приведение к общему знаменателю традиционных, упрощённых, японских и местных иероглифов, тем более написанных не совсем правильно — другое дело.
                                      0
                                      Спасибо. Будем знать :)
                                0
                                Статьи из этой серии стоит переименовать в «почему стоит использовать стандартные функции самых проверенных и авторитетных (а если есть, то лучше — стандартных) библиотек и не выпендриваться».
                                  +1
                                  Дело не только в этом. Вон используют выше по тексту стандартную и проверенную функцию для lowercase в PHP, а она у турков переводит quit в QUİT, чего вы никак ожидать и протестировать не могли, если не знали об этой особенности заранее. Как раз посыл был в том, что иногда «использовать стандартное и не греть голову» не получается. Голову всё равно приходится греть.
                                    0
                                    (… не начинать холивар про PHP…… не начинать холивар про PHP...)

                                    По мне, так такие ситуации просто говорят о том, что стандартные библиотеки не справляются со своими непосредственными задачами, и выход — просто найти лучшую замену.
                                      0
                                      Я на PHP не пишу, но практически уверен, что этой функции можно указать локаль. Если просто задать в нужных местах не системную, а конкретную (English-US, там), то всё будет работать так, как вы ожидаете. Вопрос не в том, правильно ли работают библиотеки, а в том, правильно ли вы понимаете, как они работают. А для PHP это как раз плюс, что они интернационализацию поддерживают.
                                        0
                                        Ну тогда действительно, в мой коммент надо дописать «использовать библиотеки ПРАВИЛЬНО и ЧИТАТЬ ДОКУМЕНТАЦИЮ» ;)
                                          0
                                          Если бы все использовали библиотеки правильно и читали документацию… Насколько лучше был бы ПО. Но, не так всё радужно, вот совсем недавно возникала проблема с MySQL, где недочитали о том, что возвращает memcmp.
                                          Такие статьи просто обращают внимание тех разработчиков, которые ещё не вникли в проблему.
                                          0
                                          В функции нельзя, но прямо определять локаль для которой написан код в настроечном файле фреймворка/CMS — это в PHP просто необходимо.
                                        0
                                        Ну то есть мой пример с uppercase, а с lowercase наоборот — QUIT превращается в quıt.
                                      0
                                      Спасибо огромное! Интереснейшая статья.
                                        +3
                                        Вы сломали мне мозг и привели в ужас
                                          +1
                                          Unicode hell!
                                          Нет, это не нормально, с эти надо что-то делать. Unicode перестал быть удобным инструментом представления символов и превратился в адскую мешанину правил и исключений, напичканную ненужным хламом (вроде символов-картинок «на все случаи жизни»).
                                            +4
                                            Юникод должен сопровождаться библиотеками для выполнения всех этих преобразований. Как неотъемлемой частью стандарта.
                                              +3
                                              А какая может быть альтернатива?
                                              Стопицод несовместимых кодировок?

                                                +3
                                                Но Юникод адъ и мракъ потому, что адъ и мракъ языки. А поскольку Юникод покрывает важнейшие живые языки мира, весь этот ад приходится переносить и в Юникод.

                                                О картинках. Есть куча технических символов, которые гарантированно должны быть в Юникоде — например, «забой символа» (пятиугольник с крестом), «непараллельность» (острый угол с дугой угла) или «естественная шероховатость» (галочка с кружочком). Японцы в SMS общаются так называемыми «эмодзи» — картиночками, которые кодировались у ОпСоСов какими-то спецкодами. И пошло-поехало, даже не поймёшь сразу, где тут остановиться. Хорошо, что комитет Юникода непоколебим: товарных знаков в таблице быть не должно.
                                                +1
                                                Сначала нас временем напугали, теперь юникодом. Как жить, как жить…
                                                +1
                                                Кстати, вот эсцет в нижнем — ß — и верхнем — ẞ — регистре :-) STRAẞE.
                                                  0
                                                  Как-то оно чужеродно выглядит.
                                                  +2
                                                  Ещё, говорят, в текстах на литовском может встретиться заглавная Ì (U+00CC), которая превратится не в ì (U+00EC), а в (U+0069, U+0307, U+0300).
                                                  Я считаю литовский родным языком. Абсолютно не понимаю о чём речь в вышестоящем предложении.
                                                    0
                                                    Да, мне это тоже показалось странным. Пройдите по ссылке, там со слова Lithuanian почитайте. Может, я неправильно понял. Если что-то не так, я исправлю.
                                                      +2
                                                      А, так это о знаке ударения. Я думал, что это об акцентах, являющихся смысловой частью (в литовском, например, e, ė и ę – три разных буквы).

                                                      Тогда это предложение лишь о том, что в литовском языке, в противовес западной традиции, знак ударения ` не совмещается с точкой над i. И вместо западного
                                                      i + ` = ì,
                                                      в литовском
                                                      i + ` = ì̇
                                                      (обратите внимание, как, скорее всего, неправильно в вашем браузере рендерится последний символ).
                                                        +1
                                                        Спасибо за разъяснение, я немного поправил пост. Так лучше? Если начертание выглядит всё ещё неправильно, сделайте картинку с буквой и киньте на habrastorage, я вставлю.
                                                          0
                                                          Простите за задержку с ответом, но Хабр мне иногде не шлёт по почте ответы.
                                                          Не понимаю, почему.


                                                          Я не комитет по литовскому языку, но я однозначно считаю, что знак ударения должен быть над точкой, а не под ней (пример).

                                                          Тем не менее, я не думаю, что это имеет ценность для статьи. :)
                                                          0
                                                          Вебкит обрабатывает правильно.

                                                            0
                                                            Мрачно подозреваю, что зависит не только от браузера, но и от комплекта шрифтов, установленных в операционной системе.
                                                              0
                                                              Чуть выше я ответил, что, мне кажется, это неправильно.
                                                                0
                                                                Хм, получается, что это не ошибка рендера, а проблема самого юникода, в нём никак не указан приоритет что над чем.
                                                              0
                                                              В Опере нормально:
                                                                0
                                                                Чуть выше я ответил, что, мне кажется, это неправильно.
                                                          +3
                                                          Какой же хороший, годный русский язый.

                                                          Хотя у нас есть Ё, которая хоть и преобразуется однозначно, но может доставлять при сравнении слов, ибо человеческий фактор допускает её игнорирование. А простое приведение к е при сравнении может оказаться фэйлом. Кстати, у нас ещё есть неоднозначные американизмы и албанизмы.
                                                            0
                                                            Напомнило статью вро возможные ошибки связанные с функциями времени, по стилю написания.Там был указан оригинальный автор идеи такого изложения.Может, и тут стоит...?
                                                            Если конечно это не случайное совпадение)

                                                            Сама по себе интересная статья.Если не читать такого — баги потом ловить и ловить…

                                                            Only users with full accounts can post comments. Log in, please.