2009-03-26 10 views
18

Ho un elenco di restrizioni dell'intervallo di caratteri di cui ho bisogno per controllare una stringa, ma il tipo char in .NET è UTF-16 e quindi alcuni caratteri diventano invece coppie stravaganti (surrogate). Pertanto, quando si elencano tutti gli char in un string, non si ottengono i punti di codice Unicode a 32 bit e alcuni confronti con valori elevati non riescono.Come si ottiene un array di punti di codice Unicode da una stringa .NET?

Capisco Unicode abbastanza bene da poterne analizzare personalmente i byte se necessario, ma sto cercando una soluzione BCL C# /. NET Framework. Quindi ...

Come convertire un string in un array (int[]) di punti di codice Unicode a 32 bit?

risposta

9

Questa risposta non è corretta. Vedi la risposta di @ Virtlink per quella corretta.

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(); 
} 

Note: Normalizzazione è tenuto a trattare con i caratteri compositi.

+2

▼: La tua soluzione scarta qualsiasi modificatore chara chiavi, e hai a che fare con _text elements_ e non _code points_. Ad esempio, il risultato di 'ExtractScalars (" El Ni \ u006E \ u0303o ")' convertito in una stringa sarebbe '' El Nino ''invece di' "El Niño" '. – Virtlink

+0

@Virtlink: interessante. Dai documenti deve aver suonato come 'char.ConvertToUtf32 (string, int)' dovrebbe occuparsene. Modifica: I dannati dottori afferma che dovrebbe! https://msdn.microsoft.com/en-us/library/z2ys180b(v=vs.110).aspx – leppie

+0

@Virtlink: Ok, non tratta i caratteri composti, ma fa per le coppie surrogate. – leppie

16

Stai chiedendo informazioni su punti codice. In UTF-16 (C s 'char #) ci sono solo due possibilità:

  1. Il personaggio è dal Piano base multilingue, ed è codificato da una singola unità di codice.
  2. Il personaggio è fuori della BMP, e codificati utilizzando una surrogare coppia di unità di codice alto-basso

Pertanto, assumendo la stringa è valida, restituisce una matrice di codice punti per un determinato stringa:

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(); 
} 

un esempio con una coppia di surrogati ed un carattere composto ñ:

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 

Ecco un altro esempio. Questi due punti di codice rappresenta una nota 32th musicale con un accento staccato, entrambe le coppie di surrogati:

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

Quando C-normalized, vengono scomposte in una testa nota, combinando gambo, combinando bandiera e combinando accento staccato, tutte le coppie di surrogati:

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

Nota che leppie's solution non è corretto. La domanda è circa punti codice, non elementi di testo. Un elemento di testo è una combinazione di punti di codice che insieme formano un singolo grafo. Ad esempio, nell'esempio sopra, lo ñ nella stringa è rappresentato da una minuscola latina n seguita da una combinazione di tilde ̃◌. La soluzione di Leppie elimina qualsiasi combinazione di caratteri che non può essere normalizzata in un singolo punto di codice.

+1

Vorrei usare 'var codePoint = Char.ConvertToUtf32 (...); if (codePoint> 0xFFFF) i ++; 'invece di' Char.IsHighSurrogate'. – CodesInChaos

+0

@CodesInChaos: credo che sarebbe equivalente. Se e solo se il primo carattere è un surrogato elevato, puoi mai ottenere un punto di codice sopra '0xFFFF', ma per favore dimmi se mi sbaglio. – Virtlink

+0

È equivalente. Era solo un suggerimento stilistico. – CodesInChaos

3

non sembra come dovrebbe essere molto più complicato di questo:

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' usa endianness nativo,' Encoding.UTF32' usa little endian. Quindi questo si romperà su un grande sistema endian. – CodesInChaos

+1

Voglio solo dire che ho postato la stessa soluzione (praticamente) come commento alla risposta di leppie, _six seconds_ prima di inviare la risposta. E ha menzionato anche i problemi di endianità. –

+0

@JeppeStigNielsen: Chiaramente, le grandi menti pensano allo stesso modo :) –

0

mi si avvicinò con la same approach suggerito da Nicola (e Jeppe), appena più corta:

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

Il l'enumerazione era tutto ciò di cui avevo bisogno, ma ottenere un array è banale:

int[] codePoints = myString.GetCodePoints().ToArray();