Текст выросший из моего доклада Unicode-ликбез на Санкт-Петербугском воркшопе SaintPerl 2011.
Вступление
Хочу предупредить, что этот рассказ не о интернационализации или локализации, хотя это и очень близкие темы. Еще я не буду рассказывать об истории кодировок (почти). Или о том, как надо правильно работать с Unicode в Perl или каком-либо другом языке. Я хочу рассказать о том, что просто необходимо разработчику знать о Unicode и почему.
объяснение названия
Ликбез (ликвидация безграмотности) – в переносном смысле — обучение неподготовленной аудитории базовым понятиям какой-либо науки, процесса или явления.
В нашем случае аудитория вполне подготовленная. И, казалось бы, что такого можно рассказать о Unicode разработчикам? Все наверняка слышали про кодировки (мы не из ASCII-мира), и разве Unicode – это не просто еще одна расширенная универсальная кодировка, призванная заменить все предыдущие?
Честно говоря, я примерно так и воспринимал Unicode. Потому что это просто - так думать! Но такое упрощенное понимание неверно и может приводить к неожиданным ошибкам в коде и непониманию происходящего при обработке текстов, вне зависимости от используемых инструментов: редактор ли это текстов или язык программирования.
Во многих случаях упрощение – это неизбежность, т.к. невозможно (и не нужно) знать все. Но для программиста незнание или непонимание Unicode – серьезный изъян в профессиональных навыках.
К необходимости знания и понимания Unicode я пришел, основываясь на собственном опыте и ликбез относится ко мне не в последнюю очередь.
Я разделил свой доклад на три части.
Часть 1 «Задачка»
В том, что Unicode - это не просто кодировка, я убедился, когда попробовал решить простую на первый взгляд задачку:
найти в слове
Îñţérñåţîöñåļîžåţîöñ
подстроку
Nation
Выглядит довольно просто. Да, в исходном слове (реально не существующем, кстати) есть какие-то крышечки, но наверняка их можно убрать и найти нужную подстроку регулярным выражением – ведь регекспы в Perl могут все? Но как это сделать, что и где искать в документации?
Для начала я задал вопрос на stackoverflow: http://stackoverflow.com/questions/7429964/how-to-match-string-with-diacritic-in-modern-perl/7440789#7440789, после чего приступил к самостоятельному исследованию.
Первый шаг был узнать, что хвостики и крышечки – это могут быть как диакритические знаки, так и знаки ударения (accent) и что-то еще. Затем, то что в регулярном выражении в Perl их можно искать/удалять с помощью \p{Marks}, предварительно выделив эти знаки (marks) из строки с помощью NFD-нормализации.
решение №1
1 2 3 4 5 6 7 8 9 10 | |
Довольно много новой информации и новых слов: “нормализация”, “диакритические знаки”. Я был доволен и опубликовал свое решение на stackoverflow. Но, когда, спустя некоторое время, я вернулся к своему посту (чтобы порадоваться растущему рейтингу), там меня озадачил такой комментарий:
This is the wrong way to do it. You need to use a UCA match at level 1
Итак, я узнал много новых слов при решении, но все равно сделал что-то неправильно! Хмм…
Я не имел никакого представления, что за зверь «UCA match at level 1» и в чем моя ошибка, но твердо решил, что должен с этим разобраться. Особенно, после того, как посмотрел в профиле кто такой tchrist. Это оказался, известный в Perl-коммьюнити разработчик Tom Christiansen, соавтор книг о Perl, один из разработчиков языка и (как оказалось позже) Unicode-эксперт.
В результате, когда я разобрался что к чему и исправил код, он стал выглядеть скучнее:
решение №2
1 2 3 4 5 6 7 8 9 10 | |
Кстати, вот еще одна цитата tchrist, найденная на stackoverflow:
«Code that assumes you can remove diacritics to get at base ASCII letters is evil, still, broken, brain-damaged, wrong, and justification for capital punishment»
рефлексия
В чем же была моя основная ошибка (или в чем заключалось невежество)? Основной ошибкой была попытка, не понимая сути, решить, как оказалось, нетривиальную задачу с помощью простого рецепта.
Рецепты – это неплохо. Это такая хорошая штука, которая позволят здорово экономить время. Например, готовка супа по рецепту отлично работает. И для многих случаев, с которыми приходится сталкиваться в программировании, рецепты тоже работают. Но это хорошо работает только для простых задач, а не для сложных! Например, для химических экспериментов необходимо понимание происходящих процессов и основ химии. Иначе может быть бум! (Придумайте еще примеры :) И для правильной обработки Unicode, также, просто необходимо знание основ.
Часть 2 «Ликбез»
Итак мы видим, что Unicode – это большая и сложная тема. И если мы хотим в ней разобраться, то с чего начать?
Первое, с чего я рекомендую начать изучение – это терминология. Важно начать именно с нее, так как знание терминологии помогает избежать путаницы и сохранить ясность мысли в процессе обучения. В сети очень много материалов о Unicode, и хотя в целом информация полезная и хорошая, но авторы очень часто небрежно используют термины (например, не делая различия между UCS-2 и UTF-16), в результате чего новичку (и не только!) очень легко запутаться или получить неверное представление.
Начем с того, что Unicode – это не кодировка или таблица символов. Это стандарт! В который входит, помимо таблиц символов и правил их кодирования, еще много-много чего.
Стандарт Unicode это:
- таблицы символов
- несколько механизмов кодирования
- формы нормализации текста
- правила casemapping и casefolding
- гибкие правила collation
- правила переносов для слов и разбиения строк
- специальные правила для регулярных выражений
- тысячи именованых свойств (properties)
- численные эквивалентности (U+216B XII)
- направление текста
- многое другое
Важный момент: Unicode не отвечает за то, как отрисовываются символы, т.е. за их рендеринг (это делают шрифты и конечное ПО).
В Unicode можно указывать на направленность текста (но поддержка вертикальных текстов пока только в планах).
Стандарт имеет массу документов, разные версии (1.0 – October 1991, … 5.0 – July 2006, 6.0 – October 2010), собственную терминологию, в том числе множество TLA или ТБС (Three-letter acronyms или Трех Буквенных Сокращений).
Некоторые TLA (ТБС) описываются ниже.
UCS, таблицы символов
UCS (ucs-2, ucs-4) – универсальный набор символов (universal character set) задаёт однозначное соответствие символов кодам — элементам кодового пространства, представляющим неотрицательные целые числа. Определен в стандарте ISO/IEC 10646 (включен в стандарт Unicode)
UCS стандартизирует набор абстрактных символов, на данный момент примерно 100 тысяч (потенциально до 1,114,112 code points), каждый из которых имеет уникальное имя и числовое значение - кодовую точку (code point). Cейчас это часть стандарта Unicode. Стандарт включает наборы USC-2 (устаревший) и UCS-4.
Кодовое пространство (codespace) – в Unicode разбито на 17 плоскостей по 216 (65536) code points (кодовых точек), набор всех code points: 0hex to 10FFFFhex
Нулевая плоскость (BMP/Basic Multilingual Plane) – в ней расположены символы наиболее употребительных письменностей. Т.е. это code points в диапазоне 0x0 - 0xFFFF (не все используются, верхняя часть диапазона зарезервирована)
Часто встречается путаница в терминах, из-за того, что во многих системах реализованых в доюникодную эпоху (Symbyan, NT, CD_ROM, Python 2.x, Java < 7, JavaScript), используется устаревший UCS-2 для кодирования символов. И поэтому закодированный в UCS-2 текст часто называют юникодом, хотя правильнее было бы называть UCS-2 подмножеством Unicode. (программы работающие с UCS-2 не умеют полноценно обрабатывать Unicode, так как ничего не знают о стандарте)
Самый известный пример – современные реализации языка JavaScript, где строки хранятся в UCS-2 (символы там всегда шириной 2 байта и это оговорено стандартом языка). Причем сами JS-движки обычно внутри себя используют UTF-16.
Именно UCS используется при численной записи Unicode символов, которую многие видели.
u (U+0075 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴜ) + ¨(U+0308 ᴄᴏᴍʙɪɴɪɴɢ ᴅɪᴀᴇʀᴇsɪs) => ü
эти же коды используются в современных ОС для ввода с клавиатуры
mac: unicode hex input (language & text), option + code
windows: alt + “+” + code
см. также:
- http://en.wikipedia.org/wiki/Unicode_input
- http://tlt.its.psu.edu/suggestions/international/accents/codemac.html
В вебе есть базы данных символов юникод, где можно посмотреть, что кроме числового UCS-представления у этих символов существует множество свойств и способов кодирования, определенных стандартом.
UCS4 - основа всех современных unicode-кодировок.
Character General Category – каждый code point относится к одной из основных категорий: буква, знак (см диакр. знаки), число(number), пунктуационный знак, символ, разделитель (см. http://www.unicode.org/versions/Unicode6.0.0/ch02.pdf)
UTF, кодировка символов
UTF (Unicode transformation format) – семейство кодировок, которое определяет машинное представление последовательности кодов UCS. Существует 3 кодировки: UTF-8, UTF-16, UTF-32 и 6 способов закодировать code point-ы: UTF-8 (UTF-EBCDIC), UTF-16BE, UTF-16LE, UTF-32BE, UTF-32LE
BE и LE расшифровываются как big-endian (BE), дословно «тупоконечный» – порядок байт от старшего к младшему, и little-endian (LE), дословно «остроконечный» – порядок байт от младшего к старшему.
Если нет BOM (о нем ниже), то стандартом предписывается Big Endian. (Выбор BE и LE определяется архитектурой компьютера, для большинства машин (x86-совместимых) – это LE)
Все эти кодировки, кроме UTF-32 - переменной ширины!
Еще раз: UCS - таблица(-ы) code point-ов в стандарте, UTF - способ кодирования их в поток байт, которые можно сохранить в памяти/на накопителе или передать по сети.
BOM (byte order mark) – позволет понять при чтении, какая последовательность байт используется (LE или BE).
BOM важен для UTF-16 и UTF-32. Для UTF-8 обычно не нужен, но есть в стандарте. Подразумевается, что встретив незнакомый BOM, UTF-16,32 программы cмогут понять, что в файле utf-8.
BOM не нужен для правильной обработки UTF-8, но иметь в виду его необходимо, т.к. те же MS-программы, живущие в мире нескольких Unicode-кодировок (UTF-16 и UTF-8 как минимум), соблюдают это соглашение и сохраняют файлы в UTF-8 с BOM (Notepad и др.)
UTF-16 – особенность кодировки в том, что символы не из 0-й plane задаются составными кодами 16+16=32 бита, которые называются суррогатными парами
Для суррогатных пар зарезервированны значения, не задействованные в основном codespace
Софт работающий с ucs2/utf-16 зачастую имеет ошибки в обработке суррогатов, так как редко встречаются и софт редко тестируется на совместимость с ними
UTF-16 используется в Win 2000, Vista, .NET, MacOS X Cocoa, Python (до недавних версий)
Еще немного о неотображаемых code point-ах (суррогатах). Те, что в BMP-диапазоне, называются “low surrogates”, те, что выше – “high surrogates”. И они используются только в UTF-16! (основное отличие UTF-16 от UCS-2) Пары суррогатных code point соответствуют реально существующим codepoint-ам, выходящим за base plane. По отдельности они не имеют смысла.
http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters
Не надо путать составные символы и суррогатные пары
UTF32 – кодировка фиксированной ширины, всегда 4 байта.
http://en.wikipedia.org/wiki/UTF-32/UCS-4
удобна в плане простоты, но редко используется из-за необходимости выделения 4 байт для каждого символа
UTF-8 – распространена в unix/web. Переменной ширины, кодовый символ может быть от 1 до 6 байт длиной, но не встречается больше 4 (2 оставлены про запас и врядли понадобятся в обозримом будущем).
Была изобретена 2 сентября 1992 года Кеном Томпсоном и Робом Пайком. Совместима с ASCII, если не выходить за границу 128 символов.
Самая “православная” кодировка. ☺
Хитро мапит кодепоинты в байты, ипользует часть битов для спец-целей (из первого байта можно узнать длину последовательности). Отсюда следует, что коды символов не совпадают с UCS.
- http://en.wikipedia.org/wiki/UTF-EBCDIC – редкая кодировка UTF-8 “для мейнфреймов”
Perl может внутри хранить строки как в UTF-8 так и в UTF-EBCDIC
Композиция символов – cимволы задающиеся несколькими кодами (составные символы) Для некоторых символов есть как композитные так и монолитные формы записи
Ё (U+0401) и Й (U+0419)
Е + ̈ (U+0415 U+0308)
И + ̆ (U+0418 U+0306)
термины, которые полезно знать и различать при изучении Unicode
Character – минимальный компонент письменного языка, имеющий семантическое значение. Ссылается на абстрактное значение (знак) или на символ
Символ (symbol) (из греч. σύμβολον) — знак, изображение какой-нибудь вещи или животного для означения качества предмета; условный знак каких-либо понятий, идей, явлений.
Графема (grapheme) (от греч. γράφω — пишу и -ема) — единица письменной речи (в алфавите — буква, в неалфавитных системах письма — слоговой знак, иероглиф, идеограмма и др.). Графема однозначно отличима от любой другой единицы этой же письменности.
Глиф (glypth) (греч. γλύφειν — резное письмо) — элемент письма, конкретное графическое представление графемы, иногда нескольких связанных графем (составной глиф), или только части графемы (например, диакритический знак).
И если графема - это единица текста, то глиф - единица графики.
Буква (Letter) – отдельный символ какого-либо алфавита, графема. Чаще всего буква соответствует звуку в устной речи, но это необязательно. У буквы может существовать несколько равнозначных вариантов написания, не меняющих её произношения и смысла.
Диакритические знаки (diacritical mark) - различные надстрочные, подстрочные, реже внутристрочные знаки
Идеограмма - письменный знак, обозначающий (в отличие от букв) не звуки какого-либо языка, а целое слово или корень.
что еще описывается стандартом Unicode
Нормализация. Поскольку одни и те же символы можно представить различными кодами, что иногда затрудняет обработку, существуют процессы нормализации, предназначенные для приведения текста к определённому стандартному виду.
Case folding – приведение символа или строки к заданному регистру (у символов может быть от одного до трех возможных регистров).
UCA (Unicode Collation Algorithm) – алгоритм сравнения двух строк, с учетом особенностей Unicode.
ICU (International Components for Unicode) – не входит в стандарт, но полезно знать. ICU – набор C-библиотек для разработки программ с поддержкой Unicode.
для Python есть pyICU, в Perl5 своя реализация Unicode, поддерживаемая perl5 porters
Вообще-то это не все термины и понятия, но уже не мало?
здесь можно сделать небольшую передышку
☕
Часть 3. «Домашняя работа»
Итак, мы теперь знаем, что Unicode это не просто расширенная таблица символов, не плаваем в терминах и понимаем, что Unicode – это совсем не просто. Что дальше?
Дальше нужно воспользоваться дополнительными материалами, поисковиком, любимым инструментарием и глубже изучить тему самостоятельно!
Может возникнуть вопрос “Зачем учить?”. Простой ответ заключается втом, что для квалифицированного программиста в настоящее время знание хотя бы основ Unicode и того как в используемом языке, ОС реализована поддержка его стандарта – обязательное условие.
Известная статья Джоела, на эту же тему: Абсолютный минимум, который каждый разработчик программного обеспечения обязательно должен знать о Unicode и наборах символов, которая была написана в 2001 г., то есть более 10 лет(!) назад.
Незнание или, что еще хуже, поверхностное знание, может и не помешать написать вроде как правильно работающий код, но чревато тем, что он окажется неправильным в самый неожиданный и неподходящий момент.
Никто не гарантирует, что вы не столкнетесь с неправильной реализацией Unicode в сторонних библиотеках, и в таком случае хорошее знание темы может очень помочь в понимании того, что происходит.
И еще одна причина. Когда человек чего-то не знает или недопонимает, он это может считать магическим и волшебным (примеры: молния, Unicode, сборщик мусора виртуальной машины). Цитата, иллюстрирующая пример такого отношения из рассылки Moscow.pm:
«Все-таки UTF8 в перле - это немного черная магия»
Чтобы это не казалось магией – необходимо знание!
Если вопроса “Зачем учить” не возникает, то я попробую ответить на вопрос: “Как учить?”.
Хорошая новость в том, что есть множество ресурсов с тоннами информации по Unicode. Презентации, статьи – все есть в сети. Не очень хорошая новость – информации очень и очень много. Что же делать чтобы в ней не утонуть?
Мой совет – обратиться к авторитетным источникам! (cтандарт слишком большой и читать его скучно)
Во первых, прочитайте статью Джоэла (ссылка выше). Установите Unicode-шрифты (доступны в репозитории и по ссылкам в конце).
Прочитайте обзорную статью в Википедии
Обратитесь к публикациям Tom Christiansen. Обязательно ознакомьтесь с его презентациями с OSCON 2011, если вы еще не успели это сделать.
- супер комментарий-рассказ tchrist о практиках работы с Unicode
- “Unicode Support Shootout: The Good, the Bad, & the (mostly) Ugly”
- “Perl Unicode Essentials”
- “Unicode in Perl Regexes”
)ригиналы находятся на странице автора http://training.perl.com/OSCON2011/index.html но она, к сожалению, чаще не работает, чем работает ☠ ☠ ☠)
Если вы Perl-разработчик, то прочитайте документацию
Обратите внимание на ссылки внизу.
Ресурсы
- Unicode nearing 50% of the web (2010) -Unicode over 60 percent of the web (2012)
- Avoiding encoding headache (in Perl)
- shapecatcher.com – распознавание Unicode символов по рисунку
- Онлайн база Unicode:
- http://www.unicode.org/charts/charindex.html
- шрифты:
документация и технические подробности
- Документация Perl по Unicode
- Casefolding: http://perldoc.perl.org/5.14.0/Unicode/UCD.html
- UNICODE COLLATION ALGORITHM http://www.unicode.org/reports/tr10/
- набор функций Glib для обработки Unicode http://docstore.mik.ua/manuals/ru/glib_api/glib-Unicode-Manipulation.html
Wikipedia и официальная справка
- http://ru.wikipedia.org/wiki/Юникод
- http://en.wikipedia.org/wiki/UTF-8
- http://en.wikipedia.org/wiki/UTF-EBCDIC
- http://en.wikipedia.org/wiki/UTF-16
- http://en.wikipedia.org/wiki/UTF-32/UCS-4
- http://en.wikipedia.org/wiki/Byte_order_mark
- http://en.wikipedia.org/wiki/Comparison_of_Unicode_encodings
- http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters
- http://en.wikipedia.org/wiki/Canonical_equivalence
- http://www.w3.org/International/wiki/Case_folding
- http://www.unicode.org
- Официальный глоссарий www.unicode.org/glossary/
- FAQ по BOM
- про нормализацию:
статьи, публикации в блогах
- What Every Programmer Absolutely, Positively Needs To Know About Encodings And Character Sets To Work With Text
- PHP 6 не будет, не осилили (habr)
- Регистр в Unicode - это непросто
- Javascript and Unicode
- Как сортировать Unicode-строки в Perl (en)
- Применение нормализации при использовании split в Perl (en) “JavaScript strings outside of the BMP”
- http://lionet.livejournal.com/tag/unicode
- UTF-8 and Unicode FAQ for Unix/Linux
материалы конференций
- доклад Монса на YAPC::Russia May Perl 2008: часть 1, часть 2, часть 3
- рассказ Олега Алистратова на PerlMova - 2010 http://www.slideshare.net/alistratov/perl-unicode
- Unicode In Python, Completely Demystified
разное
- “UTF-8 history” from Rob ‘Commander’ Pike
- “Use ICU for Unicode in Perl?”
- http://www.unicode.org/announcements/quotations.html
- Three-letter_acronym
- Мой вопрос на StackOverflow
- JSON-редактор http://jsonviewer.stack.hu/
Сайт с большой подборкой информации о локализации и интернационализации: http://www.i18nguy.com
- статья про суррогаты: http://www.i18nguy.com/surrogates.html
- юмор: http://www.i18nguy.com/humor/unicode-humor.html
тест правильной работы c UTF-8 в JSON-парзере: https://metacpan.org/source/MLEHMANN/JSON-XS-2.32/t/01_utf8.t
книги
для углубленного изучения, если есть интерес (спасибо Dmitry Arsentiev за ссылки):