Files
Umbraco-CMS/src/Umbraco.Core/UdiParser.cs
2020-08-06 12:59:21 +02:00

224 lines
10 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
namespace Umbraco.Core
{
public sealed class UdiParser
{
private static readonly ConcurrentDictionary<string, Udi> RootUdis = new ConcurrentDictionary<string, Udi>();
internal static ConcurrentDictionary<string, UdiType> UdiTypes { get; private set; }
static UdiParser()
{
// initialize with known (built-in) Udi types
// we will add scanned types later on
UdiTypes = new ConcurrentDictionary<string, UdiType>(GetKnownUdiTypes());
}
/// <summary>
/// Internal API for tests to resets all udi types back to only the known udi types.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static void ResetUdiTypes()
{
UdiTypes = new ConcurrentDictionary<string, UdiType>(GetKnownUdiTypes());
}
/// <summary>
/// Converts the string representation of an entity identifier into the equivalent Udi instance.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <returns>An Udi instance that contains the value that was parsed.</returns>
public static Udi Parse(string s)
{
ParseInternal(s, false, false, out var udi);
return udi;
}
/// <summary>
/// Converts the string representation of an entity identifier into the equivalent Udi instance.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <param name="knownTypes">A value indicating whether to only deal with known types.</param>
/// <returns>An Udi instance that contains the value that was parsed.</returns>
/// <remarks>
/// <para>If <paramref name="knownTypes"/> is <c>true</c>, and the string could not be parsed because
/// the entity type was not known, the method succeeds but sets <c>udi</c>to an
/// <see cref="UnknownTypeUdi"/> value.</para>
/// <para>If <paramref name="knownTypes"/> is <c>true</c>, assemblies are not scanned for types,
/// and therefore only builtin types may be known. Unless scanning already took place.</para>
/// </remarks>
public static Udi Parse(string s, bool knownTypes)
{
ParseInternal(s, false, knownTypes, out var udi);
return udi;
}
/// <summary>
/// Converts the string representation of an entity identifier into the equivalent Udi instance.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <param name="udi">An Udi instance that contains the value that was parsed.</param>
/// <returns>A boolean value indicating whether the string could be parsed.</returns>
public static bool TryParse(string s, out Udi udi)
{
return ParseInternal(s, true, false, out udi);
}
/// <summary>
/// Converts the string representation of an entity identifier into the equivalent Udi instance.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <param name="udi">An Udi instance that contains the value that was parsed.</param>
/// <returns>A boolean value indicating whether the string could be parsed.</returns>
public static bool TryParse<T>(string s, out T udi)
where T : Udi
{
var result = ParseInternal(s, true, false, out var parsed);
if (result && parsed is T)
{
udi = (T)parsed;
return true;
}
udi = null;
return false;
}
/// <summary>
/// Converts the string representation of an entity identifier into the equivalent Udi instance.
/// </summary>
/// <param name="s">The string to convert.</param>
/// <param name="knownTypes">A value indicating whether to only deal with known types.</param>
/// <param name="udi">An Udi instance that contains the value that was parsed.</param>
/// <returns>A boolean value indicating whether the string could be parsed.</returns>
/// <remarks>
/// <para>If <paramref name="knownTypes"/> is <c>true</c>, and the string could not be parsed because
/// the entity type was not known, the method returns <c>false</c> but still sets <c>udi</c>
/// to an <see cref="UnknownTypeUdi"/> value.</para>
/// <para>If <paramref name="knownTypes"/> is <c>true</c>, assemblies are not scanned for types,
/// and therefore only builtin types may be known. Unless scanning already took place.</para>
/// </remarks>
public static bool TryParse(string s, bool knownTypes, out Udi udi)
{
return ParseInternal(s, true, knownTypes, out udi);
}
private static bool ParseInternal(string s, bool tryParse, bool knownTypes, out Udi udi)
{
udi = null;
Uri uri;
if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false
|| Uri.TryCreate(s, UriKind.Absolute, out uri) == false)
{
if (tryParse) return false;
throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s));
}
var entityType = uri.Host;
if (UdiTypes.TryGetValue(entityType, out var udiType) == false)
{
if (knownTypes)
{
// not knowing the type is not an error
// just return the unknown type udi
udi = UnknownTypeUdi.Instance;
return false;
}
if (tryParse) return false;
throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType));
}
var path = uri.AbsolutePath.TrimStart('/');
if (udiType == UdiType.GuidUdi)
{
if (path == string.Empty)
{
udi = GetRootUdi(uri.Host);
return true;
}
if (Guid.TryParse(path, out var guid) == false)
{
if (tryParse) return false;
throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s));
}
udi = new GuidUdi(uri.Host, guid);
return true;
}
if (udiType == UdiType.StringUdi)
{
udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path));
return true;
}
if (tryParse) return false;
throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType));
}
internal static Udi GetRootUdi(string entityType)
{
return RootUdis.GetOrAdd(entityType, x =>
{
if (UdiTypes.TryGetValue(x, out var udiType) == false)
throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType));
return udiType == UdiType.StringUdi
? (Udi)new StringUdi(entityType, string.Empty)
: new GuidUdi(entityType, Guid.Empty);
});
}
/// <summary>
/// Registers a custom entity type.
/// </summary>
/// <param name="entityType"></param>
/// <param name="udiType"></param>
public static void RegisterUdiType(string entityType, UdiType udiType) => UdiTypes.TryAdd(entityType, udiType);
public static Dictionary<string, UdiType> GetKnownUdiTypes() =>
new Dictionary<string, UdiType>
{
{ Constants.UdiEntityType.Unknown, UdiType.Unknown },
{ Constants.UdiEntityType.AnyGuid, UdiType.GuidUdi },
{ Constants.UdiEntityType.Element, UdiType.GuidUdi },
{ Constants.UdiEntityType.Document, UdiType.GuidUdi },
{ Constants.UdiEntityType.DocumentBlueprint, UdiType.GuidUdi },
{ Constants.UdiEntityType.Media, UdiType.GuidUdi },
{ Constants.UdiEntityType.Member, UdiType.GuidUdi },
{ Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi },
{ Constants.UdiEntityType.Macro, UdiType.GuidUdi },
{ Constants.UdiEntityType.Template, UdiType.GuidUdi },
{ Constants.UdiEntityType.DocumentType, UdiType.GuidUdi },
{ Constants.UdiEntityType.DocumentTypeContainer, UdiType.GuidUdi },
{ Constants.UdiEntityType.DocumentTypeBluePrints, UdiType.GuidUdi },
{ Constants.UdiEntityType.MediaType, UdiType.GuidUdi },
{ Constants.UdiEntityType.MediaTypeContainer, UdiType.GuidUdi },
{ Constants.UdiEntityType.DataType, UdiType.GuidUdi },
{ Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi },
{ Constants.UdiEntityType.MemberType, UdiType.GuidUdi },
{ Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi },
{ Constants.UdiEntityType.RelationType, UdiType.GuidUdi },
{ Constants.UdiEntityType.FormsForm, UdiType.GuidUdi },
{ Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi },
{ Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi },
{ Constants.UdiEntityType.AnyString, UdiType.StringUdi },
{ Constants.UdiEntityType.Language, UdiType.StringUdi },
{ Constants.UdiEntityType.MacroScript, UdiType.StringUdi },
{ Constants.UdiEntityType.MediaFile, UdiType.StringUdi },
{ Constants.UdiEntityType.TemplateFile, UdiType.StringUdi },
{ Constants.UdiEntityType.Script, UdiType.StringUdi },
{ Constants.UdiEntityType.PartialView, UdiType.StringUdi },
{ Constants.UdiEntityType.PartialViewMacro, UdiType.StringUdi },
{ Constants.UdiEntityType.Stylesheet, UdiType.StringUdi }
};
}
}