Как бы вы получили массив кодовых точек Unicode из строки.NET?
У меня есть список ограничений диапазона символов, с которыми мне нужно проверить строку, но char
введите в.NET UTF-16, и поэтому некоторые символы вместо этого превращаются в причудливые (суррогатные) пары. Таким образом, перечисляя все char
в string
Я не получаю 32-битный код Unicode, и некоторые сравнения с высокими значениями не удаются.
Я достаточно хорошо понимаю Unicode, чтобы при необходимости самостоятельно разбирать байты, но я ищу решение C#/.NET Framework BCL. Так...
Как бы вы преобразовали string
в массив (int[]
) 32-битных кодовых точек Unicode?
6 ответов
Этот ответ не верен. Смотрите ответ @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();
}
Примечания: нормализация необходима для работы с составными символами.
Вы спрашиваете о кодах. В UTF-16 (C# х char
) есть только две возможности:
- Символ взят из базовой многоязычной плоскости и кодируется одной кодовой единицей.
- Символ находится за пределами BMP и закодирован с использованием суррогатной пары кодов с высоким и низким уровнем
Следовательно, предполагая, что строка допустима, это возвращает массив точек кода для данной строки:
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
с последующим сочетанием тильды ̃◌
, Решение Леппи отбрасывает любые объединяющие символы, которые нельзя нормализовать в одну кодовую точку.
Не похоже, что это должно быть намного сложнее, чем это:
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;
}
}
Это решение дает те же результаты, что и решение Daniel AA Pelsmaeker, но немного короче:
public static int[] ToCodePoints(string s)
{
byte[] utf32bytes = Encoding.UTF32.GetBytes(s);
int[] codepoints = new int[utf32bytes.Length / 4];
Buffer.BlockCopy(utf32bytes, 0, codepoints, 0, utf32bytes.Length);
return codepoints;
}
Я предложил тот же подход, предложенный Николасом (и Джеппе), только короче:
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();
Еще одно решение отсюда :
public static int[] GetCodePoints(string input)
{
var cp_lst = new ArrayList();
for (var i = 0; i < input.Length; i += char.IsSurrogatePair(input, i) ? 2 : 1) {
int codepoint = char.ConvertToUtf32(input, i);
cp_lst.Add(codepoint);
//Console.WriteLine(codepoint);
}
return (int[]) cp_lst.ToArray(typeof(int));
}