2015-08-14 38 views
6

我想更好地理解Delphi中的代理对和Unicode实现。从Delphi字符串中检测和检索代码点和代理

如果我请的Unicode串S的长度():=“具有”在Delphi,我会回来,8.

这是因为,各个字符的长度[H],[A] ,[V]和[e]分别是2,3,2和1。这是因为Ĥ有替代物,有另外两个替代物,V有替代物,e没有替代物。

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

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))); 
... 

第一组:这对我来说很有意义,每个代码点的大小一倍,但这些都是一个字每德尔福给我的长度只有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 

是否有德尔福一种可靠的方法来确定一个元素在一个Unicode字符串开始和结束?

我知道使用单词元素的术语可能是关闭的,但我不认为代码点和字符是正确的,尤其是考虑到一个元素的代码点大小为3,但长度只有一个。

+0

*有人能实现以下功能*这不是一个代码编写的服务,让您发表您的要求,有人搅动了代码来满足他们?尽你最大的努力来自己写。如果遇到困难,请发布您编写的代码,解释它如何不按照您的期望工作,并询问有关该代码的**特定问题**,我们可以尝试帮助您。 *请给我代码*在这里不是一个有效的问题。 –

回答

12

我想在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位。 A UnicodeString包含一个UTF-16编码字符串(在Delphi的早期版本中,等效字符串类型为WideString),并且每个WideChar都是UTF-16编码单元。

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

如果我请的Unicode串S的长度():= '具有' 在Delphi,我会回来,8.

这是因为,各个字符的长度[H],[ à],[V]和[e]分别为2,3,2和1。

这是因为Ĥ有替代物,à有两个替代物,V有替代物,e没有替代物。

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

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

你必须开始在UnicodeString的开始和分析每个WideChar,直到你找到一个没有连接到以前WideChar一个组合标志。在Windows中,要做到这一点最简单的方法是使用CharNextW()功能,如:

var 
    S: String; 
    P: PChar; 
begin 
    S := 'Ĥà̲V̂e'; 
    P := CharNext(PChar(S)); // returns a pointer to à̲ 
end; 

德尔福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; 

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

不是字节,但码点是解码时它们代表。在该函数签名整数

仔细查看:

我跑使用常规

功能GetFirstCodepointSize(常量S:UTF8字符串)一些测试。看到参数类型?它是一个UTF-8字符串,而不是一个UTF-16字符串。这是即使在回答说你得到了函数:

下面是一个例子,如何解析UTF8

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

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

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

我知道使用单词元素的术语可能是关闭的,但我不认为代码点和字符是正确的,特别是考虑到一个元素的代码点大小为3,但长度仅为一。

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; 
+0

谢谢你的所有细节。这也清楚地表明,索引一个utf16字符串,例如S [i]并不总是按预期工作,因为字符本身可能有也可能没有组合标记,并且可能不适合widechar。感谢您帮助我更好地理解这一点。 – sse

+0

我确实相信在函数getFirstCodePointSize中会发生从utf16到utf8的自动转换。我会尽力找到一个参考。再次感谢。 – sse

+0

是的,将一个字符串类型分配给另一个字符串类型时会自动进行转换。 'UTF8String'和'UnicodeString'是不同的字符串类型。 'getFirstCodePointSize()'将一个'UTF8String'作为输入,所以它将返回与UTF-8相关的信息,而不是UTF-16。在这种情况下,它将返回用于编码UTF-8字符串中第一个代码点的8位代码单元的数量。 UTF-8使用1个,2个,3个或4个8位编码单元编码一个编码点。正如我前面所说的,UTF-16使用1或2个16位编码单元编码一个编码点。这就是为什么我说你不能使用UTF-8语义来处理UTF-16字符串。 –