|
using System.Diagnostics.CodeAnalysis; |
|
using System.Globalization; |
|
using System.Text.RegularExpressions; |
|
|
|
/// <summary> |
|
/// String extensions including |
|
/// <list type="bullet"> |
|
/// <item>Mask</item> <item>Chop</item> <item>Trim(str)</item> |
|
/// <item>PascalCaseToWords</item> <item>RegexReplace</item> <item>Matches</item> |
|
/// <item>WithHtmlLineBreaks</item> <item>IsNullOrEmpty</item> <item></item> |
|
/// <item>WithWhiteSpaceRemoved</item> |
|
/// </list> |
|
/// </summary> |
|
public static class Strings |
|
{ |
|
/// <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>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> |
|
/// A synonym for <see cref="Chop"/>. |
|
/// 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? Truncate(this string? str, int maxlength) |
|
=> Chop(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> |
|
/// Abbreviation for <see cref="TextInfo.ToTitleCase"/> |
|
/// </summary> |
|
/// <param name="str"></param> |
|
/// <param name="cultureInfo"></param> |
|
/// <param name="keepAllCaps">If true, preserves the behaviour of <see cref="TextInfo.ToTitleCase"/> which |
|
/// does not title-case an all-caps word (imagining it to be an acronym). If false, then also title-case |
|
/// allcaps words</param> |
|
/// <returns> |
|
/// <c>cultureInfo.TextInfo.ToTitleCase( keepAllCaps ? str : str.ToLower(cultureInfo))</c> |
|
/// if <paramref name="str"/> is not null. Otherwise <c>null</c> |
|
/// </returns> |
|
[return: NotNullIfNotNull("str")] |
|
public static string? ToTitleCase(this string? str, CultureInfo? cultureInfo = null, bool keepAllCaps = true) |
|
{ |
|
if (str is null) return str; |
|
cultureInfo ??= CultureInfo.CurrentCulture; |
|
return cultureInfo.TextInfo.ToTitleCase( keepAllCaps ? str : str.ToLower(cultureInfo)); |
|
} |
|
|
|
/// <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> |
|
[return: NotNullIfNotNull("str")]public static string? RegexReplace(this string? str, Regex regex, string replace) |
|
=> str is null ? null : regex.Replace(str, replace); |
|
|
|
/// <summary>Abbreviation for <c>regex.Replace(str, replace)</c></summary> |
|
[return: NotNullIfNotNull("str")]public static string? RegexReplace(this string? str, string pattern, string replace) |
|
=> str is null ? null : Regex.Replace(str, pattern,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) |
|
=> 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) |
|
=> 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); |
|
} |
|
|
|
/// <summary> |
|
/// Convert words to PascalCase wikiwords by capitalizing any letter that follows a space, and removing spaces. |
|
/// <list type="bullet"> |
|
/// <item>A sentence with words ->"ASentenceWithWords"</item> |
|
/// <item>Pascal1Case->"Pascal1 Case"</item> |
|
/// <item>PascalCaseB->"Pascal Case B"</item> |
|
/// </list> |
|
/// </summary> |
|
/// <param name="words"></param> |
|
/// <param name="space">the space character. Defaults to space, ' '.</param> |
|
/// <param name="alsoSpaces">The characters of this string will also be treated as space characters.</param> |
|
/// <returns>The transformed string</returns> |
|
[return:NotNullIfNotNull("words")]public static string? ToWikiWords(this string? words, char space=' ', string alsoSpaces="/?\\()[]{}<>") |
|
{ |
|
if (words is null) return words; |
|
words = words.TrimEnd(space); |
|
if (words.Length == 0) return words; |
|
foreach (char ch in alsoSpaces) { words = words.Replace(ch, space);} |
|
int next; |
|
while ((next = words.IndexOf(space)) >= 0 && next < words.Length-1) |
|
{ |
|
words = words.Substring(0, next) + |
|
char.ToUpper(words[next + 1]) + |
|
words.Substring(next + 2, words.Length - next-2); |
|
} |
|
|
|
return words.Replace(space.ToString(), ""); |
|
} |
|
} |