Resvolution - ShortStringHelperResolver

This commit is contained in:
Stephan
2016-08-24 19:30:33 +02:00
parent 376dec2fed
commit 3707db6c21
19 changed files with 412 additions and 514 deletions

View File

@@ -457,8 +457,8 @@ namespace Umbraco.Core
.Append(PluginManager.ResolveTypes<IPropertyValueConverter>());
// use the new DefaultShortStringHelper
ShortStringHelperResolver.Current = new ShortStringHelperResolver(Container,
factory => new DefaultShortStringHelper(factory.GetInstance<IUmbracoSettingsSection>()).WithDefaultConfig());
Container.RegisterSingleton<IShortStringHelper>(factory
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance<IUmbracoSettingsSection>())));
UrlSegmentProviderCollectionBuilder.Register(Container)
.Append<DefaultUrlSegmentProvider>();

View File

@@ -1,6 +1,8 @@
using System;
using LightInject;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
@@ -38,6 +40,7 @@ namespace Umbraco.Core.DependencyInjection
internal static void Reset()
{
_container = null;
_shortStringHelper = null;
Resetted?.Invoke(null, EventArgs.Empty);
}
@@ -78,6 +81,26 @@ namespace Umbraco.Core.DependencyInjection
public static ICultureDictionaryFactory CultureDictionaryFactory
=> Container.GetInstance<ICultureDictionaryFactory>();
private static IShortStringHelper _shortStringHelper;
public static IShortStringHelper ShortStringHelper
{
get
{
// fixme - refactor
// 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... in can, in
// some tests - we should really cleanup our tests and get rid of this!
if (_shortStringHelper != null) return _shortStringHelper;
var reg = HasContainer ? Container.GetAvailableService<IShortStringHelper>() : null;
return _shortStringHelper = reg == null
? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(UmbracoConfig.For.UmbracoSettings()))
: Container.GetInstance<IShortStringHelper>();
}
}
#endregion
}
}

View File

