From d2853490e0e1ffda0fc9c080e503a035430fac4d Mon Sep 17 00:00:00 2001 From: Marlon Date: Thu, 12 Nov 2026 10:08:52 +0100 Subject: [PATCH] fix: honor StringComparison in Cut* string helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every CutAfter, CutAfterLast, CutBefore and CutBeforeLast overload that takes a StringComparison and returns a string or writes into a destination compared ordinally. Only the CutSpan*/TryCutSpan* overloads honored the comparison. For example, "Hello World".CutAfter("WORLD", StringComparison.OrdinalIgnoreCase) returned "Hello World" instead of "". CutAfter and CutAfterLast also cut at index + needle.Length. With a culture-sensitive comparison, the matched text can be longer or shorter than the needle ("Å" matches "Å", a soft hyphen is ignored), so they returned the wrong text or threw ArgumentOutOfRangeException. They now cut after the actual match. Co-Authored-By: Claude Opus 5.5 (xhigh) --- src/OffiUtils.Tests/StringUtils.ExtTests.cs | 609 +++++++++++++++++- .../StringUtils.Ext.CutAfter.String.cs | 44 +- .../StringUtils.Ext.CutAfterLast.String.cs | 8 +- .../StringUtils.Ext.CutBefore.String.cs | 4 +- .../StringUtils.Ext.CutBeforeLast.String.cs | 4 +- 5 files changed, 648 insertions(+), 21 deletions(-) diff --git a/src/OffiUtils.Tests/StringUtils.ExtTests.cs b/src/OffiUtils.Tests/StringUtils.ExtTests.cs index 3893328..b3a9831 100644 --- a/src/OffiUtils.Tests/StringUtils.ExtTests.cs +++ b/src/OffiUtils.Tests/StringUtils.ExtTests.cs @@ -1,4 +1,6 @@ -using CommunityToolkit.HighPerformance.Buffers; +using System.Globalization; + +using CommunityToolkit.HighPerformance.Buffers; #pragma warning disable IDE0018 @@ -107,7 +109,7 @@ public void CutAfter_Char() Assert.Equal(result, resultSpan); Assert.Equal(resultSpan.Length, tryWritten); - Assert.Equal(badValue, badValue.CutSpanAfter(needle)); + Assert.Equal(badValue, badValue.AsSpan().CutSpanAfter(needle)); Assert.Equal(result, value.AsSpan().CutSpanAfter(needle)); Assert.False(badValue.AsSpan().TryCutSpanAfter(needle, out trySpanResult)); @@ -197,7 +199,7 @@ public void CutAfterLast_Char() Assert.Equal(result, resultSpan); Assert.Equal(resultSpan.Length, tryWritten); - Assert.Equal(badValue, badValue.CutSpanAfterLast(needle)); + Assert.Equal(badValue, badValue.AsSpan().CutSpanAfterLast(needle)); Assert.Equal(result, value.AsSpan().CutSpanAfterLast(needle)); Assert.False(badValue.AsSpan().TryCutSpanAfterLast(needle, out trySpanResult)); @@ -287,7 +289,7 @@ public void CutBefore_Char() Assert.Equal(result, resultSpan); Assert.Equal(resultSpan.Length, tryWritten); - Assert.Equal(badValue, badValue.CutSpanBefore(needle)); + Assert.Equal(badValue, badValue.AsSpan().CutSpanBefore(needle)); Assert.Equal(result, value.AsSpan().CutSpanBefore(needle)); Assert.False(badValue.AsSpan().TryCutSpanBefore(needle, out trySpanResult)); @@ -377,7 +379,7 @@ public void CutBeforeLast_Char() Assert.Equal(result, resultSpan); Assert.Equal(resultSpan.Length, tryWritten); - Assert.Equal(badValue, badValue.CutSpanBeforeLast(needle)); + Assert.Equal(badValue, badValue.AsSpan().CutSpanBeforeLast(needle)); Assert.Equal(result, value.AsSpan().CutSpanBeforeLast(needle)); Assert.False(badValue.AsSpan().TryCutSpanBeforeLast(needle, out trySpanResult)); @@ -467,7 +469,7 @@ public void CutAfter_String() Assert.Equal(result, resultSpan); Assert.Equal(resultSpan.Length, tryWritten); - Assert.Equal(badValue, badValue.CutSpanAfter(needle)); + Assert.Equal(badValue, badValue.AsSpan().CutSpanAfter(needle)); Assert.Equal(result, value.AsSpan().CutSpanAfter(needle)); Assert.False(badValue.AsSpan().TryCutSpanAfter(needle, out trySpanResult)); @@ -557,7 +559,7 @@ public void CutAfterLast_String() Assert.Equal(result, resultSpan); Assert.Equal(resultSpan.Length, tryWritten); - Assert.Equal(badValue, badValue.CutSpanAfterLast(needle)); + Assert.Equal(badValue, badValue.AsSpan().CutSpanAfterLast(needle)); Assert.Equal(result, value.AsSpan().CutSpanAfterLast(needle)); Assert.False(badValue.AsSpan().TryCutSpanAfterLast(needle, out trySpanResult)); @@ -647,7 +649,7 @@ public void CutBefore_String() Assert.Equal(result, resultSpan); Assert.Equal(resultSpan.Length, tryWritten); - Assert.Equal(badValue, badValue.CutSpanBefore(needle)); + Assert.Equal(badValue, badValue.AsSpan().CutSpanBefore(needle)); Assert.Equal(result, value.AsSpan().CutSpanBefore(needle)); Assert.False(badValue.AsSpan().TryCutSpanBefore(needle, out trySpanResult)); @@ -737,7 +739,7 @@ public void CutBeforeLast_String() Assert.Equal(result, resultSpan); Assert.Equal(resultSpan.Length, tryWritten); - Assert.Equal(badValue, badValue.CutSpanBeforeLast(needle)); + Assert.Equal(badValue, badValue.AsSpan().CutSpanBeforeLast(needle)); Assert.Equal(result, value.AsSpan().CutSpanBeforeLast(needle)); Assert.False(badValue.AsSpan().TryCutSpanBeforeLast(needle, out trySpanResult)); @@ -746,4 +748,593 @@ public void CutBeforeLast_String() Assert.True(value.AsSpan().TryCutSpanBeforeLast(needle, out trySpanResult)); Assert.Equal(result, trySpanResult); } + + [Fact] + public void CutAfter_String_Comparison() + { + const string needle = "AB"; + const string value = "123_ab_456_Ab_789"; + const string result = "_456_Ab_789"; + const char resultSpanInitChar = '#'; + + Span resultSpanInit = stackalloc char[result.Length]; + resultSpanInit.Fill(resultSpanInitChar); + + Span resultSpan = stackalloc char[result.Length]; + Span smallResultSpan = stackalloc char[result.Length - 1]; + StringPool pool = new(); + string? tryResult; + ReadOnlySpan trySpanResult; + int tryWritten; + + // string for value + // the needle only matches when ignoring case, so overloads without a comparison must not match + Assert.Equal(value, value.CutAfter(needle)); + Assert.Equal(value, value.CutAfter(needle, pool)); + Assert.False(value.TryCutAfter(needle, out tryResult)); + Assert.False(value.TryCutAfter(needle, pool, out tryResult)); + Assert.False(value.TryCutAfter(needle, resultSpan, out tryWritten)); + Assert.Equal(value, value.CutSpanAfter(needle)); + Assert.False(value.TryCutSpanAfter(needle, out trySpanResult)); + + Assert.Equal(value, value.CutAfter(needle, StringComparison.Ordinal)); + Assert.Equal(value, value.CutAfter(needle, StringComparison.Ordinal, pool)); + + Assert.Equal(result, value.CutAfter(needle, StringComparison.OrdinalIgnoreCase)); + Assert.Same(pool.GetOrAdd(result), value.CutAfter(needle, StringComparison.OrdinalIgnoreCase, pool)); + + Assert.False(value.TryCutAfter(needle, StringComparison.Ordinal, out tryResult)); + Assert.Null(tryResult); + Assert.False(value.TryCutAfter(needle, StringComparison.Ordinal, pool, out tryResult)); + Assert.Null(tryResult); + + Assert.True(value.TryCutAfter(needle, StringComparison.OrdinalIgnoreCase, out tryResult)); + Assert.Equal(result, tryResult); + Assert.True(value.TryCutAfter(needle, StringComparison.OrdinalIgnoreCase, pool, out tryResult)); + Assert.Same(pool.GetOrAdd(result), tryResult); + + resultSpan.Fill(resultSpanInitChar); + Assert.False(value.TryCutAfter(needle, StringComparison.Ordinal, resultSpan, out tryWritten)); + Assert.Equal(resultSpanInit, resultSpan); + Assert.Equal(0, tryWritten); + + resultSpan.Fill(resultSpanInitChar); + Assert.True(value.TryCutAfter(needle, StringComparison.OrdinalIgnoreCase, resultSpan, out tryWritten)); + Assert.Equal(result, resultSpan); + Assert.Equal(resultSpan.Length, tryWritten); + + smallResultSpan.Fill(resultSpanInitChar); + Assert.False(value.TryCutAfter(needle, StringComparison.OrdinalIgnoreCase, smallResultSpan, out tryWritten)); + Assert.Equal(resultSpanInit[..smallResultSpan.Length], smallResultSpan); + Assert.Equal(0, tryWritten); + + Assert.Equal(value, value.CutSpanAfter(needle, StringComparison.Ordinal)); + Assert.Equal(result, value.CutSpanAfter(needle, StringComparison.OrdinalIgnoreCase)); + + Assert.False(value.TryCutSpanAfter(needle, StringComparison.Ordinal, out trySpanResult)); + Assert.Equal(ReadOnlySpan.Empty, trySpanResult); + + Assert.True(value.TryCutSpanAfter(needle, StringComparison.OrdinalIgnoreCase, out trySpanResult)); + Assert.Equal(result, trySpanResult); + + // span for value + // the needle only matches when ignoring case, so overloads without a comparison must not match + Assert.Equal(value, value.AsSpan().CutAfter(needle)); + Assert.Equal(value, value.AsSpan().CutAfter(needle, pool)); + Assert.False(value.AsSpan().TryCutAfter(needle, out tryResult)); + Assert.False(value.AsSpan().TryCutAfter(needle, pool, out tryResult)); + Assert.False(value.AsSpan().TryCutAfter(needle, resultSpan, out tryWritten)); + Assert.Equal(value, value.AsSpan().CutSpanAfter(needle)); + Assert.False(value.AsSpan().TryCutSpanAfter(needle, out trySpanResult)); + + Assert.Equal(value, value.AsSpan().CutAfter(needle, StringComparison.Ordinal)); + Assert.Equal(value, value.AsSpan().CutAfter(needle, StringComparison.Ordinal, pool)); + + Assert.Equal(result, value.AsSpan().CutAfter(needle, StringComparison.OrdinalIgnoreCase)); + Assert.Same(pool.GetOrAdd(result), value.AsSpan().CutAfter(needle, StringComparison.OrdinalIgnoreCase, pool)); + + Assert.False(value.AsSpan().TryCutAfter(needle, StringComparison.Ordinal, out tryResult)); + Assert.Null(tryResult); + Assert.False(value.AsSpan().TryCutAfter(needle, StringComparison.Ordinal, pool, out tryResult)); + Assert.Null(tryResult); + + Assert.True(value.AsSpan().TryCutAfter(needle, StringComparison.OrdinalIgnoreCase, out tryResult)); + Assert.Equal(result, tryResult); + Assert.True(value.AsSpan().TryCutAfter(needle, StringComparison.OrdinalIgnoreCase, pool, out tryResult)); + Assert.Same(pool.GetOrAdd(result), tryResult); + + resultSpan.Fill(resultSpanInitChar); + Assert.False(value.AsSpan().TryCutAfter(needle, StringComparison.Ordinal, resultSpan, out tryWritten)); + Assert.Equal(resultSpanInit, resultSpan); + Assert.Equal(0, tryWritten); + + resultSpan.Fill(resultSpanInitChar); + Assert.True(value.AsSpan().TryCutAfter(needle, StringComparison.OrdinalIgnoreCase, resultSpan, out tryWritten)); + Assert.Equal(result, resultSpan); + Assert.Equal(resultSpan.Length, tryWritten); + + smallResultSpan.Fill(resultSpanInitChar); + Assert.False(value.AsSpan().TryCutAfter(needle, StringComparison.OrdinalIgnoreCase, smallResultSpan, out tryWritten)); + Assert.Equal(resultSpanInit[..smallResultSpan.Length], smallResultSpan); + Assert.Equal(0, tryWritten); + + Assert.Equal(value, value.AsSpan().CutSpanAfter(needle, StringComparison.Ordinal)); + Assert.Equal(result, value.AsSpan().CutSpanAfter(needle, StringComparison.OrdinalIgnoreCase)); + + Assert.False(value.AsSpan().TryCutSpanAfter(needle, StringComparison.Ordinal, out trySpanResult)); + Assert.Equal(ReadOnlySpan.Empty, trySpanResult); + + Assert.True(value.AsSpan().TryCutSpanAfter(needle, StringComparison.OrdinalIgnoreCase, out trySpanResult)); + Assert.Equal(result, trySpanResult); + } + + [Fact] + public void CutAfterLast_String_Comparison() + { + const string needle = "AB"; + const string value = "123_ab_456_Ab_789"; + const string result = "_789"; + const char resultSpanInitChar = '#'; + + Span resultSpanInit = stackalloc char[result.Length]; + resultSpanInit.Fill(resultSpanInitChar); + + Span resultSpan = stackalloc char[result.Length]; + Span smallResultSpan = stackalloc char[result.Length - 1]; + StringPool pool = new(); + string? tryResult; + ReadOnlySpan trySpanResult; + int tryWritten; + + // string for value + // the needle only matches when ignoring case, so overloads without a comparison must not match + Assert.Equal(value, value.CutAfterLast(needle)); + Assert.Equal(value, value.CutAfterLast(needle, pool)); + Assert.False(value.TryCutAfterLast(needle, out tryResult)); + Assert.False(value.TryCutAfterLast(needle, pool, out tryResult)); + Assert.False(value.TryCutAfterLast(needle, resultSpan, out tryWritten)); + Assert.Equal(value, value.CutSpanAfterLast(needle)); + Assert.False(value.TryCutSpanAfterLast(needle, out trySpanResult)); + + Assert.Equal(value, value.CutAfterLast(needle, StringComparison.Ordinal)); + Assert.Equal(value, value.CutAfterLast(needle, StringComparison.Ordinal, pool)); + + Assert.Equal(result, value.CutAfterLast(needle, StringComparison.OrdinalIgnoreCase)); + Assert.Same(pool.GetOrAdd(result), value.CutAfterLast(needle, StringComparison.OrdinalIgnoreCase, pool)); + + Assert.False(value.TryCutAfterLast(needle, StringComparison.Ordinal, out tryResult)); + Assert.Null(tryResult); + Assert.False(value.TryCutAfterLast(needle, StringComparison.Ordinal, pool, out tryResult)); + Assert.Null(tryResult); + + Assert.True(value.TryCutAfterLast(needle, StringComparison.OrdinalIgnoreCase, out tryResult)); + Assert.Equal(result, tryResult); + Assert.True(value.TryCutAfterLast(needle, StringComparison.OrdinalIgnoreCase, pool, out tryResult)); + Assert.Same(pool.GetOrAdd(result), tryResult); + + resultSpan.Fill(resultSpanInitChar); + Assert.False(value.TryCutAfterLast(needle, StringComparison.Ordinal, resultSpan, out tryWritten)); + Assert.Equal(resultSpanInit, resultSpan); + Assert.Equal(0, tryWritten); + + resultSpan.Fill(resultSpanInitChar); + Assert.True(value.TryCutAfterLast(needle, StringComparison.OrdinalIgnoreCase, resultSpan, out tryWritten)); + Assert.Equal(result, resultSpan); + Assert.Equal(resultSpan.Length, tryWritten); + + smallResultSpan.Fill(resultSpanInitChar); + Assert.False(value.TryCutAfterLast(needle, StringComparison.OrdinalIgnoreCase, smallResultSpan, out tryWritten)); + Assert.Equal(resultSpanInit[..smallResultSpan.Length], smallResultSpan); + Assert.Equal(0, tryWritten); + + Assert.Equal(value, value.CutSpanAfterLast(needle, StringComparison.Ordinal)); + Assert.Equal(result, value.CutSpanAfterLast(needle, StringComparison.OrdinalIgnoreCase)); + + Assert.False(value.TryCutSpanAfterLast(needle, StringComparison.Ordinal, out trySpanResult)); + Assert.Equal(ReadOnlySpan.Empty, trySpanResult); + + Assert.True(value.TryCutSpanAfterLast(needle, StringComparison.OrdinalIgnoreCase, out trySpanResult)); + Assert.Equal(result, trySpanResult); + + // span for value + // the needle only matches when ignoring case, so overloads without a comparison must not match + Assert.Equal(value, value.AsSpan().CutAfterLast(needle)); + Assert.Equal(value, value.AsSpan().CutAfterLast(needle, pool)); + Assert.False(value.AsSpan().TryCutAfterLast(needle, out tryResult)); + Assert.False(value.AsSpan().TryCutAfterLast(needle, pool, out tryResult)); + Assert.False(value.AsSpan().TryCutAfterLast(needle, resultSpan, out tryWritten)); + Assert.Equal(value, value.AsSpan().CutSpanAfterLast(needle)); + Assert.False(value.AsSpan().TryCutSpanAfterLast(needle, out trySpanResult)); + + Assert.Equal(value, value.AsSpan().CutAfterLast(needle, StringComparison.Ordinal)); + Assert.Equal(value, value.AsSpan().CutAfterLast(needle, StringComparison.Ordinal, pool)); + + Assert.Equal(result, value.AsSpan().CutAfterLast(needle, StringComparison.OrdinalIgnoreCase)); + Assert.Same(pool.GetOrAdd(result), value.AsSpan().CutAfterLast(needle, StringComparison.OrdinalIgnoreCase, pool)); + + Assert.False(value.AsSpan().TryCutAfterLast(needle, StringComparison.Ordinal, out tryResult)); + Assert.Null(tryResult); + Assert.False(value.AsSpan().TryCutAfterLast(needle, StringComparison.Ordinal, pool, out tryResult)); + Assert.Null(tryResult); + + Assert.True(value.AsSpan().TryCutAfterLast(needle, StringComparison.OrdinalIgnoreCase, out tryResult)); + Assert.Equal(result, tryResult); + Assert.True(value.AsSpan().TryCutAfterLast(needle, StringComparison.OrdinalIgnoreCase, pool, out tryResult)); + Assert.Same(pool.GetOrAdd(result), tryResult); + + resultSpan.Fill(resultSpanInitChar); + Assert.False(value.AsSpan().TryCutAfterLast(needle, StringComparison.Ordinal, resultSpan, out tryWritten)); + Assert.Equal(resultSpanInit, resultSpan); + Assert.Equal(0, tryWritten); + + resultSpan.Fill(resultSpanInitChar); + Assert.True(value.AsSpan().TryCutAfterLast(needle, StringComparison.OrdinalIgnoreCase, resultSpan, out tryWritten)); + Assert.Equal(result, resultSpan); + Assert.Equal(resultSpan.Length, tryWritten); + + smallResultSpan.Fill(resultSpanInitChar); + Assert.False(value.AsSpan().TryCutAfterLast(needle, StringComparison.OrdinalIgnoreCase, smallResultSpan, out tryWritten)); + Assert.Equal(resultSpanInit[..smallResultSpan.Length], smallResultSpan); + Assert.Equal(0, tryWritten); + + Assert.Equal(value, value.AsSpan().CutSpanAfterLast(needle, StringComparison.Ordinal)); + Assert.Equal(result, value.AsSpan().CutSpanAfterLast(needle, StringComparison.OrdinalIgnoreCase)); + + Assert.False(value.AsSpan().TryCutSpanAfterLast(needle, StringComparison.Ordinal, out trySpanResult)); + Assert.Equal(ReadOnlySpan.Empty, trySpanResult); + + Assert.True(value.AsSpan().TryCutSpanAfterLast(needle, StringComparison.OrdinalIgnoreCase, out trySpanResult)); + Assert.Equal(result, trySpanResult); + } + + [Fact] + public void CutBefore_String_Comparison() + { + const string needle = "AB"; + const string value = "123_ab_456_Ab_789"; + const string result = "123_"; + const char resultSpanInitChar = '#'; + + Span resultSpanInit = stackalloc char[result.Length]; + resultSpanInit.Fill(resultSpanInitChar); + + Span resultSpan = stackalloc char[result.Length]; + Span smallResultSpan = stackalloc char[result.Length - 1]; + StringPool pool = new(); + string? tryResult; + ReadOnlySpan trySpanResult; + int tryWritten; + + // string for value + // the needle only matches when ignoring case, so overloads without a comparison must not match + Assert.Equal(value, value.CutBefore(needle)); + Assert.Equal(value, value.CutBefore(needle, pool)); + Assert.False(value.TryCutBefore(needle, out tryResult)); + Assert.False(value.TryCutBefore(needle, pool, out tryResult)); + Assert.False(value.TryCutBefore(needle, resultSpan, out tryWritten)); + Assert.Equal(value, value.CutSpanBefore(needle)); + Assert.False(value.TryCutSpanBefore(needle, out trySpanResult)); + + Assert.Equal(value, value.CutBefore(needle, StringComparison.Ordinal)); + Assert.Equal(value, value.CutBefore(needle, StringComparison.Ordinal, pool)); + + Assert.Equal(result, value.CutBefore(needle, StringComparison.OrdinalIgnoreCase)); + Assert.Same(pool.GetOrAdd(result), value.CutBefore(needle, StringComparison.OrdinalIgnoreCase, pool)); + + Assert.False(value.TryCutBefore(needle, StringComparison.Ordinal, out tryResult)); + Assert.Null(tryResult); + Assert.False(value.TryCutBefore(needle, StringComparison.Ordinal, pool, out tryResult)); + Assert.Null(tryResult); + + Assert.True(value.TryCutBefore(needle, StringComparison.OrdinalIgnoreCase, out tryResult)); + Assert.Equal(result, tryResult); + Assert.True(value.TryCutBefore(needle, StringComparison.OrdinalIgnoreCase, pool, out tryResult)); + Assert.Same(pool.GetOrAdd(result), tryResult); + + resultSpan.Fill(resultSpanInitChar); + Assert.False(value.TryCutBefore(needle, StringComparison.Ordinal, resultSpan, out tryWritten)); + Assert.Equal(resultSpanInit, resultSpan); + Assert.Equal(0, tryWritten); + + resultSpan.Fill(resultSpanInitChar); + Assert.True(value.TryCutBefore(needle, StringComparison.OrdinalIgnoreCase, resultSpan, out tryWritten)); + Assert.Equal(result, resultSpan); + Assert.Equal(resultSpan.Length, tryWritten); + + smallResultSpan.Fill(resultSpanInitChar); + Assert.False(value.TryCutBefore(needle, StringComparison.OrdinalIgnoreCase, smallResultSpan, out tryWritten)); + Assert.Equal(resultSpanInit[..smallResultSpan.Length], smallResultSpan); + Assert.Equal(0, tryWritten); + + Assert.Equal(value, value.CutSpanBefore(needle, StringComparison.Ordinal)); + Assert.Equal(result, value.CutSpanBefore(needle, StringComparison.OrdinalIgnoreCase)); + + Assert.False(value.TryCutSpanBefore(needle, StringComparison.Ordinal, out trySpanResult)); + Assert.Equal(ReadOnlySpan.Empty, trySpanResult); + + Assert.True(value.TryCutSpanBefore(needle, StringComparison.OrdinalIgnoreCase, out trySpanResult)); + Assert.Equal(result, trySpanResult); + + // span for value + // the needle only matches when ignoring case, so overloads without a comparison must not match + Assert.Equal(value, value.AsSpan().CutBefore(needle)); + Assert.Equal(value, value.AsSpan().CutBefore(needle, pool)); + Assert.False(value.AsSpan().TryCutBefore(needle, out tryResult)); + Assert.False(value.AsSpan().TryCutBefore(needle, pool, out tryResult)); + Assert.False(value.AsSpan().TryCutBefore(needle, resultSpan, out tryWritten)); + Assert.Equal(value, value.AsSpan().CutSpanBefore(needle)); + Assert.False(value.AsSpan().TryCutSpanBefore(needle, out trySpanResult)); + + Assert.Equal(value, value.AsSpan().CutBefore(needle, StringComparison.Ordinal)); + Assert.Equal(value, value.AsSpan().CutBefore(needle, StringComparison.Ordinal, pool)); + + Assert.Equal(result, value.AsSpan().CutBefore(needle, StringComparison.OrdinalIgnoreCase)); + Assert.Same(pool.GetOrAdd(result), value.AsSpan().CutBefore(needle, StringComparison.OrdinalIgnoreCase, pool)); + + Assert.False(value.AsSpan().TryCutBefore(needle, StringComparison.Ordinal, out tryResult)); + Assert.Null(tryResult); + Assert.False(value.AsSpan().TryCutBefore(needle, StringComparison.Ordinal, pool, out tryResult)); + Assert.Null(tryResult); + + Assert.True(value.AsSpan().TryCutBefore(needle, StringComparison.OrdinalIgnoreCase, out tryResult)); + Assert.Equal(result, tryResult); + Assert.True(value.AsSpan().TryCutBefore(needle, StringComparison.OrdinalIgnoreCase, pool, out tryResult)); + Assert.Same(pool.GetOrAdd(result), tryResult); + + resultSpan.Fill(resultSpanInitChar); + Assert.False(value.AsSpan().TryCutBefore(needle, StringComparison.Ordinal, resultSpan, out tryWritten)); + Assert.Equal(resultSpanInit, resultSpan); + Assert.Equal(0, tryWritten); + + resultSpan.Fill(resultSpanInitChar); + Assert.True(value.AsSpan().TryCutBefore(needle, StringComparison.OrdinalIgnoreCase, resultSpan, out tryWritten)); + Assert.Equal(result, resultSpan); + Assert.Equal(resultSpan.Length, tryWritten); + + smallResultSpan.Fill(resultSpanInitChar); + Assert.False(value.AsSpan().TryCutBefore(needle, StringComparison.OrdinalIgnoreCase, smallResultSpan, out tryWritten)); + Assert.Equal(resultSpanInit[..smallResultSpan.Length], smallResultSpan); + Assert.Equal(0, tryWritten); + + Assert.Equal(value, value.AsSpan().CutSpanBefore(needle, StringComparison.Ordinal)); + Assert.Equal(result, value.AsSpan().CutSpanBefore(needle, StringComparison.OrdinalIgnoreCase)); + + Assert.False(value.AsSpan().TryCutSpanBefore(needle, StringComparison.Ordinal, out trySpanResult)); + Assert.Equal(ReadOnlySpan.Empty, trySpanResult); + + Assert.True(value.AsSpan().TryCutSpanBefore(needle, StringComparison.OrdinalIgnoreCase, out trySpanResult)); + Assert.Equal(result, trySpanResult); + } + + [Fact] + public void CutBeforeLast_String_Comparison() + { + const string needle = "AB"; + const string value = "123_ab_456_Ab_789"; + const string result = "123_ab_456_"; + const char resultSpanInitChar = '#'; + + Span resultSpanInit = stackalloc char[result.Length]; + resultSpanInit.Fill(resultSpanInitChar); + + Span resultSpan = stackalloc char[result.Length]; + Span smallResultSpan = stackalloc char[result.Length - 1]; + StringPool pool = new(); + string? tryResult; + ReadOnlySpan trySpanResult; + int tryWritten; + + // string for value + // the needle only matches when ignoring case, so overloads without a comparison must not match + Assert.Equal(value, value.CutBeforeLast(needle)); + Assert.Equal(value, value.CutBeforeLast(needle, pool)); + Assert.False(value.TryCutBeforeLast(needle, out tryResult)); + Assert.False(value.TryCutBeforeLast(needle, pool, out tryResult)); + Assert.False(value.TryCutBeforeLast(needle, resultSpan, out tryWritten)); + Assert.Equal(value, value.CutSpanBeforeLast(needle)); + Assert.False(value.TryCutSpanBeforeLast(needle, out trySpanResult)); + + Assert.Equal(value, value.CutBeforeLast(needle, StringComparison.Ordinal)); + Assert.Equal(value, value.CutBeforeLast(needle, StringComparison.Ordinal, pool)); + + Assert.Equal(result, value.CutBeforeLast(needle, StringComparison.OrdinalIgnoreCase)); + Assert.Same(pool.GetOrAdd(result), value.CutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, pool)); + + Assert.False(value.TryCutBeforeLast(needle, StringComparison.Ordinal, out tryResult)); + Assert.Null(tryResult); + Assert.False(value.TryCutBeforeLast(needle, StringComparison.Ordinal, pool, out tryResult)); + Assert.Null(tryResult); + + Assert.True(value.TryCutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, out tryResult)); + Assert.Equal(result, tryResult); + Assert.True(value.TryCutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, pool, out tryResult)); + Assert.Same(pool.GetOrAdd(result), tryResult); + + resultSpan.Fill(resultSpanInitChar); + Assert.False(value.TryCutBeforeLast(needle, StringComparison.Ordinal, resultSpan, out tryWritten)); + Assert.Equal(resultSpanInit, resultSpan); + Assert.Equal(0, tryWritten); + + resultSpan.Fill(resultSpanInitChar); + Assert.True(value.TryCutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, resultSpan, out tryWritten)); + Assert.Equal(result, resultSpan); + Assert.Equal(resultSpan.Length, tryWritten); + + smallResultSpan.Fill(resultSpanInitChar); + Assert.False(value.TryCutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, smallResultSpan, out tryWritten)); + Assert.Equal(resultSpanInit[..smallResultSpan.Length], smallResultSpan); + Assert.Equal(0, tryWritten); + + Assert.Equal(value, value.CutSpanBeforeLast(needle, StringComparison.Ordinal)); + Assert.Equal(result, value.CutSpanBeforeLast(needle, StringComparison.OrdinalIgnoreCase)); + + Assert.False(value.TryCutSpanBeforeLast(needle, StringComparison.Ordinal, out trySpanResult)); + Assert.Equal(ReadOnlySpan.Empty, trySpanResult); + + Assert.True(value.TryCutSpanBeforeLast(needle, StringComparison.OrdinalIgnoreCase, out trySpanResult)); + Assert.Equal(result, trySpanResult); + + // span for value + // the needle only matches when ignoring case, so overloads without a comparison must not match + Assert.Equal(value, value.AsSpan().CutBeforeLast(needle)); + Assert.Equal(value, value.AsSpan().CutBeforeLast(needle, pool)); + Assert.False(value.AsSpan().TryCutBeforeLast(needle, out tryResult)); + Assert.False(value.AsSpan().TryCutBeforeLast(needle, pool, out tryResult)); + Assert.False(value.AsSpan().TryCutBeforeLast(needle, resultSpan, out tryWritten)); + Assert.Equal(value, value.AsSpan().CutSpanBeforeLast(needle)); + Assert.False(value.AsSpan().TryCutSpanBeforeLast(needle, out trySpanResult)); + + Assert.Equal(value, value.AsSpan().CutBeforeLast(needle, StringComparison.Ordinal)); + Assert.Equal(value, value.AsSpan().CutBeforeLast(needle, StringComparison.Ordinal, pool)); + + Assert.Equal(result, value.AsSpan().CutBeforeLast(needle, StringComparison.OrdinalIgnoreCase)); + Assert.Same(pool.GetOrAdd(result), value.AsSpan().CutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, pool)); + + Assert.False(value.AsSpan().TryCutBeforeLast(needle, StringComparison.Ordinal, out tryResult)); + Assert.Null(tryResult); + Assert.False(value.AsSpan().TryCutBeforeLast(needle, StringComparison.Ordinal, pool, out tryResult)); + Assert.Null(tryResult); + + Assert.True(value.AsSpan().TryCutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, out tryResult)); + Assert.Equal(result, tryResult); + Assert.True(value.AsSpan().TryCutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, pool, out tryResult)); + Assert.Same(pool.GetOrAdd(result), tryResult); + + resultSpan.Fill(resultSpanInitChar); + Assert.False(value.AsSpan().TryCutBeforeLast(needle, StringComparison.Ordinal, resultSpan, out tryWritten)); + Assert.Equal(resultSpanInit, resultSpan); + Assert.Equal(0, tryWritten); + + resultSpan.Fill(resultSpanInitChar); + Assert.True(value.AsSpan().TryCutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, resultSpan, out tryWritten)); + Assert.Equal(result, resultSpan); + Assert.Equal(resultSpan.Length, tryWritten); + + smallResultSpan.Fill(resultSpanInitChar); + Assert.False(value.AsSpan().TryCutBeforeLast(needle, StringComparison.OrdinalIgnoreCase, smallResultSpan, out tryWritten)); + Assert.Equal(resultSpanInit[..smallResultSpan.Length], smallResultSpan); + Assert.Equal(0, tryWritten); + + Assert.Equal(value, value.AsSpan().CutSpanBeforeLast(needle, StringComparison.Ordinal)); + Assert.Equal(result, value.AsSpan().CutSpanBeforeLast(needle, StringComparison.OrdinalIgnoreCase)); + + Assert.False(value.AsSpan().TryCutSpanBeforeLast(needle, StringComparison.Ordinal, out trySpanResult)); + Assert.Equal(ReadOnlySpan.Empty, trySpanResult); + + Assert.True(value.AsSpan().TryCutSpanBeforeLast(needle, StringComparison.OrdinalIgnoreCase, out trySpanResult)); + Assert.Equal(result, trySpanResult); + } + + [Theory] + [InlineData(StringComparison.CurrentCulture)] + [InlineData(StringComparison.CurrentCultureIgnoreCase)] + [InlineData(StringComparison.InvariantCulture)] + [InlineData(StringComparison.InvariantCultureIgnoreCase)] + public void Cut_String_CultureMatchLength(StringComparison comparisonType) + { + // with a culture-sensitive comparison the matched text can be longer or shorter than the needle + const string precomposed = "\u00C5"; // Å + const string decomposed = "A\u030A"; // A + combining ring above, canonically equivalent to Å + const string softHyphen = "\u00AD"; // ignored by culture-sensitive comparisons + + Span resultSpan = stackalloc char[16]; + string? tryResult; + ReadOnlySpan trySpanResult; + int tryWritten; + + // the match is longer than the needle + const string decomposedValue = "1" + decomposed + "2" + decomposed + "3"; + Assert.Equal("2" + decomposed + "3", decomposedValue.CutAfter(precomposed, comparisonType)); + Assert.Equal("3", decomposedValue.CutAfterLast(precomposed, comparisonType)); + Assert.Equal("1", decomposedValue.CutBefore(precomposed, comparisonType)); + Assert.Equal("1" + decomposed + "2", decomposedValue.CutBeforeLast(precomposed, comparisonType)); + Assert.Equal("2" + decomposed + "3", decomposedValue.AsSpan().CutSpanAfter(precomposed, comparisonType)); + Assert.Equal("3", decomposedValue.AsSpan().CutSpanAfterLast(precomposed, comparisonType)); + + const string softHyphenValue = "1b" + softHyphen + "c2b" + softHyphen + "c3"; + Assert.Equal("2b" + softHyphen + "c3", softHyphenValue.CutAfter("bc", comparisonType)); + Assert.Equal("3", softHyphenValue.CutAfterLast("bc", comparisonType)); + Assert.Equal("1", softHyphenValue.CutBefore("bc", comparisonType)); + Assert.Equal("1b" + softHyphen + "c2", softHyphenValue.CutBeforeLast("bc", comparisonType)); + Assert.Equal("2b" + softHyphen + "c3", softHyphenValue.AsSpan().CutSpanAfter("bc", comparisonType)); + Assert.Equal("3", softHyphenValue.AsSpan().CutSpanAfterLast("bc", comparisonType)); + + // the match is shorter than the needle + const string precomposedValue = "1" + precomposed + "2" + precomposed + "3"; + Assert.Equal("2" + precomposed + "3", precomposedValue.CutAfter(decomposed, comparisonType)); + Assert.Equal("3", precomposedValue.CutAfterLast(decomposed, comparisonType)); + Assert.Equal("1", precomposedValue.CutBefore(decomposed, comparisonType)); + Assert.Equal("1" + precomposed + "2", precomposedValue.CutBeforeLast(decomposed, comparisonType)); + Assert.Equal("2" + precomposed + "3", precomposedValue.AsSpan().CutSpanAfter(decomposed, comparisonType)); + Assert.Equal("3", precomposedValue.AsSpan().CutSpanAfterLast(decomposed, comparisonType)); + + const string softHyphenNeedle = "b" + softHyphen + "c"; + Assert.Equal("2bc3", "1bc2bc3".CutAfter(softHyphenNeedle, comparisonType)); + Assert.Equal("3", "1bc2bc3".CutAfterLast(softHyphenNeedle, comparisonType)); + Assert.Equal("1", "1bc2bc3".CutBefore(softHyphenNeedle, comparisonType)); + Assert.Equal("1bc2", "1bc2bc3".CutBeforeLast(softHyphenNeedle, comparisonType)); + Assert.Equal("2bc3", "1bc2bc3".AsSpan().CutSpanAfter(softHyphenNeedle, comparisonType)); + Assert.Equal("3", "1bc2bc3".AsSpan().CutSpanAfterLast(softHyphenNeedle, comparisonType)); + + // a match at the end of the value that is shorter than the needle + const string precomposedEndValue = "1" + precomposed; + Assert.True(precomposedEndValue.TryCutAfter(decomposed, comparisonType, out tryResult)); + Assert.Equal("", tryResult); + Assert.True(precomposedEndValue.TryCutAfterLast(decomposed, comparisonType, out tryResult)); + Assert.Equal("", tryResult); + Assert.True(precomposedEndValue.AsSpan().TryCutSpanAfter(decomposed, comparisonType, out trySpanResult)); + Assert.Equal("", trySpanResult); + Assert.True(precomposedEndValue.AsSpan().TryCutSpanAfterLast(decomposed, comparisonType, out trySpanResult)); + Assert.Equal("", trySpanResult); + + // other result shapes + Assert.True(decomposedValue.AsSpan().TryCutAfter(precomposed, comparisonType, StringPool.Shared, out tryResult)); + Assert.Equal("2" + decomposed + "3", tryResult); + Assert.True(decomposedValue.TryCutAfter(precomposed, comparisonType, resultSpan, out tryWritten)); + Assert.Equal("2" + decomposed + "3", resultSpan[..tryWritten]); + Assert.True(precomposedValue.AsSpan().TryCutAfterLast(decomposed, comparisonType, resultSpan, out tryWritten)); + Assert.Equal("3", resultSpan[..tryWritten]); + + // a needle made only of ignorable characters matches like an empty needle + Assert.True("abc".TryCutAfter(softHyphen, comparisonType, out tryResult)); + Assert.Equal("abc", tryResult); + Assert.True("abc".TryCutAfterLast(softHyphen, comparisonType, out tryResult)); + Assert.Equal("", tryResult); + Assert.True("abc".TryCutBefore(softHyphen, comparisonType, out tryResult)); + Assert.Equal("", tryResult); + Assert.True("abc".TryCutBeforeLast(softHyphen, comparisonType, out tryResult)); + Assert.Equal("abc", tryResult); + } + + [Fact] + public void Cut_String_CurrentCulture() + { + // in Turkish, "I" is the upper case of the dotless "ı" and not of "i" + const string needle = "I"; + const string dotlessI = "\u0131"; // ı + const string value = "1i2" + dotlessI + "3"; + + CultureInfo culture = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR"); + + Assert.False(value.TryCutAfter(needle, StringComparison.CurrentCulture, out _)); + Assert.False(value.TryCutAfter(needle, StringComparison.InvariantCulture, out _)); + + Assert.Equal("3", value.CutAfter(needle, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal("3", value.CutAfterLast(needle, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal("1i2", value.CutBefore(needle, StringComparison.CurrentCultureIgnoreCase)); + Assert.Equal("1i2", value.CutBeforeLast(needle, StringComparison.CurrentCultureIgnoreCase)); + + Assert.Equal("2" + dotlessI + "3", value.CutAfter(needle, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal("2" + dotlessI + "3", value.CutAfterLast(needle, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal("1", value.CutBefore(needle, StringComparison.InvariantCultureIgnoreCase)); + Assert.Equal("1", value.CutBeforeLast(needle, StringComparison.InvariantCultureIgnoreCase)); + } + finally + { + CultureInfo.CurrentCulture = culture; + } + } } diff --git a/src/OffiUtils/StringUtils.Ext.CutAfter.String.cs b/src/OffiUtils/StringUtils.Ext.CutAfter.String.cs index 7420683..f9aefee 100644 --- a/src/OffiUtils/StringUtils.Ext.CutAfter.String.cs +++ b/src/OffiUtils/StringUtils.Ext.CutAfter.String.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Globalization; using CommunityToolkit.HighPerformance.Buffers; @@ -74,7 +75,7 @@ public bool TryCutAfter(ReadOnlySpan needle, StringPool? pool, [NotNullWhe public bool TryCutAfter(ReadOnlySpan needle, StringComparison comparisonType, StringPool? pool, [NotNullWhen(true)] out string? result) { - if (value.TryCutSpanAfter(needle, out ReadOnlySpan cutValue)) + if (value.TryCutSpanAfter(needle, comparisonType, out ReadOnlySpan cutValue)) { result = pool is null ? cutValue.ToString() : pool.GetOrAdd(cutValue); return true; @@ -88,7 +89,7 @@ public bool TryCutAfter(ReadOnlySpan needle, Span destination, out i public bool TryCutAfter(ReadOnlySpan needle, StringComparison comparisonType, Span destination, out int charsWritten) { - if (value.TryCutSpanAfter(needle, out ReadOnlySpan cutValue) && cutValue.TryCopyTo(destination)) + if (value.TryCutSpanAfter(needle, comparisonType, out ReadOnlySpan cutValue) && cutValue.TryCopyTo(destination)) { charsWritten = cutValue.Length; return true; @@ -107,10 +108,45 @@ public bool TryCutSpanAfter(ReadOnlySpan needle, StringComparison comparis { cutValue = default; if (value.IsEmpty) return false; - int index = value.IndexOf(needle, comparisonType); + int index = IndexOf(value, needle, comparisonType, out int matchLength); if (index == -1) return false; - cutValue = value[(index + needle.Length)..]; + cutValue = value[(index + matchLength)..]; return true; } } + + // culture-sensitive matches can be longer or shorter than the needle (ignorable characters like U+00AD, + // or "\u00C5" matching "A\u030A"), so cutting after a match needs the length of the matched text + private static int IndexOf(ReadOnlySpan value, ReadOnlySpan needle, StringComparison comparisonType, out int matchLength) + { + if (comparisonType is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) + { + matchLength = needle.Length; + return value.IndexOf(needle, comparisonType); + } + + (CompareInfo compareInfo, CompareOptions options) = GetCompareInfo(comparisonType); + return compareInfo.IndexOf(value, needle, options, out matchLength); + } + + private static int LastIndexOf(ReadOnlySpan value, ReadOnlySpan needle, StringComparison comparisonType, out int matchLength) + { + if (comparisonType is StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase) + { + matchLength = needle.Length; + return value.LastIndexOf(needle, comparisonType); + } + + (CompareInfo compareInfo, CompareOptions options) = GetCompareInfo(comparisonType); + return compareInfo.LastIndexOf(value, needle, options, out matchLength); + } + + private static (CompareInfo CompareInfo, CompareOptions Options) GetCompareInfo(StringComparison comparisonType) => comparisonType switch + { + StringComparison.CurrentCulture => (CultureInfo.CurrentCulture.CompareInfo, CompareOptions.None), + StringComparison.CurrentCultureIgnoreCase => (CultureInfo.CurrentCulture.CompareInfo, CompareOptions.IgnoreCase), + StringComparison.InvariantCulture => (CultureInfo.InvariantCulture.CompareInfo, CompareOptions.None), + StringComparison.InvariantCultureIgnoreCase => (CultureInfo.InvariantCulture.CompareInfo, CompareOptions.IgnoreCase), + _ => throw new ArgumentException("The string comparison type passed in is currently not supported.", nameof(comparisonType)) + }; } diff --git a/src/OffiUtils/StringUtils.Ext.CutAfterLast.String.cs b/src/OffiUtils/StringUtils.Ext.CutAfterLast.String.cs index 6af506d..8f3d981 100644 --- a/src/OffiUtils/StringUtils.Ext.CutAfterLast.String.cs +++ b/src/OffiUtils/StringUtils.Ext.CutAfterLast.String.cs @@ -73,7 +73,7 @@ public bool TryCutAfterLast(ReadOnlySpan needle, StringPool? pool, [NotNul public bool TryCutAfterLast(ReadOnlySpan needle, StringComparison comparisonType, StringPool? pool, [NotNullWhen(true)] out string? result) { - if (value.TryCutSpanAfterLast(needle, out ReadOnlySpan cutValue)) + if (value.TryCutSpanAfterLast(needle, comparisonType, out ReadOnlySpan cutValue)) { result = pool is null ? cutValue.ToString() : pool.GetOrAdd(cutValue); return true; @@ -87,7 +87,7 @@ public bool TryCutAfterLast(ReadOnlySpan needle, Span destination, o public bool TryCutAfterLast(ReadOnlySpan needle, StringComparison comparisonType, Span destination, out int charsWritten) { - if (value.TryCutSpanAfterLast(needle, out ReadOnlySpan cutValue) && cutValue.TryCopyTo(destination)) + if (value.TryCutSpanAfterLast(needle, comparisonType, out ReadOnlySpan cutValue) && cutValue.TryCopyTo(destination)) { charsWritten = cutValue.Length; return true; @@ -106,9 +106,9 @@ public bool TryCutSpanAfterLast(ReadOnlySpan needle, StringComparison comp { cutValue = default; if (value.IsEmpty) return false; - int index = value.LastIndexOf(needle, comparisonType); + int index = LastIndexOf(value, needle, comparisonType, out int matchLength); if (index == -1) return false; - cutValue = value[(index + needle.Length)..]; + cutValue = value[(index + matchLength)..]; return true; } } diff --git a/src/OffiUtils/StringUtils.Ext.CutBefore.String.cs b/src/OffiUtils/StringUtils.Ext.CutBefore.String.cs index 5b4da58..e535421 100644 --- a/src/OffiUtils/StringUtils.Ext.CutBefore.String.cs +++ b/src/OffiUtils/StringUtils.Ext.CutBefore.String.cs @@ -73,7 +73,7 @@ public bool TryCutBefore(ReadOnlySpan needle, StringPool? pool, [NotNullWh public bool TryCutBefore(ReadOnlySpan needle, StringComparison comparisonType, StringPool? pool, [NotNullWhen(true)] out string? result) { - if (value.TryCutSpanBefore(needle, out ReadOnlySpan cutValue)) + if (value.TryCutSpanBefore(needle, comparisonType, out ReadOnlySpan cutValue)) { result = pool is null ? cutValue.ToString() : pool.GetOrAdd(cutValue); return true; @@ -87,7 +87,7 @@ public bool TryCutBefore(ReadOnlySpan needle, Span destination, out public bool TryCutBefore(ReadOnlySpan needle, StringComparison comparisonType, Span destination, out int charsWritten) { - if (value.TryCutSpanBefore(needle, out ReadOnlySpan cutValue) && cutValue.TryCopyTo(destination)) + if (value.TryCutSpanBefore(needle, comparisonType, out ReadOnlySpan cutValue) && cutValue.TryCopyTo(destination)) { charsWritten = cutValue.Length; return true; diff --git a/src/OffiUtils/StringUtils.Ext.CutBeforeLast.String.cs b/src/OffiUtils/StringUtils.Ext.CutBeforeLast.String.cs index ec5696a..95b3259 100644 --- a/src/OffiUtils/StringUtils.Ext.CutBeforeLast.String.cs +++ b/src/OffiUtils/StringUtils.Ext.CutBeforeLast.String.cs @@ -73,7 +73,7 @@ public bool TryCutBeforeLast(ReadOnlySpan needle, StringPool? pool, [NotNu public bool TryCutBeforeLast(ReadOnlySpan needle, StringComparison comparisonType, StringPool? pool, [NotNullWhen(true)] out string? result) { - if (value.TryCutSpanBeforeLast(needle, out ReadOnlySpan cutValue)) + if (value.TryCutSpanBeforeLast(needle, comparisonType, out ReadOnlySpan cutValue)) { result = pool is null ? cutValue.ToString() : pool.GetOrAdd(cutValue); return true; @@ -87,7 +87,7 @@ public bool TryCutBeforeLast(ReadOnlySpan needle, Span destination, public bool TryCutBeforeLast(ReadOnlySpan needle, StringComparison comparisonType, Span destination, out int charsWritten) { - if (value.TryCutSpanBeforeLast(needle, out ReadOnlySpan cutValue) && cutValue.TryCopyTo(destination)) + if (value.TryCutSpanBeforeLast(needle, comparisonType, out ReadOnlySpan cutValue) && cutValue.TryCopyTo(destination)) { charsWritten = cutValue.Length; return true;