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;
using System.Xml;
using Newtonsoft.Json;
using Umbraco.Core.Configuration;
using System.Web.Security;
using Umbraco.Core.Strings;
using Umbraco.Core.CodeAnnotations;
namespace Umbraco.Core
{
///
/// String extension methods
///
public static class StringExtensions
{
[UmbracoWillObsolete("Do not use this constants. See IShortStringHelper.CleanStringForSafeAliasJavaScriptCode.")]
public const string UmbracoValidAliasCharacters = "_-abcdefghijklmnopqrstuvwxyz1234567890";
[UmbracoWillObsolete("Do not use this constants. See IShortStringHelper.CleanStringForSafeAliasJavaScriptCode.")]
public const string UmbracoInvalidFirstCharacters = "01234567890";
private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray();
private static readonly char[] ToCSharpEscapeChars;
static StringExtensions()
{
var escapes = new[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" };
ToCSharpEscapeChars = new char[escapes.Max(e => e[0]) + 1];
foreach (var escape in escapes)
ToCSharpEscapeChars[escape[0]] = escape[1];
}
///
/// This tries to detect a json string, this is not a fail safe way but it is quicker than doing
/// a try/catch when deserializing when it is not json.
///
///
///
internal static bool DetectIsJson(this string input)
{
input = input.Trim();
return (input.StartsWith("{") && input.EndsWith("}"))
|| (input.StartsWith("[") && input.EndsWith("]"));
}
///
/// Returns a JObject/JArray instance if the string can be converted to json, otherwise returns the string
///
///
///
internal static object ConvertToJsonIfPossible(this string input)
{
if (input.DetectIsJson() == false)
{
return input;
}
try
{
var obj = JsonConvert.DeserializeObject(input);
return obj;
}
catch (Exception ex)
{
return input;
}
}
internal static string ReplaceNonAlphanumericChars(this string input, char replacement)
{
//any character that is not alphanumeric, convert to a hyphen
var mName = input;
foreach (var c in mName.ToCharArray().Where(c => !char.IsLetterOrDigit(c)))
{
mName = mName.Replace(c, replacement);
}
return mName;
}
///
/// Cleans string to aid in preventing xss attacks.
///
///
///
internal static string CleanForXss(this string input)
{
//remove any html
input = input.StripHtml();
//strip out any potential chars involved with XSS
return input.ExceptChars(new HashSet("*?(){}[];:%<>/\\|&'\"".ToCharArray()));
}
public static string ExceptChars(this string str, HashSet toExclude)
{
var sb = new StringBuilder(str.Length);
foreach (var c in str.Where(c => toExclude.Contains(c) == false))
{
sb.Append(c);
}
return sb.ToString();
}
///
/// Returns a stream from a string
///
///
///
internal static Stream GenerateStreamFromString(this string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
///
/// This will append the query string to the URL
///
///
///
///
///
/// This methods ensures that the resulting URL is structured correctly, that there's only one '?' and that things are
/// delimited properly with '&'
///
internal static string AppendQueryStringToUrl(this string url, params string[] queryStrings)
{
//remove any prefixed '&' or '?'
for (var i = 0; i < queryStrings.Length; i++)
{
queryStrings[i] = queryStrings[i].TrimStart('?', '&').TrimEnd('&');
}
var nonEmpty = queryStrings.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
if (url.Contains("?"))
{
return url + string.Join("&", nonEmpty).EnsureStartsWith('&');
}
return url + string.Join("&", nonEmpty).EnsureStartsWith('?');
}
///
/// Encrypt the string using the MachineKey in medium trust
///
/// The string value to be encrypted.
/// The encrypted string.
public static string EncryptWithMachineKey(this string value)
{
if (value == null)
return null;
string valueToEncrypt = value;
List parts = new List();
const int EncrpytBlockSize = 500;
while (valueToEncrypt.Length > EncrpytBlockSize)
{
parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize));
valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize);
}
if (valueToEncrypt.Length > 0)
{
parts.Add(valueToEncrypt);
}
StringBuilder encrpytedValue = new StringBuilder();
foreach (var part in parts)
{
var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part));
encrpytedValue.AppendLine(encrpytedBlock);
}
return encrpytedValue.ToString().TrimEnd();
}
///
/// Decrypt the encrypted string using the Machine key in medium trust
///
/// The string value to be decrypted
/// The decrypted string.
public static string DecryptWithMachineKey(this string value)
{
if (value == null)
return null;
string[] parts = value.Split('\n');
StringBuilder decryptedValue = new StringBuilder();
foreach (var part in parts)
{
decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData);
}
return decryptedValue.ToString();
}
//this is from SqlMetal and just makes it a bit of fun to allow pluralisation
public static string MakePluralName(this string name)
{
if ((name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || name.EndsWith("ch", StringComparison.OrdinalIgnoreCase)) || (name.EndsWith("ss", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase)))
{
name = name + "es";
return name;
}
if ((name.EndsWith("y", StringComparison.OrdinalIgnoreCase) && (name.Length > 1)) && !IsVowel(name[name.Length - 2]))
{
name = name.Remove(name.Length - 1, 1);
name = name + "ies";
return name;
}
if (!name.EndsWith("s", StringComparison.OrdinalIgnoreCase))
{
name = name + "s";
}
return name;
}
public static bool IsVowel(this char c)
{
switch (c)
{
case 'O':
case 'U':
case 'Y':
case 'A':
case 'E':
case 'I':
case 'o':
case 'u':
case 'y':
case 'a':
case 'e':
case 'i':
return true;
}
return false;
}
///
/// 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 EncodeJsString(this string s)
{
var sb = new StringBuilder();
foreach (var c in s)
{
switch (c)
{
case '\"':
sb.Append("\\\"");
break;
case '\\':
sb.Append("\\\\");
break;
case '\b':
sb.Append("\\b");
break;
case '\f':
sb.Append("\\f");
break;
case '\n':
sb.Append("\\n");
break;
case '\r':
sb.Append("\\r");
break;
case '\t':
sb.Append("\\t");
break;
default:
int i = (int)c;
if (i < 32 || i > 127)
{
sb.AppendFormat("\\u{0:X04}", i);
}
else
{
sb.Append(c);
}
break;
}
}
return sb.ToString();
}
public static string TrimEnd(this string value, string forRemoving)
{
if (string.IsNullOrEmpty(value)) return value;
if (string.IsNullOrEmpty(forRemoving)) 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;
if (string.IsNullOrEmpty(forRemoving)) 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(CultureInfo.InvariantCulture)) ? input : value + input;
}
public static string EnsureEndsWith(this string input, char value)
{
return input.EndsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : input + value;
}
public static bool IsLowerCase(this char ch)
{
return ch.ToString(CultureInfo.InvariantCulture) == ch.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
}
public static bool IsUpperCase(this char ch)
{
return ch.ToString(CultureInfo.InvariantCulture) == ch.ToString(CultureInfo.InvariantCulture).ToUpperInvariant();
}
/// 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;
}
}
///
/// Parse string to Enum
///
/// The enum type
/// The string to parse
/// The ignore case
/// The parsed enum
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By Design")]
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By Design")]
public static T EnumParse(this string strType, bool ignoreCase)
{
return (T)Enum.Parse(typeof(T), strType, ignoreCase);
}
///
/// 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);
}
///
/// 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);
}
///
/// Converts an integer to an invariant formatted string
///
///
///
public static string ToInvariantString(this int str)
{
return str.ToString(CultureInfo.InvariantCulture);
}
///
/// 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 InvariantStartsWith(this string compare, string compareTo)
{
return compare.StartsWith(compareTo, StringComparison.InvariantCultureIgnoreCase);
}
public static bool InvariantEndsWith(this string compare, string compareTo)
{
return compare.EndsWith(compareTo, StringComparison.InvariantCultureIgnoreCase);
}
public static bool InvariantContains(this string compare, string compareTo)
{
return compare.IndexOf(compareTo, StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool InvariantContains(this IEnumerable compare, string compareTo)
{
return compare.Contains(compareTo, StringComparer.InvariantCultureIgnoreCase);
}
///
/// 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;
}
///
/// Returns a copy of the string with the first character converted to uppercase.
///
/// The string.
/// The converted string.
public static string ToFirstUpper(this string input)
{
return string.IsNullOrWhiteSpace(input)
? input
: input.Substring(0, 1).ToUpper() + input.Substring(1);
}
///
/// Returns a copy of the string with the first character converted to lowercase.
///
/// The string.
/// The converted string.
public static string ToFirstLower(this string input)
{
return string.IsNullOrWhiteSpace(input)
? input
: input.Substring(0, 1).ToLower() + input.Substring(1);
}
///
/// Returns a copy of the string with the first character converted to uppercase using the casing rules of the specified culture.
///
/// The string.
/// The culture.
/// The converted string.
public static string ToFirstUpper(this string input, CultureInfo culture)
{
return string.IsNullOrWhiteSpace(input)
? input
: input.Substring(0, 1).ToUpper(culture) + input.Substring(1);
}
///
/// Returns a copy of the string with the first character converted to lowercase using the casing rules of the specified culture.
///
/// The string.
/// The culture.
/// The converted string.
public static string ToFirstLower(this string input, CultureInfo culture)
{
return string.IsNullOrWhiteSpace(input)
? input
: input.Substring(0, 1).ToLower(culture) + input.Substring(1);
}
///
/// Returns a copy of the string with the first character converted to uppercase using the casing rules of the invariant culture.
///
/// The string.
/// The converted string.
public static string ToFirstUpperInvariant(this string input)
{
return string.IsNullOrWhiteSpace(input)
? input
: input.Substring(0, 1).ToUpperInvariant() + input.Substring(1);
}
///
/// Returns a copy of the string with the first character converted to lowercase using the casing rules of the invariant culture.
///
/// The string.
/// The converted string.
public static string ToFirstLowerInvariant(this string input)
{
return string.IsNullOrWhiteSpace(input)
? input
: input.Substring(0, 1).ToLowerInvariant() + input.Substring(1);
}
///
/// Gets the short string helper.
///
/// This is so that unit tests that do not initialize the resolver do not
/// fail and fall back to defaults. When running the whole Umbraco, CoreBootManager
/// does initialise the resolver.
private static IShortStringHelper ShortStringHelper
{
get
{
if (ShortStringHelperResolver.HasCurrent)
return ShortStringHelperResolver.Current.Helper;
if (_helper != null)
return _helper;
// we don't want Umbraco to die because the resolver hasn't been initialized
// as the ShortStringHelper is too important, so as long as it's not there
// already, we use a default one. That should never happen, but...
Logging.LogHelper.Warn("ShortStringHelperResolver.HasCurrent == false, fallback to default.");
_helper = new DefaultShortStringHelper().WithDefaultConfig();
_helper.Freeze();
return _helper;
}
}
private static IShortStringHelper _helper;
///
/// Returns a new string in which all occurences of specified strings are replaced by other specified strings.
///
/// The string to filter.
/// The replacements definition.
/// The filtered string.
public static string ReplaceMany(this string text, IDictionary replacements)
{
return ShortStringHelper.ReplaceMany(text, replacements);
}
///
/// Returns a new string in which all occurences of specified characters are replaced by a specified character.
///
/// The string to filter.
/// The characters to replace.
/// The replacement character.
/// The filtered string.
public static string ReplaceMany(this string text, char[] chars, char replacement)
{
return ShortStringHelper.ReplaceMany(text, chars, replacement);
}
// FORMAT STRINGS
// note: LegacyShortStringHelper will produce a 100% backward-compatible output for ToUrlAlias.
// this is the only reason why we keep the method, otherwise it should be removed, and with any other
// helper we fallback to ToUrlSegment anyway.
///
/// 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.
///
[Obsolete("This method should be removed. Use ToUrlSegment instead.")]
public static string ToUrlAlias(this string value, IDictionary charReplacements, bool replaceDoubleDashes, bool stripNonAscii, bool urlEncode)
{
var helper = ShortStringHelper;
var legacy = helper as LegacyShortStringHelper;
return legacy != null
? legacy.LegacyToUrlAlias(value, charReplacements, replaceDoubleDashes, stripNonAscii, urlEncode)
: helper.CleanStringForUrlSegment(value);
}
// note: LegacyShortStringHelper will produce a 100% backward-compatible output for FormatUrl.
// this is the only reason why we keep the method, otherwise it should be removed, and with any other
// helper we fallback to ToUrlSegment anyway.
///
/// Cleans a string to produce a string that can safely be used in an url segment.
///
/// The text to filter.
/// The safe url segment.
///
/// When using the legacy ShortStringHelper, uses UmbracoSettings.UrlReplaceCharacters
/// and UmbracoSettings.RemoveDoubleDashesFromUrlReplacing.
/// Other helpers may use different parameters.
///
[Obsolete("This method should be removed. Use ToUrlSegment instead.")]
public static string FormatUrl(this string url)
{
var helper = ShortStringHelper;
var legacy = helper as LegacyShortStringHelper;
return legacy != null ? legacy.LegacyFormatUrl(url) : helper.CleanStringForUrlSegment(url);
}
// note: LegacyShortStringHelper will produce a 100% backward-compatible output for ToSafeAlias
// other helpers may not. DefaultShortStringHelper produces better, but non-compatible, results.
///
/// Cleans a string to produce a string that can safely be used in an alias.
///
/// The text to filter.
/// The safe alias.
public static string ToSafeAlias(this string alias)
{
return ShortStringHelper.CleanStringForSafeAlias(alias);
}
///
/// Cleans a string to produce a string that can safely be used in an alias.
///
/// The text to filter.
/// A value indicating that we want to camel-case the alias.
/// The safe alias.
public static string ToSafeAlias(this string alias, bool camel)
{
var a = ShortStringHelper.CleanStringForSafeAlias(alias);
if (string.IsNullOrWhiteSpace(a) || camel == false) return a;
return char.ToLowerInvariant(a[0]) + a.Substring(1);
}
///
/// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias.
///
/// The text to filter.
/// The culture.
/// The safe alias.
public static string ToSafeAlias(this string alias, CultureInfo culture)
{
return ShortStringHelper.CleanStringForSafeAlias(alias, culture);
}
///
/// Cleans (but only if required) a string to produce a string that can safely be used in an alias.
///
/// The text to filter.
/// The safe alias.
/// Checks UmbracoSettings.ForceSafeAliases to determine whether it should filter the text.
public static string ToSafeAliasWithForcingCheck(this string alias)
{
return UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? alias.ToSafeAlias() : alias;
}
///
/// Cleans (but only if required) a string, in the context of a specified culture, to produce a string that can safely be used in an alias.
///
/// The text to filter.
/// The culture.
/// The safe alias.
/// Checks UmbracoSettings.ForceSafeAliases to determine whether it should filter the text.
public static string ToSafeAliasWithForcingCheck(this string alias, CultureInfo culture)
{
return UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? alias.ToSafeAlias(culture) : alias;
}
// note: LegacyShortStringHelper will produce a 100% backward-compatible output for ToUmbracoAlias.
// this is the only reason why we keep the method, otherwise it should be removed, and with any other
// helper we fallback to ToSafeAlias anyway.
///
/// Cleans a string to produce a string that can safely be used in an alias.
///
/// The text to filter.
/// The case type. THIS PARAMETER IS IGNORED.
/// Indicates whether spaces should be removed. THIS PARAMETER IS IGNORED.
/// The safe alias.
/// CamelCase, and remove spaces, whatever the parameters.
[Obsolete("This method should be removed. Use ToSafeAlias instead.")]
public static string ToUmbracoAlias(this string phrase, StringAliasCaseType caseType = StringAliasCaseType.CamelCase, bool removeSpaces = false)
{
var helper = ShortStringHelper;
var legacy = helper as LegacyShortStringHelper;
return legacy != null ? legacy.LegacyCleanStringForUmbracoAlias(phrase) : helper.CleanStringForSafeAlias(phrase);
}
// the new methods to get a url segment
///
/// Cleans a string to produce a string that can safely be used in an url segment.
///
/// The text to filter.
/// The safe url segment.
public static string ToUrlSegment(this string text)
{
return ShortStringHelper.CleanStringForUrlSegment(text);
}
///
/// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment.
///
/// The text to filter.
/// The culture.
/// The safe url segment.
public static string ToUrlSegment(this string text, CultureInfo culture)
{
return ShortStringHelper.CleanStringForUrlSegment(text, culture);
}
// note: LegacyShortStringHelper will produce 100% backward-compatible output for ConvertCase.
// this is the only reason why we keep the method, otherwise it should be removed, and with any other
// helper we fallback to CleanString(ascii, alias) anyway.
///
/// Filters a string to convert case, and more.
///
/// the text to filter.
/// The string case type.
/// The filtered text.
///
/// This is the legacy method, so we can't really change it, although it has issues (see unit tests).
/// It does more than "converting the case", and also remove spaces, etc.
///
[Obsolete("This method should be removed. Use CleanString instead.")]
public static string ConvertCase(this string phrase, StringAliasCaseType cases)
{
var helper = ShortStringHelper;
var legacy = helper as LegacyShortStringHelper;
var cases2 = cases.ToCleanStringType() & CleanStringType.CaseMask;
return legacy != null
? legacy.LegacyConvertStringCase(phrase, cases2)
: helper.CleanString(phrase, CleanStringType.Ascii | CleanStringType.ConvertCase | cases2);
}
// the new methods to clean a string (to alias, url segment...)
///
/// Cleans a string.
///
/// The text to clean.
/// A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.
/// The clean string.
/// The string is cleaned in the context of the IShortStringHelper default culture.
public static string ToCleanString(this string text, CleanStringType stringType)
{
return ShortStringHelper.CleanString(text, stringType);
}
///
/// Cleans a string, using a specified separator.
///
/// The text to clean.
/// A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.
/// The separator.
/// The clean string.
/// The string is cleaned in the context of the IShortStringHelper default culture.
public static string ToCleanString(this string text, CleanStringType stringType, char separator)
{
return ShortStringHelper.CleanString(text, stringType, separator);
}
///
/// Cleans a string in the context of a specified culture.
///
/// The text to clean.
/// A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.
/// The culture.
/// The clean string.
public static string ToCleanString(this string text, CleanStringType stringType, CultureInfo culture)
{
return ShortStringHelper.CleanString(text, stringType, culture);
}
///
/// Cleans a string in the context of a specified culture, using a specified separator.
///
/// The text to clean.
/// A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.
/// The separator.
/// The culture.
/// The clean string.
public static string ToCleanString(this string text, CleanStringType stringType, char separator, CultureInfo culture)
{
return ShortStringHelper.CleanString(text, stringType, separator, culture);
}
// note: LegacyShortStringHelper will produce 100% backward-compatible output for SplitPascalCasing.
// other helpers may not. DefaultShortStringHelper produces better, but non-compatible, results.
///
/// Splits a Pascal cased string into a phrase seperated by spaces.
///
/// The text to split.
/// The splitted text.
public static string SplitPascalCasing(this string phrase)
{
return ShortStringHelper.SplitPascalCasing(phrase, ' ');
}
///
/// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename,
/// both internally (on disk) and externally (as a url).
///
/// The text to filter.
/// The safe filename.
public static string ToSafeFileName(this string text)
{
return ShortStringHelper.CleanStringForSafeFileName(text);
}
///
/// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename,
/// both internally (on disk) and externally (as a url).
///
/// The text to filter.
/// The culture.
/// The safe filename.
public static string ToSafeFileName(this string text, CultureInfo culture)
{
return ShortStringHelper.CleanStringForSafeFileName(text, culture);
}
///
/// An extension method that returns a new string in which all occurrences of a
/// specified string in the current instance are replaced with another specified string.
/// StringComparison specifies the type of search to use for the specified string.
///
/// Current instance of the string
/// Specified string to replace
/// Specified string to inject
/// String Comparison object to specify search type
/// Updated string
public static string Replace(this string source, string oldString, string newString, StringComparison stringComparison)
{
// This initialisation ensures the first check starts at index zero of the source. On successive checks for
// a match, the source is skipped to immediately after the last replaced occurrence for efficiency
// and to avoid infinite loops when oldString and newString compare equal.
int index = -1 * newString.Length;
// Determine if there are any matches left in source, starting from just after the result of replacing the last match.
while((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0)
{
// Remove the old text.
source = source.Remove(index, oldString.Length);
// Add the replacemenet text.
source = source.Insert(index, newString);
}
return source;
}
///
/// Converts a literal string into a C# expression.
///
/// Current instance of the string.
/// The string in a C# format.
public static string ToCSharpString(this string s)
{
if (s == null) return "";
// http://stackoverflow.com/questions/323640/can-i-convert-a-c-sharp-string-value-to-an-escaped-string-literal
var sb = new StringBuilder(s.Length + 2);
for (var rp = 0; rp < s.Length; rp++)
{
var c = s[rp];
if (c < ToCSharpEscapeChars.Length && '\0' != ToCSharpEscapeChars[c])
sb.Append('\\').Append(ToCSharpEscapeChars[c]);
else if ('~' >= c && c >= ' ')
sb.Append(c);
else
sb.Append(@"\x")
.Append(ToCSharpHexDigitLower[c >> 12 & 0x0F])
.Append(ToCSharpHexDigitLower[c >> 8 & 0x0F])
.Append(ToCSharpHexDigitLower[c >> 4 & 0x0F])
.Append(ToCSharpHexDigitLower[c & 0x0F]);
}
return sb.ToString();
// requires full trust
/*
using (var writer = new StringWriter())
using (var provider = CodeDomProvider.CreateProvider("CSharp"))
{
provider.GenerateCodeFromExpression(new CodePrimitiveExpression(s), writer, null);
return writer.ToString().Replace(string.Format("\" +{0}\t\"", Environment.NewLine), "");
}
*/
}
public static bool ContainsAny(this string haystack, IEnumerable needles, StringComparison comparison = StringComparison.CurrentCulture)
{
if (haystack == null) throw new ArgumentNullException("haystack");
if (string.IsNullOrEmpty(haystack) == false || needles.Any())
{
return needles.Any(value => haystack.IndexOf(value) >= 0);
}
return false;
}
public static bool CsvContains(this string csv, string value)
{
if (string.IsNullOrEmpty(csv))
{
return false;
}
var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
return idCheckList.Contains(value);
}
}
}