“There is no software engineering problem that cannot be solved by adding another layer of indirection.” (Brian Warboys, ICL Chief Engineer, c.1980)
I wrote this after the following chapter, but wish I had read it beforehand.
In attempting to get my changes to the events.xslt to work properly I found some difficulty getting the hang of what was going on when the XSLT was run. Here are some hints and tips I came across:
- The XSLT processes (transforms) an XML file which represents the hierarchy of pages (nodes) forming the Umbraco content tree.
- There are two levels of description going on here, and it does not help that they both use the term “node” to describe the objects within their own tree.
- At the Umbraco content level a “node” is a document or other item in the content tree. A node here has a number of fields, each having a Name, Alias, Value, etc., plus a number of child nodes.
- At the XML level there is a “node” containing the details of each Umbraco node (Id, Name, Type, etc.), but also a set of “nodes” one describing each data field belonging to that Umbraco Node.
Thus to return the value of a field within an Umbraco node, one has to select the XML node representing the Umbraco node, select the XML node below that representing the field one wants, and then return the value of that.
- This structure is laid out in the Book on XSLT Basics, and specifically at http://www.umbraco.org/documentation/books/xslt-basics/understanding-currentpage.
- When writing XSLT one has to be explicit about where to get the XML to work on from. It does not seem just to assume that it has the file representing the content tree. Thus one has to start a selection with one of the following (or maybe others I have not found yet:
- $currentPage. Points to the page from which the XSLT was originally called.
- umbraco.library function such as GetXmlAll(). This in particular returns the root node of the XML file representing the content tree.
- A variable previously set up in the script to point to a specific node.
Thus, in order to find the Home Page, one cannot write for example
<xsl:for-each select="//node[@level='1']">
since that has got nothing to work on. One has to use something like:
<xsl:for-each select="$currentPage/ancestor-or-self::root/node">
The XPath statement here goes as follows:
- Start at the current node
- Take the set of all nodes above this, plus the starting node
- Pick out all those whose XML tag is “root”. There is only one, at the top.
- Pick all those with XML tag “node” (representing an Umbraco Node) immediately below the root.
- When looking at XPath statements in particular one must remember that elements such as “root”, “node” and “data” in e.g.
<xsl:for-each select="$currentPage/ancestor::root/node [string(./data[@alias='umbracoNaviHide']) != '1']">
are not XSLT/XPath/XML primitives, but tag types defined by the Umbraco designers for their particular schema of XML file. See http://www.umbraco.org/documentation/books/xslt-basics/understanding-currentpage again.
- A very useful operation is to be able to check if a field exists, since this may be a better way to select the nodes one wants than checking their nodeTypes, and is also needed to protect against sending null values to functions that don’t like it. This is done by seeing if their string value is an empty string. E.g.
<xsl:if test="string($currentPage/data[@alias='eventEndDate']) != ''">
- Somewhat counter-intuitively the xsl element <xsl:value-of … > sends to the output stream not just the content (value) of the current XML node, but a concatenation of this with all of its child nodes. This does not matter too much in practice, since the only type of node I have so far found that has an interesting value is
<data alias=”fieldname”>field value</data>
which represents an (Umbraco) field value, and has no child nodes to worry about. It can however be confusing when trying to understand what is going on.