Difference between revisions of "UTF8 strings and characters/ru"

From Lazarus wiki
Jump to navigationJump to search
 
(10 intermediate revisions by 3 users not shown)
Line 21: Line 21:
 
== Примеры ==
 
== Примеры ==
  
Простая перебор символов, как если бы строка представляла собой массив элементов одинакового размера, не работает с Юникодом. Это не что-то конкретное для UTF-8, стандарт Unicode сложен, а слово «символ» неоднозначно. Если вы хотите перебрать все кодовые точки строки UTF-8, есть два основных способа:
+
Простой перебор символов, как если бы строка представляла собой массив элементов одинакового размера, не работает с Юникодом. Это не что-то конкретное для UTF-8, стандарт Unicode сложен, а слово «символ» неоднозначно. Если вы хотите перебрать все кодовые точки строки UTF-8, есть два основных способа:
  
 
*перебирать все байты - полезно для поиска подстроки или при просмотре только символов ASCII в строке UTF8, например, при разборе файлов XML.
 
*перебирать все байты - полезно для поиска подстроки или при просмотре только символов ASCII в строке UTF8, например, при разборе файлов XML.
Line 81: Line 81:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Iterating over string looking for Unicode characters or text ===
+
=== Итерация по строке в поисках символов Юникода или текста ===
  
If you want to find all occurrances of a certain character or substring in a string, you can call PosEx() repeatedly.
+
Если вы хотите найти все вхождения определенного символа или подстроки в строку, вы можете повторно вызывать PosEx().
  
If you want to test for different text inside a loop, you can still use the fast Copy() and Length(). UTF-8 specific functions could be used but they are not needed.
+
Если вы хотите проверить другой текст внутри цикла, вы все равно можете использовать быстрые функции Copy() и Length(). Можно использовать специальные функции UTF-8, но они не нужны.
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
procedure ParseUnicode(Txt: string);
 
procedure ParseUnicode(Txt: string);
 
var
 
var
Line 93: Line 93:
 
   i: Integer;
 
   i: Integer;
 
begin
 
begin
   Ch1 := 'Й';  // Characters to search for. They can also
+
   Ch1 := 'Й';  // Символы для поиска. Они также могут
   Ch2 := 'ﯚ';  // be combined codepoints or longer text.
+
   Ch2 := 'ﯚ';  // быть комбинированными кодовыми точками или более длинным текстом
 
   Ch3 := 'Å';
 
   Ch3 := 'Å';
 
   for i:=1 to Length(Txt) do
 
   for i:=1 to Length(Txt) do
Line 108: Line 108:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The loop could be optimized by jumping over the already handled parts.
+
Цикл можно оптимизировать, перескакивая через уже обработанные части.
  
=== Iterating over string analysing individual codepoints ===
+
=== Перебор строки с анализом отдельных кодовых точек ===
  
This code copies each codepoint into a variable of type String which can then be processed further.
+
Этот код копирует каждую кодовую точку в переменную типа String, которая затем может быть обработана далее.
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
procedure IterateUTF8(S: String);
 
procedure IterateUTF8(S: String);
 
var
 
var
Line 121: Line 121:
 
   ACodePoint: String;
 
   ACodePoint: String;
 
begin
 
begin
   CurP := PChar(S);        // if S='' then PChar(S) returns a pointer to #0
+
   CurP := PChar(S);        // если S='', то PChar(S) возвращает указатель на #0
 
   EndP := CurP + length(S);
 
   EndP := CurP + length(S);
 
   while CurP < EndP do
 
   while CurP < EndP do
Line 128: Line 128:
 
     SetLength(ACodePoint, Len);
 
     SetLength(ACodePoint, Len);
 
     Move(CurP^, ACodePoint[1], Len);
 
     Move(CurP^, ACodePoint[1], Len);
     // A single codepoint is copied from the string. Do your thing with it.
+
     // Единственная кодовая точка копируется из строки. Делайте так.
 
     ShowMessageFmt('CodePoint=%s, Len=%d', [ACodePoint, Len]);
 
     ShowMessageFmt('CodePoint=%s, Len=%d', [ACodePoint, Len]);
 
     // ...
 
     // ...
Line 136: Line 136:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Accessing bytes inside one UTF8 codepoint ===
+
=== Доступ к байтам внутри одной кодовой точки UTF8 ===
  
UTF-8 encoded codepoints can vary in length, so the best solution for accessing them is to use an iteration. To iterate through codepoints use this code:
+
Кодовые точки в кодировке UTF-8 могут различаться по длине, поэтому лучшим решением для доступа к ним является использование итерации. Для перебора кодовых точек используйте этот код:
  
<syntaxhighlight>uses LazUTF8;
+
<syntaxhighlight lang=pascal>uses LazUTF8;
 
