Reputation: 295
I've got an xml file that has some data that is being output in a table split into two columns (effectively). This is the XML
<structuredBody>
<component>
<section>
<templateId root="2.16.840.1.113883.10.20.22.2.3.1" />
<entry>
<organizer>
<component>
<observation>
<code displayName="TIBC" />
<effectiveTime value="8/29/2013 12:00:00 AM" />
<value value="39" />
<referenceRange>
<observationRange>
<text />
</observationRange>
</referenceRange>
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="TSAT" />
<effectiveTime value="8/29/2013 12:00:00 AM" />
<value value="25" />
<referenceRange>
<observationRange>
<text />
</observationRange>
</referenceRange>
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="Albumin" />
<effectiveTime value="9/5/2013 12:00:00 AM" />
<value value="46" />
<referenceRange>
<observationRange>
<text />
</observationRange>
</referenceRange>
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="ALT" />
<effectiveTime value="9/5/2013 12:00:00 AM" />
<value value="48" />
<referenceRange>
<observationRange>
<text>21-72</text>
</observationRange>
</referenceRange>
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="Bicarbonate" />
<effectiveTime value="9/5/2013 12:00:00 AM" />
<value value="69" />
<referenceRange>
<observationRange>
<text />
</observationRange>
</referenceRange>
</observation>
</component>
</organizer>
</entry>
</section>
</component>
<component>
<section>
<...>
</section>
</component>
<component>
<section>
<...>
</section>
</component>
</structuredBody>
I've got the output formatted using xslt:
<xsl:template match="/">
<xsl:if test="//section[templateId/@root='2.16.840.1.113883.10.20.22.2.3.1']!=''">
<xsl:variable name="rowLabs" select="ceiling(count(//section[templateId/@root='2.16.840.1.113883.10.20.22.2.3.1']/entry) div $colLabs)" />
<div style="margin-bottom: 5px; padding: 5px; border-bottom: 1px solid #000000;">
<span style="font-weight: bold;">Lab Results:</span>
<table border="0" cellspacing="0" cellpadding="1" width="99%" style="font-size: 11px;">
<xsl:for-each select="//section[templateId/@root='2.16.840.1.113883.10.20.22.2.3.1']/entry[position() <= $rowLabs]">
<tr>
<xsl:variable name="otherEntries" select=".|following-sibling::entry[position() mod $rowLabs = 0]" />
<xsl:apply-templates select="self::*|$otherEntries" />
<xsl:call-template name="blankentries">
<xsl:with-param name="entries" select="$colLabs - count($otherEntries) - 1" />
</xsl:call-template>
<!--<xsl:apply-templates select=".|following-sibling::entry[position() < 2]" />-->
</tr>
</xsl:for-each>
</table>
</div>
</xsl:if>
</xsl:template>
<xsl:template match="section[templateId/@root='2.16.840.1.113883.10.20.22.2.3.1']/entry">
<td width="75">
<xsl:call-template name="StripTime">
<xsl:with-param name="DateTime" select="organizer/component/observation/effectiveTime/@value" />
</xsl:call-template>
</td>
<td width="198">
<xsl:value-of select="organizer/component/observation/code/@displayName"/>
</td>
<td width="50">
<xsl:value-of select="organizer/component/observation/value/@value"/>
</td>
<td width="75">
<xsl:value-of select="organizer/component/observation/referenceRange/observationRange/text"/>
</td>
</xsl:template>
<xsl:template name="blankentries">
<xsl:param name="entries" />
<xsl:if test="$entries > 0">
<td></td>
<xsl:call-template name="blankentries">
<xsl:with-param name="entries" select="$entries - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
so that the resulting entry nodes run down and then over so that the output is:
<table>
<tr>
<td>8/29/2013</td>
<td>TIBC</td>
<td>39</td>
<td></td>
<td>9/5/2013</td>
<td>ALT</td>
<td>48</td>
<td>21-72</td>
</tr>
<tr>
<td>8/29/2013</td>
<td>TSAT</td>
<td>25</td>
<td></td>
<td>9/5/2013</td>
<td>Bicarbonate</td>
<td>69</td>
<td></td>
</tr>
<tr>
<td>9/5/2013</td>
<td>Albumin</td>
<td>46</td>
<td></td>
</tr>
</table>
This gives me:
[entry 1] [entry 4]
[entry 2] [entry 5]
[entry 3]
which is what I'm looking for and that's great.
What I can't figure out is how to get the different 4-cell entry sets to alternate colors as it goes through. I can't use position() because I'm manipulating it to get the desired output order in the table. If I output the position to figure out the math, on the left column, it's always "1" and on the right column it's always "2" so I can't do a position() mod 2 = 1 to set a style attribute.
Secondly, I only want the date value to appear one time and then not appear until it changes. That would make it such that the output ideally should look like:
<table>
<tr>
<td>8/29/2013</td>
<td>TIBC</td>
<td>39</td>
<td></td>
<td bgcolor="dcdcdc"></td>
<td bgcolor="dcdcdc">ALT</td>
<td bgcolor="dcdcdc">48</td>
<td bgcolor="dcdcdc">21-72</td>
</tr>
<tr>
<td bgcolor="dcdcdc"></td>
<td bgcolor="dcdcdc">TSAT</td>
<td bgcolor="dcdcdc">25</td>
<td bgcolor="dcdcdc"></td>
<td></td>
<td>Bicarbonate</td>
<td>69</td>
<td></td>
</tr>
<tr>
<td>9/5/2013</td>
<td>Albumin</td>
<td>46</td>
<td></td>
</tr>
</table>
I can't put the bgcolor attribute in the "tr" tag because it should alternate even across the "columns" and not just the entire row.
Thanks for any help. This site has brought my xslt knowledge a long way. I've only started delving into it in the last month.
Upvotes: 1
Views: 873
Reputation: 122374
For the colours you essentially have two cases depending on whether $rowLabs
is odd or even.
("odd" and "even" counting from 1 as XPath position()
does, so the first row/column is odd, the second is even, etc.).
You can encode that logic in XSLT by adding some parameters to the templates. Replace the
<xsl:apply-templates select="self::*|$otherEntries" />
with
<xsl:apply-templates select="self::*|$otherEntries">
<xsl:with-param name="rowNum" select="position()" />
<xsl:with-param name="totalRows" select="$rowLabs" />
</xsl:apply-templates>
and add the parameters to the section
template
<xsl:template match="section[templateId/@root='2.16.840.1.113883.10.20.22.2.3.1']/entry">
<xsl:param name="rowNum" select="1" />
<xsl:param name="totalRows" select="2" />
Now we need a named template we can call to implement the logic I described above:
<xsl:template name="bgcolor">
<xsl:param name="rowNum" select="1" />
<xsl:param name="totalRows" select="2" />
<xsl:if test="($totalRows mod 2 = 0 and $rowNum mod 2 = 0) or
($totalRows mod 2 = 1 and $rowNum mod 2 != position() mod 2)">
<xsl:attribute name="bgcolor">dcdcdc</xsl:attribute>
</xsl:if>
</xsl:template>
This adds the bgcolor
attribute if either there is an even number of rows and the current rownum is even, or if there is an odd number of rows and the current column number within the row has a "different oddness" from the row number.
Finally we call this template within the <td>
elements, e.g.
<td width="50">
<xsl:call-template name="bgcolor">
<xsl:with-param name="rowNum" select="$rowNum" />
<xsl:with-param name="totalRows" select="$totalRows" />
</xsl:call-template>
<xsl:value-of select="organizer/component/observation/value/@value"/>
</td>
The crucial thing that makes all this work is the fact that you're applying templates to self::*|$otherEntries
, so a call to position()
within the applied template gives the column number (the position within this node list), not the node's original position in its parent.
To make the date appear only once the first time it is encountered, you can define a key and use a trick related to the "Muenchian grouping" technique. Declare
<xsl:key name="effectiveTimeByDate" match="effectiveTime"
use="substring-before(@value, ' ')" />
and then you can check whether this is the first occurrence of a particular date in the document using
<td width="75">
<xsl:call-template name="bgcolor">
<xsl:with-param name="rowNum" select="$rowNum" />
<xsl:with-param name="totalRows" select="$totalRows" />
</xsl:call-template>
<xsl:if test="
generate-id(organizer/component/observation/effectiveTime)
= generate-id(key('effectiveTimeByDate', substring-before(
organizer/component/observation/effectiveTime/@value, ' '))[1])">
<xsl:call-template name="StripTime">
<xsl:with-param name="DateTime" select="organizer/component/observation/effectiveTime/@value" />
</xsl:call-template>
</xsl:if>
</td>
(you may be able to scrap the StripTime
template entirely and just use the substring-before
call the same as I'm using in the key)
Upvotes: 1