2015-08-14 69 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字符串。 –