@@ -15,6 +15,7 @@ using Umbraco.Core.Configuration;
using System.Web.Security;
using Umbraco.Core.Strings;
using Umbraco.Core.CodeAnnotations;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.IO;
namespace Umbraco.Core
@@ -998,32 +999,6 @@ namespace Umbraco.Core
: input.Substring(0, 1).ToLowerInvariant() + input.Substring(1);
}
/// <summary>
/// Gets the short string helper.
/// </summary>
/// <remarks>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.</remarks>
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<IShortStringHelper>("ShortStringHelperResolver.HasCurrent == false, fallback to default.");
_helper = new DefaultShortStringHelper(UmbracoConfig.For.UmbracoSettings()).WithDefaultConfig();
_helper.Freeze();
return _helper;
}
}
private static IShortStringHelper _helper;
/// <summary>
/// Returns a new string in which all occurences of specified strings are replaced by other specified strings.
/// </summary>
@@ -1032,7 +1007,7 @@ namespace Umbraco.Core
/// <returns>The filtered string.</returns>
public static string ReplaceMany(this string text, IDictionary<string, string> replacements)
{
return ShortStringHelper.ReplaceMany(text, replacements);
return Current.ShortStringHelper.ReplaceMany(text, replacements);
}
/// <summary>
@@ -1044,7 +1019,7 @@ namespace Umbraco.Core
/// <returns>The filtered string.</returns>
public static string ReplaceMany(this string text, char[] chars, char replacement)
{
return ShortStringHelper.ReplaceMany(text, chars, replacement);
return Current.ShortStringHelper.ReplaceMany(text, chars, replacement);
}
// FORMAT STRINGS
@@ -1056,7 +1031,7 @@ namespace Umbraco.Core
/// <returns>The safe alias.</returns>
public static string ToSafeAlias(this string alias)
{
return ShortStringHelper.CleanStringForSafeAlias(alias);
return Current.ShortStringHelper.CleanStringForSafeAlias(alias);
}
/// <summary>
@@ -1067,7 +1042,7 @@ namespace Umbraco.Core
/// <returns>The safe alias.</returns>
public static string ToSafeAlias(this string alias, bool camel)
{
var a = ShortStringHelper.CleanStringForSafeAlias(alias);
var a = Current.ShortStringHelper.CleanStringForSafeAlias(alias);
if (string.IsNullOrWhiteSpace(a) || camel == false) return a;
return char.ToLowerInvariant(a[0]) + a.Substring(1);
}
@@ -1080,7 +1055,7 @@ namespace Umbraco.Core
/// <returns>The safe alias.</returns>
public static string ToSafeAlias(this string alias, CultureInfo culture)
{
return ShortStringHelper.CleanStringForSafeAlias(alias, culture);
return Current.ShortStringHelper.CleanStringForSafeAlias(alias, culture);
}
/// <summary>
@@ -1115,7 +1090,7 @@ namespace Umbraco.Core
/// <returns>The safe url segment.</returns>
public static string ToUrlSegment(this string text)
{
return ShortStringHelper.CleanStringForUrlSegment(text);
return Current.ShortStringHelper.CleanStringForUrlSegment(text);
}
/// <summary>
@@ -1126,7 +1101,7 @@ namespace Umbraco.Core
/// <returns>The safe url segment.</returns>
public static string ToUrlSegment(this string text, CultureInfo culture)
{
return ShortStringHelper.CleanStringForUrlSegment(text, culture);
return Current.ShortStringHelper.CleanStringForUrlSegment(text, culture);
}
// the new methods to clean a string (to alias, url segment...)
@@ -1138,10 +1113,10 @@ namespace Umbraco.Core
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.</param>
/// <returns>The clean string.</returns>
/// <remarks>The string is cleaned in the context of the IShortStringHelper default culture.</remarks>
/// <remarks>The string is cleaned in the context of the ICurrent.ShortStringHelper default culture.</remarks>
public static string ToCleanString(this string text, CleanStringType stringType)
{
return ShortStringHelper.CleanString(text, stringType);
return Current.ShortStringHelper.CleanString(text, stringType);
}
/// <summary>
@@ -1152,10 +1127,10 @@ namespace Umbraco.Core
/// strings are cleaned up to camelCase and Ascii.</param>
/// <param name="separator">The separator.</param>
/// <returns>The clean string.</returns>
/// <remarks>The string is cleaned in the context of the IShortStringHelper default culture.</remarks>
/// <remarks>The string is cleaned in the context of the ICurrent.ShortStringHelper default culture.</remarks>
public static string ToCleanString(this string text, CleanStringType stringType, char separator)
{
return ShortStringHelper.CleanString(text, stringType, separator);
return Current.ShortStringHelper.CleanString(text, stringType, separator);
}
/// <summary>
@@ -1168,7 +1143,7 @@ namespace Umbraco.Core
/// <returns>The clean string.</returns>
public static string ToCleanString(this string text, CleanStringType stringType, CultureInfo culture)
{
return ShortStringHelper.CleanString(text, stringType, culture);
return Current.ShortStringHelper.CleanString(text, stringType, culture);
}
/// <summary>
@@ -1182,11 +1157,11 @@ namespace Umbraco.Core
/// <returns>The clean string.</returns>
public static string ToCleanString(this string text, CleanStringType stringType, char separator, CultureInfo culture)
{
return ShortStringHelper.CleanString(text, stringType, separator, culture);
return Current.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.
// note: LegacyCurrent.ShortStringHelper will produce 100% backward-compatible output for SplitPascalCasing.
// other helpers may not. DefaultCurrent.ShortStringHelper produces better, but non-compatible, results.
/// <summary>
/// Splits a Pascal cased string into a phrase separated by spaces.
@@ -1195,7 +1170,7 @@ namespace Umbraco.Core
/// <returns>The splitted text.</returns>
public static string SplitPascalCasing(this string phrase)
{
return ShortStringHelper.SplitPascalCasing(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.
@@ -1216,7 +1191,7 @@ namespace Umbraco.Core
/// <returns>The safe filename.</returns>
public static string ToSafeFileName(this string text)
{
return ShortStringHelper.CleanStringForSafeFileName(text);
return Current.ShortStringHelper.CleanStringForSafeFileName(text);
}
/// <summary>
@@ -1228,7 +1203,7 @@ namespace Umbraco.Core
/// <returns>The safe filename.</returns>
public static string ToSafeFileName(this string text, CultureInfo culture)
{
return ShortStringHelper.CleanStringForSafeFileName(text, culture);
return Current.ShortStringHelper.CleanStringForSafeFileName(text, culture);
}
/// <summary>

View File

@@ -4,7 +4,6 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Globalization;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Core.Strings
@@ -19,30 +18,17 @@ namespace Umbraco.Core.Strings
/// </remarks>
public class DefaultShortStringHelper : IShortStringHelper
{
private readonly IUmbracoSettingsSection _umbracoSettings;
#region Ctor and vars
[Obsolete("Use the other ctor that specifies all dependencies")]
public DefaultShortStringHelper()
public DefaultShortStringHelper(IUmbracoSettingsSection settings)
{
_umbracoSettings = _umbracoSettings;
InitializeLegacyUrlReplaceCharacters();
_config = new DefaultShortStringHelperConfig().WithDefault(settings);
}
public DefaultShortStringHelper(IUmbracoSettingsSection umbracoSettings)
// clones the config so it cannot be changed at runtime
public DefaultShortStringHelper(DefaultShortStringHelperConfig config)
{
_umbracoSettings = umbracoSettings;
InitializeLegacyUrlReplaceCharacters();
}
/// <summary>
/// Freezes the helper so it can prevents its configuration from being modified.
/// </summary>
/// <remarks>Will be called by <c>ShortStringHelperResolver</c> when resolution freezes.</remarks>
public void Freeze()
{
_frozen = true;
_config = config.Clone();
}
// see notes for CleanAsciiString
@@ -50,9 +36,7 @@ namespace Umbraco.Core.Strings
//const string ValidStringCharactersSource = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
//readonly static char[] ValidStringCharacters;
private CultureInfo _defaultCulture = CultureInfo.InvariantCulture;
private bool _frozen;
private readonly Dictionary<CultureInfo, Dictionary<CleanStringType, Config>> _configs = new Dictionary<CultureInfo, Dictionary<CleanStringType, Config>>();
private readonly DefaultShortStringHelperConfig _config;
// see notes for CleanAsciiString
//static DefaultShortStringHelper()
@@ -64,27 +48,6 @@ namespace Umbraco.Core.Strings
#region Filters
private readonly Dictionary<string, string> _urlReplaceCharacters = new Dictionary<string, string>();
private void InitializeLegacyUrlReplaceCharacters()
{
foreach (var node in _umbracoSettings.RequestHandler.CharCollection)
{
if(string.IsNullOrEmpty(node.Char) == false)
_urlReplaceCharacters[node.Char] = node.Replacement;
}
}
/// <summary>
/// Returns a new string in which characters have been replaced according to the Umbraco settings UrlReplaceCharacters.
/// </summary>
/// <param name="s">The string to filter.</param>
/// <returns>The filtered string.</returns>
public string ApplyUrlReplaceCharacters(string s)
{
return s.ReplaceMany(_urlReplaceCharacters);
}
// ok to be static here because it's not configureable in any way
private static readonly char[] InvalidFileNameChars =
Path.GetInvalidFileNameChars()
@@ -97,194 +60,6 @@ namespace Umbraco.Core.Strings
return InvalidFileNameChars.Contains(c) == false;
}
public static string CutMaxLength(string text, int length)
{
return text.Length <= length ? text : text.Substring(0, length);
}
#endregion
#region Configuration
private void EnsureNotFrozen()
{
if (_frozen)
throw new InvalidOperationException("Cannot configure the helper once it is frozen.");
}
/// <summary>
/// Sets a default culture.
/// </summary>
/// <param name="culture">The default culture.</param>
/// <returns>The short string helper.</returns>
public DefaultShortStringHelper WithDefaultCulture(CultureInfo culture)
{
EnsureNotFrozen();
_defaultCulture = culture;
return this;
}
public DefaultShortStringHelper WithConfig(Config config)
{
return WithConfig(_defaultCulture, CleanStringType.RoleMask, config);
}
public DefaultShortStringHelper WithConfig(CleanStringType stringRole, Config config)
{
return WithConfig(_defaultCulture, stringRole, config);
}
public DefaultShortStringHelper WithConfig(CultureInfo culture, CleanStringType stringRole, Config config)
{
if (config == null)
throw new ArgumentNullException("config");
EnsureNotFrozen();
if (_configs.ContainsKey(culture) == false)
_configs[culture] = new Dictionary<CleanStringType, Config>();
_configs[culture][stringRole] = config.Clone(); // clone so it can't be changed
return this;
}
/// <summary>
/// Sets the default configuration.
/// </summary>
/// <returns>The short string helper.</returns>
public DefaultShortStringHelper WithDefaultConfig()
{
return WithConfig(CleanStringType.UrlSegment, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
PostFilter = x => CutMaxLength(x, 240),
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = (_umbracoSettings.RequestHandler.ConvertUrlsToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase,
BreakTermsOnUpper = false,
Separator = '-'
}).WithConfig(CleanStringType.FileName, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Utf8 | CleanStringType.LowerCase,
BreakTermsOnUpper = false,
Separator = '-'
}).WithConfig(CleanStringType.Alias, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
IsTerm = (c, leading) => leading
? char.IsLetter(c) // only letters
: (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore
StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase,
BreakTermsOnUpper = false
}).WithConfig(CleanStringType.UnderscoreAlias, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase,
BreakTermsOnUpper = false
}).WithConfig(CleanStringType.ConvertCase, new Config
{
PreFilter = null,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Ascii,
BreakTermsOnUpper = true
});
}
public sealed class Config
{
public Config()
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged;
PreFilter = null;
PostFilter = null;
IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c);
BreakTermsOnUpper = false;
CutAcronymOnNonUpper = false;
GreedyAcronyms = false;
Separator = Char.MinValue;
}
public Config Clone()
{
return new Config
{
PreFilter = PreFilter,
PostFilter = PostFilter,
IsTerm = IsTerm,
StringType = StringType,
BreakTermsOnUpper = BreakTermsOnUpper,
CutAcronymOnNonUpper = CutAcronymOnNonUpper,
GreedyAcronyms = GreedyAcronyms,
Separator = Separator
};
}
public Func<string, string> PreFilter { get; set; }
public Func<string, string> PostFilter { get; set; }
public Func<char, bool, bool> IsTerm { get; set; }
public CleanStringType StringType { get; set; }
// indicate whether an uppercase within a term eg "fooBar" is to break
// into a new term, or to be considered as part of the current term
public bool BreakTermsOnUpper { get; set; }
// indicate whether a non-uppercase within an acronym eg "FOOBar" is to cut
// the acronym (at "B" or "a" depending on GreedyAcronyms) or to give
// up the acronym and treat the term as a word
public bool CutAcronymOnNonUpper { get; set; }
// indicates whether acronyms parsing is greedy ie whether "FOObar" is
// "FOO" + "bar" (greedy) or "FO" + "Obar" (non-greedy)
public bool GreedyAcronyms { get; set; }
// the separator char
// but then how can we tell we dont want any?
public char Separator { get; set; }
// extends the config
public CleanStringType StringTypeExtend(CleanStringType stringType)
{
var st = StringType;
foreach (var mask in new[] { CleanStringType.CaseMask, CleanStringType.CodeMask })
{
var a = stringType & mask;
if (a == 0) continue;
st = st & ~mask; // clear what we have
st = st | a; // set the new value
}
return st;
}
internal static readonly Config NotConfigured = new Config();
}
private Config GetConfig(CleanStringType stringType, CultureInfo culture)
{
stringType = stringType & CleanStringType.RoleMask;
Dictionary<CleanStringType, Config> config;
if (_configs.ContainsKey(culture))
{
config = _configs[culture];
if (config.ContainsKey(stringType)) // have we got a config for _that_ role?
return config[stringType];
if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles?
return config[CleanStringType.RoleMask];
}
else if (_configs.ContainsKey(_defaultCulture))
{
config = _configs[_defaultCulture];
if (config.ContainsKey(stringType)) // have we got a config for _that_ role?
return config[stringType];
if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles?
return config[CleanStringType.RoleMask];
}
return Config.NotConfigured;
}
#endregion
#region JavaScript
@@ -333,8 +108,7 @@ function validateSafeAlias(input, value, immediate, callback) {{
/// </summary>
public string GetShortStringServicesJavaScript(string controllerPath)
{
return string.Format(SssjsFormat,
_umbracoSettings.Content.ForceSafeAliases ? "true" : "false", controllerPath);
return string.Format(SssjsFormat, _config.ForceSafeAliases ? "true" : "false", controllerPath);
}
#endregion
@@ -352,7 +126,7 @@ function validateSafeAlias(input, value, immediate, callback) {{
/// </remarks>
public virtual string CleanStringForSafeAlias(string text)
{
return CleanStringForSafeAlias(text, _defaultCulture);
return CleanStringForSafeAlias(text, _config.DefaultCulture);
}
/// <summary>
@@ -380,7 +154,7 @@ function validateSafeAlias(input, value, immediate, callback) {{
/// </remarks>
public virtual string CleanStringForUrlSegment(string text)
{
return CleanStringForUrlSegment(text, _defaultCulture);
return CleanStringForUrlSegment(text, _config.DefaultCulture);
}
/// <summary>
@@ -406,7 +180,7 @@ function validateSafeAlias(input, value, immediate, callback) {{
/// <remarks>Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented.</remarks>
public virtual string CleanStringForSafeFileName(string text)
{
return CleanStringForSafeFileName(text, _defaultCulture);
return CleanStringForSafeFileName(text, _config.DefaultCulture);
}
/// <summary>
@@ -452,43 +226,43 @@ function validateSafeAlias(input, value, immediate, callback) {{
// - Leading digits are removed.
// - Many consecutive separators are folded into one unique separator.
const byte StateBreak = 1;
const byte StateUp = 2;
const byte StateWord = 3;
const byte StateAcronym = 4;
private const byte StateBreak = 1;
private const byte StateUp = 2;
private const byte StateWord = 3;
private const byte StateAcronym = 4;
/// <summary>
/// Cleans a string.
/// </summary>
/// <param name="text">The text to clean.</param>
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.</param>
/// <returns>The clean string.</returns>
/// <remarks>The string is cleaned in the context of the default culture.</remarks>
public string CleanString(string text, CleanStringType stringType)
{
return CleanString(text, stringType, _defaultCulture, null);
return CleanString(text, stringType, _config.DefaultCulture, null);
}
/// <summary>
/// Cleans a string, using a specified separator.
/// </summary>
/// <param name="text">The text to clean.</param>
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.</param>
/// <param name="separator">The separator.</param>
/// <returns>The clean string.</returns>
/// <remarks>The string is cleaned in the context of the default culture.</remarks>
public string CleanString(string text, CleanStringType stringType, char separator)
{
return CleanString(text, stringType, _defaultCulture, separator);
return CleanString(text, stringType, _config.DefaultCulture, separator);
}
/// <summary>
/// Cleans a string in the context of a specified culture.
/// </summary>
/// <param name="text">The text to clean.</param>
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.</param>
/// <param name="culture">The culture.</param>
/// <returns>The clean string.</returns>
@@ -501,7 +275,7 @@ function validateSafeAlias(input, value, immediate, callback) {{
/// Cleans a string in the context of a specified culture, using a specified separator.
/// </summary>
/// <param name="text">The text to clean.</param>
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// <param name="stringType">A flag indicating the target casing and encoding of the string. By default,
/// strings are cleaned up to camelCase and Ascii.</param>
/// <param name="separator">The separator.</param>
/// <param name="culture">The culture.</param>
@@ -514,13 +288,11 @@ function validateSafeAlias(input, value, immediate, callback) {{
protected virtual string CleanString(string text, CleanStringType stringType, CultureInfo culture, char? separator)
{
// be safe
if (text == null)
throw new ArgumentNullException("text");
if (culture == null)
throw new ArgumentNullException("culture");
if (text == null) throw new ArgumentNullException(nameof(text));
if (culture == null) throw new ArgumentNullException(nameof(culture));
// get config
var config = GetConfig(stringType, culture);
var config = _config.For(stringType, culture);
stringType = config.StringTypeExtend(stringType);
// apply defaults
@@ -542,8 +314,8 @@ function validateSafeAlias(input, value, immediate, callback) {{
// recode
var codeType = stringType & CleanStringType.CodeMask;
text = codeType == CleanStringType.Ascii
? Utf8ToAsciiConverter.ToAsciiString(text)
text = codeType == CleanStringType.Ascii
? Utf8ToAsciiConverter.ToAsciiString(text)
: RemoveSurrogatePairs(text);
// clean
@@ -552,7 +324,7 @@ function validateSafeAlias(input, value, immediate, callback) {{
// apply post-filter
if (config.PostFilter != null)
text = config.PostFilter(text);
return text;
}
@@ -584,7 +356,7 @@ function validateSafeAlias(input, value, immediate, callback) {{
// that the utf8 version. Micro-optimizing sometimes isn't such a good idea.
// note: does NOT support surrogate pairs in text
internal string CleanCodeString(string text, CleanStringType caseType, char separator, CultureInfo culture, Config config)
internal string CleanCodeString(string text, CleanStringType caseType, char separator, CultureInfo culture, DefaultShortStringHelperConfig.Config config)
{
int opos = 0, ipos = 0;
var state = StateBreak;
@@ -822,12 +594,12 @@ function validateSafeAlias(input, value, immediate, callback) {{
{
term = term.Substring(i);
term.CopyTo(0, output, opos, term.Length);
opos += term.Length;
opos += term.Length;
}
break;
default:
throw new ArgumentOutOfRangeException("caseType");
throw new ArgumentOutOfRangeException(nameof(caseType));
}
}
@@ -847,7 +619,7 @@ function validateSafeAlias(input, value, immediate, callback) {{
{
// be safe
if (text == null)
throw new ArgumentNullException("text");
throw new ArgumentNullException(nameof(text));
var input = text.ToCharArray();
var output = new char[input.Length * 2];
@@ -898,12 +670,10 @@ function validateSafeAlias(input, value, immediate, callback) {{
public virtual string ReplaceMany(string text, IDictionary<string, string> replacements)
{
// be safe
if (text == null)
throw new ArgumentNullException("text");
if (replacements == null)
throw new ArgumentNullException("replacements");
if (text == null) throw new ArgumentNullException(nameof(text));
if (replacements == null) throw new ArgumentNullException(nameof(replacements));
// Have done various tests, implementing my own "super fast" state machine to handle
// Have done various tests, implementing my own "super fast" state machine to handle
// replacement of many items, or via regexes, but on short strings and not too
// many replacements (which prob. is going to be our case) nothing can beat this...
// (at least with safe and checked code -- we don't want unsafe/unchecked here)
@@ -924,10 +694,8 @@ function validateSafeAlias(input, value, immediate, callback) {{
public virtual string ReplaceMany(string text, char[] chars, char replacement)
{
// be safe
if (text == null)
throw new ArgumentNullException("text");
if (chars == null)
throw new ArgumentNullException("chars");
if (text == null) throw new ArgumentNullException(nameof(text));
if (chars == null) throw new ArgumentNullException(nameof(chars));
// see note above

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Core.Strings
{
public class DefaultShortStringHelperConfig
{
private readonly Dictionary<CultureInfo, Dictionary<CleanStringType, Config>> _configs = new Dictionary<CultureInfo, Dictionary<CleanStringType, Config>>();
public DefaultShortStringHelperConfig Clone()
{
var config = new DefaultShortStringHelperConfig();
config.DefaultCulture = DefaultCulture;
config.ForceSafeAliases = ForceSafeAliases;
config.UrlReplaceCharacters = UrlReplaceCharacters;
foreach (var kvp1 in _configs)
{
var c = config._configs[kvp1.Key] = new Dictionary<CleanStringType, Config>();
foreach (var kvp2 in _configs[kvp1.Key])
c[kvp2.Key] = kvp2.Value.Clone();
}
return config;
}
public CultureInfo DefaultCulture { get; set; } = CultureInfo.InvariantCulture;
public Dictionary<string, string> UrlReplaceCharacters { get; set; }
public bool ForceSafeAliases { get; set; }
public DefaultShortStringHelperConfig WithConfig(Config config)
{
return WithConfig(DefaultCulture, CleanStringType.RoleMask, config);
}
public DefaultShortStringHelperConfig WithConfig(CleanStringType stringRole, Config config)
{
return WithConfig(DefaultCulture, stringRole, config);
}
public DefaultShortStringHelperConfig WithConfig(CultureInfo culture, CleanStringType stringRole, Config config)
{
if (config == null) throw new ArgumentNullException(nameof(config));
if (_configs.ContainsKey(culture) == false)
_configs[culture] = new Dictionary<CleanStringType, Config>();
_configs[culture][stringRole] = config;
return this;
}
/// <summary>
/// Sets the default configuration.
/// </summary>
/// <returns>The short string helper.</returns>
public DefaultShortStringHelperConfig WithDefault(IUmbracoSettingsSection umbracoSettings)
{
ForceSafeAliases = umbracoSettings.Content.ForceSafeAliases;
UrlReplaceCharacters = umbracoSettings.RequestHandler.CharCollection
.Where(x => string.IsNullOrEmpty(x.Char) == false)
.ToDictionary(x => x.Char, x => x.Replacement);
var convertUrlsToAscii = umbracoSettings.RequestHandler.ConvertUrlsToAscii;
return WithConfig(CleanStringType.UrlSegment, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
PostFilter = x => CutMaxLength(x, 240),
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = (convertUrlsToAscii ? CleanStringType.Ascii : CleanStringType.Utf8) | CleanStringType.LowerCase,
BreakTermsOnUpper = false,
Separator = '-'
}).WithConfig(CleanStringType.FileName, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Utf8 | CleanStringType.LowerCase,
BreakTermsOnUpper = false,
Separator = '-'
}).WithConfig(CleanStringType.Alias, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
IsTerm = (c, leading) => leading
? char.IsLetter(c) // only letters
: (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore
StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase,
BreakTermsOnUpper = false
}).WithConfig(CleanStringType.UnderscoreAlias, new Config
{
PreFilter = ApplyUrlReplaceCharacters,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase,
BreakTermsOnUpper = false
}).WithConfig(CleanStringType.ConvertCase, new Config
{
PreFilter = null,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Ascii,
BreakTermsOnUpper = true
});
}
// internal: we don't want ppl to retrieve a config and modify it
// (the helper uses a private clone to prevent modifications)
internal Config For(CleanStringType stringType, CultureInfo culture)
{
stringType = stringType & CleanStringType.RoleMask;
Dictionary<CleanStringType, Config> config;
if (_configs.ContainsKey(culture))
{
config = _configs[culture];
if (config.ContainsKey(stringType)) // have we got a config for _that_ role?
return config[stringType];
if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles?
return config[CleanStringType.RoleMask];
}
else if (_configs.ContainsKey(DefaultCulture))
{
config = _configs[DefaultCulture];
if (config.ContainsKey(stringType)) // have we got a config for _that_ role?
return config[stringType];
if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles?
return config[CleanStringType.RoleMask];
}
return Config.NotConfigured;
}
public sealed class Config
{
public Config()
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged;
PreFilter = null;
PostFilter = null;
IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c);
BreakTermsOnUpper = false;
CutAcronymOnNonUpper = false;
GreedyAcronyms = false;
Separator = char.MinValue;
}
public Config Clone()
{
return new Config
{
PreFilter = PreFilter,
PostFilter = PostFilter,
IsTerm = IsTerm,
StringType = StringType,
BreakTermsOnUpper = BreakTermsOnUpper,
CutAcronymOnNonUpper = CutAcronymOnNonUpper,
GreedyAcronyms = GreedyAcronyms,
Separator = Separator
};
}
public Func<string, string> PreFilter { get; set; }
public Func<string, string> PostFilter { get; set; }
public Func<char, bool, bool> IsTerm { get; set; }
public CleanStringType StringType { get; set; }
// indicate whether an uppercase within a term eg "fooBar" is to break
// into a new term, or to be considered as part of the current term
public bool BreakTermsOnUpper { get; set; }
// indicate whether a non-uppercase within an acronym eg "FOOBar" is to cut
// the acronym (at "B" or "a" depending on GreedyAcronyms) or to give
// up the acronym and treat the term as a word
public bool CutAcronymOnNonUpper { get; set; }
// indicates whether acronyms parsing is greedy ie whether "FOObar" is
// "FOO" + "bar" (greedy) or "FO" + "Obar" (non-greedy)
public bool GreedyAcronyms { get; set; }
// the separator char
// but then how can we tell we dont want any?
public char Separator { get; set; }
// extends the config
public CleanStringType StringTypeExtend(CleanStringType stringType)
{
var st = StringType;
foreach (var mask in new[] { CleanStringType.CaseMask, CleanStringType.CodeMask })
{
var a = stringType & mask;
if (a == 0) continue;
st = st & ~mask; // clear what we have
st = st | a; // set the new value
}
return st;
}
internal static readonly Config NotConfigured = new Config();
}
/// <summary>
/// Returns a new string in which characters have been replaced according to the Umbraco settings UrlReplaceCharacters.
/// </summary>
/// <param name="s">The string to filter.</param>
/// <returns>The filtered string.</returns>
public string ApplyUrlReplaceCharacters(string s)
{
return UrlReplaceCharacters == null ? s : s.ReplaceMany(UrlReplaceCharacters);
}
public static string CutMaxLength(string text, int length)
{
return text.Length <= length ? text : text.Substring(0, length);
}
}
}

View File

@@ -9,12 +9,6 @@ namespace Umbraco.Core.Strings
/// <remarks>Not necessarily optimized to work on large bodies of text.</remarks>
public interface IShortStringHelper
{
/// <summary>
/// Freezes the helper so it can prevents its configuration from being modified.
/// </summary>
/// <remarks>Will be called by <c>ShortStringHelperResolver</c> when resolution freezes.</remarks>
void Freeze();
/// <summary>
/// Gets the JavaScript code defining client-side short string services.
/// </summary>

View File

@@ -1,64 +0,0 @@
using System;
using System.Linq.Expressions;
using LightInject;
using Umbraco.Core.ObjectResolution;
namespace Umbraco.Core.Strings
{
/// <summary>
/// Resolves the IShortStringHelper object
/// </summary>
public sealed class ShortStringHelperResolver : ContainerSingleObjectResolver<ShortStringHelperResolver, IShortStringHelper>
{
/// <summary>
/// Initializes the resolver to use IoC
/// </summary>
/// <param name="container"></param>
internal ShortStringHelperResolver(IServiceContainer container)
: base(container)
{
Resolution.Frozen += (sender, args) => Value.Freeze();
}
/// <summary>
/// Initializes the resolver to use IoC
/// </summary>
/// <param name="container"></param>
/// <param name="implementationType"></param>
internal ShortStringHelperResolver(IServiceContainer container, Func<IServiceFactory, IShortStringHelper> implementationType)
: base(container, implementationType)
{
Resolution.Frozen += (sender, args) => Value.Freeze();
}
/// <summary>
/// Initializes a new instance of the <see cref="ShortStringHelperResolver"/> class with an instance of a helper.
/// </summary>
/// <param name="helper">A instance of a helper.</param>
/// <remarks>The resolver is created by the <c>CoreBootManager</c> and thus the constructor remains internal.</remarks>
internal ShortStringHelperResolver(IShortStringHelper helper)
: base(helper)
{
Resolution.Frozen += (sender, args) => Value.Freeze();
}
/// <summary>
/// Sets the helper.
/// </summary>
/// <param name="helper">The helper.</param>
/// <remarks>For developers, at application startup.</remarks>
public void SetHelper(IShortStringHelper helper)
{
Value = helper;
}
/// <summary>
/// Gets the helper.
/// </summary>
public IShortStringHelper Helper
{
get { return Value; }
}
}
}

View File

@@ -1128,6 +1128,7 @@
<Compile Include="IBootManager.cs" />
<Compile Include="IntExtensions.cs" />
<Compile Include="LambdaExpressionCacheKey.cs" />
<Compile Include="Strings\DefaultShortStringHelperConfig.cs" />
<Compile Include="Strings\UrlSegmentProviderCollection.cs" />
<Compile Include="Strings\UrlSegmentProviderCollectionBuilder.cs" />
<Compile Include="Xml\XPath\RenamedRootNavigator.cs" />
@@ -1250,7 +1251,6 @@
<Compile Include="Sync\ConfigServerRegistrar.cs" />
<Compile Include="Strings\Utf8ToAsciiConverter.cs" />
<Compile Include="Strings\CleanStringType.cs" />
<Compile Include="Strings\ShortStringHelperResolver.cs" />
<Compile Include="Strings\IShortStringHelper.cs" />
<Compile Include="Strings\StringAliasCaseTypeExtensions.cs" />
<Compile Include="Strings\DefaultShortStringHelper.cs" />

View File

@@ -30,8 +30,6 @@ namespace Umbraco.Tests.Cache.DistributedCache
CacheRefresherCollectionBuilder.Register(container)
.Add<TestCacheRefresher>();
Resolution.Freeze();
}
[TearDown]

View File

@@ -1,8 +1,10 @@
using System;
using System.Threading;
using LightInject;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
@@ -20,15 +22,17 @@ namespace Umbraco.Tests.PropertyEditors
{
//normalize culture
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault()));
Resolution.Freeze();
var container = new ServiceContainer();
container.ConfigureUmbracoCore();
container.Register<IShortStringHelper>(_
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())));
}
[TearDown]
public virtual void TestTearDown()
{
ResolverCollection.ResetAll();
Resolution.Reset();
Current.Reset();
}
[TestCase("{prop1: 'val1', prop2: 'val2'}", true)]

View File

@@ -12,10 +12,10 @@ using Umbraco.Web.Models;
using Umbraco.Web.Mvc;
using Umbraco.Web.Routing;
using Umbraco.Web.WebApi;
using umbraco.BusinessLogic;
using Umbraco.Core.Plugins;
using Umbraco.Core.Profiling;
using Umbraco.Core.Strings;
using Umbraco.Core.DependencyInjection;
using Current = Umbraco.Web.Current;
namespace Umbraco.Tests.Routing
{
@@ -47,7 +47,7 @@ namespace Umbraco.Tests.Routing
var umbracoApiControllerTypes = new UmbracoApiControllerTypeCollection(PluginManager.Current.ResolveUmbracoApiControllers());
Container.RegisterInstance(umbracoApiControllerTypes);
ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault()));
Container.RegisterSingleton<IShortStringHelper>(_ => new DefaultShortStringHelper(SettingsForTests.GetDefault()));
base.FreezeResolution();
}

View File

@@ -5,10 +5,12 @@ using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using LightInject;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
@@ -32,57 +34,57 @@ namespace Umbraco.Tests.Strings
// NOTE pre-filters runs _before_ Recode takes place
// so there still may be utf8 chars even though you want ascii
_helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.FileName, new DefaultShortStringHelper.Config
_helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.FileName, new DefaultShortStringHelperConfig.Config
{
//PreFilter = ClearFileChars, // done in IsTerm
IsTerm = (c, leading) => (char.IsLetterOrDigit(c) || c == '_') && DefaultShortStringHelper.IsValidFileNameChar(c),
StringType = CleanStringType.LowerCase | CleanStringType.Ascii,
Separator = '-'
})
.WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config
.WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelperConfig.Config
{
PreFilter = StripQuotes,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_',
StringType = CleanStringType.LowerCase | CleanStringType.Ascii,
Separator = '-'
})
.WithConfig(new CultureInfo("fr-FR"), CleanStringType.UrlSegment, new DefaultShortStringHelper.Config
.WithConfig(new CultureInfo("fr-FR"), CleanStringType.UrlSegment, new DefaultShortStringHelperConfig.Config
{
PreFilter = FilterFrenchElisions,
IsTerm = (c, leading) => leading ? char.IsLetter(c) : (char.IsLetterOrDigit(c) || c == '_'),
StringType = CleanStringType.LowerCase | CleanStringType.Ascii,
Separator = '-'
})
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
PreFilter = StripQuotes,
IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c),
StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii
})
.WithConfig(new CultureInfo("fr-FR"), CleanStringType.Alias, new DefaultShortStringHelper.Config
.WithConfig(new CultureInfo("fr-FR"), CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
PreFilter = WhiteQuotes,
IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c),
StringType = CleanStringType.UmbracoCase | CleanStringType.Ascii
})
.WithConfig(CleanStringType.ConvertCase, new DefaultShortStringHelper.Config
.WithConfig(CleanStringType.ConvertCase, new DefaultShortStringHelperConfig.Config
{
PreFilter = null,
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore
StringType = CleanStringType.Ascii,
BreakTermsOnUpper = true
});
}));
ShortStringHelperResolver.Reset();
ShortStringHelperResolver.Current = new ShortStringHelperResolver(_helper);
Resolution.Freeze();
var container = new ServiceContainer();
container.ConfigureUmbracoCore();
container.Register<IShortStringHelper>(_ => _helper);
}
[TearDown]
public void TearDown()
{
ShortStringHelperResolver.Reset();
Current.Reset();
}
static readonly Regex FrenchElisionsRegex = new Regex("\\b(c|d|j|l|m|n|qu|s|t)('|\u8217)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -114,17 +116,17 @@ namespace Umbraco.Tests.Strings
const string input = "ÆØÅ and æøå and 中文测试 and אודות האתר and größer БбДдЖж page";
var helper = new DefaultShortStringHelper(settings).WithDefaultConfig(); // unicode
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(settings)); // unicode
var output = helper.CleanStringForUrlSegment(input);
Assert.AreEqual("æøå-and-æøå-and-中文测试-and-אודות-האתר-and-größer-ббдджж-page", output);
helper = new DefaultShortStringHelper(settings)
.WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(settings)
.WithConfig(CleanStringType.UrlSegment, new DefaultShortStringHelperConfig.Config
{
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_',
StringType = CleanStringType.LowerCase | CleanStringType.Ascii, // ascii
Separator = '-'
});
}));
output = helper.CleanStringForUrlSegment(input);
Assert.AreEqual("aeoa-and-aeoa-and-and-and-grosser-bbddzhzh-page", output);
}
@@ -132,103 +134,103 @@ namespace Umbraco.Tests.Strings
[Test]
public void CleanStringUnderscoreInTerm()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
// underscore is accepted within terms
IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_',
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("foo_bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
// underscore is not accepted within terms
IsTerm = (c, leading) => char.IsLetterOrDigit(c),
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("foo*bar*nil", helper.CleanString("foo_bar nil", CleanStringType.Alias));
}
[Test]
public void CleanStringLeadingChars()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
// letters and digits are valid leading chars
IsTerm = (c, leading) => char.IsLetterOrDigit(c),
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("0123foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
// only letters are valid leading chars
IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c),
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias));
Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123 foo_bar 543 nil 321", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault()).WithDefaultConfig();
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault()));
Assert.AreEqual("child2", helper.CleanStringForSafeAlias("1child2"));
}
[Test]
public void CleanStringTermOnUpper()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
// uppercase letter means new term
BreakTermsOnUpper = true,
Separator = '*'
});
}));
Assert.AreEqual("foo*Bar", helper.CleanString("fooBar", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
// uppercase letter is part of term
BreakTermsOnUpper = false,
Separator = '*'
});
}));
Assert.AreEqual("fooBar", helper.CleanString("fooBar", CleanStringType.Alias));
}
[Test]
public void CleanStringAcronymOnNonUpper()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
// non-uppercase letter means cut acronym
CutAcronymOnNonUpper = true,
Separator = '*'
});
}));
Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias));
Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias));
Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias));
Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
// non-uppercase letter means word
CutAcronymOnNonUpper = false,
Separator = '*'
});
}));
Assert.AreEqual("foo*BARRnil", helper.CleanString("foo BARRnil", CleanStringType.Alias));
Assert.AreEqual("foo*BARnil", helper.CleanString("foo BARnil", CleanStringType.Alias));
Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias));
@@ -238,27 +240,27 @@ namespace Umbraco.Tests.Strings
[Test]
public void CleanStringGreedyAcronyms()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
CutAcronymOnNonUpper = true,
GreedyAcronyms = true,
Separator = '*'
});
}));
Assert.AreEqual("foo*BARR*nil", helper.CleanString("foo BARRnil", CleanStringType.Alias));
Assert.AreEqual("foo*BAR*nil", helper.CleanString("foo BARnil", CleanStringType.Alias));
Assert.AreEqual("foo*BA*nil", helper.CleanString("foo BAnil", CleanStringType.Alias));
Assert.AreEqual("foo*Bnil", helper.CleanString("foo Bnil", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
CutAcronymOnNonUpper = true,
GreedyAcronyms = false,
Separator = '*'
});
}));
Assert.AreEqual("foo*BAR*Rnil", helper.CleanString("foo BARRnil", CleanStringType.Alias));
Assert.AreEqual("foo*BA*Rnil", helper.CleanString("foo BARnil", CleanStringType.Alias));
Assert.AreEqual("foo*BAnil", helper.CleanString("foo BAnil", CleanStringType.Alias));
@@ -268,12 +270,12 @@ namespace Umbraco.Tests.Strings
[Test]
public void CleanStringWhiteSpace()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("foo", helper.CleanString(" foo ", CleanStringType.Alias));
Assert.AreEqual("foo*bar", helper.CleanString(" foo bar ", CleanStringType.Alias));
}
@@ -281,47 +283,47 @@ namespace Umbraco.Tests.Strings
[Test]
public void CleanStringSeparator()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("foo*bar", helper.CleanString("foo bar", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = ' '
});
}));
Assert.AreEqual("foo bar", helper.CleanString("foo bar", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged
});
}));
Assert.AreEqual("foobar", helper.CleanString("foo bar", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '文'
});
}));
Assert.AreEqual("foo文bar", helper.CleanString("foo bar", CleanStringType.Alias));
}
[Test]
public void CleanStringSymbols()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("house*2", helper.CleanString("house (2)", CleanStringType.Alias));
// FIXME but for a filename we want to keep them!
@@ -370,21 +372,21 @@ namespace Umbraco.Tests.Strings
[Test]
public void CleanStringEncoding()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("中文测试", helper.CleanString("中文测试", CleanStringType.Alias));
Assert.AreEqual("léger*中文测试*ZÔRG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias));
helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Ascii | CleanStringType.Unchanged,
Separator = '*'
});
}));
Assert.AreEqual("", helper.CleanString("中文测试", CleanStringType.Alias));
Assert.AreEqual("leger*ZORG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias));
}
@@ -397,7 +399,7 @@ namespace Umbraco.Tests.Strings
contentMock.Setup(x => x.CharCollection).Returns(Enumerable.Empty<IChar>());
contentMock.Setup(x => x.ConvertUrlsToAscii).Returns(false);
var helper = new DefaultShortStringHelper(settings).WithDefaultConfig();
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(settings));
const string input = "0123 中文测试 中文测试 léger ZÔRG (2) a?? *x";
@@ -418,12 +420,12 @@ namespace Umbraco.Tests.Strings
[Test]
public void CleanStringCasing()
{
var helper = new DefaultShortStringHelper(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelper.Config
var helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefault())
.WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config
{
StringType = CleanStringType.Utf8 | CleanStringType.Unchanged,
Separator = ' '
});
}));
// BBB is an acronym
// E is a word (too short to be an acronym)

