diff --git a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/Program.cs b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/Program.cs index 77cb8ee750..db218dbec4 100644 --- a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/Program.cs +++ b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/Program.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; using StringTruncationInCSharp; -BenchmarkRunner.Run(); \ No newline at end of file +BenchmarkRunner.Run(args: args); \ No newline at end of file diff --git a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringHelper.cs b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringHelper.cs index 522bb49348..fc21440fa3 100644 --- a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringHelper.cs +++ b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringHelper.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Globalization; +using System.Text; using System.Text.RegularExpressions; namespace StringTruncationInCSharp; @@ -48,7 +49,7 @@ public static string TruncateWithRegularExpressions(string originalString, int m return TruncateString(originalString, maxLength, UsingRegularExpressions); static string UsingRegularExpressions(string str, int length) => - Regex.Replace(str, $"^(.{{0,{length}}}).*$", "$1"); + Regex.Replace(str, $"^(.{{0,{length}}}).*$", "$1", RegexOptions.Singleline); } public static string TruncateWithSpan(string originalString, int maxLength) @@ -58,6 +59,38 @@ public static string TruncateWithSpan(string originalString, int maxLength) static string UsingSpan(string str, int length) => str.AsSpan()[..length].ToString(); } + public static string TruncateWithSurrogateCheck(string originalString, int maxLength) + { + return TruncateString(originalString, maxLength, UsingSurrogateCheck); + + static string UsingSurrogateCheck(string str, int length) => + char.IsHighSurrogate(str[length - 1]) ? str[..(length - 1)] : str[..length]; + } + + public static string TruncateWithTextElements(string originalString, int maxLength) + { + return TruncateString(originalString, maxLength, UsingTextElements); + + static string UsingTextElements(string str, int length) + { + var end = 0; + + while (end < length) + { + var next = StringInfo.GetNextTextElementLength(str, end); + + if (end + next > length) + { + break; + } + + end += next; + } + + return str[..end]; + } + } + private static string TruncateString(string originalString, int maxLength, Func specificTruncateMethod) { diff --git a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringHelperBenchmark.cs b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringHelperBenchmark.cs index e8e8c7b9b2..982b24f49c 100644 --- a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringHelperBenchmark.cs +++ b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringHelperBenchmark.cs @@ -14,38 +14,44 @@ public class StringHelperBenchmark public int MaxLength; [Benchmark] - public void TruncateWithSubstring() + public string TruncateWithSubstring() { - StringHelper.TruncateWithSubstring(_originalString, MaxLength); + return StringHelper.TruncateWithSubstring(_originalString, MaxLength); } [Benchmark] - public void TruncateWithForLoopStringBuilder() + public string TruncateWithForLoopStringBuilder() { - StringHelper.TruncateWithForLoopStringBuilder(_originalString, MaxLength); + return StringHelper.TruncateWithForLoopStringBuilder(_originalString, MaxLength); } [Benchmark] - public void TruncateWithRegularExpressions() + public string TruncateWithRegularExpressions() { - StringHelper.TruncateWithRegularExpressions(_originalString, MaxLength); + return StringHelper.TruncateWithRegularExpressions(_originalString, MaxLength); } [Benchmark] - public void TruncateWithRemove() + public string TruncateWithRemove() { - StringHelper.TruncateWithRemove(_originalString, MaxLength); + return StringHelper.TruncateWithRemove(_originalString, MaxLength); } [Benchmark] - public void TruncateWithLINQ() + public string TruncateWithLINQ() { - StringHelper.TruncateWithLINQ(_originalString, MaxLength); + return StringHelper.TruncateWithLINQ(_originalString, MaxLength); } [Benchmark] - public void TruncateWithSpan() + public string TruncateWithSpan() { - StringHelper.TruncateWithSpan(_originalString, MaxLength); + return StringHelper.TruncateWithSpan(_originalString, MaxLength); + } + + [Benchmark] + public string TruncateWithTextElements() + { + return StringHelper.TruncateWithTextElements(_originalString, MaxLength); } } \ No newline at end of file diff --git a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringTruncationInCSharp.csproj b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringTruncationInCSharp.csproj index a0c610ccf1..62be4f867f 100644 --- a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringTruncationInCSharp.csproj +++ b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharp/StringTruncationInCSharp.csproj @@ -2,13 +2,13 @@ Exe - net8.0 + net10.0 enable enable - + diff --git a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharpTests/StringTruncationInCSharpTests.csproj b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharpTests/StringTruncationInCSharpTests.csproj index ea72a381cf..d31ba488a5 100644 --- a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharpTests/StringTruncationInCSharpTests.csproj +++ b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharpTests/StringTruncationInCSharpTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -10,13 +10,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharpTests/StringTruncationUnitTest.cs b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharpTests/StringTruncationUnitTest.cs index 0b31a769fe..8f43769fe3 100644 --- a/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharpTests/StringTruncationUnitTest.cs +++ b/strings-csharp/StringTruncationInCSharp/StringTruncationInCSharpTests/StringTruncationUnitTest.cs @@ -261,4 +261,85 @@ public void GivenANonPositiveLength_WhenTruncatingAString_ThenUseSpanMethodAndRe Assert.Equal(string.Empty, truncatedString); } + + [Fact] + public void GivenAMultiLineString_WhenTruncatingAString_ThenUseRegularExpressionsMethodToTruncateTheString() + { + var truncatedString = StringHelper.TruncateWithRegularExpressions("First line\nSecond line", 5); + + Assert.Equal("First", truncatedString); + } + + [Fact] + public void GivenATrailingNewLine_WhenTruncatingAString_ThenUseRegularExpressionsMethodToTruncateTheString() + { + var truncatedString = StringHelper.TruncateWithRegularExpressions("Hello world\n", 5); + + Assert.Equal("Hello", truncatedString); + } + + + [Fact] + public void WhenTruncatingAString_ThenUseTextElementsMethodToTruncateTheString() + { + var truncatedString = StringHelper.TruncateWithTextElements(OriginalString, MaxLength); + + Assert.Equal(ExpectedTruncatedString, truncatedString); + } + + [Fact] + public void GivenAZeroLength_WhenTruncatingAString_ThenUseTextElementsMethodAndReturnAnEmptyString() + { + var truncatedString = StringHelper.TruncateWithTextElements(OriginalString, 0); + + Assert.Equal(string.Empty, truncatedString); + } + + [Fact] + public void GivenALengthGreaterThanTheStringLength_WhenTruncatingAString_ThenUseTextElementsMethodWithoutTruncatingTheString() + { + var truncatedString = StringHelper.TruncateWithTextElements(OriginalString, OriginalString.Length + 10); + + Assert.Equal(OriginalString, truncatedString); + } + + [Fact] + public void GivenAnEmojiAtTheCut_WhenTruncatingWithSubstring_ThenTheResultEndsWithAHalfSurrogatePair() + { + var truncatedString = StringHelper.TruncateWithSubstring("Thumbs up \U0001F44D", 11); + + Assert.True(char.IsHighSurrogate(truncatedString[^1])); + } + + [Fact] + public void GivenAnEmojiAtTheCut_WhenTruncatingWithTextElements_ThenTheWholeEmojiIsDropped() + { + var truncatedString = StringHelper.TruncateWithTextElements("Thumbs up \U0001F44D", 11); + + Assert.Equal("Thumbs up ", truncatedString); + } + + [Fact] + public void GivenAJoinedEmojiAtTheCut_WhenTruncatingWithTextElements_ThenTheWholeEmojiIsDropped() + { + var truncatedString = StringHelper.TruncateWithTextElements("Family \U0001F468\u200D\U0001F469\u200D\U0001F467", 12); + + Assert.Equal("Family ", truncatedString); + } + + [Fact] + public void GivenAnEmojiAtTheCut_WhenTruncatingWithSurrogateCheck_ThenTheWholeEmojiIsDropped() + { + var truncatedString = StringHelper.TruncateWithSurrogateCheck("Thumbs up \U0001F44D", 11); + + Assert.Equal("Thumbs up ", truncatedString); + } + + [Fact] + public void WhenTruncatingAString_ThenUseSurrogateCheckMethodToTruncateTheString() + { + var truncatedString = StringHelper.TruncateWithSurrogateCheck(OriginalString, MaxLength); + + Assert.Equal(ExpectedTruncatedString, truncatedString); + } } \ No newline at end of file