Skip to content

Commit 3281440

Browse files
Fix .Append for StringSegment and add .Trim() methods for StringBuilder.
1 parent bd206c0 commit 3281440

2 files changed

Lines changed: 114 additions & 1 deletion

File tree

Source/StringBuilderExtensions.cs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,8 @@ public static StringBuilder AppendWithSeparator<T>(this StringBuilder target, ID
404404
/// <exception cref="ArgumentNullException">If the target is null.</exception>
405405
[ExcludeFromCodeCoverage] // Reason: component parts are tested.
406406
[SuppressMessage("Style", "IDE0046:Convert to conditional expression")]
407-
public static StringBuilder Append(this StringBuilder target, StringSegment value)
407+
// Need to have a different name than just Append because of the potential collision with string.
408+
public static StringBuilder AppendSegment(this StringBuilder target, StringSegment value)
408409
{
409410
if (target is null) throw new ArgumentNullException(nameof(target));
410411
if (!value.HasValue) return target;
@@ -430,4 +431,101 @@ public static StringBuilder Append(this StringBuilder target, ReadOnlySpan<char>
430431
}
431432
#endif
432433

434+
/// <summary>
435+
/// Trims whitespace from the end of the <see cref="StringBuilder"/>.
436+
/// </summary>
437+
/// <exception cref="ArgumentNullException">If <paramref name="sb"/> is null.</exception>
438+
public static StringBuilder TrimEnd(this StringBuilder sb)
439+
{
440+
if (sb is null) throw new ArgumentNullException(nameof(sb));
441+
Contract.EndContractBlock();
442+
443+
var last = sb.Length - 1;
444+
if (last == -1) return sb;
445+
446+
var end = last;
447+
while (end >= 0 && char.IsWhiteSpace(sb[end]))
448+
end--;
449+
450+
if (end < last)
451+
sb.Remove(end + 1, sb.Length - end - 1);
452+
453+
return sb;
454+
}
455+
456+
/// <summary>
457+
/// Trims the specified characters from the end of the <see cref="StringBuilder"/>.
458+
/// </summary>
459+
/// <exception cref="ArgumentNullException">If <paramref name="sb"/> is null.</exception>
460+
public static StringBuilder TrimEnd(this StringBuilder sb, ReadOnlySpan<char> characters)
461+
{
462+
if (sb is null) throw new ArgumentNullException(nameof(sb));
463+
Contract.EndContractBlock();
464+
465+
var last = sb.Length - 1;
466+
if (last == -1) return sb;
467+
468+
var end = last;
469+
while (end >= 0 && characters.IndexOf(sb[end]) != -1)
470+
end--;
471+
472+
if (end < last)
473+
sb.Remove(end + 1, sb.Length - end - 1);
474+
475+
return sb;
476+
}
477+
478+
/// <summary>
479+
/// Trims whitespace from the beginning of the <see cref="StringBuilder"/>.
480+
/// </summary>
481+
/// <exception cref="ArgumentNullException">If <paramref name="sb"/> is null.</exception>
482+
public static StringBuilder TrimStart(this StringBuilder sb)
483+
{
484+
if (sb is null) throw new ArgumentNullException(nameof(sb));
485+
Contract.EndContractBlock();
486+
487+
var start = 0;
488+
while (start < sb.Length && char.IsWhiteSpace(sb[start]))
489+
start++;
490+
491+
if (start > 0)
492+
sb.Remove(0, start);
493+
494+
return sb;
495+
}
496+
497+
498+
/// <summary>
499+
/// Trims the specified characters from the beginning of the <see cref="StringBuilder"/>.
500+
/// </summary>
501+
/// <exception cref="ArgumentNullException">If <paramref name="sb"/> is null.</exception>
502+
public static StringBuilder TrimStart(this StringBuilder sb, ReadOnlySpan<char> characters)
503+
{
504+
if (sb is null) throw new ArgumentNullException(nameof(sb));
505+
Contract.EndContractBlock();
506+
507+
var start = 0;
508+
while (start < sb.Length && characters.IndexOf(sb[start]) != -1)
509+
start++;
510+
511+
if (start > 0)
512+
sb.Remove(0, start);
513+
514+
return sb;
515+
}
516+
517+
/// <summary>
518+
/// Trims whitespace from the beginning and end of the <see cref="StringBuilder"/>.
519+
/// </summary>
520+
/// <exception cref="ArgumentNullException">If <paramref name="sb"/> is null.</exception>
521+
public static StringBuilder Trim(this StringBuilder sb)
522+
=> TrimEnd(sb).TrimStart();
523+
524+
525+
/// <summary>
526+
/// Trims the specified characters from the beginning and end of the <see cref="StringBuilder"/>.
527+
/// </summary>
528+
/// <exception cref="ArgumentNullException">If <paramref name="sb"/> is null.</exception>
529+
public static StringBuilder Trim(this StringBuilder sb, ReadOnlySpan<char> characters)
530+
=> TrimEnd(sb, characters).TrimStart(characters);
433531
}

Tests/StringBuilderTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,19 @@ public static void ToStringBuilderSeparatedString(string? source, string? separa
212212
else Assert.Equal(source + separator + suffix, sb.ToString());
213213
}
214214
}
215+
216+
// Unit Tests: Theories for StringBuilder.Trim() extension that have multiple values added.
217+
[Theory]
218+
// Expected, A, B, C, D
219+
[InlineData("Hello there.", "Hello", " ", "there.", "")]
220+
[InlineData("Hello there.", " Hello", " ", "there.", "")]
221+
[InlineData("Hello there.", " Hello", " ", "there.", " ")]
222+
[InlineData("Hello there.", "Hello", " ", "there.", " ")]
223+
public static void Trim(string expected, string a, string b, string c, string d)
224+
{
225+
var sb = new StringBuilder();
226+
sb.Append(a).Append(b).Append(c).Append(d).Trim();
227+
Assert.Equal(expected, sb.ToString());
228+
}
229+
215230
}

0 commit comments

Comments
 (0)