Wednesday, 16 May 2007
Link database to replace XPath statements
Very often I notice developers who are trying to use XPath or Sitecore query statements to resolve referrals for an item, - for example to resolve which documents are using a certain Meta data field. This is absolutely feasible, and with small data structures even a reasonable approach, but with larger data structures, XPath queries that iterates large part of the structure, something that hurts performance.
Let’s assume the following scenario:
We have the following content structure:

Every single document in the content structure is being categorized by a Meta data field, e.g. a drop down field with data from the document category.
Let’s assume the solution has a web control in the right hand side display “other documents of this category”, using the meta data to look up these controls. In reality, what we want to do in this control is to take the current documents category, - then find the documents that refer the category, e.g. if “Find us” has a company information tag, we want to find other documents that have that too.
Using XSLT
With small data structures, this can be done from XSLT by using the following code:
<xsl:variable name="docCategoryItem"
select="sc:item(sc:fld('Category',.),.)" />
<xsl:variable name="ItemsLinkingToThisCategory"
select="$home//item[sc:fld('Category',.)=$docCategoryItem/@id]" />
<h2>Other documents in the category '
<xsl:value-of select="$docCategoryItem/@name"/>':</h2>
<xsl:for-each
select="$ItemsLinkingToThisCategory[@id!=$sc_currentitem/@id]">
<p><sc:link><sc:text field="title" /></sc:link></p>
</xsl:for-each>
This will result in full content structure iteration every time you load a document, which is fine with small data structures, but will hurt performance fatally with large data structures.
Using Sitecore query is faster, - but what happens if you are referring multiple categories? For every category, a full structure scan would be performed.
So, how can we easily resolve referrers?
Link database
The answer lies in the name itself. To find all referrers, use the link database which maintains all ingoing and outgoing links from all standard lookup types and html types. If you want to do it from XSLT, build a XSL helper class that returns an XPathNodeIterator.
I have created a class testxslhelper, with the following code in a traditional Class Library project using Visual Studio:
using System.Xml.XPath;
using Sitecore;
using Sitecore.Data.Items;
using Sitecore.Links;
using Sitecore.Xml;
namespace Sitecore
{
public class references
{
public Item getItem(XPathNodeIterator xpni) {
xpni.MoveNext();
return (Context.Database.GetItem(
xpni.Current.GetAttribute("id", "")));
}
public XPathNodeIterator referrersfiltered(
XPathNodeIterator xpni, string template) {
template = template.ToLower();
Item currentitem = getItem(xpni);
ItemLink[] links =
Globals.LinkDatabase.GetReferers(currentitem);
Packet packet = new Packet("references");
foreach (ItemLink link in links)
if (link != null) {
Item sourceitem = link.GetSourceItem();
if (sourceitem != null) {
if ((sourceitem.TemplateName.ToLower() == template) &&
(sourceitem.Parent.ID != ItemIDs.MastersRoot))
packet.AddElement("item", link.SourceItemID.ToString());
}
}
return packet.XmlDocument.CreateNavigator().Select("*");
}
}
}
To use this method, you should add it to your web.config XSLT extensions section:
<xslExtensions>
<extension mode="on"
type="Sitecore.Xml.Xsl.XslHelper, Sitecore.Kernel"
namespace="http://www.sitecore.net/sc"
singleInstance="true">
</extension>
:
<extension mode="on"
type="Sitecore.references, miscXSLHelper"
namespace="http://www.sitecore.net/refs"
singleInstance="true">
</extension>
</xslExtensions>
Then apply it to your stylesheet definition:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sc="http://www.sitecore.net/sc"
xmlns:dot=http://www.sitecore.net/dot
xmlns:refs=http://www.sitecore.net/refs
exclude-result-prefixes="dot sc refs">
Finally, use the new method from your XSLT:
<xsl:template match="*" mode="getLinkRefs">
<xsl:variable name="docCategoryItem"
select="sc:item(sc:fld('Category',.),.)" />
<xsl:variable name="refs"
select="test:referrersfiltered(
$docCategoryItem,'sample item')/item" />
<h2>Other documents in the category '
<xsl:value-of select="$docCategoryItem/@name"/>':
</h2>
<xsl:for-each select="$refs">
<xsl:variable name="refitem"
select="sc:item(.,$sc_currentitem)" />
<xsl:if test="$refitem/@id!=$sc_currentitem/@id">
<p>
<sc:link select="$refitem">
<sc:text field="title" select="$refitem" />
</sc:link>
</p>
</xsl:if>
</xsl:for-each>
</xsl:template>
With small data structures, the difference between direct XSLT XPath lookup and the link database index lookup is minimal (e.g. a site with only 50 nodes), but even there I can measure a difference. If your content structure is more than 200 documents I would recommend that you consider using the second method.
Source: referncesource.zip
11:05 Posted in Sitecore | Permalink | Comments (0) | Email this | Tags: Sitecore, Link database, XSLT
Tuesday, 20 February 2007
XSLT Best practices: ancestor-or-self vs. descendants-or-self
I’ve been following a thread on our forum, XSL processing correctly in preview mode but not on the live website (requires registration), where a question was raised on how to find a landing page through XSLT. We had this XML scenario:
Sitecore
Content
Home [@template = ‘homeitem’]
Section A [@template=’section]
Document 1 [@template=’document’]
Document 2 [@template=’document’]
Section B [@template=’section]
Document 3 [@template=’document’]
Document 4 [@template=’document’]
Document 5 [@template=’document’]
Document 6 [@template=’document’]
Document 7 [@template=’document’]
The challenge was to find the section item depending on the current node. For example, if my current node was Document 5, the question was how to find the item based on template type section which was residing in the branch. In this example, it should have returned Section B.
The developer was almost there with this XPath:
<xsl:variable name=”home”
select=”/*/item[@key=’content’]/item[@key=’home’]” />
<xsl:variable name=”sectionitem”
select=”$home/item[descendant-or-self::item=current()]” />
This method seemed to work fine in some instances, but failed sometimes. Why was this?
We soon figured out it was the node comparison that failed: Comparing item=current() matches the entire node structure of these nodes, and even though the above data structure looks simple, the real data structure contains several nodes and attributes per item (e.g. versions, internal references and whatever makes up a Sitecore item xml).
Another problem is this comparison can be extremely slow, depending on the size of the data structure: When comparing node B with the current node, these entire structures, with items, children and attributes is compared.
Instead of using this approach, comparison should match for attributes:
<xsl:variable name=”sectionitem”
select=”$home/descendent-or-self::item[@id = current()/@id]” />
In the above example, only the attribute of the item is matched. I would estimate the match to be somewhere around 10-1000 times faster, naturally depending on the size of the data structure.
Now, another developer on the forum, Ben Golden gave another observation on optimization:
By using the above (well functioning, faster, but still slow) XPath, we would scan the entire data structure (match this item, then descendants, to find the top section node), in this case matching 9 items). Why not use current position (currentitem) to find the matching ancestor, instead of using the home node and matching on all underlying items?
E.g.something like:
<xsl:variable name="sectionitem" select="ancestor-or-self::item[@template='landingpage']" />
Naturally Ben is right. When current node would be Document 5, we would only match for 6 items. And with a larger structure, this number of comparisons would not increase while the descendants approach would.
Lessons learned:
- Always match on attributes!
- Always try to find an ancestor-of-self alternative for descendants-or-self when searching for a specific node!
19:32 Posted in Sitecore | Permalink | Comments (0) | Email this | Tags: Sitecore, XSLT


