I have been messing around with for loops, trying to get a completely generic solution, and thought I'd share my results. It's an intellectual exercise, really; it's probably much more efficient to just write specific recursive templates for your use cases rather than calling document() all the time.
It is generic because you use the $function parameter to pass in the name of the template you want to call. You must also pass the node for your required context in $context.
You must define your "callback" templates in a specific way in order for them to be used - here is one to print the ID of nodes at the current position in the $context:
As you say - it's an "intellectual exercise" - and as such, a clever example, really - though not something I'd ever try to execute in that way myself...
I never succeed in trying to come up with an example or use case for a generic (as in just using the numbers from x to y) "for loop" in XSLT (closest thing I could cook up was this one - and that's not at all representative for something I've ever had the need to do with XSLT).
Would you mind sharing some examples where you'd pick something like this over actual XSLT?
The only thing I've ever used for loops for really is grids - so data displayed on a fixed width / fixed height grid where the source XML only has elements for populated locations; calendars are probably the most common type of thing, but spreadsheet type functions or raw graphing data.
Generics could be useful for something like generating fixed sized grids where the markup needed to differ, although again, you could probably do that with a process just generic enough to generate the arbitrary grids you needed.
The for loop above is probably too simple a process to be useful; the link above I got the technique talks about functions such as binary search, which would probably be a more useful application of a generic process as the function itself would be a lot more complicated and thus you probably only want a single place to maintain it.
I think you are almost certainly right in saying that you just wouldn't do it this way - generic behaviour is extremely useful in other languages and the above shows it can just about be done in XSLT, but working with the language rather than forcing to behave like another has to be the best way to go most of the time.
What I didn't try is having the generic function in a separate file from the caller - without that working it truly is useless :o)
Although document('') does not work in the "Visualize XSLT" popup, it DOES when you actually run the macro in page, so you don't in fact need the hack.
In order to make this work from an import, there are a few issues:
The dynamic named templates will obviously need to be in the calling stylesheet, so you need another parameter to the for loop template, $callbacks, containing a list of the named templates.
Although in the above example you can select directly off the document('') call, this does not appear to work when passed as a parameter, so you need to call msxml:node-set on it (adding yet another inefficiency).
You cannot have a DOCTYPE in the file you load using document(''), so that means no custom entities.
Final code for defining generics in external stylesheet:
Generic templates / call template by dynamic name
I have been messing around with for loops, trying to get a completely generic solution, and thought I'd share my results. It's an intellectual exercise, really; it's probably much more efficient to just write specific recursive templates for your use cases rather than calling document() all the time.
The idea is based on http://www.dpawson.co.uk/xsl/sect2/generic.html#d5411e177, which nearly works.
However I can't load the current style sheet using document(''), as it throws an exception: "This operation is not supported for a relative URI".
What you CAN do, is a bit hacky - you can hardcode the physical path to your template in.
Here is the code for a generic for loop:
It is generic because you use the $function parameter to pass in the name of the template you want to call. You must also pass the node for your required context in $context.
You must define your "callback" templates in a specific way in order for them to be used - here is one to print the ID of nodes at the current position in the $context:
And here is how you call the for loop, with most parameters left as defaults:
Pseduocode equivalent: for(var i = 1; i <= 5; i++) { contextCallback(i, blogNode); }
Hi Rob,
As you say - it's an "intellectual exercise" - and as such, a clever example, really - though not something I'd ever try to execute in that way myself...
I never succeed in trying to come up with an example or use case for a generic (as in just using the numbers from x to y) "for loop" in XSLT (closest thing I could cook up was this one - and that's not at all representative for something I've ever had the need to do with XSLT).
Would you mind sharing some examples where you'd pick something like this over actual XSLT?
/Chriztian
Chriztian, I would if I had any :o)
The only thing I've ever used for loops for really is grids - so data displayed on a fixed width / fixed height grid where the source XML only has elements for populated locations; calendars are probably the most common type of thing, but spreadsheet type functions or raw graphing data.
Generics could be useful for something like generating fixed sized grids where the markup needed to differ, although again, you could probably do that with a process just generic enough to generate the arbitrary grids you needed.
The for loop above is probably too simple a process to be useful; the link above I got the technique talks about functions such as binary search, which would probably be a more useful application of a generic process as the function itself would be a lot more complicated and thus you probably only want a single place to maintain it.
I think you are almost certainly right in saying that you just wouldn't do it this way - generic behaviour is extremely useful in other languages and the above shows it can just about be done in XSLT, but working with the language rather than forcing to behave like another has to be the best way to go most of the time.
What I didn't try is having the generic function in a separate file from the caller - without that working it truly is useless :o)
Just as an addendum to this, couple of points:
<xsl:template name="for">
<xsl:param name="callbacks" />
<xsl:param name="from" select="1" />
<xsl:param name="to"/>
<xsl:param name="step" select="1" />
<xsl:param name="i" select="$from" />
<xsl:param name="context" />
<xsl:param name="function" />
<xsl:if test="$i <= $to">
<xsl:apply-templates select="msxml:node-set($callbacks)[@name = $function]">
<xsl:with-param name="i" select="$i" />
<xsl:with-param name="context" select="$context" />
</xsl:apply-templates>
<xsl:call-template name="for">
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
<xsl:with-param name="step" select="$step"/>
<xsl:with-param name="i" select="$i + $step"/>
<xsl:with-param name="callbacks" select="$callbacks"/>
<xsl:with-param name="context" select="$context"/>
<xsl:with-param name="function" select="$function"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Final code for using this:
Callbacks are defined as in first post.
That's as far as I'm going to go with this. Neat trick, currently utterly useless :o)
is working on a reply...