【问题标题】:How would you get an array of Unicode code points from a .NET String?如何从 .NET 字符串中获取 Unicode 代码点数组?
【发布时间】:2010-10-15 19:12:19
【问题描述】:

我有一个需要检查字符串的字符范围限制列表,但 .NET 中的 char 类型是 UTF-16,因此某些字符会变成古怪的(代理)对。因此,当枚举 string 中的所有 char 时,我没有得到 32 位 Unicode 代码点,并且一些高值比较失败。

我对 Unicode 有足够的了解,如有必要,我可以自己解析字节,但我正在寻找 C#/.NET Framework BCL 解决方案。所以...

如何将 string 转换为 32 位 Unicode 代码点的数组 (int[])?

【问题讨论】:

    标签: c# string unicode char astral-plane


    【解决方案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();
    }
    

    注意事项:处理复合字符需要规范化。

    【讨论】:

    • ▼:您的解决方案丢弃了任何修饰符字符,并且您处理的是文本元素而不是代码点。例如,ExtractScalars("El Ni\u006E\u0303o") 转换回字符串的结果将是 "El Nino" 而不是 "El Niño"
    • @Virtlink:很有趣。从文档看来,char.ConvertToUtf32(string, int) 应该会处理它。编辑:该死的文档声称它应该! msdn.microsoft.com/en-us/library/z2ys180b(v=vs.110).aspx
    • @Virtlink:好的,它不处理复合字符,但处理代理对。
    • 是的,我只是在调查。例如,梵文音节“ni”是一个可组合字符\u0928\u093F,标准化后不会变成一个代码点。此外,如果您有一个带有多个修饰符的拉丁字符(例如 ^~),它也不会被规范化为单个代码点。您必须接受您的代码处理文本元素(表示单个字素的代码点组合),并且通过执行ConvertToUtf32(e, 0) 丢弃除第一个之外的所有代码点。无法使用文本元素使您的代码与代码点一起工作。
    • 另一种策略是:var bytes = Encoding.UTF32.GetBytes(s); var ints = new int[bytes.Length / 4]; for (var idx = 0; idx &lt; ints.Length; ++idx) { ints[idx] = BitConverter.ToInt32(bytes, 4 * idx); }。当然,您仍然可以先规范化s。如果你想要奇怪的字节序,你可以使用new UTF32Encoding(...)
    【解决方案2】:

    您在询问代码点。在 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-normalized时,它们被分解成一个符头,组合词干、组合标志和组合重音-断奏,所有代理对:

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

    请注意leppie's solution 不正确。问题是关于代码点,而不是文本元素。文本元素是代码点的组合,它们一起形成一个字素。例如,在上面的示例中,字符串中的ñ 由小写拉丁文n 后跟一个组合波浪号̃◌ 表示。 Leppie 的解决方案会丢弃任何无法归一化为单个代码点的组合字符。

    【讨论】:

    • 我会使用var codePoint = Char.ConvertToUtf32(...); if(codePoint &gt; 0xFFFF) i++; 而不是Char.IsHighSurrogate
    • @CodesInChaos:我相信这将是等效的。当且仅当第一个字符是高代理项时,您才能获得高于 0xFFFF 的代码点,但如果我弄错了,请告诉我。
    • 是等价的。这只是一个风格上的建议。
    • 您可能还想在此处添加您的 梵文音节“ni” 示例,即由两个不合并到单个代码点的代码点组成的单个文本元素在任何规范化形式下。波浪号 n,ñ,可以通过(适当的)规范化变成一个代码点。
    【解决方案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;
      }
    
    }
    

    【讨论】:

    • BitConverter 使用原生字节序,Encoding.UTF32 使用小字节序。所以这将在大端系统上中断。
    • 我只想说,在您提交答案之前 六秒,我发布了相同的解决方案(实际上)作为对 leppie 答案的评论。并提到了字节序问题。
    • @JeppeStigNielsen:很明显,伟大的思想都一样:)
    【解决方案4】:

    我想出了 Nicholas(和 Jeppe)建议的 same approach,只是更短:

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

    【讨论】:

      【解决方案5】:

      此解决方案产生与the solution by Daniel A.A. 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;
      }
      

      【讨论】:

        猜你喜欢
        • 2010-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-02
        • 1970-01-01
        • 2023-03-14
        相关资源
        最近更新 更多