jjasper0729
jjasper0729

Reputation: 295

xslt 1.0 - get data to flow into 2 columns down then over

I've got an xml file that has the following data:

<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 want to output the data that's in the fleshed out section snippet such that it is in a two column table that contains four columns. What I mean is I want each entry to have four values extracted and then the complete entry output (needs to contain effectiveTime/@value, code/@displayName, value/@value and referenceRange/observationRange/text when output) to cascade onto the page first down then over. Using the example data, it would look something like:

<table>
  <tr>
    <td>entry set 1</td>
    <td>entry set 4</td>
  </tr>
  <tr>
    <td>entry set 2</td>
    <td>entry set 5</td>
  </tr>
  <tr>
    <td>entry set 3</td>
    <td>&nbsp;</td>
  </tr>
</table>

I'm able to get it to go across and then down by doing a mod on the position but I can't figure out how to do it to go down first and then across.

Here's the xsl that I have but I'm not getting any output:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:ms="urn:schemas-microsoft-com:xslt"
  xmlns="http://www.w3.org/1999/xhtml">

  <xsl:output method="html" indent="yes"/>
  <xsl:param name="colLabs" select="2"/>

  <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(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() &lt;= $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>
            </tr>
          </xsl:for-each>
        </table>
      </div>
    </xsl:if>

  </xsl:template>

  <xsl:template match="entry">
    <td>
      <xsl:number />
    </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>
</xsl:stylesheet>

Thanks for any help in this. The only other thing I need to have done is to supress the effectiveTime/@value when it's the same as the entry that preceeded it (so only show each date one time).

Thanks again.

Upvotes: 0

Views: 1191

Answers (1)

Tim C
Tim C

Reputation: 70618

Simplifying the issue, I would start off by counting the number of rows you require for the table (I am assuming you want a table per section element here)

<xsl:variable name="rows" select="ceiling(count(entry) div $columns)" />

(Note $columns is a variable that will hold '2' in this instance, but can be changed to any number of columns you want)

This would allow you to select the correct number of entry elements for the start of each row

<xsl:for-each select="entry[position() &lt;= $rows]">

Then it is a case of outputing a table cell for the first element, and then other entry elements in the row. To get the other elements, you would do this:

<xsl:variable name="otherEntries" select="following-sibling::entry[position() mod $rows = 0]" />

The only slight issue here is that you want to output empty cells if there are not enough other entry elements to fill the row. In XSLT 1.0 you will have to use a recursively called named template for this.

Try the following XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" method="xml" />

  <xsl:param name="columns" select="2" />

  <xsl:template match="section">
    <xsl:variable name="rows" select="ceiling(count(entry) div $columns)" />

    <table>
      <xsl:for-each select="entry[position() &lt;= $rows]">
        <tr>
          <xsl:variable name="otherEntries" select="following-sibling::entry[position() mod $rows = 0]" />
          <xsl:apply-templates select="self::*|$otherEntries" />
          <xsl:call-template name="blankentries">
            <xsl:with-param name="entries" select="$columns - count($otherEntries) - 1" />
          </xsl:call-template>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:template>

  <xsl:template match="entry">
    <td>
      <xsl:number />
    </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>
</xsl:stylesheet>

When applied to you XML, the following is output

<table>
  <tr>
    <td>1</td>
    <td>4</td>
  </tr>
  <tr>
    <td>2</td>
    <td>5</td>
  </tr>
  <tr>
    <td>3</td>
    <td />
  </tr>
</table>

Obviously you would then pad this out with the information you needed.

Upvotes: 3

Related Questions