2009-03-26 1316 views
18

Tengo una lista de restricciones de rango de caracteres con las que necesito verificar una cadena, pero el tipo char en .NET es UTF-16 y, por lo tanto, algunos personajes se convierten en pares extravagantes (sustituto). Por lo tanto, al enumerar todos los char en un string, no obtengo los puntos de código Unicode de 32 bits y algunas comparaciones con valores altos fallan.¿Cómo obtendría una matriz de puntos de código Unicode de una cadena .NET?

Entiendo Unicode lo suficientemente bien como para poder analizar los bytes yo mismo si es necesario, pero estoy buscando una solución C#/.NET Framework BCL. Entonces ...

¿Cómo convertirías un string a una matriz (int[]) de puntos de código Unicode de 32 bits?

Respuesta

9

Esta respuesta no es correcta. Ver la respuesta de @Virtlink para la correcta. La normalización es necesaria para hacer frente a los caracteres compuestos:

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

Notas.

+2

▼: Su solución descarta cualquier chara de modificador cters, y se trata de _text elements_ y no _code points_. Por ejemplo, el resultado de 'ExtractScalars (" El Ni \ u006E \ u0303o ")' convertido de nuevo a una cadena sería '" El Niño "' en lugar de '" El Niño "'. – Virtlink

+0

@Virtlink: Interesante. Desde los documentos, debe haber sonado como 'char.ConvertToUtf32 (string, int)' debería tratarlo. Editar: ¡Los malditos documentos dicen que debería! https://msdn.microsoft.com/en-us/library/z2ys180b(v=vs.110).aspx – leppie

+0

@Virtlink: Ok, no se trata de caracteres compuestos, pero lo hace para los pares suplentes. – leppie

16

Usted está preguntando acerca de puntos de código. En UTF-16 (C# 's char) sólo hay dos posibilidades:

  1. El personaje es desde el plano multilingüe básica, y se codifica por una sola unidad de código.
  2. El carácter está fuera del BMP, y codificado utilizando un par surrogare alto-bajo de unidades de código

Por lo tanto, suponiendo que la cadena es válida, este devuelve una matriz de código puntos para un determinado cadena:

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 ejemplo con un par suplente y un carácter compuesto ñ:

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 

Aquí hay otro ejemplo. Estos dos puntos de código representa una nota 32 ª musical con un acento staccato, ambos pares suplentes:

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

Cuando C-normalized, que se descomponen en una cabeza de nota, la combinación de vástago, la combinación de la bandera y la combinación de acento-staccato, todos los pares suplentes:

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

Tenga en cuenta que leppie's solution no es correcto. La pregunta es sobre puntos de código, no elementos de texto. Un elemento de texto es una combinación de puntos de código que juntos forman un solo grafema. Por ejemplo, en el ejemplo anterior, el ñ en la cadena está representado por una minúscula latina n seguida de una tilde combinada ̃◌. La solución de Leppie descarta cualquier combinación de caracteres que no se puedan normalizar en un solo punto de código.

+1

Usaría 'var codePoint = Char.ConvertToUtf32 (...); if (codePoint> 0xFFFF) i ++; 'en lugar de' Char.IsHighSurrogate'. – CodesInChaos

+0

@CodesInChaos: Creo que eso sería equivalente. Si y solo si el primer char es un sustituto alto, puede obtener un punto de código por encima de '0xFFFF', pero dígame si me equivoco. – Virtlink

+0

Es equivalente. Fue solo una sugerencia estilística. – CodesInChaos

3

no parece que debería ser mucho más complicado que esto:

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' utiliza little endian. Entonces esto se romperá en un gran sistema endian. – CodesInChaos

+1

Solo quiero decir que publiqué la misma solución (prácticamente) como un comentario a la respuesta de Leppie, _six segundos_ antes de enviar su respuesta. Y mencionó problemas endianness también. –

+0

@JeppeStigNielsen: Claramente, las mentes geniales piensan igual :) –

0

me ocurrió con el same approach sugerido por Nicholas (y Jeppe), simplemente 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)); 
    } 

El la enumeración era todo lo que necesitaba, pero obtener una matriz es trivial:

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