diff --git a/src/Umbraco.Core/Dynamics/ClassFactory.cs b/src/Umbraco.Core/Dynamics/ClassFactory.cs new file mode 100644 index 0000000000..9ed1897f17 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/ClassFactory.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; + +namespace Umbraco.Core.Dynamics +{ + internal class ClassFactory + { + public static readonly ClassFactory Instance = new ClassFactory(); + + static ClassFactory() { } // Trigger lazy initialization of static fields + + ModuleBuilder module; + Dictionary classes; + int classCount; + ReaderWriterLock rwLock; + + private ClassFactory() + { + AssemblyName name = new AssemblyName("DynamicClasses"); + AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); +#if ENABLE_LINQ_PARTIAL_TRUST + new ReflectionPermission(PermissionState.Unrestricted).Assert(); +#endif + try + { + module = assembly.DefineDynamicModule("Module"); + } + finally + { +#if ENABLE_LINQ_PARTIAL_TRUST + PermissionSet.RevertAssert(); +#endif + } + classes = new Dictionary(); + rwLock = new ReaderWriterLock(); + } + + public Type GetDynamicClass(IEnumerable properties) + { + rwLock.AcquireReaderLock(Timeout.Infinite); + try + { + Signature signature = new Signature(properties); + Type type; + if (!classes.TryGetValue(signature, out type)) + { + type = CreateDynamicClass(signature.properties); + classes.Add(signature, type); + } + return type; + } + finally + { + rwLock.ReleaseReaderLock(); + } + } + + Type CreateDynamicClass(DynamicProperty[] properties) + { + LockCookie cookie = rwLock.UpgradeToWriterLock(Timeout.Infinite); + try + { + string typeName = "DynamicClass" + (classCount + 1); +#if ENABLE_LINQ_PARTIAL_TRUST + new ReflectionPermission(PermissionState.Unrestricted).Assert(); +#endif + try + { + TypeBuilder tb = this.module.DefineType(typeName, TypeAttributes.Class | + TypeAttributes.Public, typeof(DynamicClass)); + FieldInfo[] fields = GenerateProperties(tb, properties); + GenerateEquals(tb, fields); + GenerateGetHashCode(tb, fields); + Type result = tb.CreateType(); + classCount++; + return result; + } + finally + { +#if ENABLE_LINQ_PARTIAL_TRUST + PermissionSet.RevertAssert(); +#endif + } + } + finally + { + rwLock.DowngradeFromWriterLock(ref cookie); + } + } + + FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties) + { + FieldInfo[] fields = new FieldBuilder[properties.Length]; + for (int i = 0; i < properties.Length; i++) + { + DynamicProperty dp = properties[i]; + FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private); + PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null); + MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + dp.Type, Type.EmptyTypes); + ILGenerator genGet = mbGet.GetILGenerator(); + genGet.Emit(OpCodes.Ldarg_0); + genGet.Emit(OpCodes.Ldfld, fb); + genGet.Emit(OpCodes.Ret); + MethodBuilder mbSet = tb.DefineMethod("set_" + dp.Name, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + null, new Type[] { dp.Type }); + ILGenerator genSet = mbSet.GetILGenerator(); + genSet.Emit(OpCodes.Ldarg_0); + genSet.Emit(OpCodes.Ldarg_1); + genSet.Emit(OpCodes.Stfld, fb); + genSet.Emit(OpCodes.Ret); + pb.SetGetMethod(mbGet); + pb.SetSetMethod(mbSet); + fields[i] = fb; + } + return fields; + } + + void GenerateEquals(TypeBuilder tb, FieldInfo[] fields) + { + MethodBuilder mb = tb.DefineMethod("Equals", + MethodAttributes.Public | MethodAttributes.ReuseSlot | + MethodAttributes.Virtual | MethodAttributes.HideBySig, + typeof(bool), new Type[] { typeof(object) }); + ILGenerator gen = mb.GetILGenerator(); + LocalBuilder other = gen.DeclareLocal(tb); + Label next = gen.DefineLabel(); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Isinst, tb); + gen.Emit(OpCodes.Stloc, other); + gen.Emit(OpCodes.Ldloc, other); + gen.Emit(OpCodes.Brtrue_S, next); + gen.Emit(OpCodes.Ldc_I4_0); + gen.Emit(OpCodes.Ret); + gen.MarkLabel(next); + foreach (FieldInfo field in fields) + { + Type ft = field.FieldType; + Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); + next = gen.DefineLabel(); + gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldfld, field); + gen.Emit(OpCodes.Ldloc, other); + gen.Emit(OpCodes.Ldfld, field); + gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new Type[] { ft, ft }), null); + gen.Emit(OpCodes.Brtrue_S, next); + gen.Emit(OpCodes.Ldc_I4_0); + gen.Emit(OpCodes.Ret); + gen.MarkLabel(next); + } + gen.Emit(OpCodes.Ldc_I4_1); + gen.Emit(OpCodes.Ret); + } + + void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields) + { + MethodBuilder mb = tb.DefineMethod("GetHashCode", + MethodAttributes.Public | MethodAttributes.ReuseSlot | + MethodAttributes.Virtual | MethodAttributes.HideBySig, + typeof(int), Type.EmptyTypes); + ILGenerator gen = mb.GetILGenerator(); + gen.Emit(OpCodes.Ldc_I4_0); + foreach (FieldInfo field in fields) + { + Type ft = field.FieldType; + Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); + gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldfld, field); + gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new Type[] { ft }), null); + gen.Emit(OpCodes.Xor); + } + gen.Emit(OpCodes.Ret); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicBackingItem.cs b/src/Umbraco.Core/Dynamics/DynamicBackingItem.cs new file mode 100644 index 0000000000..bff49b1c8b --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicBackingItem.cs @@ -0,0 +1,506 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using umbraco.interfaces; +using System.Data; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicBackingItem + { + private readonly IDocument _content; + private IEnumerable _cachedChildren; + + //internal ExamineBackedMedia media; + public DynamicBackingItemType Type; + + public DynamicBackingItem(IDocument iNode) + { + this._content = iNode; + this.Type = DynamicBackingItemType.Content; + } + //public DynamicBackingItem(ExamineBackedMedia media) + //{ + // this.media = media; + // this.Type = DynamicBackingItemType.Media; + //} + //public DynamicBackingItem(int Id) + //{ + // NodeFactory.Node baseNode = new NodeFactory.Node(Id); + + // this._content = baseNode; + // this.Type = DynamicBackingItemType.Content; + // if (baseNode.Id == 0 && Id != 0) + // { + // this.media = ExamineBackedMedia.GetUmbracoMedia(Id); + // this.Type = DynamicBackingItemType.Media; + // if (this.media == null) + // { + // this.Type = DynamicBackingItemType.Content; + // } + // return; + // } + + //} + //public DynamicBackingItem(int Id, DynamicBackingItemType Type) + //{ + // NodeFactory.Node baseNode = new NodeFactory.Node(Id); + // if (Type == DynamicBackingItemType.Media) + // { + // this.media = ExamineBackedMedia.GetUmbracoMedia(Id); + // this.Type = Type; + // } + // else + // { + // this._content = baseNode; + // this.Type = Type; + // } + //} + + //public DynamicBackingItem(CMSNode node) + //{ + // this._content = (INode)node; + // this.Type = DynamicBackingItemType.Content; + //} + + public bool IsNull() + { + //return (_content == null && media == null); + return (_content == null); + } + public IEnumerable Children + { + get + { + if (_cachedChildren == null) + { + if (IsNull()) return null; + if (Type == DynamicBackingItemType.Content) + { + var children = _content.Children; + if (children != null) + { + _cachedChildren = children.Select(c => new DynamicBackingItem(c)); + } + } + else + { + //var children = media.ChildrenAsList.Value; + //if (children != null) + //{ + // return children.ToList().ConvertAll(m => new DynamicBackingItem(m)); + //} + + _cachedChildren = new List(); + } + + } + return _cachedChildren; + } + } + + public PropertyResult GetProperty(string alias) + { + if (IsNull()) return null; + if (Type == DynamicBackingItemType.Content) + { + return GetPropertyInternal(alias, _content); + } + //else + //{ + // return GetPropertyInternal(alias, media); + //} + return null; + } + + private PropertyResult GetPropertyInternal(string alias, IDocument content) + { + var prop = content.GetProperty(alias); + if (prop != null) + { + return new PropertyResult(prop) { ContextAlias = content.DocumentTypeAlias, ContextId = content.Id }; + } + else + { + if (alias.Substring(0, 1).ToUpper() == alias.Substring(0, 1)) + { + prop = content.GetProperty(alias.Substring(0, 1).ToLower() + alias.Substring((1))); + if (prop != null) + { + return new PropertyResult(prop) { ContextAlias = content.DocumentTypeAlias, ContextId = content.Id }; + } + else + { + //reflect + object result = null; + try + { + result = content.GetType().InvokeMember(alias, + System.Reflection.BindingFlags.GetProperty | + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic, + null, + content, + null); + } + catch (MissingMethodException) + { + + } + if (result != null) + { + return new PropertyResult(alias, string.Format("{0}", result), Guid.Empty) { ContextAlias = content.DocumentTypeAlias, ContextId = content.Id }; + } + } + } + } + return null; + } + //private PropertyResult GetPropertyInternal(string alias, ExamineBackedMedia content) + //{ + // bool propertyExists = false; + // var prop = content.GetProperty(alias, out propertyExists); + // if (prop != null) + // { + // return new PropertyResult(prop) { ContextAlias = content.NodeTypeAlias, ContextId = content.Id }; + // } + // else + // { + // if (alias.Substring(0, 1).ToUpper() == alias.Substring(0, 1) && !propertyExists) + // { + // prop = content.GetProperty(alias.Substring(0, 1).ToLower() + alias.Substring((1)), out propertyExists); + // if (prop != null) + // { + // return new PropertyResult(prop) { ContextAlias = content.NodeTypeAlias, ContextId = content.Id }; + // } + // else + // { + // object result = null; + // try + // { + // result = content.GetType().InvokeMember(alias, + // System.Reflection.BindingFlags.GetProperty | + // System.Reflection.BindingFlags.Instance | + // System.Reflection.BindingFlags.Public | + // System.Reflection.BindingFlags.NonPublic, + // null, + // content, + // null); + // } + // catch (MissingMethodException) + // { + // } + // if (result != null) + // { + // return new PropertyResult(alias, string.Format("{0}", result), Guid.Empty) { ContextAlias = content.NodeTypeAlias, ContextId = content.Id }; + // } + // } + // } + // } + // return null; + //} + public PropertyResult GetProperty(string alias, out bool propertyExists) + { + if (IsNull()) + { + propertyExists = false; + return null; + } + PropertyResult property = null; + IDocumentProperty innerProperty = null; + if (Type == DynamicBackingItemType.Content) + { + innerProperty = _content.GetProperty(alias); + propertyExists = innerProperty != null; + if (innerProperty != null) + { + property = new PropertyResult(innerProperty); + property.ContextAlias = _content.DocumentTypeAlias; + property.ContextId = _content.Id; + } + } + else + { + //string[] internalProperties = new string[] { + // "id", "nodeName", "updateDate", "writerName", "path", "nodeTypeAlias", + // "parentID", "__NodeId", "__IndexType", "__Path", "__NodeTypeAlias", + // "__nodeName", "umbracoBytes","umbracoExtension","umbracoFile","umbracoWidth", + // "umbracoHeight" + //}; + //if (media.WasLoadedFromExamine && !internalProperties.Contains(alias) && !media.Values.ContainsKey(alias)) + //{ + // //examine doesn't load custom properties + // innerProperty = media.LoadCustomPropertyNotFoundInExamine(alias, out propertyExists); + // if (innerProperty != null) + // { + // property = new PropertyResult(innerProperty); + // property.ContextAlias = media.NodeTypeAlias; + // property.ContextId = media.Id; + // } + //} + //else + //{ + // innerProperty = media.GetProperty(alias, out propertyExists); + // if (innerProperty != null) + // { + // property = new PropertyResult(innerProperty); + // property.ContextAlias = media.NodeTypeAlias; + // property.ContextId = media.Id; + // } + //} + propertyExists = false; + } + return property; + } + + public PropertyResult GetProperty(string alias, bool recursive) + { + bool propertyExists = false; + return GetProperty(alias, recursive, out propertyExists); + } + public PropertyResult GetProperty(string alias, bool recursive, out bool propertyExists) + { + if (!recursive) + { + return GetProperty(alias, out propertyExists); + } + if (IsNull()) + { + propertyExists = false; + return null; + } + DynamicBackingItem context = this; + PropertyResult prop = this.GetProperty(alias, out propertyExists); + while (prop == null || string.IsNullOrEmpty(prop.Value)) + { + context = context.Parent; + if (context == null) break; + prop = context.GetProperty(alias, out propertyExists); + } + if (prop != null) + { + return prop; + } + return null; + } + public string GetPropertyValue(string alias) + { + var prop = GetProperty(alias); + if (prop != null) return prop.Value; + return null; + } + public string GetPropertyValue(string alias, bool recursive) + { + var prop = GetProperty(alias, recursive); + if (prop != null) return prop.Value; + return null; + } + public IEnumerable Properties + { + get + { + if (IsNull()) return null; + if (Type == DynamicBackingItemType.Content) + { + return _content.Properties; + } + //else + //{ + // return media.PropertiesAsList; + //} + return null; + } + } + //public DataTable ChildrenAsTable() + //{ + // if (IsNull()) return null; + // if (Type == DynamicBackingItemType.Content) + // { + // return _content.ChildrenAsTable(); + // } + // else + // { + // //sorry + // return null; + // } + + //} + //public DataTable ChildrenAsTable(string nodeTypeAlias) + //{ + // if (IsNull()) return null; + // if (Type == DynamicBackingItemType.Content) + // { + // return _content.ChildrenAsTable(nodeTypeAlias); + // } + // else + // { + // //sorry + // return null; + // } + + //} + public int Level + { + //get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.Level : media.Level; } + get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.Level : 0; } + } + + + public int Id + { + //get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.Id : media.Id; } + get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.Id : 0; } + } + + public string NodeTypeAlias + { + //get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.NodeTypeAlias : media.NodeTypeAlias; } + get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.DocumentTypeAlias : null; } + } + + public DynamicBackingItem Parent + { + get + { + if (IsNull()) return null; + if (Type == DynamicBackingItemType.Content) + { + var parent = _content.Parent; + if (parent != null) + { + + return new DynamicBackingItem(parent); + } + + } + //else + //{ + // var parent = media.Parent; + // if (parent != null && parent.Value != null) + // { + // return new DynamicBackingItem(parent.Value); + // } + //} + return null; + } + } + public DateTime CreateDate + { + //get { if (IsNull()) return DateTime.MinValue; return Type == DynamicBackingItemType.Content ? _content.CreateDate : media.CreateDate; } + get { if (IsNull()) return DateTime.MinValue; return Type == DynamicBackingItemType.Content ? _content.CreateDate : DateTime.MinValue; } + } + public DateTime UpdateDate + { + //get { if (IsNull()) return DateTime.MinValue; return Type == DynamicBackingItemType.Content ? _content.UpdateDate : media.UpdateDate; } + get { if (IsNull()) return DateTime.MinValue; return Type == DynamicBackingItemType.Content ? _content.UpdateDate : DateTime.MinValue; } + } + + public string WriterName + { + get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.WriterName : null; } + } + + public string Name + { + //get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.Name : media.Name; } + get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.Name : null; } + } + public string nodeName + { + get { return Name; } + } + public string pageName + { + get { return Name; } + } + public Guid Version + { + //get { if (IsNull()) return Guid.Empty; return Type == DynamicBackingItemType.Content ? _content.Version : media.Version; } + get { if (IsNull()) return Guid.Empty; return Type == DynamicBackingItemType.Content ? _content.Version : Guid.Empty; } + } + + //public string Url + //{ + // //get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.Url : media.Url; } + // get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.Url : null; } + //} + + //public string NiceUrl + //{ + // //get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.NiceUrl : media.NiceUrl; } + // get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.NiceUrl : null; } + //} + + public string UrlName + { + get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.UrlName : null; } + } + + public int TemplateId + { + get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.TemplateId : 0; } + } + + public int SortOrder + { + //get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.SortOrder : media.SortOrder; } + get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.SortOrder : 0; } + } + + + public string CreatorName + { + //get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.CreatorName : media.CreatorName; } + get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.CreatorName : null; } + } + + public int WriterId + { + get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.WriterId : 0; } + } + + public int CreatorId + { + //get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.CreatorID : media.CreatorID; } + get { if (IsNull()) return 0; return Type == DynamicBackingItemType.Content ? _content.CreatorId : 0; } + } + + public string Path + { + //get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.Path : media.Path; } + get { if (IsNull()) return null; return Type == DynamicBackingItemType.Content ? _content.Path : null; } + } + + public IEnumerable GetChildren + { + get + { + if (Type == DynamicBackingItemType.Content) + { + var children = _content.Children.ToArray(); + //testing + if (!children.Any() && _content.Id == 0) + { + return new List(new DynamicBackingItem[] { this }); + } + return children.Select(n => new DynamicBackingItem(n)); + } + //else + //{ + // List children = media.ChildrenAsList.Value; + // //testing + // if (children.Count == 0 && _content.Id == 0) + // { + // return new List(new DynamicBackingItem[] { this }); + // } + // return children.ConvertAll(n => new DynamicBackingItem(n)); + //} + return Enumerable.Empty(); + } + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicBackingItemType.cs b/src/Umbraco.Core/Dynamics/DynamicBackingItemType.cs new file mode 100644 index 0000000000..8a83a9481d --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicBackingItemType.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Dynamics +{ + internal enum DynamicBackingItemType + { + Content, + Media + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicClass.cs b/src/Umbraco.Core/Dynamics/DynamicClass.cs new file mode 100644 index 0000000000..13914cad42 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicClass.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using System.Text; + +namespace Umbraco.Core.Dynamics +{ + public abstract class DynamicClass + { + public override string ToString() + { + PropertyInfo[] props = this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); + StringBuilder sb = new StringBuilder(); + sb.Append("{"); + for (int i = 0; i < props.Length; i++) + { + if (i > 0) sb.Append(", "); + sb.Append(props[i].Name); + sb.Append("="); + sb.Append(props[i].GetValue(this, null)); + } + sb.Append("}"); + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicDictionary.cs b/src/Umbraco.Core/Dynamics/DynamicDictionary.cs new file mode 100644 index 0000000000..9aef032bc3 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicDictionary.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Umbraco.Core.Dynamics +{ + public class DynamicDictionary : DynamicObject + { + readonly Dictionary _dictionary; + public DynamicDictionary(Dictionary sourceItems) + { + _dictionary = sourceItems; + } + public override bool TrySetMember(SetMemberBinder binder, object value) + { + if (_dictionary.ContainsKey(binder.Name)) + { + _dictionary[binder.Name.ToLower()] = value; + } + else + { + _dictionary.Add(binder.Name.ToLower(), value); + } + return true; + } + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (_dictionary != null) + { + if (_dictionary.TryGetValue(binder.Name.ToLower(), out result)) + { + return true; + } + } + result = null; + return true; + } + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicExpression.cs b/src/Umbraco.Core/Dynamics/DynamicExpression.cs new file mode 100644 index 0000000000..defb58caed --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicExpression.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Umbraco.Core.Dynamics +{ + internal static class DynamicExpression + { + public static bool ConvertDynamicNullToBooleanFalse = false; + public static Expression Parse(Type resultType, string expression, bool convertDynamicNullToBooleanFalse, params object[] values) + { + ConvertDynamicNullToBooleanFalse = convertDynamicNullToBooleanFalse; + ExpressionParser parser = new ExpressionParser(null, expression, values); + return parser.Parse(resultType); + } + + public static LambdaExpression ParseLambda(Type itType, Type resultType, string expression, bool convertDynamicNullToBooleanFalse, params object[] values) + { + return ParseLambda(new ParameterExpression[] { Expression.Parameter(itType, "") }, resultType, expression, convertDynamicNullToBooleanFalse, values); + } + + public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, bool convertDynamicNullToBooleanFalse, params object[] values) + { + ConvertDynamicNullToBooleanFalse = convertDynamicNullToBooleanFalse; + ExpressionParser parser = new ExpressionParser(parameters, expression, values); + return Expression.Lambda(parser.Parse(resultType), parameters); + } + + public static Expression> ParseLambda(string expression, bool convertDynamicNullToBooleanFalse, params object[] values) + { + return (Expression>)ParseLambda(typeof(T), typeof(S), expression, convertDynamicNullToBooleanFalse, values); + } + + public static Type CreateClass(params DynamicProperty[] properties) + { + return ClassFactory.Instance.GetDynamicClass(properties); + } + + public static Type CreateClass(IEnumerable properties) + { + return ClassFactory.Instance.GetDynamicClass(properties); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicGrouping.cs b/src/Umbraco.Core/Dynamics/DynamicGrouping.cs new file mode 100644 index 0000000000..f5e51e414a --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicGrouping.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using System.Collections; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicGrouping : IEnumerable + { + public IEnumerable> Inner; + + public DynamicGrouping OrderBy(string expression) + { + return this; + } + + public DynamicGrouping(DynamicNodeList list, string groupBy) + { + Inner = + list + .Items + .Select(node => + { + string predicate = groupBy; + var internalList = new DynamicNodeList(new DynamicNode[] { node }); + var query = (IQueryable)internalList.Select(predicate, new object[] { }); + var key = query.FirstOrDefault(); + return new + { + Key = key, + Node = node + }; + }) + .Where(item => item.Key != null) + .GroupBy(item => item.Key) + .Select(item => new Grouping() + { + Key = item.Key, + Elements = item.Select(inner => inner.Node) + }); + } + public DynamicGrouping(IEnumerable> source) + { + this.Inner = source; + } + + public IEnumerator GetEnumerator() + { + return Inner.GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicNode.cs b/src/Umbraco.Core/Dynamics/DynamicNode.cs new file mode 100644 index 0000000000..5292edd77d --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicNode.cs @@ -0,0 +1,1659 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Web; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using umbraco.interfaces; +using System.Reflection; +using System.Xml.Linq; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicNode : DynamicObject + { + #region consts + // these are private readonlys as const can't be Guids + private readonly Guid DATATYPE_YESNO_GUID = new Guid("38b352c1-e9f8-4fd8-9324-9a2eab06d97a"); + private readonly Guid DATATYPE_TINYMCE_GUID = new Guid("5e9b75ae-face-41c8-b47e-5f4b0fd82f83"); + private readonly Guid DATATYPE_DATETIMEPICKER_GUID = new Guid("b6fb1622-afa5-4bbf-a3cc-d9672a442222"); + private readonly Guid DATATYPE_DATEPICKER_GUID = new Guid("23e93522-3200-44e2-9f29-e61a6fcbb79a"); + //private readonly Guid DATATYPE_INTEGER_GUID = new Guid("1413afcb-d19a-4173-8e9a-68288d2a73b8"); + #endregion + + private readonly DynamicBackingItem _backingItem; + private DynamicNodeList _cachedChildren; + + internal DynamicNodeList OwnerList { get; set; } + + public DynamicNode(DynamicBackingItem n) + { + if (n != null) + this._backingItem = n; + else + throw new ArgumentNullException("n", "A node must be provided to make a dynamic instance"); + } + //public DynamicNode(int nodeId) + //{ + // this._n = new DynamicBackingItem(nodeId); + //} + //public DynamicNode(int nodeId, DynamicBackingItemType itemType) + //{ + // this._n = new DynamicBackingItem(nodeId, itemType); + //} + //public DynamicNode(string nodeId) + //{ + // int DynamicBackingItemId = 0; + // if (int.TryParse(nodeId, out DynamicBackingItemId)) + // { + // this._n = new DynamicBackingItem(DynamicBackingItemId); + // return; + // } + // throw new ArgumentException("Cannot instantiate a DynamicNode without an id"); + //} + public DynamicNode(IDocument node) + { + this._backingItem = new DynamicBackingItem(node); + } + //public DynamicNode(object nodeId) + //{ + // var dynamicBackingItemId = 0; + // if (int.TryParse(string.Format("{0}", nodeId), out dynamicBackingItemId)) + // { + // this._n = new DynamicBackingItem(dynamicBackingItemId); + // return; + // } + // throw new ArgumentException("Cannot instantiate a DynamicNode without an id"); + //} + public DynamicNode() + { + //Empty constructor for a special case with Generic Methods + } + + public dynamic AsDynamic() + { + return this; + } + + public DynamicNode Up() + { + return DynamicNodeWalker.Up(this); + } + public DynamicNode Up(int number) + { + return DynamicNodeWalker.Up(this, number); + } + public DynamicNode Up(string nodeTypeAlias) + { + return DynamicNodeWalker.Up(this, nodeTypeAlias); + } + public DynamicNode Down() + { + return DynamicNodeWalker.Down(this); + } + public DynamicNode Down(int number) + { + return DynamicNodeWalker.Down(this, number); + } + public DynamicNode Down(string nodeTypeAlias) + { + return DynamicNodeWalker.Down(this, nodeTypeAlias); + } + public DynamicNode Next() + { + return DynamicNodeWalker.Next(this); + } + public DynamicNode Next(int number) + { + return DynamicNodeWalker.Next(this, number); + } + public DynamicNode Next(string nodeTypeAlias) + { + return DynamicNodeWalker.Next(this, nodeTypeAlias); + } + + public DynamicNode Previous() + { + return DynamicNodeWalker.Previous(this); + } + public DynamicNode Previous(int number) + { + return DynamicNodeWalker.Previous(this, number); + } + public DynamicNode Previous(string nodeTypeAlias) + { + return DynamicNodeWalker.Previous(this, nodeTypeAlias); + } + public DynamicNode Sibling(int number) + { + return DynamicNodeWalker.Sibling(this, number); + } + public DynamicNode Sibling(string nodeTypeAlias) + { + return DynamicNodeWalker.Sibling(this, nodeTypeAlias); + } + + private DynamicNodeList GetChildren() + { + if (_cachedChildren == null) + { + var children = _backingItem.Children; + //testing + if (!children.Any() && _backingItem.Id == 0) + { + _cachedChildren = new DynamicNodeList(new List { new DynamicNode(this._backingItem) }); + } + else + { + _cachedChildren = new DynamicNodeList(_backingItem.Children.Select(x => new DynamicNode(x))); + } + } + return _cachedChildren; + } + //public DynamicNodeList XPath(string xPath) + //{ + // //if this DN was initialized with an underlying NodeFactory.Node + // if (n != null && n.Type == DynamicBackingItemType.Content) + // { + // //get the underlying xml content + // XmlDocument doc = umbraco.content.Instance.XmlContent; + // if (doc != null) + // { + // //get n as a XmlNode (to be used as the context point for the xpath) + // //rather than just applying the xPath to the root node, this lets us use .. etc from the DynamicNode point + + + // //in test mode, n.Id is 0, let this always succeed + // if (n.Id == 0) + // { + // List selfList = new List() { this }; + // return new DynamicNodeList(selfList); + // } + // XmlNode node = doc.SelectSingleNode(string.Format("//*[@id='{0}']", n.Id)); + // if (node != null) + // { + // //got the current node (within the XmlContent instance) + // XmlNodeList nodes = node.SelectNodes(xPath); + // if (nodes.Count > 0) + // { + // //we got some resulting nodes + // List nodeFactoryNodeList = new List(); + // //attempt to convert each node in the set to a NodeFactory.Node + // foreach (XmlNode nodeXmlNode in nodes) + // { + // try + // { + // nodeFactoryNodeList.Add(new NodeFactory.Node(nodeXmlNode)); + // } + // catch (Exception) { } //swallow the exceptions - the returned nodes might not be full nodes, e.g. property + // } + // //Wanted to do this, but because we return DynamicNodeList here, the only + // //common parent class is DynamicObject + // //maybe some future refactoring will solve this? + // //if (nodeFactoryNodeList.Count == 0) + // //{ + // // //if the xpath resulted in a node set, but none of them could be converted to NodeFactory.Node + // // XElement xElement = XElement.Parse(node.OuterXml); + // // //return + // // return new DynamicXml(xElement); + // //} + // //convert the NodeFactory nodelist to IEnumerable and return it as a DynamicNodeList + // return new DynamicNodeList(nodeFactoryNodeList.ConvertAll(nfNode => new DynamicNode((INode)nfNode))); + // } + // else + // { + // // XPath returned no nodes, return an empty DynamicNodeList + // return new DynamicNodeList(); + // } + // } + // else + // { + // throw new NullReferenceException("Couldn't locate the DynamicNode within the XmlContent"); + // } + // } + // else + // { + // throw new NullReferenceException("umbraco.content.Instance.XmlContent is null"); + // } + // } + // else + // { + // throw new NullReferenceException("DynamicNode wasn't initialized with an underlying NodeFactory.Node"); + // } + //} + + + //public DynamicNodeList Search(string term, bool useWildCards = true, string searchProvider = null) + //{ + // var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; + // if(!string.IsNullOrEmpty(searchProvider)) + // searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; + + // var t = term.Escape().Value; + // if (useWildCards) + // t = term.MultipleCharacterWildcard().Value; + + // string luceneQuery = "+__Path:(" + this.Path.Replace("-", "\\-") + "*) +" + t; + // var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery); + + // return Search(crit, searcher); + //} + + //public DynamicNodeList SearchDescendants(string term, bool useWildCards = true, string searchProvider = null) + //{ + // return Search(term, useWildCards, searchProvider); + //} + + //public DynamicNodeList SearchChildren(string term, bool useWildCards = true, string searchProvider = null) + //{ + // var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; + // if (!string.IsNullOrEmpty(searchProvider)) + // searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; + + // var t = term.Escape().Value; + // if (useWildCards) + // t = term.MultipleCharacterWildcard().Value; + + // string luceneQuery = "+parentID:" + this.Id.ToString() + " +" + t; + // var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery); + + // return Search(crit, searcher); + //} + + + //public DynamicNodeList Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + //{ + // var s = Examine.ExamineManager.Instance.DefaultSearchProvider; + // if (searchProvider != null) + // s = searchProvider; + + // var results = s.Search(criteria); + // return ExamineSearchUtill.convertSearchResultToDynamicNode(results); + //} + + + + + public bool HasProperty(string name) + { + if (_backingItem != null) + { + try + { + var prop = _backingItem.GetProperty(name); + if (prop == null) + { + // check for nicer support of Pascal Casing EVEN if alias is camelCasing: + if (name.Substring(0, 1).ToUpper() == name.Substring(0, 1)) + { + prop = _backingItem.GetProperty(name.Substring(0, 1).ToLower() + name.Substring((1))); + } + } + return (prop != null); + } + catch (Exception) + { + return false; + } + } + return false; + } + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + try + { + //Property? + result = typeof(DynamicNode).InvokeMember(binder.Name, + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic | + System.Reflection.BindingFlags.GetProperty, + null, + this, + args); + return true; + } + catch (MissingMethodException) + { + try + { + //Static or Instance Method? + result = typeof(DynamicNode).InvokeMember(binder.Name, + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic | + System.Reflection.BindingFlags.Static | + System.Reflection.BindingFlags.InvokeMethod, + null, + this, + args); + return true; + } + catch (MissingMethodException) + { + try + { + result = ExecuteExtensionMethod(args, binder.Name, false); + return true; + } + catch (TargetInvocationException) + { + result = new DynamicNull(); + return true; + } + + catch + { + result = null; + return false; + } + + } + + + } + catch + { + result = null; + return false; + } + + } + + private object ExecuteExtensionMethod(object[] args, string name, bool argsContainsThis) + { + object result = null; + + MethodInfo methodToExecute = ExtensionMethodFinder.FindExtensionMethod(typeof(IEnumerable), args, name, false); + if (methodToExecute == null) + { + methodToExecute = ExtensionMethodFinder.FindExtensionMethod(typeof(DynamicNodeList), args, name, false); + } + if (methodToExecute != null) + { + var genericArgs = (new[] { this }).Concat(args); + result = methodToExecute.Invoke(null, genericArgs.ToArray()); + } + else + { + throw new MissingMethodException(); + } + if (result != null) + { + //if (result is IEnumerable) + //{ + // result = new DynamicNodeList((IEnumerable)result); + //} + if (result is IEnumerable) + { + result = new DynamicNodeList((IEnumerable)result); + } + if (result is DynamicBackingItem) + { + result = new DynamicNode((DynamicBackingItem)result); + } + } + return result; + } + + //private static Dictionary, Type> _razorDataTypeModelTypes = null; + //private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + + //internal static Dictionary, Type> RazorDataTypeModelTypes + //{ + // get + // { + // using (var l = new UpgradeableReadLock(Locker)) + // { + // if (_razorDataTypeModelTypes == null) + // { + // l.UpgradeToWriteLock(); + + // var foundTypes = new Dictionary, Type>(); + + // //HttpContext.Current.Trace.Write("RazorDataTypeModelTypes cache is empty, populating cache using PluginTypeResolver..."); + // try + // { + // PluginManager.Current.ResolveRazorDataTypeModels() + // .ToList() + // .ConvertAll(type => + // { + // var razorDataTypeModelAttributes = type.GetCustomAttributes(true); + // return razorDataTypeModelAttributes.ToList().ConvertAll(razorDataTypeModelAttribute => + // { + // var g = razorDataTypeModelAttribute.DataTypeEditorId; + // var priority = razorDataTypeModelAttribute.Priority; + // return new KeyValuePair, Type>(new System.Tuple(g, priority), type); + // }); + // }) + // .SelectMany(item => item) + // .ToList() + // .ForEach(item => + // { + // System.Tuple key = item.Key; + // if (!foundTypes.ContainsKey(key)) + // { + // foundTypes.Add(key, item.Value); + // } + // }); + // //HttpContext.Current.Trace.Write(string.Format("{0} items added to cache...", foundTypes.Count)); + // var i = 1; + // foreach (var item in foundTypes) + // { + // HttpContext.Current.Trace.Write(string.Format("{0}/{1}: {2}@{4} => {3}", i, foundTypes.Count, item.Key.Item1, item.Value.FullName, item.Key.Item2)); + // i++; + // } + + // //there is no error, so set the collection + // _razorDataTypeModelTypes = foundTypes; + + // } + // catch (Exception ex) + // { + // //HttpContext.Current.Trace.Warn("Exception occurred while populating cache, will keep RazorDataTypeModelTypes to null so that this error remains visible and you don't end up with an empty cache with silent failure."); + // //HttpContext.Current.Trace.Warn(string.Format("The exception was {0} and the message was {1}. {2}", ex.GetType().FullName, ex.Message, ex.StackTrace)); + // } + + // } + // return _razorDataTypeModelTypes; + // } + // } + //} + + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + + var name = binder.Name; + result = null; //this will never be returned + + if (name == "ChildrenAsList" || name == "Children") + { + result = GetChildren(); + return true; + } + bool propertyExists = false; + if (_backingItem != null) + { + bool recursive = false; + if (name.StartsWith("_")) + { + name = name.Substring(1, name.Length - 1); + recursive = true; + } + var data = _backingItem.GetProperty(name, recursive, out propertyExists); + // check for nicer support of Pascal Casing EVEN if alias is camelCasing: + if (data == null && name.Substring(0, 1).ToUpper() == name.Substring(0, 1) && !propertyExists) + { + data = _backingItem.GetProperty(name.Substring(0, 1).ToLower() + name.Substring((1)), recursive, out propertyExists); + } + + if (data != null) + { + result = data.Value; + //special casing for true/false properties + //int/decimal are handled by ConvertPropertyValueByDataType + //fallback is stringT + if (_backingItem.NodeTypeAlias == null && data.Alias == null) + { + throw new ArgumentNullException("No node alias or property alias available. Unable to look up the datatype of the property you are trying to fetch."); + } + + //contextAlias is the node which the property data was returned from + Guid dataType = DynamicNodeDataSourceResolver.Current.DataSource.GetDataType(data.ContextAlias, data.Alias); + //HttpContext.Current.Trace.Write(string.Format("RazorDynamicNode got datatype {0} for {1} on {2}", dataType, data.Alias, data.ContextAlias)); + + //HttpContext.Current.Trace.Write(string.Format("Checking for a RazorDataTypeModel for data type guid {0}...", dataType)); + //HttpContext.Current.Trace.Write("Checking the RazorDataTypeModelTypes static mappings to see if there is a static mapping..."); + + //var staticMapping = UmbracoSettings.RazorDataTypeModelStaticMapping.FirstOrDefault(mapping => + //{ + // return mapping.Applies(dataType, data.ContextAlias, data.Alias); + //}); + //if (staticMapping != null) + //{ + // //HttpContext.Current.Trace.Write(string.Format("Found a staticMapping defined {0}, instantiating type and attempting to apply model...", staticMapping.Raw)); + // Type dataTypeType = Type.GetType(staticMapping.TypeName); + // if (dataTypeType != null) + // { + // object instance = null; + // if (TryCreateInstanceRazorDataTypeModel(dataType, dataTypeType, data.Value, out instance)) + // { + // result = instance; + // return true; + // } + // else + // { + // //HttpContext.Current.Trace.Write("Failed"); + // //HttpContext.Current.Trace.Warn(string.Format("Failed to create the instance of the model binder")); + // } + // } + // else + // { + // //HttpContext.Current.Trace.Warn(string.Format("staticMapping type name {0} came back as null from Type.GetType; check the casing, assembly presence, assembly framework version, namespace", staticMapping.TypeName)); + // } + //} + //else + //{ + // //HttpContext.Current.Trace.Write(string.Format("There isn't a staticMapping defined so checking the RazorDataTypeModelTypes cache...")); + //} + + + //if (RazorDataTypeModelTypes != null && RazorDataTypeModelTypes.Any(model => model.Key.Item1 == dataType) && dataType != Guid.Empty) + //{ + // var razorDataTypeModelDefinition = RazorDataTypeModelTypes.Where(model => model.Key.Item1 == dataType).OrderByDescending(model => model.Key.Item2).FirstOrDefault(); + // if (!(razorDataTypeModelDefinition.Equals(default(KeyValuePair, Type>)))) + // { + // Type dataTypeType = razorDataTypeModelDefinition.Value; + // object instance = null; + // if (TryCreateInstanceRazorDataTypeModel(dataType, dataTypeType, data.Value, out instance)) + // { + // result = instance; + // return true; + // } + // else + // { + // //HttpContext.Current.Trace.Write("Failed"); + // //HttpContext.Current.Trace.Warn(string.Format("Failed to create the instance of the model binder")); + // } + // } + // else + // { + // //HttpContext.Current.Trace.Write("Failed"); + // //HttpContext.Current.Trace.Warn(string.Format("Could not get the dataTypeType for the RazorDataTypeModel")); + // } + //} + //else + //{ + // if (RazorDataTypeModelTypes == null) + // { + // //HttpContext.Current.Trace.Write(string.Format("RazorDataTypeModelTypes is null, probably an exception while building the cache, falling back to ConvertPropertyValueByDataType", dataType)); + // } + // else + // { + // //HttpContext.Current.Trace.Write(string.Format("GUID {0} does not have a DataTypeModel, falling back to ConvertPropertyValueByDataType", dataType)); + // } + + //} + + //convert the string value to a known type + return ConvertPropertyValueByDataType(ref result, name, dataType); + + } + + //check if the alias is that of a child type + + var typeChildren = _backingItem.Children; + if (typeChildren != null) + { + var filteredTypeChildren = typeChildren.Where(x => + { + var ancestorAliases = DynamicNodeDataSourceResolver.Current.DataSource.GetAncestorOrSelfNodeTypeAlias(x); + if (ancestorAliases == null) + { + return false; + } + return ancestorAliases.Any(alias => alias == name || MakePluralName(alias) == name); + }).ToArray(); + + if (filteredTypeChildren.Any()) + { + result = new DynamicNodeList(filteredTypeChildren.Select(x => new DynamicNode(x))); + return true; + } + + } + + try + { + result = _backingItem.GetType().InvokeMember(binder.Name, + System.Reflection.BindingFlags.GetProperty | + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic, + null, + _backingItem, + null); + return true; + } + catch + { + //result = null; + //return false; + } + } + + //if property access, type lookup and member invoke all failed + //at this point, we're going to return null + //instead, we return a DynamicNull - see comments in that file + //this will let things like Model.ChildItem work and return nothing instead of crashing + if (!propertyExists && result == null) + { + //.Where explictly checks for this type + //and will make it false + //which means backwards equality (&& property != true) will pass + //forwwards equality (&& property or && property == true) will fail + result = new DynamicNull(); + return true; + } + return true; + } + //private bool TryCreateInstanceRazorDataTypeModel(Guid dataType, Type dataTypeType, string value, out object result) + //{ + // //HttpContext.Current.Trace.Write(string.Format("Found dataType {0} for GUID {1}", dataTypeType.FullName, dataType)); + // IRazorDataTypeModel razorDataTypeModel = Activator.CreateInstance(dataTypeType, false) as IRazorDataTypeModel; + // //HttpContext.Current.Trace.Write(string.Format("Instantiating {0}...", dataTypeType.FullName)); + // if (razorDataTypeModel != null) + // { + // //HttpContext.Current.Trace.Write("Success"); + // object instance = null; + // //HttpContext.Current.Trace.Write("Calling Init on razorDataTypeModel"); + // if (razorDataTypeModel.Init(_n.Id, value, out instance)) + // { + // if (instance != null) + // { + // //HttpContext.Current.Trace.Write(string.Format("razorDataTypeModel successfully instantiated and returned a valid instance of type {0}", instance.GetType().FullName)); + // } + // else + // { + // //HttpContext.Current.Trace.Warn("razorDataTypeModel successfully instantiated but returned null for instance"); + // } + // result = instance; + // return true; + // } + // else + // { + // if (instance != null) + // { + // //HttpContext.Current.Trace.Write(string.Format("razorDataTypeModel returned false but returned a valid instance of type {0}", instance.GetType().FullName)); + // } + // else + // { + // //HttpContext.Current.Trace.Warn("razorDataTypeModel successfully instantiated but returned null for instance"); + // } + // } + // } + // else + // { + // //HttpContext.Current.Trace.Write("Failed"); + // //HttpContext.Current.Trace.Warn(string.Format("DataTypeModel {0} failed to instantiate, perhaps it is lacking a parameterless constructor or doesn't implement IRazorDataTypeModel?", dataTypeType.FullName)); + // } + // result = null; + // return false; + //} + private bool ConvertPropertyValueByDataType(ref object result, string name, Guid dataType) + { + //the resulting property is a string, but to support some of the nice linq stuff in .Where + //we should really check some more types + string sResult = string.Format("{0}", result).Trim(); + + //boolean + if (dataType == DATATYPE_YESNO_GUID) + { + bool parseResult; + if (string.IsNullOrEmpty(string.Format("{0}", result))) + { + result = false; + return true; + } + if (Boolean.TryParse(sResult.Replace("1", "true").Replace("0", "false"), out parseResult)) + { + result = parseResult; + return true; + } + } + + ////integer + ////this will eat csv strings, so only do it if the decimal also includes a decimal seperator (according to the current culture) + //if (dataType == DATATYPE_INTEGER_GUID) + //{ + // int iResult = 0; + // if (int.TryParse(sResult, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.CurrentCulture, out iResult)) + // { + // result = iResult; + // return true; + // } + //} + + //this will eat csv strings, so only do it if the decimal also includes a decimal seperator (according to the current culture) + if (sResult.Contains(System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator)) + { + //decimal + decimal dResult = 0; + if (decimal.TryParse(sResult, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.CurrentCulture, out dResult)) + { + result = dResult; + return true; + } + } + if (dataType == DATATYPE_DATETIMEPICKER_GUID || dataType == DATATYPE_DATEPICKER_GUID) + { + //date + DateTime dtResult = DateTime.MinValue; + if (DateTime.TryParse(string.Format("{0}", result), out dtResult)) + { + result = dtResult; + return true; + } + else + { + result = new DynamicNull(); + return true; + } + } + + // Rich text editor (return IHtmlString so devs doesn't need to decode html + if (dataType == DATATYPE_TINYMCE_GUID) + { + result = new HtmlString(result.ToString()); + return true; + } + + + if (string.Equals("true", sResult, StringComparison.CurrentCultureIgnoreCase)) + { + result = true; + return true; + } + if (string.Equals("false", sResult, StringComparison.CurrentCultureIgnoreCase)) + { + result = false; + return true; + } + + if (result != null) + { + //a really rough check to see if this may be valid xml + if (sResult.StartsWith("<") && sResult.EndsWith(">") && sResult.Contains("/")) + { + try + { + XElement e = XElement.Parse(DynamicXml.StripDashesInElementOrAttributeNames(sResult), LoadOptions.None); + if (e != null) + { + //check that the document element is not one of the disallowed elements + //allows RTE to still return as html if it's valid xhtml + string documentElement = e.Name.LocalName; + if (!UmbracoSettings.NotDynamicXmlDocumentElements.Any(tag => + string.Equals(tag, documentElement, StringComparison.CurrentCultureIgnoreCase))) + { + result = new DynamicXml(e); + return true; + } + else + { + //we will just return this as a string + return true; + } + } + } + catch (Exception) + { + //we will just return this as a string + return true; + } + + } + } + + return true; + } + + //public DynamicNode Media(string propertyAlias) + //{ + // if (_n != null) + // { + // IProperty prop = _n.GetProperty(propertyAlias); + // if (prop != null) + // { + // int mediaNodeId; + // if (int.TryParse(prop.Value, out mediaNodeId)) + // { + // return _razorLibrary.Value.MediaById(mediaNodeId); + // } + // } + // return null; + // } + // return null; + //} + //public bool IsProtected + //{ + // get + // { + // if (_n != null) + // { + // return umbraco.library.IsProtected(_n.Id, _n.Path); + // } + // return false; + // } + //} + //public bool HasAccess + //{ + // get + // { + // if (_n != null) + // { + // return umbraco.library.HasAccess(_n.Id, _n.Path); + // } + // return true; + // } + //} + + //public string Media(string propertyAlias, string mediaPropertyAlias) + //{ + // if (_n != null) + // { + // IProperty prop = _n.GetProperty(propertyAlias); + // if (prop == null && propertyAlias.Substring(0, 1).ToUpper() == propertyAlias.Substring(0, 1)) + // { + // prop = _n.GetProperty(propertyAlias.Substring(0, 1).ToLower() + propertyAlias.Substring((1))); + // } + // if (prop != null) + // { + // int mediaNodeId; + // if (int.TryParse(prop.Value, out mediaNodeId)) + // { + // umbraco.cms.businesslogic.media.Media media = new cms.businesslogic.media.Media(mediaNodeId); + // if (media != null) + // { + // Property mprop = media.getProperty(mediaPropertyAlias); + // // check for nicer support of Pascal Casing EVEN if alias is camelCasing: + // if (prop == null && mediaPropertyAlias.Substring(0, 1).ToUpper() == mediaPropertyAlias.Substring(0, 1)) + // { + // mprop = media.getProperty(mediaPropertyAlias.Substring(0, 1).ToLower() + mediaPropertyAlias.Substring((1))); + // } + // if (mprop != null) + // { + // return string.Format("{0}", mprop.Value); + // } + // } + // } + // } + // } + // return null; + //} + + //this is from SqlMetal and just makes it a bit of fun to allow pluralisation + private static string MakePluralName(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; + } + + private static bool IsVowel(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; + } + public DynamicNode AncestorOrSelf() + { + //TODO: Why is this query like this?? + return AncestorOrSelf(node => node.Level == 1); + } + public DynamicNode AncestorOrSelf(int level) + { + return AncestorOrSelf(node => node.Level == level); + } + public DynamicNode AncestorOrSelf(string nodeTypeAlias) + { + return AncestorOrSelf(node => node.NodeTypeAlias == nodeTypeAlias); + } + public DynamicNode AncestorOrSelf(Func func) + { + var node = this; + while (node != null) + { + if (func(node)) return node; + DynamicNode parent = node.Parent; + if (parent != null) + { + if (this != parent) + { + node = parent; + } + else + { + return node; + } + } + else + { + return null; + } + } + return node; + } + public DynamicNodeList AncestorsOrSelf(Func func) + { + var ancestorList = new List(); + var node = this; + ancestorList.Add(node); + while (node != null) + { + if (node.Level == 1) break; + DynamicNode parent = node.Parent; + if (parent != null) + { + if (this != parent) + { + node = parent; + if (func(node)) + { + ancestorList.Add(node); + } + } + else + { + break; + } + } + else + { + break; + } + } + ancestorList.Reverse(); + return new DynamicNodeList(ancestorList); + } + public DynamicNodeList AncestorsOrSelf() + { + return AncestorsOrSelf(n => true); + } + public DynamicNodeList AncestorsOrSelf(string nodeTypeAlias) + { + return AncestorsOrSelf(n => n.NodeTypeAlias == nodeTypeAlias); + } + public DynamicNodeList AncestorsOrSelf(int level) + { + return AncestorsOrSelf(n => n.Level <= level); + } + public DynamicNodeList Descendants(string nodeTypeAlias) + { + return Descendants(p => p.NodeTypeAlias == nodeTypeAlias); + } + public DynamicNodeList Descendants(int level) + { + return Descendants(p => p.Level >= level); + } + public DynamicNodeList Descendants() + { + return Descendants(n => true); + } + public DynamicNodeList Descendants(Func func) + { + var flattenedNodes = this._backingItem.Children.Map(func, (DynamicBackingItem n) => n.Children); + return new DynamicNodeList(flattenedNodes.ToList().ConvertAll(dynamicBackingItem => new DynamicNode(dynamicBackingItem))); + } + public DynamicNodeList DescendantsOrSelf(int level) + { + return DescendantsOrSelf(p => p.Level >= level); + } + public DynamicNodeList DescendantsOrSelf(string nodeTypeAlias) + { + return DescendantsOrSelf(p => p.NodeTypeAlias == nodeTypeAlias); + } + public DynamicNodeList DescendantsOrSelf() + { + return DescendantsOrSelf(p => true); + } + public DynamicNodeList DescendantsOrSelf(Func func) + { + if (this._backingItem != null) + { + var thisNode = new List(); + if (func(this._backingItem)) + { + thisNode.Add(this._backingItem); + } + var flattenedNodes = this._backingItem.Children.Map(func, (DynamicBackingItem n) => n.Children); + return new DynamicNodeList(thisNode.Concat(flattenedNodes).ToList().ConvertAll(dynamicBackingItem => new DynamicNode(dynamicBackingItem))); + } + return new DynamicNodeList(Enumerable.Empty()); + } + public DynamicNodeList Ancestors(int level) + { + return Ancestors(n => n.Level <= level); + } + public DynamicNodeList Ancestors(string nodeTypeAlias) + { + return Ancestors(n => n.NodeTypeAlias == nodeTypeAlias); + } + public DynamicNodeList Ancestors() + { + return Ancestors(n => true); + } + public DynamicNodeList Ancestors(Func func) + { + var ancestorList = new List(); + var node = this; + while (node != null) + { + if (node.Level == 1) break; + DynamicNode parent = node.Parent; + if (parent != null) + { + if (this != parent) + { + node = parent; + if (func(node)) + { + ancestorList.Add(node); + } + } + else + { + break; + } + } + else + { + break; + } + } + ancestorList.Reverse(); + return new DynamicNodeList(ancestorList); + } + public DynamicNode Parent + { + get + { + if (_backingItem == null) + { + return null; + } + if (_backingItem.Parent != null) + { + return new DynamicNode(_backingItem.Parent); + } + if (_backingItem != null && _backingItem.Id == 0) + { + return this; + } + return null; + } + } + + //private readonly Lazy _razorLibrary = new Lazy(() => new RazorLibraryCore(null)); + + public int TemplateId + { + get { if (_backingItem == null) return 0; return _backingItem.TemplateId; } + } + + public int SortOrder + { + get { if (_backingItem == null) return 0; return _backingItem.SortOrder; } + } + + public string Name + { + get { if (_backingItem == null) return null; return _backingItem.Name; } + } + public bool Visible + { + get + { + if (_backingItem == null) return true; + var umbracoNaviHide = _backingItem.GetProperty("umbracoNaviHide"); + if (umbracoNaviHide != null) + { + return umbracoNaviHide.Value != "1"; + } + return true; + } + } + //public string Url + //{ + // get { if (_n == null) return null; return _n.Url; } + //} + + public string UrlName + { + get { if (_backingItem == null) return null; return _backingItem.UrlName; } + } + + public string NodeTypeAlias + { + get { if (_backingItem == null) return null; return _backingItem.NodeTypeAlias; } + } + + public string WriterName + { + get { if (_backingItem == null) return null; return _backingItem.WriterName; } + } + + public string CreatorName + { + get { if (_backingItem == null) return null; return _backingItem.CreatorName; } + } + + public int WriterId + { + get { if (_backingItem == null) return 0; return _backingItem.WriterId; } + } + + public int CreatorId + { + get { if (_backingItem == null) return 0; return _backingItem.CreatorId; } + } + + public string Path + { + get { return _backingItem.Path; } + } + + public DateTime CreateDate + { + get { if (_backingItem == null) return DateTime.MinValue; return _backingItem.CreateDate; } + } + public int Id + { + get { if (_backingItem == null) return 0; return _backingItem.Id; } + } + + public DateTime UpdateDate + { + get { if (_backingItem == null) return DateTime.MinValue; return _backingItem.UpdateDate; } + } + + public Guid Version + { + get { if (_backingItem == null) return Guid.Empty; return _backingItem.Version; } + } + + //public string NiceUrl + //{ + // get { if (_n == null) return null; return _n.NiceUrl; } + //} + + public int Level + { + get { if (_backingItem == null) return 0; return _backingItem.Level; } + } + + public IEnumerable Properties + { + get { if (_backingItem == null) return null; return _backingItem.Properties; } + } + + //public IEnumerable Children + //{ + // get { if (_n == null) return null; return _n.Children; } + //} + + public IEnumerable Children + { + get { if (_backingItem == null) return null; return GetChildren(); } + } + + public IDocumentProperty GetProperty(string alias) + { + if (_backingItem == null) return null; + return _backingItem.GetProperty(alias); + } + public IDocumentProperty GetProperty(string alias, bool recursive) + { + if (!recursive) return GetProperty(alias); + if (_backingItem == null) return null; + DynamicBackingItem context = this._backingItem; + var prop = _backingItem.GetProperty(alias); + while (prop == null) + { + context = context.Parent; + if (context == null) break; + prop = context.GetProperty(alias); + } + if (prop != null) + { + return prop; + } + return null; + } + public string GetPropertyValue(string alias) + { + return GetPropertyValue(alias, null); + } + public string GetPropertyValue(string alias, string fallback) + { + var prop = GetProperty(alias); + if (prop != null) return prop.Value; + return fallback; + } + public string GetPropertyValue(string alias, bool recursive) + { + return GetPropertyValue(alias, recursive, null); + } + public string GetPropertyValue(string alias, bool recursive, string fallback) + { + var prop = GetProperty(alias, recursive); + if (prop != null) return prop.Value; + return fallback; + } + //public System.Data.DataTable ChildrenAsTable() + //{ + // if (_n == null) return null; + // return _n.ChildrenAsTable(); + //} + + //public System.Data.DataTable ChildrenAsTable(string nodeTypeAliasFilter) + //{ + // if (_n == null) return null; + // return _n.ChildrenAsTable(nodeTypeAliasFilter); + //} + public bool IsNull(string alias, bool recursive) + { + var prop = GetProperty(alias, recursive); + if (prop == null) return true; + return (prop as PropertyResult).IsNull(); + } + public bool IsNull(string alias) + { + return IsNull(alias, false); + } + public bool HasValue(string alias) + { + return HasValue(alias, false); + } + public bool HasValue(string alias, bool recursive) + { + var prop = GetProperty(alias, recursive); + if (prop == null) return false; + return (prop as PropertyResult).HasValue(); + } + public IHtmlString HasValue(string alias, string valueIfTrue, string valueIfFalse) + { + return HasValue(alias, false) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); + } + public IHtmlString HasValue(string alias, bool recursive, string valueIfTrue, string valueIfFalse) + { + return HasValue(alias, recursive) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); + } + public IHtmlString HasValue(string alias, string valueIfTrue) + { + return HasValue(alias, false) ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); + } + public IHtmlString HasValue(string alias, bool recursive, string valueIfTrue) + { + return HasValue(alias, recursive) ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); + } + public int Position() + { + return this.Index(); + } + public int Index() + { + if (this.OwnerList == null && this.Parent != null) + { + //var list = this.Parent.Children.Select(n => new DynamicNode(n)); + var list = this.Parent.Children; + this.OwnerList = new DynamicNodeList(list); + } + if (this.OwnerList != null) + { + List container = this.OwnerList.Items.ToList(); + int currentIndex = container.FindIndex(n => n.Id == this.Id); + if (currentIndex != -1) + { + return currentIndex; + } + else + { + throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", this.Id)); + } + } + else + { + throw new ArgumentNullException(string.Format("Node {0} has been orphaned and doesn't belong to a DynamicNodeList", this.Id)); + } + } + public bool IsFirst() + { + return IsHelper(n => n.Index() == 0); + } + public HtmlString IsFirst(string valueIfTrue) + { + return IsHelper(n => n.Index() == 0, valueIfTrue); + } + public HtmlString IsFirst(string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Index() == 0, valueIfTrue, valueIfFalse); + } + public bool IsNotFirst() + { + return !IsHelper(n => n.Index() == 0); + } + public HtmlString IsNotFirst(string valueIfTrue) + { + return IsHelper(n => n.Index() != 0, valueIfTrue); + } + public HtmlString IsNotFirst(string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Index() != 0, valueIfTrue, valueIfFalse); + } + public bool IsPosition(int index) + { + if (this.OwnerList == null) + { + return false; + } + return IsHelper(n => n.Index() == index); + } + public HtmlString IsPosition(int index, string valueIfTrue) + { + if (this.OwnerList == null) + { + return new HtmlString(string.Empty); + } + return IsHelper(n => n.Index() == index, valueIfTrue); + } + public HtmlString IsPosition(int index, string valueIfTrue, string valueIfFalse) + { + if (this.OwnerList == null) + { + return new HtmlString(valueIfFalse); + } + return IsHelper(n => n.Index() == index, valueIfTrue, valueIfFalse); + } + public bool IsModZero(int modulus) + { + if (this.OwnerList == null) + { + return false; + } + return IsHelper(n => n.Index() % modulus == 0); + } + public HtmlString IsModZero(int modulus, string valueIfTrue) + { + if (this.OwnerList == null) + { + return new HtmlString(string.Empty); + } + return IsHelper(n => n.Index() % modulus == 0, valueIfTrue); + } + public HtmlString IsModZero(int modulus, string valueIfTrue, string valueIfFalse) + { + if (this.OwnerList == null) + { + return new HtmlString(valueIfFalse); + } + return IsHelper(n => n.Index() % modulus == 0, valueIfTrue, valueIfFalse); + } + + public bool IsNotModZero(int modulus) + { + if (this.OwnerList == null) + { + return false; + } + return IsHelper(n => n.Index() % modulus != 0); + } + public HtmlString IsNotModZero(int modulus, string valueIfTrue) + { + if (this.OwnerList == null) + { + return new HtmlString(string.Empty); + } + return IsHelper(n => n.Index() % modulus != 0, valueIfTrue); + } + public HtmlString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse) + { + if (this.OwnerList == null) + { + return new HtmlString(valueIfFalse); + } + return IsHelper(n => n.Index() % modulus != 0, valueIfTrue, valueIfFalse); + } + public bool IsNotPosition(int index) + { + if (this.OwnerList == null) + { + return false; + } + return !IsHelper(n => n.Index() == index); + } + public HtmlString IsNotPosition(int index, string valueIfTrue) + { + if (this.OwnerList == null) + { + return new HtmlString(string.Empty); + } + return IsHelper(n => n.Index() != index, valueIfTrue); + } + public HtmlString IsNotPosition(int index, string valueIfTrue, string valueIfFalse) + { + if (this.OwnerList == null) + { + return new HtmlString(valueIfFalse); + } + return IsHelper(n => n.Index() != index, valueIfTrue, valueIfFalse); + } + public bool IsLast() + { + if (this.OwnerList == null) + { + return false; + } + int count = this.OwnerList.Count(); + return IsHelper(n => n.Index() == count - 1); + } + public HtmlString IsLast(string valueIfTrue) + { + if (this.OwnerList == null) + { + return new HtmlString(string.Empty); + } + int count = this.OwnerList.Count(); + return IsHelper(n => n.Index() == count - 1, valueIfTrue); + } + public HtmlString IsLast(string valueIfTrue, string valueIfFalse) + { + if (this.OwnerList == null) + { + return new HtmlString(valueIfFalse); + } + int count = this.OwnerList.Count(); + return IsHelper(n => n.Index() == count - 1, valueIfTrue, valueIfFalse); + } + public bool IsNotLast() + { + if (this.OwnerList == null) + { + return false; + } + int count = this.OwnerList.Count(); + return !IsHelper(n => n.Index() == count - 1); + } + public HtmlString IsNotLast(string valueIfTrue) + { + if (this.OwnerList == null) + { + return new HtmlString(string.Empty); + } + int count = this.OwnerList.Count(); + return IsHelper(n => n.Index() != count - 1, valueIfTrue); + } + public HtmlString IsNotLast(string valueIfTrue, string valueIfFalse) + { + if (this.OwnerList == null) + { + return new HtmlString(valueIfFalse); + } + int count = this.OwnerList.Count(); + return IsHelper(n => n.Index() != count - 1, valueIfTrue, valueIfFalse); + } + public bool IsEven() + { + return IsHelper(n => n.Index() % 2 == 0); + } + public HtmlString IsEven(string valueIfTrue) + { + return IsHelper(n => n.Index() % 2 == 0, valueIfTrue); + } + public HtmlString IsEven(string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Index() % 2 == 0, valueIfTrue, valueIfFalse); + } + public bool IsOdd() + { + return IsHelper(n => n.Index() % 2 == 1); + } + public HtmlString IsOdd(string valueIfTrue) + { + return IsHelper(n => n.Index() % 2 == 1, valueIfTrue); + } + public HtmlString IsOdd(string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Index() % 2 == 1, valueIfTrue, valueIfFalse); + } + public bool IsEqual(DynamicNode other) + { + return IsHelper(n => n.Id == other.Id); + } + public HtmlString IsEqual(DynamicNode other, string valueIfTrue) + { + return IsHelper(n => n.Id == other.Id, valueIfTrue); + } + public HtmlString IsEqual(DynamicNode other, string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Id == other.Id, valueIfTrue, valueIfFalse); + } + public bool IsNotEqual(DynamicNode other) + { + return IsHelper(n => n.Id != other.Id); + } + public HtmlString IsNotEqual(DynamicNode other, string valueIfTrue) + { + return IsHelper(n => n.Id != other.Id, valueIfTrue); + } + public HtmlString IsNotEqual(DynamicNode other, string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Id != other.Id, valueIfTrue, valueIfFalse); + } + public bool IsDescendant(DynamicNode other) + { + var ancestors = this.Ancestors(); + return IsHelper(n => ancestors.Items.Find(ancestor => ancestor.Id == other.Id) != null); + } + public HtmlString IsDescendant(DynamicNode other, string valueIfTrue) + { + var ancestors = this.Ancestors(); + return IsHelper(n => ancestors.Items.Find(ancestor => ancestor.Id == other.Id) != null, valueIfTrue); + } + public HtmlString IsDescendant(DynamicNode other, string valueIfTrue, string valueIfFalse) + { + var ancestors = this.Ancestors(); + return IsHelper(n => ancestors.Items.Find(ancestor => ancestor.Id == other.Id) != null, valueIfTrue, valueIfFalse); + } + public bool IsDescendantOrSelf(DynamicNode other) + { + var ancestors = this.AncestorsOrSelf(); + return IsHelper(n => ancestors.Items.Find(ancestor => ancestor.Id == other.Id) != null); + } + public HtmlString IsDescendantOrSelf(DynamicNode other, string valueIfTrue) + { + var ancestors = this.AncestorsOrSelf(); + return IsHelper(n => ancestors.Items.Find(ancestor => ancestor.Id == other.Id) != null, valueIfTrue); + } + public HtmlString IsDescendantOrSelf(DynamicNode other, string valueIfTrue, string valueIfFalse) + { + var ancestors = this.AncestorsOrSelf(); + return IsHelper(n => ancestors.Items.Find(ancestor => ancestor.Id == other.Id) != null, valueIfTrue, valueIfFalse); + } + public bool IsAncestor(DynamicNode other) + { + var descendants = this.Descendants(); + return IsHelper(n => descendants.Items.Find(descendant => descendant.Id == other.Id) != null); + } + public HtmlString IsAncestor(DynamicNode other, string valueIfTrue) + { + var descendants = this.Descendants(); + return IsHelper(n => descendants.Items.Find(descendant => descendant.Id == other.Id) != null, valueIfTrue); + } + public HtmlString IsAncestor(DynamicNode other, string valueIfTrue, string valueIfFalse) + { + var descendants = this.Descendants(); + return IsHelper(n => descendants.Items.Find(descendant => descendant.Id == other.Id) != null, valueIfTrue, valueIfFalse); + } + public bool IsAncestorOrSelf(DynamicNode other) + { + var descendants = this.DescendantsOrSelf(); + return IsHelper(n => descendants.Items.Find(descendant => descendant.Id == other.Id) != null); + } + public HtmlString IsAncestorOrSelf(DynamicNode other, string valueIfTrue) + { + var descendants = this.DescendantsOrSelf(); + return IsHelper(n => descendants.Items.Find(descendant => descendant.Id == other.Id) != null, valueIfTrue); + } + public HtmlString IsAncestorOrSelf(DynamicNode other, string valueIfTrue, string valueIfFalse) + { + var descendants = this.DescendantsOrSelf(); + return IsHelper(n => descendants.Items.Find(descendant => descendant.Id == other.Id) != null, valueIfTrue, valueIfFalse); + } + public bool IsHelper(Func test) + { + return test(this); + } + public HtmlString IsHelper(Func test, string valueIfTrue) + { + return IsHelper(test, valueIfTrue, string.Empty); + } + public HtmlString IsHelper(Func test, string valueIfTrue, string valueIfFalse) + { + return test(this) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); + } + public HtmlString Where(string predicate, string valueIfTrue) + { + return Where(predicate, valueIfTrue, string.Empty); + } + public HtmlString Where(string predicate, string valueIfTrue, string valueIfFalse) + { + if (Where(predicate)) + { + return new HtmlString(valueIfTrue); + } + return new HtmlString(valueIfFalse); + } + public bool Where(string predicate) + { + //Totally gonna cheat here + var dynamicNodeList = new DynamicNodeList(); + dynamicNodeList.Add(this); + var filtered = dynamicNodeList.Where(predicate); + if (Queryable.Count(filtered) == 1) + { + //this node matches the predicate + return true; + } + return false; + } + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicNodeDataSourceResolver.cs b/src/Umbraco.Core/Dynamics/DynamicNodeDataSourceResolver.cs new file mode 100644 index 0000000000..4db42564f4 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicNodeDataSourceResolver.cs @@ -0,0 +1,19 @@ +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core.Dynamics +{ + /// + /// This exists only because we want Dynamics in the Core project but DynamicNode has references to ContentType to run some queries + /// and currently the business logic part of Umbraco is still in the legacy project and we don't want to move that to the core so in the + /// meantime until the new APIs are made, we need to have this data source in place with a resolver which is set in the web project. + /// + internal class DynamicNodeDataSourceResolver : SingleObjectResolverBase + { + public IDynamicNodeDataSource DataSource { get; private set; } + + public DynamicNodeDataSourceResolver(IDynamicNodeDataSource dataSource) + { + DataSource = dataSource; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicNodeIdEqualityComparer.cs b/src/Umbraco.Core/Dynamics/DynamicNodeIdEqualityComparer.cs new file mode 100644 index 0000000000..9d5110dfcd --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicNodeIdEqualityComparer.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicNodeIdEqualityComparer : EqualityComparer + { + + public override bool Equals(DynamicNode x, DynamicNode y) + { + //Check whether the compared objects reference the same data. + if (Object.ReferenceEquals(x, y)) return true; + + //Check whether any of the compared objects is null. + if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) + return false; + + //Check whether the nodes ids are equal. + return x.Id == y.Id; + + } + + public override int GetHashCode(DynamicNode obj) + { + if (Object.ReferenceEquals(obj, null)) return 0; + + //Get hash code for the Name field if it is not null. + int hashId = obj.Id.GetHashCode(); + + return hashId; + } + + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicNodeList.cs b/src/Umbraco.Core/Dynamics/DynamicNodeList.cs new file mode 100644 index 0000000000..089ff3ad2b --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicNodeList.cs @@ -0,0 +1,485 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Dynamic; +using Umbraco.Core.Models; +using umbraco.interfaces; +using System.Collections; +using System.Reflection; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicNodeList : DynamicObject, IEnumerable + { + internal List Items { get; set; } + + public DynamicNodeList() + { + Items = new List(); + } + public DynamicNodeList(IEnumerable items) + { + List list = items.ToList(); + list.ForEach(node => node.OwnerList = this); + Items = list; + } + + //public DynamicNodeList(IEnumerable items) + //{ + // List list = items.ToList().ConvertAll(n => new DynamicNode(n)); + // list.ForEach(node => node.OwnerList = this); + // Items = list; + //} + + public DynamicNodeList(IEnumerable items) + { + List list = items.Select(x => new DynamicNode(x)).ToList(); + list.ForEach(node => node.OwnerList = this); + Items = list; + } + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + int index = (int)indexes[0]; + try + { + result = this.Items.ElementAt(index); + return true; + } + catch (IndexOutOfRangeException) + { + result = new DynamicNull(); + return true; + } + } + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + var name = binder.Name; + if (name == "Where") + { + string predicate = args.First().ToString(); + var values = args.Skip(1).ToArray(); + result = new DynamicNodeList(this.Where(predicate, values).ToList()); + return true; + } + if (name == "OrderBy") + { + result = new DynamicNodeList(this.OrderBy(args.First().ToString()).ToList()); + return true; + } + if (name == "InGroupsOf") + { + int groupSize = 0; + if (int.TryParse(args.First().ToString(), out groupSize)) + { + result = this.InGroupsOf(groupSize); + return true; + } + result = new DynamicNull(); + return true; + } + if (name == "GroupedInto") + { + int groupCount = 0; + if (int.TryParse(args.First().ToString(), out groupCount)) + { + result = this.GroupedInto(groupCount); + return true; + } + result = new DynamicNull(); + return true; + } + if (name == "GroupBy") + { + result = this.GroupBy(args.First().ToString()); + return true; + } + if (name == "Average" || name == "Min" || name == "Max" || name == "Sum") + { + result = Aggregate(args, name); + return true; + } + if (name == "Union") + { + if ((args.First() as IEnumerable) != null) + { + result = new DynamicNodeList(this.Items.Union(args.First() as IEnumerable)); + return true; + } + if ((args.First() as DynamicNodeList) != null) + { + result = new DynamicNodeList(this.Items.Union((args.First() as DynamicNodeList).Items)); + return true; + } + } + if (name == "Except") + { + if ((args.First() as IEnumerable) != null) + { + result = new DynamicNodeList(this.Items.Except(args.First() as IEnumerable, new DynamicNodeIdEqualityComparer())); + return true; + } + if ((args.First() as DynamicNodeList) != null) + { + result = new DynamicNodeList(this.Items.Except((args.First() as DynamicNodeList).Items, new DynamicNodeIdEqualityComparer())); + return true; + } + } + if (name == "Intersect") + { + if ((args.First() as IEnumerable) != null) + { + result = new DynamicNodeList(this.Items.Intersect(args.First() as IEnumerable, new DynamicNodeIdEqualityComparer())); + return true; + } + if ((args.First() as DynamicNodeList) != null) + { + result = new DynamicNodeList(this.Items.Intersect((args.First() as DynamicNodeList).Items, new DynamicNodeIdEqualityComparer())); + return true; + } + } + if (name == "Distinct") + { + result = new DynamicNodeList(this.Items.Distinct(new DynamicNodeIdEqualityComparer())); + return true; + } + if (name == "Pluck" || name == "Select") + { + result = Pluck(args); + return true; + } + try + { + //Property? + result = Items.GetType().InvokeMember(binder.Name, + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic | + System.Reflection.BindingFlags.GetProperty, + null, + Items, + args); + return true; + } + catch (MissingMethodException) + { + try + { + //Static or Instance Method? + result = Items.GetType().InvokeMember(binder.Name, + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic | + System.Reflection.BindingFlags.Static | + System.Reflection.BindingFlags.InvokeMethod, + null, + Items, + args); + return true; + } + catch (MissingMethodException) + { + + try + { + result = ExecuteExtensionMethod(args, name, false); + return true; + } + catch (TargetInvocationException) + { + //We do this to enable error checking of Razor Syntax when a method e.g. ElementAt(2) is used. + //When the Script is tested, there's no Children which means ElementAt(2) is invalid (IndexOutOfRange) + //Instead, we are going to return an empty DynamicNode. + result = new DynamicNode(); + return true; + } + + catch + { + result = null; + return false; + } + + } + + + } + catch + { + result = null; + return false; + } + + } + private T Aggregate(List data, string name) where T : struct + { + switch (name) + { + case "Min": + return data.Min(); + case "Max": + return data.Max(); + case "Average": + if (typeof(T) == typeof(int)) + { + return (T)Convert.ChangeType((data as List).Average(), typeof(T)); + } + if (typeof(T) == typeof(decimal)) + { + return (T)Convert.ChangeType((data as List).Average(), typeof(T)); + } + break; + case "Sum": + if (typeof(T) == typeof(int)) + { + return (T)Convert.ChangeType((data as List).Sum(), typeof(T)); + } + if (typeof(T) == typeof(decimal)) + { + return (T)Convert.ChangeType((data as List).Sum(), typeof(T)); + } + break; + } + return default(T); + } + private object Aggregate(object[] args, string name) + { + object result; + string predicate = args.First().ToString(); + var values = args.Skip(1).ToArray(); + var query = (IQueryable)this.Select(predicate, values); + object firstItem = query.FirstOrDefault(); + if (firstItem == null) + { + result = new DynamicNull(); + } + else + { + var types = from i in query + group i by i.GetType() into g + where g.Key != typeof(DynamicNull) + orderby g.Count() descending + select new { g, Instances = g.Count() }; + var dominantType = types.First().g.Key; + //remove items that are not the dominant type + //e.g. string,string,string,string,false[DynamicNull],string + var itemsOfDominantTypeOnly = query.ToList(); + itemsOfDominantTypeOnly.RemoveAll(item => !item.GetType().IsAssignableFrom(dominantType)); + if (dominantType == typeof(string)) + { + throw new ArgumentException("Can only use aggregate methods on properties which are numeric"); + } + else if (dominantType == typeof(int)) + { + List data = (List)itemsOfDominantTypeOnly.Cast().ToList(); + return Aggregate(data, name); + } + else if (dominantType == typeof(decimal)) + { + List data = (List)itemsOfDominantTypeOnly.Cast().ToList(); + return Aggregate(data, name); + } + else if (dominantType == typeof(bool)) + { + throw new ArgumentException("Can only use aggregate methods on properties which are numeric or datetime"); + } + else if (dominantType == typeof(DateTime)) + { + if (name != "Min" || name != "Max") + { + throw new ArgumentException("Can only use aggregate min or max methods on properties which are datetime"); + } + List data = (List)itemsOfDominantTypeOnly.Cast().ToList(); + return Aggregate(data, name); + } + else + { + result = query.ToList(); + } + } + return result; + } + private object Pluck(object[] args) + { + object result; + string predicate = args.First().ToString(); + var values = args.Skip(1).ToArray(); + var query = (IQueryable)this.Select(predicate, values); + object firstItem = query.FirstOrDefault(); + if (firstItem == null) + { + result = new List(); + } + else + { + var types = from i in query + group i by i.GetType() into g + where g.Key != typeof(DynamicNull) + orderby g.Count() descending + select new { g, Instances = g.Count() }; + var dominantType = types.First().g.Key; + //remove items that are not the dominant type + //e.g. string,string,string,string,false[DynamicNull],string + var itemsOfDominantTypeOnly = query.ToList(); + itemsOfDominantTypeOnly.RemoveAll(item => !item.GetType().IsAssignableFrom(dominantType)); + if (dominantType == typeof(string)) + { + result = (List)itemsOfDominantTypeOnly.Cast().ToList(); + } + else if (dominantType == typeof(int)) + { + result = (List)itemsOfDominantTypeOnly.Cast().ToList(); + } + else if (dominantType == typeof(decimal)) + { + result = (List)itemsOfDominantTypeOnly.Cast().ToList(); + } + else if (dominantType == typeof(bool)) + { + result = (List)itemsOfDominantTypeOnly.Cast().ToList(); + } + else if (dominantType == typeof(DateTime)) + { + result = (List)itemsOfDominantTypeOnly.Cast().ToList(); + } + else + { + result = query.ToList(); + } + } + return result; + } + + private object ExecuteExtensionMethod(object[] args, string name, bool argsContainsThis) + { + object result = null; + + MethodInfo methodToExecute = ExtensionMethodFinder.FindExtensionMethod(typeof(IEnumerable), args, name, false); + if (methodToExecute == null) + { + methodToExecute = ExtensionMethodFinder.FindExtensionMethod(typeof(DynamicNodeList), args, name, false); + } + if (methodToExecute != null) + { + if (methodToExecute.GetParameters().First().ParameterType == typeof(DynamicNodeList)) + { + var genericArgs = (new[] { this }).Concat(args); + result = methodToExecute.Invoke(null, genericArgs.ToArray()); + } + else + { + var genericArgs = (new[] { Items }).Concat(args); + result = methodToExecute.Invoke(null, genericArgs.ToArray()); + } + } + else + { + throw new MissingMethodException(); + } + if (result != null) + { + if (result is IEnumerable) + { + result = new DynamicNodeList((IEnumerable)result); + } + if (result is IEnumerable) + { + result = new DynamicNodeList((IEnumerable)result); + } + if (result is INode) + { + result = new DynamicNode((IDocument)result); + } + } + return result; + } + + //IEnumerator IEnumerable.GetEnumerator() + //{ + // return Items.GetEnumerator(); + //} + + //public IEnumerator GetEnumerator() + //{ + // return Items.GetEnumerator(); + //} + + public IQueryable Where(string predicate, params object[] values) + { + return ((IQueryable)Items.AsQueryable()).Where(predicate, values); + } + public IQueryable OrderBy(string key) + { + return ((IQueryable)Items.AsQueryable()).OrderBy(key); + } + public DynamicGrouping GroupBy(string key) + { + DynamicGrouping group = new DynamicGrouping(this, key); + return group; + } + public DynamicGrouping GroupedInto(int groupCount) + { + int groupSize = (int)Math.Ceiling(((decimal)Items.Count() / groupCount)); + return new DynamicGrouping( + this + .Items + .Select((node, index) => new KeyValuePair(index, node)) + .GroupBy(kv => (object)(kv.Key / groupSize)) + .Select(item => new Grouping() + { + Key = item.Key, + Elements = item.Select(inner => inner.Value) + })); + } + public DynamicGrouping InGroupsOf(int groupSize) + { + return new DynamicGrouping( + this + .Items + .Select((node, index) => new KeyValuePair(index, node)) + .GroupBy(kv => (object)(kv.Key / groupSize)) + .Select(item => new Grouping() + { + Key = item.Key, + Elements = item.Select(inner => inner.Value) + })); + + } + + public IQueryable Select(string predicate, params object[] values) + { + return DynamicQueryable.Select(Items.AsQueryable(), predicate, values); + } + + public void Add(DynamicNode node) + { + node.OwnerList = this; + this.Items.Add(node); + } + public void Remove(DynamicNode node) + { + if (this.Items.Contains(node)) + { + node.OwnerList = null; + this.Items.Remove(node); + } + } + public bool IsNull() + { + return false; + } + public bool HasValue() + { + return true; + } + + public IEnumerator GetEnumerator() + { + return Items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicNodeListOrdering.cs b/src/Umbraco.Core/Dynamics/DynamicNodeListOrdering.cs new file mode 100644 index 0000000000..bcbd3458bb --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicNodeListOrdering.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Umbraco.Core.Dynamics +{ + internal static class DynamicNodeListOrdering + { + + private static TOut Reduce(Func func, DynamicNode node) + { + var value = func(node); + while (value is Func) + { + value = (value as Func)(node); + } + //when you're sorting a list of properties + //and one of those properties doesn't exist, it will come back as DynamicNull + //when that gets handled by the expression tree parser, it's converted to be false + //which lets .Where work properly with e.g. umbracoNaviHide which may not be defined + //on all properties + //this checks to see if the type of value is bool, and if it is, and it's false + //then return false + //in a case where Reduce is being called, this will return false, which is the + //same as the actual real value + //but when Reduce [a dynamicnode dynamic property call] is called + //it will result in null, (because default(object) is null) + //which will cause the item with the missing property to sort up to the top of the list + if (value.GetType() == typeof(bool) && !((value as bool?) ?? false)) + { + return default(TOut); + } + if (value.GetType() == typeof(string) && string.IsNullOrEmpty((value as string))) + { + return default(TOut); + } + return (TOut)value; + } + public static IOrderedQueryable OrderBy(object source, object key) + { + IEnumerable typedSource = source as IEnumerable; + LambdaExpression lambda = key as LambdaExpression; + //if the lambda we have returns an actual property, not a dynamic one, + //then the TOut of the func will be the actual type, not object + //Func func = (Func)lambda.Compile(); + var func = lambda.Compile(); + var TOut = func.GetType().GetGenericArguments()[1]; + IOrderedQueryable result = null; + if (TOut == typeof(Func)) + { + result = (IOrderedQueryable)typedSource + .OrderBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(object)) + { + result = (IOrderedQueryable)typedSource + .OrderBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(bool)) + { + result = (IOrderedQueryable)typedSource + .OrderBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(decimal)) + { + result = (IOrderedQueryable)typedSource + .OrderBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(int)) + { + result = (IOrderedQueryable)typedSource + .OrderBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(string)) + { + result = (IOrderedQueryable)typedSource + .OrderBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(DateTime)) + { + result = (IOrderedQueryable)typedSource + .OrderBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + return result; + } + public static IOrderedQueryable ThenBy(object source, object key) + { + IOrderedQueryable typedSource = source as IOrderedQueryable; + LambdaExpression lambda = key as LambdaExpression; + var func = lambda.Compile(); + var TOut = func.GetType().GetGenericArguments()[1]; + IOrderedQueryable result = null; + if (TOut == typeof(Func)) + { + result = (IOrderedQueryable)typedSource + .ThenBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(object)) + { + result = (IOrderedQueryable)typedSource + .ThenBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(bool)) + { + result = (IOrderedQueryable)typedSource + .ThenBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(decimal)) + { + result = (IOrderedQueryable)typedSource + .ThenBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(int)) + { + result = (IOrderedQueryable)typedSource + .ThenBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(string)) + { + result = (IOrderedQueryable)typedSource + .ThenBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(DateTime)) + { + result = (IOrderedQueryable)typedSource + .ThenBy(x => Reduce(func as Func, x)) + .AsQueryable(); + } + return result; + } + public static IOrderedQueryable OrderByDescending(object source, object key) + { + IEnumerable typedSource = source as IEnumerable; + LambdaExpression lambda = key as LambdaExpression; + var func = lambda.Compile(); + var TOut = func.GetType().GetGenericArguments()[1]; + IOrderedQueryable result = null; + if (TOut == typeof(Func)) + { + result = (IOrderedQueryable)typedSource + .OrderByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(object)) + { + result = (IOrderedQueryable)typedSource + .OrderByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(bool)) + { + result = (IOrderedQueryable)typedSource + .OrderByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(decimal)) + { + result = (IOrderedQueryable)typedSource + .OrderByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(int)) + { + result = (IOrderedQueryable)typedSource + .OrderByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(string)) + { + result = (IOrderedQueryable)typedSource + .OrderByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(DateTime)) + { + result = (IOrderedQueryable)typedSource + .OrderByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + return result; + } + public static IOrderedQueryable ThenByDescending(object source, object key) + { + IOrderedQueryable typedSource = source as IOrderedQueryable; + LambdaExpression lambda = key as LambdaExpression; + var func = lambda.Compile(); + var TOut = func.GetType().GetGenericArguments()[1]; + IOrderedQueryable result = null; + if (TOut == typeof(Func)) + { + result = (IOrderedQueryable)typedSource + .ThenByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(object)) + { + result = (IOrderedQueryable)typedSource + .ThenByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(bool)) + { + result = (IOrderedQueryable)typedSource + .ThenByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(decimal)) + { + result = (IOrderedQueryable)typedSource + .ThenByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(int)) + { + result = (IOrderedQueryable)typedSource + .ThenByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(string)) + { + result = (IOrderedQueryable)typedSource + .ThenByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + if (TOut == typeof(DateTime)) + { + result = (IOrderedQueryable)typedSource + .ThenByDescending(x => Reduce(func as Func, x)) + .AsQueryable(); + } + return result; + } + + } +} + diff --git a/src/Umbraco.Core/Dynamics/DynamicNodeWalker.cs b/src/Umbraco.Core/Dynamics/DynamicNodeWalker.cs new file mode 100644 index 0000000000..1f5feda4dd --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicNodeWalker.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Dynamics +{ + internal static class DynamicNodeWalker + { + public static DynamicNode Up(this DynamicNode context) + { + return context.Up(0); + } + public static DynamicNode Up(this DynamicNode context, int number) + { + if (number == 0) + { + return context.Parent; + } + else + { + while ((context = context.Parent) != null && --number >= 0) ; + return context; + } + } + public static DynamicNode Up(this DynamicNode context, string nodeTypeAlias) + { + if (string.IsNullOrEmpty(nodeTypeAlias)) + { + return context.Parent; + } + else + { + while ((context = context.Parent) != null && context.NodeTypeAlias != nodeTypeAlias) ; + return context; + } + } + + public static DynamicNode Down(this DynamicNode context) + { + return context.Down(0); + } + public static DynamicNode Down(this DynamicNode context, int number) + { + var children = new DynamicNodeList(context.Children); + if (number == 0) + { + return children.Items.First(); + } + else + { + DynamicNode working = context; + while (number-- >= 0) + { + working = children.Items.First(); + children = new DynamicNodeList(working.Children); + } + return working; + } + } + public static DynamicNode Down(this DynamicNode context, string nodeTypeAlias) + { + + if (string.IsNullOrEmpty(nodeTypeAlias)) + { + var children = new DynamicNodeList(context.Children); + return children.Items.First(); + } + else + { + return context.Descendants(nodeTypeAlias).Items.FirstOrDefault(); + } + } + public static DynamicNode Next(this DynamicNode context) + { + return context.Next(0); + } + public static DynamicNode Next(this DynamicNode context, int number) + { + if (context.OwnerList == null && context.Parent != null) + { + //var list = context.Parent.Children.Select(n => new DynamicNode(n)); + var list = context.Parent.Children; + context.OwnerList = new DynamicNodeList(list); + } + if (context.OwnerList != null) + { + var container = context.OwnerList.Items.ToList(); + var currentIndex = container.FindIndex(n => n.Id == context.Id); + if (currentIndex != -1) + { + return container.ElementAtOrDefault(currentIndex + (number + 1)); + } + else + { + throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", context.Id)); + } + } + else + { + throw new ArgumentNullException(string.Format("Node {0} has been orphaned and doesn't belong to a DynamicNodeList", context.Id)); + } + } + public static DynamicNode Sibling(this DynamicNode context, int number) + { + if (context.OwnerList == null && context.Parent != null) + { + //var list = context.Parent.Children.Select(n => new DynamicNode(n)); + var list = context.Parent.Children; + context.OwnerList = new DynamicNodeList(list); + } + if (context.OwnerList != null) + { + var container = context.OwnerList.Items.ToList(); + var currentIndex = container.FindIndex(n => n.Id == context.Id); + if (currentIndex != -1) + { + return container.ElementAtOrDefault(currentIndex + number); + } + else + { + throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", context.Id)); + } + } + else + { + throw new ArgumentNullException(string.Format("Node {0} has been orphaned and doesn't belong to a DynamicNodeList", context.Id)); + } + } + public static DynamicNode Sibling(this DynamicNode context, string nodeTypeAlias) + { + if (context.OwnerList == null && context.Parent != null) + { + //var list = context.Parent.Children.Select(n => new DynamicNode(n)); + var list = context.Parent.Children; + context.OwnerList = new DynamicNodeList(list); + } + if (context.OwnerList != null) + { + var container = context.OwnerList.Items.ToList(); + var currentIndex = container.FindIndex(n => n.Id == context.Id); + if (currentIndex != -1) + { + var workingIndex = currentIndex + 1; + while (workingIndex != currentIndex) + { + var working = container.ElementAtOrDefault(workingIndex); + if (working != null && working.NodeTypeAlias == nodeTypeAlias) + { + return working; + } + workingIndex++; + if (workingIndex > container.Count) + { + workingIndex = 0; + } + } + return null; + } + else + { + throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", context.Id)); + } + } + else + { + throw new ArgumentNullException(string.Format("Node {0} has been orphaned and doesn't belong to a DynamicNodeList", context.Id)); + } + } + public static DynamicNode Next(this DynamicNode context, string nodeTypeAlias) + { + if (context.OwnerList == null && context.Parent != null) + { + //var list = context.Parent.Children.Select(n => new DynamicNode(n)); + var list = context.Parent.Children; + context.OwnerList = new DynamicNodeList(list); + } + if (context.OwnerList != null) + { + var container = context.OwnerList.Items.ToList(); + var currentIndex = container.FindIndex(n => n.Id == context.Id); + if (currentIndex != -1) + { + var newIndex = container.FindIndex(currentIndex, n => n.NodeTypeAlias == nodeTypeAlias); + if (newIndex != -1) + { + return container.ElementAt(newIndex); + } + return null; + } + else + { + throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", context.Id)); + } + } + else + { + throw new ArgumentNullException(string.Format("Node {0} has been orphaned and doesn't belong to a DynamicNodeList", context.Id)); + } + } + public static DynamicNode Previous(this DynamicNode context) + { + return context.Previous(0); + } + public static DynamicNode Previous(this DynamicNode context, int number) + { + if (context.OwnerList == null && context.Parent != null) + { + //var list = context.Parent.Children.Select(n => new DynamicNode(n)); + var list = context.Parent.Children; + context.OwnerList = new DynamicNodeList(list); + } + if (context.OwnerList != null) + { + List container = context.OwnerList.Items.ToList(); + int currentIndex = container.FindIndex(n => n.Id == context.Id); + if (currentIndex != -1) + { + return container.ElementAtOrDefault(currentIndex + (number - 1)); + } + else + { + throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", context.Id)); + } + } + else + { + throw new ArgumentNullException(string.Format("Node {0} has been orphaned and doesn't belong to a DynamicNodeList", context.Id)); + } + } + public static DynamicNode Previous(this DynamicNode context, string nodeTypeAlias) + { + if (context.OwnerList == null && context.Parent != null) + { + //var list = context.Parent.Children.Select(n => new DynamicNode(n)); + var list = context.Parent.Children; + context.OwnerList = new DynamicNodeList(list); + } + if (context.OwnerList != null) + { + List container = context.OwnerList.Items.ToList(); + int currentIndex = container.FindIndex(n => n.Id == context.Id); + if (currentIndex != -1) + { + var previousNodes = container.Take(currentIndex).ToList(); + int newIndex = previousNodes.FindIndex(n => n.NodeTypeAlias == nodeTypeAlias); + if (newIndex != -1) + { + return container.ElementAt(newIndex); + } + return null; + } + else + { + throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", context.Id)); + } + } + else + { + throw new ArgumentNullException(string.Format("Node {0} has been orphaned and doesn't belong to a DynamicNodeList", context.Id)); + } + } + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicNull.cs b/src/Umbraco.Core/Dynamics/DynamicNull.cs new file mode 100644 index 0000000000..5139df17b6 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicNull.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.Dynamic; +using System.Web; + +namespace Umbraco.Core.Dynamics +{ + //This type is used as a return type when TryGetMember fails on a DynamicNode + //.Where explicitly checks for this type, to indicate that nothing was returned + //Because it's IEnumerable, if the user is actually trying @Model.TextPages or similar + //it will still return an enumerable object (assuming the call actually failed because there were no children of that type) + //but in .Where, if they use a property that doesn't exist, the lambda will bypass this and return false + internal class DynamicNull : DynamicObject, IEnumerable, IHtmlString + { + public IEnumerator GetEnumerator() + { + return (new List()).GetEnumerator(); + } + public DynamicNull Where(string predicate, params object[] values) + { + return this; + } + public DynamicNull OrderBy(string orderBy) + { + return this; + } + public int Count() + { + return 0; + } + public override string ToString() + { + return string.Empty; + } + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = this; + return true; + } + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + result = this; + return true; + } + public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) + { + result = this; + return true; + } + public bool IsNull() + { + return true; + } + public bool HasValue() + { + return false; + } + public string Name + { + get + { + return string.Empty; + } + } + public int Id + { + get + { + return 0; + } + } + + public static implicit operator bool(DynamicNull n) + { + return false; + } + public static implicit operator DateTime(DynamicNull n) + { + return DateTime.MinValue; + } + public static implicit operator int(DynamicNull n) + { + return 0; + } + public static implicit operator string(DynamicNull n) + { + return string.Empty; + } + + public string ToHtmlString() + { + return string.Empty; + } + + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicOrdering.cs b/src/Umbraco.Core/Dynamics/DynamicOrdering.cs new file mode 100644 index 0000000000..b4f0bf0d3a --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicOrdering.cs @@ -0,0 +1,10 @@ +using System.Linq.Expressions; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicOrdering + { + public Expression Selector; + public bool Ascending; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicProperty.cs b/src/Umbraco.Core/Dynamics/DynamicProperty.cs new file mode 100644 index 0000000000..acb29bf387 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicProperty.cs @@ -0,0 +1,28 @@ +using System; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicProperty + { + readonly string _name; + readonly Type _type; + + public DynamicProperty(string name, Type type) + { + if (name == null) throw new ArgumentNullException("name"); + if (type == null) throw new ArgumentNullException("type"); + this._name = name; + this._type = type; + } + + public string Name + { + get { return _name; } + } + + public Type Type + { + get { return _type; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/DynamicQueryable.cs b/src/Umbraco.Core/Dynamics/DynamicQueryable.cs new file mode 100644 index 0000000000..76b0dc07bc --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicQueryable.cs @@ -0,0 +1,315 @@ +//Copyright (C) Microsoft Corporation. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Diagnostics; + +namespace Umbraco.Core.Dynamics +{ + internal static class DynamicQueryable + { + public static IQueryable Where(this IQueryable source, string predicate, params object[] values) + { + return (IQueryable)Where((IQueryable)source, predicate, values); + } + + public static IQueryable Where(this IQueryable source, string predicate, params object[] values) + { + if (source == null) throw new ArgumentNullException("source"); + if (predicate == null) throw new ArgumentNullException("predicate"); + LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, true, values); + if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(DynamicNode)) + { + //source list is DynamicNode and the lambda returns a Func + IQueryable typedSource = source as IQueryable; + var compiledFunc = lambda.Compile(); + Func func = null; + Func boolFunc = null; + if (compiledFunc is Func) + { + func = (Func)compiledFunc; + } + if (compiledFunc is Func) + { + boolFunc = (Func)compiledFunc; + } + return typedSource.Where(delegate(DynamicNode node) + { + object value = -1; + //value = func(node); + //I can't figure out why this is double func<>'d + try + { + if (func != null) + { + var firstFuncResult = func(node); + if (firstFuncResult is Func) + { + value = (firstFuncResult as Func)(node); + } + if (firstFuncResult is Func) + { + value = (firstFuncResult as Func)(node); + } + if (firstFuncResult is bool) + { + return (bool)firstFuncResult; + } + if (value is bool) + { + return (bool)value; + } + } + if (boolFunc != null) + { + return boolFunc(node); + } + return false; + } + catch (Exception ex) + { + Trace.WriteLine(ex.Message); + return false; + } + }).AsQueryable(); + } + else + { + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Where", + new Type[] { source.ElementType }, + source.Expression, Expression.Quote(lambda))); + } + } + + public static IQueryable Select(this IQueryable source, string selector, params object[] values) + { + if (source == null) throw new ArgumentNullException("source"); + if (selector == null) throw new ArgumentNullException("selector"); + LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(object), selector, false, values); + if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(DynamicNode)) + { + //source list is DynamicNode and the lambda returns a Func + IQueryable typedSource = source as IQueryable; + var compiledFunc = lambda.Compile(); + Func func = null; + if (compiledFunc is Func) + { + func = (Func)compiledFunc; + } + return typedSource.Select(delegate(DynamicNode node) + { + object value = null; + value = func(node); + if (value is Func) + { + var innerValue = (value as Func)(node); + return innerValue; + } + return value; + }).AsQueryable(); + } + else + { + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Select", + new Type[] { source.ElementType, lambda.Body.Type }, + source.Expression, Expression.Quote(lambda))); + } + } + + public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) + { + return (IQueryable)OrderBy((IQueryable)source, ordering, values); + } + + public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) + { + if (source == null) throw new ArgumentNullException("source"); + if (ordering == null) throw new ArgumentNullException("ordering"); + + IQueryable typedSource = source as IQueryable; + if (!ordering.Contains(",")) + { + bool descending = false; + if (ordering.IndexOf(" descending", StringComparison.CurrentCultureIgnoreCase) >= 0) + { + ordering = ordering.Replace(" descending", ""); + descending = true; + } + if (ordering.IndexOf(" desc", StringComparison.CurrentCultureIgnoreCase) >= 0) + { + ordering = ordering.Replace(" desc", ""); + descending = true; + } + + LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(object), ordering, false, values); + if (lambda.Parameters.Count > 0 && lambda.Parameters[0].Type == typeof(DynamicNode)) + { + //source list is DynamicNode and the lambda returns a Func + Func func = (Func)lambda.Compile(); + //get the values out + var query = typedSource.ToList().ConvertAll(item => new { node = item, key = EvaluateDynamicNodeFunc(item, func) }); + if (query.Count == 0) + { + return source; + } + var types = from i in query + group i by i.key.GetType() into g + where g.Key != typeof(DynamicNull) + orderby g.Count() descending + select new { g, Instances = g.Count() }; + var dominantType = types.First().g.Key; + + // NH - add culture dependencies + StringComparer comp = StringComparer.Create(CultureInfo.CurrentCulture, true); + + if (!descending) + { + // if the dominant type is a string we'll ensure that strings are sorted based on culture settings on node + if (dominantType.FullName == "System.String") + return query.OrderBy(item => item.key.ToString(), comp).Select(item => item.node).AsQueryable(); + else + return query.OrderBy(item => GetObjectAsTypeOrDefault(item.key, dominantType)).Select(item => item.node).AsQueryable(); + } + else + { + if (dominantType.FullName == "System.String") + return query.OrderByDescending(item => item.key.ToString(), comp).Select(item => item.node).AsQueryable(); + else + return query.OrderByDescending(item => GetObjectAsTypeOrDefault(item.key, dominantType)).Select(item => item.node).AsQueryable(); + } + } + } + + bool isDynamicNodeList = false; + if (typedSource != null) + { + isDynamicNodeList = true; + } + + ParameterExpression[] parameters = new ParameterExpression[] { + Expression.Parameter(source.ElementType, "") }; + ExpressionParser parser = new ExpressionParser(parameters, ordering, values); + IEnumerable orderings = parser.ParseOrdering(); + Expression queryExpr = source.Expression; + string methodAsc = "OrderBy"; + string methodDesc = "OrderByDescending"; + foreach (DynamicOrdering o in orderings) + { + if (!isDynamicNodeList) + { + queryExpr = Expression.Call( + typeof(Queryable), o.Ascending ? methodAsc : methodDesc, + new Type[] { source.ElementType, o.Selector.Type }, + queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters))); + } + else + { + //reroute each stacked Expression.Call into our own methods that know how to deal + //with DynamicNode + queryExpr = Expression.Call( + typeof(DynamicNodeListOrdering), + o.Ascending ? methodAsc : methodDesc, + null, + queryExpr, + Expression.Quote(Expression.Lambda(o.Selector, parameters)) + ); + } + methodAsc = "ThenBy"; + methodDesc = "ThenByDescending"; + } + if (isDynamicNodeList) + { + return typedSource.Provider.CreateQuery(queryExpr); + } + return source.Provider.CreateQuery(queryExpr); + + } + private static object GetObjectAsTypeOrDefault(object value, Type type) + { + if (type.IsAssignableFrom(value.GetType())) + { + return (object)Convert.ChangeType(value, type); + } + else + { + if (type.IsValueType) + { + return Activator.CreateInstance(type); + } + return null; + } + } + private static object EvaluateDynamicNodeFunc(DynamicNode node, Func func) + { + object value = -1; + var firstFuncResult = func(node); + if (firstFuncResult is Func) + { + value = (firstFuncResult as Func)(node); + } + if (firstFuncResult.GetType().IsValueType || firstFuncResult is string) + { + value = firstFuncResult; + } + return value; + } + public static IQueryable Take(this IQueryable source, int count) + { + if (source == null) throw new ArgumentNullException("source"); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Take", + new Type[] { source.ElementType }, + source.Expression, Expression.Constant(count))); + } + + public static IQueryable Skip(this IQueryable source, int count) + { + if (source == null) throw new ArgumentNullException("source"); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Skip", + new Type[] { source.ElementType }, + source.Expression, Expression.Constant(count))); + } + + public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values) + { + if (source == null) throw new ArgumentNullException("source"); + if (keySelector == null) throw new ArgumentNullException("keySelector"); + if (elementSelector == null) throw new ArgumentNullException("elementSelector"); + LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, true, values); + LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, true, values); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "GroupBy", + new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type }, + source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda))); + } + + public static bool Any(this IQueryable source) + { + if (source == null) throw new ArgumentNullException("source"); + return (bool)source.Provider.Execute( + Expression.Call( + typeof(Queryable), "Any", + new Type[] { source.ElementType }, source.Expression)); + } + + public static int Count(this IQueryable source) + { + if (source == null) throw new ArgumentNullException("source"); + return (int)source.Provider.Execute( + Expression.Call( + typeof(Queryable), "Count", + new Type[] { source.ElementType }, source.Expression)); + } + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicQueryableGetMemberBinder.cs b/src/Umbraco.Core/Dynamics/DynamicQueryableGetMemberBinder.cs new file mode 100644 index 0000000000..92d7cfcdc9 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicQueryableGetMemberBinder.cs @@ -0,0 +1,15 @@ +using System; +using System.Dynamic; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicQueryableGetMemberBinder : GetMemberBinder + { + public DynamicQueryableGetMemberBinder(string name, bool ignoreCase) : base(name, ignoreCase) { } + + public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs new file mode 100644 index 0000000000..bd359e4319 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs @@ -0,0 +1,660 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Dynamic; +using System.Xml.Linq; +using System.Xml.XPath; +using System.Collections; +using System.IO; +using System.Web; + +namespace Umbraco.Core.Dynamics +{ + internal class DynamicXml : DynamicObject, IEnumerable + { + public XElement BaseElement { get; set; } + + public DynamicXml(XElement baseElement) + { + this.BaseElement = baseElement; + } + public DynamicXml(string xml) + { + var baseElement = XElement.Parse(xml); + this.BaseElement = baseElement; + } + public DynamicXml(XPathNodeIterator xpni) + { + if (xpni != null) + { + if (xpni.Current != null) + { + var xml = xpni.Current.OuterXml; + var baseElement = XElement.Parse(xml); + this.BaseElement = baseElement; + } + } + } + public string InnerText + { + get + { + return BaseElement.Value; + } + } + public string ToXml() + { + return BaseElement.ToString(SaveOptions.DisableFormatting); + } + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + { + int index = 0; + if (indexes.Length > 0) + { + index = (int)indexes[0]; + result = new DynamicXml(this.BaseElement.Elements().ToList()[index]); + return true; + } + return base.TryGetIndex(binder, indexes, out result); + } + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + if (args.Length == 0 && binder.Name == "ToXml") + { + result = this.BaseElement.ToString(); + return true; + } + if (args.Length == 1 && binder.Name == "XPath") + { + var elements = this.BaseElement.XPathSelectElements(args[0].ToString()); + HandleIEnumerableXElement(elements, out result); + return true; //anyway + } + return base.TryInvokeMember(binder, args, out result); + } + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (BaseElement == null || binder == null) + { + result = null; + return false; + } + //Go ahead and try to fetch all of the elements matching the member name, and wrap them + var elements = BaseElement.Elements(binder.Name); + if (elements.Count() == 0 && BaseElement.Name == "root" && BaseElement.Elements().Count() == 1) + { + //no elements matched, lets try first child + elements = BaseElement.Elements().ElementAt(0).Elements(binder.Name); + } + if (HandleIEnumerableXElement(elements, out result)) + { + return true; + } + else + { + + //Ok, so no elements matched, so lets try attributes + IEnumerable attributes = BaseElement.Attributes(binder.Name).Select(attr => attr.Value); + int count = attributes.Count(); + + if (count > 0) + { + if (count > 1) + result = attributes; //more than one attribute matched, lets return the collection + else + result = attributes.FirstOrDefault(); //only one attribute matched, lets just return it + + return true; // return true because we matched + } + else + { + //no attributes matched, lets try first child + if (BaseElement.Name == "root" && BaseElement.Elements().Count() == 1) + { + attributes = BaseElement.Elements().ElementAt(0).Attributes(binder.Name).Select(attr => attr.Value); + count = attributes.Count(); + if (count > 1) + result = attributes; //more than one attribute matched, lets return the collection + else + result = attributes.FirstOrDefault(); //only one attribute matched, lets just return it + + return true; // return true because we matched + } + } + } + return base.TryGetMember(binder, out result); + } + + private bool HandleIEnumerableXElement(IEnumerable elements, out object result) + { + //Get the count now, so we don't have to call it twice + int count = elements.Count(); + if (count > 0) + { + var firstElement = elements.FirstOrDefault(); + //we have a single element, does it have any children? + if (firstElement != null && firstElement.Elements().Count() == 0 && !firstElement.HasAttributes) + { + //no, return the text + result = firstElement.Value; + return true; + } + else + { + //We have more than one matching element, so let's return the collection + //elements is IEnumerable + //but we want to be able to re-enter this code + XElement root = new XElement(XName.Get("root")); + root.Add(elements); + result = new DynamicXml(root); + + //From here, you'll either end up back here (because you have ) + //or you use [] indexing and you end up with a single element + return true; + } + } + result = null; + return false; + } + public DynamicXml XPath(string expression) + { + var matched = this.BaseElement.XPathSelectElements(expression); + DynamicXml root = new DynamicXml(""); + foreach (var element in matched) + { + root.BaseElement.Add(element); + } + return root; + } + public IHtmlString ToHtml() + { + return new HtmlString(this.ToXml()); + } + public DynamicXml Find(string expression) + { + return new DynamicXml(this.BaseElement.XPathSelectElements(expression).FirstOrDefault()); + } + + public DynamicXml Find(string attributeName, object value) + { + string expression = string.Format("//*[{0}='{1}']", attributeName, value); + return new DynamicXml(this.BaseElement.XPathSelectElements(expression).FirstOrDefault()); + } + + public IEnumerator GetEnumerator() + { + return this.BaseElement.Elements().Select(e => new DynamicXml(e)).GetEnumerator(); + } + public int Count() + { + return this.BaseElement.Elements().Count(); + } + + public bool IsNull() + { + return false; + } + public bool HasValue() + { + return true; + } + + public bool IsFirst() + { + return IsHelper(n => n.Index() == 0); + } + public HtmlString IsFirst(string valueIfTrue) + { + return IsHelper(n => n.Index() == 0, valueIfTrue); + } + public HtmlString IsFirst(string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Index() == 0, valueIfTrue, valueIfFalse); + } + public bool IsNotFirst() + { + return !IsHelper(n => n.Index() == 0); + } + public HtmlString IsNotFirst(string valueIfTrue) + { + return IsHelper(n => n.Index() != 0, valueIfTrue); + } + public HtmlString IsNotFirst(string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Index() != 0, valueIfTrue, valueIfFalse); + } + public bool IsPosition(int index) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return false; + } + return IsHelper(n => n.Index() == index); + } + public HtmlString IsPosition(int index, string valueIfTrue) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(string.Empty); + } + return IsHelper(n => n.Index() == index, valueIfTrue); + } + public HtmlString IsPosition(int index, string valueIfTrue, string valueIfFalse) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(valueIfFalse); + } + return IsHelper(n => n.Index() == index, valueIfTrue, valueIfFalse); + } + public bool IsModZero(int modulus) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return false; + } + return IsHelper(n => n.Index() % modulus == 0); + } + public HtmlString IsModZero(int modulus, string valueIfTrue) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(string.Empty); + } + return IsHelper(n => n.Index() % modulus == 0, valueIfTrue); + } + public HtmlString IsModZero(int modulus, string valueIfTrue, string valueIfFalse) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(valueIfFalse); + } + return IsHelper(n => n.Index() % modulus == 0, valueIfTrue, valueIfFalse); + } + + public bool IsNotModZero(int modulus) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return false; + } + return IsHelper(n => n.Index() % modulus != 0); + } + public HtmlString IsNotModZero(int modulus, string valueIfTrue) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(string.Empty); + } + return IsHelper(n => n.Index() % modulus != 0, valueIfTrue); + } + public HtmlString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(valueIfFalse); + } + return IsHelper(n => n.Index() % modulus != 0, valueIfTrue, valueIfFalse); + } + public bool IsNotPosition(int index) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return false; + } + return !IsHelper(n => n.Index() == index); + } + public HtmlString IsNotPosition(int index, string valueIfTrue) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(string.Empty); + } + return IsHelper(n => n.Index() != index, valueIfTrue); + } + public HtmlString IsNotPosition(int index, string valueIfTrue, string valueIfFalse) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(valueIfFalse); + } + return IsHelper(n => n.Index() != index, valueIfTrue, valueIfFalse); + } + public bool IsLast() + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return false; + } + int count = this.BaseElement.Parent.Elements().Count(); + return IsHelper(n => n.Index() == count - 1); + } + public HtmlString IsLast(string valueIfTrue) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(string.Empty); + } + int count = this.BaseElement.Parent.Elements().Count(); + return IsHelper(n => n.Index() == count - 1, valueIfTrue); + } + public HtmlString IsLast(string valueIfTrue, string valueIfFalse) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(valueIfFalse); + } + int count = this.BaseElement.Parent.Elements().Count(); + return IsHelper(n => n.Index() == count - 1, valueIfTrue, valueIfFalse); + } + public bool IsNotLast() + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return false; + } + int count = this.BaseElement.Parent.Elements().Count(); + return !IsHelper(n => n.Index() == count - 1); + } + public HtmlString IsNotLast(string valueIfTrue) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(string.Empty); + } + int count = this.BaseElement.Parent.Elements().Count(); + return IsHelper(n => n.Index() != count - 1, valueIfTrue); + } + public HtmlString IsNotLast(string valueIfTrue, string valueIfFalse) + { + if (this.BaseElement == null || this.BaseElement.Parent == null) + { + return new HtmlString(valueIfFalse); + } + int count = this.BaseElement.Parent.Elements().Count(); + return IsHelper(n => n.Index() != count - 1, valueIfTrue, valueIfFalse); + } + public bool IsEven() + { + return IsHelper(n => n.Index() % 2 == 0); + } + public HtmlString IsEven(string valueIfTrue) + { + return IsHelper(n => n.Index() % 2 == 0, valueIfTrue); + } + public HtmlString IsEven(string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Index() % 2 == 0, valueIfTrue, valueIfFalse); + } + public bool IsOdd() + { + return IsHelper(n => n.Index() % 2 == 1); + } + public HtmlString IsOdd(string valueIfTrue) + { + return IsHelper(n => n.Index() % 2 == 1, valueIfTrue); + } + public HtmlString IsOdd(string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.Index() % 2 == 1, valueIfTrue, valueIfFalse); + } + public bool IsEqual(DynamicXml other) + { + return IsHelper(n => n.BaseElement == other.BaseElement); + } + public HtmlString IsEqual(DynamicXml other, string valueIfTrue) + { + return IsHelper(n => n.BaseElement == other.BaseElement, valueIfTrue); + } + public HtmlString IsEqual(DynamicXml other, string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.BaseElement == other.BaseElement, valueIfTrue, valueIfFalse); + } + public bool IsNotEqual(DynamicXml other) + { + return IsHelper(n => n.BaseElement != other.BaseElement); + } + public HtmlString IsNotEqual(DynamicXml other, string valueIfTrue) + { + return IsHelper(n => n.BaseElement != other.BaseElement, valueIfTrue); + } + public HtmlString IsNotEqual(DynamicXml other, string valueIfTrue, string valueIfFalse) + { + return IsHelper(n => n.BaseElement != other.BaseElement, valueIfTrue, valueIfFalse); + } + public bool IsDescendant(DynamicXml other) + { + var ancestors = this.Ancestors(); + return IsHelper(n => ancestors.Find(ancestor => ancestor.BaseElement == other.BaseElement) != null); + } + public HtmlString IsDescendant(DynamicXml other, string valueIfTrue) + { + var ancestors = this.Ancestors(); + return IsHelper(n => ancestors.Find(ancestor => ancestor.BaseElement == other.BaseElement) != null, valueIfTrue); + } + public HtmlString IsDescendant(DynamicXml other, string valueIfTrue, string valueIfFalse) + { + var ancestors = this.Ancestors(); + return IsHelper(n => ancestors.Find(ancestor => ancestor.BaseElement == other.BaseElement) != null, valueIfTrue, valueIfFalse); + } + public bool IsDescendantOrSelf(DynamicXml other) + { + var ancestors = this.AncestorsOrSelf(); + return IsHelper(n => ancestors.Find(ancestor => ancestor.BaseElement == other.BaseElement) != null); + } + public HtmlString IsDescendantOrSelf(DynamicXml other, string valueIfTrue) + { + var ancestors = this.AncestorsOrSelf(); + return IsHelper(n => ancestors.Find(ancestor => ancestor.BaseElement == other.BaseElement) != null, valueIfTrue); + } + public HtmlString IsDescendantOrSelf(DynamicXml other, string valueIfTrue, string valueIfFalse) + { + var ancestors = this.AncestorsOrSelf(); + return IsHelper(n => ancestors.Find(ancestor => ancestor.BaseElement == other.BaseElement) != null, valueIfTrue, valueIfFalse); + } + public bool IsAncestor(DynamicXml other) + { + var descendants = this.Descendants(); + return IsHelper(n => descendants.Find(descendant => descendant.BaseElement == other.BaseElement) != null); + } + public HtmlString IsAncestor(DynamicXml other, string valueIfTrue) + { + var descendants = this.Descendants(); + return IsHelper(n => descendants.Find(descendant => descendant.BaseElement == other.BaseElement) != null, valueIfTrue); + } + public HtmlString IsAncestor(DynamicXml other, string valueIfTrue, string valueIfFalse) + { + var descendants = this.Descendants(); + return IsHelper(n => descendants.Find(descendant => descendant.BaseElement == other.BaseElement) != null, valueIfTrue, valueIfFalse); + } + public bool IsAncestorOrSelf(DynamicXml other) + { + var descendants = this.DescendantsOrSelf(); + return IsHelper(n => descendants.Find(descendant => descendant.BaseElement == other.BaseElement) != null); + } + public HtmlString IsAncestorOrSelf(DynamicXml other, string valueIfTrue) + { + var descendants = this.DescendantsOrSelf(); + return IsHelper(n => descendants.Find(descendant => descendant.BaseElement == other.BaseElement) != null, valueIfTrue); + } + public HtmlString IsAncestorOrSelf(DynamicXml other, string valueIfTrue, string valueIfFalse) + { + var descendants = this.DescendantsOrSelf(); + return IsHelper(n => descendants.Find(descendant => descendant.BaseElement == other.BaseElement) != null, valueIfTrue, valueIfFalse); + } + public List Descendants() + { + return Descendants(n => true); + } + public List Descendants(Func func) + { + var flattenedNodes = this.BaseElement.Elements().Map(func, (XElement n) => { return n.Elements(); }); + return flattenedNodes.ToList().ConvertAll(n => new DynamicXml(n)); + } + public List DescendantsOrSelf() + { + return DescendantsOrSelf(n => true); + } + public List DescendantsOrSelf(Func func) + { + var flattenedNodes = this.BaseElement.Elements().Map(func, (XElement n) => { return n.Elements(); }); + var list = new List(); + list.Add(this); + list.AddRange(flattenedNodes.ToList().ConvertAll(n => new DynamicXml(n))); + return list; + } + public List Ancestors() + { + return Ancestors(item => true); + } + public List Ancestors(Func func) + { + List ancestorList = new List(); + var node = this.BaseElement; + while (node != null) + { + if (node.Parent == null) break; + XElement parent = node.Parent; + if (parent != null) + { + if (this.BaseElement != parent) + { + node = parent; + if (func(node)) + { + ancestorList.Add(node); + } + } + else + { + break; + } + } + else + { + break; + } + } + ancestorList.Reverse(); + return ancestorList.ConvertAll(item => new DynamicXml(item)); + } + public List AncestorsOrSelf() + { + return AncestorsOrSelf(item => true); + } + public List AncestorsOrSelf(Func func) + { + List ancestorList = new List(); + var node = this.BaseElement; + ancestorList.Add(node); + while (node != null) + { + if (node.Parent == null) break; + XElement parent = node.Parent; + if (parent != null) + { + if (this.BaseElement != parent) + { + node = parent; + if (func(node)) + { + ancestorList.Add(node); + } + } + else + { + break; + } + } + else + { + break; + } + } + ancestorList.Reverse(); + return ancestorList.ConvertAll(item => new DynamicXml(item)); + } + + public int Index() + { + if (this.BaseElement != null && this.BaseElement.Parent != null) + { + var elements = this.BaseElement.Parent.Elements(); + int index = 0; + foreach (var element in elements) + { + if (element == this.BaseElement) break; + index++; + } + return index; + } + return 0; + } + + public bool IsHelper(Func test) + { + return test(this); + } + public HtmlString IsHelper(Func test, string valueIfTrue) + { + return IsHelper(test, valueIfTrue, string.Empty); + } + public HtmlString IsHelper(Func test, string valueIfTrue, string valueIfFalse) + { + return test(this) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); + } + + public static string StripDashesInElementOrAttributeNames(string xml) + { + using (MemoryStream outputms = new MemoryStream()) + { + using (TextWriter outputtw = new StreamWriter(outputms)) + { + using (MemoryStream ms = new MemoryStream()) + { + using (TextWriter tw = new StreamWriter(ms)) + { + tw.Write(xml); + tw.Flush(); + ms.Position = 0; + using (TextReader tr = new StreamReader(ms)) + { + bool IsInsideElement = false, IsInsideQuotes = false; + int ic = 0; + while ((ic = tr.Read()) != -1) + { + if (ic == (int)'<' && !IsInsideQuotes) + { + if (tr.Peek() != (int)'!') + { + IsInsideElement = true; + } + } + if (ic == (int)'>' && !IsInsideQuotes) + { + IsInsideElement = false; + } + if (ic == (int)'"') + { + IsInsideQuotes = !IsInsideQuotes; + } + if (!IsInsideElement || ic != (int)'-' || IsInsideQuotes) + { + outputtw.Write((char)ic); + } + } + + } + } + } + outputtw.Flush(); + outputms.Position = 0; + using (TextReader outputtr = new StreamReader(outputms)) + { + return outputtr.ReadToEnd(); + } + } + } + } + } +} diff --git a/src/Umbraco.Core/Dynamics/ExpressionParser.cs b/src/Umbraco.Core/Dynamics/ExpressionParser.cs new file mode 100644 index 0000000000..87158069fd --- /dev/null +++ b/src/Umbraco.Core/Dynamics/ExpressionParser.cs @@ -0,0 +1,2264 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace Umbraco.Core.Dynamics +{ + internal class ExpressionParser + { + struct Token + { + public TokenId id; + public string text; + public int pos; + } + + enum TokenId + { + Unknown, + End, + Identifier, + StringLiteral, + IntegerLiteral, + RealLiteral, + Exclamation, + Percent, + Amphersand, + OpenParen, + CloseParen, + Asterisk, + Plus, + Comma, + Minus, + Dot, + Slash, + Colon, + LessThan, + Equal, + GreaterThan, + Question, + OpenBracket, + CloseBracket, + Bar, + ExclamationEqual, + DoubleAmphersand, + LessThanEqual, + LessGreater, + DoubleEqual, + GreaterThanEqual, + DoubleBar + } + + interface ILogicalSignatures + { + void F(bool x, bool y); + void F(bool? x, bool? y); + } + + interface IArithmeticSignatures + { + void F(int x, int y); + void F(uint x, uint y); + void F(long x, long y); + void F(ulong x, ulong y); + void F(float x, float y); + void F(double x, double y); + void F(decimal x, decimal y); + void F(int? x, int? y); + void F(uint? x, uint? y); + void F(long? x, long? y); + void F(ulong? x, ulong? y); + void F(float? x, float? y); + void F(double? x, double? y); + void F(decimal? x, decimal? y); + } + + interface IRelationalSignatures : IArithmeticSignatures + { + void F(string x, string y); + void F(char x, char y); + void F(DateTime x, DateTime y); + void F(TimeSpan x, TimeSpan y); + void F(char? x, char? y); + void F(DateTime? x, DateTime? y); + void F(TimeSpan? x, TimeSpan? y); + } + + interface IEqualitySignatures : IRelationalSignatures + { + void F(bool x, bool y); + void F(bool? x, bool? y); + } + + interface IAddSignatures : IArithmeticSignatures + { + void F(DateTime x, TimeSpan y); + void F(TimeSpan x, TimeSpan y); + void F(DateTime? x, TimeSpan? y); + void F(TimeSpan? x, TimeSpan? y); + } + + interface ISubtractSignatures : IAddSignatures + { + void F(DateTime x, DateTime y); + void F(DateTime? x, DateTime? y); + } + + interface INegationSignatures + { + void F(int x); + void F(long x); + void F(float x); + void F(double x); + void F(decimal x); + void F(int? x); + void F(long? x); + void F(float? x); + void F(double? x); + void F(decimal? x); + } + + interface INotSignatures + { + void F(bool x); + void F(bool? x); + } + + interface IEnumerableSignatures + { + void Where(bool predicate); + void Any(); + void Any(bool predicate); + void All(bool predicate); + void Count(); + void Count(bool predicate); + void Min(object selector); + void Max(object selector); + void Sum(int selector); + void Sum(int? selector); + void Sum(long selector); + void Sum(long? selector); + void Sum(float selector); + void Sum(float? selector); + void Sum(double selector); + void Sum(double? selector); + void Sum(decimal selector); + void Sum(decimal? selector); + void Average(int selector); + void Average(int? selector); + void Average(long selector); + void Average(long? selector); + void Average(float selector); + void Average(float? selector); + void Average(double selector); + void Average(double? selector); + void Average(decimal selector); + void Average(decimal? selector); + } + + static readonly Type[] predefinedTypes = { + typeof(Object), + typeof(Boolean), + typeof(Char), + typeof(String), + typeof(SByte), + typeof(Byte), + typeof(Int16), + typeof(UInt16), + typeof(Int32), + typeof(UInt32), + typeof(Int64), + typeof(UInt64), + typeof(Single), + typeof(Double), + typeof(Decimal), + typeof(DateTime), + typeof(TimeSpan), + typeof(Guid), + typeof(Math), + typeof(Convert) + }; + + static readonly Expression trueLiteral = Expression.Constant(true); + static readonly Expression falseLiteral = Expression.Constant(false); + static readonly Expression nullLiteral = Expression.Constant(null); + + static readonly string keywordIt = "it"; + static readonly string keywordIif = "iif"; + static readonly string keywordNew = "new"; + + static Dictionary keywords; + + Dictionary symbols; + IDictionary externals; + Dictionary literals; + ParameterExpression it; + string text; + int textPos; + int textLen; + char ch; + Token token; + + public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values) + { + if (expression == null) throw new ArgumentNullException("expression"); + if (keywords == null) keywords = CreateKeywords(); + symbols = new Dictionary(StringComparer.OrdinalIgnoreCase); + literals = new Dictionary(); + if (parameters != null) ProcessParameters(parameters); + if (values != null) ProcessValues(values); + text = expression; + textLen = text.Length; + SetTextPos(0); + NextToken(); + } + + void ProcessParameters(ParameterExpression[] parameters) + { + foreach (ParameterExpression pe in parameters) + if (!String.IsNullOrEmpty(pe.Name)) + AddSymbol(pe.Name, pe); + if (parameters.Length == 1 && String.IsNullOrEmpty(parameters[0].Name)) + it = parameters[0]; + } + + void ProcessValues(object[] values) + { + for (int i = 0; i < values.Length; i++) + { + object value = values[i]; + if (i == values.Length - 1 && value is IDictionary) + { + externals = (IDictionary)value; + } + else + { + AddSymbol("@" + i.ToString(System.Globalization.CultureInfo.InvariantCulture), value); + } + } + } + + void AddSymbol(string name, object value) + { + if (symbols.ContainsKey(name)) + throw ParseError(Res.DuplicateIdentifier, name); + symbols.Add(name, value); + } + + public Expression Parse(Type resultType) + { + int exprPos = token.pos; + Expression expr = ParseExpression(); + if (resultType != null) + if ((expr = PromoteExpression(expr, resultType, true)) == null) + throw ParseError(exprPos, Res.ExpressionTypeMismatch, GetTypeName(resultType)); + ValidateToken(TokenId.End, Res.SyntaxError); + return expr; + } + +#pragma warning disable 0219 + public IEnumerable ParseOrdering() + { + List orderings = new List(); + while (true) + { + Expression expr = ParseExpression(); + bool ascending = true; + if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) + { + NextToken(); + } + else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) + { + NextToken(); + ascending = false; + } + orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending }); + if (token.id != TokenId.Comma) break; + NextToken(); + } + ValidateToken(TokenId.End, Res.SyntaxError); + return orderings; + } +#pragma warning restore 0219 + + // ?: operator + Expression ParseExpression() + { + int errorPos = token.pos; + Expression expr = ParseLogicalOr(); + if (token.id == TokenId.Question) + { + NextToken(); + Expression expr1 = ParseExpression(); + ValidateToken(TokenId.Colon, Res.ColonExpected); + NextToken(); + Expression expr2 = ParseExpression(); + expr = GenerateConditional(expr, expr1, expr2, errorPos); + } + return expr; + } + + // ||, or operator + Expression ParseLogicalOr() + { + Expression left = ParseLogicalAnd(); + while (token.id == TokenId.DoubleBar || TokenIdentifierIs("or")) + { + Token op = token; + NextToken(); + Expression right = ParseLogicalAnd(); + CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos); + left = HandleDynamicNodeLambdas(ExpressionType.OrElse, left, right); + } + return left; + } + + // &&, and operator + Expression ParseLogicalAnd() + { + Expression left = ParseComparison(); + while (token.id == TokenId.DoubleAmphersand || TokenIdentifierIs("and")) + { + Token op = token; + NextToken(); + Expression right = ParseComparison(); + CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos); + left = HandleDynamicNodeLambdas(ExpressionType.AndAlso, left, right); + } + return left; + } + + // =, ==, !=, <>, >, >=, <, <= operators + Expression ParseComparison() + { + Expression left = ParseAdditive(); + while (token.id == TokenId.Equal || token.id == TokenId.DoubleEqual || + token.id == TokenId.ExclamationEqual || token.id == TokenId.LessGreater || + token.id == TokenId.GreaterThan || token.id == TokenId.GreaterThanEqual || + token.id == TokenId.LessThan || token.id == TokenId.LessThanEqual) + { + Token op = token; + NextToken(); + Expression right = ParseAdditive(); + bool isEquality = op.id == TokenId.Equal || op.id == TokenId.DoubleEqual || + op.id == TokenId.ExclamationEqual || op.id == TokenId.LessGreater; + if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType) + { + if (left.Type != right.Type) + { + if (left.Type.IsAssignableFrom(right.Type)) + { + right = Expression.Convert(right, left.Type); + } + else if (right.Type.IsAssignableFrom(left.Type)) + { + left = Expression.Convert(left, right.Type); + } + else if (left is LambdaExpression || right is LambdaExpression) + { + //do nothing here (but further down we'll handle the lambdaexpression) + } + else + { + throw IncompatibleOperandsError(op.text, left, right, op.pos); + } + } + } + else if (IsEnumType(left.Type) || IsEnumType(right.Type)) + { + if (left.Type != right.Type) + { + Expression e; + if ((e = PromoteExpression(right, left.Type, true)) != null) + { + right = e; + } + else if ((e = PromoteExpression(left, right.Type, true)) != null) + { + left = e; + } + else + { + throw IncompatibleOperandsError(op.text, left, right, op.pos); + } + } + } + else + { + CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), + op.text, ref left, ref right, op.pos); + } + switch (op.id) + { + case TokenId.Equal: + case TokenId.DoubleEqual: + left = HandleDynamicNodeLambdas(ExpressionType.Equal, left, right); + break; + case TokenId.ExclamationEqual: + case TokenId.LessGreater: + left = HandleDynamicNodeLambdas(ExpressionType.NotEqual, left, right); + break; + case TokenId.GreaterThan: + left = HandleDynamicNodeLambdas(ExpressionType.GreaterThan, left, right); + break; + case TokenId.GreaterThanEqual: + left = HandleDynamicNodeLambdas(ExpressionType.GreaterThanOrEqual, left, right); + break; + case TokenId.LessThan: + left = HandleDynamicNodeLambdas(ExpressionType.LessThan, left, right); + break; + case TokenId.LessThanEqual: + left = HandleDynamicNodeLambdas(ExpressionType.LessThanOrEqual, left, right); + break; + } + } + return left; + } + + // +, -, & operators + Expression ParseAdditive() + { + Expression left = ParseMultiplicative(); + while (token.id == TokenId.Plus || token.id == TokenId.Minus || + token.id == TokenId.Amphersand) + { + Token op = token; + NextToken(); + Expression right = ParseMultiplicative(); + switch (op.id) + { + case TokenId.Plus: + if (left.Type == typeof(string) || right.Type == typeof(string)) + goto case TokenId.Amphersand; + CheckAndPromoteOperands(typeof(IAddSignatures), op.text, ref left, ref right, op.pos); + left = GenerateAdd(left, right); + break; + case TokenId.Minus: + CheckAndPromoteOperands(typeof(ISubtractSignatures), op.text, ref left, ref right, op.pos); + left = GenerateSubtract(left, right); + break; + case TokenId.Amphersand: + left = GenerateStringConcat(left, right); + break; + } + } + return left; + } + + // *, /, %, mod operators + Expression ParseMultiplicative() + { + Expression left = ParseUnary(); + while (token.id == TokenId.Asterisk || token.id == TokenId.Slash || + token.id == TokenId.Percent || TokenIdentifierIs("mod")) + { + Token op = token; + NextToken(); + Expression right = ParseUnary(); + CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.text, ref left, ref right, op.pos); + switch (op.id) + { + case TokenId.Asterisk: + left = Expression.Multiply(left, right); + break; + case TokenId.Slash: + left = Expression.Divide(left, right); + break; + case TokenId.Percent: + case TokenId.Identifier: + left = HandleDynamicNodeLambdas(ExpressionType.Modulo, left, right); + break; + } + } + return left; + } + + // -, !, not unary operators + Expression ParseUnary() + { + if (token.id == TokenId.Minus || token.id == TokenId.Exclamation || + TokenIdentifierIs("not")) + { + Token op = token; + NextToken(); + if (op.id == TokenId.Minus && (token.id == TokenId.IntegerLiteral || + token.id == TokenId.RealLiteral)) + { + token.text = "-" + token.text; + token.pos = op.pos; + return ParsePrimary(); + } + Expression expr = ParseUnary(); + if (op.id == TokenId.Minus) + { + CheckAndPromoteOperand(typeof(INegationSignatures), op.text, ref expr, op.pos); + expr = Expression.Negate(expr); + } + else + { + CheckAndPromoteOperand(typeof(INotSignatures), op.text, ref expr, op.pos); + if (expr is LambdaExpression) + { + ParameterExpression[] parameters = new ParameterExpression[(expr as LambdaExpression).Parameters.Count]; + (expr as LambdaExpression).Parameters.CopyTo(parameters, 0); + var invokedExpr = Expression.Invoke(expr, parameters); + var not = Expression.Not(Expression.TypeAs(invokedExpr, typeof(Nullable))); + expr = Expression.Lambda>( + Expression.Condition( + Expression.Property(not, "HasValue"), + Expression.Property(not, "Value"), + Expression.Constant(false, typeof(bool)) + ), parameters); + } + else + { + expr = Expression.Not(expr); + } + } + return expr; + } + return ParsePrimary(); + } + + Expression ParsePrimary() + { + Expression expr = ParsePrimaryStart(); + while (true) + { + if (token.id == TokenId.Dot) + { + NextToken(); + expr = ParseMemberAccess(null, expr); + } + else if (token.id == TokenId.OpenBracket) + { + expr = ParseElementAccess(expr); + } + else + { + break; + } + } + return expr; + } + + Expression ParsePrimaryStart() + { + switch (token.id) + { + case TokenId.Identifier: + return ParseIdentifier(); + case TokenId.StringLiteral: + return ParseStringLiteral(); + case TokenId.IntegerLiteral: + return ParseIntegerLiteral(); + case TokenId.RealLiteral: + return ParseRealLiteral(); + case TokenId.OpenParen: + return ParseParenExpression(); + default: + throw ParseError(Res.ExpressionExpected); + } + } + + Expression ParseStringLiteral() + { + ValidateToken(TokenId.StringLiteral); + char quote = token.text[0]; + string s = token.text.Substring(1, token.text.Length - 2); + int start = 0; + while (true) + { + int i = s.IndexOf(quote, start); + if (i < 0) break; + s = s.Remove(i, 1); + start = i + 1; + } + if (quote == '\'') + { + if (s.Length != 1) + throw ParseError(Res.InvalidCharacterLiteral); + NextToken(); + return CreateLiteral(s[0], s); + } + NextToken(); + return CreateLiteral(s, s); + } + + Expression ParseIntegerLiteral() + { + ValidateToken(TokenId.IntegerLiteral); + string text = token.text; + if (text[0] != '-') + { + ulong value; + if (!UInt64.TryParse(text, out value)) + throw ParseError(Res.InvalidIntegerLiteral, text); + NextToken(); + if (value <= (ulong)Int32.MaxValue) return CreateLiteral((int)value, text); + if (value <= (ulong)UInt32.MaxValue) return CreateLiteral((uint)value, text); + if (value <= (ulong)Int64.MaxValue) return CreateLiteral((long)value, text); + return CreateLiteral(value, text); + } + else + { + long value; + if (!Int64.TryParse(text, out value)) + throw ParseError(Res.InvalidIntegerLiteral, text); + NextToken(); + if (value >= Int32.MinValue && value <= Int32.MaxValue) + return CreateLiteral((int)value, text); + return CreateLiteral(value, text); + } + } + + Expression ParseRealLiteral() + { + ValidateToken(TokenId.RealLiteral); + string text = token.text; + object value = null; + char last = text[text.Length - 1]; + if (last == 'F' || last == 'f') + { + float f; + if (Single.TryParse(text.Substring(0, text.Length - 1), out f)) value = f; + } + else + { + double d; + if (Double.TryParse(text, out d)) value = d; + } + if (value == null) throw ParseError(Res.InvalidRealLiteral, text); + NextToken(); + return CreateLiteral(value, text); + } + + Expression CreateLiteral(object value, string text) + { + ConstantExpression expr = Expression.Constant(value); + literals.Add(expr, text); + return expr; + } + + Expression ParseParenExpression() + { + ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + NextToken(); + Expression e = ParseExpression(); + ValidateToken(TokenId.CloseParen, Res.CloseParenOrOperatorExpected); + NextToken(); + return e; + } + + Expression ParseIdentifier() + { + ValidateToken(TokenId.Identifier); + object value; + if (keywords.TryGetValue(token.text, out value)) + { + if (value is Type) return ParseTypeAccess((Type)value); + if (value == (object)keywordIt) return ParseIt(); + if (value == (object)keywordIif) return ParseIif(); + if (value == (object)keywordNew) return ParseNew(); + NextToken(); + return (Expression)value; + } + if (symbols.TryGetValue(token.text, out value) || + externals != null && externals.TryGetValue(token.text, out value)) + { + Expression expr = value as Expression; + if (expr == null) + { + expr = Expression.Constant(value); + } + else + { + LambdaExpression lambda = expr as LambdaExpression; + if (lambda != null) return ParseLambdaInvocation(lambda); + } + NextToken(); + return expr; + } + if (it != null) return ParseMemberAccess(null, it); + throw ParseError(Res.UnknownIdentifier, token.text); + } + + Expression ParseIt() + { + if (it == null) + throw ParseError(Res.NoItInScope); + NextToken(); + return it; + } + + Expression ParseIif() + { + int errorPos = token.pos; + NextToken(); + Expression[] args = ParseArgumentList(); + if (args.Length != 3) + throw ParseError(errorPos, Res.IifRequiresThreeArgs); + return GenerateConditional(args[0], args[1], args[2], errorPos); + } + + Expression GenerateConditional(Expression test, Expression expr1, Expression expr2, int errorPos) + { + if (test.Type != typeof(bool)) + throw ParseError(errorPos, Res.FirstExprMustBeBool); + if (expr1.Type != expr2.Type) + { + Expression expr1as2 = expr2 != nullLiteral ? PromoteExpression(expr1, expr2.Type, true) : null; + Expression expr2as1 = expr1 != nullLiteral ? PromoteExpression(expr2, expr1.Type, true) : null; + if (expr1as2 != null && expr2as1 == null) + { + expr1 = expr1as2; + } + else if (expr2as1 != null && expr1as2 == null) + { + expr2 = expr2as1; + } + else + { + string type1 = expr1 != nullLiteral ? expr1.Type.Name : "null"; + string type2 = expr2 != nullLiteral ? expr2.Type.Name : "null"; + if (expr1as2 != null && expr2as1 != null) + throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2); + throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2); + } + } + return Expression.Condition(test, expr1, expr2); + } + + Expression ParseNew() + { + NextToken(); + ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + NextToken(); + List properties = new List(); + List expressions = new List(); + while (true) + { + int exprPos = token.pos; + Expression expr = ParseExpression(); + string propName; + if (TokenIdentifierIs("as")) + { + NextToken(); + propName = GetIdentifier(); + NextToken(); + } + else + { + MemberExpression me = expr as MemberExpression; + if (me == null) throw ParseError(exprPos, Res.MissingAsClause); + propName = me.Member.Name; + } + expressions.Add(expr); + properties.Add(new DynamicProperty(propName, expr.Type)); + if (token.id != TokenId.Comma) break; + NextToken(); + } + ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); + NextToken(); + Type type = DynamicExpression.CreateClass(properties); + MemberBinding[] bindings = new MemberBinding[properties.Count]; + for (int i = 0; i < bindings.Length; i++) + bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); + return Expression.MemberInit(Expression.New(type), bindings); + } + + Expression ParseLambdaInvocation(LambdaExpression lambda) + { + int errorPos = token.pos; + NextToken(); + Expression[] args = ParseArgumentList(); + MethodBase method; + if (FindMethod(lambda.Type, "Invoke", false, args, out method) != 1) + throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda); + return Expression.Invoke(lambda, args); + } + + Expression ParseTypeAccess(Type type) + { + int errorPos = token.pos; + NextToken(); + if (token.id == TokenId.Question) + { + if (!type.IsValueType || IsNullableType(type)) + throw ParseError(errorPos, Res.TypeHasNoNullableForm, GetTypeName(type)); + type = typeof(Nullable<>).MakeGenericType(type); + NextToken(); + } + if (token.id == TokenId.OpenParen) + { + Expression[] args = ParseArgumentList(); + MethodBase method; + switch (FindBestMethod(type.GetConstructors(), args, out method)) + { + case 0: + if (args.Length == 1) + return GenerateConversion(args[0], type, errorPos); + throw ParseError(errorPos, Res.NoMatchingConstructor, GetTypeName(type)); + case 1: + return Expression.New((ConstructorInfo)method, args); + default: + throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, GetTypeName(type)); + } + } + ValidateToken(TokenId.Dot, Res.DotOrOpenParenExpected); + NextToken(); + return ParseMemberAccess(type, null); + } + + Expression GenerateConversion(Expression expr, Type type, int errorPos) + { + Type exprType = expr.Type; + if (exprType == type) return expr; + if (exprType.IsValueType && type.IsValueType) + { + if ((IsNullableType(exprType) || IsNullableType(type)) && + GetNonNullableType(exprType) == GetNonNullableType(type)) + return Expression.Convert(expr, type); + if ((IsNumericType(exprType) || IsEnumType(exprType)) && + (IsNumericType(type)) || IsEnumType(type)) + return Expression.ConvertChecked(expr, type); + } + if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) || + exprType.IsInterface || type.IsInterface) + return Expression.Convert(expr, type); + throw ParseError(errorPos, Res.CannotConvertValue, + GetTypeName(exprType), GetTypeName(type)); + } + + Expression ParseMemberAccess(Type type, Expression instance) + { + if (instance != null) type = instance.Type; + int errorPos = token.pos; + string id = GetIdentifier(); + NextToken(); + if (token.id == TokenId.OpenParen) + { + if (instance != null && type != typeof(string)) + { + Type enumerableType = FindGenericType(typeof(IEnumerable<>), type); + if (enumerableType != null) + { + Type elementType = enumerableType.GetGenericArguments()[0]; + return ParseAggregate(instance, elementType, id, errorPos); + } + } + Expression[] args = ParseArgumentList(); + MethodBase mb; + LambdaExpression instanceAsString = null; + ParameterExpression instanceExpression = Expression.Parameter(typeof(DynamicNode), "instance"); + if (type.IsGenericType && type != typeof(string)) + { + var typeArgs = type.GetGenericArguments(); + if (typeArgs[0] == typeof(DynamicNode)) + { + if (instance != null && instance is LambdaExpression) + { + if (typeArgs[1] == typeof(object)) + { + instanceAsString = StringFormat(instance as LambdaExpression, instanceExpression); + type = typeof(string); + } + if (typeArgs[1] == typeof(string)) + { + instanceAsString = instance as LambdaExpression; + type = typeof(string); + } + } + } + } + switch (FindMethod(type, id, instance == null, args, out mb)) + { + case 0: + //not found + if (type == typeof(string) && instanceAsString != null) + { + Expression[] newArgs = (new List() { Expression.Invoke(instanceAsString, instanceExpression) }).Concat(args).ToArray(); + mb = ExtensionMethodFinder.FindExtensionMethod(typeof(string), newArgs, id, true); + if (mb != null) + { + return CallMethodOnDynamicNode(instance, newArgs, instanceAsString, instanceExpression, (MethodInfo)mb, true); + } + } + if (type == typeof(string) && instanceAsString == null && instance is MemberExpression) + { + Expression[] newArgs = (new List() { instance }).Concat(args).ToArray(); + mb = ExtensionMethodFinder.FindExtensionMethod(typeof(string), newArgs, id, true); + if (mb != null) + { + return Expression.Call(null, (MethodInfo)mb, newArgs); + } + } + + throw ParseError(errorPos, Res.NoApplicableMethod, + id, GetTypeName(type)); + case 1: + MethodInfo method = (MethodInfo)mb; + if (!IsPredefinedType(method.DeclaringType)) + throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType)); + if (method.ReturnType == typeof(void)) + throw ParseError(errorPos, Res.MethodIsVoid, + id, GetTypeName(method.DeclaringType)); + if (instanceAsString != null) + { + return CallMethodOnDynamicNode(instance, args, instanceAsString, instanceExpression, method, false); + } + return Expression.Call(instance, (MethodInfo)method, args); + default: + throw ParseError(errorPos, Res.AmbiguousMethodInvocation, + id, GetTypeName(type)); + } + } + else + { + //Looks for a member on the type, but above, we're rerouting that into TryGetMember + MemberInfo member = FindPropertyOrField(type, id, instance == null); + if (member == null) + { + if (typeof(DynamicObject).IsAssignableFrom(type)) + { + //We are going to generate a dynamic method by hand coding the expression tree + //this will invoke TryGetMember (but wrapped in an expression tree) + //so that when it's evaluated, DynamicNode should be supported + + ParameterExpression instanceExpression = Expression.Parameter(typeof(DynamicNode), "instance"); + ParameterExpression convertDynamicNullToBooleanFalse = Expression.Parameter(typeof(bool), "convertDynamicNullToBooleanFalse"); + ParameterExpression result = Expression.Parameter(typeof(object), "result"); + ParameterExpression binder = Expression.Variable(typeof(DynamicQueryableGetMemberBinder), "binder"); + ParameterExpression ignoreCase = Expression.Variable(typeof(bool), "ignoreCase"); + ConstructorInfo getMemberBinderConstructor = typeof(DynamicQueryableGetMemberBinder).GetConstructor(new Type[] { typeof(string), typeof(bool) }); + LabelTarget blockReturnLabel = Expression.Label(typeof(object)); + MethodInfo method = typeof(DynamicNode).GetMethod("TryGetMember"); + + BlockExpression block = Expression.Block( + typeof(object), + new[] { ignoreCase, binder, result, convertDynamicNullToBooleanFalse }, + Expression.Assign(convertDynamicNullToBooleanFalse, Expression.Constant(DynamicExpression.ConvertDynamicNullToBooleanFalse, typeof(bool))), + Expression.Assign(ignoreCase, Expression.Constant(false, typeof(bool))), + Expression.Assign(binder, Expression.New(getMemberBinderConstructor, Expression.Constant(id, typeof(string)), ignoreCase)), + Expression.Assign(result, Expression.Constant(null)), + Expression.IfThen(Expression.NotEqual(Expression.Constant(null), instanceExpression), + Expression.Call(instanceExpression, method, binder, result)), + Expression.IfThen( + Expression.AndAlso( + Expression.TypeEqual(result, typeof(DynamicNull)), + Expression.Equal(convertDynamicNullToBooleanFalse, Expression.Constant(true, typeof(bool))) + ), + Expression.Assign(result, Expression.Constant(false, typeof(object))) + ), + Expression.Return(blockReturnLabel, result), + Expression.Label(blockReturnLabel, Expression.Constant(-2, typeof(object))) + ); + LambdaExpression lax = Expression.Lambda>(block, instanceExpression); + return lax; + } + if (typeof(Func).IsAssignableFrom(type)) + { + //accessing a property off an already resolved DynamicNode TryGetMember call + //e.g. uBlogsyPostDate.Date + MethodInfo ReflectPropertyValue = this.GetType().GetMethod("ReflectPropertyValue", BindingFlags.NonPublic | BindingFlags.Static); + ParameterExpression convertDynamicNullToBooleanFalse = Expression.Parameter(typeof(bool), "convertDynamicNullToBooleanFalse"); + ParameterExpression result = Expression.Parameter(typeof(object), "result"); + ParameterExpression idParam = Expression.Parameter(typeof(string), "id"); + ParameterExpression lambdaResult = Expression.Parameter(typeof(object), "lambdaResult"); + ParameterExpression lambdaInstanceExpression = Expression.Parameter(typeof(DynamicNode), "lambdaInstanceExpression"); + ParameterExpression instanceExpression = Expression.Parameter(typeof(Func), "instance"); + LabelTarget blockReturnLabel = Expression.Label(typeof(object)); + + BlockExpression block = Expression.Block( + typeof(object), + new[] { lambdaResult, result, idParam, convertDynamicNullToBooleanFalse }, + Expression.Assign(convertDynamicNullToBooleanFalse, Expression.Constant(DynamicExpression.ConvertDynamicNullToBooleanFalse, typeof(bool))), + Expression.Assign(lambdaResult, Expression.Invoke(instance, lambdaInstanceExpression)), + Expression.Assign(result, Expression.Call(ReflectPropertyValue, lambdaResult, Expression.Constant(id))), + Expression.IfThen( + Expression.AndAlso( + Expression.TypeEqual(result, typeof(DynamicNull)), + Expression.Equal(convertDynamicNullToBooleanFalse, Expression.Constant(true, typeof(bool))) + ), + Expression.Assign(result, Expression.Constant(false, typeof(object))) + ), + Expression.Return(blockReturnLabel, result), + Expression.Label(blockReturnLabel, Expression.Constant(-2, typeof(object))) + ); + LambdaExpression lax = Expression.Lambda>(block, lambdaInstanceExpression); + return lax; + } + } + else + { + return member is PropertyInfo ? + Expression.Property(instance, (PropertyInfo)member) : + Expression.Field(instance, (FieldInfo)member); + } + throw ParseError(errorPos, Res.UnknownPropertyOrField, + id, GetTypeName(type)); + + } + } + static object ReflectPropertyValue(object o, string name) + { + PropertyInfo propertyInfo = o.GetType().GetProperty(name); + if (propertyInfo != null) + { + object result = propertyInfo.GetValue(o, null); + return result; + } + return null; + } + private static Expression CallMethodOnDynamicNode(Expression instance, Expression[] args, LambdaExpression instanceAsString, ParameterExpression instanceExpression, MethodInfo method, bool isStatic) + { + ConstantExpression defaultReturnValue = Expression.Constant(null, typeof(object)); + Type methodReturnType = method.ReturnType; + switch (methodReturnType.Name) + { + case "String": + defaultReturnValue = Expression.Constant(null, typeof(string)); + break; + case "Int32": + defaultReturnValue = Expression.Constant(0, typeof(int)); + break; + case "Boolean": + defaultReturnValue = Expression.Constant(false, typeof(bool)); + break; + } + ParameterExpression result = Expression.Parameter(method.ReturnType, "result"); + LabelTarget blockReturnLabel = Expression.Label(method.ReturnType); + BlockExpression block = Expression.Block( + method.ReturnType, + new[] { result }, + Expression.Assign(result, + Expression.Call( + isStatic ? null : Expression.Invoke(instanceAsString, instanceExpression), + method, + args) + ), + Expression.Return(blockReturnLabel, result), + Expression.Label(blockReturnLabel, defaultReturnValue) + ); + + switch (methodReturnType.Name) + { + case "String": + return Expression.Lambda>(block, instanceExpression); + case "Int32": + return Expression.Lambda>(block, instanceExpression); + case "Boolean": + return Expression.Lambda>(block, instanceExpression); + } + return Expression.Call(instance, (MethodInfo)method, args); + } + + static Type FindGenericType(Type generic, Type type) + { + while (type != null && type != typeof(object)) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == generic) return type; + if (generic.IsInterface) + { + foreach (Type intfType in type.GetInterfaces()) + { + Type found = FindGenericType(generic, intfType); + if (found != null) return found; + } + } + type = type.BaseType; + } + return null; + } + LambdaExpression StringFormat(LambdaExpression lax, ParameterExpression instanceExpression) + { + ParameterExpression cresult = Expression.Parameter(typeof(string), "cresult"); + ParameterExpression temp = Expression.Parameter(typeof(object), "temp"); + ParameterExpression stemp = Expression.Parameter(typeof(string), "string"); + LabelTarget cblockReturnLabel = Expression.Label(typeof(string)); + + MethodInfo stringFormat = typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }); + BlockExpression cblock = Expression.Block( + typeof(string), + new[] { cresult, temp }, + Expression.Assign(temp, Expression.Invoke(lax, instanceExpression)), + Expression.Assign(cresult, Expression.Call(stringFormat, Expression.Constant("{0}"), temp)), + Expression.Return(cblockReturnLabel, cresult), + Expression.Label(cblockReturnLabel, Expression.Constant(null, typeof(string)))); + + LambdaExpression lax2 = Expression.Lambda>(cblock, instanceExpression); + var expression = Expression.Lambda>(cblock, instanceExpression); + return expression; + + } + Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos) + { + ParameterExpression outerIt = it; + ParameterExpression innerIt = Expression.Parameter(elementType, ""); + it = innerIt; + Expression[] args = ParseArgumentList(); + it = outerIt; + MethodBase signature; + if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1) + throw ParseError(errorPos, Res.NoApplicableAggregate, methodName); + Type[] typeArgs; + if (signature.Name == "Min" || signature.Name == "Max") + { + typeArgs = new Type[] { elementType, args[0].Type }; + } + else + { + typeArgs = new Type[] { elementType }; + } + if (args.Length == 0) + { + args = new Expression[] { instance }; + } + else + { + args = new Expression[] { instance, Expression.Lambda(args[0], innerIt) }; + } + return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args); + } + + Expression[] ParseArgumentList() + { + ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + NextToken(); + Expression[] args = token.id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; + ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); + NextToken(); + return args; + } + + Expression[] ParseArguments() + { + List argList = new List(); + while (true) + { + argList.Add(ParseExpression()); + if (token.id != TokenId.Comma) break; + NextToken(); + } + return argList.ToArray(); + } + + Expression ParseElementAccess(Expression expr) + { + int errorPos = token.pos; + ValidateToken(TokenId.OpenBracket, Res.OpenParenExpected); + NextToken(); + Expression[] args = ParseArguments(); + ValidateToken(TokenId.CloseBracket, Res.CloseBracketOrCommaExpected); + NextToken(); + if (expr.Type.IsArray) + { + if (expr.Type.GetArrayRank() != 1 || args.Length != 1) + throw ParseError(errorPos, Res.CannotIndexMultiDimArray); + Expression index = PromoteExpression(args[0], typeof(int), true); + if (index == null) + throw ParseError(errorPos, Res.InvalidIndex); + return Expression.ArrayIndex(expr, index); + } + else + { + MethodBase mb; + switch (FindIndexer(expr.Type, args, out mb)) + { + case 0: + throw ParseError(errorPos, Res.NoApplicableIndexer, + GetTypeName(expr.Type)); + case 1: + return Expression.Call(expr, (MethodInfo)mb, args); + default: + throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, + GetTypeName(expr.Type)); + } + } + } + + static bool IsPredefinedType(Type type) + { + foreach (Type t in predefinedTypes) if (t == type) return true; + return false; + } + + static bool IsNullableType(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + static Type GetNonNullableType(Type type) + { + return IsNullableType(type) ? type.GetGenericArguments()[0] : type; + } + + static string GetTypeName(Type type) + { + Type baseType = GetNonNullableType(type); + string s = baseType.Name; + if (type != baseType) s += '?'; + return s; + } + + static bool IsNumericType(Type type) + { + return GetNumericTypeKind(type) != 0; + } + + static bool IsSignedIntegralType(Type type) + { + return GetNumericTypeKind(type) == 2; + } + + static bool IsUnsignedIntegralType(Type type) + { + return GetNumericTypeKind(type) == 3; + } + + static int GetNumericTypeKind(Type type) + { + type = GetNonNullableType(type); + if (type.IsEnum) return 0; + switch (Type.GetTypeCode(type)) + { + case TypeCode.Char: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return 1; + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + return 2; + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return 3; + default: + return 0; + } + } + + static bool IsEnumType(Type type) + { + return GetNonNullableType(type).IsEnum; + } + + void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos) + { + Expression[] args = new Expression[] { expr }; + MethodBase method; + if (FindMethod(signatures, "F", false, args, out method) != 1) + throw ParseError(errorPos, Res.IncompatibleOperand, + opName, GetTypeName(args[0].Type)); + expr = args[0]; + } + + void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos) + { + Expression[] args = new Expression[] { left, right }; + MethodBase method; + if (FindMethod(signatures, "F", false, args, out method) != 1) + throw IncompatibleOperandsError(opName, left, right, errorPos); + left = args[0]; + right = args[1]; + } + + Exception IncompatibleOperandsError(string opName, Expression left, Expression right, int pos) + { + return ParseError(pos, Res.IncompatibleOperands, + opName, GetTypeName(left.Type), GetTypeName(right.Type)); + } + + MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) + { + BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | + (staticAccess ? BindingFlags.Static : BindingFlags.Instance); + foreach (Type t in SelfAndBaseTypes(type)) + { + MemberInfo[] members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, + flags, Type.FilterNameIgnoreCase, memberName); + if (members.Length != 0) return members[0]; + } + return null; + } + + int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method) + { + BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | + (staticAccess ? BindingFlags.Static : BindingFlags.Instance); + foreach (Type t in SelfAndBaseTypes(type)) + { + MemberInfo[] members = t.FindMembers(MemberTypes.Method, + flags, Type.FilterNameIgnoreCase, methodName); + int count = FindBestMethod(members.Cast(), args, out method); + if (count != 0) return count; + } + method = null; + return 0; + } + + int FindIndexer(Type type, Expression[] args, out MethodBase method) + { + foreach (Type t in SelfAndBaseTypes(type)) + { + MemberInfo[] members = t.GetDefaultMembers(); + if (members.Length != 0) + { + IEnumerable methods = members. + OfType(). + Select(p => (MethodBase)p.GetGetMethod()). + Where(m => m != null); + int count = FindBestMethod(methods, args, out method); + if (count != 0) return count; + } + } + method = null; + return 0; + } + + static IEnumerable SelfAndBaseTypes(Type type) + { + if (type.IsInterface) + { + List types = new List(); + AddInterface(types, type); + return types; + } + return SelfAndBaseClasses(type); + } + + static IEnumerable SelfAndBaseClasses(Type type) + { + while (type != null) + { + yield return type; + type = type.BaseType; + } + } + + static void AddInterface(List types, Type type) + { + if (!types.Contains(type)) + { + types.Add(type); + foreach (Type t in type.GetInterfaces()) AddInterface(types, t); + } + } + + class MethodData + { + public MethodBase MethodBase; + public ParameterInfo[] Parameters; + public Expression[] Args; + } + + int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method) + { + MethodData[] applicable = methods. + Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }). + Where(m => IsApplicable(m, args)). + ToArray(); + if (applicable.Length > 1) + { + applicable = applicable. + Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))). + ToArray(); + } + if (applicable.Length == 1) + { + MethodData md = applicable[0]; + for (int i = 0; i < args.Length; i++) args[i] = md.Args[i]; + method = md.MethodBase; + } + else + { + method = null; + } + return applicable.Length; + } + + bool IsApplicable(MethodData method, Expression[] args) + { + if (method.Parameters.Length != args.Length) return false; + Expression[] promotedArgs = new Expression[args.Length]; + for (int i = 0; i < args.Length; i++) + { + ParameterInfo pi = method.Parameters[i]; + if (pi.IsOut) return false; + Expression promoted = PromoteExpression(args[i], pi.ParameterType, false); + if (promoted == null) return false; + promotedArgs[i] = promoted; + } + method.Args = promotedArgs; + return true; + } + + Expression PromoteExpression(Expression expr, Type type, bool exact) + { + //if the type of the expression is the correct target type, just return it here + if (expr.Type == type) return expr; + //if the type of the expression is a func - invokable returning object, + //we are going to return it here, because we can get the real value when we actually have the instance + //if (typeof(Func).IsAssignableFrom(expr.Type)) return expr; + if (expr is LambdaExpression && ((LambdaExpression)expr).Parameters.Count > 0 && ((LambdaExpression)expr).Parameters[0].Type == typeof(DynamicNode)) + { + return expr; + } + if (expr is ConstantExpression) + { + ConstantExpression ce = (ConstantExpression)expr; + if (ce == nullLiteral) + { + if (!type.IsValueType || IsNullableType(type)) + return Expression.Constant(null, type); + } + else + { + string text; + if (literals.TryGetValue(ce, out text)) + { + Type target = GetNonNullableType(type); + Object value = null; + switch (Type.GetTypeCode(ce.Type)) + { + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + value = ParseNumber(text, target); + break; + case TypeCode.Double: + if (target == typeof(decimal)) value = ParseNumber(text, target); + break; + case TypeCode.String: + value = ParseEnum(text, target); + break; + } + if (value != null) + return Expression.Constant(value, type); + } + } + } + if (IsCompatibleWith(expr.Type, type)) + { + if (type.IsValueType || exact) return Expression.Convert(expr, type); + return expr; + } + return null; + } + + static object ParseNumber(string text, Type type) + { + switch (Type.GetTypeCode(GetNonNullableType(type))) + { + case TypeCode.SByte: + sbyte sb; + if (sbyte.TryParse(text, out sb)) return sb; + break; + case TypeCode.Byte: + byte b; + if (byte.TryParse(text, out b)) return b; + break; + case TypeCode.Int16: + short s; + if (short.TryParse(text, out s)) return s; + break; + case TypeCode.UInt16: + ushort us; + if (ushort.TryParse(text, out us)) return us; + break; + case TypeCode.Int32: + int i; + if (int.TryParse(text, out i)) return i; + break; + case TypeCode.UInt32: + uint ui; + if (uint.TryParse(text, out ui)) return ui; + break; + case TypeCode.Int64: + long l; + if (long.TryParse(text, out l)) return l; + break; + case TypeCode.UInt64: + ulong ul; + if (ulong.TryParse(text, out ul)) return ul; + break; + case TypeCode.Single: + float f; + if (float.TryParse(text, out f)) return f; + break; + case TypeCode.Double: + double d; + if (double.TryParse(text, out d)) return d; + break; + case TypeCode.Decimal: + decimal e; + if (decimal.TryParse(text, out e)) return e; + break; + } + return null; + } + + static object ParseEnum(string name, Type type) + { + if (type.IsEnum) + { + MemberInfo[] memberInfos = type.FindMembers(MemberTypes.Field, + BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static, + Type.FilterNameIgnoreCase, name); + if (memberInfos.Length != 0) return ((FieldInfo)memberInfos[0]).GetValue(null); + } + return null; + } + + static bool IsCompatibleWith(Type source, Type target) + { + if (source == target) return true; + if (!target.IsValueType) return target.IsAssignableFrom(source); + Type st = GetNonNullableType(source); + Type tt = GetNonNullableType(target); + if (st != source && tt == target) return false; + TypeCode sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st); + TypeCode tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt); + switch (sc) + { + case TypeCode.SByte: + switch (tc) + { + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Byte: + switch (tc) + { + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int16: + switch (tc) + { + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt16: + switch (tc) + { + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int32: + switch (tc) + { + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt32: + switch (tc) + { + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int64: + switch (tc) + { + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt64: + switch (tc) + { + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Single: + switch (tc) + { + case TypeCode.Single: + case TypeCode.Double: + return true; + } + break; + default: + if (st == tt) return true; + break; + } + return false; + } + + static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2) + { + bool better = false; + for (int i = 0; i < args.Length; i++) + { + int c = CompareConversions(args[i].Type, + m1.Parameters[i].ParameterType, + m2.Parameters[i].ParameterType); + if (c < 0) return false; + if (c > 0) better = true; + } + return better; + } + + // Return 1 if s -> t1 is a better conversion than s -> t2 + // Return -1 if s -> t2 is a better conversion than s -> t1 + // Return 0 if neither conversion is better + static int CompareConversions(Type s, Type t1, Type t2) + { + if (t1 == t2) return 0; + if (s == t1) return 1; + if (s == t2) return -1; + bool t1t2 = IsCompatibleWith(t1, t2); + bool t2t1 = IsCompatibleWith(t2, t1); + if (t1t2 && !t2t1) return 1; + if (t2t1 && !t1t2) return -1; + if (IsSignedIntegralType(t1) && IsUnsignedIntegralType(t2)) return 1; + if (IsSignedIntegralType(t2) && IsUnsignedIntegralType(t1)) return -1; + return 0; + } + + Expression GenerateEqual(Expression left, Expression right) + { + return HandleDynamicNodeLambdas(ExpressionType.Equal, left, right); + } + + private static Expression HandleDynamicNodeLambdas(ExpressionType expressionType, Expression left, Expression right) + { + bool leftIsLambda = false, rightIsLambda = false; + Expression innerLeft = null; + Expression innerRight = null; + UnaryExpression unboxedLeft = null, unboxedRight = null; + ParameterExpression[] parameters = null; + + if (left is LambdaExpression && (left as LambdaExpression).Type.GetGenericArguments().First() == typeof(DynamicNode)) + { + leftIsLambda = true; + } + + if (right is LambdaExpression && (right as LambdaExpression).Type.GetGenericArguments().First() == typeof(DynamicNode)) + { + rightIsLambda = true; + } + + if (leftIsLambda && !rightIsLambda) + { + parameters = new ParameterExpression[(left as LambdaExpression).Parameters.Count]; + (left as LambdaExpression).Parameters.CopyTo(parameters, 0); + if (right is ConstantExpression) + { + //left lambda, right constant + var invokedExpr = Expression.Invoke(left, (left as LambdaExpression).Parameters.Cast()); + innerLeft = Expression.Convert(invokedExpr, (right as ConstantExpression).Type); + } + if (leftIsLambda && !rightIsLambda && right is MemberExpression) + { + var invokedExpr = Expression.Invoke(left, (left as LambdaExpression).Parameters.Cast()); + innerLeft = Expression.Convert(invokedExpr, (right as MemberExpression).Type); + } + } + if (rightIsLambda && !leftIsLambda) + { + parameters = new ParameterExpression[(right as LambdaExpression).Parameters.Count]; + (right as LambdaExpression).Parameters.CopyTo(parameters, 0); + if (left is ConstantExpression) + { + //right lambda, left constant + var invokedExpr = Expression.Invoke(right, (right as LambdaExpression).Parameters.Cast()); + innerRight = Expression.Convert(invokedExpr, (left as ConstantExpression).Type); + } + if (right is MemberExpression) + { + var invokedExpr = Expression.Invoke(right, (right as LambdaExpression).Parameters.Cast()); + innerRight = Expression.Convert(invokedExpr, (left as MemberExpression).Type); + } + } + bool sequenceEqual = false; + if (leftIsLambda && rightIsLambda) + { + { + Type leftType = ((LambdaExpression)left).Type; + Type rightType = ((LambdaExpression)right).Type; + Type[] leftTypeGenericArguments = leftType.GetGenericArguments(); + Type[] rightTypeGenericArguments = rightType.GetGenericArguments(); + if (leftTypeGenericArguments.SequenceEqual(rightTypeGenericArguments)) + { + sequenceEqual = true; + if (leftTypeGenericArguments.Length == 2) + { + Type TOut = leftTypeGenericArguments[1]; + + if (expressionType == ExpressionType.AndAlso) + { + return ExpressionExtensions.And(left as Expression>, right as Expression>); + } + if (expressionType == ExpressionType.OrElse) + { + return ExpressionExtensions.Or(left as Expression>, right as Expression>); + } + + } + } + else + { + if (leftTypeGenericArguments.Length == 2) + { + //sequence not equal - could be Func && Func + if (leftTypeGenericArguments.First() == rightTypeGenericArguments.First()) + { + bool leftIsObject = leftTypeGenericArguments.ElementAt(1) == typeof(object); + bool rightIsObject = rightTypeGenericArguments.ElementAt(1) == typeof(object); + //if one is an object but not the other + if (leftIsObject ^ rightIsObject) + { + if (leftIsObject) + { + //left side is object + if (innerLeft == null) + { + parameters = new ParameterExpression[(left as LambdaExpression).Parameters.Count]; + (left as LambdaExpression).Parameters.CopyTo(parameters, 0); + innerLeft = Expression.Invoke(left, parameters); + } + unboxedLeft = Expression.Unbox(innerLeft, rightTypeGenericArguments.ElementAt(1)); + + //left is invoked and unboxed to right's TOut, right was not boxed + if (expressionType == ExpressionType.AndAlso) + { + return ExpressionExtensions.And(right as Expression>, Expression.Lambda>(unboxedLeft, parameters) as Expression>); + } + if (expressionType == ExpressionType.OrElse) + { + return ExpressionExtensions.And(right as Expression>, Expression.Lambda>(unboxedLeft, parameters) as Expression>); + } + } + else + { + //right side is object + if (innerRight == null) + { + parameters = new ParameterExpression[(right as LambdaExpression).Parameters.Count]; + (right as LambdaExpression).Parameters.CopyTo(parameters, 0); + innerRight = Expression.Invoke(right, parameters); + } + unboxedRight = Expression.Unbox(innerRight, leftTypeGenericArguments.ElementAt(1)); + + //right is invoked and unboxed to left's TOut, left was not boxed + if (expressionType == ExpressionType.AndAlso) + { + return ExpressionExtensions.And(left as Expression>, Expression.Lambda>(unboxedRight, parameters) as Expression>); + } + if (expressionType == ExpressionType.OrElse) + { + return ExpressionExtensions.And(left as Expression>, Expression.Lambda>(unboxedRight, parameters) as Expression>); + } + } + + } + } + } + } + } + } + + if (leftIsLambda && innerLeft == null) + { + //left is a lambda, but the right was an unhandled expression type + //!ConstantExpression, !MemberExpression + //make sure the left gets invoked + if (parameters == null) + { + parameters = new ParameterExpression[(left as LambdaExpression).Parameters.Count]; + (left as LambdaExpression).Parameters.CopyTo(parameters, 0); + } + innerLeft = Expression.Invoke(left, parameters); + } + if (rightIsLambda && innerRight == null) + { + //right is a lambda, but the left was an unhandled expression type + //!ConstantExpression, !MemberExpression + //make sure the right gets invoked + if (parameters == null) + { + parameters = new ParameterExpression[(right as LambdaExpression).Parameters.Count]; + (right as LambdaExpression).Parameters.CopyTo(parameters, 0); + } + innerRight = Expression.Invoke(right, parameters); + } + if (leftIsLambda && !rightIsLambda && innerLeft != null && !(innerLeft is UnaryExpression) && innerLeft.Type == typeof(object)) + { + //innerLeft is an invoke + unboxedLeft = Expression.Unbox(innerLeft, right.Type); + } + if (rightIsLambda && !leftIsLambda && innerRight != null && !(innerRight is UnaryExpression) && innerRight.Type == typeof(object)) + { + //innerRight is an invoke + unboxedRight = Expression.Unbox(innerRight, left.Type); + } + + BinaryExpression binaryExpression = null; + var finalLeft = unboxedLeft ?? innerLeft ?? left; + var finalRight = unboxedRight ?? innerRight ?? right; + switch (expressionType) + { + case ExpressionType.Equal: + binaryExpression = Expression.Equal(finalLeft, finalRight); + break; + case ExpressionType.NotEqual: + binaryExpression = Expression.NotEqual(finalLeft, finalRight); + break; + case ExpressionType.GreaterThan: + binaryExpression = Expression.GreaterThan(finalLeft, finalRight); + break; + case ExpressionType.LessThan: + binaryExpression = Expression.LessThan(finalLeft, finalRight); + break; + case ExpressionType.GreaterThanOrEqual: + binaryExpression = Expression.GreaterThanOrEqual(finalLeft, finalRight); + break; + case ExpressionType.LessThanOrEqual: + binaryExpression = Expression.LessThanOrEqual(finalLeft, finalRight); + break; + case ExpressionType.Modulo: + binaryExpression = Expression.Modulo(finalLeft, finalRight); + return (Expression.Lambda>(binaryExpression, parameters)); + case ExpressionType.AndAlso: + if ((leftIsLambda && rightIsLambda && sequenceEqual) || (!leftIsLambda && !rightIsLambda)) + { + return Expression.AndAlso(left, right); + } + else + { + return (Expression.Lambda>(Expression.AndAlso(finalLeft, finalRight), parameters)); + } + case ExpressionType.OrElse: + if (leftIsLambda && rightIsLambda && sequenceEqual || (!leftIsLambda && !rightIsLambda)) + { + return Expression.OrElse(left, right); + } + else + { + return (Expression.Lambda>(Expression.OrElse(finalLeft, finalRight), parameters)); + } + default: + return Expression.Equal(left, right); + } + if (leftIsLambda || rightIsLambda) + { + var body = Expression.Condition(Expression.TypeEqual(innerLeft, right.Type), binaryExpression, Expression.Constant(false)); + return Expression.Lambda>(body, parameters); + } + else + { + return binaryExpression; + } + + } + + Expression GenerateNotEqual(Expression left, Expression right) + { + return HandleDynamicNodeLambdas(ExpressionType.NotEqual, left, right); + } + + Expression GenerateGreaterThan(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.GreaterThan( + GenerateStaticMethodCall("Compare", left, right), + Expression.Constant(0) + ); + } + return HandleDynamicNodeLambdas(ExpressionType.GreaterThan, left, right); + } + + Expression GenerateGreaterThanEqual(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.GreaterThanOrEqual( + GenerateStaticMethodCall("Compare", left, right), + Expression.Constant(0) + ); + } + return HandleDynamicNodeLambdas(ExpressionType.GreaterThanOrEqual, left, right); + } + + Expression GenerateLessThan(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.LessThan( + GenerateStaticMethodCall("Compare", left, right), + Expression.Constant(0) + ); + } + return HandleDynamicNodeLambdas(ExpressionType.LessThan, left, right); + } + + Expression GenerateLessThanEqual(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.LessThanOrEqual( + GenerateStaticMethodCall("Compare", left, right), + Expression.Constant(0) + ); + } + return HandleDynamicNodeLambdas(ExpressionType.LessThanOrEqual, left, right); + } + + Expression GenerateAdd(Expression left, Expression right) + { + if (left.Type == typeof(string) && right.Type == typeof(string)) + { + return GenerateStaticMethodCall("Concat", left, right); + } + return Expression.Add(left, right); + } + + Expression GenerateSubtract(Expression left, Expression right) + { + return Expression.Subtract(left, right); + } + + Expression GenerateStringConcat(Expression left, Expression right) + { + return Expression.Call( + null, + typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }), + new[] { left, right }); + } + + MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) + { + return left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); + } + + Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) + { + return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); + } + + void SetTextPos(int pos) + { + textPos = pos; + ch = textPos < textLen ? text[textPos] : '\0'; + } + + void NextChar() + { + if (textPos < textLen) textPos++; + ch = textPos < textLen ? text[textPos] : '\0'; + } + + void NextToken() + { + while (Char.IsWhiteSpace(ch)) NextChar(); + TokenId t; + int tokenPos = textPos; + switch (ch) + { + case '!': + NextChar(); + if (ch == '=') + { + NextChar(); + t = TokenId.ExclamationEqual; + } + else + { + t = TokenId.Exclamation; + } + break; + case '%': + NextChar(); + t = TokenId.Percent; + break; + case '&': + NextChar(); + if (ch == '&') + { + NextChar(); + t = TokenId.DoubleAmphersand; + } + else + { + t = TokenId.Amphersand; + } + break; + case '(': + NextChar(); + t = TokenId.OpenParen; + break; + case ')': + NextChar(); + t = TokenId.CloseParen; + break; + case '*': + NextChar(); + t = TokenId.Asterisk; + break; + case '+': + NextChar(); + t = TokenId.Plus; + break; + case ',': + NextChar(); + t = TokenId.Comma; + break; + case '-': + NextChar(); + t = TokenId.Minus; + break; + case '.': + NextChar(); + t = TokenId.Dot; + break; + case '/': + NextChar(); + t = TokenId.Slash; + break; + case ':': + NextChar(); + t = TokenId.Colon; + break; + case '<': + NextChar(); + if (ch == '=') + { + NextChar(); + t = TokenId.LessThanEqual; + } + else if (ch == '>') + { + NextChar(); + t = TokenId.LessGreater; + } + else + { + t = TokenId.LessThan; + } + break; + case '=': + NextChar(); + if (ch == '=') + { + NextChar(); + t = TokenId.DoubleEqual; + } + else + { + t = TokenId.Equal; + } + break; + case '>': + NextChar(); + if (ch == '=') + { + NextChar(); + t = TokenId.GreaterThanEqual; + } + else + { + t = TokenId.GreaterThan; + } + break; + case '?': + NextChar(); + t = TokenId.Question; + break; + case '[': + NextChar(); + t = TokenId.OpenBracket; + break; + case ']': + NextChar(); + t = TokenId.CloseBracket; + break; + case '|': + NextChar(); + if (ch == '|') + { + NextChar(); + t = TokenId.DoubleBar; + } + else + { + t = TokenId.Bar; + } + break; + case '"': + case '\'': + char quote = ch; + do + { + NextChar(); + while (textPos < textLen && ch != quote) NextChar(); + if (textPos == textLen) + throw ParseError(textPos, Res.UnterminatedStringLiteral); + NextChar(); + } while (ch == quote); + t = TokenId.StringLiteral; + break; + default: + if (Char.IsLetter(ch) || ch == '@' || ch == '_') + { + do + { + NextChar(); + } while (Char.IsLetterOrDigit(ch) || ch == '_'); + t = TokenId.Identifier; + break; + } + if (Char.IsDigit(ch)) + { + t = TokenId.IntegerLiteral; + do + { + NextChar(); + } while (Char.IsDigit(ch)); + if (ch == '.') + { + t = TokenId.RealLiteral; + NextChar(); + ValidateDigit(); + do + { + NextChar(); + } while (Char.IsDigit(ch)); + } + if (ch == 'E' || ch == 'e') + { + t = TokenId.RealLiteral; + NextChar(); + if (ch == '+' || ch == '-') NextChar(); + ValidateDigit(); + do + { + NextChar(); + } while (Char.IsDigit(ch)); + } + if (ch == 'F' || ch == 'f') NextChar(); + break; + } + if (textPos == textLen) + { + t = TokenId.End; + break; + } + throw ParseError(textPos, Res.InvalidCharacter, ch); + } + token.id = t; + token.text = text.Substring(tokenPos, textPos - tokenPos); + token.pos = tokenPos; + } + + bool TokenIdentifierIs(string id) + { + return token.id == TokenId.Identifier && String.Equals(id, token.text, StringComparison.OrdinalIgnoreCase); + } + + string GetIdentifier() + { + ValidateToken(TokenId.Identifier, Res.IdentifierExpected); + string id = token.text; + if (id.Length > 1 && id[0] == '@') id = id.Substring(1); + return id; + } + + void ValidateDigit() + { + if (!Char.IsDigit(ch)) throw ParseError(textPos, Res.DigitExpected); + } + + void ValidateToken(TokenId t, string errorMessage) + { + if (token.id != t) throw ParseError(errorMessage); + } + + void ValidateToken(TokenId t) + { + if (token.id != t) throw ParseError(Res.SyntaxError); + } + + Exception ParseError(string format, params object[] args) + { + return ParseError(token.pos, format, args); + } + + Exception ParseError(int pos, string format, params object[] args) + { + return new ParseException(string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args), pos); + } + + static Dictionary CreateKeywords() + { + Dictionary d = new Dictionary(StringComparer.OrdinalIgnoreCase); + d.Add("true", trueLiteral); + d.Add("false", falseLiteral); + d.Add("null", nullLiteral); + d.Add(keywordIt, keywordIt); + d.Add(keywordIif, keywordIif); + d.Add(keywordNew, keywordNew); + foreach (Type type in predefinedTypes) d.Add(type.Name, type); + return d; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs new file mode 100644 index 0000000000..b0bfae0425 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Web.Compilation; +using System.Runtime.CompilerServices; +using System.Collections; +using System.Linq.Expressions; + +namespace Umbraco.Core.Dynamics +{ + internal static class ExtensionMethodFinder + { + private static List GetAllExtensionMethods(Type thisType, string name, int argumentCount, bool argsContainsThis) + { + //get extension methods from runtime + var candidates = ( + from assembly in BuildManager.GetReferencedAssemblies().Cast() + where assembly.IsDefined(typeof(ExtensionAttribute), false) + from type in assembly.GetTypes() + where (type.IsDefined(typeof(ExtensionAttribute), false) + && type.IsSealed && !type.IsGenericType && !type.IsNested) + from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + // this filters extension methods + where method.IsDefined(typeof(ExtensionAttribute), false) + select method + ); + + //search an explicit type (e.g. Enumerable, where most of the Linq methods are defined) + //if (explicitTypeToSearch != null) + //{ + candidates = candidates.Concat(typeof(IEnumerable).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); + //} + + //filter by name + var methodsByName = candidates.Where(m => m.Name == name); + + var isGenericAndRightParamCount = methodsByName.Where(m => m.GetParameters().Length == argumentCount + (argsContainsThis ? 0 : 1)); + + //find the right overload that can take genericParameterType + //which will be either DynamicNodeList or List which is IEnumerable` + + var withGenericParameterType = isGenericAndRightParamCount.Select(m => new { m, t = firstParameterType(m) }); + + var methodsWhereArgZeroIsTargetType = (from method in withGenericParameterType + where + method.t != null && methodArgZeroHasCorrectTargetType(method.m, method.t, thisType) + select method); + + return methodsWhereArgZeroIsTargetType.Select(mt => mt.m).ToList(); + } + private static bool methodArgZeroHasCorrectTargetType(MethodInfo method, Type firstArgumentType, Type thisType) + { + //This is done with seperate method calls because you can't debug/watch lamdas - if you're trying to figure + //out why the wrong method is returned, it helps to be able to see each boolean result + + return + + // is it defined on me? + methodArgZeroHasCorrectTargetType_TypeMatchesExactly(method, firstArgumentType, thisType) || + + // or on any of my interfaces? + methodArgZeroHasCorrectTargetType_AnInterfaceMatches(method, firstArgumentType, thisType) || + + // or on any of my base types? + methodArgZeroHasCorrectTargetType_IsASubclassOf(method, firstArgumentType, thisType) || + + //share a common interface (e.g. IEnumerable) + methodArgZeroHasCorrectTargetType_ShareACommonInterface(method, firstArgumentType, thisType); + + + } + + private static bool methodArgZeroHasCorrectTargetType_ShareACommonInterface(MethodInfo method, Type firstArgumentType, Type thisType) + { + Type[] interfaces = firstArgumentType.GetInterfaces(); + if (interfaces.Length == 0) + { + return false; + } + bool result = interfaces.All(i => thisType.GetInterfaces().Contains(i)); + return result; + } + + private static bool methodArgZeroHasCorrectTargetType_IsASubclassOf(MethodInfo method, Type firstArgumentType, Type thisType) + { + bool result = thisType.IsSubclassOf(firstArgumentType); + return result; + } + + private static bool methodArgZeroHasCorrectTargetType_AnInterfaceMatches(MethodInfo method, Type firstArgumentType, Type thisType) + { + bool result = thisType.GetInterfaces().Contains(firstArgumentType); + return result; + } + + private static bool methodArgZeroHasCorrectTargetType_TypeMatchesExactly(MethodInfo method, Type firstArgumentType, Type thisType) + { + bool result = (thisType == firstArgumentType); + return result; + } + private static Type firstParameterType(MethodInfo m) + { + ParameterInfo[] p = m.GetParameters(); + if (p.Count() > 0) + { + return p.First().ParameterType; + } + return null; + } + + public static MethodInfo FindExtensionMethod(Type thisType, object[] args, string name, bool argsContainsThis) + { + Type genericType = null; + if (thisType.IsGenericType) + { + genericType = thisType.GetGenericArguments()[0]; + } + + var methods = GetAllExtensionMethods(thisType, name, args.Length, argsContainsThis); + + if (methods.Count == 0) + { + return null; + } + MethodInfo methodToExecute = null; + if (methods.Count > 1) + { + //Given the args, lets get the types and compare the type sequence to try and find the correct overload + var argTypes = args.ToList().ConvertAll(o => + { + Expression oe = (o as Expression); + if (oe != null) + { + return oe.Type.FullName; + } + return o.GetType().FullName; + }); + var methodsWithArgTypes = methods.ConvertAll(method => new { method = method, types = method.GetParameters().ToList().ConvertAll(pi => pi.ParameterType.FullName) }); + var firstMatchingOverload = methodsWithArgTypes.FirstOrDefault(m => + { + return m.types.SequenceEqual(argTypes); + }); + if (firstMatchingOverload != null) + { + methodToExecute = firstMatchingOverload.method; + } + } + + if (methodToExecute == null) + { + MethodInfo firstMethod = methods.FirstOrDefault(); + // NH: this is to ensure that it's always the correct one being chosen when using the LINQ extension methods + if (methods.Count > 1) + { + var firstGenericMethod = methods.FirstOrDefault(x => x.IsGenericMethodDefinition); + if (firstGenericMethod != null) + { + firstMethod = firstGenericMethod; + } + } + + if (firstMethod != null) + { + if (firstMethod.IsGenericMethodDefinition) + { + if (genericType != null) + { + methodToExecute = firstMethod.MakeGenericMethod(genericType); + } + } + else + { + methodToExecute = firstMethod; + } + } + } + return methodToExecute; + } + } +} diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs new file mode 100644 index 0000000000..ee77a6bf29 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Dynamics +{ + internal static class ExtensionMethods + { + public static IEnumerable Map( + this IEnumerable source, + Func selectorFunction, + Func> getChildrenFunction) + { + if (!source.Any()) + { + return source; + } + // Add what we have to the stack + var flattenedList = source.Where(selectorFunction); + // Go through the input enumerable looking for children, + // and add those if we have them + foreach (TSource element in source) + { + var secondInner = getChildrenFunction(element); + if (secondInner.Any()) + { + secondInner = secondInner.Map(selectorFunction, getChildrenFunction); + } + flattenedList = flattenedList.Concat(secondInner); + } + return flattenedList; + } + + + public static DynamicNodeList Random(this DynamicNodeList all, int min, int max) + { + //get a random number generator + Random r = new Random(); + //choose the number of elements to be returned between Min and Max + int Number = r.Next(min, max); + //Call the other method + return Random(all, Number); + } + public static DynamicNodeList Random(this DynamicNodeList all, int max) + { + //Randomly order the items in the set by a Guid, Take the correct number, and return this wrapped in a new DynamicNodeList + return new DynamicNodeList(all.Items.OrderBy(x => Guid.NewGuid()).Take(max)); + } + + public static DynamicNode Random(this DynamicNodeList all) + { + return all.Items.OrderBy(x => Guid.NewGuid()).First(); + } + + public static bool ContainsAny(this string haystack, List needles) + { + if (haystack == null) throw new ArgumentNullException("haystack"); + if (!string.IsNullOrEmpty(haystack) || needles.Count > 0) + { + return needles.Any(haystack.Contains); + } + return false; + } + public static bool ContainsAny(this string haystack, params string[] needles) + { + if (haystack == null) throw new ArgumentNullException("haystack"); + if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) + { + return needles.Any(haystack.Contains); + } + return false; + } + public static bool ContainsAny(this string haystack, StringComparison comparison, List needles) + { + if (haystack == null) throw new ArgumentNullException("haystack"); + if (!string.IsNullOrEmpty(haystack) || needles.Count > 0) + { + return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); + } + return false; + } + public static bool ContainsAny(this string haystack, StringComparison comparison, params string[] needles) + { + if (haystack == null) throw new ArgumentNullException("haystack"); + if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) + { + return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); + } + return false; + } + public static bool ContainsInsensitive(this string haystack, string needle) + { + if (haystack == null) throw new ArgumentNullException("haystack"); + return haystack.IndexOf(needle, StringComparison.CurrentCultureIgnoreCase) >= 0; + } + + public static bool HasValue(this string s) + { + return !string.IsNullOrWhiteSpace(s); + } + + } +} diff --git a/src/Umbraco.Core/Dynamics/Grouping.cs b/src/Umbraco.Core/Dynamics/Grouping.cs new file mode 100644 index 0000000000..fcf693c65b --- /dev/null +++ b/src/Umbraco.Core/Dynamics/Grouping.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Collections; +using System.Dynamic; + +namespace Umbraco.Core.Dynamics +{ + internal class Grouping : IGrouping where T : DynamicObject + { + public K Key { get; set; } + public IEnumerable Elements; + + public IEnumerator GetEnumerator() + { + var temp = new DynamicNodeList(Elements.Cast()); + return (IEnumerator)temp.GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() + { + return (IEnumerator)GetEnumerator(); + } + + public DynamicNodeList OrderBy(string ordering) + { + bool descending = false; + if (ordering.IndexOf(" descending", StringComparison.CurrentCultureIgnoreCase) >= 0) + { + ordering = ordering.Replace(" descending", ""); + descending = true; + } + if (ordering.IndexOf(" desc", StringComparison.CurrentCultureIgnoreCase) >= 0) + { + ordering = ordering.Replace(" desc", ""); + descending = true; + } + + if (!descending) + { + return new DynamicNodeList(Elements.OrderBy(item => + { + object key = null; + (item as DynamicObject).TryGetMember(new DynamicQueryableGetMemberBinder(ordering, false), out key); + return key; + }).Cast()); + } + else + { + return new DynamicNodeList(Elements.OrderByDescending(item => + { + object key = null; + (item as DynamicObject).TryGetMember(new DynamicQueryableGetMemberBinder(ordering, false), out key); + return key; + }).Cast()); + } + } + } + +} diff --git a/src/Umbraco.Core/Dynamics/HtmlTagWrapper.cs b/src/Umbraco.Core/Dynamics/HtmlTagWrapper.cs new file mode 100644 index 0000000000..88f9bdc1be --- /dev/null +++ b/src/Umbraco.Core/Dynamics/HtmlTagWrapper.cs @@ -0,0 +1,201 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.UI; +using System.IO; +using System.Web; + +namespace Umbraco.Core.Dynamics +{ + internal class HtmlTagWrapper : IHtmlTagWrapper, IHtmlString + { + public HtmlTagWrapper Parent; + public List Children; + public List> Attributes; + public void ReflectAttributesFromAnonymousType(List> newAttributes) + { + List> mergedAttributes = + newAttributes + .Concat(Attributes) + .GroupBy(kvp => kvp.Key, kvp => kvp.Value) + .Select(g => new KeyValuePair(g.Key, string.Join(" ", g.ToArray()))) + .ToList(); + + Attributes = mergedAttributes; + } + public void ReflectAttributesFromAnonymousType(object anonymousAttributes) + { + var newAttributes = + anonymousAttributes + .GetType() + .GetProperties() + .Where(prop => !string.IsNullOrWhiteSpace(string.Format("{0}", prop.GetValue(anonymousAttributes, null)))) + .ToList() + .ConvertAll(prop => + new KeyValuePair( + prop.Name, + string.Format("{0}", prop.GetValue(anonymousAttributes, null)) + ) + ); + ReflectAttributesFromAnonymousType(newAttributes); + + } + + public List CssClasses; + public string Tag; + public bool Visible; + + public HtmlTagWrapper(string Tag) + { + this.Tag = Tag; + this.Children = new List(); + this.CssClasses = new List(); + this.Attributes = new List>(); + this.Visible = true; + } + public HtmlString Write() + { + if ((Children.Count > 0 || Attributes.Count > 0 || CssClasses.Count > 0) && Visible) + { + using (MemoryStream ms = new MemoryStream()) + { + using (TextWriter tw = new StreamWriter(ms)) + { + HtmlTextWriter html = new HtmlTextWriter(tw); + this.WriteToHtmlTextWriter(html); + tw.Flush(); + ms.Position = 0; + using (TextReader tr = new StreamReader(ms)) + { + string result = tr.ReadToEnd(); + return new HtmlString(result); + } + } + } + } + return new HtmlString(string.Empty); + } + public override string ToString() + { + return "Use @item.Write() to emit the HTML rather than @item"; + } + public IHtmlString ToHtml() + { + return this.Write(); + } + public void WriteToHtmlTextWriter(HtmlTextWriter html) + { + html.WriteBeginTag(Tag); + string cssClassNames = string.Join(" ", CssClasses.ToArray()).Trim(); + foreach (var attribute in Attributes) + { + html.WriteAttribute(attribute.Key, attribute.Value); + } + if (!string.IsNullOrWhiteSpace(cssClassNames)) + { + html.WriteAttribute("class", cssClassNames); + } + html.Write(HtmlTextWriter.TagRightChar); + foreach (var child in Children) + { + child.WriteToHtmlTextWriter(html); + } + html.WriteEndTag(Tag); + } + + public HtmlTagWrapper AddClassName(string className) + { + className = className.Trim(); + if (!this.CssClasses.Contains(className)) + { + this.CssClasses.Add(className); + } + return this; + } + + public HtmlTagWrapper RemoveClassName(string className) + { + className = className.Trim(); + if (this.CssClasses.Contains(className)) + { + this.CssClasses.Remove(className); + } + return this; + } + + public bool HasClassName(string className) + { + className = className.Trim(); + return (this.CssClasses.Contains(className)); + } + + public HtmlTagWrapper Attr(object newAttributes) + { + this.ReflectAttributesFromAnonymousType(newAttributes); + return this; + } + public HtmlTagWrapper Attr(string name, string value) + { + if (!string.IsNullOrWhiteSpace(value)) + { + List> newAttributes = new List>(); + newAttributes.Add(new KeyValuePair(name, value)); + this.ReflectAttributesFromAnonymousType(newAttributes); + } + else + { + var existingKey = this.Attributes.Find(item => item.Key == name); + Attributes.Remove(existingKey); + } + return this; + } + + public HtmlTagWrapper AddChild(IHtmlTagWrapper newChild) + { + Children.Add(newChild); + return this; + } + public HtmlTagWrapper AddChildren(params IHtmlTagWrapper[] collection) + { + Children.AddRange(collection); + return this; + } + public HtmlTagWrapper AddChild(string text) + { + Children.Add(new HtmlTagWrapperTextNode(text)); + return this; + } + public HtmlTagWrapper AddChildAt(int index, IHtmlTagWrapper newChild) + { + Children.Insert(index, newChild); + return this; + } + public HtmlTagWrapper AddChildAt(int index, string text) + { + Children.Insert(index, new HtmlTagWrapperTextNode(text)); + return this; + } + public HtmlTagWrapper AddChildrenAt(int index, params IHtmlTagWrapper[] collection) + { + Children.InsertRange(index, collection); + return this; + } + public HtmlTagWrapper RemoveChildAt(int index) + { + return this; + } + public int CountChildren() + { + return this.Children.Count; + } + public HtmlTagWrapper ClearChildren() + { + return this; + } + + public string ToHtmlString() + { + return this.Write().ToHtmlString(); + } + } + +} diff --git a/src/Umbraco.Core/Dynamics/HtmlTagWrapperBase.cs b/src/Umbraco.Core/Dynamics/HtmlTagWrapperBase.cs new file mode 100644 index 0000000000..b744a99f19 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/HtmlTagWrapperBase.cs @@ -0,0 +1,9 @@ +using System.Web.UI; + +namespace Umbraco.Core.Dynamics +{ + internal interface IHtmlTagWrapper + { + void WriteToHtmlTextWriter(HtmlTextWriter html); + } +} diff --git a/src/Umbraco.Core/Dynamics/HtmlTagWrapperTextNode.cs b/src/Umbraco.Core/Dynamics/HtmlTagWrapperTextNode.cs new file mode 100644 index 0000000000..37e1f9a6e7 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/HtmlTagWrapperTextNode.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Dynamics +{ + internal class HtmlTagWrapperTextNode : IHtmlTagWrapper + { + public string Content { get; set; } + public HtmlTagWrapperTextNode(string content) + { + this.Content = content; + } + + public void WriteToHtmlTextWriter(System.Web.UI.HtmlTextWriter html) + { + html.Write(Content); + } + } +} diff --git a/src/Umbraco.Core/Dynamics/IDynamicNodeDataSource.cs b/src/Umbraco.Core/Dynamics/IDynamicNodeDataSource.cs new file mode 100644 index 0000000000..8841f82881 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/IDynamicNodeDataSource.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Dynamics +{ + /// + /// This exists only because we want Dynamics in the Core project but DynamicNode has references to ContentType to run some queries + /// and currently the business logic part of Umbraco is still in the legacy project and we don't want to move that to the core so in the + /// meantime until the new APIs are made, we need to have this data source in place with a resolver which is set in the web project. + /// + internal interface IDynamicNodeDataSource + { + IEnumerable GetAncestorOrSelfNodeTypeAlias(DynamicBackingItem node); + Guid GetDataType(string contentTypeAlias, string propertyTypeAlias); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/IRazorDataTypeModel.cs b/src/Umbraco.Core/Dynamics/IRazorDataTypeModel.cs new file mode 100644 index 0000000000..ca17e4c81f --- /dev/null +++ b/src/Umbraco.Core/Dynamics/IRazorDataTypeModel.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Dynamics +{ + //public interface IRazorDataTypeModel + //{ + // bool Init(int currentNodeId, string propertyData, out object instance); + //} +} diff --git a/src/Umbraco.Core/Dynamics/ParseException.cs b/src/Umbraco.Core/Dynamics/ParseException.cs new file mode 100644 index 0000000000..5208644306 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/ParseException.cs @@ -0,0 +1,25 @@ +using System; + +namespace Umbraco.Core.Dynamics +{ + internal sealed class ParseException : Exception + { + readonly int _position; + + public ParseException(string message, int position) + : base(message) + { + this._position = position; + } + + public int Position + { + get { return _position; } + } + + public override string ToString() + { + return string.Format(Res.ParseExceptionFormat, Message, _position); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/PropertyResult.cs b/src/Umbraco.Core/Dynamics/PropertyResult.cs new file mode 100644 index 0000000000..82653fa997 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/PropertyResult.cs @@ -0,0 +1,49 @@ +using System; +using Umbraco.Core.Models; +using umbraco.interfaces; +using System.Web; + +namespace Umbraco.Core.Dynamics +{ + internal class PropertyResult : IDocumentProperty, IHtmlString + { + public PropertyResult(IDocumentProperty source) + { + if (source != null) + { + this.Alias = source.Alias; + this.Value = source.Value; + this.Version = source.Version; + } + } + public PropertyResult(string alias, string value, Guid version) + { + this.Alias = alias; + this.Value = value; + this.Version = version; + } + + public string Alias { get; private set; } + + public string Value { get; private set; } + + public Guid Version { get; private set; } + + public bool IsNull() + { + return Value == null; + } + public bool HasValue() + { + return !string.IsNullOrWhiteSpace(Value); + } + + public int ContextId { get; set; } + public string ContextAlias { get; set; } + + public string ToHtmlString() + { + return Value; + } + } +} diff --git a/src/Umbraco.Core/Dynamics/RazorDataTypeModel.cs b/src/Umbraco.Core/Dynamics/RazorDataTypeModel.cs new file mode 100644 index 0000000000..681dec9efe --- /dev/null +++ b/src/Umbraco.Core/Dynamics/RazorDataTypeModel.cs @@ -0,0 +1,24 @@ +using System; + +namespace Umbraco.Core.Dynamics +{ + //[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] + //public sealed class RazorDataTypeModel : Attribute + //{ + // public readonly Guid DataTypeEditorId; + // public readonly int Priority; + + // public RazorDataTypeModel(string DataTypeEditorId) + // { + // this.DataTypeEditorId = new Guid(DataTypeEditorId); + // Priority = 100; + // } + + // public RazorDataTypeModel(string DataTypeEditorId, int Priority) + // { + // this.DataTypeEditorId = new Guid(DataTypeEditorId); + // this.Priority = Priority; + // } + //} + +} diff --git a/src/Umbraco.Core/Dynamics/RazorLibraryCore.cs b/src/Umbraco.Core/Dynamics/RazorLibraryCore.cs new file mode 100644 index 0000000000..957d90f50b --- /dev/null +++ b/src/Umbraco.Core/Dynamics/RazorLibraryCore.cs @@ -0,0 +1,583 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Core.Models; +using umbraco.interfaces; +using System.Xml.Linq; +using System.Xml.XPath; +using System.Web; +using System.IO; + +namespace Umbraco.Core.Dynamics +{ + //internal class RazorLibraryCore + //{ + // private readonly IDocument _node; + // public IDocument Node + // { + // get { return _node; } + // } + // public RazorLibraryCore(IDocument node) + // { + // this._node = node; + // } + + // public dynamic NodeById(int id) + // { + // var node = new DynamicNode(id); + // if (node != null && node.Id == 0) return new DynamicNull(); + // return node; + // } + // public dynamic NodeById(string id) + // { + // var node = new DynamicNode(id); + // if (node != null && node.Id == 0) return new DynamicNull(); + // return node; + // } + // public dynamic NodeById(DynamicNull Id) + // { + // return new DynamicNull(); + // } + // public dynamic NodeById(object Id) + // { + // if (Id.GetType() == typeof(DynamicNull)) + // { + // return new DynamicNull(); + // } + // var node = new DynamicNode(Id); + // if (node != null && node.Id == 0) return new DynamicNull(); + // return node; + // } + // public dynamic NodesById(List Ids) + // { + // List nodes = new List(); + // foreach (object eachId in Ids) + // nodes.Add(new DynamicNode(eachId)); + // return new DynamicNodeList(nodes); + // } + // public dynamic NodesById(List Ids) + // { + // List nodes = new List(); + // foreach (int eachId in Ids) + // nodes.Add(new DynamicNode(eachId)); + // return new DynamicNodeList(nodes); + // } + // public dynamic NodesById(List Ids, DynamicBackingItemType ItemType) + // { + // List nodes = new List(); + // foreach (int eachId in Ids) + // nodes.Add(new DynamicNode(eachId, ItemType)); + // return new DynamicNodeList(nodes); + // } + // public dynamic NodesById(params object[] Ids) + // { + // return NodesById(Ids.ToList()); + // } + // public dynamic MediaById(DynamicNull Id) + // { + // return new DynamicNull(); + // } + // public dynamic MediaById(int Id) + // { + // var ebm = ExamineBackedMedia.GetUmbracoMedia(Id); + // if (ebm != null && ebm.Id == 0) + // { + // return new DynamicNull(); + // } + // return new DynamicNode(new DynamicBackingItem(ebm)); + // } + // public dynamic MediaById(string Id) + // { + // int mediaId = 0; + // if (int.TryParse(Id, out mediaId)) + // { + // return MediaById(mediaId); + // } + // return new DynamicNull(); + // } + // public dynamic MediaById(object Id) + // { + // if (Id.GetType() == typeof(DynamicNull)) + // { + // return new DynamicNull(); + // } + // int mediaId = 0; + // if (int.TryParse(string.Format("{0}", Id), out mediaId)) + // { + // return MediaById(mediaId); + // } + // return null; + // } + // public dynamic MediaById(List Ids) + // { + // List nodes = new List(); + // foreach (object eachId in Ids) + // nodes.Add(MediaById(eachId)); + // return new DynamicNodeList(nodes); + // } + // public dynamic MediaById(List Ids) + // { + // List nodes = new List(); + // foreach (int eachId in Ids) + // nodes.Add(MediaById(eachId)); + // return new DynamicNodeList(nodes); + // } + // public dynamic MediaById(params object[] Ids) + // { + // return MediaById(Ids.ToList()); + // } + + + // //public dynamic Search(string term, bool useWildCards = true, string searchProvider = null) + // //{ + // // var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; + // // if (!string.IsNullOrEmpty(searchProvider)) + // // searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; + + // // var results = searcher.Search(term, useWildCards); + // // return ExamineSearchUtill.convertSearchResultToDynamicNode(results); + // //} + + // //public dynamic Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + // //{ + // // var s = Examine.ExamineManager.Instance.DefaultSearchProvider; + // // if (searchProvider != null) + // // s = searchProvider; + + // // var results = s.Search(criteria); + // // return ExamineSearchUtill.convertSearchResultToDynamicNode(results); + // //} + + + // public T As() where T : class + // { + // return (this as T); + // } + + // public dynamic ToDynamicXml(string xml) + // { + // if (string.IsNullOrWhiteSpace(xml)) return null; + // var xElement = XElement.Parse(xml); + // return new DynamicXml(xElement); + // } + // public dynamic ToDynamicXml(XElement xElement) + // { + // return new DynamicXml(xElement); + // } + // public dynamic ToDynamicXml(XPathNodeIterator xpni) + // { + // return new DynamicXml(xpni); + // } + // public string Coalesce(params object[] args) + // { + // foreach (var arg in args) + // { + // if (arg != null && arg.GetType() != typeof(DynamicNull)) + // { + // var sArg = string.Format("{0}", arg); + // if (!string.IsNullOrWhiteSpace(sArg)) + // { + // return sArg; + // } + // } + // } + // return string.Empty; + // } + + // public string Concatenate(params object[] args) + // { + // StringBuilder result = new StringBuilder(); + // foreach (var arg in args) + // { + // if (arg != null && arg.GetType() != typeof(DynamicNull)) + // { + // var sArg = string.Format("{0}", arg); + // if (!string.IsNullOrWhiteSpace(sArg)) + // { + // result.Append(sArg); + // } + // } + // } + // return result.ToString(); + // } + // public string Join(string seperator, params object[] args) + // { + // List results = new List(); + // foreach (var arg in args) + // { + // if (arg != null && arg.GetType() != typeof(DynamicNull)) + // { + // var sArg = string.Format("{0}", arg); + // if (!string.IsNullOrWhiteSpace(sArg)) + // { + // results.Add(sArg); + // } + // } + // } + // return string.Join(seperator, results); + // } + + // public HtmlString If(bool test, string valueIfTrue, string valueIfFalse) + // { + // return test ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); + // } + // public HtmlString If(bool test, string valueIfTrue) + // { + // return test ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); + // } + + // public HtmlTagWrapper Wrap(string tag, string innerText, params IHtmlTagWrapper[] Children) + // { + // var item = Wrap(tag, innerText, (object)null); + // foreach (var child in Children) + // { + // item.AddChild(child); + // } + // return item; + // } + // public HtmlTagWrapper Wrap(string tag, string innerText) + // { + // return Wrap(tag, innerText, (object)null); + // } + // public HtmlTagWrapper Wrap(string tag, object inner, object anonymousAttributes) + // { + // string innerText = null; + // if (inner != null && inner.GetType() != typeof(DynamicNull)) + // { + // innerText = string.Format("{0}", inner); + // } + // return Wrap(tag, innerText, anonymousAttributes); + // } + + // public HtmlTagWrapper Wrap(string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] Children) + // { + // string innerText = null; + // if (inner != null && inner.GetType() != typeof(DynamicNull)) + // { + // innerText = string.Format("{0}", inner); + // } + // var item = Wrap(tag, innerText, anonymousAttributes); + // foreach (var child in Children) + // { + // item.AddChild(child); + // } + // return item; + // } + // public HtmlTagWrapper Wrap(string tag, object inner) + // { + // string innerText = null; + // if (inner != null && inner.GetType() != typeof(DynamicNull)) + // { + // innerText = string.Format("{0}", inner); + // } + // return Wrap(tag, innerText, null); + // } + // public HtmlTagWrapper Wrap(string tag, string innerText, object anonymousAttributes) + // { + // HtmlTagWrapper wrap = new HtmlTagWrapper(tag); + // if (anonymousAttributes != null) + // { + // wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); + // } + // if (!string.IsNullOrWhiteSpace(innerText)) + // { + // wrap.Children.Add(new HtmlTagWrapperTextNode(innerText)); + // } + // return wrap; + // } + // public HtmlTagWrapper Wrap(string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] Children) + // { + // HtmlTagWrapper wrap = new HtmlTagWrapper(tag); + // if (anonymousAttributes != null) + // { + // wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); + // } + // if (!string.IsNullOrWhiteSpace(innerText)) + // { + // wrap.Children.Add(new HtmlTagWrapperTextNode(innerText)); + // } + // foreach (var child in Children) + // { + // wrap.AddChild(child); + // } + // return wrap; + // } + + // public HtmlTagWrapper Wrap(bool visible, string tag, string innerText, object anonymousAttributes) + // { + // var item = Wrap(tag, innerText, anonymousAttributes); + // item.Visible = visible; + // return item; + // } + // public HtmlTagWrapper Wrap(bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] Children) + // { + // var item = Wrap(tag, innerText, anonymousAttributes, Children); + // item.Visible = visible; + // foreach (var child in Children) + // { + // item.AddChild(child); + // } + // return item; + // } + // public IHtmlString Truncate(IHtmlString html, int length) + // { + // return Truncate(html.ToHtmlString(), length, true, false); + // } + // public IHtmlString Truncate(IHtmlString html, int length, bool addElipsis) + // { + // return Truncate(html.ToHtmlString(), length, addElipsis, false); + // } + // public IHtmlString Truncate(IHtmlString html, int length, bool addElipsis, bool treatTagsAsContent) + // { + // return Truncate(html.ToHtmlString(), length, addElipsis, treatTagsAsContent); + // } + // public IHtmlString Truncate(DynamicNull html, int length) + // { + // return new HtmlString(string.Empty); + // } + // public IHtmlString Truncate(DynamicNull html, int length, bool addElipsis) + // { + // return new HtmlString(string.Empty); + // } + // public IHtmlString Truncate(DynamicNull html, int length, bool addElipsis, bool treatTagsAsContent) + // { + // return new HtmlString(string.Empty); + // } + // public IHtmlString Truncate(string html, int length) + // { + // return Truncate(html, length, true, false); + // } + // public IHtmlString Truncate(string html, int length, bool addElipsis) + // { + // return Truncate(html, length, addElipsis, false); + // } + // public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) + // { + // using (MemoryStream outputms = new MemoryStream()) + // { + // using (TextWriter outputtw = new StreamWriter(outputms)) + // { + // using (MemoryStream ms = new MemoryStream()) + // { + // using (TextWriter tw = new StreamWriter(ms)) + // { + // tw.Write(html); + // tw.Flush(); + // ms.Position = 0; + // Stack tagStack = new Stack(); + // using (TextReader tr = new StreamReader(ms)) + // { + // bool IsInsideElement = false; + // bool lengthReached = false; + // int ic = 0; + // int currentLength = 0, currentTextLength = 0; + // string currentTag = string.Empty; + // string tagContents = string.Empty; + // bool insideTagSpaceEncountered = false; + // bool isTagClose = false; + // while ((ic = tr.Read()) != -1) + // { + // bool write = true; + + // if (ic == (int)'<') + // { + // if (!lengthReached) + // { + // IsInsideElement = true; + // } + // insideTagSpaceEncountered = false; + // currentTag = string.Empty; + // tagContents = string.Empty; + // isTagClose = false; + // if (tr.Peek() == (int)'/') + // { + // isTagClose = true; + // } + // } + // else if (ic == (int)'>') + // { + // //if (IsInsideElement) + // //{ + // IsInsideElement = false; + // //if (write) + // //{ + // // outputtw.Write('>'); + // //} + // currentTextLength++; + // if (isTagClose && tagStack.Count > 0) + // { + // string thisTag = tagStack.Pop(); + // outputtw.Write(""); + // } + // if (!isTagClose && currentTag.Length > 0) + // { + // if (!lengthReached) + // { + // tagStack.Push(currentTag); + // outputtw.Write("<" + currentTag); + // if (tr.Peek() != (int)' ') + // { + // if (!string.IsNullOrEmpty(tagContents)) + // { + // if (tagContents.EndsWith("/")) + // { + // //short close + // tagStack.Pop(); + // } + // outputtw.Write(tagContents); + // } + // outputtw.Write(">"); + // } + // } + // } + // //} + // continue; + // } + // else + // { + // if (IsInsideElement) + // { + // if (ic == (int)' ') + // { + // if (!insideTagSpaceEncountered) + // { + // insideTagSpaceEncountered = true; + // //if (!isTagClose) + // //{ + // // tagStack.Push(currentTag); + // //} + // } + // } + // if (!insideTagSpaceEncountered) + // { + // currentTag += (char)ic; + // } + // } + // } + // if (IsInsideElement || insideTagSpaceEncountered) + // { + // write = false; + // if (insideTagSpaceEncountered) + // { + // tagContents += (char)ic; + // } + // } + // if (!IsInsideElement || treatTagsAsContent) + // { + // currentTextLength++; + // } + // currentLength++; + // if (currentTextLength <= length || (lengthReached && IsInsideElement)) + // { + // if (write) + // { + // outputtw.Write((char)ic); + // } + // } + // if (!lengthReached && currentTextLength >= length) + // { + // //reached truncate point + // if (addElipsis) + // { + // outputtw.Write("…"); + // } + // lengthReached = true; + // } + + // } + + // } + // } + // } + // outputtw.Flush(); + // outputms.Position = 0; + // using (TextReader outputtr = new StreamReader(outputms)) + // { + // return new HtmlString(outputtr.ReadToEnd().Replace(" ", " ").Trim()); + // } + // } + // } + // } + + + // public HtmlString StripHtml(IHtmlString html) + // { + // return StripHtml(html.ToHtmlString(), (List)null); + // } + // public HtmlString StripHtml(DynamicNull html) + // { + // return new HtmlString(string.Empty); + // } + // public HtmlString StripHtml(string html) + // { + // return StripHtmlTags(html, (List)null); + // } + + // public HtmlString StripHtml(IHtmlString html, List tags) + // { + // return StripHtml(html.ToHtmlString(), tags); + // } + // public HtmlString StripHtml(DynamicNull html, List tags) + // { + // return new HtmlString(string.Empty); + // } + // public HtmlString StripHtml(string html, List tags) + // { + // return StripHtmlTags(html, tags); + // } + + // public HtmlString StripHtml(IHtmlString html, params string[] tags) + // { + // return StripHtml(html.ToHtmlString(), tags.ToList()); + // } + // public HtmlString StripHtml(DynamicNull html, params string[] tags) + // { + // return new HtmlString(string.Empty); + // } + // public HtmlString StripHtml(string html, params string[] tags) + // { + // return StripHtmlTags(html, tags.ToList()); + // } + + // private HtmlString StripHtmlTags(string html, List tags) + // { + // HtmlDocument doc = new HtmlDocument(); + // doc.LoadHtml("

" + html + "

"); + // using (MemoryStream ms = new MemoryStream()) + // { + // List targets = new List(); + + // var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*"); + // if (nodes != null) + // { + // foreach (var node in nodes) + // { + // //is element + // if (node.NodeType == HtmlNodeType.Element) + // { + // bool filterAllTags = (tags == null || tags.Count == 0); + // if (filterAllTags || tags.Any(tag => string.Equals(tag, node.Name, StringComparison.CurrentCultureIgnoreCase))) + // { + // targets.Add(node); + // } + // } + // } + // foreach (var target in targets) + // { + // HtmlNode content = doc.CreateTextNode(target.InnerText); + // target.ParentNode.ReplaceChild(content, target); + // } + + // } + // else + // { + // return new HtmlString(html); + // } + // return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml); + // } + // } + + + //} +} diff --git a/src/Umbraco.Core/Dynamics/Res.cs b/src/Umbraco.Core/Dynamics/Res.cs new file mode 100644 index 0000000000..4ea5193d68 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/Res.cs @@ -0,0 +1,50 @@ +namespace Umbraco.Core.Dynamics +{ + internal static class Res + { + public const string DuplicateIdentifier = "The identifier '{0}' was defined more than once"; + public const string ExpressionTypeMismatch = "Expression of type '{0}' expected"; + public const string ExpressionExpected = "Expression expected"; + public const string InvalidCharacterLiteral = "Character literal must contain exactly one character"; + public const string InvalidIntegerLiteral = "Invalid integer literal '{0}'"; + public const string InvalidRealLiteral = "Invalid real literal '{0}'"; + public const string UnknownIdentifier = "Unknown identifier '{0}'"; + public const string NoItInScope = "No 'it' is in scope"; + public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments"; + public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'"; + public const string BothTypesConvertToOther = "Both of the types '{0}' and '{1}' convert to the other"; + public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other"; + public const string MissingAsClause = "Expression is missing an 'as' clause"; + public const string ArgsIncompatibleWithLambda = "Argument list incompatible with lambda expression"; + public const string TypeHasNoNullableForm = "Type '{0}' has no nullable form"; + public const string NoMatchingConstructor = "No matching constructor in type '{0}'"; + public const string AmbiguousConstructorInvocation = "Ambiguous invocation of '{0}' constructor"; + public const string CannotConvertValue = "A value of type '{0}' cannot be converted to type '{1}'"; + public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'"; + public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible"; + public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value"; + public const string AmbiguousMethodInvocation = "Ambiguous invocation of method '{0}' in type '{1}'"; + public const string UnknownPropertyOrField = "No property or field '{0}' exists in type '{1}'"; + public const string NoApplicableAggregate = "No applicable aggregate method '{0}' exists"; + public const string CannotIndexMultiDimArray = "Indexing of multi-dimensional arrays is not supported"; + public const string InvalidIndex = "Array index must be an integer expression"; + public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'"; + public const string AmbiguousIndexerInvocation = "Ambiguous invocation of indexer in type '{0}'"; + public const string IncompatibleOperand = "Operator '{0}' incompatible with operand type '{1}'"; + public const string IncompatibleOperands = "Operator '{0}' incompatible with operand types '{1}' and '{2}'"; + public const string UnterminatedStringLiteral = "Unterminated string literal"; + public const string InvalidCharacter = "Syntax error '{0}'"; + public const string DigitExpected = "Digit expected"; + public const string SyntaxError = "Syntax error"; + public const string TokenExpected = "{0} expected"; + public const string ParseExceptionFormat = "{0} (at index {1})"; + public const string ColonExpected = "':' expected"; + public const string OpenParenExpected = "'(' expected"; + public const string CloseParenOrOperatorExpected = "')' or operator expected"; + public const string CloseParenOrCommaExpected = "')' or ',' expected"; + public const string DotOrOpenParenExpected = "'.' or '(' expected"; + public const string OpenBracketExpected = "'[' expected"; + public const string CloseBracketOrCommaExpected = "']' or ',' expected"; + public const string IdentifierExpected = "Identifier expected"; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Dynamics/Signature.cs b/src/Umbraco.Core/Dynamics/Signature.cs new file mode 100644 index 0000000000..48c44796f4 --- /dev/null +++ b/src/Umbraco.Core/Dynamics/Signature.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Dynamics +{ + internal class Signature : IEquatable + { + public DynamicProperty[] properties; + public int hashCode; + + public Signature(IEnumerable properties) + { + this.properties = properties.ToArray(); + hashCode = 0; + foreach (DynamicProperty p in properties) + { + hashCode ^= p.Name.GetHashCode() ^ p.Type.GetHashCode(); + } + } + + public override int GetHashCode() + { + return hashCode; + } + + public override bool Equals(object obj) + { + return obj is Signature ? Equals((Signature)obj) : false; + } + + public bool Equals(Signature other) + { + if (properties.Length != other.properties.Length) return false; + for (int i = 0; i < properties.Length; i++) + { + if (properties[i].Name != other.properties[i].Name || + properties[i].Type != other.properties[i].Type) return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ExpressionExtensions.cs b/src/Umbraco.Core/ExpressionExtensions.cs new file mode 100644 index 0000000000..521c7753e0 --- /dev/null +++ b/src/Umbraco.Core/ExpressionExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace Umbraco.Core +{ + internal static class ExpressionExtensions + { + public static Expression> True() { return f => true; } + public static Expression> False() { return f => false; } + + public static Expression> Or(this Expression> left, + Expression> right) + { + var invokedExpr = Expression.Invoke(right, left.Parameters.Cast()); + return Expression.Lambda> + (Expression.OrElse(left.Body, invokedExpr), left.Parameters); + } + + public static Expression> And(this Expression> left, + Expression> right) + { + var invokedExpr = Expression.Invoke(right, left.Parameters.Cast()); + return Expression.Lambda> + (Expression.AndAlso(left.Body, invokedExpr), left.Parameters); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/TypeExtensions.cs b/src/Umbraco.Core/TypeExtensions.cs index 51b68d7e13..aa0e860784 100644 --- a/src/Umbraco.Core/TypeExtensions.cs +++ b/src/Umbraco.Core/TypeExtensions.cs @@ -13,6 +13,13 @@ namespace Umbraco.Core public static class TypeExtensions { + public static object GetDefaultValue(this Type t) + { + return t.IsValueType + ? Activator.CreateInstance(t) + : null; + } + /// /// Checks if the type is an anonymous type /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3005f77ffd..9daa0b809d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -59,6 +59,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Tests/DocumentLookups/BaseRoutingTest.cs b/src/Umbraco.Tests/DocumentLookups/BaseRoutingTest.cs index e43d502660..c0b4bd17b1 100644 --- a/src/Umbraco.Tests/DocumentLookups/BaseRoutingTest.cs +++ b/src/Umbraco.Tests/DocumentLookups/BaseRoutingTest.cs @@ -16,57 +16,21 @@ using umbraco.cms.businesslogic.template; namespace Umbraco.Tests.DocumentLookups { [TestFixture, RequiresSTA] - public abstract class BaseRoutingTest - { - [SetUp] - public virtual void Initialize() - { - TestHelper.SetupLog4NetForTests(); - TestHelper.InitializeDatabase(); - Resolution.Freeze(); - ApplicationContext = new ApplicationContext() { IsReady = true }; + public abstract class BaseRoutingTest : BaseWebTest + { + public override void Initialize() + { } - [TearDown] - public virtual void TearDown() - { - //reset the context on global settings - Umbraco.Core.Configuration.GlobalSettings.HttpContext = null; - ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", ""); - Resolution.IsFrozen = false; - TestHelper.ClearDatabase(); - Cache.ClearAllCache(); - } - - protected FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null) - { - var factory = routeData != null - ? new FakeHttpContextFactory(url, routeData) - : new FakeHttpContextFactory(url); - - - //set the state helper - StateHelper.HttpContext = factory.HttpContext; - - return factory; - } - - protected ApplicationContext ApplicationContext { get; private set; } - - private UmbracoContext GetUmbracoContext(string url, Template template, RouteData routeData = null) - { - var ctx = new UmbracoContext( - GetHttpContextFactory(url, routeData).HttpContext, - ApplicationContext, - new FakeRoutesCache()); - SetupUmbracoContextForTest(ctx, template); - return ctx; - } + public override void TearDown() + { + ConfigurationManager.AppSettings.Set("umbracoHideTopLevelNodeFromPath", ""); + } protected RoutingContext GetRoutingContext(string url, Template template, RouteData routeData = null) { var umbracoContext = GetUmbracoContext(url, template, routeData); - var contentStore = new XmlContentStore(); + var contentStore = new XmlPublishedContentStore(); var niceUrls = new NiceUrlProvider(contentStore, umbracoContext); var routingRequest = new RoutingContext( umbracoContext, @@ -77,54 +41,6 @@ namespace Umbraco.Tests.DocumentLookups return routingRequest; } - protected virtual string GetXmlContent(Template template) - { - return @" - - - - -]> - - - - - - - - - - - - - - - - - - - -"; - } - - /// - /// Initlializes the UmbracoContext with specific XML - /// - /// - /// - protected void SetupUmbracoContextForTest(UmbracoContext umbracoContext, Template template) - { - umbracoContext.GetXmlDelegate = () => - { - var xDoc = new XmlDocument(); - - //create a custom xml structure to return - - xDoc.LoadXml(GetXmlContent(template)); - //return the custom x doc - return xDoc; - }; - } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/DocumentLookups/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/DocumentLookups/RenderRouteHandlerTests.cs index 586b182625..55dad878df 100644 --- a/src/Umbraco.Tests/DocumentLookups/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/DocumentLookups/RenderRouteHandlerTests.cs @@ -43,7 +43,7 @@ namespace Umbraco.Tests.DocumentLookups var routingContext = GetRoutingContext("~/dummy-page", template, routeData); var docRequest = new DocumentRequest(routingContext.UmbracoContext.UmbracoUrl, routingContext) { - Node = routingContext.ContentStore.GetDocumentById(routingContext.UmbracoContext, 1174), + Node = routingContext.PublishedContentStore.GetDocumentById(routingContext.UmbracoContext, 1174), Template = template }; @@ -67,7 +67,7 @@ namespace Umbraco.Tests.DocumentLookups var routingContext = GetRoutingContext("~/dummy-page", template, routeData); var docRequest = new DocumentRequest(routingContext.UmbracoContext.UmbracoUrl, routingContext) { - Node = routingContext.ContentStore.GetDocumentById(routingContext.UmbracoContext, 1172), + Node = routingContext.PublishedContentStore.GetDocumentById(routingContext.UmbracoContext, 1172), Template = template }; diff --git a/src/Umbraco.Tests/DynamicNodeTests.cs b/src/Umbraco.Tests/DynamicNodeTests.cs new file mode 100644 index 0000000000..9634f6ce3f --- /dev/null +++ b/src/Umbraco.Tests/DynamicNodeTests.cs @@ -0,0 +1,76 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Dynamics; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using umbraco.BusinessLogic; +using umbraco.cms.businesslogic.template; + +namespace Umbraco.Tests +{ + [TestFixture] + public class DynamicNodeTests : BaseWebTest + { + private DynamicNode GetDynamicNode(int id) + { + var template = Template.MakeNew("test", new User(0)); + var ctx = GetUmbracoContext("/test", template); + var contentStore = new XmlPublishedContentStore(); + var doc = contentStore.GetDocumentById(ctx, id); + Assert.IsNotNull(doc); + var dynamicNode = new DynamicNode(doc); + Assert.IsNotNull(dynamicNode); + return dynamicNode; + } + + [Test] + public void Get_Children() + { + var dynamicNode = GetDynamicNode(1173); + var asDynamic = dynamicNode.AsDynamic(); + + var children = asDynamic.Children; + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(children)); + + var childrenAsList = asDynamic.ChildrenAsList; //test ChildrenAsList too + Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(childrenAsList)); + + var castChildren = (IEnumerable) children; + Assert.AreEqual(2, castChildren.Count()); + + var castChildrenAsList = (IEnumerable)childrenAsList; + Assert.AreEqual(2, castChildrenAsList.Count()); + } + + [Test] + public void Ancestor_Or_Self() + { + var dynamicNode = GetDynamicNode(1173); + var asDynamic = dynamicNode.AsDynamic(); + + var aos = asDynamic.AncestorOrSelf(); + + Assert.IsNotNull(aos); + + Assert.AreEqual(1046, aos.Id); + } + + [Test] + public void Ancestors_Or_Self() + { + var dynamicNode = GetDynamicNode(1174); + var asDynamic = dynamicNode.AsDynamic(); + + var aos = asDynamic.AncestorsOrSelf(); + + Assert.IsNotNull(aos); + + var list = (IEnumerable) aos; + Assert.AreEqual(3, list.Count()); + Assert.IsTrue(list.Select(x => x.Id).ContainsAll(new[] { 1174, 1173, 1046 })); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/ContentStoreTests.cs b/src/Umbraco.Tests/PublishContentStoreTests.cs similarity index 89% rename from src/Umbraco.Tests/ContentStoreTests.cs rename to src/Umbraco.Tests/PublishContentStoreTests.cs index 7d51e586f9..d4decac6e5 100644 --- a/src/Umbraco.Tests/ContentStoreTests.cs +++ b/src/Umbraco.Tests/PublishContentStoreTests.cs @@ -9,11 +9,11 @@ using umbraco.BusinessLogic; namespace Umbraco.Tests { [TestFixture] - public class ContentStoreTests + public class PublishContentStoreTests { private FakeHttpContextFactory _httpContextFactory; private UmbracoContext _umbracoContext; - private XmlContentStore _contentStore; + private XmlPublishedContentStore _publishedContentStore; [SetUp] public void SetUp() @@ -56,7 +56,7 @@ namespace Umbraco.Tests return xDoc; }; - _contentStore = new XmlContentStore(); + _publishedContentStore = new XmlPublishedContentStore(); } @@ -74,7 +74,7 @@ namespace Umbraco.Tests [TestCase("/home/Sub1", 1173)] //test different cases public void Get_Node_By_Route(string route, int nodeId) { - var result = _contentStore.GetDocumentByRoute(_umbracoContext, route, false); + var result = _publishedContentStore.GetDocumentByRoute(_umbracoContext, route, false); Assert.IsNotNull(result); Assert.AreEqual(nodeId, result.Id); } @@ -84,7 +84,7 @@ namespace Umbraco.Tests [TestCase("/Sub1", 1173)] public void Get_Node_By_Route_Hiding_Top_Level_Nodes(string route, int nodeId) { - var result = _contentStore.GetDocumentByRoute(_umbracoContext, route, true); + var result = _publishedContentStore.GetDocumentByRoute(_umbracoContext, route, true); Assert.IsNotNull(result); Assert.AreEqual(nodeId, result.Id); } diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs new file mode 100644 index 0000000000..b734dc86e5 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -0,0 +1,112 @@ +using System.Web.Routing; +using System.Xml; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; +using Umbraco.Tests.Stubs; +using Umbraco.Web; +using umbraco.BusinessLogic; +using umbraco.cms.businesslogic.cache; +using umbraco.cms.businesslogic.template; + +namespace Umbraco.Tests.TestHelpers +{ + [TestFixture] + public abstract class BaseWebTest + { + + [SetUp] + public virtual void Initialize() + { + TestHelper.SetupLog4NetForTests(); + TestHelper.InitializeDatabase(); + Resolution.Freeze(); + ApplicationContext = new ApplicationContext() { IsReady = true }; + } + + [TearDown] + public virtual void TearDown() + { + //reset the context on global settings + Umbraco.Core.Configuration.GlobalSettings.HttpContext = null; + Resolution.IsFrozen = false; + TestHelper.ClearDatabase(); + Cache.ClearAllCache(); + } + + protected FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null) + { + var factory = routeData != null + ? new FakeHttpContextFactory(url, routeData) + : new FakeHttpContextFactory(url); + + + //set the state helper + StateHelper.HttpContext = factory.HttpContext; + + return factory; + } + + protected ApplicationContext ApplicationContext { get; private set; } + + protected UmbracoContext GetUmbracoContext(string url, Template template, RouteData routeData = null) + { + var ctx = new UmbracoContext( + GetHttpContextFactory(url, routeData).HttpContext, + ApplicationContext, + new FakeRoutesCache()); + SetupUmbracoContextForTest(ctx, template); + return ctx; + } + + protected virtual string GetXmlContent(Template template) + { + return @" + + + + +]> + + + + + + + + + + + + + + + + + + + +"; + } + + /// + /// Initlializes the UmbracoContext with specific XML + /// + /// + /// + protected void SetupUmbracoContextForTest(UmbracoContext umbracoContext, Template template) + { + umbracoContext.GetXmlDelegate = () => + { + var xDoc = new XmlDocument(); + + //create a custom xml structure to return + + xDoc.LoadXml(GetXmlContent(template)); + //return the custom x doc + return xDoc; + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 48b4bf55ef..204fe819af 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -4,11 +4,11 @@ using System.IO; using System.Reflection; using SqlCE4Umbraco; using log4net.Config; -using umbraco; using umbraco.DataLayer; +using GlobalSettings = umbraco.GlobalSettings; namespace Umbraco.Tests.TestHelpers -{ +{ /// /// Common helper properties and methods useful to testing /// diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4c7ecb53c1..e953fc3192 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -58,7 +58,8 @@ - + + @@ -68,6 +69,7 @@ + diff --git a/src/Umbraco.Web/ContentStoreResolver.cs b/src/Umbraco.Web/ContentStoreResolver.cs index fca975a2d5..7e22beffba 100644 --- a/src/Umbraco.Web/ContentStoreResolver.cs +++ b/src/Umbraco.Web/ContentStoreResolver.cs @@ -5,26 +5,26 @@ namespace Umbraco.Web /// /// An object resolver to return the IContentStore /// - internal class ContentStoreResolver : SingleObjectResolverBase + internal class ContentStoreResolver : SingleObjectResolverBase { - internal ContentStoreResolver(IContentStore contentStore) - : base(contentStore) + internal ContentStoreResolver(IPublishedContentStore publishedContentStore) + : base(publishedContentStore) { } /// /// Can be used by developers at runtime to set their IContentStore at app startup /// - /// - public void SetContentStore(IContentStore contentStore) + /// + public void SetContentStore(IPublishedContentStore publishedContentStore) { - Value = contentStore; + Value = publishedContentStore; } /// /// Returns the IContentStore /// - public IContentStore ContentStore + public IPublishedContentStore PublishedContentStore { get { return Value; } } diff --git a/src/Umbraco.Web/DefaultDynamicNodeDataSource.cs b/src/Umbraco.Web/DefaultDynamicNodeDataSource.cs new file mode 100644 index 0000000000..d7f4387c10 --- /dev/null +++ b/src/Umbraco.Web/DefaultDynamicNodeDataSource.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Dynamics; +using umbraco.cms.businesslogic; + +namespace Umbraco.Web +{ + + /// + /// This exists only because we want Dynamics in the Core project but DynamicNode has references to ContentType to run some queries + /// and currently the business logic part of Umbraco is still in the legacy project and we don't want to move that to the core so in the + /// meantime until the new APIs are made, we need to have this data source in place with a resolver which is set in the web project. + /// + internal class DefaultDynamicNodeDataSource : IDynamicNodeDataSource + { + public IEnumerable GetAncestorOrSelfNodeTypeAlias(DynamicBackingItem node) + { + var list = new List(); + if (node != null) + { + if (node.Type == DynamicBackingItemType.Content) + { + //find the doctype node, so we can walk it's parent's tree- not the working.parent content tree + CMSNode working = ContentType.GetByAlias(node.NodeTypeAlias); + while (working != null) + { + if ((working as ContentType) != null) + { + list.Add((working as ContentType).Alias); + } + try + { + working = working.Parent; + } + catch (ArgumentException) + { + break; + } + } + } + else + { + return null; + } + } + return list; + } + + public Guid GetDataType(string contentTypeAlias, string propertyTypeAlias) + { + return ContentType.GetDataType(contentTypeAlias, propertyTypeAlias); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/IContentStore.cs b/src/Umbraco.Web/IPublishedContentStore.cs similarity index 88% rename from src/Umbraco.Web/IContentStore.cs rename to src/Umbraco.Web/IPublishedContentStore.cs index c09ea00579..3b89537340 100644 --- a/src/Umbraco.Web/IContentStore.cs +++ b/src/Umbraco.Web/IPublishedContentStore.cs @@ -2,7 +2,7 @@ using Umbraco.Core.Models; namespace Umbraco.Web { - internal interface IContentStore + internal interface IPublishedContentStore { IDocument GetDocumentById(UmbracoContext umbracoContext, int nodeId); IDocument GetDocumentByRoute(UmbracoContext umbracoContext, string route, bool? hideTopLevelNode = null); diff --git a/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs b/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs index 2a58df3f8f..a984f5f34a 100644 --- a/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs +++ b/src/Umbraco.Web/Routing/DefaultLastChanceLookup.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Routing if (handler.Execute(docRequest.Uri.AbsolutePath) && handler.redirectID > 0) { //currentPage = umbracoContent.GetElementById(handler.redirectID.ToString()); - currentPage = docRequest.RoutingContext.ContentStore.GetDocumentById( + currentPage = docRequest.RoutingContext.PublishedContentStore.GetDocumentById( docRequest.RoutingContext.UmbracoContext, handler.redirectID); diff --git a/src/Umbraco.Web/Routing/DocumentSearcher.cs b/src/Umbraco.Web/Routing/DocumentSearcher.cs index a7838f8d44..ad2674cdf4 100644 --- a/src/Umbraco.Web/Routing/DocumentSearcher.cs +++ b/src/Umbraco.Web/Routing/DocumentSearcher.cs @@ -183,7 +183,7 @@ namespace Umbraco.Web.Routing throw new InvalidOperationException("There is no node."); bool redirect = false; - string internalRedirect = _routingContext.ContentStore.GetDocumentProperty(_umbracoContext, _documentRequest.Node, "umbracoInternalRedirectId"); + string internalRedirect = _routingContext.PublishedContentStore.GetDocumentProperty(_umbracoContext, _documentRequest.Node, "umbracoInternalRedirectId"); if (!string.IsNullOrWhiteSpace(internalRedirect)) { @@ -207,7 +207,7 @@ namespace Umbraco.Web.Routing else { // redirect to another page - var node = _routingContext.ContentStore.GetDocumentById( + var node = _routingContext.PublishedContentStore.GetDocumentById( _umbracoContext, internalRedirectId); @@ -238,7 +238,7 @@ namespace Umbraco.Web.Routing if (_documentRequest.Node == null) throw new InvalidOperationException("There is no node."); - var path = _routingContext.ContentStore.GetDocumentProperty(_umbracoContext, _documentRequest.Node, "@path"); + var path = _routingContext.PublishedContentStore.GetDocumentProperty(_umbracoContext, _documentRequest.Node, "@path"); if (Access.IsProtected(_documentRequest.NodeId, path)) { @@ -251,7 +251,7 @@ namespace Umbraco.Web.Routing LogHelper.Debug("{0}Not logged in, redirect to login page", () => tracePrefix); var loginPageId = Access.GetLoginPage(path); if (loginPageId != _documentRequest.NodeId) - _documentRequest.Node = _routingContext.ContentStore.GetDocumentById( + _documentRequest.Node = _routingContext.PublishedContentStore.GetDocumentById( _umbracoContext, loginPageId); } @@ -260,7 +260,7 @@ namespace Umbraco.Web.Routing LogHelper.Debug("{0}Current member has not access, redirect to error page", () => tracePrefix); var errorPageId = Access.GetErrorPage(path); if (errorPageId != _documentRequest.NodeId) - _documentRequest.Node = _routingContext.ContentStore.GetDocumentById( + _documentRequest.Node = _routingContext.PublishedContentStore.GetDocumentById( _umbracoContext, errorPageId); } @@ -295,7 +295,7 @@ namespace Umbraco.Web.Routing { if (string.IsNullOrWhiteSpace(templateAlias)) { - templateAlias = _routingContext.ContentStore.GetDocumentProperty(_umbracoContext, _documentRequest.Node, "@TemplateId"); + templateAlias = _routingContext.PublishedContentStore.GetDocumentProperty(_umbracoContext, _documentRequest.Node, "@TemplateId"); LogHelper.Debug("{0}Look for template id={1}", () => tracePrefix, () => templateAlias); int templateId; if (!int.TryParse(templateAlias, out templateId)) @@ -337,7 +337,7 @@ namespace Umbraco.Web.Routing if (_documentRequest.HasNode) { int redirectId; - if (!int.TryParse(_routingContext.ContentStore.GetDocumentProperty(_umbracoContext, _documentRequest.Node, "umbracoRedirect"), out redirectId)) + if (!int.TryParse(_routingContext.PublishedContentStore.GetDocumentProperty(_umbracoContext, _documentRequest.Node, "umbracoRedirect"), out redirectId)) redirectId = -1; string redirectUrl = "#"; if (redirectId > 0) diff --git a/src/Umbraco.Web/Routing/LookupByAlias.cs b/src/Umbraco.Web/Routing/LookupByAlias.cs index 39fd43d43e..115db65ab4 100644 --- a/src/Umbraco.Web/Routing/LookupByAlias.cs +++ b/src/Umbraco.Web/Routing/LookupByAlias.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Routing if (docRequest.Uri.AbsolutePath != "/") // no alias if "/" { - node = docRequest.RoutingContext.ContentStore.GetDocumentByUrlAlias( + node = docRequest.RoutingContext.PublishedContentStore.GetDocumentByUrlAlias( docRequest.RoutingContext.UmbracoContext, docRequest.HasDomain ? docRequest.Domain.RootNodeId : 0, docRequest.Uri.AbsolutePath); diff --git a/src/Umbraco.Web/Routing/LookupById.cs b/src/Umbraco.Web/Routing/LookupById.cs index 0ed98d0baa..8175757783 100644 --- a/src/Umbraco.Web/Routing/LookupById.cs +++ b/src/Umbraco.Web/Routing/LookupById.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.Routing if (nodeId > 0) { LogHelper.Debug("Id={0}", () => nodeId); - node = docRequest.RoutingContext.ContentStore.GetDocumentById( + node = docRequest.RoutingContext.PublishedContentStore.GetDocumentById( docRequest.RoutingContext.UmbracoContext, nodeId); diff --git a/src/Umbraco.Web/Routing/LookupByNiceUrl.cs b/src/Umbraco.Web/Routing/LookupByNiceUrl.cs index de6ff0cf56..2f3a24a136 100644 --- a/src/Umbraco.Web/Routing/LookupByNiceUrl.cs +++ b/src/Umbraco.Web/Routing/LookupByNiceUrl.cs @@ -51,7 +51,7 @@ namespace Umbraco.Web.Routing IDocument node = null; if (nodeId > 0) { - node = docreq.RoutingContext.ContentStore.GetDocumentById( + node = docreq.RoutingContext.PublishedContentStore.GetDocumentById( docreq.RoutingContext.UmbracoContext, nodeId); @@ -69,7 +69,7 @@ namespace Umbraco.Web.Routing if (node == null) { LogHelper.Debug("Cache miss, query"); - node = docreq.RoutingContext.ContentStore.GetDocumentByRoute( + node = docreq.RoutingContext.PublishedContentStore.GetDocumentByRoute( docreq.RoutingContext.UmbracoContext, route); diff --git a/src/Umbraco.Web/Routing/NiceUrlProvider.cs b/src/Umbraco.Web/Routing/NiceUrlProvider.cs index 1b5ebfbe09..355609022c 100644 --- a/src/Umbraco.Web/Routing/NiceUrlProvider.cs +++ b/src/Umbraco.Web/Routing/NiceUrlProvider.cs @@ -20,16 +20,16 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - /// The content store. + /// The content store. /// The Umbraco context. - public NiceUrlProvider(IContentStore contentStore, UmbracoContext umbracoContext) + public NiceUrlProvider(IPublishedContentStore publishedContentStore, UmbracoContext umbracoContext) { _umbracoContext = umbracoContext; - _contentStore = contentStore; + _publishedContentStore = publishedContentStore; } private readonly UmbracoContext _umbracoContext; - private readonly IContentStore _contentStore; + private readonly IPublishedContentStore _publishedContentStore; // note: this could be a parameter... const string UrlNameProperty = "@urlName"; @@ -86,7 +86,7 @@ namespace Umbraco.Web.Routing } else { - var node = _contentStore.GetDocumentById(_umbracoContext, nodeId); + var node = _publishedContentStore.GetDocumentById(_umbracoContext, nodeId); if (node == null) return "#"; // legacy wrote to the log here... @@ -95,9 +95,9 @@ namespace Umbraco.Web.Routing domainUri = DomainUriAtNode(id, current); while (domainUri == null && id > 0) { - pathParts.Add(_contentStore.GetDocumentProperty(_umbracoContext, node, UrlNameProperty)); + pathParts.Add(_publishedContentStore.GetDocumentProperty(_umbracoContext, node, UrlNameProperty)); node = node.Parent; // set to parent node - id = int.Parse(_contentStore.GetDocumentProperty(_umbracoContext, node, "@id")); // will be -1 or 1234 + id = int.Parse(_publishedContentStore.GetDocumentProperty(_umbracoContext, node, "@id")); // will be -1 or 1234 domainUri = id > 0 ? DomainUriAtNode(id, current) : null; } @@ -146,7 +146,7 @@ namespace Umbraco.Web.Routing } else { - var node = _contentStore.GetDocumentById(_umbracoContext, nodeId); + var node = _publishedContentStore.GetDocumentById(_umbracoContext, nodeId); if (node == null) return new string[] { "#" }; // legacy wrote to the log here... @@ -155,9 +155,9 @@ namespace Umbraco.Web.Routing domainUris = DomainUrisAtNode(id, current); while (!domainUris.Any() && id > 0) { - pathParts.Add(_contentStore.GetDocumentProperty(_umbracoContext, node, UrlNameProperty)); + pathParts.Add(_publishedContentStore.GetDocumentProperty(_umbracoContext, node, UrlNameProperty)); node = node.Parent; //set to parent node - id = int.Parse(_contentStore.GetDocumentProperty(_umbracoContext, node, "@id")); // will be -1 or 1234 + id = int.Parse(_publishedContentStore.GetDocumentProperty(_umbracoContext, node, "@id")); // will be -1 or 1234 domainUris = id > 0 ? DomainUrisAtNode(id, current) : new Uri[] { }; } diff --git a/src/Umbraco.Web/Routing/RoutingContext.cs b/src/Umbraco.Web/Routing/RoutingContext.cs index 0b3bc77c14..5aae944da0 100644 --- a/src/Umbraco.Web/Routing/RoutingContext.cs +++ b/src/Umbraco.Web/Routing/RoutingContext.cs @@ -15,19 +15,19 @@ namespace Umbraco.Web.Routing /// /// The document lookups resolver. /// - /// The content store. + /// The content store. /// The nice urls resolver. internal RoutingContext( UmbracoContext umbracoContext, IEnumerable documentLookups, IDocumentLastChanceLookup documentLastChanceLookup, - IContentStore contentStore, + IPublishedContentStore publishedContentStore, NiceUrlProvider niceUrlResolver) { this.UmbracoContext = umbracoContext; this.DocumentLookups = documentLookups; DocumentLastChanceLookup = documentLastChanceLookup; - this.ContentStore = contentStore; + this.PublishedContentStore = publishedContentStore; this.NiceUrlProvider = niceUrlResolver; } @@ -49,7 +49,7 @@ namespace Umbraco.Web.Routing /// /// Gets the content store. /// - internal IContentStore ContentStore { get; private set; } + internal IPublishedContentStore PublishedContentStore { get; private set; } /// /// Gets the nice urls provider. diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 447264057b..88f8627dae 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -239,15 +239,16 @@ Properties\SolutionInfo.cs + - + - + diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 9ded87e898..21d11f6741 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -55,13 +55,13 @@ namespace Umbraco.Web UmbracoContext.Current = umbracoContext; //create the nice urls - var niceUrls = new NiceUrlProvider(ContentStoreResolver.Current.ContentStore, umbracoContext); + var niceUrls = new NiceUrlProvider(ContentStoreResolver.Current.PublishedContentStore, umbracoContext); //create the RoutingContext var routingContext = new RoutingContext( umbracoContext, DocumentLookupsResolver.Current.DocumentLookups, LastChanceLookupResolver.Current.LastChanceLookup, - ContentStoreResolver.Current.ContentStore, + ContentStoreResolver.Current.PublishedContentStore, niceUrls); //assign the routing context back to the umbraco context umbracoContext.RoutingContext = routingContext; diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index e6a1020c11..7b86a78f10 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -4,6 +4,7 @@ using System.Web.Mvc; using System.Web.Routing; using Umbraco.Core; using Umbraco.Core.Dictionary; +using Umbraco.Core.Dynamics; using Umbraco.Web.Dictionary; using Umbraco.Web.Media.ThumbnailProviders; using Umbraco.Web.Mvc; @@ -125,7 +126,7 @@ namespace Umbraco.Web { base.InitializeResolvers(); - ContentStoreResolver.Current = new ContentStoreResolver(new XmlContentStore()); + ContentStoreResolver.Current = new ContentStoreResolver(new XmlPublishedContentStore()); FilteredControllerFactoriesResolver.Current = new FilteredControllerFactoriesResolver( //add all known factories, devs can then modify this list on application startup either by binding to events @@ -156,6 +157,10 @@ namespace Umbraco.Web CultureDictionaryFactoryResolver.Current = new CultureDictionaryFactoryResolver( new DefaultCultureDictionaryFactory()); + + //This exists only because the new business logic classes aren't created yet and we want Dynamics in the Core project, + //see the note in the DynamicNodeDataSourceResolver.cs class + DynamicNodeDataSourceResolver.Current = new DynamicNodeDataSourceResolver(new DefaultDynamicNodeDataSource()); } } diff --git a/src/Umbraco.Web/XmlContentStore.cs b/src/Umbraco.Web/XmlPublishedContentStore.cs similarity index 96% rename from src/Umbraco.Web/XmlContentStore.cs rename to src/Umbraco.Web/XmlPublishedContentStore.cs index c450efca11..791ecb3944 100644 --- a/src/Umbraco.Web/XmlContentStore.cs +++ b/src/Umbraco.Web/XmlPublishedContentStore.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web /// /// An IContentStore which uses the Xml cache system to return data /// - internal class XmlContentStore : IContentStore + internal class XmlPublishedContentStore : IPublishedContentStore { private IDocument ConvertToDocument(XmlNode xmlNode) diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicQueryable.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicQueryable.cs index ca1868b8eb..059a7baf95 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicQueryable.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicQueryable.cs @@ -2940,26 +2940,4 @@ namespace System.Linq.Dynamic public const string CloseBracketOrCommaExpected = "']' or ',' expected"; public const string IdentifierExpected = "Identifier expected"; } - - public static class PredicateBuilder - { - public static Expression> True() { return f => true; } - public static Expression> False() { return f => false; } - - public static Expression> Or(this Expression> expr1, - Expression> expr2) - { - var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast()); - return Expression.Lambda> - (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters); - } - - public static Expression> And(this Expression> expr1, - Expression> expr2) - { - var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast()); - return Expression.Lambda> - (Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters); - } - } } diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/PredicateBuilder.cs b/src/umbraco.MacroEngines/RazorDynamicNode/PredicateBuilder.cs new file mode 100644 index 0000000000..95300eb923 --- /dev/null +++ b/src/umbraco.MacroEngines/RazorDynamicNode/PredicateBuilder.cs @@ -0,0 +1,29 @@ +using System.Linq.Expressions; + +namespace System.Linq.Dynamic +{ + [Obsolete("This class is superceded by Umbraco.Core.ExpressionExtensions")] + public static class PredicateBuilder + { + public static Expression> True() + { + return Umbraco.Core.ExpressionExtensions.True(); + } + public static Expression> False() + { + return Umbraco.Core.ExpressionExtensions.False(); + } + + public static Expression> Or(this Expression> expr1, + Expression> expr2) + { + return Umbraco.Core.ExpressionExtensions.Or(expr1, expr2); + } + + public static Expression> And(this Expression> expr1, + Expression> expr2) + { + return Umbraco.Core.ExpressionExtensions.And(expr1, expr2); + } + } +} \ No newline at end of file diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index c478cadd58..3316940585 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -99,6 +99,7 @@ +