diff --git a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs index 51b387b981..3839327e9c 100644 --- a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs +++ b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs @@ -19,7 +19,34 @@ namespace Umbraco.Core.Xml if (variables == null || variables.Length == 0 || variables[0] == null) return navigator.Select(expression); - var compiled = navigator.Compile(expression); + // Reflector shows that the standard XPathNavigator.Compile method just does + // return XPathExpression.Compile(xpath); + // only difference is, XPathNavigator.Compile is virtual so it could be overriden + // by a class inheriting from XPathNavigator... there does not seem to be any + // doing it in the Framework, though... so we'll assume it's much cleaner to use + // the static compile: + var compiled = XPathExpression.Compile(expression); + + var context = new DynamicContext(); + foreach (var variable in variables) + context.AddVariable(variable.Name, variable.Value); + compiled.SetContext(context); + return navigator.Select(compiled); + } + + /// + /// Selects a node set, using the specified XPath expression. + /// + /// A source XPathNavigator. + /// An XPath expression. + /// A set of XPathVariables. + /// An iterator over the nodes matching the specified expression. + public static XPathNodeIterator Select(this XPathNavigator navigator, XPathExpression expression, params XPathVariable[] variables) + { + if (variables == null || variables.Length == 0 || variables[0] == null) + return navigator.Select(expression); + + var compiled = expression.Clone(); // clone for thread-safety var context = new DynamicContext(); foreach (var variable in variables) context.AddVariable(variable.Name, variable.Value); diff --git a/src/Umbraco.Core/Xml/XmlNodeExtensions.cs b/src/Umbraco.Core/Xml/XmlNodeExtensions.cs index e6021f28d6..1af6ee0983 100644 --- a/src/Umbraco.Core/Xml/XmlNodeExtensions.cs +++ b/src/Umbraco.Core/Xml/XmlNodeExtensions.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Xml; +using System.Xml.XPath; // source: mvpxml.codeplex.com @@ -29,6 +30,24 @@ namespace Umbraco.Core.Xml 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, XPathExpression expression, IEnumerable variables) + { + var av = variables == null ? null : variables.ToArray(); + return SelectNodes(source, expression, av); + } + /// /// Selects a list of XmlNode matching an XPath expression. /// @@ -50,6 +69,27 @@ namespace Umbraco.Core.Xml return XmlNodeListFactory.CreateNodeList(iterator); } + /// + /// 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, XPathExpression expression, params XPathVariable[] variables) + { + if (variables == null || variables.Length == 0 || variables[0] == null) + return source.SelectNodes(expression); + + var iterator = source.CreateNavigator().Select(expression, variables); + return XmlNodeListFactory.CreateNodeList(iterator); + } + /// /// Selects the first XmlNode that matches an XPath expression. /// @@ -67,7 +107,25 @@ namespace Umbraco.Core.Xml 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, XPathExpression expression, IEnumerable variables) + { + var av = variables == null ? null : variables.ToArray(); + return SelectSingleNode(source, expression, av); + } + /// /// Selects the first XmlNode that matches an XPath expression. /// @@ -87,5 +145,25 @@ namespace Umbraco.Core.Xml return SelectNodes(source, expression, variables).Cast().FirstOrDefault(); } - } + + /// + /// 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, XPathExpression expression, params XPathVariable[] variables) + { + if (variables == null || variables.Length == 0 || variables[0] == null) + return source.SelectSingleNode(expression); + + return SelectNodes(source, expression, variables).Cast().FirstOrDefault(); + } + } } diff --git a/src/Umbraco.Tests/CoreXml/FrameworkXmlTests.cs b/src/Umbraco.Tests/CoreXml/FrameworkXmlTests.cs index 7ac4af2d20..a7cb581eed 100644 --- a/src/Umbraco.Tests/CoreXml/FrameworkXmlTests.cs +++ b/src/Umbraco.Tests/CoreXml/FrameworkXmlTests.cs @@ -213,5 +213,27 @@ namespace Umbraco.Tests.CoreXml Assert.AreEqual(5, count); } + + [Test] + public void OldFrameworkXPathBugIsFixed() + { + // see http://bytes.com/topic/net/answers/177129-reusing-xpathexpression-multiple-iterations + + var doc = new XmlDocument(); + doc.LoadXml(""); + + var nav = doc.CreateNavigator(); + var expr = nav.Compile("*"); + + nav.MoveToFirstChild(); //root + var iter1 = nav.Select(expr); + iter1.MoveNext(); //root/a + var iter2 = iter1.Current.Select(expr); + iter2.MoveNext(); // /root/a/a1 + iter2.MoveNext(); // /root/a/a2 + + // used to fail because iter1 and iter2 would conflict + Assert.IsTrue(iter1.MoveNext()); //root/b + } } } diff --git a/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs b/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs index 2a75c79c14..b2fb9089c4 100644 --- a/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs +++ b/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Xml.XPath; using Umbraco.Core.Dynamics; using Umbraco.Core.Xml; using Umbraco.Web.Models; @@ -36,6 +37,19 @@ namespace Umbraco.Web return content == null ? new DynamicNull() : new DynamicPublishedContent(content).AsDynamic(); } + /// + /// Gets a dynamic content resulting from an XPath query. + /// + /// The contextual cache. + /// The XPath query. + /// Optional XPath variables + /// The dynamic content, or null. + public static dynamic GetDynamicSingleByXPath(this ContextualPublishedContentCache cache, XPathExpression xpath, params XPathVariable[] vars) + { + var content = cache.GetSingleByXPath(xpath, vars); + return content == null ? new DynamicNull() : new DynamicPublishedContent(content).AsDynamic(); + } + /// /// Gets dynamic contents resulting from an XPath query. /// @@ -49,6 +63,19 @@ namespace Umbraco.Web return new DynamicPublishedContentList(content.Select(c => new DynamicPublishedContent(c))); } + /// + /// Gets dynamic contents resulting from an XPath query. + /// + /// The contextual cache. + /// The XPath query. + /// Optional XPath variables + /// The dynamic contents. + public static dynamic GetDynamicByXPath(this ContextualPublishedContentCache cache, XPathExpression xpath, params XPathVariable[] vars) + { + var content = cache.GetByXPath(xpath, vars); + return new DynamicPublishedContentList(content.Select(c => new DynamicPublishedContent(c))); + } + /// /// Gets dynamic contents at root. ///