Sign in Join
Wiki copytemporary

UTF8 strings and characters/ru

English · Suomi · Русский

Красота UTF-8

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

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

Note: аналогичные функции целостности также существуют в UTF-16. (Диапазон D800 сигнализирует первый суррогат, сигнал диапазона DC00 - второй суррогат).

Примеры

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

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

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

pascal
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.

pascal
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], как в старые времена. Большинство парсеров делают это, и они продолжают работать.

pascal
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, но они не нужны.

pascal
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, которая затем может быть обработана далее.

pascal
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 могут различаться по длине, поэтому лучшим решением для доступа к ним является использование итерации. Для перебора кодовых точек используйте этот код:

pascal
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.

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

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

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

pascal
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 автоматически преобразует умляут в трехбайтовую последовательность. Это означает:

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

См. также


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.

Revision 1, last changed by WikiArchive .

Edited on wiki.freepascal.org by 3 people, 2019–2020

From the official wiki's backup of 26 May 2024: who edited this page there, and when. Edits made there after that date were not recorded, and the earlier versions are not shown here; the main page says why.

WhenWhoWhat changed
ZoltanleoCreated page
ZoltanleoThe beauty of UTF-8
ZoltanleoExamples
ZoltanleoIterating over string looking for ASCII characters
ZoltanleoIterating over string looking for Unicode characters or text
ZoltanleoIterating over string analysing individual codepoints
ZoltanleoAccessing bytes inside one UTF8 codepoint
ZoltanleoAccessing the Nth UTF8 codepoint
ZoltanleoShowing codepoints with UTF8CharacterToUnicode
ZoltanleoОтображение кодовых точек с UTF8CharacterToUnicode
ZoltanleoDecomposed characters
ZoltanleoSee also
TrevmacOS: OSX -> macOS (minor)
Unxed
UnxedДоступ к байтам внутри одной кодовой точки UTF8 (minor)

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.