Re: [xsl] Create Table

Subject: Re: [xsl] Create Table
From: "Eric J. Bowman" <eric@xxxxxxxxxxxxxxxx>
Date: Tue, 6 Apr 2010 19:23:12 -0600
bernie bonn wrote:
> So I have my table built with all the source changes, now I want to
> add a row with the 'diffs' for each change...

I'm pretty sure you meant to say you want to add a *column* with the
relevant diffs... ;-).

> Off topic, I plan on using JavaScript to hide the diff detail unless
> user wants to see it.

Not off-topic at all.  You want the new column to be optional, OK.  I'm
assuming you'll be calling this transformation from Javascript, rather
than an xml-stylesheet PI, if you want to present the user with options
for the <table> content, to keep the diffs transformation from
executing -- otherwise you could just toggle the column by setting a
<col> tag to display:none.

I added a global boolean parameter to the stylesheet, defaulting to
true and executing the code for transforming the optional column.  This
may be overridden easily when the transform is called by Javascript, in
any browser -- executing a stylesheet with a parameter is pretty
standard stuff.  With an xml-stylesheet PI, though, only mozilla has a
proprietary mechanism to allow parameter passing.

> The challenge is after i have grabbed  the the File, version, date
> etc for the first row,  I need to then somehow grab all the data
> nodes where @field=diff, until the next node where @field!=diff, and
> place it in the next row. Seems like a while loop, but I know those
> don't exist.

You just need to think upside-down, in terms of document order.
Instead of checking against some value for the *next* node, look for
patterns in *previous* nodes.  This makes recursive design easier,
IMHO.  Instead of looping through a set until some condition is met,
think in terms of selecting a set which meets certain criteria.

In this case, we need to select all the following-sibling data elements
with @section=diff before the next <tr> delimiter, but which don't have
a preceding-sibling where @section!=diff.  This is a two-step operation
which requires us to first identify a nodeset of all @section=diff
between the delimiters, by using <variable><copy-of/></variable>.

We can then perform operations against that variable of type=nodeset,
instead of the document as a whole.  This makes it easy to then
process only the data elements we're interested in.  If we're only
ever searching up the preceding-sibling axis, then we don't need to
account for end-of-parent-element, which is what I mean by thinking

Notice I've rolled the xsl:stylesheet @version up to '1.1'.  I don't
know the story on browser compatibility with XSLT 1.0 vs. 1.1, but I do
know that all XSLT-enabled browsers grok exslt:node-set(), so if my
code doesn't work in your target browser you can alter it to use exslt:
node-set() and it ought to work.

As I said before, XSLT 1 forces one to be clever.  I wouldn't normally
code a double-negative as I have here, but the key logic turned out to
be checking that something is not(!=).  It's a bit counter-intuitive,
which is why this response took so long -- I got totally stumped on it
until today.  Further comments in the XSLT code.

> Sorry this is so muddled, let me know if you need clarifications. 
> Thanks again,

The only apologizing needed here, is from whoever designed the XML
source language you're working with.  After receiving fifty lashes with
a wet noodle.  Just sayin'.


