Re: [xsl] Processing milestoned XML leads to many preceding:: calls and horrible performance

Subject: Re: [xsl] Processing milestoned XML leads to many preceding:: calls and horrible performance
From: Andrew Welch <andrew.j.welch@xxxxxxxxx>
Date: Tue, 21 Feb 2012 10:02:18 +0000
>    <xsl:template name="genRef">
>        <xsl:variable name="refKniha" select="//kniha[1]/@jmeno"/>
>        <xsl:variable name="refKapitola" select="preceding::kap[1]/@n"/>
>        <xsl:value-of select="concat($refKniha,'.',$refKapitola,'.')"/>
>    </xsl:template>

Did you really want "//kniha[1]" or "(//Kniha)[1]" ?   It's a common
gotcha...  The former can select multiple elements, not just one.

Also, with named templates I always think you should pass in the
$context as a parameter rather than rely on the implicit one as then
you can give it a sequence type helping the maintain know what it
expected (and it helps debugging).  In this case, as you returning an
atomic you should really use a function.

>        <xsl:element name="verse">

You can just write <verse> directly here, no need for xsl:element.

>            <xsl:variable name="prevVerseID">
>                <xsl:value-of select="./preceding::vers[1]/@n" />
>            </xsl:variable>

You should use the select attribute here, eg:

<xsl:variable name="prevVerseID" select="preceding::vers[1]/@n" />

>            <xsl:attribute name="eID">
>                <xsl:value-of select="concat($rBase,$prevVerseID)" />
>            </xsl:attribute>

That could be an "attribute value template" (then there's no need for
the above):

<verse eID="{concat($rBase, preceding::vers[1]/@n}"/>

...however you could probably use a tunnelled parameter to pass the @n
forwards rather than look back for it.

>        <xsl:variable name="refBase">
>            <xsl:call-template name="genRef" />
>        </xsl:variable>

Again, this is a bit nasty and should replaced with a function call.


>        <xsl:variable name="curPos"
>
>
select="count(./preceding::kap[1]/following::*[not(count(preceding-sibling::v
ers|current())
> = count(preceding-sibling::vers))])" />

You say that's to check if it's the first <vers> in the chapter... a
good way to do that is at the parent node select the <vers> child you
want to treat as the first, and then pass it is as a parameter.  Then
in the <vers> template, you can say:

select=". is $first-vers"

to see if the <vers> you are processing is the one you want to treat
as the first.  This makes it clear what you are trying to do, and
avoids looking back up the tree.

>        <xsl:if test="not($curPos=1)">
>            <xsl:call-template name="endVerse">
>                <xsl:with-param name="rBase">
>                    <xsl:value-of select="$refBase" />
>                </xsl:with-param>
>            </xsl:call-template>
>        </xsl:if>

The last thing to say is: avoid named templates.  You very rarely need
them, typically only when you want to output boilerplate markup, and
even then moded templates are often better.


> Any ideas? Would some other XSLT processors other than xsltproc (libxml
> 20706, libxslt 10126 and libexslt 815) I am using be able to optimize this
> somehow?

ahhh... most of the above only applies for xslt 2.0.  These days it's
helpful to mention the version you are using near the top :)



--
Andrew Welch
http://andrewjwelch.com

Current Thread