From 73bda636032834a7e4108c89334f310e1c0d1fd9 Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Fri, 1 May 2020 14:21:42 -0700 Subject: [PATCH 1/7] Add StringSplitOptions.TrimEntries (not yet hooked up) --- .../System.Private.CoreLib/src/System/StringSplitOptions.cs | 3 ++- src/libraries/System.Runtime/ref/System.Runtime.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/StringSplitOptions.cs b/src/libraries/System.Private.CoreLib/src/System/StringSplitOptions.cs index d7020559a1d1ca..f71b040def265b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/StringSplitOptions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/StringSplitOptions.cs @@ -8,6 +8,7 @@ namespace System public enum StringSplitOptions { None = 0, - RemoveEmptyEntries = 1 + RemoveEmptyEntries = 1, + TrimEntries = 2 } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 040f2cbeed453b..7a9788a6e234a9 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -3526,6 +3526,7 @@ public enum StringSplitOptions { None = 0, RemoveEmptyEntries = 1, + TrimEntries = 2, } public partial class SystemException : System.Exception { From a8b291dbb7c032a09d7472e38a65c68438685b61 Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Fri, 1 May 2020 14:22:06 -0700 Subject: [PATCH 2/7] Remove Utf8StringSplitOptions in favor of StringSplitOptions --- .../System.Private.CoreLib.Shared.projitems | 1 - .../src/System/Text/Utf8Span.Manipulation.cs | 24 +++++++------- .../src/System/Utf8String.Manipulation.cs | 32 +++++++++---------- .../src/System/Utf8StringSplitOptions.cs | 17 ---------- .../ref/System.Utf8String.Experimental.cs | 19 ++++------- .../src/System.Utf8String.Experimental.csproj | 2 -- .../System/Utf8SpanTests.Manipulation.cs | 18 +++++------ .../System/Utf8StringTests.Manipulation.cs | 16 +++++----- 8 files changed, 51 insertions(+), 78 deletions(-) delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Utf8StringSplitOptions.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 638aa0cc79f8e9..4f302c80d7073c 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1852,7 +1852,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Utf8Span.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Utf8Span.Manipulation.cs index 2317de908ab375..c3734b3cb0183c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Utf8Span.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Utf8Span.Manipulation.cs @@ -11,7 +11,7 @@ namespace System.Text { public readonly ref partial struct Utf8Span { - public SplitResult Split(char separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) + public SplitResult Split(char separator, StringSplitOptions options = StringSplitOptions.None) { if (!Rune.TryCreate(separator, out Rune rune)) { @@ -25,14 +25,14 @@ public SplitResult Split(char separator, Utf8StringSplitOptions options = Utf8St return new SplitResult(this, rune, options); } - public SplitResult Split(Rune separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) + public SplitResult Split(Rune separator, StringSplitOptions options = StringSplitOptions.None) { Utf8String.CheckSplitOptions(options); return new SplitResult(this, separator, options); } - public SplitResult Split(Utf8Span separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) + public SplitResult Split(Utf8Span separator, StringSplitOptions options = StringSplitOptions.None) { if (separator.IsEmpty) { @@ -252,7 +252,7 @@ public readonly ref struct SplitResult { private readonly State _state; - internal SplitResult(Utf8Span source, Rune searchRune, Utf8StringSplitOptions splitOptions) + internal SplitResult(Utf8Span source, Rune searchRune, StringSplitOptions splitOptions) { _state = new State { @@ -263,7 +263,7 @@ internal SplitResult(Utf8Span source, Rune searchRune, Utf8StringSplitOptions sp }; } - internal SplitResult(Utf8Span source, Utf8Span searchTerm, Utf8StringSplitOptions splitOptions) + internal SplitResult(Utf8Span source, Utf8Span searchTerm, StringSplitOptions splitOptions) { _state = new State { @@ -348,7 +348,7 @@ public void Deconstruct(out Utf8Span item1, out Utf8Span item2, out Utf8Span ite private void TrimIfNeeded(ref Utf8Span span) { - if ((_state.SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) + if ((_state.SplitOptions & StringSplitOptions.TrimEntries) != 0) { span = span.Trim(); } @@ -357,7 +357,7 @@ private void TrimIfNeeded(ref Utf8Span span) [StructLayout(LayoutKind.Auto)] public ref struct Enumerator { - private const Utf8StringSplitOptions HALT_ENUMERATION = (Utf8StringSplitOptions)int.MinValue; + private const StringSplitOptions HALT_ENUMERATION = (StringSplitOptions)int.MinValue; private Utf8Span _current; private State _state; @@ -384,7 +384,7 @@ public bool MoveNext() // bit of data after the final occurrence of the search term. We'll also set a flag saying that we've // completed enumeration. - if (_current.IsEmpty && (_state.SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0) + if (_current.IsEmpty && (_state.SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0) { return false; } @@ -406,7 +406,7 @@ private ref struct State // fully mutable internal Utf8Span RemainingSearchSpace; internal int SearchRune; // -1 if not specified, takes less space than "Rune?" internal Utf8Span SearchTerm; - internal Utf8StringSplitOptions SplitOptions; + internal StringSplitOptions SplitOptions; // Returns 'true' if a match was found, 'false' otherwise. internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstItem, out Utf8Span remainder) @@ -448,7 +448,7 @@ internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstI firstItem = searchSpan; - if ((SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) + if ((SplitOptions & StringSplitOptions.TrimEntries) != 0) { firstItem = firstItem.Trim(); } @@ -463,14 +463,14 @@ internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstI firstItem = searchSpan[..matchRange.Start]; // TODO_UTF8STRING: Could use unsafe slicing as optimization remainder = searchSpan[matchRange.End..]; // TODO_UTF8STRING: Could use unsafe slicing as optimization - if ((SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) + if ((SplitOptions & StringSplitOptions.TrimEntries) != 0) { firstItem = firstItem.Trim(); } // If we're asked to remove empty entries, loop until there's a real value in 'firstItem'. - if ((SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0 && firstItem.IsEmpty) + if ((SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0 && firstItem.IsEmpty) { searchSpan = ref remainder; continue; diff --git a/src/libraries/System.Private.CoreLib/src/System/Utf8String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/Utf8String.Manipulation.cs index ac66e0e0668a38..b0df3ceba042f0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Utf8String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Utf8String.Manipulation.cs @@ -21,16 +21,16 @@ public sealed partial class Utf8String { [StackTraceHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void CheckSplitOptions(Utf8StringSplitOptions options) + internal static void CheckSplitOptions(StringSplitOptions options) { - if ((uint)options > (uint)(Utf8StringSplitOptions.RemoveEmptyEntries | Utf8StringSplitOptions.TrimEntries)) + if ((uint)options > (uint)(StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { CheckSplitOptions_Throw(options); } } [StackTraceHidden] - private static void CheckSplitOptions_Throw(Utf8StringSplitOptions options) + private static void CheckSplitOptions_Throw(StringSplitOptions options) { throw new ArgumentOutOfRangeException( paramName: nameof(options), @@ -134,7 +134,7 @@ internal Utf8String Substring(int startIndex, int length) return InternalSubstring(startIndex, length); } - public SplitResult Split(char separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) + public SplitResult Split(char separator, StringSplitOptions options = StringSplitOptions.None) { if (!Rune.TryCreate(separator, out Rune rune)) { @@ -148,14 +148,14 @@ public SplitResult Split(char separator, Utf8StringSplitOptions options = Utf8St return new SplitResult(this, rune, options); } - public SplitResult Split(Rune separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) + public SplitResult Split(Rune separator, StringSplitOptions options = StringSplitOptions.None) { CheckSplitOptions(options); return new SplitResult(this, separator, options); } - public SplitResult Split(Utf8String separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) + public SplitResult Split(Utf8String separator, StringSplitOptions options = StringSplitOptions.None) { if (IsNullOrEmpty(separator)) { @@ -372,7 +372,7 @@ private void ValidateStartIndexAndLength(int startIndex, int length) { private readonly State _state; - internal SplitResult(Utf8String source, Rune searchRune, Utf8StringSplitOptions splitOptions) + internal SplitResult(Utf8String source, Rune searchRune, StringSplitOptions splitOptions) { _state = new State { @@ -384,7 +384,7 @@ internal SplitResult(Utf8String source, Rune searchRune, Utf8StringSplitOptions }; } - internal SplitResult(Utf8String source, Utf8String searchTerm, Utf8StringSplitOptions splitOptions) + internal SplitResult(Utf8String source, Utf8String searchTerm, StringSplitOptions splitOptions) { _state = new State { @@ -526,7 +526,7 @@ public void Deconstruct(out Utf8String? item1, out Utf8String? item2, out Utf8St private unsafe Utf8String? TrimIfNeeded(Utf8Span span) { - if ((_state.SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) + if ((_state.SplitOptions & StringSplitOptions.TrimEntries) != 0) { span = span.Trim(); } @@ -541,7 +541,7 @@ public void Deconstruct(out Utf8String? item1, out Utf8String? item2, out Utf8St { // normalize empty spans to null if needed, otherwise normalize to Utf8String.Empty - if ((_state.SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0 + if ((_state.SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0 || Unsafe.AreSame(ref span.DangerousGetMutableReference(), ref Unsafe.AsRef(null))) { return null; @@ -566,7 +566,7 @@ public void Deconstruct(out Utf8String? item1, out Utf8String? item2, out Utf8St [StructLayout(LayoutKind.Auto)] public struct Enumerator : IEnumerator { - private const Utf8StringSplitOptions HALT_ENUMERATION = (Utf8StringSplitOptions)int.MinValue; + private const StringSplitOptions HALT_ENUMERATION = (StringSplitOptions)int.MinValue; private Utf8String? _current; private State _state; @@ -595,7 +595,7 @@ public bool MoveNext() // bit of data after the final occurrence of the search term. We'll also set a flag saying that we've // completed enumeration. - if (firstItem.IsEmpty && (_state.SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0) + if (firstItem.IsEmpty && (_state.SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0) { return false; } @@ -630,7 +630,7 @@ private struct State // fully mutable internal int OffsetAtWhichToContinueSearch; internal int SearchRune; // -1 if not specified, takes less space than "Rune?" internal Utf8String? SearchTerm; - internal Utf8StringSplitOptions SplitOptions; + internal StringSplitOptions SplitOptions; // Returns 'true' if a match was found, 'false' otherwise. internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstItem, out Utf8Span remainder) @@ -673,7 +673,7 @@ internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstI firstItem = searchSpan; - if ((SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) + if ((SplitOptions & StringSplitOptions.TrimEntries) != 0) { firstItem = firstItem.Trim(); } @@ -688,14 +688,14 @@ internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstI firstItem = searchSpan[..matchRange.Start]; // TODO_UTF8STRING: Could use unsafe slicing as optimization remainder = searchSpan[matchRange.End..]; // TODO_UTF8STRING: Could use unsafe slicing as optimization - if ((SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) + if ((SplitOptions & StringSplitOptions.TrimEntries) != 0) { firstItem = firstItem.Trim(); } // If we're asked to remove empty entries, loop until there's a real value in 'firstItem'. - if ((SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0 && firstItem.IsEmpty) + if ((SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0 && firstItem.IsEmpty) { searchSpan = ref remainder; continue; diff --git a/src/libraries/System.Private.CoreLib/src/System/Utf8StringSplitOptions.cs b/src/libraries/System.Private.CoreLib/src/System/Utf8StringSplitOptions.cs deleted file mode 100644 index 29a00a253fe98d..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Utf8StringSplitOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System -{ - // TODO_UTF8STRING: This should be removed and we should use regular StringSplitOptions - // once a 'TrimEntries' flag gets added to the type. - - [Flags] - public enum Utf8StringSplitOptions - { - None = 0, - RemoveEmptyEntries = 1, - TrimEntries = 2 - } -} diff --git a/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs b/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs index 4a906ba12ca443..03d5b84be1b7e9 100644 --- a/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs +++ b/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs @@ -122,9 +122,9 @@ public Utf8String(string value) { } public System.Utf8String Normalize(System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; } public static bool operator !=(System.Utf8String? left, System.Utf8String? right) { throw null; } public static bool operator ==(System.Utf8String? left, System.Utf8String? right) { throw null; } - public SplitResult Split(char separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } - public SplitResult Split(System.Text.Rune separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } - public SplitResult Split(System.Utf8String separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } + public SplitResult Split(char separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } + public SplitResult Split(System.Text.Rune separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } + public SplitResult Split(System.Utf8String separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } public SplitOnResult SplitOn(char separator) { throw null; } public SplitOnResult SplitOn(char separator, System.StringComparison comparisonType) { throw null; } public SplitOnResult SplitOn(System.Text.Rune separator) { throw null; } @@ -262,13 +262,6 @@ public readonly struct SplitOnResult public void Deconstruct(out System.Utf8String before, out System.Utf8String? after) { throw null; } } } - [System.FlagsAttribute] - public enum Utf8StringSplitOptions - { - None = 0, - RemoveEmptyEntries = 1, - TrimEntries = 2 - } } namespace System.Net.Http { @@ -327,9 +320,9 @@ public readonly ref partial struct Utf8Span public static bool operator !=(System.Text.Utf8Span left, System.Text.Utf8Span right) { throw null; } public static bool operator ==(System.Text.Utf8Span left, System.Text.Utf8Span right) { throw null; } public System.Text.Utf8Span this[System.Range range] { get { throw null; } } - public SplitResult Split(char separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } - public SplitResult Split(System.Text.Rune separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } - public SplitResult Split(System.Text.Utf8Span separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } + public SplitResult Split(char separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } + public SplitResult Split(System.Text.Rune separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } + public SplitResult Split(System.Text.Utf8Span separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } public SplitOnResult SplitOn(char separator) { throw null; } public SplitOnResult SplitOn(char separator, System.StringComparison comparisonType) { throw null; } public SplitOnResult SplitOn(System.Text.Rune separator) { throw null; } diff --git a/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj b/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj index d40ffab549bb04..b6ffd891b594be 100644 --- a/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj +++ b/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj @@ -73,8 +73,6 @@ Link="System\Text\Unicode\Utf8Utility.Transcoding.cs" /> - actualRanges = new List(); - foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.None)) + foreach (Utf8Span slice in splitAction(span, StringSplitOptions.None)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } @@ -181,7 +181,7 @@ private static void SplitTest_Common(ustring source, Utf8SpanSplitDelegate split // Next, run the split with empty entries removed actualRanges = new List(); - foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.RemoveEmptyEntries)) + foreach (Utf8Span slice in splitAction(span, StringSplitOptions.RemoveEmptyEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } @@ -197,7 +197,7 @@ private static void SplitTest_Common(ustring source, Utf8SpanSplitDelegate split } actualRanges = new List(); - foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.TrimEntries)) + foreach (Utf8Span slice in splitAction(span, StringSplitOptions.TrimEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } @@ -207,7 +207,7 @@ private static void SplitTest_Common(ustring source, Utf8SpanSplitDelegate split // Finally, run the split both trimmed and with empty entries removed actualRanges = new List(); - foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.TrimEntries | Utf8StringSplitOptions.RemoveEmptyEntries)) + foreach (Utf8Span slice in splitAction(span, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8StringTests.Manipulation.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8StringTests.Manipulation.cs index e80e611241ee0f..d6b14c1f4de90d 100644 --- a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8StringTests.Manipulation.cs +++ b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8StringTests.Manipulation.cs @@ -14,7 +14,7 @@ namespace System.Tests { public unsafe partial class Utf8StringTests { - private delegate Utf8String.SplitResult Utf8StringSplitDelegate(Utf8String ustr, Utf8StringSplitOptions splitOptions); + private delegate Utf8String.SplitResult Utf8StringSplitDelegate(Utf8String ustr, StringSplitOptions splitOptions); [Fact] public static void Split_Utf8StringSeparator_WithNullOrEmptySeparator_Throws() @@ -98,13 +98,13 @@ public static void Split_Deconstruct_WithOptions() // into the original buffer), not deep (textual) equality checks. { - (Utf8String a, Utf8String b) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries); + (Utf8String a, Utf8String b) = ustr.Split(',', StringSplitOptions.RemoveEmptyEntries); Assert.Equal(u8("a"), a); Assert.Equal(u8(" , b, c,, d, e"), b); } { - (Utf8String a, Utf8String x, Utf8String b, Utf8String c, Utf8String d, Utf8String e) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries); + (Utf8String a, Utf8String x, Utf8String b, Utf8String c, Utf8String d, Utf8String e) = ustr.Split(',', StringSplitOptions.RemoveEmptyEntries); Assert.Equal(u8("a"), a); // "a" Assert.Equal(u8(" "), x); // " " Assert.Equal(u8(" b"), b); // " b" @@ -114,7 +114,7 @@ public static void Split_Deconstruct_WithOptions() } { - (Utf8String a, Utf8String b, Utf8String c, Utf8String d, Utf8String e, Utf8String f, Utf8String g, Utf8String h) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries | Utf8StringSplitOptions.TrimEntries); + (Utf8String a, Utf8String b, Utf8String c, Utf8String d, Utf8String e, Utf8String f, Utf8String g, Utf8String h) = ustr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); Assert.Equal(u8("a"), a); Assert.Equal(u8("b"), b); Assert.Equal(u8("c"), c); @@ -150,25 +150,25 @@ private static void SplitTest_Common(Utf8String source, Utf8StringSplitDelegate Assert.Equal( expected: expectedRanges.Select(range => source[range]), - actual: splitAction(source, Utf8StringSplitOptions.None)); + actual: splitAction(source, StringSplitOptions.None)); // Next, run the split with empty entries removed Assert.Equal( expected: expectedRanges.Select(range => source[range]).Where(ustr => ustr.Length != 0), - actual: splitAction(source, Utf8StringSplitOptions.RemoveEmptyEntries)); + actual: splitAction(source, StringSplitOptions.RemoveEmptyEntries)); // Next, run the split with results trimmed (but allowing empty results) Assert.Equal( expected: expectedRanges.Select(range => source[range].Trim()), - actual: splitAction(source, Utf8StringSplitOptions.TrimEntries)); + actual: splitAction(source, StringSplitOptions.TrimEntries)); // Finally, run the split both trimmed and with empty entries removed Assert.Equal( expected: expectedRanges.Select(range => source[range].Trim()).Where(ustr => ustr.Length != 0), - actual: splitAction(source, Utf8StringSplitOptions.TrimEntries | Utf8StringSplitOptions.RemoveEmptyEntries)); + actual: splitAction(source, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)); } public static IEnumerable Trim_TestData() => Utf8SpanTests.Trim_TestData(); From c05fe7be0bd1d1c6656583a5dd355bd428a685e4 Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Fri, 1 May 2020 15:41:26 -0700 Subject: [PATCH 3/7] Make string.Split aware of new flags --- .../src/System/String.Manipulation.cs | 168 ++++++++++++------ .../tests/System/String.SplitTests.cs | 74 ++++++-- 2 files changed, 172 insertions(+), 70 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index 2fe2781e2b71b8..d2f677633c875e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -1270,19 +1270,30 @@ private string[] SplitInternal(ReadOnlySpan separators, int count, StringS throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount); - if (options < StringSplitOptions.None || options > StringSplitOptions.RemoveEmptyEntries) - throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, options)); + CheckStringSplitOptions(options); - bool omitEmptyEntries = (options == StringSplitOptions.RemoveEmptyEntries); - - if ((count == 0) || (omitEmptyEntries && Length == 0)) + ShortCircuit: + if (count <= 1 || Length == 0) { - return Array.Empty(); + // Per the method's documentation, we'll short-circuit the search for separators. + // But we still need to post-process the results based on the caller-provided flags. + + string candidate = this; + if (((options & StringSplitOptions.TrimEntries) != 0) && (count > 0)) + { + candidate = candidate.Trim(); + } + if (((options & StringSplitOptions.RemoveEmptyEntries) != 0) && (candidate.Length == 0)) + { + count = 0; + } + return (count == 0) ? Array.Empty() : new string[] { candidate }; } - if (count == 1) + if (separators.IsEmpty) { - return new string[] { this }; + // Caller is already splitting on whitespace; no need for separate trim step + options &= ~StringSplitOptions.TrimEntries; } var sepListBuilder = new ValueListBuilder(stackalloc int[StackallocIntBufferSizeLimit]); @@ -1293,12 +1304,13 @@ private string[] SplitInternal(ReadOnlySpan separators, int count, StringS // Handle the special case of no replaces. if (sepList.Length == 0) { - return new string[] { this }; + count = 1; + goto ShortCircuit; } - string[] result = omitEmptyEntries - ? SplitOmitEmptyEntries(sepList, default, 1, count) - : SplitKeepEmptyEntries(sepList, default, 1, count); + string[] result = (options != StringSplitOptions.None) + ? SplitWithPostProcessing(sepList, default, 1, count, options) + : SplitWithoutPostProcessing(sepList, default, 1, count); sepListBuilder.Dispose(); @@ -1333,33 +1345,45 @@ private string[] SplitInternal(string? separator, string?[]? separators, int cou SR.ArgumentOutOfRange_NegativeCount); } - if (options < StringSplitOptions.None || options > StringSplitOptions.RemoveEmptyEntries) - { - throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)options)); - } - - bool omitEmptyEntries = (options == StringSplitOptions.RemoveEmptyEntries); + CheckStringSplitOptions(options); bool singleSeparator = separator != null; if (!singleSeparator && (separators == null || separators.Length == 0)) { + // split on whitespace return SplitInternal(default(ReadOnlySpan), count, options); } - if ((count == 0) || (omitEmptyEntries && Length == 0)) + ShortCircuit: + if (count <= 1 || Length == 0) { - return Array.Empty(); - } + // Per the method's documentation, we'll short-circuit the search for separators. + // But we still need to post-process the results based on the caller-provided flags. - if (count == 1 || (singleSeparator && separator!.Length == 0)) - { - return new string[] { this }; + string candidate = this; + if (((options & StringSplitOptions.TrimEntries) != 0) && (count > 0)) + { + candidate = candidate.Trim(); + } + if (((options & StringSplitOptions.RemoveEmptyEntries) != 0) && (candidate.Length == 0)) + { + count = 0; + } + return (count == 0) ? Array.Empty() : new string[] { candidate }; } if (singleSeparator) { - return SplitInternal(separator!, count, options); + if (separator!.Length == 0) + { + count = 1; + goto ShortCircuit; + } + else + { + return SplitInternal(separator, count, options); + } } var sepListBuilder = new ValueListBuilder(stackalloc int[StackallocIntBufferSizeLimit]); @@ -1375,9 +1399,9 @@ private string[] SplitInternal(string? separator, string?[]? separators, int cou return new string[] { this }; } - string[] result = omitEmptyEntries - ? SplitOmitEmptyEntries(sepList, lengthList, 0, count) - : SplitKeepEmptyEntries(sepList, lengthList, 0, count); + string[] result = (options != StringSplitOptions.None) + ? SplitWithPostProcessing(sepList, lengthList, 0, count, options) + : SplitWithoutPostProcessing(sepList, lengthList, 0, count); sepListBuilder.Dispose(); lengthListBuilder.Dispose(); @@ -1394,19 +1418,27 @@ private string[] SplitInternal(string separator, int count, StringSplitOptions o if (sepList.Length == 0) { // there are no separators so sepListBuilder did not rent an array from pool and there is no need to dispose it - return new string[] { this }; + string candidate = this; + if ((options & StringSplitOptions.TrimEntries) != 0) + { + candidate = candidate.Trim(); + } + return ((candidate.Length == 0) && ((options & StringSplitOptions.RemoveEmptyEntries) != 0)) + ? Array.Empty() + : new string[] { candidate }; } - string[] result = options == StringSplitOptions.RemoveEmptyEntries - ? SplitOmitEmptyEntries(sepList, default, separator.Length, count) - : SplitKeepEmptyEntries(sepList, default, separator.Length, count); + string[] result = (options != StringSplitOptions.None) + ? SplitWithPostProcessing(sepList, default, separator.Length, count, options) + : SplitWithoutPostProcessing(sepList, default, separator.Length, count); sepListBuilder.Dispose(); return result; } - private string[] SplitKeepEmptyEntries(ReadOnlySpan sepList, ReadOnlySpan lengthList, int defaultLength, int count) + // This function will not trim entries or special-case empty entries + private string[] SplitWithoutPostProcessing(ReadOnlySpan sepList, ReadOnlySpan lengthList, int defaultLength, int count) { Debug.Assert(count >= 2); @@ -1442,8 +1474,8 @@ private string[] SplitKeepEmptyEntries(ReadOnlySpan sepList, ReadOnlySpan sepList, ReadOnlySpan lengthList, int defaultLength, int count) + // This function may trim entries or omit empty entries + private string[] SplitWithPostProcessing(ReadOnlySpan sepList, ReadOnlySpan lengthList, int defaultLength, int count, StringSplitOptions options) { Debug.Assert(count >= 2); @@ -1458,19 +1490,40 @@ private string[] SplitOmitEmptyEntries(ReadOnlySpan sepList, ReadOnlySpan thisEntry; + + for (int i = 0; i < numReplaces; i++) { - if (sepList[i] - currIndex > 0) + thisEntry = this.AsSpan(currIndex, sepList[i] - currIndex); + if ((options & StringSplitOptions.TrimEntries) != 0) { - splitStrings[arrIndex++] = Substring(currIndex, sepList[i] - currIndex); + thisEntry = thisEntry.Trim(); + } + if (!thisEntry.IsEmpty || ((options & StringSplitOptions.RemoveEmptyEntries) == 0)) + { + splitStrings[arrIndex++] = thisEntry.ToString(); } currIndex = sepList[i] + (lengthList.IsEmpty ? defaultLength : lengthList[i]); if (arrIndex == count - 1) { - // If all the remaining entries at the end are empty, skip them - while (i < numReplaces - 1 && currIndex == sepList[++i]) + // The next iteration of the loop will provide the final entry into the + // results array. If needed, skip over all empty entries before that + // point. + if ((options & StringSplitOptions.RemoveEmptyEntries) != 0) { - currIndex += (lengthList.IsEmpty ? defaultLength : lengthList[i]); + while (i < numReplaces - 1) + { + thisEntry = this.AsSpan(currIndex, sepList[i] - currIndex); + if ((options & StringSplitOptions.TrimEntries) != 0) + { + thisEntry = thisEntry.Trim(); + } + if (!thisEntry.IsEmpty) + { + break; // there's useful data here + } + currIndex += (lengthList.IsEmpty ? defaultLength : lengthList[i]); + } } break; } @@ -1479,22 +1532,20 @@ private string[] SplitOmitEmptyEntries(ReadOnlySpan sepList, ReadOnlySpan @@ -1639,6 +1690,17 @@ private void MakeSeparatorList(string?[] separators, ref ValueListBuilder s } } + private static void CheckStringSplitOptions(StringSplitOptions options) + { + const StringSplitOptions AllValidFlags = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries; + + if ((options & ~AllValidFlags) != 0) + { + // at least one invalid flag was set + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidFlag, ExceptionArgument.options); + } + } + // Returns a substring of this string. // public string Substring(int startIndex) => Substring(startIndex, Length - startIndex); diff --git a/src/libraries/System.Runtime/tests/System/String.SplitTests.cs b/src/libraries/System.Runtime/tests/System/String.SplitTests.cs index 9ccb1a16c6df74..9b0f009166f150 100644 --- a/src/libraries/System.Runtime/tests/System/String.SplitTests.cs +++ b/src/libraries/System.Runtime/tests/System/String.SplitTests.cs @@ -30,24 +30,24 @@ public static void SplitInvalidOptions() const string value = "a,b"; const int count = int.MaxValue; const StringSplitOptions optionsTooLow = StringSplitOptions.None - 1; - const StringSplitOptions optionsTooHigh = StringSplitOptions.RemoveEmptyEntries + 1; + const StringSplitOptions optionsTooHigh = (StringSplitOptions)0x04; - AssertExtensions.Throws(null, () => value.Split(',', optionsTooLow)); - AssertExtensions.Throws(null, () => value.Split(',', optionsTooHigh)); - AssertExtensions.Throws(null, () => value.Split(',', count, optionsTooLow)); - AssertExtensions.Throws(null, () => value.Split(',', count, optionsTooHigh)); - AssertExtensions.Throws(null, () => value.Split(new[] { ',' }, optionsTooLow)); - AssertExtensions.Throws(null, () => value.Split(new[] { ',' }, optionsTooHigh)); - AssertExtensions.Throws(null, () => value.Split(new[] { ',' }, count, optionsTooLow)); - AssertExtensions.Throws(null, () => value.Split(new[] { ',' }, count, optionsTooHigh)); - AssertExtensions.Throws(null, () => value.Split(",", optionsTooLow)); - AssertExtensions.Throws(null, () => value.Split(",", optionsTooHigh)); - AssertExtensions.Throws(null, () => value.Split(",", count, optionsTooLow)); - AssertExtensions.Throws(null, () => value.Split(",", count, optionsTooHigh)); - AssertExtensions.Throws(null, () => value.Split(new[] { "," }, optionsTooLow)); - AssertExtensions.Throws(null, () => value.Split(new[] { "," }, optionsTooHigh)); - AssertExtensions.Throws(null, () => value.Split(new[] { "," }, count, optionsTooLow)); - AssertExtensions.Throws(null, () => value.Split(new[] { "," }, count, optionsTooHigh)); + AssertExtensions.Throws("options", () => value.Split(',', optionsTooLow)); + AssertExtensions.Throws("options", () => value.Split(',', optionsTooHigh)); + AssertExtensions.Throws("options", () => value.Split(',', count, optionsTooLow)); + AssertExtensions.Throws("options", () => value.Split(',', count, optionsTooHigh)); + AssertExtensions.Throws("options", () => value.Split(new[] { ',' }, optionsTooLow)); + AssertExtensions.Throws("options", () => value.Split(new[] { ',' }, optionsTooHigh)); + AssertExtensions.Throws("options", () => value.Split(new[] { ',' }, count, optionsTooLow)); + AssertExtensions.Throws("options", () => value.Split(new[] { ',' }, count, optionsTooHigh)); + AssertExtensions.Throws("options", () => value.Split(",", optionsTooLow)); + AssertExtensions.Throws("options", () => value.Split(",", optionsTooHigh)); + AssertExtensions.Throws("options", () => value.Split(",", count, optionsTooLow)); + AssertExtensions.Throws("options", () => value.Split(",", count, optionsTooHigh)); + AssertExtensions.Throws("options", () => value.Split(new[] { "," }, optionsTooLow)); + AssertExtensions.Throws("options", () => value.Split(new[] { "," }, optionsTooHigh)); + AssertExtensions.Throws("options", () => value.Split(new[] { "," }, count, optionsTooLow)); + AssertExtensions.Throws("options", () => value.Split(new[] { "," }, count, optionsTooHigh)); } [Fact] @@ -423,6 +423,30 @@ public static void SplitNoMatchSingleResult() [InlineData("first,second,third", ' ', M, StringSplitOptions.RemoveEmptyEntries, new[] { "first,second,third" })] [InlineData("Foo Bar Baz", ' ', 2, StringSplitOptions.RemoveEmptyEntries, new[] { "Foo", "Bar Baz" })] [InlineData("Foo Bar Baz", ' ', M, StringSplitOptions.None, new[] { "Foo", "Bar", "Baz" })] + [InlineData("a", ',', 0, StringSplitOptions.None, new string[0])] + [InlineData("a", ',', 0, StringSplitOptions.RemoveEmptyEntries, new string[0])] + [InlineData("a", ',', 0, StringSplitOptions.TrimEntries, new string[0])] + [InlineData("a", ',', 0, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new string[0])] + [InlineData("a", ',', 1, StringSplitOptions.None, new string[] { "a" })] + [InlineData("a", ',', 1, StringSplitOptions.RemoveEmptyEntries, new string[] { "a" })] + [InlineData("a", ',', 1, StringSplitOptions.TrimEntries, new string[] { "a" })] + [InlineData("a", ',', 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new string[] { "a" })] + [InlineData(" ", ',', 0, StringSplitOptions.None, new string[0])] + [InlineData(" ", ',', 0, StringSplitOptions.RemoveEmptyEntries, new string[0])] + [InlineData(" ", ',', 0, StringSplitOptions.TrimEntries, new string[0])] + [InlineData(" ", ',', 0, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new string[0])] + [InlineData(" ", ',', 1, StringSplitOptions.None, new string[] { " " })] + [InlineData(" ", ',', 1, StringSplitOptions.RemoveEmptyEntries, new string[] { " " })] + [InlineData(" ", ',', 1, StringSplitOptions.TrimEntries, new string[] { "" })] + [InlineData(" ", ',', 1, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new string[0])] + [InlineData(" a,, b, c ", ',', 2, StringSplitOptions.None, new string[] { " a", ", b, c " })] + [InlineData(" a,, b, c ", ',', 2, StringSplitOptions.RemoveEmptyEntries, new string[] { " a", " b, c " })] + [InlineData(" a,, b, c ", ',', 2, StringSplitOptions.TrimEntries, new string[] { "a", ", b, c" })] + [InlineData(" a,, b, c ", ',', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new string[] { "a", "b, c" })] + [InlineData(" a,, b, c ", ',', 3, StringSplitOptions.None, new string[] { " a", "", " b, c " })] + [InlineData(" a,, b, c ", ',', 3, StringSplitOptions.RemoveEmptyEntries, new string[] { " a", " b", " c " })] + [InlineData(" a,, b, c ", ',', 3, StringSplitOptions.TrimEntries, new string[] { "a", "", "b, c" })] + [InlineData(" a,, b, c ", ',', 3, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new string[] { "a", "b", "c" })] public static void SplitCharSeparator(string value, char separator, int count, StringSplitOptions options, string[] expected) { Assert.Equal(expected, value.Split(separator, count, options)); @@ -456,6 +480,10 @@ public static void SplitCharSeparator(string value, char separator, int count, S [InlineData("aaabaaabaaa", "aa", M, StringSplitOptions.None, new[] { "", "ab", "ab", "a" })] [InlineData("aaabaaabaaa", "aa", M, StringSplitOptions.RemoveEmptyEntries, new[] { "ab", "ab", "a" })] [InlineData("this, is, a, string, with some spaces", ", ", M, StringSplitOptions.None, new[] { "this", "is", "a", "string", "with some spaces" })] + [InlineData("Monday, Tuesday, Wednesday, Thursday, Friday", ",", M, StringSplitOptions.TrimEntries, new[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" })] + [InlineData("Monday, Tuesday,\r, Wednesday,\n, Thursday, Friday", ",", M, StringSplitOptions.TrimEntries, new[] { "Monday", "Tuesday", "", "Wednesday", "", "Thursday", "Friday" })] + [InlineData("Monday, Tuesday,\r, Wednesday,\n, Thursday, Friday", ",", M, StringSplitOptions.RemoveEmptyEntries, new[] { "Monday", " Tuesday", "\r", " Wednesday", "\n", " Thursday", " Friday" })] + [InlineData("Monday, Tuesday,\r, Wednesday,\n, Thursday, Friday", ",", M, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" })] public static void SplitStringSeparator(string value, string separator, int count, StringSplitOptions options, string[] expected) { Assert.Equal(expected, value.Split(separator, count, options)); @@ -499,6 +527,10 @@ public static void SplitNullCharArraySeparator_BindsToCharArrayOverload() [InlineData("this, is, a, string, with some spaces", new[] { ',', ' ' }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "this", "is", "a", "string", "with", "some", "spaces" })] [InlineData("this, is, a, string, with some spaces", new[] { ',', ' ', 's' }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "thi", "i", "a", "tring", "with", "ome", "pace" })] [InlineData("this, is, a, string, with some spaces", new[] { ',', ' ', 's', 'a' }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "thi", "i", "tring", "with", "ome", "p", "ce" })] + [InlineData("this, is, a, string, with some spaces", new[] { ',', 's', 'a' }, M, StringSplitOptions.None, new[] { "thi" /*s*/, "" /*,*/, " i" /*s*/, "" /*,*/, " " /*a*/, "" /*,*/, " " /*s*/, "tring" /*,*/, " with " /*s*/, "ome " /*s*/, "p" /*a*/, "ce" /*s*/, "" })] + [InlineData("this, is, a, string, with some spaces", new[] { ',', 's', 'a' }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "thi", " i", " ", " ", "tring", " with ", "ome ", "p", "ce" })] + [InlineData("this, is, a, string, with some spaces", new[] { ',', 's', 'a' }, M, StringSplitOptions.TrimEntries, new[] { "thi", "", "i", "", "", "", "", "tring", "with", "ome", "p", "ce", "" })] + [InlineData("this, is, a, string, with some spaces", new[] { ',', 's', 'a' }, M, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new[] { "thi", "i", "tring", "with", "ome", "p", "ce" })] public static void SplitCharArraySeparator(string value, char[] separators, int count, StringSplitOptions options, string[] expected) { Assert.Equal(expected, value.Split(separators, count, options)); @@ -522,6 +554,14 @@ public static void SplitCharArraySeparator(string value, char[] separators, int [InlineData("this, is, a, string, with some spaces", new[] { ", ", " " }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "this", "is", "a", "string", "with", "some", "spaces" })] [InlineData("this, is, a, string, with some spaces", new[] { ",", " ", "s" }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "thi", "i", "a", "tring", "with", "ome", "pace" })] [InlineData("this, is, a, string, with some spaces", new[] { ",", " ", "s", "a" }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "thi", "i", "tring", "with", "ome", "p", "ce" })] + [InlineData("this, is, a, string, with some spaces", new[] { ",", "s", "a" }, M, StringSplitOptions.None, new[] { "thi" /*s*/, "" /*,*/, " i" /*s*/, "" /*,*/, " " /*a*/, "" /*,*/, " " /*s*/, "tring" /*,*/, " with " /*s*/, "ome " /*s*/, "p" /*a*/, "ce" /*s*/, "" })] + [InlineData("this, is, a, string, with some spaces", new[] { ",", "s", "a" }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "thi", " i", " ", " ", "tring", " with ", "ome ", "p", "ce" })] + [InlineData("this, is, a, string, with some spaces", new[] { ",", "s", "a" }, M, StringSplitOptions.TrimEntries, new[] { "thi", "", "i", "", "", "", "", "tring", "with", "ome", "p", "ce", "" })] + [InlineData("this, is, a, string, with some spaces", new[] { ",", "s", "a" }, M, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new[] { "thi", "i", "tring", "with", "ome", "p", "ce" })] + [InlineData("this, is, a, string, with some spaces, ", new[] { ",", " s" }, M, StringSplitOptions.None, new[] { "this", " is", " a", "", "tring", " with", "ome", "paces", " " })] + [InlineData("this, is, a, string, with some spaces, ", new[] { ",", " s" }, M, StringSplitOptions.RemoveEmptyEntries, new[] { "this", " is", " a", "tring", " with", "ome", "paces", " " })] + [InlineData("this, is, a, string, with some spaces, ", new[] { ",", " s" }, M, StringSplitOptions.TrimEntries, new[] { "this", "is", "a", "", "tring", "with", "ome", "paces", "" })] + [InlineData("this, is, a, string, with some spaces, ", new[] { ",", " s" }, M, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries, new[] { "this", "is", "a", "tring", "with", "ome", "paces" })] public static void SplitStringArraySeparator(string value, string[] separators, int count, StringSplitOptions options, string[] expected) { Assert.Equal(expected, value.Split(separators, count, options)); From fda1b84611222bdea97e8ff3eee16b6422321048 Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Fri, 1 May 2020 19:32:39 -0700 Subject: [PATCH 4/7] Change some string.Split call sites to use new API --- .../src/System/Data/DataTable.cs | 4 ++-- .../Drawing/Printing/PrintingServices.Unix.cs | 4 ++-- .../HttpEnvironmentProxy.cs | 20 +------------------ .../System/Net/WebSockets/HttpWebSocket.cs | 4 ++-- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Data.Common/src/System/Data/DataTable.cs b/src/libraries/System.Data.Common/src/System/Data/DataTable.cs index 264d03d1f728bc..917908ec2a451f 100644 --- a/src/libraries/System.Data.Common/src/System/Data/DataTable.cs +++ b/src/libraries/System.Data.Common/src/System/Data/DataTable.cs @@ -3688,12 +3688,12 @@ internal IndexField[] ParseSortString(string sortString) IndexField[] indexDesc = Array.Empty(); if ((null != sortString) && (0 < sortString.Length)) { - string[] split = sortString.Split(','); + string[] split = sortString.Split(',', StringSplitOptions.TrimEntries); indexDesc = new IndexField[split.Length]; for (int i = 0; i < split.Length; i++) { - string current = split[i].Trim(); + string current = split[i]; // handle ASC and DESC. int length = current.Length; diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintingServices.Unix.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintingServices.Unix.cs index c9da109eb951d3..baf19efb84f6ca 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintingServices.Unix.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintingServices.Unix.cs @@ -422,9 +422,9 @@ internal static void LoadPrinterResolutions(string printer, PrinterSettings sett int x_resolution, y_resolution; try { - if (resolution.Contains("x")) // string.Contains(char) is .NetCore2.1+ specific + if (resolution.Contains('x')) { - string[] resolutions = resolution.Split(new[] { 'x' }); + string[] resolutions = resolution.Split('x'); x_resolution = Convert.ToInt32(resolutions[0]); y_resolution = Convert.ToInt32(resolutions[1]); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs index f110c70d31d3ee..1147dee06a7948 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpEnvironmentProxy.cs @@ -107,25 +107,7 @@ private HttpEnvironmentProxy(Uri? httpProxy, Uri? httpsProxy, string? bypassList _httpsProxyUri = httpsProxy; _credentials = HttpEnvironmentProxyCredentials.TryCreate(httpProxy, httpsProxy); - - if (!string.IsNullOrWhiteSpace(bypassList)) - { - string[] list = bypassList.Split(','); - List tmpList = new List(list.Length); - - foreach (string value in list) - { - string tmp = value.Trim(); - if (tmp.Length > 0) - { - tmpList.Add(tmp); - } - } - if (tmpList.Count > 0) - { - _bypass = tmpList.ToArray(); - } - } + _bypass = bypassList?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); } /// diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/WebSockets/HttpWebSocket.cs b/src/libraries/System.Net.HttpListener/src/System/Net/WebSockets/HttpWebSocket.cs index c53818676fba44..d474242626e016 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/WebSockets/HttpWebSocket.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/WebSockets/HttpWebSocket.cs @@ -62,14 +62,14 @@ internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketPro // here, we know that the client has specified something, it's not empty // and the server has specified exactly one protocol - string[] requestProtocols = clientSecWebSocketProtocol.Split(',', StringSplitOptions.RemoveEmptyEntries); + string[] requestProtocols = clientSecWebSocketProtocol.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); acceptProtocol = subProtocol; // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that // this exists in the list the client specified. for (int i = 0; i < requestProtocols.Length; i++) { - string currentRequestProtocol = requestProtocols[i].Trim(); + string currentRequestProtocol = requestProtocols[i]; if (string.Equals(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase)) { return true; From 1bee1620bc9cf79a30b458e04925470cc7bb927d Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Fri, 12 Jun 2020 15:18:08 -0700 Subject: [PATCH 5/7] Revert "Remove Utf8StringSplitOptions in favor of StringSplitOptions" This reverts commit a8b291dbb7c032a09d7472e38a65c68438685b61. --- .../System.Private.CoreLib.Shared.projitems | 1 + .../src/System/Text/Utf8Span.Manipulation.cs | 24 +++++++------- .../src/System/Utf8String.Manipulation.cs | 32 +++++++++---------- .../src/System/Utf8StringSplitOptions.cs | 17 ++++++++++ .../ref/System.Utf8String.Experimental.cs | 19 +++++++---- .../src/System.Utf8String.Experimental.csproj | 2 ++ .../System/Utf8SpanTests.Manipulation.cs | 18 +++++------ .../System/Utf8StringTests.Manipulation.cs | 16 +++++----- 8 files changed, 78 insertions(+), 51 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Utf8StringSplitOptions.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 8c64772a399ff7..1e9f20c2943119 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1894,6 +1894,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Utf8Span.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Utf8Span.Manipulation.cs index c3734b3cb0183c..2317de908ab375 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Utf8Span.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Utf8Span.Manipulation.cs @@ -11,7 +11,7 @@ namespace System.Text { public readonly ref partial struct Utf8Span { - public SplitResult Split(char separator, StringSplitOptions options = StringSplitOptions.None) + public SplitResult Split(char separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) { if (!Rune.TryCreate(separator, out Rune rune)) { @@ -25,14 +25,14 @@ public SplitResult Split(char separator, StringSplitOptions options = StringSpli return new SplitResult(this, rune, options); } - public SplitResult Split(Rune separator, StringSplitOptions options = StringSplitOptions.None) + public SplitResult Split(Rune separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) { Utf8String.CheckSplitOptions(options); return new SplitResult(this, separator, options); } - public SplitResult Split(Utf8Span separator, StringSplitOptions options = StringSplitOptions.None) + public SplitResult Split(Utf8Span separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) { if (separator.IsEmpty) { @@ -252,7 +252,7 @@ public readonly ref struct SplitResult { private readonly State _state; - internal SplitResult(Utf8Span source, Rune searchRune, StringSplitOptions splitOptions) + internal SplitResult(Utf8Span source, Rune searchRune, Utf8StringSplitOptions splitOptions) { _state = new State { @@ -263,7 +263,7 @@ internal SplitResult(Utf8Span source, Rune searchRune, StringSplitOptions splitO }; } - internal SplitResult(Utf8Span source, Utf8Span searchTerm, StringSplitOptions splitOptions) + internal SplitResult(Utf8Span source, Utf8Span searchTerm, Utf8StringSplitOptions splitOptions) { _state = new State { @@ -348,7 +348,7 @@ public void Deconstruct(out Utf8Span item1, out Utf8Span item2, out Utf8Span ite private void TrimIfNeeded(ref Utf8Span span) { - if ((_state.SplitOptions & StringSplitOptions.TrimEntries) != 0) + if ((_state.SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) { span = span.Trim(); } @@ -357,7 +357,7 @@ private void TrimIfNeeded(ref Utf8Span span) [StructLayout(LayoutKind.Auto)] public ref struct Enumerator { - private const StringSplitOptions HALT_ENUMERATION = (StringSplitOptions)int.MinValue; + private const Utf8StringSplitOptions HALT_ENUMERATION = (Utf8StringSplitOptions)int.MinValue; private Utf8Span _current; private State _state; @@ -384,7 +384,7 @@ public bool MoveNext() // bit of data after the final occurrence of the search term. We'll also set a flag saying that we've // completed enumeration. - if (_current.IsEmpty && (_state.SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0) + if (_current.IsEmpty && (_state.SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0) { return false; } @@ -406,7 +406,7 @@ private ref struct State // fully mutable internal Utf8Span RemainingSearchSpace; internal int SearchRune; // -1 if not specified, takes less space than "Rune?" internal Utf8Span SearchTerm; - internal StringSplitOptions SplitOptions; + internal Utf8StringSplitOptions SplitOptions; // Returns 'true' if a match was found, 'false' otherwise. internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstItem, out Utf8Span remainder) @@ -448,7 +448,7 @@ internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstI firstItem = searchSpan; - if ((SplitOptions & StringSplitOptions.TrimEntries) != 0) + if ((SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) { firstItem = firstItem.Trim(); } @@ -463,14 +463,14 @@ internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstI firstItem = searchSpan[..matchRange.Start]; // TODO_UTF8STRING: Could use unsafe slicing as optimization remainder = searchSpan[matchRange.End..]; // TODO_UTF8STRING: Could use unsafe slicing as optimization - if ((SplitOptions & StringSplitOptions.TrimEntries) != 0) + if ((SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) { firstItem = firstItem.Trim(); } // If we're asked to remove empty entries, loop until there's a real value in 'firstItem'. - if ((SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0 && firstItem.IsEmpty) + if ((SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0 && firstItem.IsEmpty) { searchSpan = ref remainder; continue; diff --git a/src/libraries/System.Private.CoreLib/src/System/Utf8String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/Utf8String.Manipulation.cs index b0df3ceba042f0..ac66e0e0668a38 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Utf8String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Utf8String.Manipulation.cs @@ -21,16 +21,16 @@ public sealed partial class Utf8String { [StackTraceHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void CheckSplitOptions(StringSplitOptions options) + internal static void CheckSplitOptions(Utf8StringSplitOptions options) { - if ((uint)options > (uint)(StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + if ((uint)options > (uint)(Utf8StringSplitOptions.RemoveEmptyEntries | Utf8StringSplitOptions.TrimEntries)) { CheckSplitOptions_Throw(options); } } [StackTraceHidden] - private static void CheckSplitOptions_Throw(StringSplitOptions options) + private static void CheckSplitOptions_Throw(Utf8StringSplitOptions options) { throw new ArgumentOutOfRangeException( paramName: nameof(options), @@ -134,7 +134,7 @@ internal Utf8String Substring(int startIndex, int length) return InternalSubstring(startIndex, length); } - public SplitResult Split(char separator, StringSplitOptions options = StringSplitOptions.None) + public SplitResult Split(char separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) { if (!Rune.TryCreate(separator, out Rune rune)) { @@ -148,14 +148,14 @@ public SplitResult Split(char separator, StringSplitOptions options = StringSpli return new SplitResult(this, rune, options); } - public SplitResult Split(Rune separator, StringSplitOptions options = StringSplitOptions.None) + public SplitResult Split(Rune separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) { CheckSplitOptions(options); return new SplitResult(this, separator, options); } - public SplitResult Split(Utf8String separator, StringSplitOptions options = StringSplitOptions.None) + public SplitResult Split(Utf8String separator, Utf8StringSplitOptions options = Utf8StringSplitOptions.None) { if (IsNullOrEmpty(separator)) { @@ -372,7 +372,7 @@ private void ValidateStartIndexAndLength(int startIndex, int length) { private readonly State _state; - internal SplitResult(Utf8String source, Rune searchRune, StringSplitOptions splitOptions) + internal SplitResult(Utf8String source, Rune searchRune, Utf8StringSplitOptions splitOptions) { _state = new State { @@ -384,7 +384,7 @@ internal SplitResult(Utf8String source, Rune searchRune, StringSplitOptions spli }; } - internal SplitResult(Utf8String source, Utf8String searchTerm, StringSplitOptions splitOptions) + internal SplitResult(Utf8String source, Utf8String searchTerm, Utf8StringSplitOptions splitOptions) { _state = new State { @@ -526,7 +526,7 @@ public void Deconstruct(out Utf8String? item1, out Utf8String? item2, out Utf8St private unsafe Utf8String? TrimIfNeeded(Utf8Span span) { - if ((_state.SplitOptions & StringSplitOptions.TrimEntries) != 0) + if ((_state.SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) { span = span.Trim(); } @@ -541,7 +541,7 @@ public void Deconstruct(out Utf8String? item1, out Utf8String? item2, out Utf8St { // normalize empty spans to null if needed, otherwise normalize to Utf8String.Empty - if ((_state.SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0 + if ((_state.SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0 || Unsafe.AreSame(ref span.DangerousGetMutableReference(), ref Unsafe.AsRef(null))) { return null; @@ -566,7 +566,7 @@ public void Deconstruct(out Utf8String? item1, out Utf8String? item2, out Utf8St [StructLayout(LayoutKind.Auto)] public struct Enumerator : IEnumerator { - private const StringSplitOptions HALT_ENUMERATION = (StringSplitOptions)int.MinValue; + private const Utf8StringSplitOptions HALT_ENUMERATION = (Utf8StringSplitOptions)int.MinValue; private Utf8String? _current; private State _state; @@ -595,7 +595,7 @@ public bool MoveNext() // bit of data after the final occurrence of the search term. We'll also set a flag saying that we've // completed enumeration. - if (firstItem.IsEmpty && (_state.SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0) + if (firstItem.IsEmpty && (_state.SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0) { return false; } @@ -630,7 +630,7 @@ private struct State // fully mutable internal int OffsetAtWhichToContinueSearch; internal int SearchRune; // -1 if not specified, takes less space than "Rune?" internal Utf8String? SearchTerm; - internal StringSplitOptions SplitOptions; + internal Utf8StringSplitOptions SplitOptions; // Returns 'true' if a match was found, 'false' otherwise. internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstItem, out Utf8Span remainder) @@ -673,7 +673,7 @@ internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstI firstItem = searchSpan; - if ((SplitOptions & StringSplitOptions.TrimEntries) != 0) + if ((SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) { firstItem = firstItem.Trim(); } @@ -688,14 +688,14 @@ internal readonly bool DeconstructHelper(in Utf8Span source, out Utf8Span firstI firstItem = searchSpan[..matchRange.Start]; // TODO_UTF8STRING: Could use unsafe slicing as optimization remainder = searchSpan[matchRange.End..]; // TODO_UTF8STRING: Could use unsafe slicing as optimization - if ((SplitOptions & StringSplitOptions.TrimEntries) != 0) + if ((SplitOptions & Utf8StringSplitOptions.TrimEntries) != 0) { firstItem = firstItem.Trim(); } // If we're asked to remove empty entries, loop until there's a real value in 'firstItem'. - if ((SplitOptions & StringSplitOptions.RemoveEmptyEntries) != 0 && firstItem.IsEmpty) + if ((SplitOptions & Utf8StringSplitOptions.RemoveEmptyEntries) != 0 && firstItem.IsEmpty) { searchSpan = ref remainder; continue; diff --git a/src/libraries/System.Private.CoreLib/src/System/Utf8StringSplitOptions.cs b/src/libraries/System.Private.CoreLib/src/System/Utf8StringSplitOptions.cs new file mode 100644 index 00000000000000..29a00a253fe98d --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Utf8StringSplitOptions.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System +{ + // TODO_UTF8STRING: This should be removed and we should use regular StringSplitOptions + // once a 'TrimEntries' flag gets added to the type. + + [Flags] + public enum Utf8StringSplitOptions + { + None = 0, + RemoveEmptyEntries = 1, + TrimEntries = 2 + } +} diff --git a/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs b/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs index 03d5b84be1b7e9..4a906ba12ca443 100644 --- a/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs +++ b/src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs @@ -122,9 +122,9 @@ public Utf8String(string value) { } public System.Utf8String Normalize(System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; } public static bool operator !=(System.Utf8String? left, System.Utf8String? right) { throw null; } public static bool operator ==(System.Utf8String? left, System.Utf8String? right) { throw null; } - public SplitResult Split(char separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } - public SplitResult Split(System.Text.Rune separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } - public SplitResult Split(System.Utf8String separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } + public SplitResult Split(char separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } + public SplitResult Split(System.Text.Rune separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } + public SplitResult Split(System.Utf8String separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } public SplitOnResult SplitOn(char separator) { throw null; } public SplitOnResult SplitOn(char separator, System.StringComparison comparisonType) { throw null; } public SplitOnResult SplitOn(System.Text.Rune separator) { throw null; } @@ -262,6 +262,13 @@ public readonly struct SplitOnResult public void Deconstruct(out System.Utf8String before, out System.Utf8String? after) { throw null; } } } + [System.FlagsAttribute] + public enum Utf8StringSplitOptions + { + None = 0, + RemoveEmptyEntries = 1, + TrimEntries = 2 + } } namespace System.Net.Http { @@ -320,9 +327,9 @@ public readonly ref partial struct Utf8Span public static bool operator !=(System.Text.Utf8Span left, System.Text.Utf8Span right) { throw null; } public static bool operator ==(System.Text.Utf8Span left, System.Text.Utf8Span right) { throw null; } public System.Text.Utf8Span this[System.Range range] { get { throw null; } } - public SplitResult Split(char separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } - public SplitResult Split(System.Text.Rune separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } - public SplitResult Split(System.Text.Utf8Span separator, System.StringSplitOptions options = System.StringSplitOptions.None) { throw null; } + public SplitResult Split(char separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } + public SplitResult Split(System.Text.Rune separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } + public SplitResult Split(System.Text.Utf8Span separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; } public SplitOnResult SplitOn(char separator) { throw null; } public SplitOnResult SplitOn(char separator, System.StringComparison comparisonType) { throw null; } public SplitOnResult SplitOn(System.Text.Rune separator) { throw null; } diff --git a/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj b/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj index 702cc2e600d8e5..8c12e127139639 100644 --- a/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj +++ b/src/libraries/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj @@ -76,6 +76,8 @@ Link="System\Text\Unicode\Utf8Utility.Transcoding.cs" /> + actualRanges = new List(); - foreach (Utf8Span slice in splitAction(span, StringSplitOptions.None)) + foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.None)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } @@ -181,7 +181,7 @@ private static void SplitTest_Common(ustring source, Utf8SpanSplitDelegate split // Next, run the split with empty entries removed actualRanges = new List(); - foreach (Utf8Span slice in splitAction(span, StringSplitOptions.RemoveEmptyEntries)) + foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.RemoveEmptyEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } @@ -197,7 +197,7 @@ private static void SplitTest_Common(ustring source, Utf8SpanSplitDelegate split } actualRanges = new List(); - foreach (Utf8Span slice in splitAction(span, StringSplitOptions.TrimEntries)) + foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.TrimEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } @@ -207,7 +207,7 @@ private static void SplitTest_Common(ustring source, Utf8SpanSplitDelegate split // Finally, run the split both trimmed and with empty entries removed actualRanges = new List(); - foreach (Utf8Span slice in splitAction(span, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)) + foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.TrimEntries | Utf8StringSplitOptions.RemoveEmptyEntries)) { actualRanges.Add(GetRangeOfSubspan(span, slice)); } diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8StringTests.Manipulation.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8StringTests.Manipulation.cs index d6b14c1f4de90d..e80e611241ee0f 100644 --- a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8StringTests.Manipulation.cs +++ b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8StringTests.Manipulation.cs @@ -14,7 +14,7 @@ namespace System.Tests { public unsafe partial class Utf8StringTests { - private delegate Utf8String.SplitResult Utf8StringSplitDelegate(Utf8String ustr, StringSplitOptions splitOptions); + private delegate Utf8String.SplitResult Utf8StringSplitDelegate(Utf8String ustr, Utf8StringSplitOptions splitOptions); [Fact] public static void Split_Utf8StringSeparator_WithNullOrEmptySeparator_Throws() @@ -98,13 +98,13 @@ public static void Split_Deconstruct_WithOptions() // into the original buffer), not deep (textual) equality checks. { - (Utf8String a, Utf8String b) = ustr.Split(',', StringSplitOptions.RemoveEmptyEntries); + (Utf8String a, Utf8String b) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries); Assert.Equal(u8("a"), a); Assert.Equal(u8(" , b, c,, d, e"), b); } { - (Utf8String a, Utf8String x, Utf8String b, Utf8String c, Utf8String d, Utf8String e) = ustr.Split(',', StringSplitOptions.RemoveEmptyEntries); + (Utf8String a, Utf8String x, Utf8String b, Utf8String c, Utf8String d, Utf8String e) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries); Assert.Equal(u8("a"), a); // "a" Assert.Equal(u8(" "), x); // " " Assert.Equal(u8(" b"), b); // " b" @@ -114,7 +114,7 @@ public static void Split_Deconstruct_WithOptions() } { - (Utf8String a, Utf8String b, Utf8String c, Utf8String d, Utf8String e, Utf8String f, Utf8String g, Utf8String h) = ustr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + (Utf8String a, Utf8String b, Utf8String c, Utf8String d, Utf8String e, Utf8String f, Utf8String g, Utf8String h) = ustr.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries | Utf8StringSplitOptions.TrimEntries); Assert.Equal(u8("a"), a); Assert.Equal(u8("b"), b); Assert.Equal(u8("c"), c); @@ -150,25 +150,25 @@ private static void SplitTest_Common(Utf8String source, Utf8StringSplitDelegate Assert.Equal( expected: expectedRanges.Select(range => source[range]), - actual: splitAction(source, StringSplitOptions.None)); + actual: splitAction(source, Utf8StringSplitOptions.None)); // Next, run the split with empty entries removed Assert.Equal( expected: expectedRanges.Select(range => source[range]).Where(ustr => ustr.Length != 0), - actual: splitAction(source, StringSplitOptions.RemoveEmptyEntries)); + actual: splitAction(source, Utf8StringSplitOptions.RemoveEmptyEntries)); // Next, run the split with results trimmed (but allowing empty results) Assert.Equal( expected: expectedRanges.Select(range => source[range].Trim()), - actual: splitAction(source, StringSplitOptions.TrimEntries)); + actual: splitAction(source, Utf8StringSplitOptions.TrimEntries)); // Finally, run the split both trimmed and with empty entries removed Assert.Equal( expected: expectedRanges.Select(range => source[range].Trim()).Where(ustr => ustr.Length != 0), - actual: splitAction(source, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)); + actual: splitAction(source, Utf8StringSplitOptions.TrimEntries | Utf8StringSplitOptions.RemoveEmptyEntries)); } public static IEnumerable Trim_TestData() => Utf8SpanTests.Trim_TestData(); From bdba61249501c5288cb4888296dc6ff934d9c097 Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Fri, 12 Jun 2020 15:38:31 -0700 Subject: [PATCH 6/7] Add StringSplitOptions devdoc --- .../src/System/StringSplitOptions.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/StringSplitOptions.cs b/src/libraries/System.Private.CoreLib/src/System/StringSplitOptions.cs index f71b040def265b..7ddac3edf3616e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/StringSplitOptions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/StringSplitOptions.cs @@ -4,11 +4,45 @@ namespace System { + // Examples of StringSplitOptions combinations: + // + // string str = "a,,b, c, , d ,e"; + // + // string[] split = str.Split(',', StringSplitOptions.None); + // split := [ "a", "", "b", " c", " ", " d ", "e" ] + // + // string[] split = str.Split(',', StringSplitOptions.RemoveEmptyEntries); + // split := [ "a", "b", " c", " ", " d ", "e" ] + // + // string[] split = str.Split(',', StringSplitOptions.TrimEntries); + // split := [ "a", "", "b", "c", "", "d", "e" ] + // + // string[] split = str.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + // split := [ "a", "b", "c", "d", "e" ] + + /// + /// Specifies how the results should be transformed when splitting a string into substrings. + /// [Flags] public enum StringSplitOptions { + /// + /// Do not transform the results. This is the default behavior. + /// None = 0, + + /// + /// Remove empty (zero-length) substrings from the result. + /// + /// + /// If and are specified together, + /// then substrings that consist only of whitespace characters are also removed from the result. + /// RemoveEmptyEntries = 1, + + /// + /// Trim whitespace from each substring in the result. + /// TrimEntries = 2 } } From 5b07dbcf687df27f33d2a76d5dbcc33f17571a02 Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Fri, 12 Jun 2020 16:32:31 -0700 Subject: [PATCH 7/7] Fix off-by-one errors in SplitWithPostProcessing --- .../System.Private.CoreLib/src/System/String.Manipulation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index d2f677633c875e..00ff97b8455e51 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -1511,7 +1511,7 @@ private string[] SplitWithPostProcessing(ReadOnlySpan sepList, ReadOnlySpan // point. if ((options & StringSplitOptions.RemoveEmptyEntries) != 0) { - while (i < numReplaces - 1) + while (++i < numReplaces) { thisEntry = this.AsSpan(currIndex, sepList[i] - currIndex); if ((options & StringSplitOptions.TrimEntries) != 0) @@ -1522,7 +1522,7 @@ private string[] SplitWithPostProcessing(ReadOnlySpan sepList, ReadOnlySpan { break; // there's useful data here } - currIndex += (lengthList.IsEmpty ? defaultLength : lengthList[i]); + currIndex = sepList[i] + (lengthList.IsEmpty ? defaultLength : lengthList[i]); } } break;