From 0632560899912418d7e425f59a9d29514713a557 Mon Sep 17 00:00:00 2001 From: YoshiRulz Date: Sun, 17 May 2020 22:57:47 +1000 Subject: [PATCH] Split StringExtensions, fix and add to substring extensions --- .../Api/ExternalToolAttributes.cs | 2 +- .../inputAdapters/UDLRController.cs | 8 +- .../Extensions/NumericStringExtensions.cs | 173 ++++++++++++++ .../Extensions/StringExtensions.cs | 214 ++--------------- .../Extensions/SubstringExtensions.cs | 215 ++++++++++++++++++ 5 files changed, 407 insertions(+), 205 deletions(-) create mode 100644 src/BizHawk.Common/Extensions/NumericStringExtensions.cs create mode 100644 src/BizHawk.Common/Extensions/SubstringExtensions.cs diff --git a/src/BizHawk.Client.Common/Api/ExternalToolAttributes.cs b/src/BizHawk.Client.Common/Api/ExternalToolAttributes.cs index 3cc88d9d8a..098c6683d7 100644 --- a/src/BizHawk.Client.Common/Api/ExternalToolAttributes.cs +++ b/src/BizHawk.Client.Common/Api/ExternalToolAttributes.cs @@ -36,7 +36,7 @@ namespace BizHawk.Client.Common public RomWhitelist(CoreSystem system, params string[] romHashes) { if (system == CoreSystem.Null) throw new ArgumentException("there are no roms for the NULL system", nameof(system)); - if (!romHashes.All(StringExtensions.IsHex)) throw new ArgumentException("misformatted hash", nameof(romHashes)); + if (!romHashes.All(NumericStringExtensions.IsHex)) throw new ArgumentException("misformatted hash", nameof(romHashes)); _system = system; _romHashes = romHashes.ToList(); } diff --git a/src/BizHawk.Client.Common/inputAdapters/UDLRController.cs b/src/BizHawk.Client.Common/inputAdapters/UDLRController.cs index c5e1277c01..5d2ea6ea80 100644 --- a/src/BizHawk.Client.Common/inputAdapters/UDLRController.cs +++ b/src/BizHawk.Client.Common/inputAdapters/UDLRController.cs @@ -31,7 +31,7 @@ namespace BizHawk.Client.Common _unpresses.Remove(button); } - prefix = button.GetPrecedingString("Down"); + prefix = button.SubstringBeforeOrNull("Down"); string other = $"{prefix}Up"; if (Source.IsPressed(other)) { @@ -60,7 +60,7 @@ namespace BizHawk.Client.Common _unpresses.Remove(button); } - prefix = button.GetPrecedingString("Up"); + prefix = button.SubstringBeforeOrNull("Up"); string other = $"{prefix}Down"; if (Source.IsPressed(other)) { @@ -89,7 +89,7 @@ namespace BizHawk.Client.Common _unpresses.Remove(button); } - prefix = button.GetPrecedingString("Right"); + prefix = button.SubstringBeforeOrNull("Right"); string other = $"{prefix}Left"; if (Source.IsPressed(other)) { @@ -118,7 +118,7 @@ namespace BizHawk.Client.Common _unpresses.Remove(button); } - prefix = button.GetPrecedingString("Left"); + prefix = button.SubstringBeforeOrNull("Left"); string other = $"{prefix}Right"; if (Source.IsPressed(other)) { diff --git a/src/BizHawk.Common/Extensions/NumericStringExtensions.cs b/src/BizHawk.Common/Extensions/NumericStringExtensions.cs new file mode 100644 index 0000000000..e25359d2a4 --- /dev/null +++ b/src/BizHawk.Common/Extensions/NumericStringExtensions.cs @@ -0,0 +1,173 @@ +using System.Linq; +using System.Text; + +namespace BizHawk.Common.StringExtensions +{ + /// TODO how many of these methods can be replaced with int.TryParse or similar? --yoshi + public static class NumericStringExtensions + { + /// iff is either '0' or '1' + public static bool IsBinary(this char c) => c == '0' || c == '1'; + + /// iff is not and all chars of are either '0' or '1' + /// should exclude the prefix 0b + public static bool IsBinary(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsBinary); + + /// iff is '.' or a digit + /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. + public static bool IsFixedPoint(this char c) => IsUnsigned(c) || c == '.'; + + /// + /// iff is not ,
+ /// all chars of are '.' or a digit, and
+ /// contains at most 1 decimal separator '.' + ///
+ /// + /// should exclude the suffix M.
+ /// This method returning for some does not imply that float.TryParse will also return .
+ /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. + ///
+ public static bool IsFixedPoint(this string? str) => !string.IsNullOrWhiteSpace(str) && str.HowMany('.') <= 1 && str.All(IsFixedPoint); + + /// iff is '-', '.', or a digit + /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. + public static bool IsFloat(this char c) => c.IsFixedPoint() || c == '-'; + + /// iff is a hex digit ([0-9A-Fa-f]) + public static bool IsHex(this char c) => IsUnsigned(c) || 'A' <= char.ToUpperInvariant(c) && char.ToUpperInvariant(c) <= 'F'; + + /// iff is not and all chars of are hex digits ([0-9A-Fa-f]) + /// should exclude the prefix 0x + public static bool IsHex(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsHex); + + /// iff is '-' or a digit + public static bool IsSigned(this char c) => IsUnsigned(c) || c == '-'; + + /// iff is a digit + public static bool IsUnsigned(this char c) => char.IsDigit(c); + + /// iff is not and all chars of are digits + public static bool IsUnsigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsUnsigned); + + /// + /// A copy of with characters removed so that the whole thing passes IsBinary.
+ /// That is, all chars of the copy will be either '0' or '1'. + ///
+ public static string OnlyBinary(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsBinary)); + + /// + /// A copy of with characters removed so that the whole thing passes IsHex.
+ /// That is, all chars of the copy will be hex digits ([0-9A-F]). + ///
+ public static string OnlyHex(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsHex)).ToUpperInvariant(); + + /// + /// A copy of with characters removed so that the whole thing passes IsUnsigned.
+ /// That is, all chars of the copy will be digits. + ///
+ public static string OnlyUnsigned(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsUnsigned)); + +#pragma warning disable CS8602 + + /// + /// iff is not ,
+ /// the first char of is '-', '.', or a digit,
+ /// all subsequent chars of are '.' or a digit, and
+ /// contains at most 1 decimal separator '.' + ///
+ /// + /// should exclude the suffix f.
+ /// This method returning for some does not imply that float.TryParse will also return .
+ /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. + ///
+ public static bool IsFloat(this string? str) => !string.IsNullOrWhiteSpace(str) && str.HowMany('.') <= 1 && str[0].IsFloat() && str.Substring(1).All(IsFixedPoint); + + /// + /// iff is not , + /// the first char of is '-' or a digit, and + /// all subsequent chars of are digits + /// + public static bool IsSigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str[0].IsSigned() && str.Substring(1).All(IsUnsigned); + + /// + /// A copy of with characters removed so that the whole thing passes IsFixedPoint.
+ /// That is, the all chars of the copy will be '.' or a digit and the copy will contain at most 1 decimal separator '.'. + ///
+ /// + /// The returned value may not be parseable by float.TryParse.
+ /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. + ///
+ public static string OnlyFixedPoint(this string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) return string.Empty; + var output = new StringBuilder(); + var usedDot = false; + foreach (var chr in raw) + { + if (chr == '.') + { + if (usedDot) continue; + output.Append(chr); + usedDot = true; + } + else if (chr.IsUnsigned()) output.Append(chr); + } + return output.ToString(); + } + + /// + /// A copy of with characters removed so that the whole thing passes IsFloat.
+ /// That is, the first char of the copy will be '-', '.', or a digit,
+ /// all subsequent chars of the copy will be '.' or a digit, and
+ /// the copy will contain at most 1 decimal separator '.'. + ///
+ /// + /// If contains a serialized negative decimal, it must be at the start ([0] == '-') or the sign will be dropped.
+ /// The returned value may not be parseable by float.TryParse.
+ /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. + ///
+ public static string OnlyFloat(this string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) return string.Empty; + + var output = new StringBuilder(); + var usedDot = false; + + var first = raw[0]; + if (first.IsFloat()) + { + output.Append(first); + if (first == '.') usedDot = true; + } + + for (int i = 1, l = raw.Length; i < l; i++) + { + var chr = raw[i]; + if (chr == '.') + { + if (usedDot) continue; + output.Append(chr); + usedDot = true; + } + else if (chr.IsUnsigned()) output.Append(chr); + } + + return output.ToString(); + } + + /// + /// A copy of with characters removed so that the whole thing passes IsSigned.
+ /// That is, the first char of the copy will be '-' or a digit, and all subsequent chars of the copy will be digits. + ///
+ /// If contains a serialized negative integer, it must be at the start ([0] == '-') or the sign will be dropped. + public static string OnlySigned(this string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) return string.Empty; + return raw[0].IsSigned() + ? raw[0] + string.Concat(raw.Skip(1).Where(IsUnsigned)) + : string.Concat(raw.Skip(1).Where(IsUnsigned)); + } + +#pragma warning restore CS8602 + } +} diff --git a/src/BizHawk.Common/Extensions/StringExtensions.cs b/src/BizHawk.Common/Extensions/StringExtensions.cs index cfd8d4602c..7ee06cd6c4 100644 --- a/src/BizHawk.Common/Extensions/StringExtensions.cs +++ b/src/BizHawk.Common/Extensions/StringExtensions.cs @@ -1,200 +1,14 @@ -using System; -using System.Linq; -using System.Text; - -namespace BizHawk.Common.StringExtensions -{ - /// TODO how many of these Is*/Only* methods' callers can use int.TryParse or similar instead? --yoshi - public static class StringExtensions - { -#pragma warning disable CS8602 // no idea --yoshi - - /// the substring of before the first occurrence of , or if not found - /// TODO rename SubstringBeforeOrNull, might as well split this class at the same time - public static string? GetPrecedingString(this string str, string value) - { - var index = str.IndexOf(value); - return index < 0 ? null : str.Substring(0, index); - } - - /// iff appears in (case-insensitive) - public static bool In(this string str, params string[] options) => options.Any(opt => string.Equals(opt, str, StringComparison.InvariantCultureIgnoreCase)); - - /// how many times appears in , or 0 if is null - public static int HowMany(this string? str, char c) => string.IsNullOrEmpty(str) ? 0 : str.Count(t => t == c); - - /// iff is not and all chars of are digits - public static bool IsUnsigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsUnsigned); - - /// iff is a digit - public static bool IsUnsigned(this char c) => char.IsDigit(c); - - /// - /// iff is not , - /// the first char of is '-' or a digit, and - /// all subsequent chars of are digits - /// - public static bool IsSigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str[0].IsSigned() && str.Substring(1).All(IsUnsigned); - - /// iff is '-' or a digit - public static bool IsSigned(this char c) => IsUnsigned(c) || c == '-'; - - /// iff is not and all chars of are hex digits ([0-9A-Fa-f]) - /// should exclude the prefix 0x - public static bool IsHex(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsHex); - - /// iff is a hex digit ([0-9A-Fa-f]) - public static bool IsHex(this char c) => IsUnsigned(c) || 'A' <= char.ToUpperInvariant(c) && char.ToUpperInvariant(c) <= 'F'; - - /// iff is not and all chars of are either '0' or '1' - /// should exclude the prefix 0b - public static bool IsBinary(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsBinary); - - /// iff is either '0' or '1' - public static bool IsBinary(this char c) => c == '0' || c == '1'; - - /// - /// iff is not ,
- /// all chars of are '.' or a digit, and
- /// contains at most 1 decimal separator '.' - ///
- /// - /// should exclude the suffix M.
- /// This method returning for some does not imply that float.TryParse will also return .
- /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. - ///
- public static bool IsFixedPoint(this string? str) => !string.IsNullOrWhiteSpace(str) && str.HowMany('.') <= 1 && str.All(IsFixedPoint); - - /// iff is '.' or a digit - /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. - public static bool IsFixedPoint(this char c) => IsUnsigned(c) || c == '.'; - - /// - /// iff is not ,
- /// the first char of is '-', '.', or a digit,
- /// all subsequent chars of are '.' or a digit, and
- /// contains at most 1 decimal separator '.' - ///
- /// - /// should exclude the suffix f.
- /// This method returning for some does not imply that float.TryParse will also return .
- /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. - ///
- public static bool IsFloat(this string? str) => !string.IsNullOrWhiteSpace(str) && str.HowMany('.') <= 1 && str[0].IsFloat() && str.Substring(1).All(IsFixedPoint); - - /// iff is '-', '.', or a digit - /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. - public static bool IsFloat(this char c) => c.IsFixedPoint() || c == '-'; - - /// - /// A copy of with characters removed so that the whole thing passes IsBinary.
- /// That is, all chars of the copy will be either '0' or '1'. - ///
- public static string OnlyBinary(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsBinary)); - - /// - /// A copy of with characters removed so that the whole thing passes IsUnsigned.
- /// That is, all chars of the copy will be digits. - ///
- public static string OnlyUnsigned(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsUnsigned)); - - /// - /// A copy of with characters removed so that the whole thing passes IsSigned.
- /// That is, the first char of the copy will be '-' or a digit, and all subsequent chars of the copy will be digits. - ///
- /// If contains a serialized negative integer, it must be at the start ([0] == '-') or the sign will be dropped. - public static string OnlySigned(this string? raw) - { - if (string.IsNullOrWhiteSpace(raw)) return string.Empty; - return raw[0].IsSigned() - ? raw[0] + string.Concat(raw.Skip(1).Where(IsUnsigned)) - : string.Concat(raw.Skip(1).Where(IsUnsigned)); - } - - /// - /// A copy of with characters removed so that the whole thing passes IsHex.
- /// That is, all chars of the copy will be hex digits ([0-9A-F]). - ///
- public static string OnlyHex(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsHex)).ToUpperInvariant(); - - /// - /// A copy of with characters removed so that the whole thing passes IsFixedPoint.
- /// That is, the all chars of the copy will be '.' or a digit and the copy will contain at most 1 decimal separator '.'. - ///
- /// - /// The returned value may not be parseable by float.TryParse.
- /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsUnsignedDecimal. - ///
- public static string OnlyFixedPoint(this string? raw) - { - if (string.IsNullOrWhiteSpace(raw)) return string.Empty; - var output = new StringBuilder(); - var usedDot = false; - foreach (var chr in raw) - { - if (chr == '.') - { - if (usedDot) continue; - output.Append(chr); - usedDot = true; - } - else if (chr.IsUnsigned()) output.Append(chr); - } - return output.ToString(); - } - - /// - /// A copy of with characters removed so that the whole thing passes IsFloat.
- /// That is, the first char of the copy will be '-', '.', or a digit,
- /// all subsequent chars of the copy will be '.' or a digit, and
- /// the copy will contain at most 1 decimal separator '.'. - ///
- /// - /// If contains a serialized negative decimal, it must be at the start ([0] == '-') or the sign will be dropped.
- /// The returned value may not be parseable by float.TryParse.
- /// Also this has nothing to do with fixed- vs. floating-point numbers, a better name would be IsSignedDecimal. - ///
- public static string OnlyFloat(this string? raw) - { - if (string.IsNullOrWhiteSpace(raw)) return string.Empty; - - var output = new StringBuilder(); - var usedDot = false; - - var first = raw[0]; - if (first.IsFloat()) - { - output.Append(first); - if (first == '.') usedDot = true; - } - - for (int i = 1, l = raw.Length; i < l; i++) - { - var chr = raw[i]; - if (chr == '.') - { - if (usedDot) continue; - output.Append(chr); - usedDot = true; - } - else if (chr.IsUnsigned()) output.Append(chr); - } - - return output.ToString(); - } - -#pragma warning restore CS8602 - - /// with the last char removed (iff the last char is , otherwise unmodified) - public static string RemoveSuffix(this string str, char suffix) => str[str.Length - 1] == suffix ? str.Substring(0, str.Length - 1) : str; - - /// with the trailing substring removed (iff ends with , otherwise unmodified) - public static string RemoveSuffix(this string str, string suffix) => str.EndsWith(suffix) ? str.Substring(0, str.Length - suffix.Length) : str; - - public static string SubstringBefore(this string str, char separator) - { - var index = str.IndexOf(separator); - return index < 0 ? str : str.Substring(0, index); - } - } -} +using System; +using System.Linq; + +namespace BizHawk.Common.StringExtensions +{ + public static class StringExtensions + { + /// how many times appears in , or 0 if is null + public static int HowMany(this string? str, char c) => string.IsNullOrEmpty(str) ? 0 : str.Count(t => t == c); + + /// iff appears in (case-insensitive) + public static bool In(this string str, params string[] options) => options.Any(opt => string.Equals(opt, str, StringComparison.InvariantCultureIgnoreCase)); + } +} diff --git a/src/BizHawk.Common/Extensions/SubstringExtensions.cs b/src/BizHawk.Common/Extensions/SubstringExtensions.cs new file mode 100644 index 0000000000..804cfdd964 --- /dev/null +++ b/src/BizHawk.Common/Extensions/SubstringExtensions.cs @@ -0,0 +1,215 @@ +using System; + +namespace BizHawk.Common.StringExtensions +{ + public static class SubstringExtensions + { + /// with the first char removed, or the original if the first char of is not + public static string RemovePrefix(this string str, char prefix) => str.RemovePrefix(prefix, notFoundValue: str); + + /// with the first char removed, or if the first char of is not + public static string RemovePrefix(this string str, char prefix, string notFoundValue) => str.Length != 0 && str[0] == prefix ? str.Substring(1, str.Length - 1) : notFoundValue; + + /// with the leading substring removed, or the original if does not start with + public static string RemovePrefix(this string str, string prefix) => str.RemovePrefix(prefix, notFoundValue: str); + + /// with the leading substring removed, or if does not start with + public static string RemovePrefix(this string str, string prefix, string notFoundValue) => str.StartsWith(prefix) ? str.Substring(prefix.Length, str.Length - prefix.Length) : notFoundValue; + + /// with the first char removed, or string.Empty if the first char of is not + public static string RemovePrefixOrEmpty(this string str, char prefix) => str.RemovePrefix(prefix, notFoundValue: str); + + /// with the leading substring removed, or string.Empty if does not start with + public static string RemovePrefixOrEmpty(this string str, string prefix) => str.RemovePrefix(prefix, notFoundValue: str); + + /// with the first char removed, or < if the first char of is not + public static string? RemovePrefixOrNull(this string str, char prefix) => str.Length != 0 && str[0] == prefix ? str.Substring(1, str.Length - 1) : null; + + /// with the leading substring removed, or if does not start with + public static string? RemovePrefixOrNull(this string str, string prefix) => str.StartsWith(prefix) ? str.Substring(prefix.Length, str.Length - prefix.Length) : null; + + /// with the last char removed, or the original if the last char of is not + public static string RemoveSuffix(this string str, char suffix) => str.RemoveSuffix(suffix, notFoundValue: str); + + /// with the last char removed, or if the last char of is not + public static string RemoveSuffix(this string str, char suffix, string notFoundValue) => str.Length != 0 && str[str.Length - 1] == suffix ? str.Substring(0, str.Length - 1) : notFoundValue; + + /// with the trailing substring removed, or the original if does not end with + public static string RemoveSuffix(this string str, string suffix) => str.RemoveSuffix(suffix, notFoundValue: str); + + /// with the trailing substring removed, or if does not end with + public static string RemoveSuffix(this string str, string suffix, string notFoundValue) => str.EndsWith(suffix) ? str.Substring(0, str.Length - suffix.Length) : notFoundValue; + + /// with the last char removed, or string.Empty if the last char of is not + public static string RemoveSuffixOrEmpty(this string str, char suffix) => str.RemoveSuffix(suffix, notFoundValue: str); + + /// with the trailing substring removed, or string.Empty if does not end with + public static string RemoveSuffixOrEmpty(this string str, string suffix) => str.RemoveSuffix(suffix, notFoundValue: str); + + /// with the last char removed, or < if the last char of is not + public static string? RemoveSuffixOrNull(this string str, char suffix) => str.Length != 0 && str[str.Length - 1] == suffix ? str.Substring(0, str.Length - 1) : null; + + /// with the trailing substring removed, or if does not end with + public static string? RemoveSuffixOrNull(this string str, string suffix) => str.EndsWith(suffix) ? str.Substring(0, str.Length - suffix.Length) : null; + + /// the substring of after the first occurrence of , or the original if not found + public static string SubstringAfter(this string str, char delimiter) => str.SubstringAfter(delimiter, notFoundValue: str); + + /// the substring of after the first occurrence of , or if not found + public static string SubstringAfter(this string str, char delimiter, string notFoundValue) + { + var index = str.IndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(index + 1, str.Length - index - 1); + } + + /// the substring of after the first occurrence of , or the original if not found + public static string SubstringAfter(this string str, string delimiter) => str.SubstringAfter(delimiter, notFoundValue: str); + + /// the substring of after the first occurrence of , or if not found + public static string SubstringAfter(this string str, string delimiter, string notFoundValue) + { + var index = str.IndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(index + delimiter.Length, str.Length - index - delimiter.Length); + } + + /// the substring of after the last occurrence of , or the original if not found + public static string SubstringAfterLast(this string str, char delimiter) => str.SubstringAfterLast(delimiter, notFoundValue: str); + + /// the substring of after the last occurrence of , or if not found + public static string SubstringAfterLast(this string str, char delimiter, string notFoundValue) + { + var index = str.LastIndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(index + 1, str.Length - index - 1); + } + + /// the substring of after the last occurrence of , or the original if not found + public static string SubstringAfterLast(this string str, string delimiter) => str.SubstringAfterLast(delimiter, notFoundValue: str); + + /// the substring of after the last occurrence of , or if not found + public static string SubstringAfterLast(this string str, string delimiter, string notFoundValue) + { + var index = str.LastIndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(index + delimiter.Length, str.Length - index - delimiter.Length); + } + + /// the substring of after the last occurrence of , or string.Empty if not found + public static string SubstringAfterLastOrEmpty(this string str, char delimiter) => str.SubstringAfterLast(delimiter, notFoundValue: str); + + /// the substring of after the last occurrence of , or string.Empty if not found + public static string SubstringAfterLastOrEmpty(this string str, string delimiter) => str.SubstringAfterLast(delimiter, notFoundValue: str); + + /// the substring of after the last occurrence of , or if not found + public static string? SubstringAfterLastOrNull(this string str, char delimiter) + { + var index = str.LastIndexOf(delimiter); + return index < 0 ? null : str.Substring(index + 1, str.Length - index - 1); + } + + /// the substring of after the last occurrence of , or if not found + public static string? SubstringAfterLastOrNull(this string str, string delimiter) + { + var index = str.LastIndexOf(delimiter); + return index < 0 ? null : str.Substring(index + delimiter.Length, str.Length - index - delimiter.Length); + } + + /// the substring of after the first occurrence of , or string.Empty if not found + public static string SubstringAfterOrEmpty(this string str, char delimiter) => str.SubstringAfter(delimiter, notFoundValue: str); + + /// the substring of after the first occurrence of , or string.Empty if not found + public static string SubstringAfterOrEmpty(this string str, string delimiter) => str.SubstringAfter(delimiter, notFoundValue: str); + + /// the substring of after the first occurrence of , or if not found + public static string? SubstringAfterOrNull(this string str, char delimiter) + { + var index = str.IndexOf(delimiter); + return index < 0 ? null : str.Substring(index + 1, str.Length - index - 1); + } + + /// the substring of after the first occurrence of , or if not found + public static string? SubstringAfterOrNull(this string str, string delimiter) + { + var index = str.IndexOf(delimiter); + return index < 0 ? null : str.Substring(index + delimiter.Length, str.Length - index - delimiter.Length); + } + + /// the substring of before the first occurrence of , or the original if not found + public static string SubstringBefore(this string str, char delimiter) => str.SubstringBefore(delimiter, notFoundValue: str); + + /// the substring of before the first occurrence of , or if not found + public static string SubstringBefore(this string str, char delimiter, string notFoundValue) + { + var index = str.IndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(0, index); + } + + /// the substring of before the first occurrence of , or the original if not found + public static string SubstringBefore(this string str, string delimiter) => str.SubstringBefore(delimiter, notFoundValue: str); + + /// the substring of before the first occurrence of , or if not found + public static string SubstringBefore(this string str, string delimiter, string notFoundValue) + { + var index = str.IndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(0, index); + } + + /// the substring of before the last occurrence of , or the original if not found + public static string SubstringBeforeLast(this string str, char delimiter) => str.SubstringBeforeLast(delimiter, notFoundValue: str); + + /// the substring of before the last occurrence of , or if not found + public static string SubstringBeforeLast(this string str, char delimiter, string notFoundValue) + { + var index = str.LastIndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(0, index); + } + + /// the substring of before the last occurrence of , or the original if not found + public static string SubstringBeforeLast(this string str, string delimiter) => str.SubstringBeforeLast(delimiter, notFoundValue: str); + + /// the substring of before the last occurrence of , or if not found + public static string SubstringBeforeLast(this string str, string delimiter, string notFoundValue) + { + var index = str.LastIndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(0, index); + } + + /// the substring of before the last occurrence of , or string.Empty if not found + public static string SubstringBeforeLastOrEmpty(this string str, char delimiter) => str.SubstringBeforeLast(delimiter, notFoundValue: str); + + /// the substring of before the last occurrence of , or string.Empty if not found + public static string SubstringBeforeLastOrEmpty(this string str, string delimiter) => str.SubstringBeforeLast(delimiter, notFoundValue: str); + + /// the substring of before the last occurrence of , or if not found + public static string? SubstringBeforeLastOrNull(this string str, char delimiter) + { + var index = str.LastIndexOf(delimiter); + return index < 0 ? null : str.Substring(0, index); + } + + /// the substring of before the last occurrence of , or if not found + public static string? SubstringBeforeLastOrNull(this string str, string delimiter) + { + var index = str.LastIndexOf(delimiter); + return index < 0 ? null : str.Substring(0, index); + } + + /// the substring of before the first occurrence of , or string.Empty if not found + public static string SubstringBeforeOrEmpty(this string str, char delimiter) => str.SubstringBefore(delimiter, notFoundValue: str); + + /// the substring of before the first occurrence of , or string.Empty if not found + public static string SubstringBeforeOrEmpty(this string str, string delimiter) => str.SubstringBefore(delimiter, notFoundValue: str); + + /// the substring of before the first occurrence of , or if not found + public static string? SubstringBeforeOrNull(this string str, char delimiter) + { + var index = str.IndexOf(delimiter); + return index < 0 ? null : str.Substring(0, index); + } + + /// the substring of before the first occurrence of , or if not found + public static string? SubstringBeforeOrNull(this string str, string delimiter) + { + var index = str.IndexOf(delimiter); + return index < 0 ? null : str.Substring(0, index); + } + } +}