xsl-list
[Top] [All Lists]

RE: [xsl] How do I build a nodeset "programmatically" for passing to another template?

2006-08-17 14:17:30
Wendell,

Thanks for the background and the hint to look for "micropipelining".
I'm using Saxon 6.5.x which already supports automatic conversion from
RTF to nodeset (assuming I correctly read Michael Kay's excellent "XSLT
Programmer's Reference 2nd edition" book).  I'm assuming that for other
XSLT engines, the node-set() function would be needed in my output table
call...along the lines of <xsl:with-param name="entries"
select="xalan:node-set($speed_table_values)/*"/>.  Correct me if I'm not
understanding the proper extension usage for micropipelining.

To close the loop for the next poor sod trying to understand this
problem, I'm posting my working mini-demonstration of a stylesheet that
does what I want.  I make no claims as to having any skill whatsoever in
writing "good" XSL code.  Since the stylesheet only looks for a root
node, just feed it to itself to test it out.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
        <xsl:strip-space elements="*"/>
        <xsl:output method="xml" indent="yes"/>
        

        <xsl:template match="/">
                <xsl:variable name="max_ay" select="0.600"/>
                <xsl:variable name="radii">
                        <radius>30</radius>
                        <radius>60</radius>
                        <radius>61</radius>
                        <radius>100</radius>
                        <radius>120</radius>
                        <radius>150</radius>
                </xsl:variable>
                <xsl:variable name="ay_90pct" select="format-number(0.9
* $max_ay, '#.##')"/>
                <xsl:variable name="speed_table_values">
                        <xsl:call-template name="generate_speed_table">
                                <xsl:with-param name="ay_target"
select="$ay_90pct"/>
                                <xsl:with-param name="radii"
select="$radii/*"/>
                                <xsl:with-param
name="string_format">#.#</xsl:with-param>
                        </xsl:call-template>
                </xsl:variable>
                <!-- Show that the nodeset was built properly -->
                <output><xsl:copy-of
select="$speed_table_values"/></output>
                <!-- Now use the nodeset to generate our output table
-->
                <table>
                        <xsl:call-template name="output_rows">
                                <xsl:with-param name="entries"
select="$speed_table_values/*"/>
                        </xsl:call-template>
                </table>
        </xsl:template>
        
        <xsl:template name="output_rows">
                <xsl:param name="entries"/>
                
                <xsl:if test="$entries">
                        <row>
                                <cell><xsl:value-of
select="$entries[1]/radius"/></cell>
                                <cell><xsl:value-of
select="$entries[1]/speed_kmh"/></cell>
                                <cell><xsl:value-of
select="$entries[1]/speed_mph"/></cell>
                        </row>
                        <xsl:call-template name="output_rows">
                                <xsl:with-param name="entries"
select="$entries[position() != 1]"/>
                        </xsl:call-template>
                </xsl:if>
        </xsl:template>
        
        
        <xsl:template name="generate_speed_table">
                <xsl:param name="ay_target"/>  <!-- lateral acceleration
in g's -->
                <xsl:param name="radii"/> <!-- nodeset of <radius>
entries, in meters -->
                <xsl:param name="string_format">#.#</xsl:param> <!--
format to pass to string-format() -->
                <xsl:param name="result"></xsl:param>
                
                <xsl:choose>
                        <xsl:when test="not($radii)">
                                <xsl:copy-of select="$result"/>
                        </xsl:when>
                        <xsl:otherwise>
                                <!-- 
                                velocity = sqrt(radius * ay_target *
9.80665);
                                speed_kmh = velocity * 3.6;
                                speed_mph = velocity * 2.236936;
                                -->

                                <xsl:variable name="velocity">
                                        <xsl:call-template name="sqrt">
                                                <xsl:with-param
name="number" select="$radii[1] * $ay_target * 9.80665"/>
                                        </xsl:call-template>
                                </xsl:variable>
                                <xsl:variable name="speed_kmh"
select="format-number($velocity * 3.6, $string_format)"/>
                                <xsl:variable name="speed_mph"
select="format-number($velocity * 2.236936, $string_format)"/>
                                <xsl:variable name="value">
                                        <entry>
                                                <xsl:copy-of
select="$radii[1]"/>
                                                <speed_kmh>
                                                        <xsl:value-of
select="format-number($velocity * 3.6, $string_format)"/>
                                                </speed_kmh>
                                                <speed_mph>
                                                        <xsl:value-of
select="format-number($velocity * 2.236936, $string_format)"/>
                                                </speed_mph>
                                        </entry>
                                </xsl:variable> 
                                <xsl:call-template
name="generate_speed_table">
                                        <xsl:with-param name="ay_target"
select="$ay_target"/>
                                        <xsl:with-param name="radii"
select="$radii[position() != 1]"></xsl:with-param>
                                        <xsl:with-param
name="string_format" select="$string_format"/>
                                        <xsl:with-param
name="result"><xsl:copy-of select="$result"/><xsl:copy-of
select="$value"/></xsl:with-param>
                                </xsl:call-template>
                        </xsl:otherwise>
                </xsl:choose>
        </xsl:template>
        
        <xsl:template name="sqrt"> <!-- copied from XSLT Cookbook
