2009-03-26 11 views
18

Mam listę ograniczeń zakresu znaków, które muszę sprawdzić ciąg znaków przed, ale typ char w .NET jest UTF-16 i dlatego niektóre postacie zamiast tego stają się zwariowane (zastępcze). Tak więc przy wyliczaniu wszystkich char w string, nie otrzymuję 32-bitowych punktów kodowych Unicode i niektóre porównania z wartościami wysokimi kończą się niepowodzeniem.Jak można uzyskać tablicę punktów kodu Unicode z ciągu .NET?

Rozumiem Unicode na tyle dobrze, żebym mógł w razie potrzeby sparsować bajty, ale szukam rozwiązania C#/.NET Framework BCL. Więc ...

Jak przekonwertować string na tablicę (int[]) 32-bitowych punktów kodu Unicode?

Odpowiedz

9

Ta odpowiedź jest nieprawidłowa. Zobacz odpowiedź @ Virtlink na prawidłową.

static int[] ExtractScalars(string s) 
{ 
    if (!s.IsNormalized()) 
    { 
    s = s.Normalize(); 
    } 

    List<int> chars = new List<int>((s.Length * 3)/2); 

    var ee = StringInfo.GetTextElementEnumerator(s); 

    while (ee.MoveNext()) 
    { 
    string e = ee.GetTextElement(); 
    chars.Add(char.ConvertToUtf32(e, 0)); 
    } 

    return chars.ToArray(); 
} 

Uwagi: Normalizacja jest wymagane do czynienia z postaciami kompozytowych.

+2

▼: Twoje rozwiązanie odrzuca wszelkie modyfikatory chara cters, i masz do czynienia z _text elements_, a nie _code points_. Na przykład wynikiem 'ExtractScalars (" El Ni \ u006E \ u0303o ")' przekonwertowane z powrotem na ciąg znaków byłoby '" El Nino "' zamiast '" El Niño "'. – Virtlink

+0

@Virtlink: Interesujące. Z dokumentów musi to brzmieć jak 'char.ConvertToUtf32 (string, int)' powinien sobie z tym poradzić. Edytuj: Cholerni doktorzy twierdzą, że powinno! https://msdn.microsoft.com/en-us/library/z2ys180b(v=vs.110).aspx – leppie

+0

@Virtlink: Ok, nie dotyczy znaków złożonych, ale nie zastępuje par zastępczych. – leppie

16

Pytasz o punktów kodowych. W UTF-16 (C# 's char) istnieją tylko dwie możliwości:

  1. Znak jest z Podstawowe Multilingual Plane i jest kodowany przez pojedynczą jednostkę kodu.
  2. charakter jest poza BMP i kodowane przy użyciu surrogare wysokie niskie parą zespołów kodów

związku z tym, zakładając, że łańcuch jest ważny, to zwraca tablicy kodu punktów dla danego ciąg:

public static int[] ToCodePoints(string str) 
{ 
    if (str == null) 
     throw new ArgumentNullException("str"); 

    var codePoints = new List<int>(str.Length); 
    for (int i = 0; i < str.Length; i++) 
    { 
     codePoints.Add(Char.ConvertToUtf32(str, i)); 
     if (Char.IsHighSurrogate(str[i])) 
      i += 1; 
    } 

    return codePoints.ToArray(); 
} 

przykładem z zastępczym pary i złożonego charakteru ñ:

ToCodePoints("\U0001F300 El Ni\u006E\u0303o");      // El Niño 
// { 0x1f300, 0x20, 0x45, 0x6c, 0x20, 0x4e, 0x69, 0x6e, 0x303, 0x6f } // E l N i n ̃◌ o 

Oto kolejny przykład. Te dwa punkty kod reprezentuje 32th nuta z akcent staccato zarówno zastępczych parach

ToCodePoints("\U0001D162\U0001D181");    // 
// { 0x1d162, 0x1d181 }       // ◌ 

Gdy C-normalized, są rozkładane do notehead, łącząc trzpień, łączenie flag i łączenie akcent staccato wszelkie zastępczych parach

ToCodePoints("\U0001D162\U0001D181".Normalize()); // 
// { 0x1d158, 0x1d165, 0x1d170, 0x1d181 }   // ◌ 

Zauważ, że leppie's solution nie jest poprawna. Pytanie dotyczy punktów kodowych, a nie elementów tekstowych. Element tekstowy to kombinacja punktów kodowych, które razem tworzą pojedynczy grafem. Na przykład w powyższym przykładzie wartość ñ w łańcuchu jest reprezentowana przez łacińską małą literę n, a następnie kombinację tyldy ̃◌. Rozwiązanie Leppie'ego odrzuca wszystkie kombinacje znaków, których nie można znormalizować w jeden punkt kodowy.

+1

Użyłbym 'var codePoint = Char.ConvertToUtf32 (...); if (codePoint> 0xFFFF) i ++; 'zamiast' Char.IsHighSurrogate'. – CodesInChaos

+0

@CodesInChaos: Wierzę, że byłby równoważny. Jeśli i tylko jeśli pierwszy znak jest wysokim zastępcą, możesz uzyskać punkt kodowy powyżej '0xFFFF', ale proszę powiedz mi, czy się mylę. – Virtlink

+0

To odpowiednik. To była tylko sugestia stylistyczna. – CodesInChaos

3

nie wydaje się powinno być znacznie bardziej skomplikowana niż to:

public static IEnumerable<int> Utf32CodePoints(this IEnumerable<char> s) 
{ 
    bool  useBigEndian = !BitConverter.IsLittleEndian; 
    Encoding utf32  = new UTF32Encoding(useBigEndian , false , true) ; 
    byte[] octets  = utf32.GetBytes(s) ; 

    for (int i = 0 ; i < octets.Length ; i+=4) 
    { 
    int codePoint = BitConverter.ToInt32(octets,i); 
    yield return codePoint; 
    } 

} 
+0

'BitConverter' używa native endianness,' Encoding.UTF32' używa little endian. Tak więc złamie się duży system endianowski. – CodesInChaos

+1

Chcę tylko powiedzieć, że napisałem to samo rozwiązanie (w zasadzie) jako komentarz do odpowiedzi leppiego, _six sekundy_ przed przesłaniem odpowiedzi. Wspomniałem także o kłopotach z endianizmem. –

+0

@JeppeStigNielsen: Oczywiście, wielkie umysły myślą podobnie :) –

0

wpadłem na same approach sugerowanej przez Mikołaja (Jeppe), po prostu krócej:

public static IEnumerable<int> GetCodePoints(this string s) { 
     var utf32 = new UTF32Encoding(!BitConverter.IsLittleEndian, false, true); 
     var bytes = utf32.GetBytes(s); 
     return Enumerable.Range(0, bytes.Length/4).Select(i => BitConverter.ToInt32(bytes, i * 4)); 
    } 

wyliczenie było wszystkim, czego potrzebowałem, ale uzyskanie tablicy jest banalne:

int[] codePoints = myString.GetCodePoints().ToArray(); 
Powiązane problemy