Безопасная подстрока UTF-16 в C#.NET


Я хочу получить подстроку заданной длины, скажем, 150. Однако я хочу убедиться, что не обрезаю строку между символами Юникода.

Например, смотрите следующий код:
var str = "Hello
2 8

2 ответа:

Это должно возвращать максимальную подстроку, начинающуюся с индекса startIndex и длиной до length "полных" графем... Таким образом, начальные/конечные "расщепленные" суррогатные пары будут удалены, начальные комбинирующие метки будут удалены, конечные символы, отсутствующие в их комбинирующих метках, будут удалены.

Обратите внимание, что, вероятно, это не то, что вы просили... Вы, кажется, хотите использовать графемы в качестве единицы измерения (или, возможно, вы хотите включить последнюю графему, даже если ее длина будет превышать length параметр)
public static class StringEx
{
    public static string UnicodeSafeSubstring(this string str, int startIndex, int length)
    {
        if (str == null)
        {
            throw new ArgumentNullException("str");
        }

        if (startIndex < 0 || startIndex > str.Length)
        {
            throw new ArgumentOutOfRangeException("startIndex");
        }

        if (length < 0)
        {
            throw new ArgumentOutOfRangeException("length");
        }

        if (startIndex + length > str.Length)
        {
            throw new ArgumentOutOfRangeException("length");
        }

        if (length == 0)
        {
            return string.Empty;
        }

        var sb = new StringBuilder(length);

        int end = startIndex + length;

        var enumerator = StringInfo.GetTextElementEnumerator(str, startIndex);

        while (enumerator.MoveNext())
        {
            string grapheme = enumerator.GetTextElement();
            startIndex += grapheme.Length;

            if (startIndex > length)
            {
                break;
            }

            // Skip initial Low Surrogates/Combining Marks
            if (sb.Length == 0)
            {
                if (char.IsLowSurrogate(grapheme[0]))
                {
                    continue;
                }

                UnicodeCategory cat = char.GetUnicodeCategory(grapheme, 0);

                if (cat == UnicodeCategory.NonSpacingMark || cat == UnicodeCategory.SpacingCombiningMark || cat == UnicodeCategory.EnclosingMark)
                {
                    continue;
                }
            }

            sb.Append(grapheme);

            if (startIndex == length)
            {
                break;
            }
        }

        return sb.ToString();
    }
}

Вариант, который будет просто включать "лишние" символы в конце подстроки, если это необходимо, чтобы сделать целую графему:

public static class StringEx
{
    public static string UnicodeSafeSubstring(this string str, int startIndex, int length)
    {
        if (str == null)
        {
            throw new ArgumentNullException("str");
        }

        if (startIndex < 0 || startIndex > str.Length)
        {
            throw new ArgumentOutOfRangeException("startIndex");
        }

        if (length < 0)
        {
            throw new ArgumentOutOfRangeException("length");
        }

        if (startIndex + length > str.Length)
        {
            throw new ArgumentOutOfRangeException("length");
        }

        if (length == 0)
        {
            return string.Empty;
        }

        var sb = new StringBuilder(length);

        int end = startIndex + length;

        var enumerator = StringInfo.GetTextElementEnumerator(str, startIndex);

        while (enumerator.MoveNext())
        {
            if (startIndex >= length)
            {
                break;
            }

            string grapheme = enumerator.GetTextElement();
            startIndex += grapheme.Length;

            // Skip initial Low Surrogates/Combining Marks
            if (sb.Length == 0)
            {
                if (char.IsLowSurrogate(grapheme[0]))
                {
                    continue;
                }

                UnicodeCategory cat = char.GetUnicodeCategory(grapheme, 0);

                if (cat == UnicodeCategory.NonSpacingMark || cat == UnicodeCategory.SpacingCombiningMark || cat == UnicodeCategory.EnclosingMark)
                {
                    continue;
                }
            }

            sb.Append(grapheme);
        }

        return sb.ToString();
    }
}

Это вернет то, что вы просили "Hello world!".UnicodeSafeSubstring(0, 6) == "Hello".

Похоже, что вы хотите разделить строку награфемы , то есть на отдельные отображаемые символы.

В таком случае у вас есть удобный метод: StringInfo.SubstringByTextElements:

var str = "Hello world!";
var substr = new StringInfo(str).SubstringByTextElements(0, 6);