View File

@@ -1,33 +0,0 @@
using NUnit.Framework;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Strings;
namespace Umbraco.Tests.Strings
{
[TestFixture]
public class ShortStringHelperResolverTest
{
[SetUp]
public void Setup()
{
ShortStringHelperResolver.Reset();
}
[TearDown]
public void TearDown()
{
ShortStringHelperResolver.Reset();
}
[Test]
public void FreezesHelperWhenResolutionFreezes()
{
var helper = new MockShortStringHelper();
ShortStringHelperResolver.Current = new ShortStringHelperResolver(helper);
Assert.IsFalse(helper.IsFrozen);
Resolution.Freeze();
Assert.AreSame(helper, ShortStringHelperResolver.Current.Helper);
Assert.IsTrue(helper.IsFrozen);
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Diagnostics;
using System.Globalization;
using LightInject;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Strings;
@@ -14,15 +16,22 @@ namespace Umbraco.Tests.Strings
[SetUp]
public void Setup()
{
ShortStringHelperResolver.Reset();
ShortStringHelperResolver.Current = new ShortStringHelperResolver(new MockShortStringHelper());
Resolution.Freeze();
var container = new ServiceContainer();
container.ConfigureUmbracoCore();
container.RegisterSingleton<IShortStringHelper>(_ => new MockShortStringHelper());
}
[TearDown]
public void TearDown()
{
ShortStringHelperResolver.Reset();
Current.Reset();
}
[Test]
public void CurrentHelper()
{
var helper = Current.ShortStringHelper;
Assert.IsInstanceOf<MockShortStringHelper>(helper);
}
[TestCase("hello", "world", false)]

View File

@@ -1,9 +1,11 @@
using System;
using LightInject;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
using Umbraco.Core.DependencyInjection;
namespace Umbraco.Tests
{
@@ -14,14 +16,15 @@ namespace Umbraco.Tests
public void SetUp()
{
var settings = SettingsForTests.GetDefault();
ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(settings).WithDefaultConfig());
Resolution.Freeze();
var container = new ServiceContainer();
container.ConfigureUmbracoCore();
container.RegisterSingleton<IShortStringHelper>(_ => new DefaultShortStringHelper(settings));
}
[TearDown]
public void TearDown()
{
ShortStringHelperResolver.Reset();
Current.Reset();
}
[Test]

View File

@@ -406,7 +406,6 @@
<Compile Include="CoreXml\NavigableNavigatorTests.cs" />
<Compile Include="Cache\PublishedCache\PublishedMediaCacheTests.cs" />
<Compile Include="Strings\CmsHelperCasingTests.cs" />
<Compile Include="Strings\ShortStringHelperResolverTest.cs" />
<Compile Include="Strings\DefaultShortStringHelperTests.cs" />
<Compile Include="Strings\MockShortStringHelper.cs" />
<Compile Include="Macros\MacroTests.cs" />

View File

@@ -1,7 +1,9 @@
using Moq;
using System.IO;
using LightInject;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Persistence.Mappers;
@@ -29,12 +31,9 @@ namespace Umbraco.Tests.UmbracoExamine
/// </summary>
protected override void FreezeResolution()
{
ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault()));
Container.RegisterSingleton<IShortStringHelper>(_ => new DefaultShortStringHelper(SettingsForTests.GetDefault()));
base.FreezeResolution();
}
}
}

View File

@@ -232,6 +232,9 @@ namespace Umbraco.Web
public static ICultureDictionaryFactory CultureDictionaryFactory
=> Container.GetInstance<ICultureDictionaryFactory>();
public static IShortStringHelper ShortStringHelper
=> Container.GetInstance<IShortStringHelper>();
#endregion
}
}

View File

@@ -25,7 +25,7 @@ namespace Umbraco.Web.WebServices
public JavaScriptResult ServicesJavaScript()
{
var controllerPath = Url.GetCoreStringsControllerPath();
var js = ShortStringHelperResolver.Current.Helper.GetShortStringServicesJavaScript(controllerPath);
var js = Current.ShortStringHelper.GetShortStringServicesJavaScript(controllerPath);
return JavaScript(js);
}
}