2015-08-14 8 views
6

Ich versuche, Ersatzpaare und Unicode-Implementierung in Delphi besser zu verstehen.Erkennen und Abrufen von Codepoints und Surrogate aus einer Delphi-Zeichenfolge

Wenn ich Länge() aufrufen, auf der Unicode-String S: = 'haben' in Delphi, ich werde zurückkommen, 8.

Dies liegt daran, die Längen der einzelnen Zeichen [H], [a] , [V] und [e] sind 2, 3, 2 bzw. 1. Dies liegt daran, dass Ĥ einen Surrogat hat, has zwei zusätzliche Surrogate hat, V einen Surrogat hat und e keine Surrogate hat.

Wenn ich das zweite Element in der Zeichenfolge einschließlich aller Surrogate zurückgeben wollte, [à], wie würde ich das tun? Ich weiß, dass ich die einzelnen Bytes testen müsste. Ich habe einige Tests mit der Routine

durchgeführt, die in this SO Question verwiesen wird.

haben aber einige ungewöhnliche Ergebnisse, zB hier sind einige Längen und Größen von verschiedenen Codepunkten. Unten ist ein Ausschnitt, wie ich diese Tabellen erzeugt habe.

... 
UTFCRUDResultStrings.add('INPUT: '+#9#9+ DATA +#9#9+ 'GetFirstCodePointSize = ' +intToStr(GetFirstCodepointSize(DATA)) 
+#9#9+ 'Length =' + intToStr(length(DATA))); 
... 

Erster Satz: Das macht Sinn für mich ist jeder Code Punktgröße verdoppelt, aber diese sind ein Zeichen jeder und Delphi gibt mir die Länge als nur 1, perfekt.

Zweiter Satz: Es sieht zunächst so aus, als ob die Längen und Codepunkte umgekehrt sind? Ich vermute der Grund dafür ist, dass die Zeichen + Surrogate einzeln behandelt werden, daher ist die erste Codepunktgröße für das 'H', das ist 1, aber die Länge gibt die Längen von 'H' plus '^' zurück.

INPUT:  Ĥ  GetFirstCodePointSize = 1  Length =2 
INPUT:  à̲  GetFirstCodePointSize = 1  Length =3 
INPUT:  V̂  GetFirstCodePointSize = 1  Length =2 
INPUT:  e  GetFirstCodePointSize = 1  Length =1 

Einige zusätzliche Tests ...

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 

Gibt es eine zuverlässige Art und Weise in Delphi, um zu bestimmen, wo ein Element in einem Unicode-String beginnt und endet?

Ich weiß, dass meine Terminologie mit dem Wort-Element möglicherweise ausgeschaltet ist, aber ich glaube nicht, Codepoint und Zeichen sind auch nicht richtig, vor allem gegeben, dass ein Element eine Codepoint-Größe von 3 haben kann, aber nur eine Länge haben.

+0

* Könnte jemand die folgende Funktion implementieren * Dies ist kein Schreiben von Code-Dienst ist, wo Sie Ihre Anforderungen und jemand schreiben churns den Code aus, sie zu treffen?. Bemühen Sie sich, es selbst zu schreiben. Wenn Sie Probleme haben, schreiben Sie den Code, den Sie geschrieben haben, erklären Sie, wie es nicht wie erwartet funktioniert, und stellen Sie eine ** spezifische Frage ** zu diesem Code, und wir können versuchen, Ihnen zu helfen. * Bitte gib mir den Code * ist keine gültige Frage hier. –

Antwort

12

Ich versuche, Ersatzpaare und Unicode-Implementierung in Delphi besser zu verstehen.

Lassen Sie uns einige Begriffe aus dem Weg räumen.

jedes „Zeichen“ (auch bekannt als Graphem), die von Unicode definiert ist, wird eine einzigartige Codepunkt zugeordnet.

In einer Unicode Transformation Format (UTF) Kodierung - UTF-7, UTF-8, UTF-16 und UTF-32 - jeder Codepunkt wird als eine Folge von kodierten Codeunits. Die Größe jeder Codeeinheit wird durch die Codierung bestimmt - 7 Bits für UTF-7, 8 Bits für UTF-8, 16 Bits für UTF-16 und 32 Bits für UTF-32 (daher deren Namen).

In Delphi 2009 und später ist String ein Alias ​​für UnicodeString, und Char ist ein Alias ​​für WideChar. WideChar ist 16 Bits. A UnicodeString enthält eine codierte Zeichenfolge UTF-16 (in früheren Versionen von Delphi war der entsprechende Zeichenfolientyp WideString) und jede WideChar ist eine UTF-16-Codeeinheit.

In UTF-16 kann ein Codepunkt mit 1 oder 2 Codeeinheiten codiert werden. Eine Codeeinheit kann Codepunktwerte im BMP-Bereich (Basic Multilingual Plane) codieren - $ 0000 bis einschließlich $ FFFF. Höhere Codepunkte erfordern 2 Codeeinheiten, die auch als Ersatzpaar bezeichnet werden.

Wenn ich Länge() aufrufen, auf der Unicode-String S: = 'haben' in Delphi, ich werde zurückkommen, 8.

Dies liegt daran, die Längen der einzelnen Zeichen [H], [ à], [V] und [e] sind 2, 3, 2 bzw. 1.

Dies ist, weil Ĥ ein Ersatz hat, has hat zwei zusätzliche Surrogate, V hat ein Surrogat und e hat keine Surrogate.

Ja, gibt es 8 WideChar Elemente (Codeunits) im UTF-16 UnicodeString. Was Sie "Surrogate" nennen, nennt man eigentlich "Kombinationszeichen". Jede Kombinationsmarkierung ist ihr eigener eindeutiger Codepunkt und somit ihre eigene Codeeinheitssequenz.

Wenn ich das zweite Element in der Zeichenfolge einschließlich aller Ersatzzeichen zurückgeben wollte, [à], wie würde ich das tun?

Sie haben zu Beginn des UnicodeString starten und jedes WideChar analysieren, bis Sie einen finden, der nicht ein Kombinationszeichen zu einem früheren WideChar angebracht ist. Unter Windows ist der einfachste Weg, dies zu tun, um die CharNextW() Funktion, zB zu verwenden:

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

Das Delphi-RTL keine entsprechende Funktion hat. Sie würden einen manuell schreiben oder eine Bibliothek eines Drittanbieters verwenden. Die RTL hat eine StrNextChar() Funktion, aber es behandelt nur UTF-16-Surrogate, keine Kombination von Marken (CharNext() behandelt beide). So konnten Sie StrNextChar() verwenden, um durch jeden Codepunkt in der UnicodeString zu scannen, aber man muss bei jedem Codepunkt loo wissen, ob es sich um eine Kombination von Zeichen ist oder nicht, zum Beispiel:

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; 

Ich weiß, dass ich brauchen würde um die einzelnen Bytes zu testen.

Nicht die Bytes, aber die Codepoints, dass sie, wenn dekodiert darstellen.

lief ich ein paar Tests, um die Routine

Funktion GetFirstCodepointSize (const S: UTF8String): mit Integer

Schauen Sie genau in dieser Funktion Unterschrift. Siehe den Parametertyp? Es ist eine UTF-8 Zeichenfolge, keine UTF-16 Zeichenfolge. Dies wurde auch in der Antwort angegeben Sie diese Funktion aus bekam:

Hier ist ein Beispiel, wie man analysieren UTF8 String

UTF8 und UTF-16 sind sehr unterschiedliche Kodierungen und damit haben unterschiedliche Semantiken. Sie können UTF-8-Semantik nicht verwenden, um eine UTF-16-Zeichenfolge zu verarbeiten und umgekehrt.

Gibt es einen zuverlässigen Weg in Delphi zu bestimmen, wo ein Element in einer Unicode-Zeichenfolge beginnt und endet?

Nicht direkt. Sie müssen die Zeichenfolge von Anfang an analysieren und Elemente nach Bedarf überspringen, bis Sie das gewünschte Element erreicht haben. Denken Sie daran, dass jeder Codepunkt als 1 oder 2 Codeeinheitselemente codiert sein kann und jedes logische Symbol unter Verwendung mehrerer Codepunkte (und somit mehrerer Codeeinheitsequenzen) codiert sein kann.

Ich weiß, meine Terminologie mit dem Wort Element kann aus sein, aber ich glaube nicht, Codepoint und Zeichen sind entweder richtig, vor allem gegeben, dass ein Element eine Codepoint-Größe von 3 haben kann, aber nur eine Länge von ein.

1 Glyphe besteht aus 1 + Codepoints, und jeder Codepunkt ist als 1+ Codeunits codiert.

Kann jemand die folgende Funktion implementieren?

Funktion GetElementAtIndex (S: String; StrIdx: Integer): String;

so etwas wie dieses Versuchen:

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

danke für alle Details. Dies macht auch deutlich, dass das Indizieren einer utf16-Zeichenfolge, z. B. S [i], nicht immer wie erwartet funktioniert, da das Zeichen selbst Kombinierungszeichen haben kann oder nicht und möglicherweise nicht in ein widehear passt. Danke, dass du mir geholfen hast, das besser zu verstehen. – sse

+0

Ich glaube, dass eine automatische Konvertierung von utf16 zu utf8 in der Funktion getFirstCodePointSize auftritt. Ich werde versuchen, eine Referenz zu finden. Danke noch einmal. – sse

+0

Ja, es gibt eine automatische Konvertierung, wenn Sie einen Zeichenkettentyp einem anderen zuweisen. 'UTF8String' und' UnicodeString' sind separate String-Typen. 'getFirstCodePointSize()' nimmt einen 'UTF8String' als Eingabe, so dass Informationen zu UTF-8 zurückgegeben werden, nicht zu UTF-16.In diesem Fall wird die Anzahl der 8-Bit-Codeeinheiten zurückgegeben, die zum Codieren des ersten Codepunkts in der UTF-8-Zeichenfolge verwendet wurden. UTF-8 codiert einen Codepunkt unter Verwendung von 1, 2, 3 oder 4 8-Bit-Codeeinheiten. Wie ich bereits sagte, codiert UTF-16 einen Codepunkt mit 1 oder 2 16-Bit-Codeunits. Deshalb habe ich gesagt, dass Sie keine UTF-8-Semantik verwenden können, um eine UTF-16-Zeichenfolge zu verarbeiten. –