Dominic Cronin's weblog
Parameter type quirks of the XSLT mediator
Today I was working on a template with an XSLT building block. I'd added a parameter to the package further up, and expected to use it simply by having an <xsl:param/> element with a matching name. Instead I got the error message you can see in the screencap below... Value cannot be null, Parameter name: parameter.
So what's going on here? Well I had a bit of a dig... (obviously by using my secret powers, and nothing as humdrum as technology) and came up with a couple of interesting things. Firstly, the way I'd imagined things was all wrong. I had assumed that the mediator would loop through the package variables, and add them as parameters to the XSLT. In fact, it's the other way round. The mediator parses the XSLT to get the param elements that are declared, and loops through these to see if it can find a satisfactory parameter to add.
If you look in the documentation, you will find that there are some "magic" parameter names that will cause the mediator to pass various relevant data items as parameters. These are tcm:Publication, tcm:ResolvedItem, tcm:ResolvedTemplate and tcm:XsltTemplate. In addition to these documented parameters, tcm:Page and tcm:ComponentTemplate would also appear to work under the correct circumstances, but of course, if you want your templates to be future-proof, it's better not to use such undocumented features, especially seeing as you could just add the relevant items as XML to your package, and have the same result. It all reminds me of the old XSLT component templates, that also had magic parameters that very few people knew about.
Anyway, back to my bug - for it is indeed a bug. In addition to providing magic parameters, of course the mediator also wires up parameters that are in the package. So - having found a parameter name in the XSLT, it looks for a package item with the matching name. If the item is of type "text" or "html", then it gets added as a string. For any other item type, it tries to get an appropriate XmlDocument and add that. If this process fails, any exceptions get swallowed, and instead of an XmlDocument the "parameter" parameter of AddParam becomes null. And then we see the aforementioned "Value cannot be null. Parameter name: parameter" message, which is the .NET framework quite correctly checking its input values and refusing to play.
The solution is easy - instead of using ContentType.String when I added my parameter to the package, I used ContentType.Text, and everything worked like a charm. But not obvious, and hence the blog post. I'm sure to forget this, and having it in my "external memory" might help.
It's easy to see how this could happen. In fact, it's our old friend LOLA. The GetAsXmlDocument() method of a Templating Item returns a null if it can't manage to return the relevant XmlDocument - for all I know, this is the correct semantics for such a method. Maybe there are very good reasons for it. Still - if you're writing client code, and you don't know this, you'll fail to do the null check, and things will break. FWIW the null check is also missing in older versions of the mediator.
So - there - I've got that off my chest. I should probably report this to customer support. But it's the weekend, and seeing as my stuff works, and the answer is now google-able, I might possibly not have that much energy :-)
The Tridion XSLT mediator, and page templates that don't template pages
My current customer is in the process of upgrading to Tridion 2013, and as part of that, switching over from the old XSLT mediator to the new. By the new one, I mean the one that was released by SDL as part of the 2013 product. The new one is very similar to the old one, and changing over hasn't created any significant problems, but I came across a quirk this week that I thought worth sharing.
The symptoms were pretty odd, and it took me a while to figure out what was happening. One of the page templates was emitting entirely the wrong content. In fact, instead of templating out a page, what I could see was more or less a copy of the XML of a component that was on the page. If you have the same problem, your symptoms may be different, depending on how you've written your XSLT template. It's also possible that you don't get any output at all, or something different.
Obviously, what the XSTL mediator does, is use your XSLT to transform an XML document. The question is - where does the XML document come from? The documentation states "By default, the XSLT template transforms the item called Component
(if the Template Building Block is part of a Component Template) or Page
(if part of a Page Template)."
This is not really as true as you might hope. It would actually be possible for the mediator to determine the type of item being rendered, but it doesn't do that. What it does by default is look in the package for an item called "Component", and if it finds one, that's what it transforms. Only then does it look to see if there's an item called "Page". If you ask me, that logic is backwards, as it's far more unlikely that a Component package has a Page item than the other way round. (There's even a standard Tridion TBB whose purpose is to add a Component item to a Page package.)
Anyway - all is not lost - the fix is quite simple. It's possible to override the default behaviour by specifying an input item. The old mediator used template building block parameters to control this behaviour, and IMNSHO that was a good choice in the context. With the new mediator, you do the same thing by using a processing instruction in the XSLT itself, or specifically by setting an attribute on the PI. In fact, if you look at the documentation, you can combine the two techniques, but that would surely be overkill for this.
So the bottom line is that in a page template, you should have something like this:
<?XsltMediator inputItemName="Page"?>
You may have noticed that I said "should". Yes - really... "should". Then you're clear that you want your page to render the Page item, even if at some point in the future, one of your colleagues decides to add an item called Component to the package. When the default behaviour is as non-obvious as this, it's wise not to rely on the defaults.
This was a public service announcement - or perhaps it was my penance for not spotting sooner what was going on.
Default namespaces (again - this time in XSLT and XPath)
For a long time, the default XSLT for a Rich Text Format area (RTF) in Tridion began with a fairly standard namespace declaration, like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="b"> <strong> <xsl:apply-templates/> </strong> </xsl:template>
I've just checked on my Tridion 2013 system, and the default RTF XSLT is done in this style, but I've seen examples in the field that use a rather different, and perhaps bizarre style to achieve the same thing. (I have a suspicion that there was a version of Tridion that used this style, or maybe it was something to do with an upgrade that converted to it, but I have no direct evidence. Maybe there's another explanation as to why we see this in the field.)
This other style I'm referring to looks quite different. It starts with a namepace declaration like this:
<stylesheet xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
As you see, the XSLT namespace is used as the default, enabling the template code to be slightly terser. The same template would look like this:
<template match="b"> <strong xmlns=""> <xsl:apply-templates/> </strong> </template>
When I first saw this kind of code I was almost pulling my hair out. I actually had to manually execute a test XSLT to prove to myself that it would actually work. And it does.
What was upsetting me? Well was convinced that the match should fail. I mean surely, you'd have to do something like:
<template match="empty:b" xmlns:empty=""> <strong xmlns=""> <xsl:apply-templates/> </strong> </template>
the set of namespace declarations are those in scope on the element which has the attribute in which the expression occurs; this includes the implicit declaration of the prefix xml
required by the the XML Namespaces Recommendation [XML Names]; the default namespace (as declared by xmlns
) is not part of this set
So XSLT doesn't let the XPath see the default namespace, but it wouldn't matter, because XPath would ignore it: From the XPath recommendation:
A QName in the node test is expanded into an expanded-name using the namespace declarations from the expression context. This is the same way expansion is done for element type names in start and end-tags except that the default namespace declared with xmlns
is not used: if the QName does not have a prefix, then the namespace URI is null (this is the same way attribute names are expanded).
So in an XSLT, the normal namespace weirdness that we're used to for attributes applies to elements as well.
Well, that explains that then. I think I'll be sticking to the more conventional approach though.
A poor man's Component synchroniser - or using the .NET framework to run XSLT from the PowerShell
Just lately, I've been doing some work on porting the old Component Synchroniser power tool to the current version of Tridion. If you are familiar with the original implementation, you might know that it is based on a pretty advanced XSLT transformation (thankfully, that's not the part that needs porting), that pulls in data about the fields specified by the schema (including recursive evaluation of embedded schemas), and ensures that the component data is valid in terms of the schema. Quite often on an upgrade or migration project, any schema changes can be dealt with well enough by this approach, but sometimes you need to write a custom transformation to get your old component data to match the changes you've made in your schema. For example, the generic component synchroniser will remove any data that no longer has a field, but if you add a new field that needs to be populated on the basis of one of the old fields, you'll be reaching for your favourite XSLT editor and knocking up a migration transform.
This might sound like a lot of work, but very often, it isn't that painful. In any case, the XSLT itself is the hard part. The rest is just about having some boilerplate code to execute the transform. In the past, I've used various approaches, including quick-and-dirty console apps written in C#. As you probably know, in recent times, I've been a big fan of using the Windows Powershell to work with Tridion servers, and when I had to fix up some component migrations last week, of course, I looked to see whether it could be done with the PowerShell. A quick Google led me (as often happens!) to Scott Hanselman's site where he describes a technique using NXSLT2. Sadly, NXSLT2 now seems to be defunct, and anyway it struck me as perhaps inelegant, or at the least less PowerShell-ish to have to install another executable, when I already have the .NET framework,, with System.Xml.Xsl.XslCompiledTransform, available to me.
I've looked at doing XSLT transforms this way before, but there are so many overloads (of everything) that sometimes you end up being seduced by memory streams and 19 flavours of readers and writers. This time, I remembered System.IO.StringWriter, and the resulting execution of the transform took about four lines of code. The rest of what you see below is Tridion code that executes the transform against all the components based on a given schema. Sharp-eyed observers will note that in spite of a recent post here to the effect that I'm trying to wean myself from the TOM to the core service, this is TOM code. Yup - I was working on a Tridion 2009 server, so that was my only option. The good news is that the same PowerShell/XSLT technique will work just as well with the core service.
$tdse = new-object -com TDS.TDSE
$xslt = new-object System.Xml.XmlDocument$xslt.Load("c:\Somewhere\TransformFooComponent.xslt")$transform = new-object System.Xml.Xsl.XslCompiledTransform$transform.Load($xslt)$sb = new-object System.Text.StringBuilder$writer = new-object System.IO.StringWriter $sbfilter FixFooComponent(){$sb.Length = 0$component = $tdse.GetObject($_, 2)$xml = [xml]$component.GetXml(1919)$transform.Transform($xml, $null, $writer)$component.UpdateXml($sb.ToString())$component.Save($true)}$schema = $tdse.GetObject("/webdav/SomePub/Building%20Blocks/System/Schemas/Foo.xsd",1)([xml]$schema.Info.GetListUsingItems()).ListUsingItems.Item | ? {$_.Type -eq 16}| %{$_.ID} | FixFooComponent
Muenchian groupings in XSLT
On Friday, a colleage at work asked me how to do something in XSLT. After a minute or so humming and hah'ing I said I'd have to think about it for a while. As it turned out, it took most of the weekend. (OK - as a parent of 2 under-3s, the amount of free time in a weekend is limited, but still, this was a tricky problem.)
The source XML document was something like this:
<list> <item> <Message ID="first" "> <Title>Foo</Title> <Body>You can now find details of the blah blah blah</Body> <Date>20050612</Date> </Message> </item> <item> <Message ID="second" > <Title>Bar</Title> <Body>Flibble de dee</Body> <Date>20050805</Date> </Message> </item> </list>
Of course, there were lot more messages, but the important point was that my colleague wanted to group the messages by month. That meant that any Message whose Date element's first six characters were the same should be displayed together, no matter where they came in the source document.
This turns out to be a non-trivial problem in XSLT, and has been explicitly addressed as a weakness by the people responsible for XSLT 2.0. Anyway - the following works in XSLT 1.1; it's based on the Meunchian grouping technique (aka Fu-Muench-u).
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:key name="messages" match="/list/item/Message" use="substring(Date, 1,6)"/> <xsl:template match="/"> <xsl:variable name="messagesWithUniqueMonth" select="/list/item/Message[generate-id()=generate-id(key('messages', substring(Date,1,6)))]"/> <xsl:for-each select="$messagesWithUniqueMonth"> <xsl:sort select="substring(Date,1,6)"/> <xsl:call-template name="doMonth"> <xsl:with-param name="month"> <xsl:value-of select="substring(Date,1,6)"/> </xsl:with-param> </xsl:call-template> </xsl:for-each> </xsl:template> <xsl:template name="doMonth"> <xsl:param name="month"/> <div month='{$month}'> <xsl:for-each select="key('messages', $month)"> <xsl:sort select="Date"/> <p> <xsl:value-of select="Title"/><xsl:value-of select="Date"/> </p> </xsl:for-each> </div> </xsl:template> </xsl:stylesheet>
I had wanted to do something less arcane. I tried various approaches beginning with a variable containing all the dates, and then refining it, but most of the time I ended up with a <xsl:for-each/> unable to consume a result fragment or some such. The Muenchian technique works, even if it is opaque, and nothing else I tried did. I think it's probably possible using a recursive template to create a delimited string of months, but this way is definitely getting added to my toolbox.
For what it's worth, I'll recommend that we don't do this at all. The XML being transformed is already generated programatically, and it should be simple enough to add a month field that will make life a lot simpler.