(O'Reilly) -->
                <!-- The number you want to find the square root of -->
                <xsl:param name="number" select="0"/>
                <!-- The current 'try'.  This is used internally. -->
                <xsl:param name="try" select="($number &lt; 100) + 
        
($number >= 100 and $number &lt; 1000) * 10 +
        
($number >= 1000 and $number &lt; 10000) * 31 +
        
($number >= 10000) * 100"/>
                <!-- The current iteration, checked against maxiter to
limit loop count -->
                <xsl:param name="iter" select="1"/>
                <!-- Set this up to ensure against infinite loops -->
                <xsl:param name="maxiter" select="20"/>
                <!-- This template was written by Nate Austin using Sir
Isaac Newton's method of finding roots -->
                <xsl:choose>
                        <xsl:when test="$try * $try = $number or $iter >
$maxiter">
                                <xsl:value-of select="$try"/>
                        </xsl:when>
                        <xsl:otherwise>
                                <xsl:call-template name="sqrt">
                                        <xsl:with-param name="number"
select="$number"/>
                                        <xsl:with-param name="try"
select="$try - (($try * $try - $number) div (2 * $try))"/>
                                        <xsl:with-param name="iter"
select="$iter + 1"/>
                                        <xsl:with-param name="maxiter"
select="$maxiter"/>
                                </xsl:call-template>
                        </xsl:otherwise>
                </xsl:choose>
        </xsl:template>
        
</xsl:stylesheet>


--Matthew

-----Original Message-----
From: Wendell Piez [mailto:wapiez(_at_)mulberrytech(_dot_)com] 
Sent: Thursday, August 17, 2006 11:48 AM
To: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
Subject: Re: [xsl] How do I build a nodeset 
"programmatically" for passing to another template?

Matthew,

What you're trying to do is a useful and very powerful 
technique, only unfortunately disallowed by a restriction in 
classical XSLT 1.0, which defines a variable bound 
dynamically in such a way as a result tree fragment (RTF), 
which by definition may not be processed further. (An RTF may 
only be copied to the result tree or converted to a string, 
which is somewhat useful but not as much as one would like.)

Because this restriction was considered by some to be rather 
artificial, and the technique so useful, it was removed in 
XSLT 2.0, where what you are doing will work transparently.

In a conformant XSLT 1.0 processor, you will get an error 
when you try this, typically along the lines of "cannot 
process Result Tree Fragment". To get around this, many XSLT 
1.0 processors provide an extension function. For example, a 
function node-set() is available, in many processors, in the 
EXSLT namespace (see www.exslt.org), which you could use, as 
in node-set($speed_table_values), to get up and running.

I believe the node-set() extension function may also be 
available natively in an XSLT 1.1 processor, but since the 
1.1 specification was shelved before making Recommendation 
status, YYMV.

FWIW, some of us are calling this technique 
"micropipelining", as it entails creating a tree fragment and 
then processing it. Not only is it very powerful, it's very 
much in the XSLT 2.0 spirit of things, and at the core of any 
number of advanced methodologies such as
(pre-eminently) FXSL.

Cheers,
Wendell

At 11:34 AM 8/17/2006, you wrote:
I'm using Saxon 6.5.x (and XSL 1.1).

Searches on the web and this list's archives give me 
tantalizing hints 
that what I want to do is theoretically possible.  All of 
the examples 
I find seem to be "reducers" (e.g. sum a set of numbers spread 
throughout a nodeset, or combine nodes into a single string), rather 
than "builders".

My stylesheets are pull style that build XSL-FO output for further 
processing by Apache-FOP.  What I'm trying to do is build a 
nodeset in 
a variable so that I can rely on some generic fo:table templates to 
produce the output rather than custom templates for each occurrence.
Due to limits of what is currently in the XML input files, 
I'm forced 
to do some calculation/nodeset building to supplement the structure 
already in the XML file.

Is it possible for a chunk of XSL like:
<xsl:variable name="max_ay"
select="/vdt:report/vdt:run_metrics/*[normalize-space(text()) = 'Avg 
Ay']/vdt:statistics/vdt:statistic[(_at_)stat_name='Average']"/>
<xsl:variable name="ay_90pct" select="format-number(0.9 * $max_ay, 
'#.##')"/> <xsl:variable name="speed_table_values">
        <xsl:call-template name="ml_generate_speed_table">
                <xsl:with-param name="ay_target" 
select="$ay_90pct"/>
                <xsl:with-param name="radii">
                        <radius>30</radius>
                        <radius>60</radius>
                        <radius>61</radius>
                        <radius>100</radius>
                </xsl:with-param>
                <xsl:with-param
name="string_format">#.#</xsl:with-param>
        </xsl:call-template>
</xsl:variable>


To result in the variable $speed_table_values looking 
something like ?:
<entry>
        <radius>30</radius>
        <speed_km_h>60.9</speed_km_h>
        <speed_mph>50.1</speed_mph>
</entry>
<entry>
        <radius>60</radius>
        <speed_km_h>72.9</speed_km_h>
        <speed_mph>60.1</speed_mph>
</entry>
<entry>
        <radius>61</radius>
        <speed_km_h>81.9</speed_km_h>
        <speed_mph>70.4</speed_mph>
</entry>
<entry>
        <radius>100</radius>
        <speed_km_h>99.9</speed_km_h>
        <speed_mph>75.1</speed_mph>
</entry>

(The numbers in the table are bogus but the nodeset 
structure is what 
I'm interested in.)

I would then have a later <xsl:call-template> that contained 
<xsl:with-param select="$speed_table_values">

As you can see, this particular problem is mostly interating 
over nodes 
provided within the XSL sheet itself rather than the source 
XML.  Only 
the first line of my example is grabbing a value/node from 
the source 
tree.  I need to accomplish this in a single pass (not 
multiple calls 
to an XSLT transformer).  Assuming what I want to do is 
possible in XSL 
1.1, any links to similar examples would be much appreciated.


--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: 
<mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--




--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--

<Prev in Thread] Current Thread [Next in Thread>