Last active
February 3, 2024 13:16
-
-
Save chrisfcarroll/2911ac14ccdf028f6cc2ccb2607f82b3 to your computer and use it in GitHub Desktop.
C# string.MaskStart(), .MaskEnd(), string.Chop() string.ToWikiWords(), PascalCaseToWords() string.WithWhiteSpaceRemoved() and similar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics.CodeAnalysis; | |
using System.Linq; | |
using System.Text.RegularExpressions; | |
public static class StringExtensions | |
{ | |
/// <summary>Trim one occurrence of <paramref name="terminator"/> string from | |
/// the end of <paramref name="str"/>, if there is one. | |
/// </summary> | |
/// <param name="str"></param> | |
/// <param name="terminator"></param> | |
/// <returns> | |
/// <c>str.Substring(0, str.Length - terminator.Length)</c> if <paramref name="str"/> | |
/// ends with <paramref name="terminator"/>, or else returns <paramref name="str"/> if not. | |
/// </returns> | |
[return: NotNullIfNotNull("str")]public static string? TrimEnd(this string? str, string terminator) | |
{ | |
if (terminator is null || str is null | |
|| str.Length == 0 || terminator.Length == 0 | |
|| terminator.Length > str.Length) return str; | |
if (str.EndsWith(terminator)) return str.Substring(0, str.Length - terminator.Length); | |
return str; | |
} | |
/// <summary>Trim one occurrence of <paramref name="terminator"/> string from | |
/// the start of <paramref name="str"/>, if there is one. | |
/// </summary> | |
/// <param name="str"></param> | |
/// <param name="terminator"></param> | |
/// <returns> | |
/// <c>str.Substring(terminator.Length, str.Length - terminator.Length)</c> | |
/// if <paramref name="str"/> starts with <paramref name="terminator"/>, | |
/// or else returns <paramref name="str"/> if not. | |
/// </returns> | |
[return: NotNullIfNotNull("str")]public static string? TrimStart(this string str, string terminator) | |
{ | |
if (terminator is null || str is null | |
|| str.Length == 0 || terminator.Length == 0 | |
|| terminator.Length > str.Length) return str; | |
if (str.StartsWith(terminator)) | |
return str.Substring(terminator.Length, str.Length - terminator.Length); | |
return str; | |
} | |
[return: NotNullIfNotNull("str")]public static string? Trim(this string str, string terminator) | |
=> str.TrimEnd(terminator).TrimStart(terminator); | |
/// <summary>Abbreviation for | |
/// <c>string.Join(delim, values.Where(v => v is not null));</c></summary> | |
/// <returns>The joined string</returns> | |
public static string JoinDropNulls<T>(this IEnumerable<T> values, string delim) | |
=> string.Join(delim, values.Where(v => v is not null)); | |
/// <summary>Abbreviation for | |
/// <c>string.Join(delim, values.Where(v => v is not null));</c></summary> | |
/// <returns>The joined string</returns> | |
public static string JoinDropNulls<T>(this IEnumerable<T> values, char delim = ',') | |
=> string.Join(delim, values.Where(v => v is not null)); | |
/// <summary>Chop the string to maximum length of <paramref name="maxlength"/></summary> | |
/// <param name="str"></param> | |
/// <param name="maxlength"></param> | |
/// <returns>the chopped string. If the string is shorter than <paramref name="maxlength"/> | |
/// then the whole string is returned.</returns> | |
[return: NotNullIfNotNull("str")]public static string? Chop(this string? str, int maxlength) | |
{ | |
if (str is null) return null; | |
if (str.Length <= maxlength) return str; | |
return str[..maxlength]; | |
} | |
/// <summary>Replaces newline characters — <c>\n</c> or <see cref="Environment.NewLine"/> — | |
/// with <c>>br/></c></summary> | |
/// <param name="str"></param> | |
/// <returns>the altered string</returns> | |
public static string WithHtmlLineBreaks(this string str) | |
=> str.Replace("\n", "<br/>").Replace(Environment.NewLine,"<br/>"); | |
/// <summary>Abbreviation for <c>str.LastIndexOf(sought, StringComparison.Ordinal)</c></summary> | |
public static int LastIndexOfOrdinal(this string str, string sought) | |
=> str.LastIndexOf(sought, StringComparison.Ordinal); | |
/// <summary>Abbreviation for <see cref="string.IsNullOrEmpty"/></summary> | |
public static bool IsNullOrEmpty(this string str) => string.IsNullOrEmpty(str); | |
static readonly Regex RegexlowerUpper = new Regex(@"(\p{Ll}|\d)(\p{Lu})"); | |
static readonly Regex RegexWord_Word = new Regex(@"(\S)_(\S)"); | |
static readonly Regex Regex_Word = new Regex(@"_(\S)"); | |
static readonly Regex RegexWord_ = new Regex(@"(\S)_"); | |
/// <summary> | |
/// Convert PascalCase string to words, e.g. : | |
/// <list type="bullet"> | |
/// <item>PascalCase->"Pascal Case"</item> | |
/// <item>Pascal1Case->"Pascal1 Case"</item> | |
/// <item>PascalCaseB->"Pascal Case B"</item> | |
/// </list> | |
/// </summary> | |
/// <param name="wikiWordString"></param> | |
/// <returns>The transformed string</returns> | |
[return:NotNullIfNotNull("wikiWordString")]public static string? PascalCaseToWords(this string? wikiWordString) | |
=> wikiWordString == null ? null :RegexlowerUpper.Replace(wikiWordString, "$1 $2"); | |
/// <summary>Convert Snake_Cased_Strings to Words - with - Hyphens | |
/// <list type="bullet"> | |
/// <item>Snake_Cased_Strings->"Snake - Cased - Strings"</item> | |
/// <item>Snake _ Cased->"Snake - Cased"</item> | |
/// <item>Snake_Cased_ ->"Snake - Cased -"</item> | |
/// </list> | |
/// </summary> | |
/// <param name="snakeCasedString"></param> | |
/// <returns>The transformed string</returns> | |
[return:NotNullIfNotNull("snakeCasedString")]public static string? UnderscoreToHyphenedWords(this string? snakeCasedString) | |
=> snakeCasedString?.RegexReplace(RegexWord_Word, "$1 - $2") | |
.RegexReplace(Regex_Word, "- $1") | |
.RegexReplace(RegexWord_, "$1 -") | |
.Replace("_","-"); | |
/// <summary> | |
/// Combines both <see cref="PascalCaseToWords"/> and <see cref="UnderscoreToHyphenedWords"/> | |
/// <list type="bullet"> | |
/// <item>PascalCased_Snake->"Pascal Cased - Snake"</item> | |
/// <item>PascalCased _ Snake->"Pascal Cased - Snake"</item> | |
/// <item>PascalCased_->"Pascal Cased -"</item> | |
/// </list> | |
/// </summary> | |
/// <param name="str"></param> | |
/// <returns>the transformed string</returns> | |
public static string PascalSnakeToHyphenedWords(this string str) | |
=> str.UnderscoreToHyphenedWords().PascalCaseToWords(); | |
/// <summary> | |
/// Combines both <see cref="PascalCaseToWords"/> and <see cref="UnderscoreToHyphenedWords"/> | |
/// to an enum's ToString() value | |
/// <list type="bullet"> | |
/// <item>PascalCased_Snake->"Pascal Cased - Snake"</item> | |
/// <item>PascalCased _ Snake->"Pascal Cased - Snake"</item> | |
/// <item>PascalCased_->"Pascal Cased -"</item> | |
/// </list> | |
/// </summary> | |
/// <param name="value">an enum value of type <typeparamref name="T"/></param> | |
/// <returns>the transformed string</returns> | |
public static string PascalSnakeToHyphenedWords<T>(this T value) where T : struct,Enum | |
=> value.ToString().UnderscoreToHyphenedWords().PascalCaseToWords(); | |
/// <summary>Abbreviation for <c>regex.Replace(str, replace)</c></summary> | |
public static string RegexReplace(this string str, Regex regex, string replace) | |
=> regex.Replace(str, replace); | |
/// <summary>Removes spaces, newlines (<c>\n</c>)and tabs (<c>\t</c>) from <paramref name="str"/> | |
/// and returns the results</summary> | |
/// <param name="str"></param> | |
/// <returns></returns> | |
[return: NotNullIfNotNull("str")]public static string? WithWhiteSpaceRemoved(this string str) | |
{ | |
return str?.Replace(" ", "").Replace("\n", "").Replace("\r", "").Replace("\t", ""); | |
} | |
/// <summary>Abbreviation for <c>Regex.IsMatch(@this, pattern, options)</c></summary> | |
/// <param name="str">The input string</param> | |
/// <param name="pattern">the regex pattern to match against</param> | |
/// <param name="options">the <see cref="RegexOptions"/> to use</param> | |
/// <returns><c>true</c> if <paramref name="str"/> matches the pattern with the options</returns> | |
public static bool Matches(this string str, string pattern, RegexOptions options = RegexOptions.None) | |
{ | |
return Regex.IsMatch(str, pattern, options); | |
} | |
/// <summary>Masks – that is, overwrites with the mask character – the beginning of a string. | |
/// </summary> | |
/// <example> | |
/// <c>"hello there".MaskToFirst('l')</c> => <c>"***lo there"</c> | |
/// </example> | |
/// <param name="str"></param> | |
/// <param name="marker">The phrase to mask as far as.</param> | |
/// <param name="maskChar"></param> | |
/// <param name="inclusive">If this is 0, then the marker phrase itself is not masked. | |
/// If it is 1, then the first character of marker is masked. | |
/// Any other value (positive or negative) is an offset from the marker for how far to mask | |
/// from the first character of marker.</param> | |
/// <seealso cref="MaskEnd"/> | |
/// <seealso cref="MaskStart"/> | |
/// <returns>The masked string.</returns> | |
public static string MaskToFirst(this string str, string marker, char maskChar = '*', int inclusive=1) | |
=> MaskStart(str, str.LastIndexOfOrdinal(marker)+inclusive, maskChar); | |
/// <summary>Masks – that is, overwrites with the mask character – the beginning of a string. | |
/// </summary> | |
/// <example> | |
/// <c>"hello there".MaskToFirst('l')</c> => <c>"***lo there"</c> | |
/// </example> | |
/// <param name="str"></param> | |
/// <param name="marker">The character to mask as far as.</param> | |
/// <param name="maskChar"></param> | |
/// <param name="offset">If this is 0, then the marker phrase itself is masked. | |
/// If it is 1, then the first character of the marker phrase is not masked. | |
/// Any other value (positive or negative) is an offset from the first character of marker | |
/// for how far to mask.</param> | |
/// <seealso cref="MaskEnd"/> | |
/// <seealso cref="MaskStart"/> | |
/// <returns>The masked string.</returns> | |
public static string MaskFromLast(this string str, string marker, char maskChar = '*', int offset=0) | |
{ | |
var index = str.LastIndexOfOrdinal(marker); | |
return index == -1 | |
? MaskEnd(str, offset, maskChar) | |
: MaskEnd(str, str.Length-index + offset, maskChar); | |
} | |
/// <summary>Masks – that is, overwrites with the mask character – the beginning of a string. | |
/// </summary> | |
/// <example> | |
/// <c>"hello there".MaskStart(3)</c> => <c>"***lo there"</c> | |
/// <c>"hello there".MaskStart(-3)</c> => <c>"********ere"</c> | |
/// </example> | |
/// <param name="str"></param> | |
/// <param name="masked">How many characters to mask. If masked is negative it is interpreted | |
/// as “How many characters to leave unmasked”</param> | |
/// <param name="maskChar"></param> | |
/// <seealso cref="MaskEnd"/> | |
/// <returns>The masked string. | |
/// If <paramref name="str"/> is shorter than <paramref name="masked"/> then only a | |
/// string of <paramref name="maskChar"/> of length <paramref name="masked"/> is returned | |
/// If <paramref name="masked"/> is longer than <c>me.Length</c> then only a | |
/// string of <paramref name="maskChar"/> of length <c>me.Length</c> is returned. | |
/// If <paramref name="masked"/>==0 then the original string is returned. | |
/// </returns> | |
public static string MaskStart(this string str, int masked=4, char maskChar = '*') | |
{ | |
if (string.IsNullOrEmpty(str)) return str; | |
if (masked==0) return str; | |
var length = str.Length; | |
if (masked >= length) return new string(maskChar,length); | |
int unmasked; | |
if (masked < 0) | |
{ | |
unmasked = -masked; | |
if (unmasked > length) unmasked = length; | |
masked = length - unmasked; | |
} | |
else | |
{ | |
unmasked = length - masked; | |
} | |
return str.Substring(masked, unmasked).PadLeft(str.Length, maskChar); | |
} | |
/// <summary>Masks – that is, overwrites with the mask character – the end of a string. | |
/// </summary> | |
/// <example> | |
/// <c>"hello there".MaskEnd(3)</c> => <c>"Hello th***"</c> | |
/// <c>"hello there".MaskEnd(-3)</c> => <c>"Hel********"</c> | |
/// </example> | |
/// <param name="str"></param> | |
/// <param name="masked">How many characters to mask. If masked is negative it is interpreted | |
/// as “How many characters to leave unmasked”</param> | |
/// <param name="maskChar"></param> | |
/// <seealso cref="MaskStart"/> | |
/// <returns>The masked string. | |
/// If <paramref name="str"/> is shorter than <paramref name="masked"/> then only a | |
/// string of <paramref name="maskChar"/> of length <paramref name="masked"/> is returned | |
/// If <paramref name="masked"/> is longer than <c>me.Length</c> then only a | |
/// string of <paramref name="maskChar"/> of length <c>me.Length</c> is returned. | |
/// If <paramref name="masked"/>==0 then the original string is returned. | |
/// </returns> | |
public static string MaskEnd(this string str, int masked=4, char maskChar = '*') | |
{ | |
if (string.IsNullOrEmpty(str)) return str; | |
if (masked==0) return str; | |
var length = str.Length; | |
if (masked >= length) return new string(maskChar,length); | |
int unmasked; | |
if (masked < 0) | |
{ | |
unmasked = -masked; | |
if (unmasked > length) unmasked = length; | |
} | |
else | |
{ | |
unmasked = length - masked; | |
} | |
return str.Substring(0, unmasked).PadRight(str.Length, maskChar); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment