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.
///