From 23b289910124dbfeb19276cc9b494e5ce132cb37 Mon Sep 17 00:00:00 2001 From: "shannon@ShandemVaio" Date: Tue, 17 Jul 2012 03:51:34 +0600 Subject: [PATCH] Imports many of the useful extension methods from v5 that are also used in umbraMVCo to begin the MVC integration. Imported the unit tests associated with each of these imported classes. --- src/Umbraco.Core/DelegateEqualityComparer.cs | 60 ++ src/Umbraco.Core/EnumerableExtensions.cs | 269 ++++++++ src/Umbraco.Core/IfExtensions.cs | 67 ++ src/Umbraco.Core/StringAliasCaseType.cs | 9 + src/Umbraco.Core/StringExtensions.cs | 606 ++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 7 + src/Umbraco.Core/WriteLock.cs | 34 + .../EnumerableExtensionsTests.cs | 60 ++ src/Umbraco.Tests/StringExtensionsTests.cs | 133 ++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 6 + src/Umbraco.Web/ControllerExtensions.cs | 31 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 12 files changed, 1283 insertions(+) create mode 100644 src/Umbraco.Core/DelegateEqualityComparer.cs create mode 100644 src/Umbraco.Core/EnumerableExtensions.cs create mode 100644 src/Umbraco.Core/IfExtensions.cs create mode 100644 src/Umbraco.Core/StringAliasCaseType.cs create mode 100644 src/Umbraco.Core/StringExtensions.cs create mode 100644 src/Umbraco.Core/WriteLock.cs create mode 100644 src/Umbraco.Tests/EnumerableExtensionsTests.cs create mode 100644 src/Umbraco.Tests/StringExtensionsTests.cs create mode 100644 src/Umbraco.Web/ControllerExtensions.cs diff --git a/src/Umbraco.Core/DelegateEqualityComparer.cs b/src/Umbraco.Core/DelegateEqualityComparer.cs new file mode 100644 index 0000000000..140ea016d1 --- /dev/null +++ b/src/Umbraco.Core/DelegateEqualityComparer.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core +{ + /// + /// A custom equality comparer that excepts a delegate to do the comparison operation + /// + /// + public class DelegateEqualityComparer : IEqualityComparer + { + private readonly Func _equals; + private readonly Func _getHashcode; + + #region Implementation of IEqualityComparer + + public DelegateEqualityComparer(Func equals, Func getHashcode) + { + _getHashcode = getHashcode; + _equals = equals; + } + + public static DelegateEqualityComparer CompareMember(Func memberExpression) where TMember : IEquatable + { + return new DelegateEqualityComparer( + (x, y) => memberExpression.Invoke(x).Equals((TMember)memberExpression.Invoke(y)), + x => + { + var invoked = memberExpression.Invoke(x); + return !ReferenceEquals(invoked, default(TMember)) ? invoked.GetHashCode() : 0; + }); + } + + /// + /// Determines whether the specified objects are equal. + /// + /// + /// true if the specified objects are equal; otherwise, false. + /// + /// The first object of type to compare.The second object of type to compare. + public bool Equals(T x, T y) + { + return _equals.Invoke(x, y); + } + + /// + /// Returns a hash code for the specified object. + /// + /// + /// A hash code for the specified object. + /// + /// The for which a hash code is to be returned.The type of is a reference type and is null. + public int GetHashCode(T obj) + { + return _getHashcode.Invoke(obj); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs new file mode 100644 index 0000000000..3fa4847a26 --- /dev/null +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; + +namespace Umbraco.Core +{ + /// + /// Extensions for enumerable sources + /// + public static class EnumerableExtensions + { + public static IEnumerable> InGroupsOf(this IEnumerable source, int groupSize) + { + var i = 0; + var length = source.Count(); + + while ((i * groupSize) < length) + { + yield return source.Skip(i * groupSize).Take(groupSize); + i++; + } + } + + + /// The distinct by. + /// The source. + /// The key selector. + /// Source type + /// Key type + /// the unique list + public static IEnumerable DistinctBy(this IEnumerable source, Func keySelector) + where TKey : IEquatable + { + return source.Distinct(DelegateEqualityComparer.CompareMember(keySelector)); + } + + /// + /// Returns a sequence of length whose elements are the result of invoking . + /// + /// + /// The factory. + /// The count. + /// + public static IEnumerable Range(Func factory, int count) + { + for (int i = 1; i <= count; i++) + { + yield return factory.Invoke(i - 1); + } + } + + /// The if not null. + /// The items. + /// The action. + /// The type + public static void IfNotNull(this IEnumerable items, Action action) where TItem : class + { + if (items != null) + { + foreach (TItem item in items) + { + item.IfNotNull(action); + } + } + } + + /// The for each. + /// The items. + /// The func. + /// item type + /// Result type + /// the Results + public static TResult[] ForEach(this IEnumerable items, Func func) + { + return items.Select(func).ToArray(); + } + + /// The for each. + /// The items. + /// The action. + /// Item type + /// list of TItem + public static IEnumerable ForEach(this IEnumerable items, Action action) + { + if (items != null) + { + foreach (TItem item in items) + { + action(item); + } + } + + return items; + } + + /// The flatten list. + /// The items. + /// The select child. + /// Item type + /// list of TItem + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "By design")] + public static IEnumerable FlattenList(this IEnumerable items, Func> selectChild) + { + IEnumerable children = items != null && items.Any() + ? items.SelectMany(selectChild).FlattenList(selectChild) + : Enumerable.Empty(); + + if (items != null) + { + return items.Concat(children); + } + + return null; + } + + /// + /// Returns true if all items in the other collection exist in this collection + /// + /// + /// + /// + /// + public static bool ContainsAll(this IEnumerable source, IEnumerable other) + { + var matches = true; + foreach (var i in other) + { + matches = source.Contains(i); + if (!matches) break; + } + return matches; + } + + /// + /// Returns true if the source contains any of the items in the other list + /// + /// + /// + /// + /// + public static bool ContainsAny(this IEnumerable source, IEnumerable other) + { + return other.Any(i => source.Contains(i)); + } + + /// + /// Removes all matching items from an . + /// + /// + /// The list. + /// The predicate. + /// + public static void RemoveAll(this IList list, Func predicate) + { + for (var i = 0; i < list.Count; i++) + { + if (predicate(list[i])) + { + list.RemoveAt(i--); + } + } + } + + /// + /// Removes all matching items from an . + /// + /// + /// The list. + /// The predicate. + /// + public static void RemoveAll(this ICollection list, Func predicate) + { + var matches = list.Where(predicate).ToArray(); + foreach (var match in matches) + { + list.Remove(match); + } + } + + public static IEnumerable SelectRecursive( + this IEnumerable source, + Func> recursiveSelector, int maxRecusionDepth = 100) + { + var stack = new Stack>(); + stack.Push(source.GetEnumerator()); + + try + { + while (stack.Count > 0) + { + if (stack.Count > maxRecusionDepth) + throw new InvalidOperationException("Maximum recursion depth reached of " + maxRecusionDepth); + + if (stack.Peek().MoveNext()) + { + var current = stack.Peek().Current; + + yield return current; + + stack.Push(recursiveSelector(current).GetEnumerator()); + } + else + { + stack.Pop().Dispose(); + } + } + } + finally + { + while (stack.Count > 0) + { + stack.Pop().Dispose(); + } + } + } + + /// + /// Filters a sequence of values to ignore those which are null. + /// + /// + /// The coll. + /// + /// + public static IEnumerable WhereNotNull(this IEnumerable coll) where T : class + { + return coll.Where(x => x != null); + } + + public static IEnumerable ForAllThatAre(this IEnumerable sequence, Action projection) + where TActual : class + { + return sequence.Select( + x => + { + if (typeof(TActual).IsAssignableFrom(x.GetType())) + { + var casted = x as TActual; + projection.Invoke(casted); + } + return x; + }); + } + + ///Finds the index of the first item matching an expression in an enumerable. + ///The enumerable to search. + ///The expression to test the items against. + ///The index of the first matching item, or -1 if no items match. + public static int FindIndex(this IEnumerable items, Func predicate) + { + if (items == null) throw new ArgumentNullException("items"); + if (predicate == null) throw new ArgumentNullException("predicate"); + + var retVal = 0; + foreach (var item in items) + { + if (predicate(item)) return retVal; + retVal++; + } + return -1; + } + ///Finds the index of the first occurence of an item in an enumerable. + ///The enumerable to search. + ///The item to find. + ///The index of the first matching item, or -1 if the item was not found. + public static int IndexOf(this IEnumerable items, T item) { return items.FindIndex(i => EqualityComparer.Default.Equals(item, i)); } + } +} diff --git a/src/Umbraco.Core/IfExtensions.cs b/src/Umbraco.Core/IfExtensions.cs new file mode 100644 index 0000000000..13c1a11f36 --- /dev/null +++ b/src/Umbraco.Core/IfExtensions.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core +{ + + /// + /// Extension methods for 'If' checking like checking If something is null or not null + /// + public static class IfExtensions + { + + /// The if not null. + /// The item. + /// The action. + /// The type + public static void IfNotNull(this TItem item, Action action) where TItem : class + { + if (item != null) + { + action(item); + } + } + + /// The if true. + /// The predicate. + /// The action. + public static void IfTrue(this bool predicate, Action action) + { + if (predicate) + { + action(); + } + } + + /// + /// Checks if the item is not null, and if so returns an action on that item, or a default value + /// + /// the result type + /// The type + /// The item. + /// The action. + /// The default value. + /// + public static TResult IfNotNull(this TItem item, Func action, TResult defaultValue = default(TResult)) + where TItem : class + { + return item != null ? action(item) : defaultValue; + } + + /// + /// Checks if the value is null, if it is it returns the value specified, otherwise returns the non-null value + /// + /// + /// + /// + /// + public static TItem IfNull(this TItem item, Func action) + where TItem : class + { + return item ?? action(item); + } + + } +} diff --git a/src/Umbraco.Core/StringAliasCaseType.cs b/src/Umbraco.Core/StringAliasCaseType.cs new file mode 100644 index 0000000000..420c504897 --- /dev/null +++ b/src/Umbraco.Core/StringAliasCaseType.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Core +{ + public enum StringAliasCaseType + { + PascalCase, + CamelCase, + Unchanged + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs new file mode 100644 index 0000000000..4469af3176 --- /dev/null +++ b/src/Umbraco.Core/StringExtensions.cs @@ -0,0 +1,606 @@ +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 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; + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 4882d98267..b45944b80c 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -33,6 +33,7 @@ + @@ -43,7 +44,13 @@ Properties\SolutionInfo.cs + + + + + +