/* The MIT License (MIT) Copyright (c) 2016 Jason Wall Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ namespace JMW.Extensions.String; public static class Extensions { #region ParseToIndexOf public static string ParseToIndexOf(this string s, params string[] parms) { return s.ParseToIndexOf(false, parms); } /// /// Returns a string from the begining to the index of one of the provided string parameters /// /// The string to parse /// Wheather to performs a case insensitive search or not /// The strings to parse to /// Subsection of the string from begining to the index of the first matching parameter public static string ParseToIndexOf(this string s, bool case_insensitive = false, params string[] parms) { // return empty if string is empty if (s.Length == 0) { return string.Empty; } // return full string if no params were provided if (parms.Length == 0) { return s; } // lowercase the string if we're working with case insensitive search var str = case_insensitive ? s.ToLower() : s; var idx = -1; // look for earch parm, return first one found. foreach (var p in parms) { // get the index of our search parameter var tidx = IndexOf(str, p, case_insensitive); if (idx == -1) { idx = tidx; } else if (idx > tidx && tidx > -1) { idx = tidx; } } // if its greater than 0, return it. if (idx > -1) { return s.SafeSubstring(0, idx); } // worst case, return the string as is. return s; } #endregion ParseToIndexOf #region ParseToLastIndexOf public static string ParseToLastIndexOf(this string s, params string[] parms) { return s.ParseToLastIndexOf(false, parms); } /// /// Returns a string from the begining to the last index of one of the provided string parameters /// /// The string to parse /// Wheather to performs a case insensitive search or not /// The strings to parse to public static string ParseToLastIndexOf(this string s, bool case_insensitive = false, params string[] parms) { // return empty if string is empty if (s.Length == 0) { return string.Empty; } // return full string if no params were provided if (parms.Length == 0) { return s; } // lowercase the string if we're working with case insensitive search var str = case_insensitive ? s.ToLower() : s; var idx = -1; // look for earch parm, return first one found. foreach (var p in parms) { // get the index of our search parameter var tidx = IndexOf(str, p, case_insensitive, last: true); if (idx == -1) { idx = tidx; } else if (idx < tidx && tidx > -1) { idx = tidx; } } // if its greater than 0, return it. if (idx > -1) { return s.SafeSubstring(0, idx); } // worst case, return the string as is. return s; } #endregion ParseToLastIndexOf #region ParseToIndexOf_PlusLength public static string ParseToIndexOf_PlusLength(this string s, params string[] parms) { return s.ParseToIndexOf_PlusLength(false, parms); } /// /// Returns a string from the begining to the index of one of the provided string parameters, plus the length of the found /// parameter /// /// The string to parse /// Wheather to performs a case insensitive search or not /// The strings to parse to /// Subsection of the string from begining to the index of the first matching parameter public static string ParseToIndexOf_PlusLength(this string s, bool case_insensitive = false, params string[] parms) { // return empty if string is empty if (s.Length == 0) { return string.Empty; } // return full string if no params were provided if (parms.Length == 0) { return s; } // lowercase the string if we're working with case insensitive search var str = case_insensitive ? s.ToLower() : s; var idx = -1; var l = 0; // look for earch parm, return first one found. foreach (var p in parms) { // get the index of our search parameter var tidx = IndexOf(str, p, case_insensitive); if (idx == -1) { idx = tidx; l = p.Length; } else if (idx > tidx && tidx > -1) { idx = tidx; l = p.Length; } } // if its greater than 0, return it. if (idx > -1) { return s.SafeSubstring(0, idx + l); } // worst case, return the string as is. return s; } #endregion ParseToIndexOf_PlusLength #region ParseToLastIndexOf_PlusLength public static string ParseToLastIndexOf_PlusLength(this string s, params string[] parms) { return s.ParseToLastIndexOf_PlusLength(false, parms); } /// /// Returns a string from the begining to the last index of one of the provided string parameters, plus the length of the /// found parameter /// /// The string to parse /// Wheather to performs a case insensitive search or not /// The strings to parse to public static string ParseToLastIndexOf_PlusLength(this string s, bool case_insensitive = false, params string[] parms) { // return empty if string is empty if (s.Length == 0) { return string.Empty; } // return full string if no params were provided if (parms.Length == 0) { return s; } // lowercase the string if we're working with case insensitive search var str = case_insensitive ? s.ToLower() : s; var idx = -1; var l = 0; // look for earch parm, return first one found. foreach (var p in parms) { // get the index of our search parameter var tidx = IndexOf(str, p, case_insensitive, last: true); if (idx == -1) { idx = tidx; l = p.Length; } else if (idx < tidx && tidx > -1) { idx = tidx; l = p.Length; } } // if its greater than 0, return it. if (idx > -1) { return s.SafeSubstring(0, idx + l); } // worst case, return the string as is. return s; } #endregion ParseToLastIndexOf_PlusLength #region ParseToIndexOf_PlusLength public static string ParseAfterIndexOf_PlusLength(this string s, params string[] parms) { return s.ParseAfterIndexOf_PlusLength(false, parms); } /// /// Returns a string starting after the first provided string parameter discovered to the end of the string /// /// The string to parse /// Wheather to performs a case insensitive search or not /// The strings to parse to public static string ParseAfterIndexOf_PlusLength(this string s, bool case_insensitive = false, params string[] parms) { // return empty if string is empty if (s.Length == 0) { return string.Empty; } // return full string if no params were provided if (parms.Length == 0) { return s; } // lowercase the string if we're working with case insensitive search var str = case_insensitive ? s.ToLower() : s; var idx = -1; var l = 0; // look for earch parm, return first one found. foreach (var p in parms) { // get the index of our search parameter var tidx = IndexOf(str, p, case_insensitive); if (idx == -1) { idx = tidx; l = p.Length; } else if (idx > tidx && tidx > -1) { idx = tidx; l = p.Length; } } // if its greater than 0, return it. if (idx > -1) { return s.SafeSubstring(idx + l); } // worst case, return the string as is. return s; } #endregion ParseToIndexOf_PlusLength #region ParseAfterLastIndexOf_PlusLength public static string ParseAfterLastIndexOf_PlusLength(this string s, params string[] parms) { return s.ParseAfterLastIndexOf_PlusLength(false, parms); } /// /// Returns a string starting after the last index of the first provided string parameter discovered to the end of the /// string /// /// The string to parse /// Wheather to performs a case insensitive search or not /// The strings to parse to public static string ParseAfterLastIndexOf_PlusLength(this string s, bool case_insensitive = false, params string[] parms) { // return empty if string is empty if (s.Length == 0) { return string.Empty; } // return full string if no params were provided if (parms.Length == 0) { return s; } // lowercase the string if we're working with case insensitive search var str = case_insensitive ? s.ToLower() : s; var idx = -1; var l = 0; // look for earch parm, return first one found. foreach (var p in parms) { // get the index of our search parameter var tidx = IndexOf(str, p, case_insensitive, last: true); if (idx == -1) { idx = tidx; l = p.Length; } else if (idx < tidx && tidx > -1) { idx = tidx; l = p.Length; } } // if its greater than 0, return it. if (idx > -1) { return s.SafeSubstring(idx + l); } // worst case, return the string as is. return s; } #endregion ParseAfterLastIndexOf_PlusLength #region ParseAfter/To DesignatedIndexOf_PlusLength /// /// Returns a string from the begining to the last index of one of the provided string parameters, plus the length of the /// found parameter /// /// The string to parse /// The number of the search patter to parse after /// Wheather to performs a case insensitive search or not /// The strings to parse to public static string ParseToDesignatedIndexOf_PlusLength(this string s, int qty, bool case_insensitive, params string[] parms) { var r = string.Empty; var v = s; for (var i = 0; i < qty; i++) { r += v.ParseToIndexOf_PlusLength(case_insensitive, parms); v = v.ParseAfterIndexOf_PlusLength(case_insensitive, parms); } // worst case, return the string as is. return r; } /// /// Returns a string starting after the last index of the first provided string parameter discovered to the end of the /// string /// /// The string to parse /// The number of the search patter to parse after /// Wheather to performs a case insensitive search or not /// The strings to parse to public static string ParseAfterDesignatedIndexOf_PlusLength(this string s, int qty, bool case_insensitive = false, params string[] parms) { var r = s; for (var i = 0; i < qty; i++) { r = r.ParseAfterIndexOf_PlusLength(case_insensitive, parms); } // worst case, return the string as is. return r; } #endregion ParseAfter/To DesignatedIndexOf_PlusLength #region ParseAfterLastIndexOf public static string ParseAfterLastIndexOf(this string s, params string[] parms) { return s.ParseAfterLastIndexOf(false, parms); } /// /// Returns a string starting after the last index of the first provided string parameter discovered to the end of the /// string /// /// The string to parse /// Wheather to performs a case insensitive search or not /// The strings to parse to public static string ParseAfterLastIndexOf(this string s, bool case_insensitive = false, params string[] parms) { // return empty if string is empty if (s.Length == 0) { return string.Empty; } // return full string if no params were provided if (parms.Length == 0) { return s; } // lowercase the string if we're working with case insensitive search var str = case_insensitive ? s.ToLower() : s; var idx = -1; // look for earch parm, return first one found. foreach (var p in parms) { // get the index of our search parameter var tidx = IndexOf(str, p, case_insensitive, last: true); if (idx == -1) { idx = tidx; } else if (idx < tidx && tidx > -1) { idx = tidx; } } // if its greater than 0, return it. if (idx > -1) { return s.SafeSubstring(idx); } // worst case, return the string as is. return s; } #endregion ParseAfterLastIndexOf #region ParseAfterLastIndexOf public static string ParseAfterIndexOf(this string s, params string[] parms) { return s.ParseAfterIndexOf(false, parms); } /// /// Returns a string starting after the last index of the first provided string parameter discovered to the end of the /// string /// /// The string to parse /// Wheather to performs a case insensitive search or not /// The strings to parse to public static string ParseAfterIndexOf(this string s, bool case_insensitive = false, params string[] parms) { // return empty if string is empty if (s.Length == 0) { return string.Empty; } // return full string if no params were provided if (parms.Length == 0) { return s; } // lowercase the string if we're working with case insensitive search var str = case_insensitive ? s.ToLower() : s; var idx = -1; // look for earch parm, return first one found. foreach (var p in parms) { // get the index of our search parameter var tidx = IndexOf(str, p, case_insensitive); if (idx == -1) { idx = tidx; } else if (idx > tidx && tidx > -1) { idx = tidx; } } // if its greater than 0, return it. if (idx > -1) { return s.SafeSubstring(idx); } // worst case, return the string as is. return s; } #endregion ParseAfterLastIndexOf /// /// Finds the index of a search string. Optionally allows case insensitivity or option to search for last index. /// /// String to search /// String to search for /// Ignore case /// Search for last instance public static int IndexOf(this string str, string search_string, bool case_insensitive, bool last = false) { // get the index of our search parameter int idx; if (case_insensitive) { idx = last ? str.LastIndexOf(search_string.ToLower(), StringComparison.Ordinal) : str.IndexOf(search_string.ToLower(), StringComparison.Ordinal); } else { idx = last ? str.LastIndexOf(search_string, StringComparison.Ordinal) : str.IndexOf(search_string, StringComparison.Ordinal); } return idx; } /// /// Finds the index of a search string. Optionally allows case insensitivity or option to search for specific index. /// /// String to search /// String to search for /// Ignore case /// Search for specific instance public static int IndexOf(this string str, string search_string, bool case_insensitive, int qty) { // get the index of our search parameter var idx = -1; if (case_insensitive) { var c = 0; while (c != qty) { idx = str.IndexOf(search_string.ToLower(), idx < 0 ? 0 : idx + search_string.Length, StringComparison.Ordinal); c++; } } else { var c = 0; while (c != qty) { idx = str.IndexOf(search_string, idx < 0 ? 0 : idx + search_string.Length, StringComparison.Ordinal); c++; } } return idx; } /// /// Safely handles the cases where your start exceeds the length of the string, or your start+length exceeds the length of /// the string. /// /// The string to do the substring on /// The index of the string to start parsing from /// The number of characters to return /// A subsection of the provided string public static string SafeSubstring(this string s, int start, int? length = null) { // return empty if the requested start index is past the length of the provided string if (start > s.Length) { return string.Empty; } // if length isn't provided, return without it. if (length is null) { return s.Substring(start); } // if the length is 0 or less, the string should be empty. if (length < 1) { return string.Empty; } var len = (int)length; if (start + length > s.Length) { len = s.Length - start; } return s.Substring(start, len); } /// /// Determines if the string is either null or has no characters /// /// String to evaluate /// True or False public static bool IsEmpty(this string? str) { return str is null || str.Length == 0; } /// /// Determines if the string is both not null and has at least 1 character. /// /// String to evaluate /// True or False public static bool IsNotEmpty(this string? str) { return !str.IsEmpty(); } /// /// Searches a string for a given /// /// String to search /// to find public static bool ContainsFast(this string str, char c) { for (var i = 0; i < str.Length; i++) { if (str[i] == c) { return true; } } return false; } /// /// Converts a string to PascalCase. Removes any illegal file name characters. /// /// string to convert public static string ToPascalCase(this string str) { var v = str; foreach (var c in Path.GetInvalidFileNameChars()) { v = v.Replace(c.ToString(), string.Empty); } foreach (var c in Path.GetInvalidFileNameChars()) { v = v.Replace(c.ToString(), string.Empty); } var words = v.Split(' '); var r = string.Empty; foreach (var w in words) { r += string.Concat(w.Substring(0, 1).ToUpper(), w.AsSpan(1)); } return r; } /// /// Counts the numbers of instances of the param in the /// /// string. /// /// string to search /// char to search for /// doesn't count "port" the same as "portindex" /// int public static int CountInstances(this string test, string c, bool respect_word_boundaries = false) { var s = test + " "; // to avoid geriatrics with strings not ending in a word boundary. var cnt = 0; if (respect_word_boundaries) { c += " "; } while (s.Contains(c)) { s = s.Substring(s.IndexOf(c, StringComparison.Ordinal) + c.Length); cnt++; } return cnt; } public static Dictionary GetCharHistogram(this string line, bool collapse_whitespace = true) { var hist = new Dictionary(); var prev_char_is_ws = false; foreach (var c in line) { var my_char = c; if (collapse_whitespace && char.IsWhiteSpace(c)) { my_char = ' '; if (prev_char_is_ws) { continue; } } // Only the first whitespace counts we ignore the rest if (hist.TryGetValue(my_char, out var value)) { hist[my_char] = ++value; } else { hist[my_char] = 1; } prev_char_is_ws = char.IsWhiteSpace(c); } return hist; } public static IEnumerable ToLines(this string s) { using var sr = new StringReader(s); string? line; while ((line = sr.ReadLine()) is not null) { yield return line; } } }