Как вы получите массив кодов Unicode из .NET String?
У меня есть список ограничений диапазона символов, которые мне нужны для проверки строки, но тип char
в .NET - это UTF-16, и поэтому некоторые символы становятся дурацкими (суррогатными) парами. Таким образом, при перечислении всех char
в string
я не получаю 32-битные кодовые точки Unicode, а некоторые сравнения с высокими значениями не работают.
Я понимаю Юникод достаточно хорошо, чтобы я мог самостоятельно разбирать байты, но я искал решение BCL С#/.NET Framework. Итак...
Как бы вы преобразовали string
в массив (int[]
) 32-разрядных кодовых точек Unicode?
Ответы
Ответ 1
Этот ответ неверен. См. Ответ @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();
}
Примечания. Для обработки составных символов требуется нормализация.
Ответ 2
Вы спрашиваете о кодовых точках. В UTF-16 (С# 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-normalized, они разлагаются в записную книжку, объединяют стебель, объединяют флаг и объединяют акцент-стаккато, все суррогатные пары:
ToCodePoints("\U0001D162\U0001D181".Normalize()); // 𝅘𝅥𝅰𝆁
// { 0x1d158, 0x1d165, 0x1d170, 0x1d181 } // 𝅘 𝅥 𝅰 𝆁◌
Обратите внимание, что leppie solution неверно. Речь идет о кодовых точках, а не о текстовых элементах. Текстовый элемент представляет собой комбинацию кодовых точек, которые вместе образуют единую графему. Например, в приведенном выше примере строка ñ
в строке представлена латинским нижним регистром n
, за которым следует комбинированная тильда ̃◌
. Решение Leppie отбрасывает любые комбинации символов, которые не могут быть нормализованы в единую кодовую точку.
Ответ 3
Не похоже, что это должно быть намного сложнее, чем это:
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;
}
}
Ответ 4
Я придумал тот же подход, предложенный Николасом (и Джеппе), короче:
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();