using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Strings;
namespace Umbraco.Core
{
///
/// String extension methods
///
public static class StringExtensions
{
///
/// Based on the input string, this will detect if the string is a JS path or a JS snippet.
/// If a path cannot be determined, then it is assumed to be a snippet the original text is returned
/// with an invalid attempt, otherwise a valid attempt is returned with the resolved path
///
///
///
///
/// This is only used for legacy purposes for the Action.JsSource stuff and shouldn't be needed in v8
///
internal static Attempt DetectIsJavaScriptPath(this string input)
{
//validate that this is a url, if it is not, we'll assume that it is a text block and render it as a text
//block instead.
var isValid = true;
if (Uri.IsWellFormedUriString(input, UriKind.RelativeOrAbsolute))
{
//ok it validates, but so does alert('hello'); ! so we need to do more checks
//here are the valid chars in a url without escaping
if (Regex.IsMatch(input, @"[^a-zA-Z0-9-._~:/?#\[\]@!$&'\(\)*\+,%;=]"))
isValid = false;
//we'll have to be smarter and just check for certain js patterns now too!
var jsPatterns = new[] { @"\+\s*\=", @"\);", @"function\s*\(", @"!=", @"==" };
if (jsPatterns.Any(p => Regex.IsMatch(input, p)))
isValid = false;
if (isValid)
{
var ioHelper = Current.Factory.GetInstance();
var resolvedUrlResult = ioHelper.TryResolveUrl(input);
//if the resolution was success, return it, otherwise just return the path, we've detected
// it's a path but maybe it's relative and resolution has failed, etc... in which case we're just
// returning what was given to us.
return resolvedUrlResult.Success
? resolvedUrlResult
: Attempt.Succeed(input);
}
}
return Attempt.Fail(input);
}
// FORMAT STRINGS
///
/// 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 Current.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 = Current.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, string culture)
{
return Current.ShortStringHelper.CleanStringForSafeAlias(alias, culture);
}
// 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)
{
if (text == null) throw new ArgumentNullException(nameof(text));
if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text));
return Current.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, string culture)
{
if (text == null) throw new ArgumentNullException(nameof(text));
if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text));
return Current.ShortStringHelper.CleanStringForUrlSegment(text, culture);
}
// 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 ICurrent.ShortStringHelper default culture.
public static string ToCleanString(this string text, CleanStringType stringType)
{
return Current.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 ICurrent.ShortStringHelper default culture.
public static string ToCleanString(this string text, CleanStringType stringType, char separator)
{
return Current.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, string culture)
{
return Current.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, string culture)
{
return Current.ShortStringHelper.CleanString(text, stringType, separator, culture);
}
// note: LegacyCurrent.ShortStringHelper will produce 100% backward-compatible output for SplitPascalCasing.
// other helpers may not. DefaultCurrent.ShortStringHelper produces better, but non-compatible, results.
///
/// Splits a Pascal cased string into a phrase separated by spaces.
///
/// The text to split.
/// The split text.
public static string SplitPascalCasing(this string phrase)
{
return Current.ShortStringHelper.SplitPascalCasing(phrase, ' ');
}
//NOTE: Not sure what this actually does but is used a few places, need to figure it out and then move to StringExtensions and obsolete.
// it basically is yet another version of SplitPascalCasing
// plugging string extensions here to be 99% compatible
// the only diff. is with numbers, Number6Is was "Number6 Is", and the new string helper does it too,
// but the legacy one does "Number6Is"... assuming it is not a big deal.
internal static string SpaceCamelCasing(this string phrase)
{
return phrase.Length < 2 ? phrase : phrase.SplitPascalCasing().ToFirstUpperInvariant();
}
///
/// 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 Current.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, string culture)
{
return Current.ShortStringHelper.CleanStringForSafeFileName(text, culture);
}
///
/// Checks if a given path is a full path including drive letter
///
///
///
// From: http://stackoverflow.com/a/35046453/5018
internal static bool IsFullPath(this string path)
{
return string.IsNullOrWhiteSpace(path) == false
&& path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1
&& Path.IsPathRooted(path)
&& Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false;
}
///
/// Ensures that a path has `~/` as prefix
///
///
///
internal static string EnsurePathIsApplicationRootPrefixed(this string path)
{
if (path.StartsWith("~/"))
return path;
if (path.StartsWith("/") == false && path.StartsWith("\\") == false)
path = string.Format("/{0}", path);
if (path.StartsWith("~") == false)
path = string.Format("~{0}", path);
return path;
}
}
}