diff --git a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs index 1117a57592..1ab925e8f1 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs @@ -56,42 +56,45 @@ namespace Umbraco.Web.Routing // 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 + // these should probably be taken care of earlier on + + // TODO + // can we normalize the values so that they contain no whitespaces, and no leading slashes? + // and then the comparisons in IsMatch can be way faster - and allocate way less strings - alias = alias.TrimStart('/'); - var xpathBuilder = new StringBuilder(); - xpathBuilder.Append(XPathStrings.Root); + const string propertyAlias = "umbracoUrlAlias"; + + var test1 = alias.TrimStart('/') + ","; + var test2 = ",/" + test1; // test2 is ",/alias," + test1 = "," + test1; // test1 is ",alias," + + bool IsMatch(IPublishedContent c, string a1, string a2) + { + // this basically implements the original XPath query ;-( + // + // "//* [@isDoc and (" + + // "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" + + // " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" + + // ")]" + + if (!c.HasProperty(propertyAlias)) return false; + var v = "," + c.Value(propertyAlias).Replace(" ", "") + ","; + return v.Contains(a1) || v.Contains(a2); + } 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"; + var rootNode = cache.GetById(rootNodeId); + return rootNode?.Descendants().FirstOrDefault(x => IsMatch(x, test1, test2)); } - xpathBuilder.AppendFormat(XPathStrings.DescendantDocumentByAlias, alias); - var xpath = xpathBuilder.ToString(); + foreach (var rootContent in cache.GetAtRoot()) + { + var c = rootContent.DescendantsOrSelf().FirstOrDefault(x => IsMatch(x, test1, test2)); + if (c != null) return c; + } - // note: it's OK if var is null, will be ignored - return cache.GetSingleByXPath(xpath, var); + return null; } - - #region XPath Strings - - static class XPathStrings - { - public static string Root => "/root"; - public const string DescendantDocumentById = "//* [@isDoc and @id={0}]"; - public const string DescendantDocumentByAlias = "//* [@isDoc and (" - + "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" - + " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" - + ")]"; - } - - #endregion } }