从 Delphi 字符串中检测和检索代码点和代理项
Detecting and Retrieving codepoints and surrogates from a Delphi String
我试图更好地理解 Delphi 中的代理对和 Unicode 实现。
如果我在 Delphi 中对 Unicode 字符串 S := 'Ĥà̲V̂e' 调用 length(),我会返回 8。
这是因为[Ĥ]、[à̲]、[V̂]、[e]这几个字符的长度分别为2、3、2、1。这是因为 Ĥ 有一个代理项,à̲ 有两个额外的代理项,V̂ 有一个代理项而 e 没有代理项。
如果我想 return 字符串中的第二个元素包括所有代理项 [à̲],我该怎么做?我知道我需要对单个字节进行某种测试。我运行一些测试使用例程
function GetFirstCodepointSize(const S: UTF8String): Integer;
在 this SO Question 中引用。
但得到了一些不寻常的结果,例如,这里有一些不同代码点的长度和大小。 下面是我如何生成这些表格的片段。
...
UTFCRUDResultStrings.add('INPUT: '+#9#9+ DATA +#9#9+ 'GetFirstCodePointSize = ' +intToStr(GetFirstCodepointSize(DATA))
+#9#9+ 'Length =' + intToStr(length(DATA)));
...
第一组:这对我来说很有意义,每个代码点的大小都加倍了,但是每个都是一个字符,Delphi 给我的长度只有 1,完美。
INPUT: ď GetFirstCodePointSize = 2 Length =1
INPUT: ơ GetFirstCodePointSize = 2 Length =1
INPUT: ǥ GetFirstCodePointSize = 2 Length =1
第二套:我一开始觉得长度和码位是反的?我猜这是因为字符 + 代理项被单独处理,因此第一个代码点大小是 'H',即 1,但长度是 returning [的长度=44=]加上'^'.
INPUT: Ĥ GetFirstCodePointSize = 1 Length =2
INPUT: à̲ GetFirstCodePointSize = 1 Length =3
INPUT: V̂ GetFirstCodePointSize = 1 Length =2
INPUT: e GetFirstCodePointSize = 1 Length =1
一些额外的测试...
INPUT: ¼ GetFirstCodePointSize = 2 Length =1
INPUT: ₧ GetFirstCodePointSize = 3 Length =1
INPUT: GetFirstCodePointSize = 4 Length =2
INPUT: ß GetFirstCodePointSize = 2 Length =1
INPUT: GetFirstCodePointSize = 4 Length =2
Delphi 中是否有可靠的方法来确定 Unicode 字符串中 元素 的开始和结束位置?
我知道我使用单词元素的术语可能不正确,但我认为代码点和字符也不正确,特别是考虑到一个元素的代码点大小可能为 3,但长度仅为 1 .
I am trying to better understand surrogate pairs and Unicode implementation in Delphi.
让我们排除一些术语。
每个由 Unicode 定义的 "character"(称为 字形)都被分配了一个唯一的 codepoint。
在 Unicode 转换格式 (UTF) 编码中 - UTF-7、UTF-8、UTF-16 和 UTF-32 - 每个代码点被编码为一系列代码单位。每个代码单元的大小由编码决定——UTF-7 为 7 位,UTF-8 为 8 位,UTF-16 为 16 位,UTF-32 为 32 位(因此得名)。
在Delphi 2009 及之后的版本中,String
是UnicodeString
的别名,Char
是WideChar
的别名。 WideChar
是 16 位。一个 UnicodeString
包含一个 UTF-16 编码的字符串(在早期版本的 Delphi 中,等效的字符串类型是 WideString
),并且每个 WideChar
是一个 UTF-16 代码单元。
在 UTF-16 中,可以使用 1 个或 2 个代码单元对代码点进行编码。 1 个代码单元可以对基本多语言平面 (BMP) 范围内的代码点值进行编码 - $0000 到 $FFFF,包括在内。更高的代码点需要 2 个代码单元,也称为 代理对.
If I call length() on the Unicode string S := 'Ĥà̲V̂e' in Delphi, I will get back, 8.
This is because the lengths of the individual characters [Ĥ],[à̲],[V̂], and [e] are 2, 3, 2, and 1 respectively.
This is because Ĥ has a surrogate, à̲ has two additional surrogates, V̂ has a surrogate and e has no surrogates.
是的,您的 UTF-16 UnicodeString
中有 8 个 WideChar
元素(代码单元)。你所说的 "surrogates" 实际上被称为 "combining marks"。每个组合标记都是它自己唯一的代码点,因此也是它自己的代码单元序列。
If I wanted to return the second element in the string including all surrogates, [à̲], how would I do that?
您必须从 UnicodeString
的开头开始分析每个 WideChar
,直到找到一个不是附加到前一个 WideChar
的组合标记。在 Windows 上,最简单的方法是使用 CharNextW()
函数,例如:
var
S: String;
P: PChar;
begin
S := 'Ĥà̲V̂e';
P := CharNext(PChar(S)); // returns a pointer to à̲
end;
Delphi RTL 没有等效的功能。您可能会手动编写一个,或者使用第三方库。 RTL 确实有一个 StrNextChar()
函数,但它只处理 UTF-16 代理,而不是组合标记(CharNext()
处理两者)。因此,您可以使用 StrNextChar()
扫描 UnicodeString
中的每个代码点,但您必须查看每个代码点才能知道它是否是组合标记,例如:
uses
Character;
function MyCharNext(P: PChar): PChar;
begin
if (P <> nil) and (P^ <> #0) then
begin
Result := StrNextChar(P);
while GetUnicodeCategory(Result^) = ucCombiningMark do
Result := StrNextChar(Result);
end else begin
Result := nil;
end;
end;
var
S: String;
P: PChar;
begin
S := 'Ĥà̲V̂e';
P := MyCharNext(PChar(S)); // should return a pointer to à̲
end;
I know I would need to do some sort of testing of the individual bytes.
不是字节,而是它们在解码时表示的代码点。
I ran some tests using the routine
function GetFirstCodepointSize(const S: UTF8String): Integer
仔细查看该函数签名。看到参数类型了吗?它是 UTF-8 字符串,而不是 UTF-16 字符串。甚至在您从以下位置获得该功能的答案中也说明了这一点:
Here is an example how to parse UTF8 string
UTF-8 和 UTF-16 是非常不同的编码,因此具有不同的语义。您不能使用 UTF-8 语义来处理 UTF-16 字符串,反之亦然。
Is there a reliable way in Delphi to determine where an element in a Unicode String starts and ends?
不直接。您必须从头开始解析字符串,根据需要跳过元素,直到到达所需的元素。请记住,每个代码点可以编码为 1 个或 2 个代码单元元素,并且每个逻辑字形可以使用多个代码点(因此多个代码单元序列)进行编码。
I know my terminology using the word element may be off, but I don't think codepoint and character are right either, particularly given that one element may have a codepoint size of 3, but have a length of only one.
1 个字形由 1+ 个代码点组成,每个代码点被编码为 1+ 个代码单元。
Could someone implement the following function?
function GetElementAtIndex(S: String; StrIdx : Integer): String;
尝试这样的事情:
uses
SysUtils, Character;
function MyCharNext(P: PChar): PChar;
begin
Result := P;
if Result <> nil then
begin
Result := StrNextChar(Result);
while GetUnicodeCategory(Result^) = ucCombiningMark do
Result := StrNextChar(Result);
end;
end;
function GetElementAtIndex(S: String; StrIdx : Integer): String;
var
pStart, pEnd: PChar;
begin
Result := '';
if (S = '') or (StrIdx < 0) then Exit;
pStart := PChar(S);
while StrIdx > 1 do
begin
pStart := MyCharNext(pStart);
if pStart^ = #0 then Exit;
Dec(StrIdx);
end;
pEnd := MyCharNext(pStart);
{$POINTERMATH ON}
SetString(Result, pStart, pEnd-pStart);
end;
遍历字符串的字素可能比您想象的要复杂。在 Unicode 13 中,一些字素长达 14 个字节。我建议为此使用 third-party 库。最好的之一是 Skia4Delphi:https://github.com/skia4delphi/skia4delphi
代码很简单:
var LUnicode: ISkUnicode := TSkUnicode.Create;
for var LGrapheme: string in LUnicode.GetBreaks('Text', TSkBreakType.Graphemes) do
Showmessage(LGrapheme);
在库演示本身中也有一个字素迭代器的例子。看:
我试图更好地理解 Delphi 中的代理对和 Unicode 实现。
如果我在 Delphi 中对 Unicode 字符串 S := 'Ĥà̲V̂e' 调用 length(),我会返回 8。
这是因为[Ĥ]、[à̲]、[V̂]、[e]这几个字符的长度分别为2、3、2、1。这是因为 Ĥ 有一个代理项,à̲ 有两个额外的代理项,V̂ 有一个代理项而 e 没有代理项。
如果我想 return 字符串中的第二个元素包括所有代理项 [à̲],我该怎么做?我知道我需要对单个字节进行某种测试。我运行一些测试使用例程
function GetFirstCodepointSize(const S: UTF8String): Integer;
在 this SO Question 中引用。
但得到了一些不寻常的结果,例如,这里有一些不同代码点的长度和大小。 下面是我如何生成这些表格的片段。
...
UTFCRUDResultStrings.add('INPUT: '+#9#9+ DATA +#9#9+ 'GetFirstCodePointSize = ' +intToStr(GetFirstCodepointSize(DATA))
+#9#9+ 'Length =' + intToStr(length(DATA)));
...
第一组:这对我来说很有意义,每个代码点的大小都加倍了,但是每个都是一个字符,Delphi 给我的长度只有 1,完美。
INPUT: ď GetFirstCodePointSize = 2 Length =1
INPUT: ơ GetFirstCodePointSize = 2 Length =1
INPUT: ǥ GetFirstCodePointSize = 2 Length =1
第二套:我一开始觉得长度和码位是反的?我猜这是因为字符 + 代理项被单独处理,因此第一个代码点大小是 'H',即 1,但长度是 returning [的长度=44=]加上'^'.
INPUT: Ĥ GetFirstCodePointSize = 1 Length =2
INPUT: à̲ GetFirstCodePointSize = 1 Length =3
INPUT: V̂ GetFirstCodePointSize = 1 Length =2
INPUT: e GetFirstCodePointSize = 1 Length =1
一些额外的测试...
INPUT: ¼ GetFirstCodePointSize = 2 Length =1
INPUT: ₧ GetFirstCodePointSize = 3 Length =1
INPUT: GetFirstCodePointSize = 4 Length =2
INPUT: ß GetFirstCodePointSize = 2 Length =1
INPUT: GetFirstCodePointSize = 4 Length =2
Delphi 中是否有可靠的方法来确定 Unicode 字符串中 元素 的开始和结束位置?
我知道我使用单词元素的术语可能不正确,但我认为代码点和字符也不正确,特别是考虑到一个元素的代码点大小可能为 3,但长度仅为 1 .
I am trying to better understand surrogate pairs and Unicode implementation in Delphi.
让我们排除一些术语。
每个由 Unicode 定义的 "character"(称为 字形)都被分配了一个唯一的 codepoint。
在 Unicode 转换格式 (UTF) 编码中 - UTF-7、UTF-8、UTF-16 和 UTF-32 - 每个代码点被编码为一系列代码单位。每个代码单元的大小由编码决定——UTF-7 为 7 位,UTF-8 为 8 位,UTF-16 为 16 位,UTF-32 为 32 位(因此得名)。
在Delphi 2009 及之后的版本中,String
是UnicodeString
的别名,Char
是WideChar
的别名。 WideChar
是 16 位。一个 UnicodeString
包含一个 UTF-16 编码的字符串(在早期版本的 Delphi 中,等效的字符串类型是 WideString
),并且每个 WideChar
是一个 UTF-16 代码单元。
在 UTF-16 中,可以使用 1 个或 2 个代码单元对代码点进行编码。 1 个代码单元可以对基本多语言平面 (BMP) 范围内的代码点值进行编码 - $0000 到 $FFFF,包括在内。更高的代码点需要 2 个代码单元,也称为 代理对.
If I call length() on the Unicode string S := 'Ĥà̲V̂e' in Delphi, I will get back, 8.
This is because the lengths of the individual characters [Ĥ],[à̲],[V̂], and [e] are 2, 3, 2, and 1 respectively.
This is because Ĥ has a surrogate, à̲ has two additional surrogates, V̂ has a surrogate and e has no surrogates.
是的,您的 UTF-16 UnicodeString
中有 8 个 WideChar
元素(代码单元)。你所说的 "surrogates" 实际上被称为 "combining marks"。每个组合标记都是它自己唯一的代码点,因此也是它自己的代码单元序列。
If I wanted to return the second element in the string including all surrogates, [à̲], how would I do that?
您必须从 UnicodeString
的开头开始分析每个 WideChar
,直到找到一个不是附加到前一个 WideChar
的组合标记。在 Windows 上,最简单的方法是使用 CharNextW()
函数,例如:
var
S: String;
P: PChar;
begin
S := 'Ĥà̲V̂e';
P := CharNext(PChar(S)); // returns a pointer to à̲
end;
Delphi RTL 没有等效的功能。您可能会手动编写一个,或者使用第三方库。 RTL 确实有一个 StrNextChar()
函数,但它只处理 UTF-16 代理,而不是组合标记(CharNext()
处理两者)。因此,您可以使用 StrNextChar()
扫描 UnicodeString
中的每个代码点,但您必须查看每个代码点才能知道它是否是组合标记,例如:
uses
Character;
function MyCharNext(P: PChar): PChar;
begin
if (P <> nil) and (P^ <> #0) then
begin
Result := StrNextChar(P);
while GetUnicodeCategory(Result^) = ucCombiningMark do
Result := StrNextChar(Result);
end else begin
Result := nil;
end;
end;
var
S: String;
P: PChar;
begin
S := 'Ĥà̲V̂e';
P := MyCharNext(PChar(S)); // should return a pointer to à̲
end;
I know I would need to do some sort of testing of the individual bytes.
不是字节,而是它们在解码时表示的代码点。
I ran some tests using the routine
function GetFirstCodepointSize(const S: UTF8String): Integer
仔细查看该函数签名。看到参数类型了吗?它是 UTF-8 字符串,而不是 UTF-16 字符串。甚至在您从以下位置获得该功能的答案中也说明了这一点:
Here is an example how to parse UTF8 string
UTF-8 和 UTF-16 是非常不同的编码,因此具有不同的语义。您不能使用 UTF-8 语义来处理 UTF-16 字符串,反之亦然。
Is there a reliable way in Delphi to determine where an element in a Unicode String starts and ends?
不直接。您必须从头开始解析字符串,根据需要跳过元素,直到到达所需的元素。请记住,每个代码点可以编码为 1 个或 2 个代码单元元素,并且每个逻辑字形可以使用多个代码点(因此多个代码单元序列)进行编码。
I know my terminology using the word element may be off, but I don't think codepoint and character are right either, particularly given that one element may have a codepoint size of 3, but have a length of only one.
1 个字形由 1+ 个代码点组成,每个代码点被编码为 1+ 个代码单元。
Could someone implement the following function?
function GetElementAtIndex(S: String; StrIdx : Integer): String;
尝试这样的事情:
uses
SysUtils, Character;
function MyCharNext(P: PChar): PChar;
begin
Result := P;
if Result <> nil then
begin
Result := StrNextChar(Result);
while GetUnicodeCategory(Result^) = ucCombiningMark do
Result := StrNextChar(Result);
end;
end;
function GetElementAtIndex(S: String; StrIdx : Integer): String;
var
pStart, pEnd: PChar;
begin
Result := '';
if (S = '') or (StrIdx < 0) then Exit;
pStart := PChar(S);
while StrIdx > 1 do
begin
pStart := MyCharNext(pStart);
if pStart^ = #0 then Exit;
Dec(StrIdx);
end;
pEnd := MyCharNext(pStart);
{$POINTERMATH ON}
SetString(Result, pStart, pEnd-pStart);
end;
遍历字符串的字素可能比您想象的要复杂。在 Unicode 13 中,一些字素长达 14 个字节。我建议为此使用 third-party 库。最好的之一是 Skia4Delphi:https://github.com/skia4delphi/skia4delphi
代码很简单:
var LUnicode: ISkUnicode := TSkUnicode.Create;
for var LGrapheme: string in LUnicode.GetBreaks('Text', TSkBreakType.Graphemes) do
Showmessage(LGrapheme);
在库演示本身中也有一个字素迭代器的例子。看: