using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Web; namespace Umbraco.Core { /// /// String extension methods /// public static class StringExtensions { /// /// Trims the specified value from a string; accepts a string input whereas the in-built implementation only accepts char or char[]. /// /// The value. /// For removing. /// public static string Trim(this string value, string forRemoving) { if (string.IsNullOrEmpty(value)) return value; return value.TrimEnd(forRemoving).TrimStart(forRemoving); } public static string TrimEnd(this string value, string forRemoving) { if (string.IsNullOrEmpty(value)) return value; while (value.EndsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) { value = value.Remove(value.LastIndexOf(forRemoving, StringComparison.InvariantCultureIgnoreCase)); } return value; } public static string TrimStart(this string value, string forRemoving) { if (string.IsNullOrEmpty(value)) return value; while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) { value = value.Substring(forRemoving.Length); } return value; } public static string EnsureStartsWith(this string input, string toStartWith) { if (input.StartsWith(toStartWith)) return input; return toStartWith + input.TrimStart(toStartWith.ToArray()); // Ensure each char is removed first from input, e.g. ~/ plus /Path will equal ~/Path not ~//Path } public static string EnsureStartsWith(this string input, char value) { return input.StartsWith(value.ToString()) ? input : value + input; } public static string EnsureEndsWith(this string input, char value) { return input.EndsWith(value.ToString()) ? input : input + value; } public static bool IsLowerCase(this char ch) { return ch.ToString(CultureInfo.InvariantCulture) == ch.ToString(CultureInfo.InvariantCulture).ToLower(); } /// Is null or white space. /// The str. /// The is null or white space. public static bool IsNullOrWhiteSpace(this string str) { return (str == null) || (str.Trim().Length == 0); } public static string IfNullOrWhiteSpace(this string str, string defaultValue) { return str.IsNullOrWhiteSpace() ? defaultValue : str; } /// The to delimited list. /// The list. /// The delimiter. /// the list [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "By design")] public static IList ToDelimitedList(this string list, string delimiter = ",") { var delimiters = new[] { delimiter }; return !list.IsNullOrWhiteSpace() ? list.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) .Select(i => i.Trim()) .ToList() : new List(); } /// enum try parse. /// The str type. /// The ignore case. /// The result. /// The type /// The enum try parse. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By Design")] [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By Design")] public static bool EnumTryParse(this string strType, bool ignoreCase, out T result) { try { result = (T)Enum.Parse(typeof(T), strType, ignoreCase); return true; } catch { result = default(T); return false; } } /// /// Strips all html from a string. /// /// The text. /// Returns the string without any html tags. public static string StripHtml(this string text) { const string pattern = @"<(.|\n)*?>"; return Regex.Replace(text, pattern, String.Empty); } /// /// Converts string to a URL alias. /// /// The value. /// The char replacements. /// if set to true replace double dashes. /// if set to true strip non ASCII. /// if set to true URL encode. /// /// /// This ensures that ONLY ascii chars are allowed and of those ascii chars, only digits and lowercase chars, all /// punctuation, etc... are stripped out, however this method allows you to pass in string's to replace with the /// specified replacement character before the string is converted to ascii and it has invalid characters stripped out. /// This allows you to replace strings like & , etc.. with your replacement character before the automatic /// reduction. /// public static string ToUrlAlias(this string value, IDictionary charReplacements, bool replaceDoubleDashes, bool stripNonAscii, bool urlEncode) { //first to lower case value = value.ToLowerInvariant(); //then replacement chars value = charReplacements.Aggregate(value, (current, kvp) => current.Replace(kvp.Key, kvp.Value)); //then convert to only ascii, this will remove the rest of any invalid chars if (stripNonAscii) { value = Encoding.ASCII.GetString( Encoding.Convert( Encoding.UTF8, Encoding.GetEncoding( Encoding.ASCII.EncodingName, new EncoderReplacementFallback(String.Empty), new DecoderExceptionFallback()), Encoding.UTF8.GetBytes(value))); //remove all characters that do not fall into the following categories (apart from the replacement val) var validCodeRanges = //digits Enumerable.Range(48, 10).Concat( //lowercase chars Enumerable.Range(97, 26)); var sb = new StringBuilder(); foreach (var c in value.Where(c => charReplacements.Values.Contains(c.ToString()) || validCodeRanges.Contains(c))) { sb.Append(c); } value = sb.ToString(); } //trim dashes from end value = value.Trim('-', '_'); //replace double occurances of - or _ value = replaceDoubleDashes ? Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled) : value; //url encode result return urlEncode ? HttpUtility.UrlEncode(value) : value; } /// /// Converts a string for use with an entity alias which is camel case and without invalid characters /// /// The phrase. /// By default this is camel case /// if set to true [remove spaces]. /// public static string ToUmbracoAlias(this string phrase, StringAliasCaseType caseType = StringAliasCaseType.CamelCase, bool removeSpaces = false) { if (string.IsNullOrEmpty(phrase)) return string.Empty; //convert case first var tmp = phrase.ConvertCase(caseType); //remove non-alphanumeric chars var result = Regex.Replace(tmp, @"[^a-zA-Z0-9\s\.-]+", "", RegexOptions.Compiled); if (removeSpaces) result = result.Replace(" ", ""); return result; } /// /// Converts the phrase to specified convention. /// /// /// The cases. /// string public static string ConvertCase(this string phrase, StringAliasCaseType cases) { var splittedPhrase = Regex.Split(phrase, @"[^a-zA-Z0-9\']", RegexOptions.Compiled); if (cases == StringAliasCaseType.Unchanged) return string.Join("", splittedPhrase); //var splittedPhrase = phrase.Split(' ', '-', '.'); var sb = new StringBuilder(); foreach (var splittedPhraseChars in splittedPhrase.Select(s => s.ToCharArray())) { if (splittedPhraseChars.Length > 0) { splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpper().ToCharArray())[0]; } sb.Append(new String(splittedPhraseChars)); } var result = sb.ToString(); if (cases == StringAliasCaseType.CamelCase) { if (result.Length > 1) { var pattern = new Regex("^([A-Z]*)([A-Z].*)$", RegexOptions.Singleline | RegexOptions.Compiled); var match = pattern.Match(result); if (match.Success) { result = match.Groups[1].Value.ToLower() + match.Groups[2].Value; return result.Substring(0, 1).ToLower() + result.Substring(1); } return result; } return result.ToLower(); } return result; } /// /// Encodes as GUID. /// /// The input. /// public static Guid EncodeAsGuid(this string input) { if (string.IsNullOrWhiteSpace(input)) throw new ArgumentNullException("input"); var convertToHex = input.ConvertToHex(); var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32; var hex = convertToHex.Substring(0, hexLength).PadLeft(32, '0'); var output = Guid.Empty; return Guid.TryParse(hex, out output) ? output : Guid.Empty; } /// /// Converts to hex. /// /// The input. /// public static string ConvertToHex(this string input) { if (String.IsNullOrEmpty(input)) return String.Empty; var sb = new StringBuilder(input.Length); foreach (char c in input) { int tmp = c; sb.AppendFormat("{0:x2}", Convert.ToUInt32(c)); } return sb.ToString(); } /// /// Encodes a string to a safe URL base64 string /// /// /// public static string ToUrlBase64(this string input) { if (input == null) throw new ArgumentNullException("input"); if (String.IsNullOrEmpty(input)) return String.Empty; var bytes = Encoding.UTF8.GetBytes(input); return UrlTokenEncode(bytes); //return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ","); } /// /// Decodes a URL safe base64 string back /// /// /// public static string FromUrlBase64(this string input) { if (input == null) throw new ArgumentNullException("input"); //if (input.IsInvalidBase64()) return null; try { //var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "=")); byte[] decodedBytes = UrlTokenDecode(input); return decodedBytes != null ? Encoding.UTF8.GetString(decodedBytes) : null; } catch (FormatException ex) { return null; } } /// /// formats the string with invariant culture /// /// The format. /// The args. /// public static string InvariantFormat(this string format, params object[] args) { return String.Format(CultureInfo.InvariantCulture, format, args); } /// /// Compares 2 strings with invariant culture and case ignored /// /// The compare. /// The compare to. /// public static bool InvariantEquals(this string compare, string compareTo) { return String.Equals(compare, compareTo, StringComparison.InvariantCultureIgnoreCase); } public static bool InvariantContains(this IEnumerable compare, string compareTo) { return compare.Contains(compareTo, new DelegateEqualityComparer((source, dest) => source.Equals(dest, StringComparison.InvariantCultureIgnoreCase), x => x.GetHashCode())); } /// /// Determines if the string is a Guid /// /// /// /// public static bool IsGuid(this string str, bool withHyphens) { var isGuid = false; if (!String.IsNullOrEmpty(str)) { Regex guidRegEx; if (withHyphens) { guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$"); } else { guidRegEx = new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}([0-9a-fA-F]){4}([0-9a-fA-F]){4}([0-9a-fA-F]){4}([0-9a-fA-F]){12}\}{0,1})$"); } isGuid = guidRegEx.IsMatch(str); } return isGuid; } /// /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method /// /// /// /// public static T ParseInto(this string val) { return (T)val.ParseInto(typeof(T)); } /// /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method /// /// /// /// public static object ParseInto(this string val, Type type) { if (!String.IsNullOrEmpty(val)) { TypeConverter tc = TypeDescriptor.GetConverter(type); return tc.ConvertFrom(val); } return val; } /// /// Converts the string to MD5 /// /// referrs to itself /// the md5 hashed string public static string ToMd5(this string stringToConvert) { //create an instance of the MD5CryptoServiceProvider var md5Provider = new MD5CryptoServiceProvider(); //convert our string into byte array var byteArray = Encoding.UTF8.GetBytes(stringToConvert); //get the hashed values created by our MD5CryptoServiceProvider var hashedByteArray = md5Provider.ComputeHash(byteArray); //create a StringBuilder object var stringBuilder = new StringBuilder(); //loop to each each byte foreach (var b in hashedByteArray) { //append it to our StringBuilder stringBuilder.Append(b.ToString("x2").ToLower()); } //return the hashed value return stringBuilder.ToString(); } /// /// Decodes a string that was encoded with UrlTokenEncode /// /// /// internal static byte[] UrlTokenDecode(string input) { if (input == null) { throw new ArgumentNullException("input"); } int length = input.Length; if (length < 1) { return new byte[0]; } int num2 = input[length - 1] - '0'; if ((num2 < 0) || (num2 > 10)) { return null; } char[] inArray = new char[(length - 1) + num2]; for (int i = 0; i < (length - 1); i++) { char ch = input[i]; switch (ch) { case '-': inArray[i] = '+'; break; case '_': inArray[i] = '/'; break; default: inArray[i] = ch; break; } } for (int j = length - 1; j < inArray.Length; j++) { inArray[j] = '='; } return Convert.FromBase64CharArray(inArray, 0, inArray.Length); } /// /// Encodes a string so that it is 'safe' for URLs, files, etc.. /// /// /// internal static string UrlTokenEncode(byte[] input) { if (input == null) { throw new ArgumentNullException("input"); } if (input.Length < 1) { return String.Empty; } string str = null; int index = 0; char[] chArray = null; str = Convert.ToBase64String(input); if (str == null) { return null; } index = str.Length; while (index > 0) { if (str[index - 1] != '=') { break; } index--; } chArray = new char[index + 1]; chArray[index] = (char)((0x30 + str.Length) - index); for (int i = 0; i < index; i++) { char ch = str[i]; switch (ch) { case '+': chArray[i] = '-'; break; case '/': chArray[i] = '_'; break; case '=': chArray[i] = ch; break; default: chArray[i] = ch; break; } } return new string(chArray); } /// /// Ensures that the folder path endds with a DirectorySeperatorChar /// /// /// public static string NormaliseDirectoryPath(this string currentFolder) { currentFolder = currentFolder .IfNull(x => String.Empty) .TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; return currentFolder; } /// /// Truncates the specified text string. /// /// The text. /// Length of the max. /// The suffix. /// public static string Truncate(this string text, int maxLength, string suffix = "...") { // replaces the truncated string to a ... var truncatedString = text; if (maxLength <= 0) return truncatedString; var strLength = maxLength - suffix.Length; if (strLength <= 0) return truncatedString; if (text == null || text.Length <= maxLength) return truncatedString; truncatedString = text.Substring(0, strLength); truncatedString = truncatedString.TrimEnd(); truncatedString += suffix; return truncatedString; } /// /// Strips carrage returns and line feeds from the specified text. /// /// The input. /// public static string StripNewLines(this string input) { return input.Replace("\r", "").Replace("\n", ""); } public static string OrIfNullOrWhiteSpace(this string input, string alternative) { return !string.IsNullOrWhiteSpace(input) ? input : alternative; } } }