RE: [xsl] Different Colors for Alternating Rows

Subject: RE: [xsl] Different Colors for Alternating Rows
From: "Schwartz, Rechell R, ALABS" <rrschwartz@xxxxxxx>
Date: Mon, 30 Jun 2003 15:49:27 -0500
Kevin,

Thanks for your help. I got the following error, when I tried your stylesheet. Does this mean my parser won't support extensions? What can I do about this?

Rechell

####<Jun 30, 2003 1:05:03 PM EDT> <Info> <GUI> <maeder> <FM> <ExecuteThread: '7' for queue: 'default'> <> <> <0
00000> <data.jsp::XSL Transformation Exception:java.lang.NoSuchMethodException: For extension function, could n
ot find method weblogic.apache.xerces.dom.DocumentFragmentImpl.node-set([ExpressionContext,] ).>
####<Jun 30, 2003 1:05:03 PM EDT> <Info> <GUI> <maeder> <FM> <ExecuteThread: '7' for queue: 'default'> <> <> <0
00000> <javax.xml.transform.TransformerException: java.lang.NoSuchMethodException: For extension function, coul
d not find method weblogic.apache.xerces.dom.DocumentFragmentImpl.node-set([ExpressionContext,] ).
        at weblogic.apache.xalan.transformer.TransformerImpl.transformNode(TransformerImpl.java:1248)
        at weblogic.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:483)
        at weblogic.apache.xalan.transformer.TransformerImpl.transform(TransformerImpl.java:1153)
        at jsp_servlet._maint.__data._jspService(__data.java:247)
        at weblogic.servlet.jsp.JspBase.service(JspBase.java:27)
        at weblogic.servlet.internal.ServletStubImpl.invokeServlet(ServletStubImpl.java:265)
        at weblogic.servlet.internal.ServletStubImpl.invokeServlet(ServletStubImpl.java:304)
        at weblogic.servlet.internal.ServletStubImpl.invokeServlet(ServletStubImpl.java:200)
        at weblogic.servlet.internal.WebAppServletContext.invokeServlet(WebAppServletContext.java:2495)
        at weblogic.servlet.internal.ServletRequestImpl.execute(ServletRequestImpl.java:2204)
        at weblogic.kernel.ExecuteThread.execute(ExecuteThread.java:139)
        at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:120)
---------
; Line#: 27; Column#: 55

-----Original Message-----
From: Kevin Jones [mailto:kjones@xxxxxxxxxxx]
Sent: Saturday, June 28, 2003 5:20 AM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx; Schwartz, Rechell R, ALABS
Subject: Re: [xsl] Different Colors for Alternating Rows


On Thursday 26 June 2003 16:07, Schwartz, Rechell R, ALABS wrote:
> Before I throw in the towel on this one, I tried my own approach, which I
> thought should work, but didn't. 

Hi Rechell,

Looked like this was an interesting problem so I thought I would use it as a 
test bed of the various techniques I know for making this run quickly. To save 
you some time I can say that while the last solution posted looks interesting 
it performs badly on large data sets. Your last version looked like it was 
heading into the dead end of stack overflow although I didn't get the full 
intent of it.

I have benchmarked the problem with five techniques on samples in the 
1000-9000 entry range. The best performer is what is sometimes called forward 
calling, Jarno gave you a good example of that in an earlier post. 
Unfortunately you need a processor that supports tail recursion optimisation 
to avoid this creating the stack faults you have been getting. Saxon 7.5 does 
this very well in this case but I understand you can't change processor 
easily.

The next best technique is divide and conquer (also known as DVC). While this 
performs well it can be a bit complex to get your head around. A simpler 
technique is what you might refer to as RTF indexing, this gives almost the 
same performance as DVC but is simpler to understand so I will explain how I 
implemented that. For reference, with 6000 entries this technique is roughly 
75 times quicker than your original although it will use a bit more memory 
but there is no danger of it causing a stack overflow.

This technique relies on creating a result tree fragment (a temporary XML 
fragment) that can the be indexed using the key() function. To create the 
tree your first want to add a global variable like so,

	<xsl:variable name="index">
		<xsl:for-each select='/table/tr[td[not(a or @class)]]'>
			<entry key='{generate-id()}' even="{position() mod 2 = 1 }"/>
		</xsl:for-each>
	</xsl:variable>

