Sign in Join
Wiki copytemporary

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

OldNewLine
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).*

Back to history