Skip to main content

Extension methods for the String class.

using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace Extensions
{
    public static class StringExtensions
    {
        public static bool EqualsIgnoreCase(this string s1, string s2)
        {
            return string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
        }
        internal static bool EqualsOrdinal(this string s1, string s2)
        {
            return string.Equals(s1, s2, StringComparison.Ordinal);
        }

        public static string RestrictTo(this string self, int size)
        {
            if (string.IsNullOrEmpty(self) || self.Length <= size)
            {
                return self;
            }
            return self.Substring(0, size);
        }

        public static string Truncate(this string str, int maxLength = 255, string truncateWith = "")
        {
            if (string.IsNullOrEmpty(str))
            {
                return str;
            }

            if (str.Length < maxLength)
            {
                return str;
            }

            if (string.IsNullOrEmpty(truncateWith))
            {
                return str.Substring(0, Math.Min(str.Length, maxLength));
            }

            return string.Format("{0}{1}", str.Substring(0, Math.Min(str.Length, maxLength)), truncateWith);
        }

        public static string OnlyLettersDigits(this string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                return str;
            }

            return new String(str.Where(c => Char.IsLetter(c) || Char.IsDigit(c)).ToArray());
        }

        public static string OnlyAlpha(this string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                return str;
            }

            return new String(str.ToCharArray().Where(c => !Char.IsLetter(c)).ToArray());
        }

        public static string ReplaceNonAlphanumeric(this string str, char replacementChar)
        {
            if (string.IsNullOrEmpty(str))
            {
                return str;
            }

            var sb = new StringBuilder(str.Length);
            foreach (char c in str)
            {
                if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9')
                {
                    sb.Append(c);
                }
                else
                {
                    sb.Append(replacementChar);
                }
            }

            return sb.ToString();
        }

        // public static string RemoveWhitespace(this string str)
        // {
        //     if (string.IsNullOrEmpty(str))
        //     {
        //         return str;
        //     }
        //     return new String(str.ToCharArray().Where(c => !Char.IsWhiteSpace(c)).ToArray());
        // }

        /// <summary>
        /// Calculates the SHA1 hash of the supplied string and returns a base 64 string.
        /// </summary>
        /// <param name="str">String that must be hashed.</param>
        /// <exception cref="ArgumentException">Occurs when str or key is null or empty.</exception>
        /// <returns>The hashed string or null if hashing failed.</returns>
        public static string GetSha1Hash(this string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                throw new ArgumentException("An empty string value cannot be hashed.", str);
            }

            return Convert.ToBase64String(new SHA1CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(str)));
        }

        public static bool Contains(this string str, string value, StringComparison comparisonType)
        {
            return str.IndexOf(value, comparisonType) >= 0;
        }

        public static string Left(this string str, int length)
        {
            if (length < 1) return string.Empty;
            if (length < str.Length) return str.Substring(0, length);
            return str;
        }

        public static string Right(this string str, int length)
        {
            if (length < 1) return string.Empty;
            if (length < str.Length) return str.Substring(str.Length - length);
            return str;
        }

        public static string RemoveLeft(this string str, int length)
        {
            if (length < 1) return string.Empty;
            if (length < str.Length) return str.Remove(0, length);
            return str;
        }

        public static string RemoveRight(this string str, int length)
        {
            if (length < 1) return string.Empty;
            if (length < str.Length) return str.Remove(str.Length - length);
            return str;
        }

        public static string Between(this string text, string first, string last, bool isFirstMatchForEnd = false, bool includeFirstAndLast = false)
        {
            int start = text.IndexOf(first);
            if (start < 0) return null;
            if (!includeFirstAndLast) start += first.Length;
            text = text.Substring(start);

            int end = isFirstMatchForEnd ? text.IndexOf(last) : text.LastIndexOf(last);
            if (end < 0) return null;
            if (includeFirstAndLast) end += last.Length;
            return text.Remove(end);
        }

        public static string Repeat(this string str, int count)
        {
            if (!string.IsNullOrEmpty(str) && count > 0)
            {
                StringBuilder sb = new StringBuilder(str.Length * count);
                for (int i = 0; i < count; i++)
                {
                    sb.Append(str);
                }
                return sb.ToString();
            }
            return null;
        }

        public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
        {
            if (string.IsNullOrEmpty(oldValue))
            {
                return str;
            }

            StringBuilder sb = new StringBuilder();

            int previousIndex = 0;
            int index = str.IndexOf(oldValue, comparison);
            while (index != -1)
            {
                sb.Append(str.Substring(previousIndex, index - previousIndex));
                sb.Append(newValue);
                index += oldValue.Length;

                previousIndex = index;
                index = str.IndexOf(oldValue, index, comparison);
            }
            sb.Append(str.Substring(previousIndex));

            return sb.ToString();
        }

        public static string ReplaceWith(this string str, string search, string replace,
            int occurrence = 0, StringComparison comparison = StringComparison.InvariantCultureIgnoreCase)
        {
            if (!string.IsNullOrEmpty(search))
            {
                int count = 0, location;

                while (occurrence == 0 || occurrence > count)
                {
                    location = str.IndexOf(search, comparison);
                    if (location < 0) break;
                    count++;
                    str = str.Remove(location, search.Length).Insert(location, replace);
                }
            }

            return str;
        }

        public static bool ReplaceFirst(this string text, string search, string replace, out string result)
        {
            int location = text.IndexOf(search);

            if (location < 0)
            {
                result = text;
                return false;
            }

            result = text.Remove(location, search.Length).Insert(location, replace);
            return true;
        }

        public static string ReplaceAll(this string text, string search, Func<string> replace)
        {
            while (true)
            {
                int location = text.IndexOf(search);

                if (location < 0) break;

                text = text.Remove(location, search.Length).Insert(location, replace());
            }

            return text;
        }

        public static string RemoveWhiteSpaces(this string str)
        {
            StringBuilder result = new StringBuilder();
            foreach (char c in str)
            {
                if (!Char.IsWhiteSpace(c)) result.Append(c);
            }
            return result.ToString();
        }

        public static string Reverse(this string str)
        {
            char[] chars = str.ToCharArray();
            Array.Reverse(chars);
            return new String(chars);
        }

        public static string Truncate(this string str, int maxLength)
        {
            if (!string.IsNullOrEmpty(str) && str.Length > maxLength)
            {
                return str.Substring(0, maxLength);
            }
            return str;
        }

        // public static string Truncate(this string str, int maxLength, string endings, bool truncateFromRight = true)
        // {
        //     if (!string.IsNullOrEmpty(str) && str.Length > maxLength)
        //     {
        //         int length = maxLength - endings.Length;
        //         if (length > 0)
        //         {
        //             if (truncateFromRight)
        //             {
        //                 str = str.Left(length) + endings;
        //             }
        //             else
        //             {
        //                 str = endings + str.Right(length);
        //             }
        //         }
        //     }
        //     return str;
        // }

        public static bool IsValidUrl(this string url)
        {
            return Uri.IsWellFormedUriString(url.Trim(), UriKind.Absolute);
        }

        public static byte[] HexToBytes(this string hex)
        {
            byte[] bytes = new byte[hex.Length / 2];
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
            }
            return bytes;
        }

        public static string ParseQuoteString(this string str)
        {
            str = str.Trim();

            int firstQuote = str.IndexOf('"');
            if (firstQuote >= 0)
            {
                str = str.Substring(firstQuote + 1);
                int secondQuote = str.IndexOf('"');
                if (secondQuote >= 0)
                {
                    str = str.Remove(secondQuote);
                }
            }

            return str;
        }

        public static bool IsNumber(this string text)
        {
            foreach (char c in text)
            {
                if (!char.IsNumber(c)) return false;
            }
            return true;
        }

        public static string[] Lines(this string text)
        {
            return text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
        }

        public static IEnumerable<Tuple<string, string>> ForEachBetween(this string text, string front, string back)
        {
            int f = 0;
            int b = 0;
            while (text.Length > f
                   && 0 <= (f = text.IndexOf(front, f))
                   && 0 <= (b = text.IndexOf(back, f + front.Length)))
            {
                string result = text.Substring(f, (b + back.Length) - f);
                yield return new Tuple<string, string>(result, result.Substring(front.Length, (result.Length - back.Length) - front.Length));
                f += front.Length;
            }
        }

        public static int FromBase(this string text, int radix, string digits)
        {
            if (string.IsNullOrEmpty(digits))
            {
                throw new ArgumentNullException("digits", string.Format("Digits must contain character value representations"));
            }

            radix = Math.Abs(radix);
            if (radix > digits.Length || radix < 2)
            {
                throw new ArgumentOutOfRangeException("radix", radix, string.Format("Radix has to be > 2 and < {0}", digits.Length));
            }

            // Convert to Base 10
            int value = 0;
            if (!string.IsNullOrEmpty(text))
            {
                for (int i = text.Length - 1; i >= 0; --i)
                {
                    int temp = digits.IndexOf(text[i]) * (int)Math.Pow(radix, text.Length - (i + 1));
                    if (0 > temp)
                    {
                        throw new IndexOutOfRangeException("Text contains characters not found in digits.");
                    }
                    value += temp;
                }
            }
            return value;
        }

        public static string ToBase(this string text, int fromRadix, string fromDigits, int toRadix, string toDigits)
        {
            return text.FromBase(fromRadix, fromDigits).ToBase(toRadix, toDigits);
        }

        public static string ToBase(this string text, int from, int to, string digits)
        {
            return text.FromBase(from, digits).ToBase(to, digits);
        }

        public static string RemoveSurroundingQuotes(this string s)
        {
            if (s.Length < 2)
                return s;

            var quoteCharacters = new[] { '"', '\'' };
            char firstCharacter = s[0];
            if (!quoteCharacters.Contains(firstCharacter))
                return s;

            if (firstCharacter != s[s.Length - 1])
                return s;

            return s.Substring(1, s.Length - 2);
        }

        public static Int32 ToInt32(this string s)
        {
            Int32 val;
            return Int32.TryParse(s, out val) ? val : 0;
        }

        /// <summary>
        /// Wrap a string to the specified length.
        /// </summary>
        /// <param name="text">The text string to wrap</param>
        /// <param name="maxLength">The character length to wrap at</param>
        /// <returns>A wrapped string using the platform's default newline
        /// character. This string will end in a newline.</returns>
        public static string Wrap(this string text, int maxLength = 72)
        {
            if (text.Length == 0) return string.Empty;

            var sb = new StringBuilder();
            foreach (var unwrappedLine in text.Split(new[] { Environment.NewLine }, StringSplitOptions.None))
            {
                var line = new StringBuilder();
                foreach (var word in unwrappedLine.Split(' '))
                {
                    var needsLeadingSpace = line.Length > 0;

                    var extraLength = (needsLeadingSpace ? 1 : 0) + word.Length;
                    if (line.Length + extraLength > maxLength)
                    {
                        sb.AppendLine(line.ToString());
                        line.Clear();
                        needsLeadingSpace = false;
                    }

                    if (needsLeadingSpace)
                    {
                        line.Append(" ");
                    }

                    line.Append(word);
                }

                sb.AppendLine(line.ToString());
            }

            return sb.ToString();
        }

        public static Uri ToUriSafe(this string url)
        {
            Uri uri;
            Uri.TryCreate(url, UriKind.Absolute, out uri);
            return uri;
        }

        public static string NormalizeLineEndings(this string self, string lineEnding = null)
        {
            if (String.IsNullOrEmpty(lineEnding))
            {
                lineEnding = Environment.NewLine;
            }

            self = self.Replace("\r\n", "\n");
            if (lineEnding != "\n")
            {
                self = self.Replace("\r\n", lineEnding);
            }

            return self;
        }

        /// <summary>
        /// Extension method to convert a string into a byte array
        /// </summary>
        /// <param name="str">String to convert to bytes.</param>
        /// <returns>Byte array</returns>
        public static byte[] GetBytes(this string str)
        {
            var bytes = new byte[str.Length * sizeof(char)];
            Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
            return bytes;
        }

        /// <summary>
        /// Breaks the specified string into csv components, all commas are considered separators
        /// </summary>
        /// <param name="str">The string to be broken into csv</param>
        /// <param name="size">The expected size of the output list</param>
        /// <returns>A list of the csv pieces</returns>
        public static List<string> ToCsv(this string str, int size = 4)
        {
            int last = 0;
            var csv = new List<string>(size);
            for (int i = 0; i < str.Length; i++)
            {
                if (str[i] == ',')
                {
                    if (last != 0) last = last + 1;
                    csv.Add(str.Substring(last, i - last));
                    last = i;
                }
            }
            if (last != 0) last = last + 1;
            csv.Add(str.Substring(last));
            return csv;
        }

        /// <summary>
        /// Breaks the specified string into csv components, works correctly with commas in data fields
        /// </summary>
        /// <param name="str">The string to be broken into csv</param>
        /// <param name="size">The expected size of the output list</param>
        /// <returns>A list of the csv pieces</returns>
        public static List<string> ToCsvData(this string str, int size = 4)
        {
            var csv = new List<string>(size);

            int last = -1;
            bool textDataField = false;

            for (var i = 0; i < str.Length; i++)
            {
                switch (str[i])
                {
                    case '"':
                        textDataField = !textDataField;
                        break;
                    case ',':
                        if (!textDataField)
                        {
                            csv.Add(str.Substring(last + 1, (i - last)).Trim(' ', ','));
                            last = i;
                        }
                        break;
                    default:
                        break;
                }
            }

            if (last != str.Length - 1)
            {
                csv.Add(str.Substring(last + 1).Trim());
            }

            return csv;
        }

        /// <summary>
        /// Extension method to convert strings to stream to be read.
        /// </summary>
        /// <param name="str">String to convert to stream</param>
        /// <returns>Stream instance</returns>
        public static Stream ToStream(this string str)
        {
            var stream = new MemoryStream();
            var writer = new StreamWriter(stream);
            writer.Write(str);
            writer.Flush();
            stream.Position = 0;
            return stream;
        }

        /// <summary>
        /// Encrypt the token:time data to make our API hash.
        /// </summary>
        /// <param name="data">Data to be hashed by SHA256</param>
        /// <returns>Hashed string.</returns>
        public static string ToSHA256(this string data)
        {
            var crypt = new SHA256Managed();
            var hash = new StringBuilder();
            var crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(data), 0,
                Encoding.UTF8.GetByteCount(data));

            foreach (var theByte in crypto)
            {
                hash.Append(theByte.ToString("x2"));
            }

            return hash.ToString();
        }

        /// <summary>
        /// Save values checking length
        /// </summary>
        /// <returns>The string limit.</returns>
        /// <param name="self">String.</param>
        /// <param name="limit">Limit.</param>
        /// <remarks>http://extensionmethod.net/5447/csharp/string/tostringlimit-limit</remarks>
        public static string ToStringLimit(this string self, int limit)
        {
            if (self.Length > limit)
            {
                return self.Substring(0, limit);
            }

            return self;
        }

        /// <summary>
        /// Removes any special characters in the input string not provided
        /// in the allowed special character list.
        ///
        /// Sometimes it is required to remove some special characters like
        /// carriage return, or new line which can be considered as invalid
        /// characters, especially while file processing. This method removes any
        /// special characters in the input string which is not included in the
        /// allowed special character list.
        /// </summary>
        /// <param name="input">Input string to process</param>
        /// <param name="allowedCharacters">list of allowed special characters </param>
        /// <returns>
        /// The original string with special charactersremoved.
        /// </returns>
        /// <example>
        ///
        /// Remove carriage return from the input string:
        ///
        ///     var processedString = RemoveSpecialCharacters(
        ///         "Hello! This is string to process. \r\n", @""",-{}.! "
        ///     );
        ///
        /// </example>
        /// <remarks>
        /// http://extensionmethod.net/5470/csharp/string/removespecialcharacters
        /// </remarks>
        public static string RemoveSpecialCharacters(string input, string allowedCharacters)
        {
            char[] buffer = new char[input.Length];
            int index = 0;

            char[] allowedSpecialCharacters = allowedCharacters.ToCharArray();

            foreach (char c in input.Where(
                         c => char.IsLetterOrDigit(c) || allowedSpecialCharacters.Any(
                             x => x == c)
                     ))
            {
                buffer[index] = c;
                index++;
            }

            return new string(buffer, 0, index);
        }

        /// <summary>
        /// Splits a string into a NameValueCollection, where each "namevalue"
        /// is separated by the "OuterSeparator". The parameter "NameValueSeparator"
        /// sets the split between Name and Value.
        /// </summary>
        /// <param name="self">String to process</param>
        /// <param name="outerSeparator">Separator for each "NameValue"</param>
        /// <param name="nameValueSeparator">Separator for Name/Value splitting</param>
        /// <returns>NameValueCollection of values.</returns>
        /// <example>
        /// Example:
        ///     string str = "param1=value1;param2=value2";
        ///     NameValueCollection nvOut = str.ToNameValueCollection(';', '=');
        ///
        /// The result is a NameValueCollection where:
        ///     key[0] is "param1" and value[0] is "value1"
        ///     key[1] is "param2" and value[1] is "value2"
        /// </example>
        /// <remarks>http://extensionmethod.net/1686/csharp/string/tonamevaluecollection</remarks>
        public static System.Collections.Specialized.NameValueCollection ToNameValueCollection(
            this string self, char outerSeparator, char nameValueSeparator)
        {
            System.Collections.Specialized.NameValueCollection nvText = null;
            self = self.TrimEnd(outerSeparator);

            if (!string.IsNullOrEmpty(self))
            {
                string[] arrStrings = self.TrimEnd(outerSeparator).Split(outerSeparator);
                for (int i = 0; i < arrStrings.Length; i++)
                {
                    string s = arrStrings[i];
                    int posSep = s.IndexOf(nameValueSeparator);
                    string name = s.Substring(0, posSep);
                    string value = s.Substring(posSep + 1);
                    if (nvText == null)
                    {
                        nvText = new System.Collections.Specialized.NameValueCollection();
                    }
                    nvText.Add(name, value);
                }
            }

            return nvText;
        }

        /// <summary>
        /// Check that the given string is in a list of potential matches.
        /// </summary>
        /// <returns><c>true</c>, if any was equalsed, <c>false</c> otherwise.</returns>
        /// <param name="str">String.</param>
        /// <param name="args">Arguments.</param>
        /// <remarks>Inspired by StackOverflow answer http://stackoverflow.com/a/20644611/23199</remarks>
        /// <example>
        /// string custName = "foo";
        /// bool isMatch = (custName.EqualsAny("bar", "baz", "FOO"));
        /// </example>
        public static bool EqualsAny(this string str, params string[] args)
        {
            return args.Any(x => StringComparer.OrdinalIgnoreCase.Equals(x, str));
        }

        /// <summary>
        /// Returns the plural form of the specified word.
        /// </summary>
        /// <param name="count">How many of the specified word there are. A count equal to 1 will not pluralize the specified word.</param>
        /// <returns>A string that is the plural form of the input parameter.</returns>
        public static string ToPlural(this string @this, int count = 0)
        {
            return count == 1 ? @this : System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(new System.Globalization.CultureInfo("en-US")).Pluralize(@this);
        }

    /// <summary>
        /// Does the string contain an uppercase character?
        /// </summary>
        /// <param name="value">Value to test.</param>
        /// <returns>True/False.</returns>
        public static bool ContainsUpper(this string value)
        {
            return value != value.ToLower();
        }

        /// <summary>
        /// Does the string contain an lowercase character?
        /// </summary>
        /// <param name="value">Value to test.</param>
        /// <returns>True/False.</returns>
        public static bool ContainsLower(this string value)
        {
            return value != value.ToUpper();
        }

        /// <summary>
        /// Duplicates/multiplies a string the number of times specified.
        /// </summary>
        /// <param name="source">String to multiply.</param>
        /// <param name="multiplier">Times to multiply.</param>
        /// <returns>Resulting string.</returns>
        public static string Multiply(this string source, int multiplier)
        {
            StringBuilder sb = new StringBuilder(multiplier * source.Length);
            for (int i = 0; i < multiplier; i++)
            {
                sb.Append(source);
            }

            return sb.ToString();
        }

        /// <summary>
        /// Parse the input string to a valid datetime.
        /// </summary>
        /// <param name="stringToParse"></param>
        /// <returns>A DateTime object if s is a valid datetime string otherwise DateTime.MinValue is returned.</returns>
        public static DateTime ToDateTimeSafe(this string stringToParse)
        {
            DateTime output = DateTime.MinValue;
            DateTime.TryParse(stringToParse, out output);
            return output;
        }

        /// <summary>
        /// Check if a string is an integer
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static bool IsInteger(this string value)
        {
            int i;
            return Int32.TryParse(value, out i);
        }

        /// <summary>
        /// Checks whether or not a string value is a valid date.
        /// </summary>
        public static bool IsDate(this string value)
        {
            DateTime dt;
            return DateTime.TryParse(value, out dt);
        }

        /// <summary>
        /// Checks whether or not a string value is a valid bool.
        /// </summary>
        public static bool IsBool(this string value)
        {
            Boolean bl;
            return Boolean.TryParse(value, out bl);
        }

        /// <summary>
        /// Checks whether or not a string value is a valid double.
        /// </summary>
        public static bool IsDouble(this string value)
        {
            Double d;
            return Double.TryParse(value, out d);
        }

        /// <summary>
        /// Converts a camel case string such as OneWord to One Word.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="preserveAcronyms"></param>
        /// <returns></returns>
        public static string SplitCamelCaseWithSpace(this string text)
        {
            if (string.IsNullOrWhiteSpace(text))
                return string.Empty;

            if (text.Length == 1)
            {
                return text.ToUpper();
            }
            else
            {
                var spaceAdded = 0;
                StringBuilder sb = new StringBuilder(text.Length * 2);
                sb.Append(text);

                for (var i = 1; i < text.Length; i++)
                {
                    var c = text[i];
                    if (c >= 'A' && c <= 'Z')
                    {
                        sb.Insert(i + spaceAdded, " ");
                        spaceAdded++;
                    }
                }
                return sb.ToString();
            }
        }

    }
}