This creates a fragment in the variable that will look something like this,

	<entry key='NDJKDHKJK' even='false'/>
	<entry key='NDJKDGHJJ' even='true'/>
	<entry key='NDJKJHKKK' even='false'/>

One for each tr element you need to colour. The key value is just a unique 
identifier for the tr node that has been represented as a string. Its exact 
format is processor dependent.

For the second bit you need a key declaration that will index this. In my case 
I have

	<xsl:key name='lookup' match='entry' use='@key'/>

This effectively says create an index of 'entry' nodes and use the string 
value of the '@key' attribute of those nodes as the key for retrieving the 
nodes.

The final bit is to rewrite your template to make use of the key. 

   <xsl:template match="table/tr[td[not(a) and not(@class)]]">
        <xsl:variable name='tr' select='.'/>
        <xsl:for-each select='exslt:node-set($index)'>
            <xsl:call-template name='copytr'>
                <xsl:with-param name='node' select='$tr'/>
                <xsl:with-param name='inset' select='true()'/>
                <xsl:with-param name='even' 
select='key("lookup",generate-id($tr))/@even'/>
            </xsl:call-template>
        </xsl:for-each>
    </xsl:template>

This may look quite different from what you started with but its really the 
same thing. I have taken the formating code out into a separate template 
'copytr' to make it a bit clearer. Taking it line by line,

The match is the same as before.

When you use key() it returns results from the same document that the context 
node is in. We need results from the variable so I have added a for-each loop 
which selects the root node of that fragment. It is not really looping, as 
there is only one root element, but it does change the context so that we are 
now accessing nodes from the variable tree when using key(). To save having 
to swap the context back I have also saved the context node in the variable 
$tr before doing this.

The exslt:node-set call is needed to get around a restriction in XSL 1.0 
processors. Your processor will either support this function or something 
very similar to it. 

The only remaining code is the arguments to copytr. We pass the node that 
needs copying, a flag indicating is this is part of the set you want coloured 
and if this is an even or odd node. 

The interesting bit is of key() call used to determine if this is even or odd. 
If we use generate-id($tr) we get back the same string we use to index this 
node and we also know this is a unique string for that node. So the key call 
will return a nodeset containing the one and only 'entry' element that 
matches this identifier. It is then just a matter of checking the @even 
attribute to see if this should be considered an even or odd node. 

The complete solution is below.

Kev
 
<?xml version="1.0" encoding="iso-8859-1"?>

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
    xmlns:exslt="http://exslt.org/common";
    extension-element-prefixes="exslt"
    version="1.0">

    <xsl:output method="html" indent="yes"/>

    <xsl:key name='lookup' match='entry' use='@key'/>

    <xsl:variable name='index'>
        <xsl:for-each select='/table/tr[td[not(a or @class)]]'>
            <entry key="{generate-id()}" even="{position() mod 2 = 1}"/>
        </xsl:for-each>
    </xsl:variable>

    <xsl:template match="node()|@*" name="copy">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="table/tr[td[not(a) and not(@class)]]">
        <xsl:variable name='tr' select='.'/>
        <xsl:for-each select='exslt:node-set($index)'>
            <xsl:call-template name='copytr'>
                <xsl:with-param name='node' select='$tr'/>
                <xsl:with-param name='inset' select='true()'/>
                <xsl:with-param name='even' 
select='key("lookup",generate-id($tr))/@even'/>
            </xsl:call-template>
        </xsl:for-each>
    </xsl:template>

    <xsl:template name='copytr'>
        <xsl:param name='node' select='.'/>
        <xsl:param name='inset'/>
        <xsl:param name='even'/>

        <xsl:for-each select='$node'>
            <xsl:copy>
                <xsl:choose>
                    <xsl:when test="$inset">
                        <xsl:choose>
                            <xsl:when test="$even=true()">
                                <td class="evenMedium" width="35%">
                                    <xsl:apply-templates 
select="td[1]/node()|td[1]/@*"/>
                                </td>
                                <td class="evenMedium" width="65%">
                                    <xsl:apply-templates 
select="td[2]/node()|td[2]/@*"/>
                                </td>
                            </xsl:when>
                            <xsl:otherwise>
                                <td class="oddMedium" width="35%">
                                    <xsl:apply-templates 
select="td[1]/node()|td[1]/@*"/>
                                </td>
                                <td class="oddMedium" width="65%">
                                    <xsl:apply-templates 
select="td[2]/node()|td[2]/@*"/>
                                </td>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="node()|@*"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:copy>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread