Macros - support navigable caches

This commit is contained in:
Stephan
2013-09-06 22:05:16 +02:00
parent f63514e8ea
commit cf435011df
5 changed files with 1309 additions and 11 deletions

View File

@@ -805,6 +805,7 @@
<Compile Include="WriteLock.cs" />
<Compile Include="XmlExtensions.cs" />
<Compile Include="XmlHelper.cs" />
<Compile Include="Xml\XPath\MacroNavigator.cs" />
<Compile Include="Xml\XPath\INavigableContentType.cs" />
<Compile Include="Xml\XPath\NavigableNavigator.cs" />
<Compile Include="Xml\XPath\INavigableContent.cs" />

File diff suppressed because it is too large Load Diff

View File

@@ -594,6 +594,143 @@ namespace Umbraco.Tests.CoreXml
Assert.IsFalse(nav.MoveToId("2"));
}
[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void XsltDebugModeAndSortOrder(bool native, bool debug)
{
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<root>
<node id=""1"" isDoc=""1"">
<title>title-1</title>
<node id=""3"" isDoc=""1"">
<title>title-3</title>
<node id=""7"" isDoc=""1"">
<title>title-7</title>
</node>
<node id=""8"" isDoc=""1"">
<title>title-8</title>
</node>
</node>
<node id=""5"" isDoc=""1"">
<title>title-5</title>
</node>
</node>
<node id=""2"" isDoc=""1"">
<title>title-2</title>
<node id=""4"" isDoc=""1"">
<title>title-4</title>
</node>
<node id=""6"" isDoc=""1"">
<title>title-6</title>
</node>
</node>
</root>
";
const string xslt = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY nbsp ""&#x00A0;"">
]>
<xsl:stylesheet
version=""1.0""
xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
xmlns:msxml=""urn:schemas-microsoft-com:xslt""
xmlns:umbraco.library=""urn:umbraco.library"" xmlns:Exslt.ExsltCommon=""urn:Exslt.ExsltCommon"" xmlns:Exslt.ExsltDatesAndTimes=""urn:Exslt.ExsltDatesAndTimes"" xmlns:Exslt.ExsltMath=""urn:Exslt.ExsltMath"" xmlns:Exslt.ExsltRegularExpressions=""urn:Exslt.ExsltRegularExpressions"" xmlns:Exslt.ExsltStrings=""urn:Exslt.ExsltStrings"" xmlns:Exslt.ExsltSets=""urn:Exslt.ExsltSets"" xmlns:Examine=""urn:Examine""
exclude-result-prefixes=""msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets Examine "">
<xsl:output method=""xml"" omit-xml-declaration=""yes"" />
<xsl:param name=""currentPage""/>
<xsl:template match=""/"">
<!-- <xsl:for-each select=""/root/* [@isDoc]""> -->
<!-- <xsl:for-each select=""$currentPage/root/* [@isDoc]""> -->
<xsl:for-each select=""/macro/nav/root/* [@isDoc]"">
<xsl:text>! </xsl:text><xsl:value-of select=""title"" /><xsl:text>
</xsl:text>
<xsl:for-each select=""./* [@isDoc]"">
<xsl:text>!! </xsl:text><xsl:value-of select=""title"" /><xsl:text>
</xsl:text>
<xsl:for-each select=""./* [@isDoc]"">
<xsl:text>!!! </xsl:text><xsl:value-of select=""title"" /><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
";
const string expected = @"! title-1
!! title-3
!!! title-7
!!! title-8
!! title-5
! title-2
!! title-4
!! title-6
";
// see http://www.onenaught.com/posts/352/xslt-performance-tip-dont-indent-output
// why aren't we using an XmlWriter here?
var transform = new XslCompiledTransform(debug);
var xmlReader = new XmlTextReader(new StringReader(xslt))
{
EntityHandling = EntityHandling.ExpandEntities
};
var xslResolver = new XmlUrlResolver
{
Credentials = CredentialCache.DefaultCredentials
};
var args = new XsltArgumentList();
// .Default is more restrictive than .TrustedXslt
transform.Load(xmlReader, XsltSettings.Default, xslResolver);
XPathNavigator macro;
if (!native)
{
var source = new TestSource7();
var nav = new NavigableNavigator(source);
//args.AddParam("currentPage", string.Empty, nav.Clone());
var x = new XmlDocument();
x.LoadXml(xml);
macro = new MacroNavigator(new[]
{
// it even fails like that => macro nav. issue?
new MacroNavigator.MacroParameter("nav", x.CreateNavigator()) // nav.Clone())
}
);
}
else
{
var doc = new XmlDocument();
doc.LoadXml("<macro />");
var nav = doc.CreateElement("nav");
doc.DocumentElement.AppendChild(nav);
var x = new XmlDocument();
x.LoadXml(xml);
nav.AppendChild(doc.ImportNode(x.DocumentElement, true));
macro = doc.CreateNavigator();
}
var writer = new StringWriter();
transform.Transform(macro, args, writer);
// this was working with native, debug and non-debug
// this was working with macro nav, non-debug
// but was NOT working (changing the order of nodes) with macro nav, debug
// was due to an issue with macro nav IsSamePosition, fixed
//Console.WriteLine("--------");
//Console.WriteLine(writer.ToString());
Assert.AreEqual(expected, writer.ToString());
}
[Test]
public void WhiteSpacesAndEmptyValues()
{

View File

@@ -2085,4 +2085,4 @@
<!--<PostBuildEvent>xcopy "$(ProjectDir)..\..\lib\*.dll" "$(TargetDir)*.dll" /Y</PostBuildEvent>-->
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
</Project>
</Project>

View File

@@ -23,6 +23,7 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Xml.XPath;
using Umbraco.Core.Profiling;
using Umbraco.Web;
using Umbraco.Web.Cache;
@@ -859,21 +860,41 @@ namespace umbraco
using (DisposableTimer.DebugDuration<macro>("Executing XSLT: " + XsltFile))
{
XmlDocument macroXml = null;
MacroNavigator macroNavigator = null;
NavigableNavigator contentNavigator = null;
// get master xml document
var cache = UmbracoContext.Current.ContentCache.InnerCache as Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache;
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
XmlDocument umbracoXml = cache.GetXml(UmbracoContext.Current, UmbracoContext.Current.InPreviewMode);
macroXml = new XmlDocument();
macroXml.LoadXml("<macro/>");
foreach (var prop in macro.Model.Properties)
var canNavigate =
UmbracoContext.Current.ContentCache.XPathNavigatorIsNavigable &&
UmbracoContext.Current.MediaCache.XPathNavigatorIsNavigable;
if (!canNavigate)
{
AddMacroXmlNode(umbracoXml, macroXml, prop.Key, prop.Type, prop.Value);
// get master xml document
var cache = UmbracoContext.Current.ContentCache.InnerCache as Umbraco.Web.PublishedCache.XmlPublishedCache.PublishedContentCache;
if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported.");
XmlDocument umbracoXml = cache.GetXml(UmbracoContext.Current, UmbracoContext.Current.InPreviewMode);
macroXml = new XmlDocument();
macroXml.LoadXml("<macro/>");
foreach (var prop in macro.Model.Properties)
{
AddMacroXmlNode(umbracoXml, macroXml, prop.Key, prop.Type, prop.Value);
}
}
else
{
var parameters = new List<MacroNavigator.MacroParameter>();
contentNavigator = UmbracoContext.Current.ContentCache.GetXPathNavigator() as NavigableNavigator;
var mediaNavigator = UmbracoContext.Current.MediaCache.GetXPathNavigator() as NavigableNavigator;
foreach (var prop in macro.Model.Properties)
{
AddMacroParameter(parameters, contentNavigator, mediaNavigator, prop.Key, prop.Type, prop.Value);
}
macroNavigator = new MacroNavigator(parameters);
}
if (HttpContext.Current.Request.QueryString["umbDebug"] != null && GlobalSettings.DebugMode)
{
var outerXml = macroXml.OuterXml;
var outerXml = macroXml == null ? macroNavigator.OuterXml : macroXml.OuterXml;
return
new LiteralControl("<div style=\"border: 2px solid green; padding: 5px;\"><b>Debug from " +
macro.Name +
@@ -889,7 +910,9 @@ namespace umbraco
{
try
{
var transformed = GetXsltTransformResult(macroXml, xsltFile);
var transformed = canNavigate
? GetXsltTransformResult(macroNavigator, contentNavigator, xsltFile) // better?
: GetXsltTransformResult(macroXml, xsltFile); // document
var result = CreateControlsFromText(transformed);
return result;
@@ -1323,6 +1346,101 @@ namespace umbraco
macroXml.FirstChild.AppendChild(macroXmlNode);
}
// add parameters to the macro parameters collection
private void AddMacroParameter(ICollection<MacroNavigator.MacroParameter> parameters,
NavigableNavigator contentNavigator, NavigableNavigator mediaNavigator,
string macroPropertyAlias,string macroPropertyType, string macroPropertyValue)
{
// if no value is passed, then use the current "pageID" as value
var contentId = macroPropertyValue == string.Empty ? UmbracoContext.Current.PageId.ToString() : macroPropertyValue;
TraceInfo("umbracoMacro",
"Xslt node adding search start (" + macroPropertyAlias + ",'" +
macroPropertyValue + "')");
// beware! do not use the raw content- or media- navigators, but clones !!
switch (macroPropertyType)
{
case "contentTree":
parameters.Add(new MacroNavigator.MacroParameter(
macroPropertyAlias,
contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty
attributes: new Dictionary<string, string> { { "nodeID", contentId } }));
break;
case "contentPicker":
parameters.Add(new MacroNavigator.MacroParameter(
macroPropertyAlias,
contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty
0));
break;
case "contentSubs":
parameters.Add(new MacroNavigator.MacroParameter(
macroPropertyAlias,
contentNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty
1));
break;
case "contentAll":
parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, contentNavigator.Clone()));
break;
case "contentRandom":
var nav = contentNavigator.Clone();
if (nav.MoveToId(contentId))
{
var descendantIterator = nav.Select("./* [@isDoc]");
if (descendantIterator.MoveNext())
{
// not empty - and won't change
var descendantCount = descendantIterator.Count;
int index;
var r = library.GetRandom();
lock (r)
{
index = r.Next(descendantCount);
}
while (index > 0 && descendantIterator.MoveNext())
index--;
var node = descendantIterator.Current.UnderlyingObject as INavigableContent;
if (node != null)
{
nav = contentNavigator.CloneWithNewRoot(node.Id.ToString(CultureInfo.InvariantCulture));
parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, nav, 0));
}
else
throw new InvalidOperationException("Iterator contains non-INavigableContent elements.");
}
else
TraceWarn("umbracoMacro",
"Error adding random node - parent (" + macroPropertyValue +
") doesn't have children!");
}
else
TraceWarn("umbracoMacro",
"Error adding random node - parent (" + macroPropertyValue +
") doesn't exists!");
break;
case "mediaCurrent":
parameters.Add(new MacroNavigator.MacroParameter(
macroPropertyAlias,
mediaNavigator.CloneWithNewRoot(contentId), // null if not found - will be reported as empty
0));
break;
default:
parameters.Add(new MacroNavigator.MacroParameter(macroPropertyAlias, HttpContext.Current.Server.HtmlDecode(macroPropertyValue)));
break;
}
}
#endregion
/// <summary>