Конвертировать суррогатную пару Unicode в буквальную строку
Я пытаюсь прочитать высокий символ Unicode из одной строки в другую. Для краткости я упросту свой код, как показано ниже:
public static void UnicodeTest()
{
var highUnicodeChar = ""; //Not the standard A
var result1 = highUnicodeChar; //this works
var result2 = highUnicodeChar[0].ToString(); // returns \ud835
}
Когда я назначаю highUnicodeChar
в result1
напрямую, он сохраняет свою буквальную ценность , Когда я пытаюсь получить доступ к нему по индексу, он возвращает
\ud835
, Насколько я понимаю, это суррогатная пара символов UTF-16, используемая для представления символа UTF-32. Я уверен, что эта проблема связана с попыткой неявного преобразования char
к string
,
В конце концов, я хочу result2
дать то же значение, что и result1
, Как я могу это сделать?
2 ответа
В Unicode у вас есть кодовые точки. Это 21 бит длиной. У вашего персонажа, "Математический жирный шрифт A", есть кодовая точка U+1D400.
В кодировках Unicode у вас есть кодовые единицы. Это естественная единица кодирования: 8-битная для UTF-8, 16-битная для UTF-16 и так далее. Одна или несколько кодовых единиц кодируют одну кодовую точку.
В UTF-16 две кодовые единицы, которые образуют одну кодовую точку, называются суррогатной парой. Суррогатные пары используются для кодирования любой кодовой точки больше 16 бит, то есть U+10000 и выше.
Это становится немного сложнее в.NET, как.NET Char
представляет собой единицу кода UTF-16 и.NET String
это коллекция блоков кода.
Таким образом, ваша кодовая точка (U+1D400) не может уместиться в 16 бит и нуждается в суррогатной паре, что означает, что ваша строка содержит две кодовые единицы:
var highUnicodeChar = "";
char a = highUnicodeChar[0]; // code unit 0xD835
char b = highUnicodeChar[1]; // code unit 0xDC00
То есть, когда вы индексируете строку таким образом, вы фактически получаете только половину суррогатной пары.
Вы можете использовать IsSurrogatePair для проверки суррогатной пары. Например:
string GetFullCodePointAtIndex(string s, int idx) =>
s.Substring(idx, char.IsSurrogatePair(s, idx) ? 2 : 1);
Важно отметить, что кроличья нора переменной кодировки в Unicode не заканчивается в кодовой точке. Кластер графем - это "видимая вещь", которую большинство людей, когда ее спрашивают, в конечном итоге называют "персонажем". Кластер графемы состоит из одной или нескольких кодовых точек: базовый символ и ноль или более комбинирующих символов. Примером комбинирующего символа является умлаут или различные другие украшения / модификаторы, которые вы можете добавить. Посмотрите этот ответ для ужасающего примера того, что могут сделать комбинирующие персонажи.
Чтобы проверить объединяющий символ, вы можете использовать GetUnicodeCategory, чтобы проверить наличие вмещающей метки, метки без пробелов или меток.
Похоже, что вы хотите извлечь первый "атомарный" символ с точки зрения пользователя (то есть первый кластер графем Unicode) из highUnicodeChar
строка, где "атомарный" символ включает в себя обе половины суррогатной пары.
Ты можешь использовать StringInfo.GetTextElementEnumerator()
чтобы сделать это, сломав string
вниз в атомные куски, а затем первый.
Сначала определите следующий метод расширения:
public static class TextExtensions
{
public static IEnumerable<string> TextElements(this string s)
{
// StringInfo.GetTextElementEnumerator is a .Net 1.1 class that doesn't implement IEnumerable<string>, so convert
if (s == null)
yield break;
var enumerator = StringInfo.GetTextElementEnumerator(s);
while (enumerator.MoveNext())
yield return enumerator.GetTextElement();
}
}
Теперь вы можете сделать:
var result2 = highUnicodeChar.TextElements().FirstOrDefault() ?? "";
Обратите внимание, что StringInfo.GetTextElementEnumerator()
также сгруппирует Unicode, объединяющий символы, так что первый кластер графемы строки Ĥ=T̂+V̂
будет Ĥ
не H
,
Образец скрипки здесь.