Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Rob Watkins 369 posts 701 karma points
    Feb 23, 2011 @ 18:33
    Rob Watkins
    0

    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:

     

      <xsl:variable name="self" select="'E:\PathToUmbraco\xslt\myTemplate.xslt'"/>
      
    <xsl:template name="for">
        <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 &lt;= $to">
          <xsl:apply-templates select="document($self)/xsl:stylesheet/xsl:template[@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="context" select="$context"/>
            <xsl:with-param name="function" select="$function"/>
          </xsl:call-template>
        </xsl:if>
      </xsl:template>

     

     

    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:

     

      <xsl:template name="contextCallback" match="xsl:template[@name = 'contextCallback']">
        <xsl:param name="i"/>
        <xsl:param name="context"/>
        
        <xsl:value-of select="$context[position() = $i]/@id"/><br/>
      </xsl:template>

    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); }

        <xsl:call-template name="for">
          <xsl:with-param name="to" select="5"/>
          <xsl:with-param name="context" select="umbraco.library:GetXmlAll()//Blog"/>
          <xsl:with-param name="function" select="'contextCallback'"/>
        </xsl:call-template>

     

     

     

     

  • Chriztian Steinmeier 2800 posts 8790 karma points MVP 8x admin c-trib
    Feb 23, 2011 @ 21:24
    Chriztian Steinmeier
    0

    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 

  • Rob Watkins 369 posts 701 karma points
    Feb 24, 2011 @ 10:35
    Rob Watkins
    0

    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)

  • Rob Watkins 369 posts 701 karma points
    Feb 24, 2011 @ 12:36
    Rob Watkins
    0

    Just as an addendum to this, couple of points:

    1. 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.

    2. 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:
      <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 &lt;= $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:

        <xsl:variable name="callbacks" select="document('')/xsl:stylesheet/xsl:template" />
        <xsl:call-template name="for">
          <xsl:with-param name="callbacks" select="$callbacks"/>
          <xsl:with-param name="to" select="5"/>
          <xsl:with-param name="context" select="umbraco.library:GetXmlAll()//Blog"/>
          <xsl:with-param name="function" select="'contextCallback'"/>
        </xsl:call-template

     

    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)

Please Sign in or register to post replies

Write your reply to:

Draft