繁体   English   中英

从 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 没有代理。

如果我想返回包含所有代理项的字符串中的第二个元素 [à̲],我该怎么做? 我知道我需要对单个字节进行某种测试。 我使用例程进行了一些测试

function GetFirstCodepointSize(const S: UTF8String): Integer;  

此 SO 问题中引用。

但得到了一些不寻常的结果,例如,这里有一些不同代码点的长度和大小。 下面是我如何生成这些表的片段。

...
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,但长度返回“H”加“^”的长度。

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 字符串中元素的开始和结束位置?

我知道我使用单词 element 的术语可能不正确,但我认为 codepoint 和 character 也不正确,特别是考虑到一个元素的 codepoint 大小可能为 3,但长度仅为 1。

我试图更好地理解 Delphi 中的代理对和 Unicode 实现。

让我们先了解一些术语。

由的Unicode定义的每个“字符”(被称为石墨烯)被分配一个唯一的代码点

在一个Unicode转换格式(UTF)编码- UTF-7,UTF-8,UTF-16,和UTF-32 -每个码点被编码为CODEUNITS的序列。 每个代码单元的大小由编码决定 - UTF-7 为 7 位,UTF-8 为 8 位,UTF-16 为 16 位,UTF-32 为 32 位(因此得名)。

在 Delphi 2009 及更高版本中, StringUnicodeString的别名,而CharWideChar的别名。 WideChar是 16 位。 UnicodeString包含一个UTF-16编码的字符串(在早期版本的 Delphi 中,等效的字符串类型是WideString ),每个WideChar是一个 UTF-16 代码单元。

在 UTF-16 中,可以使用 1 或 2 个代码单元对代码点进行编码。 1 个代码单元可以对基本多语言平面 (BMP) 范围内的代码点值进行编码 - $0000 到 $FFFF,含。 更高的代码点需要 2 个代码单元,也称为代理对

如果我在 Delphi 中对 Unicode 字符串 S := 'Ĥà̲V̂e' 调用 length(),我会返回,8。

这是因为单个字符[Ĥ]、[à̲]、[V̂]和[e]的长度分别为2、3、2和1。

这是因为 Ĥ 有一个代理,à̲ 有两个额外的代理,V̂ 有一个代理,而 e 没有代理。

是的,您的 UTF-16 UnicodeString有 8 个WideChar元素(代码单元)。 您所说的“代理”实际上被称为“组合标记”。 每个组合标记是它自己唯一的代码点,因此是它自己的代码单元序列。

如果我想返回包含所有代理项的字符串中的第二个元素 [à̲],我该怎么做?

您必须从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;

我知道我需要对单个字节进行某种测试。

不是bytes ,而是它们在解码时表示的代码点

我使用例程进行了一些测试

函数 GetFirstCodepointSize(const S: UTF8String): 整数

仔细看看那个函数签名。 看到参数类型了吗? 它是一个UTF-8字符串,而不是一个UTF-16字符串。 甚至在您从以下位置获得该功能的答案中也说明了这一点:

这是一个如何解析UTF8字符串的示例

UTF-8 和 UTF-16 是非常不同的编码,因此具有不同的语义。 您不能使用 UTF-8 语义来处理 UTF-16 字符串,反之亦然。

Delphi 中是否有一种可靠的方法来确定 Unicode 字符串中元素的开始和结束位置?

不直接。 您必须从头开始解析字符串,根据需要跳过元素,直到到达所需的元素。 请记住,每个代码点都可以编码为 1 或 2 个代码单元元素,并且每个逻辑字形都可以使用多个代码点(以及多个代码单元序列)进行编码。

我知道我使用单词 element 的术语可能不正确,但我认为 codepoint 和 character 也不正确,特别是考虑到一个元素的 codepoint 大小可能为 3,但长度仅为 1。

1 个字形由 1+ 个代码点组成,每个代码点被编码为 1+ 个代码单元。

有人可以实现以下功能吗?

函数 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;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM