2013-05-29 6 views
11

Я использую этот код для генерации U+10FFFCИспользование Юникода символов больше, чем 2 байта с .Net

var s = Encoding.UTF8.GetString(new byte[] {0xF4,0x8F,0xBF,0xBC}); 

Я знаю, что для частного использования и такого, но это отображение одного символа, как я при отображении. Проблемы возникают при манипулировании этим символом юникода.

Если я позже это сделать:

foreach(var ch in s) 
{ 
    Console.WriteLine(ch); 
} 

Вместо этого печать только одного символа, он печатает два символа (то есть строка, по-видимому, состоит из двух символов). Если бы я изменить свой цикл, чтобы добавить эти символы обратно в пустую строку, например, так:

string tmp=""; 
foreach(var ch in s) 
{ 
    Console.WriteLine(ch); 
    tmp += ch; 
} 

В конце этого tmp будет печатать только один символ.

Что именно происходит здесь? Я думал, что char содержит одиночный символ юникода, и мне никогда не приходилось беспокоиться о том, сколько байтов имеет символ, если я не делаю преобразование в байты. Мой реальный прецедент - я должен уметь обнаруживать, когда в строке используются очень большие символы Юникода. В настоящее время у меня есть что-то вроде этого:

foreach(var ch in s) 
{ 
    if(ch>=0x100000 && ch<=0x10FFFF) 
    { 
     Console.WriteLine("special character!"); 
    } 
} 

Однако из-за этого разделения очень больших символов это не работает. Как я могу изменить это, чтобы заставить его работать?

ответ

29

U + 10FFFC - это одна кодовая точка Юникода, но интерфейс string не отображает последовательность кодов Unicode напрямую. Его интерфейс предоставляет последовательность кодовых блоков UTF-16. Это очень низкоуровневое представление текста. Очень печально, что такое низкоуровневое представление текста было перенесено на наиболее очевидный и интуитивно понятный интерфейс ... Я постараюсь не рассказать о том, как мне не нравится этот дизайн, и просто скажу, что не важно как печально, это просто (грустный) факт, с которым вам нужно жить.

Прежде всего, я предлагаю использовать char.ConvertFromUtf32, чтобы получить начальную строку. Намного проще, гораздо более удобным для чтения:

var s = char.ConvertFromUtf32(0x10FFFC); 

Таким образом, эта строка-х Length не 1, потому что, как я уже сказал, дело интерфейса в UTF-16 единиц кода, а не Unicode код точек. U + 10FFFC использует два кодовых блока UTF-16, поэтому s.Length - 2. Все кодовые точки выше U + FFFF требуют для их представления двух кодовых блоков UTF-16.

Следует отметить, что ConvertFromUtf32 не возвращает char: char - это кодовый блок UTF-16, а не кодовая точка Юникода. Чтобы иметь возможность возвращать все кодовые точки Unicode, этот метод не может вернуть один char. Иногда ему нужно вернуть два, и именно поэтому он делает его строкой. Иногда вы найдете некоторые API, относящиеся к int s вместо char, потому что int может использоваться для обработки всех кодовых точек (это то, что ConvertFromUtf32 принимает в качестве аргумента и что дает результат ConvertToUtf32).

string инвентарь IEnumerable<char>, что означает, что при переходе по string вы получаете один кодовый блок UTF-16 на итерацию. Вот почему итерация вашей строки и ее распечатка дает некоторый сломанный вывод с двумя «вещами» в нем. Это два блока кода UTF-16, которые составляют представление U + 10FFFC.Их называют «суррогатами». Первый - суррогат высокого/свинцового, а второй - суррогат с низким/низким уровнем. Когда вы печатаете их отдельно, они не создают значимого вывода, потому что одиночные суррогаты даже не действуют в UTF-16, и они также не считаются символами Unicode.

Когда вы добавляете эти два суррогата в строку в цикле, вы фактически восстанавливаете суррогатную пару и печатаете эту пару позже , так как один получает правильный результат.

И в прощальном фронте обратите внимание, как ничто не жалуется на то, что вы использовали неправильную последовательность UTF-16 в этом цикле. Он создает строку с одиночным суррогатом, и все же все происходит так, как будто ничего не произошло: тип string не является даже типом хорошо сформированным последовательностями кода кода UTF-16, но тип любой UTF-16 последовательность кода.

The char structure предоставляет статические методы для борьбы с суррогатами: IsHighSurrogate, IsLowSurrogate, IsSurrogatePair, ConvertToUtf32 и ConvertFromUtf32. Если вы хотите, вы можете написать итератор, более символов Unicode, а не UTF-16 кодовых блоков:

static IEnumerable<int> AsCodePoints(this string s) 
{ 
    for(int i = 0; i < s.Length; ++i) 
    { 
     yield return char.ConvertToUtf32(s, i); 
     if(char.IsHighSurrogate(s, i)) 
      i++; 
    } 
} 

Тогда можно перебирать, как:

foreach(int codePoint in s.AsCodePoints()) 
{ 
    // do stuff. codePoint will be an int will value 0x10FFFC in your example 
} 

Если вы предпочитаете, чтобы получить каждую точку кода как строка вместо изменить тип возвращаемого на IEnumerable<string> и выход линию:

yield return char.ConvertFromUtf32(char.ConvertToUtf32(s, i)); 

с этой версией, следующие работы как есть:

foreach(string codePoint in s.AsCodePoints()) 
{ 
    Console.WriteLine(codePoint); 
} 
0

Как писал уже на Martinho, гораздо проще создать строку с этой частной элемент кода таким образом:

