Безопасная подстрока UTF-16 в C#.NET
Я хочу получить подстроку заданной длины, скажем, 150. Однако я хочу убедиться, что не обрезаю строку между символами Юникода.
Например, смотрите следующий код:var str = "Hello
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);