Skip to content
4 changes: 2 additions & 2 deletions src/libraries/System.Data.Common/src/System/Data/DataTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3688,12 +3688,12 @@ internal IndexField[] ParseSortString(string sortString)
IndexField[] indexDesc = Array.Empty<IndexField>();
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> tmpList = new List<string>(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);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
168 changes: 115 additions & 53 deletions src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1270,19 +1270,30 @@ private string[] SplitInternal(ReadOnlySpan<char> 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<string>();
// 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<string>() : 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<int>(stackalloc int[StackallocIntBufferSizeLimit]);
Expand All @@ -1293,12 +1304,13 @@ private string[] SplitInternal(ReadOnlySpan<char> 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();

Expand Down Expand Up @@ -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<char>), count, options);
}

if ((count == 0) || (omitEmptyEntries && Length == 0))
ShortCircuit:
if (count <= 1 || Length == 0)
{
return Array.Empty<string>();
}
// 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<string>() : 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<int>(stackalloc int[StackallocIntBufferSizeLimit]);
Expand All @@ -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();
Expand All @@ -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<string>()
: 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<int> sepList, ReadOnlySpan<int> lengthList, int defaultLength, int count)
// This function will not trim entries or special-case empty entries
private string[] SplitWithoutPostProcessing(ReadOnlySpan<int> sepList, ReadOnlySpan<int> lengthList, int defaultLength, int count)
{
Debug.Assert(count >= 2);

Expand Down Expand Up @@ -1442,8 +1474,8 @@ private string[] SplitKeepEmptyEntries(ReadOnlySpan<int> sepList, ReadOnlySpan<i
}


// This function will not keep the Empty string
private string[] SplitOmitEmptyEntries(ReadOnlySpan<int> sepList, ReadOnlySpan<int> lengthList, int defaultLength, int count)
// This function may trim entries or omit empty entries
private string[] SplitWithPostProcessing(ReadOnlySpan<int> sepList, ReadOnlySpan<int> lengthList, int defaultLength, int count, StringSplitOptions options)
{
Debug.Assert(count >= 2);

Expand All @@ -1458,19 +1490,40 @@ private string[] SplitOmitEmptyEntries(ReadOnlySpan<int> sepList, ReadOnlySpan<i
int currIndex = 0;
int arrIndex = 0;

for (int i = 0; i < numReplaces && currIndex < Length; i++)
ReadOnlySpan<char> 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)
{
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 = sepList[i] + (lengthList.IsEmpty ? defaultLength : lengthList[i]);
}
}
break;
}
Expand All @@ -1479,22 +1532,20 @@ private string[] SplitOmitEmptyEntries(ReadOnlySpan<int> sepList, ReadOnlySpan<i
// we must have at least one slot left to fill in the last string.
Debug.Assert(arrIndex < maxItems);

// Handle the last string at the end of the array if there is one.
if (currIndex < Length)
// Handle the last substring at the end of the array
// (could be empty if separator appeared at the end of the input string)
thisEntry = this.AsSpan(currIndex);
if ((options & StringSplitOptions.TrimEntries) != 0)
{
splitStrings[arrIndex++] = Substring(currIndex);
thisEntry = thisEntry.Trim();
}

string[] stringArray = splitStrings;
if (arrIndex != maxItems)
if (!thisEntry.IsEmpty || ((options & StringSplitOptions.RemoveEmptyEntries) == 0))
{
stringArray = new string[arrIndex];
for (int j = 0; j < arrIndex; j++)
{
stringArray[j] = splitStrings[j];
}
splitStrings[arrIndex++] = thisEntry.ToString();
}
return stringArray;

Array.Resize(ref splitStrings, arrIndex);
return splitStrings;
}

/// <summary>
Expand Down Expand Up @@ -1639,6 +1690,17 @@ private void MakeSeparatorList(string?[] separators, ref ValueListBuilder<int> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +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" ]

/// <summary>
/// Specifies how the results should be transformed when splitting a string into substrings.
/// </summary>
[Flags]
public enum StringSplitOptions
{
/// <summary>
/// Do not transform the results. This is the default behavior.
/// </summary>
None = 0,
RemoveEmptyEntries = 1

/// <summary>
/// Remove empty (zero-length) substrings from the result.
/// </summary>
/// <remarks>
/// If <see cref="RemoveEmptyEntries"/> and <see cref="TrimEntries"/> are specified together,
/// then substrings that consist only of whitespace characters are also removed from the result.
/// </remarks>
RemoveEmptyEntries = 1,

/// <summary>
/// Trim whitespace from each substring in the result.
/// </summary>
TrimEntries = 2
}
}
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3546,6 +3546,7 @@ public enum StringSplitOptions
{
None = 0,
RemoveEmptyEntries = 1,
TrimEntries = 2,
}
public partial class SystemException : System.Exception
{
Expand Down
Loading