From 8c79977a2352560f8e3f7d4bd5eb731a02c6bf57 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 21 Mar 2013 08:54:25 -0100 Subject: [PATCH] Web - move GetByAlias out of PublishedCache --- src/Umbraco.Core/Xml/XmlNodeExtensions.cs | 48 ++++++++++ .../ContextualPublishedCache.cs | 15 ++- .../ContextualPublishedContentCache.cs | 8 +- .../PublishedCache/IPublishedContentCache.cs | 3 - .../LegacyXmlCache/PublishedContentCache.cs | 39 -------- .../Routing/ContentFinderByUrlAlias.cs | 96 ++++++++++++++++++- 6 files changed, 157 insertions(+), 52 deletions(-) diff --git a/src/Umbraco.Core/Xml/XmlNodeExtensions.cs b/src/Umbraco.Core/Xml/XmlNodeExtensions.cs index 53a7ef08db..2dbf8775f2 100644 --- a/src/Umbraco.Core/Xml/XmlNodeExtensions.cs +++ b/src/Umbraco.Core/Xml/XmlNodeExtensions.cs @@ -19,12 +19,36 @@ namespace Umbraco.Core.Xml return source.Select(expr); } + /// + /// Selects a list of XmlNode matching an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The list of XmlNode matching the XPath expression. + /// + /// If is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// The XPath expression should reference variables as $var. + /// public static XmlNodeList SelectNodes(this XmlNode source, string expression, IEnumerable variables) { var av = variables == null ? null : variables.ToArray(); return SelectNodes(source, expression, av); } + /// + /// Selects a list of XmlNode matching an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The list of XmlNode matching the XPath expression. + /// + /// If is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// The XPath expression should reference variables as $var. + /// public static XmlNodeList SelectNodes(this XmlNode source, string expression, params XPathVariable[] variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -34,12 +58,36 @@ namespace Umbraco.Core.Xml return XmlNodeListFactory.CreateNodeList(iterator); } + /// + /// Selects the first XmlNode that matches an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The first XmlNode that matches the XPath expression. + /// + /// If is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// The XPath expression should reference variables as $var. + /// public static XmlNode SelectSingleNode(this XmlNode source, string expression, IEnumerable variables) { var av = variables == null ? null : variables.ToArray(); return SelectSingleNode(source, expression, av); } + /// + /// Selects the first XmlNode that matches an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The first XmlNode that matches the XPath expression. + /// + /// If is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// The XPath expression should reference variables as $var. + /// public static XmlNode SelectSingleNode(this XmlNode source, string expression, params XPathVariable[] variables) { if (variables == null || variables.Length == 0 || variables[0] == null) diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs index d5da24106d..fbaf997a85 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Umbraco.Core.Models; +using Umbraco.Core.Xml; namespace Umbraco.Web.PublishedCache { @@ -45,7 +46,12 @@ namespace Umbraco.Web.PublishedCache /// The XPath query. /// Optional XPath variables. /// The content, or null. - public virtual IPublishedContent GetSingleByXPath(string xpath, Core.Xml.XPathVariable[] vars) + /// + /// If is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// The XPath expression should reference variables as $var. + /// + public virtual IPublishedContent GetSingleByXPath(string xpath, params XPathVariable[] vars) { return _cache.GetSingleByXPath(UmbracoContext, xpath, vars); } @@ -56,7 +62,12 @@ namespace Umbraco.Web.PublishedCache /// The XPath query. /// Optional XPath variables. /// The contents. - public virtual IEnumerable GetByXPath(string xpath, Core.Xml.XPathVariable[] vars) + /// + /// If is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// The XPath expression should reference variables as $var. + /// + public virtual IEnumerable GetByXPath(string xpath, params XPathVariable[] vars) { return _cache.GetByXPath(UmbracoContext, xpath, vars); } diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs index 03a7632512..bdbd0265b5 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.PublishedCache /// Gets content identified by a route. /// /// The route - /// FIXME + /// A value forcing the HideTopLevelNode setting. /// The content, or null. /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg 123/foo/bar/nil. public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null) @@ -51,11 +51,5 @@ namespace Umbraco.Web.PublishedCache { return _cache.GetRouteById(UmbracoContext, contentId); } - - // FIXME do we want that one here? - public IPublishedContent GetByUrlAlias(int rootNodeId, string alias) - { - return _cache.GetByUrlAlias(UmbracoContext, rootNodeId, alias); - } } } diff --git a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs index f2898f7c68..3745271e43 100644 --- a/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/IPublishedContentCache.cs @@ -29,8 +29,5 @@ namespace Umbraco.Web.PublishedCache /// The content unique identifier. /// The route. string GetRouteById(UmbracoContext umbracoContext, int contentId); - - // FIXME do we want that one? - IPublishedContent GetByUrlAlias(UmbracoContext umbracoContext, int rootNodeId, string alias); } } diff --git a/src/Umbraco.Web/PublishedCache/LegacyXmlCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/LegacyXmlCache/PublishedContentCache.cs index 43cae3f61c..5cf74137c6 100644 --- a/src/Umbraco.Web/PublishedCache/LegacyXmlCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/LegacyXmlCache/PublishedContentCache.cs @@ -196,7 +196,6 @@ namespace Umbraco.Web.PublishedCache.LegacyXmlCache public static string Root { get { return "/root"; } } public string RootDocuments { get; private set; } public string DescendantDocumentById { get; private set; } - public string DescendantDocumentByAlias { get; private set; } public string ChildDocumentByUrlName { get; private set; } public string ChildDocumentByUrlNameVar { get; private set; } public string RootDocumentWithLowestSortOrder { get; private set; } @@ -211,10 +210,6 @@ namespace Umbraco.Web.PublishedCache.LegacyXmlCache case 0: RootDocuments = "/root/node"; DescendantDocumentById = "//node [@id={0}]"; - DescendantDocumentByAlias = "//node[(" - + "contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',/{0},')" - + ")]"; ChildDocumentByUrlName = "/node [@urlName='{0}']"; ChildDocumentByUrlNameVar = "/node [@urlName=${0}]"; RootDocumentWithLowestSortOrder = "/root/node [not(@sortOrder > ../node/@sortOrder)][1]"; @@ -224,10 +219,6 @@ namespace Umbraco.Web.PublishedCache.LegacyXmlCache case 1: RootDocuments = "/root/* [@isDoc]"; DescendantDocumentById = "//* [@isDoc and @id={0}]"; - DescendantDocumentByAlias = "//* [@isDoc and (" - + "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" - + ")]"; ChildDocumentByUrlName = "/* [@isDoc and @urlName='{0}']"; ChildDocumentByUrlNameVar = "/* [@isDoc and @urlName=${0}]"; RootDocumentWithLowestSortOrder = "/root/* [@isDoc and not(@sortOrder > ../* [@isDoc]/@sortOrder)][1]"; @@ -307,36 +298,6 @@ namespace Umbraco.Web.PublishedCache.LegacyXmlCache : xml.SelectNodes(xpath, vars); return ConvertToDocuments(nodes); } - - // FIXME MOVE THAT ONE OUT OF HERE? - public IPublishedContent GetByUrlAlias(UmbracoContext umbracoContext, int rootNodeId, string alias) - { - if (alias == null) throw new ArgumentNullException("alias"); - - // the alias may be "foo/bar" or "/foo/bar" - // there may be spaces as in "/foo/bar, /foo/nil" - // these should probably be taken care of earlier on - - alias = alias.TrimStart('/'); - var xpathBuilder = new StringBuilder(); - xpathBuilder.Append(XPathStringsDefinition.Root); - - if (rootNodeId > 0) - xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentById, rootNodeId); - - XPathVariable var = null; - if (alias.Contains('\'') || alias.Contains('"')) - { - // use a var, escaping gets ugly pretty quickly - var = new XPathVariable("alias", alias); - alias = "$alias"; - } - xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentByAlias, alias); - - var xpath = xpathBuilder.ToString(); - - return GetSingleByXPath(umbracoContext, xpath, var); - } public bool HasContent() { diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs index 9c8652d1d6..05e5b2c168 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs @@ -1,6 +1,11 @@ +using System; +using System.Text; +using System.Linq; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core; +using Umbraco.Core.Xml; +using Umbraco.Web.PublishedCache; namespace Umbraco.Web.Routing { @@ -24,7 +29,7 @@ namespace Umbraco.Web.Routing if (docRequest.Uri.AbsolutePath != "/") // no alias if "/" { - node = docRequest.RoutingContext.UmbracoContext.ContentCache.GetByUrlAlias( + node = FindContentByAlias(docRequest.RoutingContext.UmbracoContext.ContentCache, docRequest.HasDomain ? docRequest.Domain.RootNodeId : 0, docRequest.Uri.GetAbsolutePathDecoded()); @@ -37,5 +42,94 @@ namespace Umbraco.Web.Routing return node != null; } + + private static IPublishedContent FindContentByAlias(ContextualPublishedContentCache cache, int rootNodeId, string alias) + { + if (alias == null) throw new ArgumentNullException("alias"); + + // the alias may be "foo/bar" or "/foo/bar" + // there may be spaces as in "/foo/bar, /foo/nil" + // these should probably be taken care of earlier on + + alias = alias.TrimStart('/'); + var xpathBuilder = new StringBuilder(); + xpathBuilder.Append(XPathStringsDefinition.Root); + + if (rootNodeId > 0) + xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentById, rootNodeId); + + XPathVariable var = null; + if (alias.Contains('\'') || alias.Contains('"')) + { + // use a var, as escaping gets ugly pretty quickly + var = new XPathVariable("alias", alias); + alias = "$alias"; + } + xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentByAlias, alias); + + var xpath = xpathBuilder.ToString(); + + // note: it's OK if var is null, will be ignored + return cache.GetSingleByXPath(xpath, var); + } + + #region XPath Strings + + class XPathStringsDefinition + { + public int Version { get; private set; } + + public static string Root { get { return "/root"; } } + public string DescendantDocumentById { get; private set; } + public string DescendantDocumentByAlias { get; private set; } + + public XPathStringsDefinition(int version) + { + Version = version; + + switch (version) + { + // legacy XML schema + case 0: + DescendantDocumentById = "//node [@id={0}]"; + DescendantDocumentByAlias = "//node[(" + + "contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',{0},')" + + " or contains(concat(',',translate(data [@alias='umbracoUrlAlias'], ' ', ''),','),',/{0},')" + + ")]"; + break; + + // default XML schema as of 4.10 + case 1: + DescendantDocumentById = "//* [@isDoc and @id={0}]"; + DescendantDocumentByAlias = "//* [@isDoc and (" + + "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" + + " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" + + ")]"; + break; + + default: + throw new Exception(string.Format("Unsupported Xml schema version '{0}').", version)); + } + } + } + + static XPathStringsDefinition _xPathStringsValue; + static XPathStringsDefinition XPathStrings + { + get + { + // in theory XPathStrings should be a static variable that + // we should initialize in a static ctor - but then test cases + // that switch schemas fail - so cache and refresh when needed, + // ie never when running the actual site + + var version = global::umbraco.UmbracoSettings.UseLegacyXmlSchema ? 0 : 1; + if (_xPathStringsValue == null || _xPathStringsValue.Version != version) + _xPathStringsValue = new XPathStringsDefinition(version); + return _xPathStringsValue; + } + } + + #endregion } } \ No newline at end of file