Continuing the topic of my earlier post, Ben responds at the bottom of the post:
Drazen wrote a good response to me in Quick, which is better suited for XML transformation: C++ or XSLT? It is nicely presented, but I've got to say it ultimately only makes the point here stronger. It is significant that he uses "EXSLT" to get the power function he needed. Though he tried to preemptively defend this decision, it is an example of how you are at the mercy of your XSLT component to implement an extension, and it stops my MXSML 4.0 test from working. His new XSL is half the lines and if I remove the power function (and ignore the incorrect line numbers) it runs as fast as my C++ example. But it looks like the line numbering in his solution still depends on a certain number of elements as I gather from the 6 in math:power(6, count($recurse)), whereas my C++ example can have any number of col elements in any number of column elements. Sure he can fix that too, but the fact that someone skilled with XSLT can improve this particular stylesheet does not prove the value of XSLT for this problem.
Ben is right on almost all acounts - I did make an oversight and implemented the solution assuming that the number of child elements of column is equal for all elements (original post has been modified to mention this and provide the fix). I don't agree that using EXSLT is unfair - it's like complaining that if you used hash_map in your C++ app you are relying on an extension (even though all vendors provide this).
Nevertheless, let's see what the problem really is here. The stylesheet is more complicated and required power function only to work-around an inherent design issue of XSLT as a language without side-effects to variables - once set, you can't change variable's value but that's exactly what I need to implement a counter. If the problem did not require the result rows to be numbered, things would have worked out perfectly.
But let's see what I can do to fix the stylesheet as is, using something other than power and not using EXSLT. Due to the wrong assumption about number of child nodes of column element, I can't use power anyway. What I need to do is to compute the product of number of child elements following the current node. In order to do so, I'll change the line that computes the increment to this:
<xsl:variable name="increment">
<xsl:call-template name="product">
<xsl:with-param name="nodes" select="$recurse"/>
</xsl:call-template>
</xsl:variable>
But where is the mysterious product template? It's in the separate XSLT file - it's just a utility function that computes a product of a value of some nodes, where “value” is defined in terms of the template you provide. Think of templates here as functions that call each other to compute something. Here's the definition of product:
<xsl:template name="product">
<xsl:param name="nodes"/>
<xsl:param name="result" select="1"/>
<xsl:choose>
<xsl:when test="not($nodes)">
<xsl:value-of select="$result"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="value">
<xsl:call-template name="get-node-value">
<xsl:with-param name="node" select="$nodes[1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="product">
<xsl:with-param name="nodes" select="$nodes[position() != 1]"/>
<xsl:with-param name="result" select="$result * $value"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Aha, it's getting complicated, you might say. True, but again think of this as a general purpose utility function that is reusable for other purposes as well and can be included in any stylesheet. Otherwise I can dismiss Ben's C++ solution for using a custom string implementation instead of CString :) Note the get-node-value template it depends on - that's the “value“ of node I just talked about. In this case the template boils down to this (should be replaced with a different implementation for each distinct purpose):
<xsl:template name="get-node-value">
<xsl:param name="node"/>
<xsl:value-of select="count($node/*)"/>
</xsl:template>
The resulting complete template is only 6-7 lines longer than the original (not counting utility template product). But it's still complicated for the beginner, and Ben is right - simple problems like this should have simple solution. I wouldn't be telling you all this if I didn't have one, would I? :)) Let's look again at the real problem here - all I need is a simple counter. The only reason I bothered with power and product was to avoid counting. But there's another way to avoid counting too: two-pass solution. Don't worry - it is as performant as the original solution and a few lines shorter (complete source presented for completeness):
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ms="urn:schemas-microsoft-com:xslt">
<xsl:output method="text" omit-xml-declaration="yes" encoding="iso-8859-1"/>
<xsl:template match="columns">
<xsl:variable name="lines">
<xsl:apply-templates select="column[1]"/>
</xsl:variable>
<xsl:for-each select="ms:node-set($lines)/line">
<xsl:value-of select="concat(position(), .)"/>
</xsl:for-each>
</xsl:template>
<xsl:template match="column">
<xsl:param name="running" select="''"/>
<xsl:variable name="recurse" select="following-sibling::*"/>
<xsl:for-each select="col">
<xsl:variable name="current_running" select="concat($running, ' ', .)"/>
<xsl:if test="$recurse">
<xsl:apply-templates select="$recurse[1]">
<xsl:with-param name="running" select="$current_running"/>
</xsl:apply-templates>
</xsl:if>
<xsl:if test="not($recurse)">
<xsl:element name="line">
<xsl:value-of select="concat($current_running, ' ')"/>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
First I create a temporary node set, and then output it out using the position of the nodes as a counter. I know - another extension (node-set). Hey, it works with MSXSL 4.0 so at least Ben shouldn't complain ;) Seriously, the only reason I am using the extension is because it is practically a mandatory extension for every decent XSLT processor as it rectifies what I consider a flawed spec for XSLT 1.0. The proof is in the XSLT 2.0 spec - if you remove the extension the stylesheet will work as-is with any 2.0 conformant XSLT processor (tried with Saxon).
I don't think this example proves that one should avoid XSLT. What is obvious though is that the mental shift required for an average developer when going from C++ to XSLT might indeed be too high. If you can't figure XSLT out, by all means use CMarkup or a similar library. But don't do it just because XSLT is different - there's value there too. If we'd all skip technologies and languages we are not familiar with we'd code most of the Web apps in assembler instead of PHP, Ruby or Python :)
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5