...
 
...
 
procedure DoSomethingWithString(AnUTF8String: string);
 
procedure DoSomethingWithString(AnUTF8String: string);
Line 152: Line 152:
 
     CPLen := UTF8CodepointSize(p);
 
     CPLen := UTF8CodepointSize(p);
  
     // Here you have a pointer to the char and its length
+
     // Здесь у вас есть указатель на символ и его длину
     // You can access the bytes of the UTF-8 Char like this:
+
     // Вы можете получить доступ к байтам UTF-8 Char следующим образом:
 
     if CPLen >= 1 then FirstByte := P[0];
 
     if CPLen >= 1 then FirstByte := P[0];
 
     if CPLen >= 2 then SecondByte := P[1];
 
     if CPLen >= 2 then SecondByte := P[1];
Line 163: Line 163:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
=== Accessing the Nth UTF8 codepoint ===
+
=== Доступ к N-й кодовой точке UTF8 ===
  
Besides iterating one might also want to have random access to UTF-8 codepoints.
+
Помимо итерации можно также иметь произвольный доступ к кодовым точкам UTF-8.
  
<syntaxhighlight>uses LazUTF8;
+
<syntaxhighlight lang=pascal>uses LazUTF8;
 
...
 
...
 
var
 
var
Line 175: Line 175:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Showing codepoints with UTF8CharacterToUnicode ===
+
=== Отображение кодовых точек с UTF8CharacterToUnicode ===
  
The following demonstrates how to show the 32bit code point value of each codepoint in an UTF8 string:
+
Ниже показано, как отобразить 32-битное значение кодовой точки каждой кодовой точки в строке UTF8:
  
<syntaxhighlight>uses LazUTF8;
+
<syntaxhighlight lang=pascal>uses LazUTF8;
 
...
 
...
 
procedure IterateUTF8Codepoints(const AnUTF8String: string);
 
procedure IterateUTF8Codepoints(const AnUTF8String: string);
Line 188: Line 188:
 
begin
 
begin
 
   p:=PChar(AnUTF8String);
 
   p:=PChar(AnUTF8String);
 +
  CPLen := UTF8CodepointSize(p);
 
   repeat
 
   repeat
 
     unicode:=UTF8CodepointToUnicode(p,CPLen);
 
     unicode:=UTF8CodepointToUnicode(p,CPLen);
Line 195: Line 196:
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
== Decomposed characters ==
+
== Составные символы ==
  
Due to the ambiguity of Unicode, compare functions and Pos() might show unexpected behavior when e.g. one of the string contains decomposed characters, while the other uses the direct codes for the same letter. This is not automatically handled by the RTL.
+
Из-за неоднозначности Unicode функции сравнения и Pos() могут показывать неожиданное поведение, например, когда одна строка содержит составные символы, а другая использует прямые коды для той же буквы. RTL автоматически это не обрабатывает.
It is not specific to any encoding but Unicode in general.
+
Это характерно не для какой-либо кодировки, а для Unicode в целом.
  
=== Mac OS X ===
+
=== macOS ===
  
