截断字符串时意外拆分 unicode 字符

Joe*_*oel 5 c# string postgresql unicode

我正在将一些来自第三方的字符串保存到我的数据库 (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:"
Run Code Online (Sandbox Code Playgroud)

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

Wik*_*żew 5

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

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

查看C# 演示:

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
Run Code Online (Sandbox Code Playgroud)


Joe*_*oel 1

我最终在这里使用了 xanatos 答案的修改版。不同之处在于,此版本将删除最后一个字素,如果添加它会给出比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();
    }
}
Run Code Online (Sandbox Code Playgroud)