var s = char.ConvertFromUtf32(0x10FFFC); 

Но петли через два символьных элементов этой строки не имеет смысла:

foreach(var ch in s) 
{ 
    Console.WriteLine(ch); 
} 

Для чего? Вы просто получите высокий и низкий суррогат, который кодирует код. Помните, что char - это 16-разрядный тип, поэтому он может содержать только максимальное значение 0xFFFF. Ваш код не подходит для 16-разрядного типа, и для самого высокого кода вам понадобится 21 бит (0x10FFFF), поэтому следующий более широкий тип будет просто 32-битным. Два элемента char - это не символы, а суррогатная пара. Значение 0x10FFFC кодируется в два суррогата.

0

В то время как @R. Ответ Martinho Fernandes является правильным, его AsCodePoints метод расширения имеет два вопроса:

  1. Это сгенерирует ArgumentException на недействительных кодовых точек (высокий суррогат без низкой суррогата или наоборот).
  2. Вы не можете использовать char статические методы, которые принимают (char) или (string, int) (например, char.IsNumber()), если у вас есть только внутренние коды.

Я разделил код на два метода, один похожий на оригинал, но возвращает Unicode Replacement Character по неверным кодовым точкам.Второй метод возвращает-структуру IEnumerable с более полезными полями:

StringCodePointExtensions.cs

public static class StringCodePointExtensions { 

    const char ReplacementCharacter = '\ufffd'; 

    public static IEnumerable<CodePointIndex> CodePointIndexes(this string s) { 
     for (int i = 0; i < s.Length; i++) { 
      if (char.IsHighSurrogate(s, i)) { 
       if (i + 1 < s.Length && char.IsLowSurrogate(s, i + 1)) { 
        yield return CodePointIndex.Create(i, true, true); 
        i++; 
        continue; 

       } else { 
        // High surrogate without low surrogate 
        yield return CodePointIndex.Create(i, false, false); 
        continue; 
       } 

      } else if (char.IsLowSurrogate(s, i)) { 
       // Low surrogate without high surrogate 
       yield return CodePointIndex.Create(i, false, false); 
       continue; 
      } 

      yield return CodePointIndex.Create(i, true, false); 
     } 
    } 

    public static IEnumerable<int> CodePointInts(this string s) { 
     return s 
      .CodePointIndexes() 
      .Select(
      cpi => { 
       if (cpi.Valid) { 
        return char.ConvertToUtf32(s, cpi.Index); 
       } else { 
        return (int)ReplacementCharacter; 
       } 
      }); 
    } 
} 

CodePointIndex.cs:

public struct CodePointIndex { 
    public int Index; 
    public bool Valid; 
    public bool IsSurrogatePair; 

    public static CodePointIndex Create(int index, bool valid, bool isSurrogatePair) { 
     return new CodePointIndex { 
      Index = index, 
      Valid = valid, 
      IsSurrogatePair = isSurrogatePair, 
     }; 
    } 
} 

CC0

в максимально возможной степени в соответствии с законом, лицо, связанный CC0 с этой работой отказался от всех авторских и смежных или смежных прав t o эта работа.

0

Еще одна альтернатива перечисления символов UTF32 в строке C# заключается в использовании метода System.Globalization.StringInfo.GetTextElementEnumerator, как в приведенном ниже коде.

public static class StringExtensions 
{ 
    public static System.Collections.Generic.IEnumerable<UTF32Char> GetUTF32Chars(this string s) 
    { 
     var tee = System.Globalization.StringInfo.GetTextElementEnumerator(s); 

     while (tee.MoveNext()) 
     { 
      yield return new UTF32Char(s, tee.ElementIndex); 
     } 
    } 
} 

public struct UTF32Char 
{ 
    private string s; 
    private int index; 

    public UTF32Char(string s, int index) 
    { 
     this.s = s; 
     this.index = index; 
    } 

    public override string ToString() 
    { 
     return char.ConvertFromUtf32(this.UTF32Code); 
    } 

    public int UTF32Code { get { return char.ConvertToUtf32(s, index); } } 
    public double NumericValue { get { return char.GetNumericValue(s, index); } } 
    public UnicodeCategory UnicodeCategory { get { return char.GetUnicodeCategory(s, index); } } 
    public bool IsControl { get { return char.IsControl(s, index); } } 
    public bool IsDigit { get { return char.IsDigit(s, index); } } 
    public bool IsLetter { get { return char.IsLetter(s, index); } } 
    public bool IsLetterOrDigit { get { return char.IsLetterOrDigit(s, index); } } 
    public bool IsLower { get { return char.IsLower(s, index); } } 
    public bool IsNumber { get { return char.IsNumber(s, index); } } 
    public bool IsPunctuation { get { return char.IsPunctuation(s, index); } } 
    public bool IsSeparator { get { return char.IsSeparator(s, index); } } 
    public bool IsSurrogatePair { get { return char.IsSurrogatePair(s, index); } } 
    public bool IsSymbol { get { return char.IsSymbol(s, index); } } 
    public bool IsUpper { get { return char.IsUpper(s, index); } } 
    public bool IsWhiteSpace { get { return char.IsWhiteSpace(s, index); } } 
} 
+0

System.Globalization.StringInfo - это путь. Остальная часть кода неверна. Посмотрите: https://msdn.microsoft.com/en-us/library/system.globalization.stringinfo(v=vs.110).aspx – X181

+0

Неясно, что вы имеете в виду. Есть ли проблема с кодом из этого ответа? –

Смежные вопросы