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

From Free Pascal wiki
Jump to navigationJump to search
(Created page with "{{UTF8 strings and characters}} == The beauty of UTF-8 == Bytes starting with '0' (0xxxxxxx) are reserved for ASCII-compatible single byte characters. With multi-byte co...")
 
 
(13 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
{{UTF8 strings and characters}}
 
{{UTF8 strings and characters}}
  
== The beauty of UTF-8 ==
+
== Красота UTF-8 ==
  
Bytes starting with '0' (0xxxxxxx) are reserved for [[ASCII]]-compatible single byte characters.
+
Байты, начинающиеся с '0' (0xxxxxxx), зарезервированы для [[ASCII/ru|ASCII]]-совместимых однобайтовых символов.
With multi-byte codepoints the number of 1’s in the leading byte determines the number of bytes the codepoint occupies. Like this :
+
В многобайтовых кодовых точках число 1 в старшем байте определяет количество байтов, которое занимает кодовая точка. Наподобие этого:
 
* 1 byte : 0xxxxxxx
 
* 1 byte : 0xxxxxxx
 
* 2 bytes : 110xxxxx 10xxxxxx
 
* 2 bytes : 110xxxxx 10xxxxxx
Line 10: Line 10:
 
* 4 bytes : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
 
* 4 bytes : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  
The design of [[UTF-8]] has some benefits over other encodings :
+
Конструкция [[UTF-8/ru|UTF-8]] имеет некоторые преимущества по сравнению с другими кодировками:
* It is backwards compatible with ASCII and produces compact data for western languages. ASCII is also used in markup language tags and other metadata which gives UTF-8 an advantage with any language. However that backwards compatibility does not extend to code, since code has to be recrafted to avoid mangling utf8 strings.
+
* Он обратно совместим с ASCII и создает компактные данные для западных языков. ASCII также используется в тегах языка разметки и других метаданных, что дает UTF-8 преимущество с любым языком. Однако обратная совместимость не распространяется на код, так как код должен быть пересмотрен, чтобы избежать искажения строк utf8.
* The integrity of multi-byte data can be verified from the number of '1'-bits at the beginning of each byte.  
+
* Целостность многобайтовых данных может быть проверена по количеству '1'-битов в начале каждого байта.
* You can always find the start of a multi-byte codepoint even if you jumped to a random byte position.
+
* Вы всегда можете найти начало многобайтовой кодовой точки, даже если вы перепрыгнули на случайную позицию байта.
* A byte at a certain position in a multi-byte sequence can never be confused with the other bytes. This allows using the old fast string functions like Pos() and Copy() in many situations. See examples below.
+
* Байт в определенной позиции в многобайтовой последовательности никогда не может быть перепутан с другими байтами. Это позволяет использовать старые быстрые строковые функции, такие как <tt>Pos()</tt> и <tt>Copy()</tt> во многих ситуациях. Смотрите примеры ниже.
 +
{{Note| аналогичные функции целостности также существуют в UTF-16. (Диапазон D800 сигнализирует первый суррогат, сигнал диапазона DC00 - второй суррогат).}}
 +
* Надежный код. Код, который имеет дело с кодовыми точками, всегда должен быть выполнен правильно с UTF-8, потому что многобайтовые кодовые точки являются общими. Для UTF-16 есть много небрежного кода, который предполагает, что кодовые точки имеют фиксированную ширину.
 +
* Большая часть текстовых данных, перемещаемых в Интернете, кодируется как UTF-8. Обработка данных непосредственно как UTF-8 исключает бесполезные преобразования. Многие (связанные с Unix) операционные системы изначально используют UTF-8.
  
Note that similar integrity features are also exists in UTF-16. (D800 range signals first surrogate, DC00 range signals second part of surrogate)
+
== Примеры ==
  
* Robust code. Code that deals with codepoints must always be done right with UTF-8 because multi-byte codepoints are common. For UTF-16 there is plenty of sloppy code which assumes codepoints to be fixed width.
+
Простой перебор символов, как если бы строка представляла собой массив элементов одинакового размера, не работает с Юникодом. Это не что-то конкретное для UTF-8, стандарт Unicode сложен, а слово «символ» неоднозначно. Если вы хотите перебрать все кодовые точки строки UTF-8, есть два основных способа:
* Most textual data moving in internet is encoded as UTF-8. Processing the data directly as UTF-8 eliminates useless conversions. Many (Unix-related) operating systems use UTF-8 natively.
 
  
== Examples ==
+
*перебирать все байты - полезно для поиска подстроки или при просмотре только символов ASCII в строке UTF8, например, при разборе файлов XML.
 +
*перебирать все кодовые точки или символы - полезно для графических компонентов, таких как [[SynEdit/ru|SynEdit]], например, когда вы хотите узнать третий напечатанный символ на экране.
  
Simply iterating over characters as if the string was an array of equal sized elements does not work with Unicode. This is not something specific to UTF-8, the Unicode standard is complex and the word "character" is ambiguous. If you want to iterate over codepoints of a UTF-8 string, there are basically two ways:
+
=== Поиск подстроки ===
  
*iterate over bytes - useful for searching a substring or when looking only at the ASCII characters in the UTF8 string, for example when parsing XML files.
+
Из-за особой природы UTF8 вы можете просто использовать обычные строковые функции для поиска в подстроке. Поиск правильной строки UTF-8 с помощью Pos всегда будет возвращать правильную позицию байта UTF-8:
*iterate over codepoints or characters - useful for graphical components like [[SynEdit]], for example when you want to know the third printed character on the screen.
 
  
=== Searching a substring ===
+
<syntaxhighlight lang=pascal>
 
 
Due to the special nature of UTF8 you can simply use the normal string functions for searching a sub-string. Searching for a valid UTF-8 string with Pos will always return a valid UTF-8 byte position:
 
 
 
<syntaxhighlight>
 
 
uses LazUTF8;
 
uses LazUTF8;
 
...
 
...
Line 42: Line 40:
 
   BytePos:=Pos(SearchFor,aText);
 
   BytePos:=Pos(SearchFor,aText);
 
   CodepointPos:=UTF8Pos(SearchFor,aText);
 
   CodepointPos:=UTF8Pos(SearchFor,aText);
   writeln('The substring "',SearchFor,'" is in the text "',aText,'"',
+
   writeln('Подстрока "',SearchFor,'" находится в тексте "',aText,'"',
     ' at byte position ',BytePos,' and at codepoint position ',CodepointPos);
+
     ' в позиция байта ',BytePos,' и в позиция кодовой точки ',CodepointPos);
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Search and copy ===
+
=== Поиск и копирование ===
  
Another example of how Pos(), Copy() and Length() work with UTF-8. This function has no code to deal with UTF-8 encoding, yet it works with any valid UTF-8 text always.
+
Еще один пример того, как <tt>Pos()</tt>, <tt>Copy()</tt> и <tt>Length()</tt> работают с UTF-8. Эта функция не имеет кода для работы с кодировкой UTF-8, но она всегда работает с любым действительным текстом UTF-8.
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
function SplitInHalf(Txt, Separator: string; out Half1, Half2: string): Boolean;
 
function SplitInHalf(Txt, Separator: string; out Half1, Half2: string): Boolean;
 
var
 
var
Line 66: Line 64:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Iterating over string looking for ASCII characters ===
+
=== Перебор строки в поисках символов ASCII ===
  
If you only want to find characters in ASCII-area, you can use Char type and compare with Txt[i] just like in old times. Most parsers do that and they continue working.
+
Если вы хотите найти только символы в ASCII-области, вы можете использовать тип Char и сравнивать с Txt[i], как в старые времена. Большинство парсеров делают это, и они продолжают работать.
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
procedure ParseAscii(Txt: string);
 
procedure ParseAscii(Txt: string);
 
var
 
var
Line 83: 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 95: 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 110: 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 123: 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 130: 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 138: 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 154: 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 165: 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 177: 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 190: Line 188:
 
begin
 
begin
 
   p:=PChar(AnUTF8String);
 
   p:=PChar(AnUTF8String);
 +
  CPLen := UTF8CodepointSize(p);
 
   repeat
 
   repeat
 
     unicode:=UTF8CodepointToUnicode(p,CPLen);
 
     unicode:=UTF8CodepointToUnicode(p,CPLen);
Line 197: 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)

См. также