The file functions of the FileUtil unit also take care of Mac OS X specific behaviour: OS X normalizes filenames. For example the filename 'ä.txt' can be encoded in Unicode with two different sequences (#$C3#$A4 and 'a'#$CC#$88). Under Linux and BSD you can create a filename with both encodings. OS X automatically converts the a umlaut to the three byte sequence. This means:
+
Файловые функции модуля FileUtil также заботятся о специфическом поведении macOS: macOS нормализует имена файлов. Например, имя файла 'ä.txt' может быть закодировано в Unicode двумя различными последовательностями (#$C3#$A4 и 'a'#$CC#$88). Под Linux и BSD вы можете создать имя файла с обеими кодировками. macOS автоматически преобразует умляут в трехбайтовую последовательность. Это означает:
  
<syntaxhighlight>if Filename1 = Filename2 then ... // is not sufficient under OS X
+
<syntaxhighlight lang=pascal>
if AnsiCompareFileName(Filename1, Filename2) = 0 then ... // not sufficient under fpc 2.2.2, not even with cwstring
+
if Filename1 = Filename2 then ... // недостаточно под macOS
if CompareFilenames(Filename1, Filename2) = 0 then ... // this always works (unit FileUtil or FileProcs</syntaxhighlight>
+
if AnsiCompareFileName(Filename1, Filename2) = 0 then ... // недостаточно для fpc 2.2.2, даже для cwstring
 +
if CompareFileNames(Filename1, Filename2) = 0 then ... // это работает всегда (модуль FileUtil или FileProcs)
 +
</syntaxhighlight>
  
== See also ==
+
== См. также ==
* [[Character and string types]]
+
* [[Character_and_string_types/ru|Типы символов и строк]]

Latest revision as of 05:20, 2 November 2020

English (en) suomi (fi) русский (ru)

Красота UTF-8

Байты, начинающиеся с '0' (0xxxxxxx), зарезервированы для ASCII-совместимых однобайтовых символов. В многобайтовых кодовых точках число 1 в старшем байте определяет количество байтов, которое занимает кодовая точка. Наподобие этого:

  • 1 byte : 0xxxxxxx
  • 2 bytes : 110xxxxx 10xxxxxx
  • 3 bytes : 1110xxxx 10xxxxxx 10xxxxxx
  • 4 bytes : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Конструкция UTF-8 имеет некоторые преимущества по сравнению с другими кодировками:

  • Он обратно совместим с ASCII и создает компактные данные для западных языков. ASCII также используется в тегах языка разметки и других метаданных, что дает UTF-8 преимущество с любым языком. Однако обратная совместимость не распространяется на код, так как код должен быть пересмотрен, чтобы избежать искажения строк utf8.
  • Целостность многобайтовых данных может быть проверена по количеству '1'-битов в начале каждого байта.
  • Вы всегда можете найти начало многобайтовой кодовой точки, даже если вы перепрыгнули на случайную позицию байта.
  • Байт в определенной позиции в многобайтовой последовательности никогда не может быть перепутан с другими байтами. Это позволяет использовать старые быстрые строковые функции, такие как Pos() и Copy() во многих ситуациях. Смотрите примеры ниже.
Light bulb  Примечание: аналогичные функции целостности также существуют в UTF-16. (Диапазон D800 сигнализирует первый суррогат, сигнал диапазона DC00 - второй суррогат).
  • Надежный код. Код, который имеет дело с кодовыми точками, всегда должен быть выполнен правильно с UTF-8, потому что многобайтовые кодовые точки являются общими. Для UTF-16 есть много небрежного кода, который предполагает, что кодовые точки имеют фиксированную ширину.
  • Большая часть текстовых данных, перемещаемых в Интернете, кодируется как UTF-8. Обработка данных непосредственно как UTF-8 исключает бесполезные преобразования. Многие (связанные с Unix) операционные системы изначально используют UTF-8.

Примеры

Простой перебор символов, как если бы строка представляла собой массив элементов одинакового размера, не работает с Юникодом. Это не что-то конкретное для UTF-8, стандарт Unicode сложен, а слово «символ» неоднозначно. Если вы хотите перебрать все кодовые точки строки UTF-8, есть два основных способа:

  • перебирать все байты - полезно для поиска подстроки или при просмотре только символов ASCII в строке UTF8, например, при разборе файлов XML.
  • перебирать все кодовые точки или символы - полезно для графических компонентов, таких как SynEdit, например, когда вы хотите узнать третий напечатанный символ на экране.

Поиск подстроки

Из-за особой природы UTF8 вы можете просто использовать обычные строковые функции для поиска в подстроке. Поиск правильной строки UTF-8 с помощью Pos всегда будет возвращать правильную позицию байта UTF-8:

uses LazUTF8;
...
procedure Where(SearchFor, aText: string);
var
  BytePos: LongInt;
  CodepointPos: LongInt;
begin
  BytePos:=Pos(SearchFor,aText);
  CodepointPos:=UTF8Pos(SearchFor,aText);
  writeln('Подстрока "',SearchFor,'" находится в тексте "',aText,'"',
    ' в позиция байта ',BytePos,' и в позиция кодовой точки ',CodepointPos);
end;

Поиск и копирование

Еще один пример того, как Pos(), Copy() и Length() работают с UTF-8. Эта функция не имеет кода для работы с кодировкой UTF-8, но она всегда работает с любым действительным текстом UTF-8.

function SplitInHalf(Txt, Separator: string; out Half1, Half2: string): Boolean;
var
  i: Integer;
begin
  i := Pos(Separator, Txt);
  Result := i > 0;
  if Result then
  begin
    Half1 := Copy(Txt, 1, i-1);
    Half2 := Copy(Txt, i+Length(Separator), Length(Txt));
  end;
end;

Перебор строки в поисках символов ASCII

Если вы хотите найти только символы в ASCII-области, вы можете использовать тип Char и сравнивать с Txt[i], как в старые времена. Большинство парсеров делают это, и они продолжают работать.

procedure ParseAscii(Txt: string);
var
  i: Integer;
begin
  for i:=1 to Length(Txt) do
    case Txt[i] of
      '(': PushOpenBracketPos(i);
      ')': HandleBracketText(i);
    end;
end;

Итерация по строке в поисках символов Юникода или текста

Если вы хотите найти все вхождения определенного символа или подстроки в строку, вы можете повторно вызывать PosEx().

Если вы хотите проверить другой текст внутри цикла, вы все равно можете использовать быстрые функции Copy() и Length(). Можно использовать специальные функции UTF-8, но они не нужны.

procedure ParseUnicode(Txt: string);
var
  Ch1, Ch2, Ch3: String;
  i: Integer;
begin
  Ch1 := 'Й';  // Символы для поиска. Они также могут 
  Ch2 := 'ﯚ';  // быть комбинированными кодовыми точками или более длинным текстом
  Ch3 := 'Å';
  for i:=1 to Length(Txt) do
  begin
    if Copy(Txt, i, Length(Ch1)) = Ch1 then
      DoCh1(...)
    else if Copy(Txt, i, Length(Ch2)) = Ch2 then
      DoCh2(...)
    else if Copy(Txt, i, Length(Ch3)) = Ch3 then
      DoCh3(...)
  end;
end;

Цикл можно оптимизировать, перескакивая через уже обработанные части.

Перебор строки с анализом отдельных кодовых точек

Этот код копирует каждую кодовую точку в переменную типа String, которая затем может быть обработана далее.

procedure IterateUTF8(S: String);
var
  CurP, EndP: PChar;
  Len: Integer;
  ACodePoint: String;
begin
  CurP := PChar(S);        // если S='', то PChar(S) возвращает указатель на #0
  EndP := CurP + length(S);
  while CurP < EndP do
  begin
    Len := UTF8CodepointSize(CurP);
    SetLength(ACodePoint, Len);
    Move(CurP^, ACodePoint[1], Len);
    // Единственная кодовая точка копируется из строки. Делайте так.
    ShowMessageFmt('CodePoint=%s, Len=%d', [ACodePoint, Len]);
    // ...
    inc(CurP, Len);
  end;
end;

Доступ к байтам внутри одной кодовой точки UTF8

Кодовые точки в кодировке UTF-8 могут различаться по длине, поэтому лучшим решением для доступа к ним является использование итерации. Для перебора кодовых точек используйте этот код:

uses LazUTF8;
...
procedure DoSomethingWithString(AnUTF8String: string);
var
  p: PChar;
  CPLen: integer;
  FirstByte, SecondByte, ThirdByte, FourthByte: Char;
begin
  p:=PChar(AnUTF8String);
  repeat
    CPLen := UTF8CodepointSize(p);

    // Здесь у вас есть указатель на символ и его длину
    // Вы можете получить доступ к байтам UTF-8 Char следующим образом:
    if CPLen >= 1 then FirstByte := P[0];
    if CPLen >= 2 then SecondByte := P[1];
    if CPLen >= 3 then ThirdByte := P[2];
    if CPLen = 4 then FourthByte := P[3];

    inc(p,CPLen);
  until (CPLen=0) or (p^ = #0);
end;

Доступ к N-й кодовой точке UTF8

Помимо итерации можно также иметь произвольный доступ к кодовым точкам UTF-8.

uses LazUTF8;
...
var
  AnUTF8String, NthCodepoint: string;
begin
  NthCodepoint := UTF8Copy(AnUTF8String, N, 1);

Отображение кодовых точек с UTF8CharacterToUnicode

Ниже показано, как отобразить 32-битное значение кодовой точки каждой кодовой точки в строке UTF8:

uses LazUTF8;
...
procedure IterateUTF8Codepoints(const AnUTF8String: string);
var
  p: PChar;
  unicode: Cardinal;
  CPLen: integer;
begin
  p:=PChar(AnUTF8String);
  CPLen := UTF8CodepointSize(p);
  repeat
    unicode:=UTF8CodepointToUnicode(p,CPLen);
    writeln('Unicode=',unicode);
    inc(p,CPLen);
  until (CPLen=0) or (unicode=0);
end;

Составные символы

Из-за неоднозначности Unicode функции сравнения и Pos() могут показывать неожиданное поведение, например, когда одна строка содержит составные символы, а другая использует прямые коды для той же буквы. RTL автоматически это не обрабатывает. Это характерно не для какой-либо кодировки, а для Unicode в целом.

macOS

Файловые функции модуля FileUtil также заботятся о специфическом поведении macOS: macOS нормализует имена файлов. Например, имя файла 'ä.txt' может быть закодировано в Unicode двумя различными последовательностями (#$C3#$A4 и 'a'#$CC#$88). Под Linux и BSD вы можете создать имя файла с обеими кодировками. macOS автоматически преобразует умляут в трехбайтовую последовательность. Это означает:

if Filename1 = Filename2 then ... // недостаточно под macOS
if AnsiCompareFileName(Filename1, Filename2) = 0 then ... // недостаточно для fpc 2.2.2, даже для cwstring
if CompareFileNames(Filename1, Filename2) = 0 then ... // это работает всегда (модуль FileUtil или FileProcs)

См. также