From e36dc368d9b6b2825205482d541a4a14c6a386ca Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 14 Nov 2014 10:26:12 +0100 Subject: [PATCH] U4-5802 - fix Ancestor, AncestorOrSelf + document --- src/Umbraco.Web/PublishedContentExtensions.cs | 254 ++++++++++++------ 1 file changed, 170 insertions(+), 84 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index c771debbad..29a96f30b0 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1,7 +1,3 @@ -// ENABLE THE FIX in 7.0.0 -// TODO if all goes well, remove the obsolete code eventually -#define FIX_AXES - using System; using System.Collections.Generic; using System.Data; @@ -911,148 +907,270 @@ namespace Umbraco.Web // - the relative order of siblings is the order in which they occur in the children property of their parent node. // - children and descendants occur before following siblings. - // SO, here we want to walk up the tree. which is what AncestorOrSelf does but NOT what AncestorsOrSelf does since - // it reverses the list, so basically ancestors are NOT XPath-compliant in Umbraco at the moment -- but fixing that - // would be a breaking change. Defining FIX_AXES would fix the situation. - + /// + /// Gets the ancestors of the content. + /// + /// The content. + /// The ancestors of the content, in down-top order. + /// Does not consider the content itself. public static IEnumerable Ancestors(this IPublishedContent content) { return content.AncestorsOrSelf(false, null); } - public static IEnumerable Ancestors(this IPublishedContent content, int level) + /// + /// Gets the ancestors of the content, at a level lesser or equal to a specified level. + /// + /// The content. + /// The level. + /// The ancestors of the content, at a level lesser or equal to the specified level, in down-top order. + /// Does not consider the content itself. Only content that are "high enough" in the tree are returned. + public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel) { - return content.AncestorsOrSelf(false, n => n.Level <= level); + return content.AncestorsOrSelf(false, n => n.Level <= maxLevel); } + /// + /// Gets the ancestors of the content, of a specified content type. + /// + /// The content. + /// The content type. + /// The ancestors of the content, of the specified content type, in down-top order. + /// Does not consider the content itself. Returns all ancestors, of the specified content type. public static IEnumerable Ancestors(this IPublishedContent content, string contentTypeAlias) { return content.AncestorsOrSelf(false, n => n.DocumentTypeAlias == contentTypeAlias); } + /// + /// Gets the ancestors of the content, of a specified content type. + /// + /// The content type. + /// The content. + /// The ancestors of the content, of the specified content type, in down-top order. + /// Does not consider the content itself. Returns all ancestors, of the specified content type. public static IEnumerable Ancestors(this IPublishedContent content) where T : class, IPublishedContent { return content.Ancestors().OfType(); } - public static IEnumerable Ancestors(this IPublishedContent content, int level) + /// + /// Gets the ancestors of the content, at a level lesser or equal to a specified level, and of a specified content type. + /// + /// The content type. + /// The content. + /// The level. + /// The ancestors of the content, at a level lesser or equal to the specified level, and of the specified + /// content type, in down-top order. + /// Does not consider the content itself. Only content that are "high enough" in the trees, and of the + /// specified content type, are returned. + public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel) where T : class, IPublishedContent { - return content.Ancestors(level).OfType(); + return content.Ancestors(maxLevel).OfType(); } + /// + /// Gets the content and its ancestors. + /// + /// The content. + /// The content and its ancestors, in down-top order. public static IEnumerable AncestorsOrSelf(this IPublishedContent content) { return content.AncestorsOrSelf(true, null); } - public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int level) + /// + /// Gets the content and its ancestors, at a level lesser or equal to a specified level. + /// + /// The content. + /// The level. + /// The content and its ancestors, at a level lesser or equal to the specified level, + /// in down-top order. + /// Only content that are "high enough" in the tree are returned. So it may or may not begin + /// with the content itself, depending on its level. + public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel) { - return content.AncestorsOrSelf(true, n => n.Level <= level); + return content.AncestorsOrSelf(true, n => n.Level <= maxLevel); } + /// + /// Gets the content and its ancestors, of a specified content type. + /// + /// The content. + /// The content type. + /// The content and its ancestors, of the specified content type, in down-top order. + /// May or may not begin with the content itself, depending on its content type. public static IEnumerable AncestorsOrSelf(this IPublishedContent content, string contentTypeAlias) { return content.AncestorsOrSelf(true, n => n.DocumentTypeAlias == contentTypeAlias); } + /// + /// Gets the content and its ancestors, of a specified content type. + /// + /// The content type. + /// The content. + /// The content and its ancestors, of the specified content type, in down-top order. + /// May or may not begin with the content itself, depending on its content type. public static IEnumerable AncestorsOrSelf(this IPublishedContent content) where T : class, IPublishedContent { return content.AncestorsOrSelf().OfType(); } - public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int level) + /// + /// Gets the content and its ancestor, at a lever lesser or equal to a specified level, and of a specified content type. + /// + /// The content type. + /// The content. + /// The level. + /// The content and its ancestors, at a level lesser or equal to the specified level, and of the specified + /// content type, in down-top order. + /// May or may not begin with the content itself, depending on its level and content type. + public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel) where T : class, IPublishedContent { - return content.AncestorsOrSelf(level).OfType(); + return content.AncestorsOrSelf(maxLevel).OfType(); } + /// + /// Gets the ancestor of the content, ie its parent. + /// + /// The content. + /// The ancestor of the content. + /// This method is here for consistency purposes but does not make much sense. public static IPublishedContent Ancestor(this IPublishedContent content) { return content.Parent; } - public static IPublishedContent Ancestor(this IPublishedContent content, int level) + /// + /// Gets the nearest ancestor of the content, at a lever lesser or equal to a specified level. + /// + /// The content. + /// The level. + /// The nearest (in down-top order) ancestor of the content, at a level lesser or equal to the specified level. + /// Does not consider the content itself. May return null. + public static IPublishedContent Ancestor(this IPublishedContent content, int maxLevel) { - return content.EnumerateAncestors(false).FirstOrDefault(x => x.Level <= level); + return content.EnumerateAncestors(false).FirstOrDefault(x => x.Level <= maxLevel); } + /// + /// Gets the nearest ancestor of the content, of a specified content type. + /// + /// The content. + /// The content type alias. + /// The nearest (in down-top order) ancestor of the content, of the specified content type. + /// Does not consider the content itself. May return null. public static IPublishedContent Ancestor(this IPublishedContent content, string contentTypeAlias) { return content.EnumerateAncestors(false).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); } + /// + /// Gets the nearest ancestor of the content, of a specified content type. + /// + /// The content type. + /// The content. + /// The nearest (in down-top order) ancestor of the content, of the specified content type. + /// Does not consider the content itself. May return null. public static T Ancestor(this IPublishedContent content) where T : class, IPublishedContent { - return content.Ancestor() as T; + return content.Ancestors().FirstOrDefault(); } - public static T Ancestor(this IPublishedContent content, int level) + /// + /// Gets the nearest ancestor of the content, at the specified level and of the specified content type. + /// + /// The content type. + /// The content. + /// The level. + /// The ancestor of the content, at the specified level and of the specified content type. + /// Does not consider the content itself. If the ancestor at the specified level is + /// not of the specified type, returns null. + public static T Ancestor(this IPublishedContent content, int maxLevel) where T : class, IPublishedContent { - return content.Ancestor(level) as T; + return content.Ancestors(maxLevel).FirstOrDefault(); } - // note: that one makes no sense and should return self -- but fixing that - // would be a breaking change. Defining FIX_AXES would fix the situation. + /// + /// Gets the content or its nearest ancestor. + /// + /// The content. + /// The content. + /// This method is here for consistency purposes but does not make much sense. public static IPublishedContent AncestorOrSelf(this IPublishedContent content) { -#if FIX_AXES return content; -#else - return content.EnumerateAncestors(true).FirstOrDefault(x => x.Level == 1); -#endif } - public static IPublishedContent AncestorOrSelf(this IPublishedContent content, int level) + /// + /// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level. + /// + /// The content. + /// The level. + /// The content or its nearest (in down-top order) ancestor, at a level lesser or equal to the specified level. + /// May or may not return the content itself depending on its level. May return null. + public static IPublishedContent AncestorOrSelf(this IPublishedContent content, int maxLevel) { - return content.EnumerateAncestors(true).FirstOrDefault(x => x.Level <= level); + return content.EnumerateAncestors(true).FirstOrDefault(x => x.Level <= maxLevel); } + /// + /// Gets the content or its nearest ancestor, of a specified content type. + /// + /// The content. + /// The content type. + /// The content or its nearest (in down-top order) ancestor, of the specified content type. + /// May or may not return the content itself depending on its content type. May return null. public static IPublishedContent AncestorOrSelf(this IPublishedContent content, string contentTypeAlias) { return content.EnumerateAncestors(true).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); } + /// + /// Gets the content or its nearest ancestor, of a specified content type. + /// + /// The content type. + /// The content. + /// The content or its nearest (in down-top order) ancestor, of the specified content type. + /// May or may not return the content itself depending on its content type. May return null. public static T AncestorOrSelf(this IPublishedContent content) where T : class, IPublishedContent { - return content.AncestorOrSelf() as T; + return content.AncestorsOrSelf().FirstOrDefault(); } - public static T AncestorOrSelf(this IPublishedContent content, int level) + /// + /// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level, and of a specified content type. + /// + /// The content type. + /// The content. + /// The level. + /// + public static T AncestorOrSelf(this IPublishedContent content, int maxLevel) where T : class, IPublishedContent { - return content.AncestorOrSelf(level) as T; + return content.AncestorsOrSelf(maxLevel).FirstOrDefault(); } - // broken until we defined FIX_AXES internal static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, Func func) { -#if FIX_AXES - return content.EnumerateAncestors(orSelf).Where(x => func == null || func(x)); -#else - var ancestors = new List(); - - if (orSelf && (func == null || func(content))) - ancestors.Add(content); - - while (content.Level > 1) // while we have a parent, consider the parent - { - content = content.Parent; - - if ((func == null || func(content))) - ancestors.Add(content); - } - - ancestors.Reverse(); - return ancestors; -#endif + var ancestorsOrSelf = content.EnumerateAncestors(orSelf); + return func == null ? ancestorsOrSelf : ancestorsOrSelf.Where(func); } + /// + /// Enumerates ancestors of the content, bottom-up. + /// + /// The content. + /// Indicates whether the content should be included. + /// Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc). internal static IEnumerable EnumerateAncestors(this IPublishedContent content, bool orSelf) { if (orSelf) yield return content; @@ -1111,10 +1229,6 @@ namespace Umbraco.Web // - every node occurs before all of its children and descendants. // - the relative order of siblings is the order in which they occur in the children property of their parent node. // - children and descendants occur before following siblings. - - // SO, here we want to implement a depth-first enumeration of children. Which is what EnumerateDescendants does, but NOT what - // DescendantsOrSelf does, so basically descendants are NOT XPath-compliant in Umbraco at the moment -- but fixing that - // would be a breaking change. Defining FIX_AXES would fix the situation. public static IEnumerable Descendants(this IPublishedContent content) { @@ -1224,24 +1338,9 @@ namespace Umbraco.Web return content.DescendantOrSelf(level) as T; } - // broken until we defined FIX_AXES internal static IEnumerable DescendantsOrSelf(this IPublishedContent content, bool orSelf, Func func) { -#if FIX_AXES return content.EnumerateDescendants(orSelf).Where(x => func == null || func(x)); -#else - var init = (orSelf && (func == null || func(content))) ? new[] { content } : new IPublishedContent[] { }; - - var descendants = init - .Union(content.Children - .FlattenList(x => x.Children) - .Where(x => func == null || func(x)) - ) - .OrderBy(x => x.Level) - .ThenBy(x => x.SortOrder); - - return descendants; -#endif } internal static IEnumerable EnumerateDescendants(this IPublishedContent content, bool orSelf) @@ -1279,9 +1378,6 @@ namespace Umbraco.Web { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); -#if (!FIX_AXES) - number += 1; // legacy is zero-based ie zero == parent -#endif return number == 0 ? content : content.EnumerateAncestors(false).Skip(number).FirstOrDefault(); } @@ -1305,9 +1401,6 @@ namespace Umbraco.Web { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); -#if (!FIX_AXES) - number += 1; // legacy is zero-based ie zero == first child -#endif if (number == 0) return content; content = content.Children.FirstOrDefault(); @@ -1348,9 +1441,6 @@ namespace Umbraco.Web { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); -#if (!FIX_AXES) - number += 1; // legacy is zero-based ie zero == next, whereas zero should be current -#endif return number == 0 ? content : content.ContentSet.ElementAtOrDefault(content.GetIndex() + number); } @@ -1418,10 +1508,6 @@ namespace Umbraco.Web { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); -#if (!FIX_AXES) - number = -number; // legacy wants negative numbers, should be positive - number += 1; // legacy is zero-based ie zero == previous, whereas zero should be current -#endif return number == 0 ? content : content.ContentSet.ElementAtOrDefault(content.GetIndex() - number); }