UTF8 strings and characters/ru: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: UTF8 strings and characters/ru
| Old | New | Line | |
|---|---|---|---|
| 1 | + | ## Красота UTF-8 | |
| 2 | + | ||
| 3 | + | Байты, начинающиеся с '0' (0xxxxxxx), зарезервированы для [[ASCII/ru|ASCII]]-совместимых однобайтовых символов. В многобайтовых кодовых точках число 1 в старшем байте определяет количество байтов, которое занимает кодовая точка. Наподобие этого: | |
| 4 | + | ||
| 5 | + | - 1 byte : 0xxxxxxx | |
| 6 | + | - 2 bytes : 110xxxxx 10xxxxxx | |
| 7 | + | - 3 bytes : 1110xxxx 10xxxxxx 10xxxxxx | |
| 8 | + | - 4 bytes : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | |
| 9 | + | ||
| 10 | + | Конструкция [[UTF-8/ru|UTF-8]] имеет некоторые преимущества по сравнению с другими кодировками: | |
| 11 | + | ||
| 12 | + | - Он обратно совместим с ASCII и создает компактные данные для западных языков. ASCII также используется в тегах языка разметки и других метаданных, что дает UTF-8 преимущество с любым языком. Однако обратная совместимость не распространяется на код, так как код должен быть пересмотрен, чтобы избежать искажения строк utf8. | |
| 13 | + | - Целостность многобайтовых данных может быть проверена по количеству '1'-битов в начале каждого байта. | |
| 14 | + | - Вы всегда можете найти начало многобайтовой кодовой точки, даже если вы перепрыгнули на случайную позицию байта. | |
| 15 | + | - Байт в определенной позиции в многобайтовой последовательности никогда не может быть перепутан с другими байтами. Это позволяет использовать старые быстрые строковые функции, такие как `Pos()` и `Copy()` во многих ситуациях. Смотрите примеры ниже. | |
| 16 | + | ||
| 17 | + | > **Note:** аналогичные функции целостности также существуют в UTF-16. (Диапазон D800 сигнализирует первый суррогат, сигнал диапазона DC00 - второй суррогат). | |
| 18 | + | ||
| 19 | + | - Надежный код. Код, который имеет дело с кодовыми точками, всегда должен быть выполнен правильно с UTF-8, потому что многобайтовые кодовые точки являются общими. Для UTF-16 есть много небрежного кода, который предполагает, что кодовые точки имеют фиксированную ширину. | |
| 20 | + | - Большая часть текстовых данных, перемещаемых в Интернете, кодируется как UTF-8. Обработка данных непосредственно как UTF-8 исключает бесполезные преобразования. Многие (связанные с Unix) операционные системы изначально используют UTF-8. | |
| 21 | + | ||
| 22 | + | ## Примеры | |
| 23 | + | ||
| 24 | + | Простой перебор символов, как если бы строка представляла собой массив элементов одинакового размера, не работает с Юникодом. Это не что-то конкретное для UTF-8, стандарт Unicode сложен, а слово «символ» неоднозначно. Если вы хотите перебрать все кодовые точки строки UTF-8, есть два основных способа: | |
| 25 | + | ||
| 26 | + | - перебирать все байты - полезно для поиска подстроки или при просмотре только символов ASCII в строке UTF8, например, при разборе файлов XML. | |
| 27 | + | - перебирать все кодовые точки или символы - полезно для графических компонентов, таких как [[SynEdit/ru|SynEdit]], например, когда вы хотите узнать третий напечатанный символ на экране. | |
| 28 | + | ||
| 29 | + | ### Поиск подстроки | |
| 30 | + | ||
| 31 | + | Из-за особой природы UTF8 вы можете просто использовать обычные строковые функции для поиска в подстроке. Поиск правильной строки UTF-8 с помощью Pos всегда будет возвращать правильную позицию байта UTF-8: | |
| 32 | + | ||
| 33 | + | ``` pascal | |
| 34 | + | uses LazUTF8; | |
| 35 | + | ... | |
| 36 | + | procedure Where(SearchFor, aText: string); | |
| 37 | + | var | |
| 38 | + | BytePos: LongInt; | |
| 39 | + | CodepointPos: LongInt; | |
| 40 | + | begin | |
| 41 | + | BytePos:=Pos(SearchFor,aText); | |
| 42 | + | CodepointPos:=UTF8Pos(SearchFor,aText); | |
| 43 | + | writeln('Подстрока "',SearchFor,'" находится в тексте "',aText,'"', | |
| 44 | + | ' в позиция байта ',BytePos,' и в позиция кодовой точки ',CodepointPos); | |
| 45 | + | end; | |
| 46 | + | ``` | |
| 47 | + | ||
| 48 | + | ### Поиск и копирование | |
| 49 | + | ||
| 50 | + | Еще один пример того, как `Pos()`, `Copy()` и `Length()` работают с UTF-8. Эта функция не имеет кода для работы с кодировкой UTF-8, но она всегда работает с любым действительным текстом UTF-8. | |
| 51 | + | ||
| 52 | + | ``` pascal | |
| 53 | + | function SplitInHalf(Txt, Separator: string; out Half1, Half2: string): Boolean; | |
| 54 | + | var | |
| 55 | + | i: Integer; | |
| 56 | + | begin | |
| 57 | + | i := Pos(Separator, Txt); | |
| 58 | + | Result := i > 0; | |
| 59 | + | if Result then | |
| 60 | + | begin | |
| 61 | + | Half1 := Copy(Txt, 1, i-1); | |
| 62 | + | Half2 := Copy(Txt, i+Length(Separator), Length(Txt)); | |
| 63 | + | end; | |
| 64 | + | end; | |
| 65 | + | ``` | |
| 66 | + | ||
| 67 | + | ### Перебор строки в поисках символов ASCII | |
| 68 | + | ||
| 69 | + | Если вы хотите найти только символы в ASCII-области, вы можете использовать тип Char и сравнивать с Txt\[i\], как в старые времена. Большинство парсеров делают это, и они продолжают работать. | |
| 70 | + | ||
| 71 | + | ``` pascal | |
| 72 | + | procedure ParseAscii(Txt: string); | |
| 73 | + | var | |
| 74 | + | i: Integer; | |
| 75 | + | begin | |
| 76 | + | for i:=1 to Length(Txt) do | |
| 77 | + | case Txt[i] of | |
| 78 | + | '(': PushOpenBracketPos(i); | |
| 79 | + | ')': HandleBracketText(i); | |
| 80 | + | end; | |
| 81 | + | end; | |
| 82 | + | ``` | |
| 83 | + | ||
| 84 | + | ### Итерация по строке в поисках символов Юникода или текста | |
| 85 | + | ||
| 86 | + | Если вы хотите найти все вхождения определенного символа или подстроки в строку, вы можете повторно вызывать PosEx(). | |
| 87 | + | ||
| 88 | + | Если вы хотите проверить другой текст внутри цикла, вы все равно можете использовать быстрые функции Copy() и Length(). Можно использовать специальные функции UTF-8, но они не нужны. | |
| 89 | + | ||
| 90 | + | ``` pascal | |
| 91 | + | procedure ParseUnicode(Txt: string); | |
| 92 | + | var | |
| 93 | + | Ch1, Ch2, Ch3: String; | |
| 94 | + | i: Integer; | |
| 95 | + | begin | |
| 96 | + | Ch1 := 'Й'; // Символы для поиска. Они также могут | |
| 97 | + | Ch2 := 'ﯚ'; // быть комбинированными кодовыми точками или более длинным текстом | |
| 98 | + | Ch3 := 'Å'; | |
| 99 | + | for i:=1 to Length(Txt) do | |
| 100 | + | begin | |
| 101 | + | if Copy(Txt, i, Length(Ch1)) = Ch1 then | |
| 102 | + | DoCh1(...) | |
| 103 | + | else if Copy(Txt, i, Length(Ch2)) = Ch2 then | |
| 104 | + | DoCh2(...) | |
| 105 | + | else if Copy(Txt, i, Length(Ch3)) = Ch3 then | |
| 106 | + | DoCh3(...) | |
| 107 | + | end; | |
| 108 | + | end; | |
| 109 | + | ``` | |
| 110 | + | ||
| 111 | + | Цикл можно оптимизировать, перескакивая через уже обработанные части. | |
| 112 | + | ||
| 113 | + | ### Перебор строки с анализом отдельных кодовых точек | |
| 114 | + | ||
| 115 | + | Этот код копирует каждую кодовую точку в переменную типа String, которая затем может быть обработана далее. | |
| 116 | + | ||
| 117 | + | ``` pascal | |
| 118 | + | procedure IterateUTF8(S: String); | |
| 119 | + | var | |
| 120 | + | CurP, EndP: PChar; | |
| 121 | + | Len: Integer; | |
| 122 | + | ACodePoint: String; | |
| 123 | + | begin | |
| 124 | + | CurP := PChar(S); // если S='', то PChar(S) возвращает указатель на #0 | |
| 125 | + | EndP := CurP + length(S); | |
| 126 | + | while CurP < EndP do | |
| 127 | + | begin | |
| 128 | + | Len := UTF8CodepointSize(CurP); | |
| 129 | + | SetLength(ACodePoint, Len); | |
| 130 | + | Move(CurP^, ACodePoint[1], Len); | |
| 131 | + | // Единственная кодовая точка копируется из строки. Делайте так. | |
| 132 | + | ShowMessageFmt('CodePoint=%s, Len=%d', [ACodePoint, Len]); | |
| 133 | + | // ... | |
| 134 | + | inc(CurP, Len); | |
| 135 | + | end; | |
| 136 | + | end; | |
| 137 | + | ``` | |
| 138 | + | ||
| 139 | + | ### Доступ к байтам внутри одной кодовой точки UTF8 | |
| 140 | + | ||
| 141 | + | Кодовые точки в кодировке UTF-8 могут различаться по длине, поэтому лучшим решением для доступа к ним является использование итерации. Для перебора кодовых точек используйте этот код: | |
| 142 | + | ||
| 143 | + | ``` pascal | |
| 144 | + | uses LazUTF8; | |
| 145 | + | ... | |
| 146 | + | procedure DoSomethingWithString(AnUTF8String: string); | |
| 147 | + | var | |
| 148 | + | p: PChar; | |
| 149 | + | CPLen: integer; | |
| 150 | + | FirstByte, SecondByte, ThirdByte, FourthByte: Char; | |
| 151 | + | begin | |
| 152 | + | p:=PChar(AnUTF8String); | |
| 153 | + | repeat | |
| 154 | + | CPLen := UTF8CodepointSize(p); | |
| 155 | + | ||
| 156 | + | // Здесь у вас есть указатель на символ и его длину | |
| 157 | + | // Вы можете получить доступ к байтам UTF-8 Char следующим образом: | |
| 158 | + | if CPLen >= 1 then FirstByte := P[0]; | |
| 159 | + | if CPLen >= 2 then SecondByte := P[1]; | |
| 160 | + | if CPLen >= 3 then ThirdByte := P[2]; | |
| 161 | + | if CPLen = 4 then FourthByte := P[3]; | |
| 162 | + | ||
| 163 | + | inc(p,CPLen); | |
| 164 | + | until (CPLen=0) or (p^ = #0); | |
| 165 | + | end; | |
| 166 | + | ``` | |
| 167 | + | ||
| 168 | + | ### Доступ к N-й кодовой точке UTF8 | |
| 169 | + | ||
| 170 | + | Помимо итерации можно также иметь произвольный доступ к кодовым точкам UTF-8. | |
| 171 | + | ||
| 172 | + | ``` pascal | |
| 173 | + | uses LazUTF8; | |
| 174 | + | ... | |
| 175 | + | var | |
| 176 | + | AnUTF8String, NthCodepoint: string; | |
| 177 | + | begin | |
| 178 | + | NthCodepoint := UTF8Copy(AnUTF8String, N, 1); | |
| 179 | + | ``` | |
| 180 | + | ||
| 181 | + | ### Отображение кодовых точек с UTF8CharacterToUnicode | |
| 182 | + | ||
| 183 | + | Ниже показано, как отобразить 32-битное значение кодовой точки каждой кодовой точки в строке UTF8: | |
| 184 | + | ||
| 185 | + | ``` pascal | |
| 186 | + | uses LazUTF8; | |
| 187 | + | ... | |
| 188 | + | procedure IterateUTF8Codepoints(const AnUTF8String: string); | |
| 189 | + | var | |
| 190 | + | p: PChar; | |
| 191 | + | unicode: Cardinal; | |
| 192 | + | CPLen: integer; | |
| 193 | + | begin | |
| 194 | + | p:=PChar(AnUTF8String); | |
| 195 | + | CPLen := UTF8CodepointSize(p); | |
| 196 | + | repeat | |
| 197 | + | unicode:=UTF8CodepointToUnicode(p,CPLen); | |
| 198 | + | writeln('Unicode=',unicode); | |
| 199 | + | inc(p,CPLen); | |
| 200 | + | until (CPLen=0) or (unicode=0); | |
| 201 | + | end; | |
| 202 | + | ``` | |
| 203 | + | ||
| 204 | + | ## Составные символы | |
| 205 | + | ||
| 206 | + | Из-за неоднозначности Unicode функции сравнения и Pos() могут показывать неожиданное поведение, например, когда одна строка содержит составные символы, а другая использует прямые коды для той же буквы. RTL автоматически это не обрабатывает. Это характерно не для какой-либо кодировки, а для Unicode в целом. | |
| 207 | + | ||
| 208 | + | ### macOS | |
| 209 | + | ||
| 210 | + | Файловые функции модуля FileUtil также заботятся о специфическом поведении macOS: macOS нормализует имена файлов. Например, имя файла 'ä.txt' может быть закодировано в Unicode двумя различными последовательностями (#\$C3#\$A4 и 'a'#\$CC#\$88). Под Linux и BSD вы можете создать имя файла с обеими кодировками. macOS автоматически преобразует умляут в трехбайтовую последовательность. Это означает: | |
| 211 | + | ||
| 212 | + | ``` pascal | |
| 213 | + | if Filename1 = Filename2 then ... // недостаточно под macOS | |
| 214 | + | if AnsiCompareFileName(Filename1, Filename2) = 0 then ... // недостаточно для fpc 2.2.2, даже для cwstring | |
| 215 | + | if CompareFileNames(Filename1, Filename2) = 0 then ... // это работает всегда (модуль FileUtil или FileProcs) | |
| 216 | + | ``` | |
| 217 | + | ||
| 218 | + | ## См. также | |
| 219 | + | ||
| 220 | + | - [[Character and string types/ru|Типы символов и строк]] | |
| 221 | + | ||
| 222 | + | --- | |
| 223 | + | *From the Free Pascal wiki, page "UTF8 strings and characters/ru", as it stood on 2020-11-02 after a change by Unxed, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
