Как бы вы получили массив кодовых точек Unicode из строки .NET? - PullRequest
19 голосов
/ 26 марта 2009

У меня есть список ограничений диапазона символов, по которым мне нужно проверить строку, но тип char в .NET - это UTF-16, и поэтому некоторые символы вместо этого превращаются в причудливые (суррогатные) пары. Таким образом, при перечислении всех char в string я не получаю 32-битные точки кода Unicode, и некоторые сравнения с высокими значениями заканчиваются неудачей.

Я достаточно хорошо понимаю Unicode, чтобы при необходимости самостоятельно разбирать байты, но я ищу решение C # / .NET Framework BCL. Итак ...

Как бы вы преобразовали string в массив (int[]) 32-битных кодовых точек Unicode?

Ответы [ 4 ]

19 голосов
/ 26 января 2015

Вы спрашиваете о кодовых точках . В UTF-16 (C # char) есть только две возможности:

  1. Символ взят из Базовой многоязычной плоскости и кодируется одной кодовой единицей.
  2. Символ находится вне BMP и закодирован с использованием суррогатной пары старших-младших единиц кода

Следовательно, если строка является допустимой, возвращается массив кода points для данной строки:

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

Пример с суррогатной парой ? и составным персонажем ñ:

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

Вот еще один пример. Эти две кодовые точки представляют 32-ю музыкальную ноту со стаккато-акцентом, обе суррогатные пары:

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

Когда C-нормализовано , они разлагаются на головку, объединяя ствол, объединяя флаг и комбинируя акцент-стаккато, все суррогатные пары:

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

Обратите внимание, что решение Леппи неверно. Вопрос о кодовых точках , а не текстовых элементах . Текстовый элемент представляет собой комбинацию кодовых точек, которые вместе образуют одну графему. Например, в приведенном выше примере символ ñ в строке представлен строчной латинской буквой n, за которой следует тильда объединения ̃◌. Решение Леппи отбрасывает любые объединяющие символы, которые нельзя нормализовать в одну кодовую точку.

8 голосов
/ 26 марта 2009

Этот ответ не верный. См. Правильный ответ @ Virtlink.

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

Примечания : нормализация необходима для работы с составными символами.

4 голосов
/ 26 января 2015

Не похоже, что это должно быть намного сложнее, чем это:

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 голосов
/ 19 июля 2016

Я придумал такой же подход , предложенный Николасом (и Джеппе), только короче:

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

Перечисление было все, что мне нужно, но получить массив тривиально:

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