【问题标题】:Accidentally splitting unicode chars when truncating strings截断字符串时意外拆分 unicode 字符
【发布时间】:2017-09-29 08:11:54
【问题描述】:

我正在将一些来自第三方的字符串保存到我的数据库 (postgres) 中。有时这些字符串太长,需要截断以适合我表中的列。

在某些随机情况下,我不小心在有 Unicode 字符的地方截断了字符串,这给了我一个无法保存到数据库中的“损坏”字符串。我收到以下错误:Unable to translate Unicode character \uD83D at index XXX to specified code page

我创建了一个最小的示例来向您展示我的意思。在这里,我有一个包含 Unicode 字符的字符串(“小蓝钻”???? U+1F539)。根据我截断的位置,它是否会给我一个有效的字符串。

var myString = @"This is a string before an emoji:???? This is after the emoji.";

var brokenString = myString.Substring(0, 34);
// Gives: "This is a string before an emoji:☐"

var test3 = myString.Substring(0, 35);
// Gives: "This is a string before an emoji:????"

有没有办法让我在不意外破坏任何 Unicode 字符的情况下截断字符串?

【问题讨论】:

    标签: c# string postgresql unicode


    【解决方案1】:

    一个 Unicode 字符可能用多个chars 表示,这就是您遇到的string.Substring 的问题。

    您可以将string 转换为StringInfo 对象,然后使用SubstringByTextElements() method 根据Unicode 字符数而不是char 数来获取子字符串。

    查看C# demo

    Console.WriteLine("?".Length); // => 2
    Console.WriteLine(new StringInfo("?").LengthInTextElements); // => 1
    
    var myString = @"This is a string before an emoji:?This is after the emoji.";
    var teMyString = new StringInfo(myString);
    Console.WriteLine(teMyString.SubstringByTextElements(0, 33));
    // => "This is a string before an emoji:"
    Console.WriteLine(teMyString.SubstringByTextElements(0, 34));
    // => This is a string before an emoji:?
    Console.WriteLine(teMyString.SubstringByTextElements(0, 35));
    // => This is a string before an emoji:?T
    

    【讨论】:

    • 好的!谢谢你。我实际上发现了这个:stackoverflow.com/a/31936096/492067。这与您的解决方案相比如何?一样吗?
    • @Joel 我研究过accepted answercompared with the current task。该子字符串方法是针对该特定问题量身定制的,请参阅 Xanatos 的解释:因此,初始/最终“拆分”代理对将被删除,初始组合标记将被删除,缺少组合标记的最终字符将被删除。
    • 我正要写同样的:)。我已经接受了答案。
    • 奇怪的是,如果我这样做 var newStr = new StringInfo(text).SubstringByTextElements(0, maxChars); 然后 newStr.Length 不等于 maxChars。我错过了什么?
    • @Toolkit 您正在计算string 的长度,为了获得newStr 中的字符数,您需要再次创建StringInfo 的实例,然后使用@ 987654327@,见this C# demo
    【解决方案2】:

    我最终使用了 xanatos 答案 here 的修改。不同的是这个版本会去掉最后一个字形,如果添加它会得到一个比length更长的字符串。

        public static string UnicodeSafeSubstring(this string str, int startIndex, int length)
        {
            if (str == null)
            {
                throw new ArgumentNullException(nameof(str));
            }
    
            if (startIndex < 0 || startIndex > str.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(startIndex));
            }
    
            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length));
            }
    
            if (startIndex + length > str.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length));
            }
    
            if (length == 0)
            {
                return string.Empty;
            }
    
            var stringBuilder = new StringBuilder(length);
    
            var enumerator = StringInfo.GetTextElementEnumerator(str, startIndex);
    
            while (enumerator.MoveNext())
            {
                var grapheme = enumerator.GetTextElement();
                startIndex += grapheme.Length;
    
                if (startIndex > str.Length)
                {
                    break;
                }
    
                // Skip initial Low Surrogates/Combining Marks
                if (stringBuilder.Length == 0)
                {
                    if (char.IsLowSurrogate(grapheme[0]))
                    {
                        continue;
                    }
    
                    var cat = char.GetUnicodeCategory(grapheme, 0);
    
                    if (cat == UnicodeCategory.NonSpacingMark || cat == UnicodeCategory.SpacingCombiningMark || cat == UnicodeCategory.EnclosingMark)
                    {
                        continue;
                    }
                }
    
                // Do not append the grapheme if the resulting string would be longer than the required length
                if (stringBuilder.Length + grapheme.Length <= length)
                {
                    stringBuilder.Append(grapheme);
                }
    
                if (stringBuilder.Length >= length)
                {
                    break;
                }
            }
    
            return stringBuilder.ToString();
        }
    }
    

    【讨论】:

      【解决方案3】:

      以下是截断 (startIndex = 0) 的示例:

      string truncatedStr = (str.Length > maxLength)
          ? str.Substring(0, maxLength - (char.IsLowSurrogate(str[maxLength]) ? 1 : 0))
          : str;
      

      【讨论】:

        【解决方案4】:

        通过字节数而不是字符串长度更好地截断

           public static string TruncateByBytes(this string text, int maxBytes)
            {
                if (string.IsNullOrEmpty(text) || Encoding.UTF8.GetByteCount(text) <= maxBytes)
                {
                    return text;
                }
                var enumerator = StringInfo.GetTextElementEnumerator(text);
                var newStr = string.Empty;
                do
                {
                    enumerator.MoveNext();
                    if (Encoding.UTF8.GetByteCount(newStr + enumerator.Current) <= maxBytes)
                    {
                        newStr += enumerator.Current;
                    }
                    else
                    {
                        break;
                    }
                } while (true);
                return newStr;
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-04-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-19
          相关资源
          最近更新 更多