<?xml version='1.0' encoding='utf-8'?>
    <data lineId='1' section='diff' field='diff'>if (reportService =
    <data lineId='2' section='diff' field='diff'>return false;</data>
    <data lineId='3' section='diff' field='diff'>return true;</data>
    <data lineId='4' section='changes'
    <data lineId='5' section='changes' field='date'>20100310.102844</data>
    <data lineId='6' section='changes' field='user'>jryan</data>
    <data lineId='7' section='changes' field='cr_number'>602018</data>
    <data lineId='8' section='changes' field='comment'>fix for log
    <data lineId='9' section='diff' field='diff'>1296a1297,1298</data>
    <data lineId='11' section='diff' field='diff'>if !(reportService =
    <data lineId='12' section='diff' field='diff'>return true;</data>
    <data lineId='13' section='diff' field='diff'>return false;</data>
    <data lineId='14' section='changes' field='file'></data>
    <data lineId='15' section='changes'
    <data lineId='16' section='changes' field='date'>20090310.102844</data>
    <data lineId='17' section='changes' field='cr_number'>602118</data>
    <data lineId='18' section='changes' field='comment'>fix for log
    <data lineId='19' section='diff' field='diff'>1293a1294,1295</data>
    <data lineId='21' section='diff' field='diff'>if (reportService !=
    <data lineId='22' section='diff' field='diff'>return bar;</data>
    <data lineId='23' section='diff' field='diff'>return foo;</data>
    <data lineId='29' section='Section1'
    <data lineId='30' section='diff' field='diff'>1012c1024</data>
    <data lineId='31' section='diff'
    <data lineId='24' section='changes' field='file'></data>
    <data lineId='25' section='changes' field='date'>20080310.102844</data>
    <data lineId='26' section='changes' field='user'>ryanj</data>
    <data lineId='27' section='changes' field='cr_number'>602218</data>
    <data lineId='28' section='diff' field='diff'>1290a1291,1292</data>

<?xml version='1.0' encoding='utf-8'?>
<xsl:stylesheet version='1.1'
    <!-- call this stylesheet with diffs=false() to suppress the optional
changeset column -->
    <xsl:param name='diffs' select='true()'/>
    <xsl:output omit-xml-declaration='no' method='xml' indent='yes'
xml:space='default' encoding='utf-8'/>
    <xsl:template match='/'>
        <html xml:lang='en'>
                <title>table example</title>
                <table width='0' summary='summary goes here for
                    <caption>caption goes here for accessibility</caption>
                            <th>Line #</th>
                            <th>CR Number</th>
test='$diffs'><th>Changeset</th></xsl:if><!-- optional column -->
                    <tfoot><!-- optional -->
                            <th>Line #</th>
                            <th>CR Number</th>
test='$diffs'><th>Changeset</th></xsl:if><!-- optional column -->
                        <!-- find and throw the delimiters between groups of
data elements, i.e. the last section=diff before a section=changes -->
                        <!-- the data elements between delimiters (or
end-of-parent) make up the contents of each table row -->
    <xsl:template match='data'>
        <!-- catch the delimiting data elements and assign a group number
based on delimiter occurrence number (position) -->
        <xsl:param name='i' select='position()'/>
        <!-- create a nodeset containing all non-section=changes data elements
from here to the next delimiter (or end-of-parent), using xsl:variable
*content* not @select -->
        <!-- if the count of the preceding delimiters is not equal to the
current group number, the data element is not part of the current group -->
        <xsl:variable name='changeset'>
select="following-sibling::data[not(@section='changes') and
count(preceding-sibling::data[@section='changes' and
        <!-- handle all section=changes data elements from here to the next
delimiter (or end-of-parent) using pull, to account for possible missing
fields -->
        <!-- if the count of the preceding delimiters is not equal to the
current group number, the data element is not part of the current group -->
            <th><xsl:value-of select='./@lineId+1'/></th>
            <td><xsl:value-of select="following-sibling::data[@field='file'
            <td><xsl:value-of select="following-sibling::data[@field='version'
            <td><xsl:value-of select="following-sibling::data[@field='date'
            <td><xsl:value-of select="following-sibling::data[@field='user'
select="following-sibling::data[@field='cr_number' and
            <td><xsl:value-of select="following-sibling::data[@field='comment'
            <!-- using dl markup for the name-value-pair semantics of line # -
code, present the optional changeset column for this row, made up only of
relevant @section=diffs from the source -->
            <!-- $changeset is a nodeset, to isolate the preceding-sibling
check for @section!=diff to the current group instead of the entire source
file -->
            <!-- the for-each @select chooses all data elements in the nodeset
with @section=diff, only if their preceding-sibling axes witin the nodeset are
clear of other @section values -->
            <xsl:if test='$diffs'>
                        <xsl:for-each select="$changeset//data[@section='diff'
and not(preceding-sibling::data[@section!='diff'])]">
                            <dt><xsl:value-of select='./@lineId'/></dt>
                            <dd><xsl:copy-of select='./text()'/>&#160;</dd>

Current Thread