Unicode Basics
Unicode is a standard that precisely defines a character set as well as a small number of encodings for it. It enables you to handle text in any language efficiently. It allows a single application executable to work for a global audience. ICU, like Java™, Microsoft® Windows NT™, Windows™ 2000 and other modern systems, provides Internationalization solutions based on Unicode.
This chapter is intended as an introduction to codepages in general and Unicode in particular. For further information, see:
Go to the online ICU demos to see how a Unicode-based server application can handle text in many languages and many encodings.
Traditional Character Sets and Unicode
Representing text-format data in computers is a matter of defining a set of characters and assigning each of them a number and a bit representation. Underlying this basic idea are three related concepts:
A character set or repertoire is an unordered collection of characters that can be represented by numeric values.
A coded character set maps characters from a character set or repertoire to numeric values.
A character encoding scheme defines the representation of numeric values from one or more coded character sets in bits and bytes.
For simple encodings such as ASCII, the last two concepts are basically the same: ASCII assigns 128 characters and control codes to consecutive numbers from 0 to 127. These characters and control codes are encoded as simple, unsigned, binary integers. Therefore, ASCII is both a coded character set and a character encoding scheme.
ASCII only encodes 128 characters, 33 of which are control codes rather than graphic, displayable characters. It was designed to represent English-language text for an American user base, and is therefore insufficient for representing text in almost any language other than American English. In fact, most traditional encodings were limited to one or few languages and scripts.
ASCII offered a natural way to extend it: Designed in the 1960’s to work in systems with 7-bit bytes while most computers and Internet protocols since the 1970’s use 8-bit bytes, the extra bit allowed another 128 byte values to represent more characters. Various encodings were developed that supported different languages. Some of these were based on ASCII, others were not.
Languages such as Japanese need to encode considerably more than 256 characters. Various encoding schemes enable large character sets with thousands or tens of thousands of characters to be represented. Most of those encodings are still byte-based, which means that many characters require two or more bytes of storage space. A process must be developed to interpret some byte values.
Various character sets and encoding schemes have been developed independently, cover only one or few languages each, and are incompatible. This makes it very difficult for a single system to handle text in more than one language at a time, and especially difficult to do so in a way that is interoperable across different systems.
Generally, the minimum requirement for the interoperable exchange of text data is that the encoding (character set & encoding scheme) must be properly specified in the document and in the protocol. For example, email/SMTP and HTML/HTTP provide the means to specify the “charset”, as it is called in Internet standards. However, very often the encoding is not specified, specified incorrectly, or the sender and receiver disagree on its implementation.
The ISO 2022 encoding scheme was created to store text in many different languages. It allows other encodings to be embedded by first announcing them and then switching between them. Full support for all features and possible encodings with ISO 2022 requires complicated processing and the need to support many encodings. For East Asian languages, subsets were developed that cover only one language or a few at a time, but they are much more manageable. ISO 2022 is not well-suited for use in internal processing. It is designed for data exchange.
Glyphs versus Characters
Programmers often need to distinguish between characters and glyphs. A character is the smallest semantic unit in a writing system. It is an abstract concept such as the letter A or the exclamation point. A glyph is the visual presentation of one or more characters, and is often dependent on adjacent characters.
There is not always a one-to-one mapping between characters and glyphs. In many languages (Arabic is a prime example), the way a character looks depends heavily on the surrounding characters. Standard printed Arabic has as many as four different printed representations (glyphs) for every letter of the alphabet. In many languages, two or more letters may combine together into a single glyph (called a ligature), or a single character might be displayed with more than one glyph.
Despite the different visual variants of a particular letter, it still retains its identity. For example, the Arabic letter heh has four different visual representations in common use. Whichever one is used, it still keeps its identity as the letter heh. It is this identity that Unicode encodes, not the visual representation. This also cuts down on the number of independent character values required.
Overview of Unicode
Unicode was developed as a single-coded character set that contains support for all languages in the world. The first version of Unicode used 16-bit numbers, which allowed for encoding 65,536 characters without complicated multibyte schemes. With the inclusion of more characters, and following implementation needs of many different platforms, Unicode was extended to allow more than one million characters. Several other encoding schemes were added. This introduced more complexity into the Unicode standard, but far less than managing a large number of different encodings.
Starting with Unicode 2.0 (published in 1996), the Unicode standard began assigning numbers from 0 to 10ffff16,which requires 21 bits but does not use them completely. This gives more than enough room for all written languages in the world. The original repertoire covered all major languages commonly used in computing. Unicode continues to grow, and it includes more scripts.
The design of Unicode differs in several ways from traditional character sets and encoding schemes:
Its repertoire enables users to include text efficiently in almost all languages within a single document.
It can be encoded in a byte-based way with one or more bytes per character, but the default encoding scheme uses 16-bit units that allow much simpler processing for all common characters.
Many characters, such as letters with accents and umlauts, can be combined from the base character and accent or umlaut modifiers. This combining reduces the number of different characters that need to be encoded separately. “Precomposed” variants for characters that existed in common character sets at the time were included for compatibility.
Characters and their usage are well-defined and described. While traditional character sets typically only provide the name or a picture of a character and its number and byte encoding, Unicode has a comprehensive database of properties available for download. It also defines a number of processes and algorithms for dealing with many aspects of text processing to make it more interoperable.
The early inclusion of all characters of commonly used character sets makes Unicode a useful “pivot” point for converting between traditional character sets, and makes it feasible to process non-Unicode text by first converting into Unicode, process the text, and convert it back to the original encoding without loss of data.
The first 128 Unicode code point values are assigned to the same characters as in US-ASCII. For example, the same number is assigned to the same character. The same is true for the first 256 code point values of Unicode compared to ISO 8859-1 (Latin-1) which itself is a direct superset of US-ASCII. This makes it easy to adapt many applications to Unicode because the numbers for many syntactically important characters are the same.
Character Encoding Forms and Schemes for Unicode
Unicode assigns characters a number from 0 to 10FFFF16, giving enough elbow room to allow for unambiguous encoding of every character in common use. Such a character number is called a “code point”.
Unicode code points are just non-negative integer numbers in a certain range. They do not have an implicit binary representation or a width of 21 or 32 bits. Binary representation and unit widths are defined for encoding forms.
For internal processing, the standard defines three encoding forms, and for file storage and protocols, some of these encoding forms have encoding schemes that differ in their byte ordering. The difference between an encoding form and an encoding scheme is that an encoding form maps the character set codes to values that fit into internal data types (like a short in C), while an encoding scheme maps to bits and bytes. For traditional encodings, they are the same since the encoding forms already map to bytes.
The different Unicode encoding forms are optimized for a variety of different uses:
UTF-16, the default encoding form, maps a character code point to either one or two 16-bit integers.
UTF-8 is a byte-based encoding that offers backwards compatibility with ASCII-based, byte-oriented APIs and protocols. A character is stored with 1, 2, 3, or 4 bytes.
UTF-32 is the simplest, but most memory-intensive encoding form: It uses one 32-bit integer per Unicode character.
SCSU is an encoding scheme that provides a simple compression of Unicode text. It is designed only for input and output, not for internal use.
ICU uses UTF-16 internally. ICU 2.0 fully supports supplementary characters (with code points 1000016..10FFFF16). Older versions of ICU provided only partial support for supplementary characters.
For input/output, character encoding schemes define a byte serialization of text. UTF-8 is itself both an encoding form, and an encoding scheme because it is byte-based. For each of UTF-16 and UTF-32, there are two variants defined: one that serializes the code units in big-endian byte order (most significant byte first), and one that serializes the code units in little-endian byte order (least significant byte first). The corresponding encoding schemes are called UTF-16BE, UTF-16LE, UTF-32BE, and UTF-32LE.
The names “UTF-16” and “UTF-32” are ambiguous. Depending on context, they refer either to character encoding forms where 16/32-bit words are processed and are naturally stored in the platform endianness, or they refer to the IANA-registered charset names, i.e., to character encoding schemes or byte serializations. In addition to simple byte serialization, the charsets with these names also use optional Byte Order Marks (see Serialized Formats below).
Overview of UTF-16
The default encoding form of the Unicode Standard uses 16-bit code units. Code point values for the most common characters are in the range of 0 to FFFF16 and are encoded with just one 16-bit unit of the same value. Code points from 1000016 to 10FFFF16 are encoded with two code units that are often called “surrogates”, and they are called a “surrogate pair” when, together, they correctly encode one Unicode character. The first surrogate in a pair must be in the range D80016 to DBFF16, and the second one must be in the range DC0016 to DFFF16. Every Unicode code point has only one possible UTF-16 encoding with either one code unit that is not a surrogate or with a correct pair of surrogates. The code point values D80016 to DFFF16 are set aside just for this mechanism and will never, by themselves, be assigned any characters.
Most commonly used characters have code points below FFFF16, but Unicode 3.1 assigns more than 40,000 supplementary characters that make use of surrogate pairs in UTF-16.
Note that comparing UTF-16 strings lexically based on their 16-bit code units does not result in the same order as comparing the code points. This is not usually an issue since only rarely-used characters are affected. Most processes do not rely on the same results in such comparisons. Where necessary, a simple modification to a string comparison can be performed that still allows efficient code unit-based comparisons and makes them compatible with code point comparisons. ICU has C and C++ API functions for this.
Overview of UTF-8
To meet the requirements of byte-oriented, ASCII-based systems, the Unicode Standard defines UTF-8. UTF-8 is a variable-length, byte-based encoding that preserves ASCII transparency.
UTF-8 maintains transparency for all the ASCII code values (0..127). These values do not appear in any byte of a transformed result except as the direct representation of the ASCII values. Thus, ASCII text is also UTF-8 text.
Characteristics of UTF-8 include:
Unicode code points 0 to 7F16 are each encoded with a single byte of the same value. Therefore, ASCII characters take up 50% less space with UTF-8 encoding than with UTF-16.
All other code points are encoded with multibyte sequences, with the first byte (lead byte) indicating the number of bytes that follow (trail bytes). This results in very efficient parsing. The lead bytes are in the range c016 to fd16, the trail bytes are in the range 8016 to bf16. The byte values fe16 and FF16 are never used.
UTF-8 is relatively compact and resource conservative in its use of the bytes required for encoding text in European scripts, but uses 50% more space than UTF-16 for East Asian text. Code points up to 7FF16 take up two bytes, code points up to FFFF16 take up three (50% more memory than UTF-16), and all others four.
Binary comparisons of UTF-8 strings based on their bytes result in the same order as comparing code point values.
Overview of UTF-32
The UTF-32 encoding form always uses one single 32-bit integer per Unicode code point. This results in a very simple encoding.
The drawback is its memory consumption: Since code point values use only 21 bits, one-third of the memory is always unused, and since most commonly used characters have code point values of up to FFFF16, they take up only one 16-bit unit in UTF-16 (50% less) and up to three bytes in UTF-8 (25% less).
UTF-32 is mainly used in APIs that are defined with the same data type for both code points and code units. Modern versions of the C standard library that support Unicode use a 32-bit wchar_t with UTF-32 semantics.
Overview of SCSU
SCSU (Standard Compression Scheme for Unicode) is designed to reduce the size of Unicode text for both input and output. It is a simple compression that transforms the text into a byte stream. It typically uses one byte per character in small scripts, and two bytes per character in large, East Asian scripts.
It is usually shorter than any of the UTFs. However, SCSU is stateful, which makes it unsuitable for internal processing. It also uses all possible byte values, which might require additional processing for protocols such as SMTP (email).
Other Unicode Encodings
Other Unicode encodings have been developed over time for various purposes. Most of them are implemented in ICU, see source/data/mappings/convrtrs.txt
BOCU-1: Binary-Ordered Compression of Unicode An encoding of Unicode that is about as compact as SCSU but has a much smaller amount of state. Unlike SCSU, it preserves code point order and can be used in 8bit emails without a transfer encoding. BOCU-1 does not preserve ASCII characters in ASCII-readable form. See Unicode Technical Note #6 .
UTF-7: Designed for 7bit emails; simple and not very compact. Since email systems have been 8-bit safe for several years, UTF-7 is not necessary any more and not recommended. Most ASCII characters are readable, others are base64-encoded. See RFC 2152 .
IMAP-mailbox-name: A variant of UTF-7 that is suitable for expressing Unicode strings as ASCII characters for Unix filenames. The name “IMAP-mailbox-name” is specific to ICU! See RFC 2060 INTERNET MESSAGE ACCESS PROTOCOL — VERSION 4rev1 section 5.1.3. Mailbox International Naming Convention.
UTF-EBCDIC: An EBCDIC-friendly encoding that is similar to UTF-8. See Unicode Technical Report #16 . As of ICU 2.6, UTF-EBCDIC is not implemented in ICU.
CESU-8: Compatibility Encoding Scheme for UTF-16: 8-Bit An incompatible variant of UTF-8 that preserves 16-bit-Unicode (UTF-16) string order instead of code point order. Not for open interchange. See Unicode Technical Report #26 .
Programming using UTFs
Programming using any of the UTFs is much more straightforward than with traditional multi-byte character encodings, even though UTF-8 and UTF-16 are also variable-width encodings.
Within each Unicode encoding form, the code unit values for singletons (code units that alone encode characters), lead units, and for trailing units are all disjointed. This has crucial implications for implementations. The following lists these implications:
Determines the number of units for one code point using the lead unit. This is especially important for UTF-8, where there can be up to 4 bytes per character.
Determines boundaries. If ICU users randomly access text, you can always determine the nearest code-point boundaries with a small number of machine instructions.
Does not have any overlap. If ICU users search for string A in string B, you never get a false match on code points. Users do not need to convert to code points for string searching. False matches never occurs since the end of one sequence is never the same as the start of another sequence. Overlap is one of the biggest problems with common multi-byte encodings like Shift-JIS. All the UTFs avoid this problem.
Uses simple iteration. Getting the next or previous code point is straightforward, and only takes a small number of machine instructions.
Can use UTF-16 encoding, which is actually fully symmetric. ICU users can determine from any single code unit whether it is the first, last, or only one for a code point. Moving (iterating) in either direction through UTF-16 text is equally fast and efficient.
Uses slow indexing by code points. This indexing procedure is a disadvantage of all variable-width encodings. Except in UTF-32, it is inefficient to find code unit boundaries corresponding to the nth code point or to find the code point offset containing the nth code unit. Both involve scanning from the start of the text or from a last known boundary. ICU, like most common APIs, always indexes by code units. It counts code units and not code points.
Conversion between different UTFs is very fast. Unlike converting to and from legacy encodings like Latin-2, conversion between UTFs does not require table look-ups.
ICU provides two basic data type definitions for Unicode. UChar32 is a 32-bit type for code points, and used for single Unicode characters. It may be signed or unsigned. It is the same as wchar_t if it is 32 bits wide. UChar is an unsigned 16-bit integer for UTF-16 code units. It is the base type for strings ( UChar * ), and it is the same as wchar_t if it is 16 bits wide.
Some higher-level APIs, used especially for formatting, use characters closer to a representation for a glyph. Such “user characters” are also called “graphemes” or “grapheme clusters” and require strings so that combining sequences can be included.
Serialized Formats
In files, input, output, and network protocols, text must be accompanied by the specification of its character encoding scheme for a client to be able to interpret it correctly. (This is called a “charset” in Internet protocols.) However, an encoding scheme specification is not necessary if the text is only used within a single platform, protocol, or application where it is otherwise clear what the encoding is. (The language and text directionality should usually be specified to enable spell checking, text-to-speech transformation, etc.)
The discussion of encoding specifications in this section applies to standard Internet protocols where charset name strings are used. Other protocols may use numeric encoding identifiers and assign different semantics to those identifiers than Internet protocols.
Typically, the encoding specification is done in a protocol- and document format-dependent way. However, the Unicode standard offers a mechanism for tagging text files with a “signature” for cases where protocols do not identify character encoding schemes.
The character ZERO WIDTH NO-BREAK SPACE (FEFF16) can be used as a signature by prepending it to a file or stream. The alternative function of U+FEFF as a format control character has been copied to U+2060 WORD JOINER, and U+FEFF should only be used for Unicode signatures.
The different character encoding schemes generate different, distinct byte sequences for U+FEFF:
UTF-32BE: 00 00 FE FF
UTF-32LE: FF FE 00 00
UTF-7: 2B 2F 76 ( 38 | 39 | 2B | 2F ) |
ICU provides the function ucnv_detectUnicodeSignature() for Unicode signature detection.
There is no signature for CESU-8 separate from the one for UTF-8. UTF-8 and CESU-8 encode U+FEFF and in fact all BMP code points with the same bytes. The opportunity for misidentification of one as the other is one of the reasons why CESU-8 should only be used in limited, closed, specific environments.
In UTF-16 and UTF-32, where the signature also distinguishes between big-endian and little-endian byte orders, it is also called a byte order mark (BOM). The signature works for UTF-16 since the code point that has the byte-swapped encoding, FFFE16, will never be a valid Unicode character. (It is a “non-character” code point.) In Internet protocols, if an encoding specification of “UTF-16” or “UTF-32” is used, it is expected that there is a signature byte sequence (BOM) that identifies the byte ordering, which is not the case for the encoding scheme/charset names with “BE” or “LE”.
If text is specified to be encoded in the UTF-16 or UTF-32 charset and does not begin with a BOM, then it must be interpreted as UTF-16BE or UTF-32BE, respectively.
A signature is not part of the content, and must be stripped when processing. For example, blindly concatenating two files will give an incorrect result.
If a signature was detected, then the signature “character” U+FEFF should be removed from the Unicode stream after conversion. Removing the signature bytes before conversion could cause the conversion to fail for stateful encodings like BOCU-1 and UTF-7.
Whether a signature is to be recognized or not depends on the protocol or application.
If a protocol specifies a charset name, then the byte stream must be interpreted according to how that name is defined. Only the “UTF-16” and “UTF-32” names include recognition of the byte order marks that are specific to them (and the ICU converters for these names do this automatically). None of the other Unicode charsets are defined to include any signature/BOM handling.
If no charset name is provided, for example for text files in most filesystems, then applications must usually rely on heuristics to determine the file encoding. Many document formats contain an embedded or implicit encoding declaration, but for plain text files it is reasonable to use Unicode signatures as simple and reliable heuristics. This is especially common on Windows systems. However, some tools for plain text file handling (e.g., many Unix command line tools) are not prepared for Unicode signatures.
The Unicode Standard Is An Industry Standard
The Unicode standard is an industry standard and parallels ISO 10646-1. Around 1993, these two standards were effectively merged into the same character set standard. Both standards have the same character repertoire and the same encoding forms and schemes.
One difference used to be that the ISO standard defined code point values to be from 0 to 7FFFFFFF16, not just up to 10FFFF16. The ISO work group decided to add an amendment to the standard. The amendment removes this difference by declaring that no characters will ever be assigned code points above 10FFFF16. The main reason for the ISO work group’s decision is interoperability between the UTFs. UTF-16 can not encode any code points above this limit.
This means that the code point space for both Unicode and ISO 10646 is now the same! These changes to ISO 10646 have been made recently and should be complete in the edition ISO 10646:2003 which also combines all parts of the standard into one.
The former, larger code space is the reason why the ISO definition of UTF-8 specifies sequences of five and six bytes to cover that whole range.
Another difference is that the ISO standard defines encoding forms “UCS-4” and “UCS-2”. UCS-4 is essentially UTF-32 with a theoretical upper limit of 7FFFFFFF16, using 31 out of the 32 bits. However, in practice, the ISO committee has accepted that the characters above 10FFFF will not be encoded, so there is essentially no difference between the forms. The “4” stands for “four-byte form”.
UCS-2 is a subset of UTF-16 that is limited to code points from 0 to FFFF, excluding the surrogate code points. Thus, it cannot represent the characters with code points above FFFF (called supplementary characters).
There is no conversion necessary between UCS-2 and UTF-16. The difference is only in the interpretation of surrogates.
The standards differ in what kind of information they provide: The Unicode standard provides more character properties and describes algorithms etc., while the ISO standard defines collections, subsets and similar.
The standards are synchronized, and the respective committees work together to add new characters and assign code point values.
1. Введение в Unicode (опять?)
Всем здравствуйте, меня зовут Антон, и этой статьей я открываю новый цикл публикаций про Unicode. Сразу может возникнуть вопрос — зачем? Их же и так море?
На Хабре, как и вообще в русскоязычном сегменте Интернета, в основном можно найти обзорные статьи, дающие лишь общее представление о Юникоде, но о том, как с ним работать — информации крайне мало. Сами же его разработчики, Unicode Consortium, предоставляют довольно подробную… но очень объемную документацию, которую при этом мало просто прочитать — для полного понимания много чего в ней стоит прокодить.
Мы, разработчики, очень любим изобретать велосипеды и заглядывать в мануалы в самый последний момент. Полагаемся на сторонние библиотеки, зачастую не до конца понимая, что там под происходит под капотом.
В работе с текстом, таких велосипедов, к сожалению, наделано много. Наверняка вы сталкивались с чем‑то подобным:
поиск не находит казалось бы одни и те же слова.
в поле ввода безобразно работает ограничение на количество введённых символов, в зависимости от выбранного языка. классика.
куда‑то пропадают эмодзи в базе данных, в текстах всплывают ромбики‑квадратики… перечислять можно бесконечно.
Что в статьях:
Основная мысль — нет ничего лучше, чем разобрать что‑то на практике. Какие‑то темы будут упомянуты вскользь (например, из кодировок мы плотно затронем лишь UTF-8), какие‑то темы оставим сильно на потом (например, системы ввода), а про что‑то вообще забудем (например, про историю — на Хабре есть что почитать на эту тему, например, тут или тут).
Итак, ближайший план:
разберём, что из себя представляет Unicode, его символы и их свойства, кодировки. Напишем валидацию строк UTF-8, научимся преобразовывать запись символа в кодировке UTF-8 в код символа (кодпоинт) Unicode и обратно.
выясним, что представляет собой нормализация текста, зачем она нужна и где её применять. расскажу про каноническую эквивалентность символов и эквивалентность совместимости, разберём как делается декомпозиция/композиция, быстрые проверки, под конец — напишем реализацию алгоритмов нормализации.
узнаем, что такое сопоставление (collation) строк, алгоритм сопоставления (UCA), что такое DUCET и CLDR; уровни и веса сопоставлений, различные подходы к взвешиванию весов, немного затронем тему баз данных, и, наконец, напишем пример.
Код примеров — на языке Rust, все примеры будут выложены на гитхаб.
небольшой оффтопик
по мере продвижения в статьях, я буду использовать термины, которые в различных источниках в силу не менее различных причин (например, переводов) могут быть обозначены по‑разному. использовать буду те, которые считаю наиболее точными, но если я где‑то не прав — пожалуйста, не стесняйтесь мне на это указать.
Что такое Unicode
Юникод — стандарт кодирования символов, который включает в себя символы огромного количества письменностей, различные графические символы, эмодзи, управляющие символы.
На момент написания этой статьи, актуальная версия Unicode — 15.0.0, и по мере развития стандарта было сломано немало копий в обсуждениях — начиная с избыточности (стоит‑ли включать в него мёртвые языки), продолжая вопросами организации размещения кодпоинтов в таблице, и заканчивая вечным обсуждением эмодзи.
✍️ кодпоинт — code point, кодовая точка — в контексте кодирования символов — числовое значение, соответствующее определенному символу.
Кодпоинты Unicode обозначаются как U+xxxx , где xxxx — шестнадцатеричный код символа. По префиксу U+ мы можем определить, что имеется ввиду именно кодпоинт Unicode.
Плоскости Unicode
Unicode — это в первую очередь про символы. Кодпоинты символов Unicode принадлежат диапазону от U+0000 до U+10FFFF включительно.
Этот диапазон разбит на крупные части, которые называются плоскостями (planes) — непрерывные диапазоны, состоящими из (65 536) последовательно расположенных кодпоинтов.
Плоскости, в свою очередь, дробятся на блоки — диапазоны кодпоинтов, сгруппированных по назначению.
аббр.
диапазон
название
BMP
Основная многоязычная плоскость
Basic Multilingual Plane
SMP
Дополнительная многоязычная плоскость
Supplementary Multilingual Plane
SIP
Дополнительная идеографическая плоскость
Supplementary Ideographic Plane
TIP
Третичная идеографическая плоскость
Tertiary Ideographic Plane
SSP
Специализированная дополнительная плоскость
Supplementary Special‑purpose Plane
SPUA‑A/B
Дополнительные области для частного использования — A/B
Supplementary Private Use Area, SPUA‑A/B
BMP — основной диапазон символов, который встретится нам в 99% случаев.
Заметим и запомним — для записи значения кодпоинта из плоскости BMP достаточно 16 бит.
Некоторые блоки плоскости (и пара особенных символов), о которых стоит упомянуть:
C0 Controls and Basic Latin
128 символов таблицы ASCII, единственный блок в Unicode, на кодирование символов которого достаточно 1 байта. Включает в себя управляющие коды C0 и символ удаления.
С1 Controls and Latin-1 Supplement
Дополнения к латинице, включает в себя дополнительные управляющие символы (C1 Control).
Суррогатные пары
Используется только в UTF-16, по сути — легаси. С помощью суррогатной пары в UTF-16 можно составить код символа, выходящий за пределы U+FFFF . А вот в кодировках, отличных от UTF-16, использование символов этого диапазона считается ошибкой, и добавляет нам пару лишних сравнений для каждого символа при валидации.
ZERO WIDTH NO‑BREAK SPACE
Пусть вас не вводит в заблуждение то, что символ находится в блоке Arabic Presentation Forms‑B. К арабскому он отношения не имеет. Зато непонимание, зачем он нужен и как его обрабатывать, привело к куче ошибок в различного рода программах. Именно этот символ используется в качестве Byte Order Mark (маркер последовательности байтов), или сокращенно — BOM.
� — REPLACEMENT CHARACTER
тот самый символ, который наверняка встречался хоть раз каждому. Этим символом заменяется неподдерживаемый символ Unicode / невалидная последовательность байт в кодировке.
✍️ Совет из разряда «Хозяйке на заметку»
Обратим внимание, что, как указано выше, первые 2 блока включают в себя управляющие символы (Control characters): U+0000 — U+001F , U+007F , U+0080 — U+009F . Хоть это и не относится к теме статьи, но будет полезным напомнить себе, что их стоит при необходимости экранировать.
SMP — преимущественно неиспользуемые языки. Однако, есть одна причина, по которой символы этой плоскости довольно‑таки часто встречаются: эмодзи.
SIP, TIP, SSP — устаревшие / редко используемые символы CJK, символы специального назначения.
⛩ CJK расшифровывается как Chinese Japanese Korean. Символы CJK имеют ряд особенностей, с первыми из которых мы столкнемся, когда доберёмся до нормализации.
SPUA‑A/B — Область частного использования (название которой говорит само за себя) можно использовать по собственному усмотрению. А можно и не использовать, что чаще всего и происходит.
Главное здесь тот факт, что авторы Unicode обязуются не задействовать Private Use при обновлениях стандарта; таким образом, значение любого уже определённого в Unicode кодпоинта занимает максимум 20 бит (так как последний кодпоинт перед началом области, U+EFFFF , требует именно столько).
Символы Unicode
До Unicode мы могли быть уверены, что за один графический символ отвечает один кодпоинт. Однако с его появлением ситуация изменилась, и та же буква Й может быть представлена как:
отдельный кодпоинт U+0419 CYRILLIC CAPITAL LETTER SHORT I ,
буква И, U+0418 CYRILLIC CAPITAL LETTER I в сочетании с бреве, U+0306 COMBINING BREVE .
На картинке мы видим 2 кодпоинта, которые, очевидно, относятся к разным категориям символов. А ведь ещё есть цифры, пунктуация, графические символы и разделители, управляющие символы и суррогатные пары, и все они отображаются (или не отображаются) по‑разному, по‑разному ведут себя при сортировке, имеют разную направленность в тексте.
Свойства символов
Стандарт Unicode очень детально описывает свойства всех включённых в него кодпоинтов, но мы же пока остановимся на некоторых из них. Очень часто в документации / коде используются сокращения названий этих свойств, они приведены в скобках.
У каждого символа есть название. Название символа является уникальным, и может содержать только прописные буквы латинского алфавита (A‑Z), цифры (0–9), пробелы и знаки дефиса (‑).
Например, название кодпоинта латинской буквы A — LATIN CAPITAL LETTER A .
General Category (Gc)
Упомянутая ранее основная категория символа. Значения этого свойства можно объединить в несколько групп по их типу:
все буквы (L)
буквы, имеющие регистр (LC)
Lu — прописная буква (Letter, uppercase)
Ll — строчная буква (Letter, lowercase)
Lt — заглавная буква (Letter, titlecase)
Lm — буква‑модификатор (Letter, modifier)
Lo — буква, прочие (Letter, other)
знаки (M)
Mn — неразрывный знак (Mark, nonspacing)
Mc — сочетаемый знак с интервалом (Mark, spacing combining)
Me — закрывающий знак (Mark, enclosing)
цифры, числа (N)
Nd — десятичная цифра (Number, decimal digit)
Nl — буквенно‑числовой символ (Number, letter)
No — число, прочие (Number, other)
пунктуация (P)
Pc — соединительный символ (Punctuation, connector)
Pd — знаки тире и дефиса (Punctuation, dash)
Ps — открывающая пунктуация (Punctuation, open)
Pe — закрывающая пунктуация (Punctuation, close)
Pi — открывающие кавычки (Punctuation, initial quote)
Pf — закрывающие кавычки (Punctuation, final quote)
Po — пунктуация, прочее (Punctuation, other)
символы (S)
Sm — математический символ (Symbol, math)
Sc — символ валюты (Symbol, currency)
Sk — символ‑модификатор (Symbol, modifier)
So — символ, прочие (Symbol, other)
разделители (Z)
Zs — пробел (Separator, space)
Zl — разделитель строк (Separator, line)
Zp — разделитель параграфов (Separator, paragraph)
разное (C)
Cc — управляющие символы (Other, control)
Cf — символы форматирования (Other, format)
Cs — суррогаты (Other, surrogate)
Co — частное использование (Other, private use)
Cn — не назначенные, в том числе не‑символы (Other, not assigned (including noncharacters))
Canonical Combining Class (ССС)
Важнейшее из свойств — класс канонического комбинирования символов. Относится к свойствам нормализации, и используется, как несложно догадаться, в алгоритмах нормализации. Значением является число от 0 до 240, которое отвечает за расположение в последовательности кодпоинтов, составляющих букву.
Кодпоинт с CCC = 0 называется стартером (starter), все прочие — не‑стартерами (non‑starter). Стартером может являться какая‑либо буква, цифра — основной символ, к которому могут быть добавлены дополнительные знаки, либо символ, вообще не подразумевающий комбинирования с чем‑либо, — например, пробел. Стартеры не подлежат сортировке при декомпозиции символа / нормализации текста.
Значение CCC не‑стартера может нести какую‑то смысловую нагрузку — например, что кодпоинт перекрывает основной знак (ССС = 1), или является диакритическим знаком для чтения CJK (ССС = 2), или указывать расположение знака относительно стартера (ССС находятся в диапазоне 202–240), или быть просто весом для сортировки в последовательности (10–132).
В практическом плане, чаще всего нас будет интересовать, является‑ли кодпоинт стартером или нет. Тем не менее, приведём полный список значений CCC:
Не подлежащие сортировке: пробелы и обрамляющие знаки, а также множество гласных и согласных знаков, даже если они не образуют отдельных символов.
Наложение: знаки, накладывающиеся на базовую букву или символ.
Чтение китайских иероглифов: диакритические знаки для китайских, японских и корейских иероглифов.
Точки нукта в письменности, основанной на брахми.
Фонетические знаки Хираганы/Катаканы.
Вирамы: знаки, обозначающие отсутствие звука между символами в санскрите, хинди и ряде других индийских языков.
Классы фиксированного положения в комбинируемой последовательности.
Знаки, прикрепленные внизу слева.
Знаки, прикрепленные под базовым символом.
Знаки, прикрепленные внизу справа.
Знаки, прикрепленные слева.
Знаки, прикрепленные справа.
Знаки, прикрепленные сверху слева.
Знаки, прикрепленные над базовым символом.
Знаки, прикрепленные сверху справа.
Отдельные знаки снизу слева.
Отдельные знаки под базовым символом.
Отдельные знаки снизу справа.
Отдельные знаки слева.
Отдельные знаки справа.
Отдельные знаки сверху слева.
Отдельные знаки над базовым символом.
Отдельные знаки сверху справа.
Двойные знаки снизу. Отдельные знаки, которые располагаются ниже двух других диакритических знаков.
Двойные знаки сверху. Отдельные знаки, которые располагаются выше двух других диакритических знаков.
Греческая подстрочная йота. единственный кодпоинт с этим CCC — U+0345
Может возникнуть вопрос — а чем, собственно, разница между, например, 214 и 230? Дело в том, что символы, имеющие класс канонического комбинирования от 200 до 212 — это диакритические знаки, являющиеся частью отображения символа, как, например, хвостик (ogonek) в польском языке ( U+0328 COMBINING OGONEK ). А вот, например, две точки в букве Ë ( U+0308 COMBINING DIAERESIS ) — расположены над символом, отдельно, и его CCC будет равен 230.
Прочие свойства
У символа также есть свойства, относящиеся к отображению текста (например, Bidirectional Class (Bidi class), отвечающее за направление текста); свойства числовых символов; связанные с кодпоинтом строчная/прописная/заглавная буква. Пока просто упомянем, что они существуют, и вернёмся к ним в дальнейших статьях.
Другое важнейшее свойство — декомпозицию символа (Decomposition Mapping + Decomposition Type), мы рассмотрим уже в следующей статье про нормализацию.
Полную информацию по свойствам символов можно найти в четвертой главе спецификации Unicode: https://www.unicode.org/versions/Unicode15.0.0/ch04.pdf
Хранятся данные о символах Unicode в Unicode Character Database (UCD), и найти её можно здесь: https://www.unicode.org/Public/UCD/latest/ucd/.
Описанию форматов файлов UCD посвящёно приложение #44 стандарта: https://www.unicode.org/reports/tr44/.
Кодировки
Спецификация Unicode описывает три формата кодирования кодпоинтов (UTF, Unicode Transformation Format): UTF-8, UTF-16 и UTF-32.
С помощью каждой из них можно закодировать любой кодпоинт, находящийся в диапазоне от U+0000 до U+10FFFF . Самой распространенной из них является UTF-8, тем не менее, и у UTF-16, и у UTF-32 находится свое применение.
well-formed и ill-formed
Строка текста, закодированная в любой из кодировок считается хорошо сформированной (well‑formed), когда для строки выполняются следующие условия:
Для любой из кодировок недопустимы кодпоинты, выходящие за пределы таблицы символов Unicode (> U+10FFFF ).
В UTF-16 недопустимо наличие одного из элементов суррогатной пары без дополняющего его элемента. Отсюда также можно вывести правило, что младший суррогат не может стоять впереди старшего.
В UTF-8 и UTF-32 недопустимо присутствие символов суррогатных пар ( U+D800 — U+DFFF ).
В UTF-8 последовательности недопустимо избыточное кодирование. если кодпоинт можно закодировать, используя меньшее количество байт — такое кодирование является недопустимым.
Старшие биты байтов последовательности UTF-8 должны соответствовать его формату.
Некорректно закодированная строка называется ill‑formed.
UTF-32
Ключевым преимуществом UTF-32 можно назвать скорость и возможность быстрого доступа к любому символу в тексте по его индексу. Платой за это будет повышенное использование памяти, так как кодирование любого кодпоинта требует 4 байт.
Учитывая, что большинство символов в тексте (если не все) скорее всего будут относиться к плоскости BMP, значение кодпоинта которого укладывается в 2 байта, — мы увидим, что до половины объема памяти будет использоваться для хранения нулей.
Порядок байт, используемый при записи 32-битного значения кодпоинта может быть как little‑endian, так и big‑endian — кодировка в таком случае обозначается как UTF-32LE и UTF-32BE соответственно. По умолчанию считается, что используется big‑endian.
Для того, чтобы однозначно определить, какой порядок байт используется, в начале текста в качестве маркера записывается символ U+FEFF ZERO WIDTH NO‑BREAK SPACE в выбранном формате. Этот маркер называется BOM — Byte Order Mark.
Приведём пример записи кодпоинта в различных форматах:
Код символа (HEX)
Кодировка
Последовательность байт
00 00 FE FF 00 01 02 03
FF FE 00 00 03 02 01 00
00 01 02 03
UTF-16
Кодировка UTF-16 в данный момент используется довольно нечасто.
Собственно, формат появился в тот момент, когда в 1996 году стало понятно, что 2 байта на символ — недостаточно (первая версия Юникода представляла собой 16-битную кодировку с фиксированной шириной символа).
Главные недостатки кодировки:
по сравнению с UTF-8, кодировка избыточна, так как даже для кодирования ASCII‑символов используется 2 байта — так что она меньше подходит для хранения и передачи текстов.
когда требуется скорость — UTF-16 стоит рядышком с UTF-32, при этом требуя меньше ресурсов, но не имеет главного преимущества UTF-32 — возможности быстрого доступа к символу по его индексу, так как кодпоинт Unicode может быть закодирован в UTF-16 как двумя, так и четырьмя байтами.
Алгоритм кодирования:
В случае, если кодпоинт Unicode находится в плоскости BMP, то он кодируется как есть, двумя байтами.
Если значение кодпоинта лежит за пределами 16 бит — то такое значение кодируется с помощью суррогатной пары — двух слов, первое из которых называется старший суррогат — high surrogate code point, и имеет значение в диапазоне U+D800 — U+DBFF , и младший суррогат — low surrogate code point, и имеет значение в диапазоне U+DC00 — U+DFFF .
Кодирование суррогатной пары осуществляется по следующей схеме распределения бит:
битовое представление кодпоинта
битовое представление суррогатной пары
В данной схеме wwww = uuuuu — 1 . Это работает потому, что максимальное значение кодпоинта — 0×10FFFF , в битовом представлении — только 21-й бит имеет значение 1, таким образом, после вычитания единицы для хранения значения кодпоинта нам потребуется на один бит меньше.
Как и в случае с UTF-32, UTF-16 так же может быть записан в BE и LE виде, и для определения, какой порядок байт используется можно так же использовать BOM.
Код символа (HEX)
Кодировка
Последовательность байт
FE FF 00 4D
FF FE 4D 00
00 4D
FE FF D8 00 DC 00
FF FE 00 D8 00 DC
D8 00 DC 00
Перед тем, как перейти к UTF-8, немного поговорим о BOM.
Причины, которые привели к его появлению понятны: смысл был в том, чтобы дать понять программе, в каком формате читать текстовый файл, учитывая, что он может быть получен из стороннего источника.
На практике же — UTF-8 занял нишу формата для хранения и передачи текстовых данных, а там, где используется UTF-32 / UTF-16 — порядок байт заранее известен и BOM излишен.
Порядок байт в UTF-8 определен заранее, и BOM в UTF-8 файлах ( EF BB BF ) выполняет только функцию обозначения, что текст — в кодировке UTF-8. Загвоздка в том, что практически всегда мы и так знаем, что текст — в UTF-8.
Проблем же он создаёт огромное количество:
Если текстовые данные имеют какой‑то формат (например, JSON), они в большинстве случаев обрабатываются программно. библиотека‑же, обрабатывающая этот JSON может не ожидать BOM.
То же самое относится и к компиляторам / интерпретаторам: там, где при токенизации ожидаются whitespace‑символы, не самое место метке, которая является всё‑таки специальным, но тем не менее валидным символом.
Определение длины текста, переход по индексу символа в тексте, замусоривание метками при конкатенации строк — везде BOM может изрядно помешать.
На данный момент, использование BOM не рекомендовано, и это хорошо.
Наиболее используемый формат для кодирования Unicode, имеющий свои плюсы и минусы:
плюсы:
Совместим с ASCII.
Использует от 1 до 4 байт для кодирования символов, что обеспечивает рациональное использование памяти.
минусы:
Использование переменного размера байт, помимо положительной стороны, имеет и отрицательную — в UTF-8 невозможно быстро перейти к символу по его индексу или узнать количество кодпоинтов без итерации по всем его байтам.
распределение бит в схеме кодирования кодпоинта в UTF-8:
биты кодпоинта
1 байт
2 байт
3 байт
4 байт
0000 0yyy yyxx xxxx
zzzz уууу ууxx xxxx
000u uuuu zzzz yyyy yyxx xxxx
Таким образом мы видим:
Если мы кодируем ACSII значение ( U+0000 — U+007F ), нам потребуется только один байт.
Для кодирования символа из плоскости BMP за пределами ASCII ( U+0080 — U+FFFF ) нам потребуется от 2 до 3 байт.
Кодирование символов, выходящих за плоскость BMP ( U+10000 — U+10FFFF ) всегда потребует 4 байта.
Если первый байт не соответствует указанной выше форме, или старшие биты 2, 3, 4 байтов не равны 10 , то текст, содержащий UTF-8-последовательность с такими байтами считается ill‑formed.
✍️ Если старшие биты встретившегося нам байта в well‑formed строке не равны 10 , то это говорит нам о том, что мы встретили первый байт UTF-8-последовательности для символа, в противном случае — мы встретили 2, 3 или 4 байт последовательности.
В практическом применении это полезно, когда мы хотим разбить UTF-8-текст на части, и не нарушить его целостности (здесь мы говорим только о UTF-8, последовательность кодпоинтов в составных символах это всё‑таки нарушит).
Давайте определим, какие последовательности UTF-8 допустимы, а какие — нет (хотя можно просто посмотреть в табличку 3–7 третьей главы официальной документации).
первый байт не может начинаться с битов 10 — исключаем диапазон 0x80 — 0xBF для него.
2, 3 и 4 байты последовательности должны начинаться с битов 10 — т. е. их диапазоны значений — 0x80 — 0xBF .
начиная с U+0080 , символы кодируются несколькими байтами.
так как UTF-8 не допускает избыточного кодирования, то должны быть задействованы биты первого байта, что исключает некоторые его значения:
2 байта на символ, диапазон начинается с U+0080 :
U+0080 — 1100 0010 1000 0000 .
таким образом, первый байт не может быть равным 0xC0 и 0xC1 .
3 байта на символ, диапазон начинается с U+0800 :
U+0800 — 1110 0000 1010 0000 1000 0000 .
вывод: если первый байт — 0xE0 , то диапазон значений второго байта — от 0xA0 до 0xBF .
4 байта на символ, диапазон начинается с U+10000 :
U+10000 — 1111 0000 1001 0000 1000 0000 1000 0000 .
похоже на предыдущий случай: если первый байт — 0xF0 , то диапазон значений второго байта — от 0x90 до 0xBF .
так как UTF-8 позволяет напрямую кодировать символы, находящиеся за пределами BMP, использование символов суррогатных пар ( U+D800 — U+DFFF ) не допускается.
запишем их в UTF-8:
U+D800 — 1110 1101 1010 0000 1000 0000
U+DFFF — 1110 1101 1011 1111 1011 1111
таким образом, последовательность UTF-8 некорректна, если первый байт равен 1110 1101 ( 0xED ), а второй байт находится в диапазоне 0xA0 — 0xBF .
последний кодпоинт в таблице Unicode — U+10FFFF , а максимальное значение, которое можно закодировать в UTF-8 — 0x1FFFFF . следовательно, существует возможность записать в UTF-8 символ, выходящий за пределы таблицы, чего допустить нельзя.
запишем последний символ таблицы Unicode в UTF-8:
U+10FFFF — 1111 0100 1000 1111 1000 1111 1011 1111
отсюда становится очевидно, что мы вышли за пределы таблицы, если:
первый байт равен 1111 0100 ( 0xF4 ), и во втором байте записано значение, больше чем 1000 1111 ( 0x8F ).
первый байт больше 1111 0100 ( 0xF4 ) — заодно мы убеждаемся в валидности последовательности старших бит первого байта.
Если записать это в виде таблицы, то мы получим ту самую таблицу 3–7:
A0 — BF
80 — 9F
90 — BF
80 — 8F
В таблице выделены значения (во втором байте последовательностей), на которые стоит обратить внимание при имплементации валидации UTF-8.
расширяем кругозор: Ill‑formed UTF-8 и MySQL старых версий
В старых версиях MySQL по‑умолчанию использовалась кодировка, которая обозначалась как utf8 . Казалось‑бы, какие могут быть проблемы? Но проблемы стали всплывать, когда в моду вошли эмодзи — оказалось, что MySQL под кодировкой utf8 в целях оптимизации понимал её ill‑formed версию, utf8mb3 , допускающую кодирование только BMP плоскости (т. е. максимум 3 байта на символ).
На смену utf8 пришла кодировка utf8mb4 , полноценно поддерживающая диапазон символов Unicode, utf8mb3 же, в свою очередь, объявлена deprecated.
Если интересно узнать побольше, можно заглянуть в официальную документацию MySQL.
Обработка ошибок при валидации UTF-8
Согласно официальной документации (и я с ней полностью согласен), допустимо 2 варианта действий при столкновении с невалидным UTF-8.
Первый — не обрабатывать ничего, и просто сообщить об ошибке. Вероятность получения «битой» UTF-8 по техническим причинам обычно крайне мала, в то же время какие‑то злонамеренные попытки прощупать валидацию пользовательского ввода на некорректность, а как следствие — пробелы в безопасности, встречаются довольно часто.
Второй — при нахождении ошибки определить максимально длинную цепочку некорректных байт, и заменить их на специально предназначенный для этого символ U+FFFD REPLACEMENT CHARACTER (�).
В любом случае, выбор варианта обработки ошибок всецело зависит от контекста.
Особенно актуально соблюдать осторожность и не игнорировать ill‑formed UTF-8 в случаях, где возможны инъекции исполняемого кода, вроде JavaScript.
Более детально вопросы безопасности рассматриваются в приложении #36 — Unicode Security Considerations.
Немного практики
Теперь, давайте подведём некоторые итоги, и напишем конвертацию UTF-8 в кодпоинты и обратно, и валидацию UTF-8.
Конечно, Rust, как и практически любой другой язык, умеет работать с Unicode.
Но почему‑бы не попробовать написать реализацию некоторых функций самим? Вторая причина, по которой это может понадобиться — бывают ситуации, когда собственные имплементации решают какую‑то специфичную задачу, и использование стандартных сценариев не совсем оптимально по скорости / памяти.
Репозиторий, и что там находится:
В v1 и v2 некоторый код дублируется, это не ошибка, — на мой взгляд, так нагляднее.
cargo bench из папки benches
UTF-8 → кодпоинт и наоборот
Алгоритм кодирования / декодирования сам по себе ничего особо интересного не представляет — обычные битовые сдвиги и маски.
Тем не менее, декодирование
(битовые операции — в макросе, т.к. этот макрос нам ещё пригодится в следующей статье):
… и кодирование:
Общий алгоритм валидации
Если он — валидный первый байт последовательности UTF-8 — уточняем количество символов последовательности и читаем их. Проверяем.
Повторяем, пока не закончатся данные.
базовая функция:
Прогоняем бенчмарки… видим — на языках, которые используют преимущественно латинский алфавит, встроенная функция валидации отрабатывает в 2 раза быстрее. как они этого добились?
Давайте предположим, что текст — в ASCII. Такой текст можно попробовать валидировать не побайтово, а блоками, что и используется в core::str::validations::run_utf8_validation ‑ почему‑бы не взять оптимизацию оттуда и не сравнить результаты?
В ней, если найден ASCII‑символ, а адрес следующего байта имеет выравнивание в памяти по usize, валидируются 2 usize‑блока с помощью битовой маски (только у ASCII‑символов старший бит равен нулю).
Что нужно сделать:
Определим границу в тексте, после которой применять оптимизацию не нужно (после неё нет достаточного количества символов).
Добавляем проверку — если адрес выровнен по usize, индекс меньше границы — читаем блок и проверяем с помощью маски, являются‑ли элементы этого блока ASCII‑символами.
Повторяем, если проверка была успешной.
проверка:
добавляем границу:
добавляем блочные проверки:
Как и ожидалось по бенчмаркам, затраты на валидацию текста с UTF-8-последовательностями, не входящими в ASCII возросли — появилась дополнительная проверка. Также замедляется проверка за счет того, что если встречается ASCII‑символ в не‑ASCII тексте (никто не отменял знаки пунктуации, пробелы, переносы строк — ну или язык может сочетать в себе символы латиницы и диакритические знаки, лежащие за пределами ASCII).
Что можно ещё сделать?
Например, можно заменить функцию проверки количества символов get_utf8_sequence_width на получение количества символов последовательности из заранее построенного массива, как это делается в Rust:
Можно вместо количества байт попробовать получать готовый кейс для сравнения последующих байт последовательности:
Можно попробовать заменить проверки диапазона допустимых значений
на проверки старших бит по битовой маске:
или, что равнозначно,
Вобщем, оптимизация — отличное занятие, когда требуется отвлечься, пробуйте, сравнивайте!
Что дальше
На этом — закончу первую часть. Следующая будет посвящена декомпозиции и нормализации; возможно, эту тему разобью на два части — в первой затронем алгоритмы декомпозиции и NFD/NFKD, во второй же части поиграемся с канонической композицией и NFC/NFKC.
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
Статья о стандарте Unicode и кодировках
DmitryTsybulkin/unicode
Name already in use
- Local
- Codespaces
Use Git or checkout with SVN using the web URL.
Work fast with our official CLI. Learn more about the CLI.
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
Unicode и кодировки UTF-8, UTF-16. Основная информация, необходимая для разработки ПО.
История и введение
При разработке программы, где одной из главных составляющих является задача обработки текста (ввод, редактирование, чтение), важно понимание системы unicode. По своей природе, компьютеры на самом простом уровне работают с битами, но человеку необходимы более сложные сущности, такие как: числа, буквы и другие символы. Поэтому, для их представления в компьютерных системах используются специальные кодировки, способные преобразовывать числовые коды в символы. Кодирование, в свою очередь, – это представление какой-либо информации в ином, закодированном виде, не репрезентативном для человека, который можно вернуть в исходное состояние. Кодировка – это набор правил, описывающий способ перевода одного представления информации в другое.
До появления стандарта unicode, в мире было множество разных схем кодирования символов. Однако, у них было значительное количество недостатков: не было универсальной кодировки — многие схемы были несовместимы по отношению друг к другу; они не описывали все необходимые пользователю символы (наличие множества языков). Для любого компьютера, особенно сервера, необходимо было поддерживать несколько кодировок, но даже при этом существовал риск того, что данные, при передаче, могли оказаться поврежденными.
Одна из наиболее популярных кодировок в тот момент это — ASCII. Всего в систему ASCII включены 128 символов — она использует все комбинации семи битов (от 0000000 до 1111111). Например, последовательность бит двоичной системы 01000001 в изложенной кодировке кодирует символ “A”, или 01000010 кодирует символ “B”. Основная проблема ASCII заключалась в том, что эта кодировка неплохо работала только в англоязычной среде. Использовать её с другими языками было затруднительно.
С целью разрешить несистематизированность и попытаться унифицировать систему кодирования символов, которая могла бы использоваться для множества языков, в 1991 году некоммерческой организацией «Консорциум Юникода» был разработан и предложен стандарт Unicode. В этот консорциум входят такие лидеры компьютерной индустрии, как Apple, HP, IBM, Microsoft, Oracle и т.д. поэтому, эта схема кодирования используется почти во всех современных технологиях и стандартах, а консорциум ставит своими задачами — развитие и распространение этой системы. В результате использования этого стандарта, можно значительно сэкономить на поддержке программного обеспечения. Главной особенностью Unicode является то, что она присваивает уникальный код любому символу, независимо от платформы, программы или языка.
Начиная с октября 1991 года, стандарт постоянно развивается. Последняя версия (12.1) вышла в мае 2019 года. В каждой новой версии появляется больше символов, иероглифов из восточных языков, эмодзи и различных письменностей.
Стандарт состоит из двух основных частей: универсального набора символов (англ. Universal character set, UCS) и семейства кодировок (англ. Unicode transformation format, UTF). Универсальный набор символов перечисляет допустимые по стандарту Unicode символы и присваивает каждому символу код в виде неотрицательного целого числа, записываемого обычно в шестнадцатеричной форме с префиксом U+, например, U+040F. Семейство кодировок определяет способы преобразования кодов символов для передачи в потоке или в файле.
Общее количество кодовых позиций в unicode — 1 112 064, несмотря на значительно больший объём возможных вариантов (2^31 = 2 147 483 648). Это было сделано для совместимости разных представлений: UTF-8, UTF-16 и UTF-32. В последней версии Unicode, за май 2019, количество всех символов составляет 137 994.
Важно отметить, что стандарт unicode полностью совместим основным предшественником — ASCII. Есть конкретная область с кодами от U+0000 до U+007F, которая содержит символы набора ASCII, соответствующие их кодами в ASCII.
Простыми словами, Unicode – это просто огромная таблица соответствия символов и чисел, а различные UTF кодировки определяют, как эти числа переводятся в биты.
Формы представления unicode: UTF-8 и UTF-16
Стандарт Unicode в настоящее время реализуется различными кодировками, самые распространённые — UTF-8 и UTF-16, кодировки с переменной длиной кодирования.
То что кодировка переменной длины, значит, что один символ может быть закодирован разным количеством структурных единиц кодировки, то есть разным количеством байт. Если символ может быть закодирован одним байтом (потому что номер пункта символа очень маленький), UTF-8 закодирует его одним байтом. Если нужно 2 байта, то используется 2 байта. Так, например, латиница кодируется одним байтом, а кириллица двумя байтами. Кодировка сообщает старшим битам, сколькими битами кодируется текущий символ. Такой способ экономит место, но так же, тратит его в случае, если эти сигнальные биты часто используются. Пример кодирования:
UTF-16 является компромиссом в использовании: все символы как минимум двухбайтные, но их размер может увеличиваться до 4 байт. На западных языках обычно эффективнее всего кодируются с использованием стандарта UTF-8 (так как большинство символов в таких текстах могут быть представлены в виде кодов размером в 1 байт). Если же говорить о восточных языках, то можно сказать, что файлы, хранящие тексты, написанные на этих языках, обычно получаются меньше при использовании UTF-16.
Любой символ юникода в кодировке UTF-16 может быть закодирован либо одной парой байт или кодовой парой, либо двумя.
Количество символов, кодируемых одной кодовой парой может быть 65 535 (2^16), что полностью совпадает с базовым блоком юникода. Все символы находящиеся в этом блоке в кодировке UTF-16 будут закодированы одной кодовой парой (двумя байтами), тут все просто.
Для символов, находящихся за пределами базового unicode диапазона потребуется уже две кодовые пары (4 байта). И механизм их кодирования немного сложнее.
Для пояснения, необходимо ввести понятие “суррогатной пары”. Суррогатная пара — это две кодовые пары используемые для кодирования одного символа (итого 4 байта). Для таких суррогатных пар в таблице юникода отведен специальный диапазон от D800 до DFFF. Это значит, что при преобразовании кодовой пары из байтового вида в шестнадцатеричный вы получаете число из этого диапазона, то перед вами не самостоятельный символ, а суррогатная пара.
Чтобы закодировать символ из диапазона 10000 — 10FFFF (то есть символ для которого нужно использовать более одной кодовой пары) нужно:
- Из кода символа вычесть 10000 (шестнадцатеричное) (это наименьшее число из диапазона 10000 — 10FFFF)
- В результате первого пункта будет получено число не больше FFFFF, занимающее до 20 бит
- Ведущие 10 бит из полученного числа суммируются с D800 (начало диапазона суррогатных пар в юникоде)
- Следующие 10 бит суммируются с DC00 (тоже число из диапазона суррогатных пар)
- После этого получатся 2 суррогатные пары по 16 бит, первые 6 бит в каждой такой паре отвечают за определение того что это суррогат,
- Десятый бит в каждом суррогате отвечает за его порядок если это 1 то это первый суррогат, если 0, то второй.
(Подробнее в дополнительной литературе: Как работают кодировки текста. Откуда появляются «кракозябры». Принципы кодирования. Обобщение и детальный разбор).
Иногда, при обработке текста, можно встретить проблему того, что символы, выглядящие для человека одинаково, имеют различное внутреннее представление. Например, букву é в можно представить двумя способами:
- С помощью одной кодовой точки U+00E9.
- С помощью комбинации буквы e и знака акута, то есть — с помощью двух кодовых точек — U+0065 и U+0301 (комбинации символов).
Таким образом, визуально буква будет выглядеть идентично, но закодирована по-разному. Это может быть серьезной проблемой, при написании ПО, потому что она может возникнуть, например, при вводе пользователем каких либо данных: аутентификация в системе — пользователь ввёл верные данные для входа, однако, при поиске записей в БД, запись может быть не найдена, потому что коды символов различны. Для решения этой проблемы существуют алгоритмы нормализации строк (приведение их к каноническому виду).
Существуют четыре стандартных формы (алгоритма) нормализации:
- NFC: Normalization Form Canonical Composition. Форма нормализации C — алгоритм, согласно которому последовательно выполняются каноническая декомпозиция и каноническая композиция.
- NFD: Normalization Form Canonical Decomposition. Каноническая декомпозиция — алгоритм, согласно которому выполняется рекурсивное разложение составных символов на последовательность из одного или нескольких простых символов в соответствии с таблицами декомпозиции. Рекурсивное потому, что в процессе разложения составной символ может быть разложен на несколько других, некоторые из которых тоже являются составными, и к которым применяется дальнейшее разложение.
- NFKC: Normalization Form Compatibility Composition. Форма нормализации KC — алгоритм, согласно которому последовательно выполняются совместимая декомпозиция (алгоритм NFKD) и каноническая композиция (алгоритм NFC).
- NFKD: Normalization Form Compatibility Decomposition. Форма нормализации KD — совместимая декомпозиция — алгоритм, согласно которому последовательно выполняются каноническая декомпозиция и замены символов текста по таблицам совместимой декомпозиции.
Чаще всего используется форма нормализации NFC. При использовании этого алгоритма все символы сначала подвергаются декомпозиции (процесс, при котором все буквы по возможности разбиваются на модификаторы), после чего все комбинирующиеся последовательности подвергаются повторной композиции (процесс, при котором все буквы по возможности объединяются в одну) в порядке, определяемом стандартом. Для практического применения можно выбрать любую форму. Главное — применять её последовательно. В результате поступление на вход программы одних и тех же данных всегда будет приводить к одному и тому же результату.
Несмотря на наличие такого унифицированного стандарта кодирования символов, у Unicode остаются некоторые нерешенные проблемы:
- Тексты на китайском, корейском и японском языках имеют традиционное написание сверху вниз, начиная с правого верхнего угла. Переключение горизонтального и вертикального написания для этих языков не предусмотрено в Unicode — это должно осуществляться средствами языков разметки или внутренними механизмами текстовых процессоров.
- Наличие или отсутствие в Unicode разных начертаний одного и того же символа в зависимости от языка. Нужно следить, чтобы текст всегда был правильно помечен как относящийся к тому или другому языку.
- Так, китайские иероглифы могут иметь разные начертания в китайском, японском (кандзи) и корейском (ханча), но при этом в Unicode обозначаются одним и тем же символом (так называемая CJK-унификация), хотя упрощённые и полные иероглифы всё же имеют разные коды.
- Аналогично, русский и сербский языки используют разное начертание курсивных букв п и т (в сербском они выглядят как и и ш, см. сербский курсив).
- Перевод из строчных букв в заглавные тоже зависит от языка.
- Даже с арабскими цифрами есть определённые типографские тонкости: цифры бывают «прописными» и «строчными», пропорциональными и моноширинными — для Unicode разницы между ними нет. Подобные нюансы остаются за программным обеспечением.
Некоторые недостатки связаны не с самим Unicode, а с возможностями обработчиков текста.
- Файлы нелатинского текста в Unicode всегда занимают больше места, так как один символ кодируется не одним байтом, как в различных национальных кодировках, а последовательностью байтов (исключение составляет UTF-8 для языков, алфавит которых укладывается в ASCII, а также наличие в тексте символов двух и более языков, алфавит которых не укладывается в ASCII). Файл шрифта, необходимый для отображения всех символов таблицы Unicode, занимает сравнительно много места в памяти и требует больших вычислительных ресурсов, чем шрифт только одного национального языка пользователя. С увеличением мощности компьютерных систем и удешевлением памяти и дискового пространства эта проблема становится всё менее существенной; тем не менее, она остаётся актуальной для портативных устройств, например, для мобильных телефонов.
- Хотя поддержка Unicode реализована в наиболее распространённых операционных системах, до сих пор не всё прикладное программное обеспечение поддерживает корректную работу с ним. В частности, не всегда обрабатываются метки порядка байтов (BOM) и плохо поддерживаются диакритические символы. Проблема является временной и есть следствие сравнительной новизны стандартов Unicode (в сравнении с однобайтовыми национальными кодировками).
- Производительность всех программ обработки строк (в том числе и сортировок в БД) снижается при использовании Unicode вместо однобайтовых кодировок.
Unicode — достаточно сложная система, в которой существует множество хитростей и лазеек: от невидимых символов и контрольных знаков до суррогатных пар и комбинированных эмодзи (когда при сложении двух знаков получается третий). По этой причине, злоумышленники, хорошо осведомленные о нюансах Unicode могут использовать их для атаки ПО.
Например, существует атака, описанная OWASP, направленная на поиск недостатков в механизме декодирования, реализованном в приложениях при декодировании формата данных Unicode. Злоумышленник может использовать эту технику для кодирования определенных символов в URL-адресе, чтобы обойти фильтры приложений, таким образом получая доступ к ограниченным ресурсам на веб-сервере или принудительно просматривая защищенные страницы.
Другой пример уязвимости, найденной сравнительно недавно, это брешь в процедуре восстановления забытого пароля на GitHub, которая позволяла злоумышленнику получить пароль от чужого аккаунта.
В рамках этой процедуры восстановления, выполнялось сравнение введённого адреса электронной почты с адресом, который хранится в базе. Алгоритм проверки:
- Введённый адрес переводится в нижний регистр с помощью метода toLowerCase .
- Введённый адрес сравнивается с адресом в базе зарегистрированных пользователей.
- Если найдено совпадение, пароль из базы данных высылается на введённый адрес.
Очевидно, разработчики не знали о коллизии трансляции адресов при использовании метода toLowerCase . В данном случае исправить ошибку просто. Достаточно отправить пароль не на введённый адрес, а на адрес из базы данных.
Связанные с Юникодом баги имеют такое свойство, что их можно встретить в любом приложении, которое обрабатывает текст, введённый пользователем. Уязвимости есть и в веб-приложениях, и в нативных программах под Android и iOS.
Большую известность получила ошибка, связанная с воспроизведением символов Unicode для индийского языка телугу. Проблема возникала на некоторых версиях iOS в приложениях, использующих дефолтный шрифт San Francisсo. Получив всего несколько символов జ్ఞా, пользователь терял управление над многими приложениями в iOS, включая почту и Facebook. Если один из символов телугу появлялся во всплывающих уведомлениях, то блокировался SpringBoard — приложение, отвечающее за главный экран в iOS.
Ошибка изменения символов, приводящая к краху системы, кроется в особенности языка телугу, бенгали и некоторых других диалектов. Она заключается в последовательном построении иероглифов из элементов письма — глифов, при этом имеется определенное расположение символов, не характерное для языка. К сбою приводит преобразование соединительных суффиксов в согласных: когда вторую согласную букву слоговой алфавитной письменности телугу присоединяют к первой согласной для объединения без значительного изменения формы слова. Вследствие несовместимости глифов возникает ошибка, которую не может обработать процессор устройства.
Подводя итог, можно отметить следующие важные моменты касательно Unicode:
Юникод в вебе: введение для начинающих
Верите вы или нет, но существует формат изображений, встроенных в браузер. Этот формат позволяет загружать изображения до того, как они понадобились, обеспечивает рендеринг изображения на обычных или retina экранах и позволяет добавлять к изображениям CSS. ОК, это не совсем правда. Это не формат изображения, хотя все остальное остается в силе. Используя его, вы можете создавать иконки, независимые от разрешения, не требующие время на загрузку и стилизуемые с помощью CSS.
В этой статье я опишу основы Unicode (далее — Юникод) и некоторые интересные вещи, которые вы можете сделать с его помощью.
Что такое Юникод?
Юникод это возможность корректно отображать буквы и знаки пунктуации из различных языков на одной страницы. Он невероятно полезен: пользователи смогут работать с вашим сайтом по всему миру и он будет показывать то, что вы хотите — это может быть французский язык с диакритическими знаками или Kanji.
Юникод продолжает развиваться: сейчас актуальна версия 8.0 в которой более 120 тысяч символов (в оригинальной статье, опубликованной в начале 2014 года, речь шла о версии 6.3 и 110 тысячах символов).
Кроме букв и цифр, в Юникоде есть и другие символы и иконки. В последних версиях в их число вошли эмодзи, которые вы можете видеть в месседжере iOS.
Страницы HTML создаются из последовательности символов Юникода и при отсылке по сети они конвертируются в байты. Каждая буква и каждый символ любого языка имеют свой уникальный код и кодируются при сохранении файла.
При использовании системы кодирования UTF-8 вы можете напрямую вставлять в текст символы Юникода, но также можно добавлять их в текст, указывая цифровую символьную ссылку. Например, ♥ это символ сердечка и вы можете вывести этот символ, просто добавив код в разметку ♥.
Эту числовую ссылку можно задавать как в десятичном формате, так и в шестнадцатеричном. Десятичный формат требует добавления в начале буквы x , запись ♥ даст то же самое сердечко (♥), что и предыдущий вариант. (2665 это шестнадцатеричный вариант 9829).
Если вы добавляете символ Юникода с помощью CSS, то вы можете использовать только шестнадцатеричные значения.
Некоторые наиболее часто используемые символы Юникода имеют более запоминаемые текстовые имена или аббревиатуры вместо цифровых кодов — это, например, амперсанд ( & — &). Такие символы называются мнемоники в HTML, их полный список есть в Википедии.
Почему вам стоит использовать Юникод?
Хороший вопрос, вот несколько причин:
- Чтобы использовать корректные символы из разных языков.
- Для замены иконок.
- Для замены иконок, подключаемых через @font-face .
- Для задания CSS-классов
Корректные символы
Первая из причин не требует никаких дополнительных действий. Если HTML сохранен в формате UTF-8 и его кодировка передана по сети как UTF-8, все должно работать как надо.
Должно. К сожалению, не все браузеры и устройства поддерживают все символы Юникода одинаково (точнее, не все шрифты поддерживают полный набор символов). Например, недавно добавленные символы эмодзи поддерживаются не везде.
Для поддержки UTF-8 в HTML5 добавьте <meta charset=utf-8> (при отсутствии доступа к настройкам сервера стоит добавить также <meta http-equiv= «Content-Type» content=»text/html; charset=utf-8″> ). При старом доктайпе используется ( <meta http-equiv=»content-type» content=»text/html; charset=UTF-8″ /> ).
Иконки
Вторая причина использования Юникода это наличие большого количества полезных символов, которые можно использовать в качестве иконок. Например, ▶, ≡ и ♥.
Их очевидный плюс в том, что вам не надо никаких дополнительных файлов, чтобы добавить их на страницу, а, значит, ваш сайт будет быстрее. Вы также можете изменить их цвет или добавить тень с помощью CSS. А добавив переходы (css transition) вы сможете плавно менять цвет иконки при наведении на нее без каких-либо дополнительных изображений.
Предположим, что я хочу подключить индикатор рейтинга со звездами на свою страницу. Я могу сделать это так:
Получится следующий результат:
Образец рейтинга в Firefox
Но если вам не повезет, вы увидите что-то вроде этого:
Тот же рейтинг на BlackBerry 9000
Так бывает, если используемые символы отсутствуют в шрифте браузера или устройства (к счастью, эти звездочки поддерживаются отлично и старые телефоны BlackBerry являются здесь единственным исключением).
Если символ Юникода отсутствует, на его месте могут быть разные символы от пустого квадрата (□) до ромба со знаком вопроса (�).
А как найти символ Юникода, который может подойти для использования в вашем дизайне? Вы можете поискать его на сайте типа Unicodinator, просматривая имеющиеся символы, но есть и лучший вариант. Shapecatcher — этот отличный сайт позволяет вам нарисовать искомую иконку, после чего предлагает вам список похожих символов Юникода.
Использование Юникода с @font-face иконками
Если вы используете иконки, подключаемые с внешним шрифтом через @font-face , символы Юникода можно использовать в качестве запасного варианта. Таким образом вы можете показать похожий символ Юникода на тех устройствах или в браузерах, где @font-face не поддерживается:
Слева иконки Font Awesome в Chrome, а справа замещающие их символы Юникода в Opera Mini.
Многие инструменты для подбора @font-face используют диапазон символов Юникода из области для частного использования (private use area). Проблема этого подхода в том, что если @font-face не поддерживается, пользователю передаются коды символов без какого-либо смысла.
Использование символов из области для частного использования может также вынудить Internet Explorer 8 перейти в режим совместимости, а это сродни открытию портала в ад и призванию отвратительных монстров из старых IE (подробнее эта проблема описана в статье Джереми Кита).
IcoMoon отлично подходит для создания наборов иконок в @font-face и позволяет выбрать в качестве основы для иконки подходящий символ Юникода.
Но будьте внимательны — некоторые браузеры и устройства не любят отдельные символы Юникода при их использовании с @font-face . Имеет смысл проверить поддержку символов Юникода с помощью Unify — это приложение поможет вам определить, насколько безопасно использование символа в наборе иконок @font-face .
Поддержка символов Юникода
Основная проблема с использованием символов Юникода в качестве запасного варианта это плохая поддержка в скринридерах (опять-таки, некоторые сведения об этом можно найти на Unify), поэтому важно осторожно выбирать используемые символы.
Если ваша иконка это просто декоративный элемент рядом с текстовой меткой, читаемым скринридером, вы можете особо не волноваться. Но если иконка расположена отдельно, стоит добавить скрытую текстовую метку, чтобы помочь пользователям скринридеров. Даже если символ Юникода будет считан скринридером, есть вероятность, что он будет сильно отличен от своего предназначения. Например, ≡ (≡) в качестве иконки-гамбургера будет считан VoiceOver на iOS как “идентичный”.
Юникод в названиях CSS-классов
То, что Юникод можно использовать в названиях классов и в таблицах стилей известно с 2007 года. Именно тогда Джонатан Снук написал об использовании символов Юникода во вспомогательных классов при верстке скругленных углов. Особого распространения эта идея не получила, но о возможности использовать Юникод в названиях классов (спецсимволы или кириллицу) знать стоит.
Выбор шрифтов
Совсем немногие шрифты поддерживают полный набор символов Юникода, поэтому при выборе шрифта сразу проверяйте наличие нужных вам символов.
Много иконок в Segoe UI Symbol или Arial Unicode MS . Эти шрифты есть и на PC и на Mac; в Lucida Grande также достаточное количество символов Юникода. Вы можете добавить эти шрифты в декларацию font-family , чтобы обеспечить наличие максимального количества символов Юникода для пользователей, у которых эти шрифты установлены.
Определение поддержки Юникода
Было бы очень удобно иметь возможность проверить наличие того или иного символа Юникода, но нет гарантированного способа сделать это.
В Modernizr есть проверка поддержки эмодзи — но она работает проверкой одного пикселя и поэтому даст неправильный результат, если в нужном символе этот пиксель не используется. Да и сама проверка наличия отдельного символа не скажет ничего о поддержке остальных ста тысяч.
Проверяйте. И обеспечивайте запасные варианты, чтобы пользователь всегда мог понять, что происходит при отсутствии нужного символа.
Юникод в письмах
Юникод можно использовать не только на веб-страницах, но и в письмах.
Но здесь есть та же проблема: некоторые почтовые клиенты и некоторые устройства поддерживают нужные символы, а некоторые нет. Небольшое тестирование, позволяющее определить уровень поддержки символов, было проведено Campaign Monitor.
Символы Юникода могут быть эффективны при наличии поддержки. Например, эмодзи в теме письма выделяет его на фоне остальных в почтовом ящике.
Заключение
Эта статья затрагивает лишь основы Юникода. Надеюсь, она окажется полезной и поможет вам лучше понять Юникод и эффективно применять его.
Юникод в вебе: введение для начинающих
Верите вы или нет, но существует формат изображений, встроенных в браузер. Этот формат позволяет загружать изображения до того, как они понадобились, обеспечивает рендеринг изображения на обычных или retina экранах и позволяет добавлять к изображениям CSS. ОК, это не совсем правда. Это не формат изображения, хотя все остальное остается в силе. Используя его, вы можете создавать иконки, независимые от разрешения, не требующие время на загрузку и стилизуемые с помощью CSS.
В этой статье я опишу основы Unicode (далее — Юникод) и некоторые интересные вещи, которые вы можете сделать с его помощью.
Что такое Юникод?
Юникод это возможность корректно отображать буквы и знаки пунктуации из различных языков на одной страницы. Он невероятно полезен: пользователи смогут работать с вашим сайтом по всему миру и он будет показывать то, что вы хотите — это может быть французский язык с диакритическими знаками или Kanji.
Юникод продолжает развиваться: сейчас актуальна версия 8.0 в которой более 120 тысяч символов (в оригинальной статье, опубликованной в начале 2014 года, речь шла о версии 6.3 и 110 тысячах символов).
Кроме букв и цифр, в Юникоде есть и другие символы и иконки. В последних версиях в их число вошли эмодзи, которые вы можете видеть в месседжере iOS.
Страницы HTML создаются из последовательности символов Юникода и при отсылке по сети они конвертируются в байты. Каждая буква и каждый символ любого языка имеют свой уникальный код и кодируются при сохранении файла.
При использовании системы кодирования UTF-8 вы можете напрямую вставлять в текст символы Юникода, но также можно добавлять их в текст, указывая цифровую символьную ссылку. Например, ♥ это символ сердечка и вы можете вывести этот символ, просто добавив код в разметку ♥.
Эту числовую ссылку можно задавать как в десятичном формате, так и в шестнадцатеричном. Десятичный формат требует добавления в начале буквы x , запись ♥ даст то же самое сердечко (♥), что и предыдущий вариант. (2665 это шестнадцатеричный вариант 9829).
Если вы добавляете символ Юникода с помощью CSS, то вы можете использовать только шестнадцатеричные значения.
Некоторые наиболее часто используемые символы Юникода имеют более запоминаемые текстовые имена или аббревиатуры вместо цифровых кодов — это, например, амперсанд ( & — &). Такие символы называются мнемоники в HTML, их полный список есть в Википедии.
Почему вам стоит использовать Юникод?
Хороший вопрос, вот несколько причин:
- Чтобы использовать корректные символы из разных языков.
- Для замены иконок.
- Для замены иконок, подключаемых через @font-face .
- Для задания CSS-классов
Корректные символы
Первая из причин не требует никаких дополнительных действий. Если HTML сохранен в формате UTF-8 и его кодировка передана по сети как UTF-8, все должно работать как надо.
Должно. К сожалению, не все браузеры и устройства поддерживают все символы Юникода одинаково (точнее, не все шрифты поддерживают полный набор символов). Например, недавно добавленные символы эмодзи поддерживаются не везде.
Для поддержки UTF-8 в HTML5 добавьте <meta charset=utf-8> (при отсутствии доступа к настройкам сервера стоит добавить также <meta http-equiv= «Content-Type» content=»text/html; charset=utf-8″> ). При старом доктайпе используется ( <meta http-equiv=»content-type» content=»text/html; charset=UTF-8″ /> ).
Иконки
Вторая причина использования Юникода это наличие большого количества полезных символов, которые можно использовать в качестве иконок. Например, ▶, ≡ и ♥.
Их очевидный плюс в том, что вам не надо никаких дополнительных файлов, чтобы добавить их на страницу, а, значит, ваш сайт будет быстрее. Вы также можете изменить их цвет или добавить тень с помощью CSS. А добавив переходы (css transition) вы сможете плавно менять цвет иконки при наведении на нее без каких-либо дополнительных изображений.
Предположим, что я хочу подключить индикатор рейтинга со звездами на свою страницу. Я могу сделать это так:
Получится следующий результат:
Образец рейтинга в Firefox
Но если вам не повезет, вы увидите что-то вроде этого:
Тот же рейтинг на BlackBerry 9000
Так бывает, если используемые символы отсутствуют в шрифте браузера или устройства (к счастью, эти звездочки поддерживаются отлично и старые телефоны BlackBerry являются здесь единственным исключением).
Если символ Юникода отсутствует, на его месте могут быть разные символы от пустого квадрата (□) до ромба со знаком вопроса (�).
А как найти символ Юникода, который может подойти для использования в вашем дизайне? Вы можете поискать его на сайте типа Unicodinator, просматривая имеющиеся символы, но есть и лучший вариант. Shapecatcher — этот отличный сайт позволяет вам нарисовать искомую иконку, после чего предлагает вам список похожих символов Юникода.
Использование Юникода с @font-face иконками
Если вы используете иконки, подключаемые с внешним шрифтом через @font-face , символы Юникода можно использовать в качестве запасного варианта. Таким образом вы можете показать похожий символ Юникода на тех устройствах или в браузерах, где @font-face не поддерживается:
Слева иконки Font Awesome в Chrome, а справа замещающие их символы Юникода в Opera Mini.
Многие инструменты для подбора @font-face используют диапазон символов Юникода из области для частного использования (private use area). Проблема этого подхода в том, что если @font-face не поддерживается, пользователю передаются коды символов без какого-либо смысла.
Использование символов из области для частного использования может также вынудить Internet Explorer 8 перейти в режим совместимости, а это сродни открытию портала в ад и призванию отвратительных монстров из старых IE (подробнее эта проблема описана в статье Джереми Кита).
IcoMoon отлично подходит для создания наборов иконок в @font-face и позволяет выбрать в качестве основы для иконки подходящий символ Юникода.
Но будьте внимательны — некоторые браузеры и устройства не любят отдельные символы Юникода при их использовании с @font-face . Имеет смысл проверить поддержку символов Юникода с помощью Unify — это приложение поможет вам определить, насколько безопасно использование символа в наборе иконок @font-face .
Поддержка символов Юникода
Основная проблема с использованием символов Юникода в качестве запасного варианта это плохая поддержка в скринридерах (опять-таки, некоторые сведения об этом можно найти на Unify), поэтому важно осторожно выбирать используемые символы.
Если ваша иконка это просто декоративный элемент рядом с текстовой меткой, читаемым скринридером, вы можете особо не волноваться. Но если иконка расположена отдельно, стоит добавить скрытую текстовую метку, чтобы помочь пользователям скринридеров. Даже если символ Юникода будет считан скринридером, есть вероятность, что он будет сильно отличен от своего предназначения. Например, ≡ (≡) в качестве иконки-гамбургера будет считан VoiceOver на iOS как “идентичный”.
Юникод в названиях CSS-классов
То, что Юникод можно использовать в названиях классов и в таблицах стилей известно с 2007 года. Именно тогда Джонатан Снук написал об использовании символов Юникода во вспомогательных классов при верстке скругленных углов. Особого распространения эта идея не получила, но о возможности использовать Юникод в названиях классов (спецсимволы или кириллицу) знать стоит.
Выбор шрифтов
Совсем немногие шрифты поддерживают полный набор символов Юникода, поэтому при выборе шрифта сразу проверяйте наличие нужных вам символов.
Много иконок в Segoe UI Symbol или Arial Unicode MS . Эти шрифты есть и на PC и на Mac; в Lucida Grande также достаточное количество символов Юникода. Вы можете добавить эти шрифты в декларацию font-family , чтобы обеспечить наличие максимального количества символов Юникода для пользователей, у которых эти шрифты установлены.
Определение поддержки Юникода
Было бы очень удобно иметь возможность проверить наличие того или иного символа Юникода, но нет гарантированного способа сделать это.
В Modernizr есть проверка поддержки эмодзи — но она работает проверкой одного пикселя и поэтому даст неправильный результат, если в нужном символе этот пиксель не используется. Да и сама проверка наличия отдельного символа не скажет ничего о поддержке остальных ста тысяч.
Проверяйте. И обеспечивайте запасные варианты, чтобы пользователь всегда мог понять, что происходит при отсутствии нужного символа.
Юникод в письмах
Юникод можно использовать не только на веб-страницах, но и в письмах.
Но здесь есть та же проблема: некоторые почтовые клиенты и некоторые устройства поддерживают нужные символы, а некоторые нет. Небольшое тестирование, позволяющее определить уровень поддержки символов, было проведено Campaign Monitor.
Символы Юникода могут быть эффективны при наличии поддержки. Например, эмодзи в теме письма выделяет его на фоне остальных в почтовом ящике.
Заключение
Эта статья затрагивает лишь основы Юникода. Надеюсь, она окажется полезной и поможет вам лучше понять Юникод и эффективно применять его.
HOWTO по Юникоду¶
В этом HOWTO обсуждается поддержка Python спецификации Юникод для представления текстовых данных и объясняются различные проблемы, с которыми люди обычно сталкиваются при работе с Юникод.
Знакомство с Юникодом¶
Определения¶
Сегодняшние программы должны уметь обрабатывать самые разные символы. Приложения часто интернационализированы для отображения сообщений и вывода на различных языках, выбираемых пользователем; той же программе может потребоваться вывести сообщение об ошибке на английском, французском, японском, иврите или русском языках. Веб-контент может быть написан на любом из этих языков, а также может включать различные символы эмодзи. Строковый тип Python использует стандарт Юникод для представления символов, что позволяет программам Python работать со всеми этими различными возможными символами.
Юникод (https://www.unicode.org/) — это спецификация, которая направлена на перечисление каждого символа, используемого человеческими языками, и присвоение каждому символу его собственного уникального кода. Спецификации Юникод постоянно пересматриваются и обновляются, чтобы добавить новые языки и символы.
Символ — это наименьший возможный компонент текста. «A», «B», «C» и т. д. — разные символы. То же самое и «È» и «Í». Символы различаются в зависимости от языка или контекста, о которых вы говорите. Например, для «римской цифры один» есть символ «Ⅰ», который отделён от прописной буквы «I». Обычно они выглядят одинаково, но это два разных символа, которые имеют разные значения.
Стандарт Юникод определяет, как символы представлены кодовыми точками. Значение кодовой точки — это целое число в диапазоне от 0 до 0x10FFFF (около 1.1 миллиона значений, из которых на данный момент присвоено около 110 тысяч). В стандарте и в этом документе кодовая точка записывается с использованием обозначения U+265E для обозначения символа со значением 0x265e (9822 в десятичной системе).
Стандарт Юникод содержит множество таблиц, в которых перечислены символы и соответствующие им кодовые точки:
Строго говоря, эти определения подразумевают, что говорить «это символ U+265E » бессмысленно. U+265E — кодовая точка, которая представляет некоторый символ; в данном случае он представляет собой символ «ЧЕРНЫЙ ШАХМАТНЫЙ КОНЬ», «♞». В неформальном контексте об этом различии между кодовыми точками и символами иногда забывают.
Символ представлен на экране или на бумаге набором графических элементов, который называется глифом. Например, глиф для прописной буквы A представляет собой два диагональных штриха и горизонтальный штрих, хотя точные детали будут зависеть от используемого шрифта. Большинству кода Python не нужно беспокоиться о глифах; определение правильного глифа для отображения, как правило, является задачей инструментария графического интерфейса пользователя или средства визуализации шрифтов терминала.
Кодировки¶
Подводя итог предыдущему разделу: Юникод строка — это последовательность кодовых точек, которые представляют собой числа от 0 до 0x10FFFF (1 114 111 в десятичной системе). Последовательность кодовых точек должна быть представлена в памяти как набор кодовых единиц, а затем кодовые единицы отображаются в 8-битные байты. Правила перевода Юникод строки в последовательность байтов называются кодировкой символов или просто кодировкой.
Первая кодировка, о которой вы можете подумать, использует 32-битные целые числа в качестве кодовой единицы, а затем использует представление ЦП 32-битных целых чисел. В этом представлении строка «Python» может выглядеть так:
Это простое представление, но при его использовании возникает ряд проблем.
- Непереносимая; разные процессоры по-разному упорядочивают байты.
- Она занимает очень много места. В большинстве текстов большинство кодовых точек меньше 127 или меньше 255, поэтому много места занимают байты 0x00 . Вышеупомянутая строка занимает 24 байта по сравнению с 6 байтами, необходимыми для представления ASCII. Повышенное использование ОЗУ не имеет большого значения (настольные компьютеры имеют гигабайты ОЗУ, а строки обычно не такие большие), но увеличение использования диска и пропускной способности сети в 4 раза недопустимо.
- Она несовместима с существующими функциями C, такими как strlen() , поэтому необходимо использовать новое семейство широких строковых функций.
Поэтому кодировка используется не очень часто, и люди вместо этого выбирают другие кодировки, более эффективные и удобные, например UTF-8.
UTF-8 — одна из наиболее часто используемых кодировок, и Python часто по умолчанию использует её. UTF означает «Формат преобразования Юникода», а «8» означает, что при кодировании используются 8-битные значения. (Существуют также кодировки UTF-16 и UTF-32, но они используются реже, чем UTF-8.) UTF-8 использует следующие правила:
- Если кодовая точка < 128, она представлена соответствующим байтовым значением.
- Если кодовая точка >= 128, она превращается в последовательность из двух, трёх или четырёх байтов, где каждый байт последовательности находится между 128 и 255.
У UTF-8 есть несколько удобных свойств:
- Её может обрабатывать любую кодовую точку Юникода.
- Строка Юникод превращается в последовательность байтов, которая содержит встроенные нулевые байты только там, где они представляют собой нулевой символ (U+0000). Это означает, что строки UTF-8 могут обрабатываться функциями C, такими как strcpy() , и отправляться через протоколы, которые не могут обрабатывать нулевые байты для чего-либо, кроме маркеров конца строки.
- Строка текста ASCII также является допустимым текстом UTF-8.
- UTF-8 довольно компактен; большинство часто используемых символов могут быть представлены одним или двумя байтами.
- Если байты повреждены или потеряны, можно определить начало следующей кодовой точки в кодировке UTF-8 и выполнить повторную синхронизацию. Также маловероятно, что случайные 8-битные данные будут выглядеть как правильный UTF-8.
- UTF-8 — это байтовая кодировка. Кодировка указывает, что каждый символ представлен определенной последовательностью из одного или нескольких байтов. Это позволяет избежать проблем с порядком следования байтов, которые могут возникнуть при использовании кодировок, ориентированных на целые числа и слова, таких как UTF-16 и UTF-32, где последовательность байтов зависит от закодировавшего строку оборудования.
Ссылки¶
Сайт Консорциума Юникода содержит символьные таблицы, глоссарий и PDF-версии спецификации. Будьте готовы к трудному чтению. Хронология происхождения и развития Юникод также доступен на сайте.
На канале Computerphile Youtube Том Скотт кратко обсуждает историю Юникод и UTF-8 (9 минут 36 секунд).
Чтобы помочь понять стандарт, Юкка Корпела написал вводное руководство для чтения таблиц символов Юникод.
Другая хорошая вступительная статья написал Джоэл Спольски. Если это введение не прояснило вам ситуацию, вам следует попробовать прочитать альтернативную статью, прежде чем продолжить.
Часто полезны записи в википедии; см., например, записи для «кодировка символов» и UTF-8.
Поддержка Юникода в Python¶
Теперь, когда вы изучили основы Юникод, мы можем взглянуть на особенности Юникода в Python.
Строковый тип¶
Начиная с Python 3.0, тип языка str содержит символы Юникод, что означает любую строку, созданную с использованием «unicode rocks!» , ‘unicode rocks!’ или сохраняется строковый синтаксис с тройными кавычками как Юникод.
Кодировка по умолчанию для исходного кода Python — UTF-8, поэтому вы можете просто включить символ Юникод в строковый литерал:
Примечание: Python 3 также разрешает использование символов Юникод в идентификаторах:
Если вы не можете ввести определенный символ в своём редакторе или по какой-то причине хотите сохранить исходный код только в формате ASCII, вы также можете использовать escape-последовательности в строковых литералах. (В зависимости от вашей системы вы можете увидеть фактический глиф с заглавными буквами вместо экранирующего символа u.):
Кроме того, можно создать строку, используя метод decode() из bytes . Метод принимает аргумент encoding, например UTF-8, и, необязательно, аргумент errors.
Аргумент errors указывает ответ, когда входная строка не может быть преобразована в соответствии с правилами кодирования. Допустимые значения этого аргумента: ‘strict’ (вызвать исключение UnicodeDecodeError ), ‘replace’ (использовать U+FFFD , СИМВОЛ ЗАМЕНЫ ), ‘ignore’ (просто исключить символ из результата Юникод) или ‘backslashreplace’ (вставить escape-последовательность \xNN ). Следующие примеры показывают различия:
Кодировки задаются как строки, содержащие имя кодировки. У Python около 100 различных кодировок; см. список в справочнике по библиотеке Python в стандартные кодировки . Некоторые кодировки имеют несколько имён; например: ‘latin-1’ , ‘iso_8859_1’ и ‘8859 — все синонимы для одной и той же кодировки.
Односимвольные Юникод строки также могут быть созданы с помощью встроенной функции chr() , которая принимает целые числа и возвращает Юникод строку длиной 1, содержащую соответствующую кодовую точку. Обратной операцией является встроенная функция ord() , которая принимает односимвольную строку Юникод и возвращает значение кодовой точки:
Преобразование в байты¶
Метод, противоположный bytes.decode() — str.encode() , который возвращает bytes представление строки Юникод, закодированной в запрошенной кодировке.
Параметр errors такой же, как параметр метода decode() , но поддерживает ещё несколько возможных обработчиков. Помимо ‘strict’ , ‘ignore’ и ‘replace’ (который в данном случае вставляет вопросительный знак вместо некодируемого символа), существует также ‘xmlcharrefreplace’ (вставляет ссылку на символ XML), backslashreplace (вставляет escape- последовательность \uNNNN ) и namereplace (вставляет escape- последовательность) \N <. >escape-последовательность).
В следующем примере показаны разные результаты:
Подпрограммы низкого уровня для регистрации и доступа к доступным кодировкам находятся в модуле codecs . Реализация новых кодировок также требует понимания модуля codecs . Однако функции кодирования и декодирования, возвращаемые этим модулем, обычно более низкоуровневые, чем это удобно, а написание новых кодировок — это специализированная задача, поэтому модуль не будет рассматриваться в этом HOWTO.
Литералы Юникода в исходном коде Python¶
В исходном коде Python определенные кодовые точки Юникод могут быть записаны с использованием escape-последовательности \u , за которой следуют четыре шестнадцатеричные цифры, дающие кодовую точку. Управляющая последовательность \U аналогична, но предполагает восемь шестнадцатеричных цифр, а не четыре:
Использование escape-последовательностей для кодовых точек больше 127 нормально в небольших количествах, но становится неприятным, если вы используете много символов с диакритическими знаками, как в программе с сообщениями на французском или другом языке с диакритическими знаками. Вы также можете собрать струны с помощью встроенной функции chr() , но это ещё более утомительно.
В идеале вы хотели бы иметь возможность писать литералы в естественной кодировке вашего языка. Затем вы можете редактировать исходный код Python с помощью вашего любимого редактора, который будет естественным образом отображать символы с диакритическими знаками и использовать правильные символы во время выполнения.
Python по умолчанию поддерживает написание исходного кода в UTF-8, но вы можете использовать практически любую кодировку, если объявите используемую кодировку. Это делается путём включения специального комментария в первой или второй строке исходного файла:
Синтаксис основан на нотации Emacs для определения локальных переменных для файла. Emacs поддерживает множество различных переменных, но Python поддерживает только «coding». Символы -*- указывают Emacs на то, что комментарий особенный; они не имеют значения для Python, но являются соглашением. Python ищет в комментарии coding: name или coding=name .
Если вы не добавите такой комментарий, по умолчанию будет использоваться кодировка UTF-8, как уже упоминалось. См. также PEP 263 для получения дополнительной информации.
Свойства Юникода¶
Спецификация Юникод включает базу данных с информацией о кодовых точках. Для каждой определенной кодовой точки информация включает имя символа, его категорию, числовое значение, если применимо (для символов, представляющих числовые понятия, например, римские цифры, дроби, одна треть и четыре пятых и т. д.). Есть также свойства, связанные с отображением, например, как использовать кодовую точку в двунаправленном тексте.
Следующая программа отображает некоторую информацию о нескольких символах и печатает числовое значение одного символа:
При запуске печатает:
Коды категорий — это аббревиатуры, определяющие характер символа. Они сгруппированы по таким категориям, как «Буква», «Число», «Пунктуация» или «Символ», которые, в свою очередь, разбиты на подкатегории. Чтобы взять коды из вышеприведенного вывода, ‘Ll’ означает «Буква, нижний регистр», ‘No’ означает «Число, другое», ‘Mn’ — «Знак, без пробелов», а ‘So’ — «Символ, другое». Список кодов категорий см. в раздел Общие значения категорий документации по базе данных символов Юникода для списка кодов категорий.
Сравнение строк¶
Юникод усложняет сравнение строк, поскольку один и тот же набор символов может быть представлен разными последовательностями кодовых точек. Например, такая буква, как «ê», может быть представлена как единая кодовая точка U+00EA или как U+0065 U+0302, являющаяся кодовой точкой для «e», за которой следует кодовая точка для «COMBINING CIRCUMFLEX ACCENT» . Они будут производить тот же вывод при печати, но одна — строка длиной 1, а другая — длиной 2.
Одним из инструментов сравнения без учёта регистра является строковый метод casefold() , который преобразует строку в форму без учета регистра в соответствии с алгоритмом, описанным в стандарте Юникод. У алгоритма есть специальная обработка символов, таких как немецкая буква «ß» (кодовая точка U+00DF), которая становится парой строчных букв «ss».
Вторым инструментом является функция normalize() модуля unicodedata , преобразующая строки в одну из нескольких нормальных форм, где буквы, за которыми следует объединяющий символ, заменяются одиночными символами. normalize() можно использовать для выполнения сравнений строк, которые не будут ложно сообщать о неравенстве, если две строки по-разному используют комбинирующие символы:
При запуске выводит:
Первым аргументом функции normalize() является строка, дающая желаемую форму нормализации, которая может быть одной из NFC, NFKC, NFD и NFKD.
Стандарт Юникод также определяет, как проводить сравнения без регистра:
(Почему NFD() вызывается дважды? Поскольку есть несколько символов, которые заставляют casefold() возвращать ненормализованную строку, поэтому результат необходимо снова нормализовать. См. обсуждение и пример в разделе 3.13 стандарта Юникод.)
Регулярные выражения Юникод¶
Регулярные выражения, поддерживаемые модулем re , могут быть представлены в виде байтов или строк. Некоторые последовательности специальных символов (например, \d и \w ) имеют разные значения в зависимости от того, предоставляется ли шаблон в виде байтов или строки. Например, \d будет соответствовать категории символов [0-9] в байтах, но в строках будет соответствовать любому символу, который находится в ‘Nd’ категории.
Строка в этом примере будет числом 57, записанное как тайскими, так и арабскими цифрами:
При запуске \d+ сопоставит тайские цифры и распечатает их. Если вы укажете флаг re.ASCII для compile() , \d+ будет соответствовать подстроке «57».
Аналогично, \w соответствует большому количеству символов Юникод, но только в байтах [a-zA-Z0-9_] или, если указан re.ASCII , а \s будет соответствовать либо пробельным символам Юникод, либо [ \t\n\r\f\v] .
Ссылки¶
Есть несколько хороших альтернативных обсуждений поддержки Юникод в Python:
-
, Ника Коглана. , презентация Неда Батчелдера на PyCon 2012.
Тип str описан в справочнике по библиотеке Python по адресу .
Документация к модулю unicodedata .
Документация к модулю codecs .
Марк-Андре Лембург представил презентацию «Python и Юникод» (слайды в формате PDF) на EuroPython 2002. Слайды представляют собой отличный обзор конструкции функций Юникод Python 2 (где строковый тип Юникод называется unicode , а литералы начинаются с u ).
Чтение и запись данных Юникод¶
Как только вы написали код, который работает с данными Юникод, следующая проблема — ввод/вывод. Как включить строки Юникод в свою программу и как преобразовать Юникод в форму, подходящую для хранения или передачи?
Возможно, вам не потребуется ничего делать в зависимости от ваших источников ввода и назначения вывода; вы должны проверить, поддерживают ли библиотеки, используемые в вашем приложении, Юникод изначально. Например, синтаксические анализаторы XML часто возвращают данные Юникод. Многие реляционные базы данных также поддерживают столбцы со значениями Юникод и могут возвращать значения Юникод из запроса SQL.
Данные Юникода обычно преобразуются в определенную кодировку, прежде чем они будут записаны на диск или отправлены через сокет. Можно выполнить всю работу самостоятельно: открыть файл, прочитать из него 8-битный объект и преобразовать байты с помощью bytes.decode(encoding) . Однако ручной подход не рекомендуется.
Одной из проблем является многобайтовый характер кодировок; один символ Юникода может представляться несколькими байтами. Если вы хотите читать файл фрагментами произвольного размера (скажем, 1024 или 4096 байтов), вам необходимо написать код обработки ошибок, чтобы уловить случай, когда только часть байтов, кодирующих один символ Юникода, читается в конце куска. Одним из решений может быть считывание всего файла в память, а затем выполнение декодирования, но это мешает вам работать с файлами очень большого размера; если вам нужно прочитать файл размером 2 ГиБ, вам потребуется 2 ГиБ ОЗУ. (Более того, поскольку, по крайней мере, на мгновение вам понадобятся как закодированная строка, так и её версия Юникод в памяти.)
Решением будет использование низкоуровневого интерфейса декодирования, чтобы уловить случай частичных последовательностей кодирования. Работа по реализации этого уже была сделана за вас: встроенная функция open() может возвращать файловый объект, который предполагает, что содержимое файла находится в указанной кодировке, и принимает параметры Юникод для таких методов, как read() и write() . Это работает через параметры encoding и errors open() , которые интерпретируются так же, как в str.encode() и bytes.decode() .
Поэтому читать Юникод из файла очень просто:
Также можно открывать файлы в режиме обновления, позволяя читать и писать:
Юникод символ U+FEFF используется в качестве метки порядка байтов (BOM) и часто записывается как первый символ файла, чтобы помочь с автоматическим определением порядка байтов в файле. Некоторые кодировки (например, UTF-16) ожидают, что спецификация будет присутствовать в начале файла; когда используется такая кодировка, спецификация будет автоматически записана как первый символ и будет автоматически отброшена при чтении файла. Существуют варианты этих кодировок, например, «utf-16-le» и «utf-16-be» для кодировок с прямым порядком байтов и обратным порядком байтов, которые определяют один порядок байтов и не пропускают BOM.
В некоторых областях также принято использовать «BOM» в начале файлов в кодировке UTF-8; имя вводит в заблуждение, поскольку UTF-8 не зависит от порядка байтов. Отметка просто сообщает, что файл закодирован в UTF-8. Для чтения таких файлов используйте кодек «utf-8-sig», чтобы автоматически пропустить отметку, если она есть.
Имена файлов в Юникоде¶
Большинство широко используемых сегодня операционных систем поддерживают имена файлов, содержащие произвольные символы Юникод. Обычно это реализуется путём преобразования строки Юникод в некоторую кодировку, которая зависит от системы. Сегодня Python все больше и больше использует UTF-8: Python в MacOS использовал UTF-8 для нескольких версий, а Python 3.6 также перешёл на использование UTF-8 в Windows. В системах Unix кодировка файловой системы будет только в том случае, если вы установили переменные среды LANG или LC_CTYPE ; если нет, кодировка по умолчанию снова UTF-8.
Функция sys.getfilesystemencoding() возвращает кодировку для использования в вашей текущей системе, если вы хотите выполнить кодирование вручную, но нет особых причин для беспокойства. При открытии файла для чтения или записи вы обычно можете просто указать строку Юникод в качестве имени файла, и она будет автоматически преобразована в нужную для вас кодировку:
Функции модуля os (например, os.stat() ) также принимают Юникод имена файлов.
Функция os.listdir() возвращает имена файлов, что вызывает вопрос: должна ли она возвращать версию имен файлов в формате Юникод или должна возвращать байты, содержащие закодированные версии? os.listdir() может делать и то, и другое, в зависимости от того, предоставили ли вы путь к каталогу в байтах или в строке Юникод. Если вы передадите строку Юникод в качестве пути, имена файлов будут декодированы с использованием кодировки файловой системы, и будет возвращен список строк Юникод, а передача байтового пути вернёт имена файлов в байтах. Например, предполагая, что кодировка файловой системы по умолчанию — UTF-8, запустите следующую программу:
вернёт следующий результат:
Первый список содержит имена файлов в кодировке UTF-8, а второй список содержит версии Юникод.
Обратите внимание, что в большинстве случаев вы можете просто использовать Юникод с этими API. API байтов следует использовать только в системах, где могут присутствовать недекодируемые имена файлов; сейчас это в основном только системы Unix.
Советы по написанию программ с поддержкой Юникод¶
В этом разделе представлены некоторые предложения по написанию программного обеспечения, работающего с Юникод.
Самый важный совет:
Если вы попытаетесь написать функции обработки, которые принимают как Юникод, так и байтовые строки, вы обнаружите, что ваша программа уязвима для ошибок везде, где вы комбинируете два разных типа строк. Нет автоматического кодирования или декодирования: если вы это сделаете, например, str + bytes , будет вызвано TypeError .
При использовании данных, поступающих из веб-браузера или другого ненадежного источника, распространенным методом является проверка на недопустимые символы в строке перед использованием строки в сгенерированной командной строке или сохранением её в базе данных. Если вы делаете это, будьте осторожны и проверяйте декодированную строку, а не закодированные байтовые данные; некоторые кодировки могут иметь интересные свойства, например, не быть биективными или не быть полностью совместимыми с ASCII. Это особенно верно, если во входных данных также указана кодировка, поскольку злоумышленник может выбрать хитрый способ скрыть вредоносный текст в закодированном байтовом потоке.
Преобразование между кодировками файлов¶
Класс StreamRecoder может прозрачно преобразовывать между кодировками, принимая поток, который возвращает данные в кодировке №1, и ведёт себя как поток, возвращающий данные в кодировке №2.
Например, если у вас есть входной файл f в Latin-1, вы можете обернуть его StreamRecoder , чтобы вернуть байты в кодировке UTF-8:
Файлы в неизвестной кодировке¶
Что делать, если вам нужно внести изменения в файл, но вы не знаете его кодировку? Если вы знаете, что кодировка совместима с ASCII, и хотите проверить или изменить только части ASCII, вы можете открыть файл с помощью обработчика ошибок surrogateescape :
Обработчик ошибок surrogateescape декодирует любые байты, отличные от ASCII, как кодовые точки в специальном диапазоне от U+DC80 до U+DCFF. Затем эти кодовые точки снова превратятся в те же байты, когда обработчик ошибок surrogateescape используется для кодирования данных и их обратной записи.
Ссылки¶
Одна секция Освоение Ввода-Вывода Python 3, речь на PyCon 2010 Дэвидом Бизли, обсуждает текстовую обработку и обработку двоичных данных.
PDF слайды для презентации Марка-Андре Лембурга «написание Юникод-зависимых приложений на Python» обсуждают вопросы кодирования символ, а также как интернационализировать и локализовать приложение. Эти слайды покрывают Python 2.x только.
Сила Юникода в Python — выступление Бенджамина Питерсона на PyCon 2013, в котором обсуждается внутреннее представление Юникода в Python 3.3.
Благодарности¶
Первоначальный черновик этого документа был написан Эндрю Кучлингом. С тех пор он был переработан Александром Белопольским, Георгом Брандлом, Эндрю Кучлингом и Эцио Мелотти.
Благодарим следующих людей, которые отметили ошибки или предложили предложения по этой статье: Эрик Араужо, Николас Бастин, Ник Коглан, Мариус Гедминас, Кент Джонсон, Кен Круглер, Марк-Андре Лембург, Мартин фон Лёвис, Терри дж. Риди, Сергей Сторчака, Эрик Сан, Чад Уитакр, Грэм Уайдман.
Unicode HOWTO¶
Этот HOWTO обсуждает Python’s поддержку спецификации юникод для представления текстовых данных и объясняет различные проблемы, с которыми люди обычно сталкиваются при работе с юникодом.
Введение в Unicode¶
Определения¶
Сегодняшние программы должны уметь обращаться с самыми разнообразными символами. Приложения часто интернационализированы для отображения сообщений и вывода на различных выбранных пользователем языках; одной и той же программе может потребоваться вывести сообщение об ошибке на английском, французском, японском, иврите или русском языках. Веб-контент может быть написан на любом из этих языков, а также может включать в себя множество эмодзи символов. Тип Python’s строка использует стандарт Unicode для представления знаков, который позволяет программам Python работать со всеми этими различными возможными знаками.
Unicode (https://www.unicode.org/) — спецификация, целью которой является перечисление каждого символ используемый по человеческим языкам и придание каждому символ своего уникального код. Спецификации юникода постоянно пересматриваются и обновляются для добавления новых языков и символов.
Символ является самым маленьким компонентом текста. „A“, „B“, „C“ и т.д. являются разными символами. Так „È“ и „Í“. Символы различаются в зависимости от языка или контекст, о которых вы говорите. Например, есть символ для «Первой римской цифры», „Ⅰ“, которая отделена от прописной буквы „I“. Обычно они выглядят одинаково, но это два разных символа, имеющих разные значения.
Стандарт юникод описывает, как символы представлены в кодовые точки. Значение кодовой точки представляет собой целое число в диапазоне от 0 до 0x10FFFF (около 1,1 миллиона значений, при этом до сих пор назначено около 110 тысяч). В стандарте и в этом документе точка код записывается с помощью обозначения U+265E для обозначения символ со значением 0x265e (9822 в десятичном формате).
Стандарт юникод содержит множество таблиц, содержащих символы и соответствующие им кодовые точки:
Строго, эти определения подразумевают, что бессмысленно сказать, что „это — символ U+265E “. U+265E — точка код, которая представляет некоторый конкретный символ; в данном случае он представляет собой символ „ЧЁРНЫЙ ШАХМАТНЫЙ КОНЬ“, „♞“. В неофициальном контексте это различие между код точками и символами иногда будет забыто.
Символ представлен на экране или на бумаге набором графических элементов, называемых глиф. Глиф для верхнего регистра а, например, представляет собой два диагональных штриха и горизонтальный штрих, хотя точные детали будут зависеть от шрифта, который должен быть используемый. Большинству Python код не нужно беспокоиться о глифах; определение правильного глифа для отображения, как правило, является заданием набора инструментов GUI или средства визуализации шрифтов терминала.
Кодировка¶
Суммировать предыдущий раздел: Unicode строка — последовательность точек код, которые являются числами от 0 до 0x10FFFF (1 114 111 десятичных чисел). Эта последовательность точек код должна быть представлена в памяти как набор кодовые единицы и кодовые единицы затем отображаются на 8-битные байты. Правила для перевода строки Unicode в последовательность байтов называются кодировка символов или просто кодировка.
Первый кодировка, о котором вы могли бы думать, использует 32-битные целые числа в качестве единицы код и затем использует представление центрального процессора 32-битных целых чисел. В этом представлении строка «Python» может выглядеть так:
Это представление является простым, но использование его представляет ряд проблем.
- Он не переносной; разные процессоры упорядочивают байты по-разному.
- Это очень расточительно для пространства. В большинстве текстов большинство точек код меньше 127, или меньше 255, поэтому много места занято 0x00 байтами. Вышеуказанное строка занимает 24 байта по сравнению с 6 байтами, необходимыми для представления ASCII. Увеличение использования ОЗУ не имеет большого значения (настольные компьютеры имеют гигабайты ОЗУ, а строки обычно не так велики), но расширение использования дисков и пропускной способности сети в 4 раза недопустимо.
- Это не совместимо с существующими функциями C, такими как strlen() , таким образом, новая семья широких функций строка должна была бы быть используемый.
Поэтому этот кодировка не используемый очень, и люди вместо этого выбирают другое кодирование, которое более эффективно и удобно, таково как UTF-8.
UTF-8 — один из обычно кодирование используемый и Python часто дефолты к использованию его. UTF означает «формат преобразования юникода», а «8» означает, что 8-битные значения являются используемый в кодировка. (Есть также кодировки UTF-16 и UTF-32, но они реже используемый, чем UTF-8.) UTF-8 использует следующие правила:
- Если точка код равна < 128, она представлена соответствующим значением байта.
- Если точка код > = 128, она превращается в последовательность из двух, трех или четырех байт, где каждый байт последовательности находится между 128 и 255.
UTF-8 обладает несколькими удобными свойствами:
- Это может обработать любую точку Unicode код.
- Unicode строка превращен в пос ледовательность байтов, которая содержит вложенные нулевые байты только там, где они представляют пустой символ (U 0000). Это означает, что UTF-8 строки может обрабатываться C-функциями, такими как strcpy() , и отправляться через протоколы, которые не могут обрабатывать ноль байт для чего- либо, кроме маркеров end-of-строка.
- строка текста ASCII — также действительный текст UTF-8.
- UTF-8 довольно компактна; большинство обычно знаков используемый может быть представлено с одним или двумя байтами.
- Если байты повреждены или потеряны, можно определить начало следующей точки UTF-8-кодированный код и выполнить повторную синхронизацию. Также маловероятно, что случайные 8-битные данные будут выглядеть как действительные UTF-8.
- UTF-8 является байт-ориентированным кодировка. Параметр кодировка указывает, что каждый символ представлен определенной последовательностью из одного или нескольких байтов. Это избегает проблем порядка байт, которые могут произойти с целым числом, и слово ориентировало кодировках, как UTF-16 и UTF-32, где последовательность байтов варьируется в зависимости от аппаратных средств, на которых строка был кодированный.
Ссылки¶
У Сайт Консорциума Юникод есть диаграммы символ, глоссарий и версии PDF спецификации Unicode. Будьте готовы к трудному чтению. Хронология происхождения и разработки юникода также имеется на сайте.
На канале Computerphile Youtube Том Скотт кратко обсуждает история Unicode и UTF-8 (9 минут 36 секунд).
Чтобы помочь понять стандарт, Юкка Корпела написал вводное руководство чтению таблиц Unicode символ.
Ещё один хорошая вступительная статья написал Джоэл Спольский. Если это введение не ясно, перед тем как продолжить, попробуйте прочитать эту альтернативную статью.
Статьи википедии часто полезны; см., например, записи для «кодировка символов» и UTF-8, например.
Поддержка Unicode Python’ом¶
Теперь, когда вы изучили рудименты Unicode, мы можем посмотреть на особенности Unicode Python’на.
Тип строки¶
Начиная с Python 3.0 тип str языка содержит знаки Unicode, означая, что любой строка создал использование "unicode rocks!" , ‘unicode rocks!’ , или трижды указанный синтаксис строка сохранен как Unicode.
кодировка по умолчанию для источника Python, код — UTF-8, таким образом, вы можете просто включать Unicode символ в литерал строка:
Примечание: Python 3 также поддерживает использование символов юникода в идентификаторах:
Если вы не можете войти в особый символ в своего редактора или хотеть сохранить источник код только для ASCII по некоторым причинам, вы можете также использовать последовательности побега в опечатках строка. (В зависимости от вашей системы, вы можете увидеть фактический глиф capital-delta вместоu escape.):
Кроме того, можно создать строка с помощью decode() метод bytes . Этот метод берет аргумент encoding, такой как UTF-8 и произвольно аргумент errors.
Аргумент errors определяет ответ, когда вход строка не может быть преобразован согласно правилам кодирования. Допустимые значения для этого аргумента: ‘strict’ (вызвать исключение UnicodeDecodeError ), ‘replace’ (использовать U+FFFD , REPLACEMENT CHARACTER ), ‘ignore’ (просто оставить символ вне результата юникода) или ‘backslashreplace’ (вставляет последовательность \xNN escape). Следующие примеры показывают различия:
Кодировки указываются как строки, содержащие имя кодировки. Python поставляется с примерно 100 различными кодировками; см. Ссылку библиотеки Python в Стандартные кодировки для списка. Некоторые кодировки имеют несколько названий; например, ‘latin-1’ , ‘iso_8859_1’ и ‘8859 „все являются синонимами для одного и того же кодировка.
Один-символ Unicode строки может также быть создан со встроенной функцией chr() , которая получает целые числа и возвращает Unicode строка из длины 1, который содержит соответствующую кодовую точку. Обратная операция — встроенная функция ord() , которая принимает один-символ Unicode строки и возвращает значение кодовой точки:
Преобразование в байты¶
метод противоположного bytes.decode() — str.encode() , который возвращает представление bytes последовательности Unicode, кодированный в запрошенный encoding.
Параметр errors совпадает с параметром decode() метод, но поддерживает еще несколько возможных обработчиков. А также ‘strict’ , ‘ignore’ и ‘replace’ (который в этом случае вставляет вопросительный знак вместо юникодного символа), есть также ‘xmlcharrefreplace’ (вставляет ссылку XML символ), backslashreplace (вставляет экранированную последовательность \uNNNN ), и namereplace (вставляет экранированную последовательность \N <. >).
В следующем примере показаны различные результаты:
Установленный порядок низкоуровневое для регистрации и доступа к доступному кодированию найден в модуле codecs . Внедрение новых кодировок также требует понимания модуля codecs . Однако функции кодировка и декодирования, возвращаемые этим модулем, обычно более низкоуровневое, чем удобно, и запись новых кодировок является специализированной задачей, поэтому модуль не будет охватываться этим HOWTO.
Литералы Unicode в исходном коде Python¶
В исходном коде Python определенные точки Unicode код могут быть написаны, используя последовательность побега \u , которая сопровождается четырьмя шестнадцатеричными цифрами, дающими точку код. Последовательность побега \U подобна, но ожидает восемь шестнадцатеричных цифр, не четыре:
Использование escape последовательностей для код точек больше 127 хорошо в малых дозах, но становится раздражением, если вы используете много акцентированных символов, как вы бы в программе с сообщениями на французском или каком-то другом языке использования акцента. Вы также можете собрать строки с помощью встроенной функции chr() , но это еще более утомительно.
Идеально, вы хотели бы написать опечатки в естественном кодировка своего языка. Затем вы можете редактировать Python source код с помощью любимого редактора, который будет отображать символы с акцентом естественно, и иметь правильные символы используемый во время выполнения.
По умолчанию Python поддерживает запись источника код в UTF-8, но можно использовать почти любой кодировка, если объявить кодировка используемый. Это выполняется путем включения специального комментария в качестве первой или второй строки исходного файла:
Синтаксис вдохновлен примечанием эмакса для определения переменных локальная к файлу. Emacs поддерживает множество различных переменных, но Python поддерживает только «кодирование». Символы -*- указывают компании Emacs, что комментарий является особенным; они не имеют никакого значения для Python, но являются конвенцией. Python ищет coding: name или coding=name в комментарии.
Если вы не будете включать такой комментарий, то кодировка используемый по умолчанию будет UTF-8, как уже упомянуто. Для получения дополнительной информации см. также раздел PEP 263.
Свойства Unicode¶
Спецификация юникода включает в себя базу данных информации о код точках. Для каждой определенной точки код информация включает в себя имя символа, его категорию, числовое значение, если применимо (для символов, представляющих числовые понятия, такие как римские цифры, доли, такие как одна треть и четыре пятых и т.д.). Существуют также свойства, связанные с отображением, например, способ использования точки код в двунаправленном тексте.
В следующей программе отображается некоторая информация о нескольких символах и печатается числовое значение одного конкретного символа:
При запуске это печатает:
Категория коды представляет собой сокращения, описывающие характер символ. Они сгруппированы в такие категории, как «Буква», «Число», «Пунктуация» или «Символ», которые в свою очередь разбиваются на подкатегории. Для получения коды из указанного выше вывода, ‘Ll’ означает «Буква, строчный регистр», ‘No’ означает «Число, другое», ‘Mn’ означает «Маркировать, не пространственно», а ‘So’ означает «Символ, другое». Список категорий раздел Общие значения категорий документации по базе данных символов Unicode см. в разделе коды.
Сравнение строк¶
Юникод добавляет некоторое усложнение к сравнению строк, поскольку один и тот же набор символов может быть представлен различными последовательностями код точек. Например, буква типа «к» может быть представлена как единственная точка код U+00EA, или как U+0065 U+0302, которая является точкой код для «е», за которой следует точка код для «комбинирования акцента CIRCUMFLEX». Они будут выдавать те же выходные данные при печати, но один является строка длины 1, а другой — длиной 2.
Одним из инструментов для сравнения без учета регистра является casefold() строка метод, который преобразует строка в форму без учета регистра в соответствии с алгоритмом, описанным в стандарте юникод. Этот алгоритм имеет специальную обработку для символов, таких как немецкая буква „s“ (точка код U+00DF), которая становится парой строчных букв „ss“.
Второй инструмент — функция normalize() модуля unicodedata , которая преобразовывает строки в одну из нескольких нормальных форм, где письма, сопровождаемые объединением символ, заменены единственными знаками. normalize() может быть используемый для выполнения сравнений строка, которые не будут ложно сообщать о неравенстве, если два строки используют объединение символов по-разному:
При выполнении это выводит:
Первым аргументом функции normalize() является строка, дающий желаемую форму нормализации, которая может быть одной из „NFC“, „NFKC“, „NFD“ и „NFKD“.
Стандарт юникода также определяет способ безрегистрового сравнения:
Это будет печатать True . (Почему NFD() вызывается дважды? поскольку существует несколько символов, которые заставляют casefold() возвращать ненормализованную строку, результат необходимо нормализовать снова. Обсуждение и пример см. в разделе 3.13 стандарта юникод.)
Регулярные выражения Unicode¶
Регулярные выражения, поддерживаемые модулем re , могут быть предоставлены в байтах или строки. Некоторые специальные последовательности символ, такие как \d и \w , имеют различное значение в зависимости от того, подается ли шаблон в виде байтов или строка. Например, \d будет соответствовать символам [0-9] в байтах, но в строки будет соответствовать любому символ, который находится в категории ‘Nd’ .
строка в этом примере имеет число 57, написанное как тайскими, так и арабскими цифрами:
При выполнении \d+ будет совпадать с тайскими цифрами и распечатывать их. Если вы будете поставлять флаг re.ASCII compile() , то \d+ будет соответствовать подстроке «57» вместо этого.
Аналогично, \w соответствует широкому спектру символов юникода, но только [a-zA-Z0-9_] в байтах или если задан параметр re.ASCII , и \s будет соответствовать символам пробела юникода или [ \t\n\r\f\v] .
Ссылки¶
Некоторые хорошие альтернативные обсуждения Python Unicode поддерживают:
-
, Ник Коглан. , презентация PyCon 2012 Недом Батчелдером.
Тип str описан в справочнике библиотеки Python по адресу Тип последовательности текста — str .
Документация для модуля unicodedata .
Документация для модуля codecs .
Марк-Андре Лембург предоставил презентацию под названием «Python и Unicode» (PDF слайды) на EuroPython 2002. Слайды — превосходный обзор дизайна особенностей Python 2’s Unicode (где тип Unicode строка называют unicode , и опечатки начинаются с u ).
Чтение и запись данных в юникоде¶
После того, как вы написали некоторые код, которые работают с данными юникода, следующей проблемой будет ввод/вывод. Как вы получаете Unicode строки в свою программу, и как делают вас новообращенный уникоуд в форму, подходящую для места хранения или передачи?
Возможно, что вам не потребуется делать что-либо в зависимости от источников ввода и адресатов вывода; вы должны проверить, поддерживают ли библиотеки используемый в вашем заявлении Unicode с рождения. Например, XML парсерами часто возвращает данные юникода. Многие реляционные базы данных также поддерживают столбцы с значениями юникода и могут возвращать значения юникода из запроса SQL.
Данные юникода обычно преобразуются в определенный кодировка перед записью на диск или передачей через сокет. Всю работу можно сделать самостоятельно: открыть файл, прочитать из него 8-битный байтовый объект и преобразовать байты с помощью bytes.decode(encoding) . Однако ручной подход не рекомендуется.
Одной из проблем является многобайтовая природа кодировок; один Unicode символ может быть представлен на несколько байтов. Если вы хотите читать, файл в чанки произвольного размера (скажем, 1024 или 4 096 байтов), вы должны написать обработке ошибок код, чтобы поймать случай, где только часть байтов кодировка единственный Unicode символ прочитана в конце чанк. Одним из решений было бы считывание всего файла в память и последующее выполнение декодирования, но это не позволяет работать с файлами, которые являются чрезвычайно большими; если необходимо прочитать файл 2 GiB, необходимо 2 GiB оперативной памяти. (Более того, действительно, так как по крайней мере на мгновение вам нужно будет иметь в памяти и кодированный строка, и его версию юникод.
Решение заключается в использовании интерфейса декодирования низкоуровневое для захвата случая последовательностей частичного кодирования. Работа осуществления этого была уже сделана для вас: встроенная функция open() может возвратить подобный файлу объект, который предполагает, что содержание файла находится в указанном кодировка, и принимает параметры Unicode для методы, такие как read() и write() . Это работает через open() encoding и errors параметры, которые интерпретируются так же, как в str.encode() и bytes.decode() .
Поэтому чтение юникода из файла является простым:
Также можно открывать файлы в режиме обновления, позволяя как чтение, так и запись:
Unicode символ U+FEFF — используемый как отметка порядка байтов (BOM) и часто пишется как первый символ файла, чтобы помочь с автообнаружением заказа байта файла. Некоторые кодировки, такие как UTF-16, ожидают присутствия ведомости материалов в начале файла; когда такой кодировка будет использоваться, BOM будет автоматически написан как первый символ и будет тихо пропущен, когда файл прочитан. Существуют варианты этих кодировок, такие как „utf-16-le“ и „utf-16-be“ для кодировок little-endian и big-endian, которые определяют один конкретный порядок байтов и не пропускают BOM.
В некоторых областях также рекомендуется использовать «BOM» в начале файлов UTF-8 кодированный; имя вводит в заблуждение, поскольку UTF-8 не зависит от порядка байтов. Отметка просто объявляет, что файл — кодированный в UTF-8. Для чтения таких файлов используйте кодировка «utf-8-sig», чтобы автоматически пропустить метку при ее наличии.
Имена файлов Unicode¶
Большинство операционных систем, используемых в настоящее время, поддерживают имена файлов, содержащие произвольные символы юникода. Обычно это осуществлено, преобразовав Unicode строка в некоторый кодировка, который варьируется в зависимости от системы. Сегодня Python сходится на использовании UTF-8: Python на MacOS имеет используемый UTF-8 для нескольких версий, и Python 3.6 переключился на использование UTF-8 на Windows. На системах Unix только будет файловая система кодировка, если вы установили LANG или переменные окружения LC_CTYPE ; если вы не имеете, дефолт, кодировка — снова UTF-8.
Функция sys.getfilesystemencoding() возвращает кодировка для использования в текущей системе, в случае, если вы хотите сделать кодировка вручную, но нет много причин беспокоить. Открывая файл для чтения или написания, вы можете обычно просто обеспечивать Unicode строка как имя файла, и это будет автоматически преобразовано направо кодировка для вас:
Функции модуля os , такие как os.stat() , также принимают имена файлов юникода.
Функция os.listdir() возвращает имена файлов, что вызывает проблему: должна ли она возвращать юникод-версию имен файлов, или должна возвращать байты, содержащие версии кодированный? os.listdir() может сделать обоих, в зависимости от того, обеспечили ли вы путь к директории как байты или Unicode строка. Если вы передадите Unicode строка как путь, то имена файлов будут расшифрованы, используя кодировка файловой системы, и список Unicode строки будет возвращен, в то время как прохождение пути байта возвратит имена файлов как байты. Например, при условии, что файловая система по умолчанию кодировка имеет значение UTF-8, выполняется следующая программа:
выведет следующие выходные данные:
Первый список содержит имена файлов UTF-8-кодированный, а второй — версии юникода.
Следует отметить, что в большинстве случаев с помощью этих API можно просто использовать юникод. Байты API должны быть используемый только в системах, где могут присутствовать недекодируемые имена файлов; это практически только системы Unix.
Советы по написанию программ с поддержкой юникода¶
В этом разделе приведены некоторые рекомендации по написанию программного обеспечения, относящегося к юникоду.
Самый важный совет:
При попытке написать функции обработки, которые принимают строки юникода и байтов, программа будет уязвима для ошибок, где бы вы ни объединяли два различных вида строки. Нет автоматического кодировка или декодирования: если вы делаете, например, str + bytes , будет поднят TypeError .
При использовании данных, поступающих из веб-браузера или какого-либо другого ненадежного источника, общепринятой методикой является проверка недопустимых символов в строка перед использованием строка в сгенерированной командной строке или сохранение их в базе данных. Если вы делаете это, внимательно проверьте декодированную строку, а не данные кодированный байт; некоторые кодировки могут иметь интересные свойства, такие как не являются биективными или не являются полностью ASCII-совместимыми. Это особенно верно, если входные данные также задают кодировку, так как злоумышленник затем может выбрать умный способ скрыть вредоносный текст в bytestream кодированный.
Преобразование между кодировками файлов¶
StreamRecoder класс может прозрачно преобразовывать между кодировками, принимая поток, который возвращает данные в кодировка # 1, и вести себя как поток, возвращающий данные в кодировка # 2.
Например, если у вас есть входной файл f, который находится в Latin-1, вы можете обернуть его StreamRecoder , чтобы вернуть байты кодированный в UTF-8:
Файлы в неизвестной кодировке¶
Что вы можете сделать, если вам нужно изменить файл, но не знаете кодировку файла? если вы знаете, что кодировка совместим с ASCII, и только хотят исследовать или изменить части ASCII, вы можете открыть файл с ошибочным обработчиком surrogateescape :
Обработчик ошибок surrogateescape декодирует любые байты, не являющиеся байтами ASCII, как точки код в специальном диапазоне от U+DC80 до U+DCFF. Эти точки код тогда возвратятся в те же байты, когда ошибочный обработчик surrogateescape будет используемый, чтобы закодировать данные и написать его в ответ.
Ссылки¶
Одна секция Освоение Ввода-Вывода Python 3, речь на PyCon 2010 Дэвидом Бизли, обсуждает текстовую обработку и обработку двоичных данных.
PDF слайды для презентации Марка-Андре Лембурга «написание Юникод-зависимых приложений на Python» обсуждают вопросы кодирования символ, а также как интернационализировать и локализовать приложение. Эти слайды покрывают Python 2.x только.
Сила Юникода в Python — это выступление Бенджамина Питерсона на PyCon 2013, в котором обсуждается внутреннее представление юникода в Python 3.3.
Благодарности¶
Первоначальный проект этого документа написал Эндрю Кучлинг. С тех пор он был пересмотрен Александром Белопольским, Георгом Брандлем, Эндрю Кучлингом и Эцио Мелотти.
Спасибо следующим людям, которые отметили ошибки или предложили изменения этой статьи: Éric Araujo, Nicholas Bastin, Nick Coghlan, Marius Gedminas, Kent Johnson, Ken Krugler, Marc-André Lemburg, Martin von Löwis, Terry J. Reedy, Serhiy Storchaka, Eryk Sun, Chad Whitacre, Graham Wideman.
Как работать с Unicode в Python
Unicode (Юникод) — это стандарт кодирования символов для большинства компьютеров. Он гарантирует, что текст — включая буквы, символы, эмодзи и даже управляющие символы — будет выглядеть одинаково на разных устройствах, платформах и в цифровых документах, независимо от операционной системы или программного обеспечения. Это важная составляющая интернета и компьютерной индустрии в целом. Без него всё было бы сложно и хаотично.
Unicode сам по себе не является кодировкой, а больше похож на базу данных почти всех возможных символов. В нём есть кодовая точка (идентификатор для каждого символа в базе данных), которая может иметь значение от 0 до 1,1 миллиона – как видите, скорее всего в ближайшее время эти уникальные кодовые точки не закончатся. Каждая кодовая точка в Unicode обозначается U+n, где U+ — кодовая точка Unicode, а n — это набор для символа из четырех-шести шестнадцатеричных цифр. Unicode намного надежнее ASCII, в котором только 128 символов. Обмен цифровым текстом с помощью ASCII сложнее, так как он основан на американском английском и не поддерживает символы с диакритическими знаками. А в Unicode почти 150 000 символов и он охватывает символы всех языков мира.
Поэтому от языков программирования и требуется правильно обрабатывать текст и обеспечивать интернационализацию программного обеспечения. Python используют для разных целей — от электронной почты до серверов и интернета — у него отличный способ обработки Unicode, он принимает стандарт Unicode для своих строк.
Иногда в работе с Unicode в Python могут возникать трудности и ошибки. В этом мануале представлены основы работы Unicode в Python, которые помогут вам избежать этих проблем. С помощью Python мы интерпретируем Unicode, применим к Unicode функцию нормализации и обработаем ошибки.
Требования
- Python, установленный локально или на удаленном сервере. Если у вас еще не установлен Python, ознакомьтесь с этим мануалом.
- Знание основ программирования и строковых методов Python. Читайте Основы работы со строками в Python 3.
- Знание принципов работы с интерактивной консолью Python. Читайте мануал Использование интерактивной консоли Python.
1: Конвертирование кодовых точек Unicode в Python
Кодирование — это процесс представления данных в читаемой компьютером форме. Существуют разные способы кодирования данных — ASCII, Latin-1 и т. д. У каждой кодировки свои сильные и слабые стороны, но пожалуй, самой распространенной является UTF-8 — тип кодирования, который отображает символы со всего мира в одном наборе. То есть, UTF-8 это незаменимый инструмент для всех, кто работает с интернационализированными данными. В целом, UTF-8 справляется с многими задачами. Он относительно эффективен и может работать в разных программах. UTF-8 конвертирует кодовую точку Unicode в понятные компьютеру шестнадцатеричные байты. Другими словами, Unicode – это маппинг, а UTF-8 позволяет компьютеру понять этот маппинг.
В Python 3 кодировка строк по умолчанию – UTF-8, значит, любая кодовая точка Unicode в строке Python автоматически конвертируется в соответствующий символ.
Сейчас мы создадим символ авторского права (©) с помощью его кодовой точки Unicode в Python. Сначала запустите интерактивную консоль Python в терминале, а затем введите:
В этом коде мы создали строку s с кодовой точкой Unicode \u00A9. Как упоминалось ранее, поскольку строка Python по умолчанию использует кодировку UTF-8, вывод значения s автоматически заменяет его на соответствующий символ Unicode. Обратите внимание, что \u в начале кода обязателен. Без него Python не сможет конвертировать кодовую точку. В выводе получим соответствующий символ Unicode:
В Python есть встроенные функции для кодирования и декодирования строк. Функция encode() конвертирует строку в байтовую строку.
Для этого откройте интерактивную консоль Python и введите код:
В результате получим байтовую строку символа:
Обратите внимание, что перед каждым байтом стоит \x, значит, это шестнадцатеричное число.
Примечание: Ввод спецсимволов Unicode в Windows и Mac отличается. В Windows предыдущий и все последующие примеры этого мануала, где используются символы, вы можете вставить с помощью утилиты Character Map. В Mac нет этой функции, поэтому лучше скопировать символ из примера кода.
Далее с помощью функции decode() конвертируем байтовую строку в обычную. Функция decode() принимает в качестве аргумента тип кодировки. Отметим, что функция decode() может декодировать только байтовую строку, которая задается с помощью буквы b в начале строки. Удаление b приведет к ошибке AttributeError.
В консоли введите:
Получим следующий вывод:
Теперь у вас есть базовое понимание интерпретации Unicode в Python. Далее мы разберем встроенный в Python модуль unicodedata, чтобы применить расширенные методы Unicode для строк.
2: Нормализация Unicode в Python
С помощью нормализации можно определить, одинаковы ли два символа, написанные разными шрифтами. Это удобно, когда два символа с разными кодовыми точками дают одинаковый результат. Например, мы воспринимаем символы Unicode R и ℜ как одинаковые, поскольку они оба представляют собой букву R, но для компьютера они разные.
Следующий пример кода это демонстрирует. Откройте консоль Python и введите следующее:
В результате получим:
Вывод будет False, потому что Python не считает эти два символа одинаковыми. Именно поэтому нормализация важна при работе с Unicode.
В Unicode некоторые символы создаются путем объединения нескольких символов в один. Нормализация также важна в этом случае, потому что она обеспечивает согласованность строк. Рассмотрим это на примере. Откройте консоль Python и введите код:
В этом коде мы создали строку s1, содержащую символ ô, а строка s2 содержит кодовую точку символа циркумфлекса ( ̂ ). Получим такой вывод:
Это значит, что две строки состоят из одинаковых символов, но имеют разную длину. Значит, они не подходят под условие равенства. Чтобы это проверить, введите в той же консоли:
Хотя строковые переменные s1 и s2 производят один и тот же символ Unicode, они различаются по длине и, следовательно, не равны.
Решить эту проблему можно с помощью функции normalize().
3: Нормализация Unicode с помощью NFD, NFC, NFKD и NFKC
Сейчас мы нормализуем строки Unicode с помощью функции normalize() из библиотеки unicodedata Python в модуле unicodedata (он обеспечивает поиск и нормализацию символов). Функция normalize() может принимать форму нормализации в качестве первого аргумента и нормализуемую строку в качестве второго аргумента. В Unicode существует четыре типа форм нормализации: NFD, NFC, NFKD и NFKC.
NFD разбивает символ на несколько комбинируемых символов. Это делает текст нечувствительным к диакритическим символам, что удобно при поиске и сортировке. Для этого нужно закодировать строку в байты.
Откройте консоль и введите следующее:
Получим следующий вывод:
При нормализации строки s1 ее длина увеличилась на один символ. Это происходит по причине того, что символ ô разбивается на два символа — o и ˆ. Следующий код это подтвердит:
В результате, после кодирования нормализованной строки символ o отделился от символа ˆ в строке s1_nfd:
Форма нормализации NFC раскладывает символ, а затем перекомпонует его с любым доступным объединяющим символом. W3C рекомендует использовать NFC в интернете, поскольку NFC компонует строку для получения максимально короткого результата. Ввод с клавиатуры по умолчанию возвращает составленные строки, поэтому в этом случае рекомендуется применять NFC.
Для примера введите в интерактивную консоль:
Вывод будет следующим:
При нормализации строки s2 её длина уменьшилась на единицу. Введите код в интерактивной консоли:
Вывод будет таким:
Символы o и ˆ соединены в один символ ô.
Формы нормализации NFKD и NFKC применяют для «строгой» нормализации и поиска, сопоставления образцов в строках Unicode. “K” в NFKD и NFKC означает совместимость.
NFD и NFC разделяют символы, но NFKD и NFKC выполняют разделение на совместимость для непохожих эквивалентых символов, при этом удаляются любые различия форматирования. Например, строка ②① не похожа на 21, но обе имеют одно значение. Формы нормализации NFKC и NFKD удаляют форматирование (в данном случае круг вокруг цифр) из символов, чтобы представить их упрощенную форму.
На примере разберем разницу между NFD и NFKD. Откройте интерактивную консоль Python и введите:
Получаем следующий вывод:
Форма NFD не смогла разделить символ экспоненты в строке s1, но NFKD вырезала форматирование экспоненты и заменила символ совместимости (в данном случае экспоненту 5) на его эквивалент (5 в виде цифры). Так как NFD и NFKD разделяют символы, следовательно, ô увеличит длину на единицу. Это подтвердит следующее:
Принцип работы NFKC аналогичный, но он скорее не разделяет символы, а компонует их. В консоли Python введите:
Вывод будет следующим:
Строка для символа ô уменьшится на единицу, поскольку в NFKC композиционный подход (в случае разделения значение увеличивается на единицу). Проверим это с помощью кода:
Мы разобрали типы нормализации и различия между ними. Далее мы устраним ошибки Unicode в Python.
4: Устранение ошибок Unicode в Python
При работе с Unicode в Python могут возникать два типа ошибок: UnicodeEncodeError и UnicodeDecodeError. Теперь разберем пути их устранения.
Устранение ошибки UnicodeEncodeError
Кодирование в Unicode — это процесс конвертирования строки Unicode в байты с помощью определенной кодировки. Ошибка UnicodeEncodeError возникает при попытке закодировать строку, символы которой не могут быть представлены в заданной кодировке.
Чтобы создать эту ошибку, давайте попробуем закодировать строку с символами, которые не входят в набор ASCII.
Откройте консоль и введите:
Получим следующий результат:
Наконец, введите следующее:
При запуске этого кода возникнет ошибка:
UnicodeEncodeError: ‘ascii’ codec can’t encode character ‘\ufb06’ in position 0: ordinal not in range(128)
В ASCII ограниченное количество символов, поэтому Python выдает ошибки при нахождении символа, которого нет в кодировке ASCII. Поскольку кодовая система ASCII не распознает кодовую точку \ufb06, Python выдаст сообщение об ошибке. В нем идет речь о том, что ASCII имеет диапазон только 128 символов, а соответствующий десятичный эквивалент этой кодовой точки не входит в этот диапазон.
UnicodeEncodeError можно обработать с помощью аргумента errors в функции encode(). У аргумента errors может быть одно из трех значений: ignore, replace и xmlcharrefreplace.
Откройте консоль и введите:
Получим следующий вывод:
Вывод будет таким:
В результате получим:
Во всех случаях Python не выдает ошибку. Значение
ignore пропускает символ, который не может быть закодирован; replace заменяет символ знаком ?; а xmlcharrefreplace заменяет некодируемые символы сущностью XML.
Устранение ошибки UnicodeDecodeError
Ошибка UnicodeEncodeError возникает при попытке декодировать строку, символы которой не могут быть представлены в заданной кодировке.
Чтобы создать эту ошибку, мы попробуем декодировать строку байтов в кодировку, которую невозможно декодировать.
Откройте консоль и введите:
Получим следующую ошибку:
Если вы столкнулись с этой ошибкой, можете применить аргумент errors в функции decode(), c помощью которого можно декодировать строку. Аргумент errors принимает два значения: ignore и replace.
Откройте консоль Python и введите код:
Вывод будет следующим:
Получим такой вывод:
Значение replace в функции decode() добавляет символ �, а ignore ничего не возвращает, поскольку декодер (в данном случае utf-8) не смог декодировать байты.
При декодировании строки следует помнить, что предполагать ее кодировку нельзя, нужно точно знать, как именно она была закодирована.
Подводим итоги
В этом мануале мы рассмотрели основы работы Unicode в Python. Мы кодировали и декодировали строки, нормализовали данные с помощью NFD, NFC, NFKD и NFKC, а также устранили ошибки Unicode. Также мы применили формы нормализации в сценариях сортировки и поиска. Эти методы помогут устранить ошибки Unicode с помощью Python. Рекомендуем ознакомиться с материалами модуля unicodedata .