|
using System.Diagnostics.CodeAnalysis; |
|
using System.Globalization; |
|
using System.Text.RegularExpressions; |
|
|
|
namespace MyStudents.BL.Extensions; |
|
|
|
/// <summary> |
|
/// String extensions including |
|
/// <list type="bullet"> |
|
/// <item>Chop</item> |
|
/// <item>Trim(str)</item> |
|
/// <item>TrimEnd(str)</item> |
|
/// <item>PascalCaseToWords</item> |
|
/// <item>RegexReplace</item> |
|
/// <item>Matches</item> |
|
/// <item>WithHtmlLineBreaks</item> |
|
/// <item>IsNullOrEmpty</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> |
|
/// 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(), ""); |
|
} |
|
|
|
/// <summary> |
|
/// Does positional parameter replacement just like <see cref="string.Format"/> |
|
/// but accepts {namedParameter}, processed in order from left to right. |
|
/// Replaces the nth {namedParameter} string in <paramref name="format"/> with the nth |
|
/// item of <paramref name="args"/>. |
|
/// </summary> |
|
/// <param name="format"></param> |
|
/// <param name="args"></param> |
|
/// <example> |
|
/// <c>"{param2}_{p1}_{p1}_{param2}".Format(1,2)</c> and |
|
/// <c>"{1}_{0}_{0}_{1}".Format(1,2)</c> both return <c>"1_2_2_1"</c> |
|
/// </example> |
|
/// <returns><see cref="string.Format(string,object?[] args)"/></returns> |
|
[return: NotNullIfNotNull("format")] |
|
public static string? Format(this string format,params object?[] args) |
|
{ |
|
if (format is null) return null; |
|
for(int i = 0; i < args.Length; i++) |
|
{ |
|
var found = formatRex.Match(format); |
|
if(found.Success) |
|
format = format.Replace(found.Value,args[i]?.ToString()??string.Empty); |
|
} |
|
return format; |
|
} |
|
|
|
static Regex formatRex = new Regex(@"\{\w+\}"); |
|
} |