Home > database >  How to count and evaluate nodes in XML to create rowspan values in HTML table?
How to count and evaluate nodes in XML to create rowspan values in HTML table?

Time:11-12

I have an xml document. And i have some nodes. 1.Management(which has some Upravlinnya), 2.Upravlinnya(which has some viddils) and that viddils (it is like department)contains some workers And I solve my problem as you can see only with viddils. But i actually do not know why it is not working whith others. My Table output(by now): enter image description here


Note that this is not a strictly correct solution. Ideally, the first row of every section should contain a cell for every column in the table, and likewise, the first row of each subsection should contain a cell for each of the columns remaining to the right-hand side of the subsection's column.

Because of this, the result is very slightly skewed: the top borders of the cells in the same row do not align perfectly. However, I believe it's a small price to pay for such simplicity of code.

CodePudding user response:

The key is to be able, for each row you want to build, to find out if the cells should be added with a rowspan that covers their descendant rows, which you were attempting to do with:

<xsl:if test="position() = 1">

But that test is only run in the context of Worker, so effectively your logic looks like this:

<xsl:if test="Worker first in Viddil">
  <td rowspan="{last()}">
    <xsl:value-of select="ancestor::Management[1]/ManagementName"/>
  </td>
</xsl:if>

<xsl:if test="Worker first in Viddil">
  <td rowspan="{last()}">
    <xsl:value-of select="ancestor::Upravlinnya[1]/UpravlinnyaName"/>
  </td>
</xsl:if>

<xsl:if test="Worker first in Viddil">
  <td rowspan="{last()}">
    <xsl:value-of select="ancestor::Viddil[1]/ViddilName"/>
  </td>
</xsl:if>

and that's why only Viddil looks correct, because the logic is correct only for Viddil; and that same Viddil-only logic is applied to Management and Upravlinnya, and it looks wrong.

In my version, I'm still testing from the context of a Worker, but I'm "reaching back up the tree" to the nearest ancestor that matters, and asking if that ancestor is the first. I'm doing that with a combination of ancestor and preceding-sibiling axes:

<xsl:variable 
    name="firstUpravlinnya"
    select="count(ancestor::Upravlinnya/preceding-sibling::Upravlinnya) = 0"/>

All that says is, "as a Worker, find my Upravlinnya, and is my Upravlinnya the first Upravlinnya amongst all the Upravlinnyas under my Management (no siblings before/preceding it)?"

I also chose not to do this all in the nested for-each loops, which would work, but become difficult to read. This uses the natural template matching characteristics of XSLT to just traverse a tree of nodes:

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

  <xsl:template match="/Managements">
    <html>
      <body>
        <table border="1">
          <tr bgcolor="#9acd32">
            <th>Management Name</th>
            <th>Upravlinnya Name</th>
            <th>Viddil Name</th>
            <th>Name</th>
            <th>Surname</th>
            <th>Birth Year</th>
          </tr>

          <!-- Find all Workers, under /Managements (your "root") -->
          <xsl:apply-templates select=".//Worker"/>

        </table>
      </body>
    </html>
  </xsl:template>


  <!--
    Simply match on Worker, and call row-builder to do actual work
  -->
  <xsl:template match="Worker">
    <xsl:call-template name="row-builder"/>
  </xsl:template>

  <xsl:template name="row-builder">

    <!-- 
      From Worker, find relative positions of ancestors (if they're the first) 
    -->
    <xsl:variable name="firstUpravlinnya" select="count(ancestor::Upravlinnya/preceding-sibling::Upravlinnya) = 0"/>
    <xsl:variable name="firstViddil" select="count(ancestor::Viddil/preceding-sibling::Viddil) = 0"/>
    <xsl:variable name="firstWorker" select="count(preceding-sibling::Worker) = 0"/>

    <!--
      Build a row from left to right, Management to Worker.
    -->

    <tr>
      <!-- 
        One Management cell for all descendant Upravlinnyas, Viddils, & Workers
      -->
      <xsl:if test="$firstUpravlinnya and $firstViddil and $firstWorker">
        <xsl:variable name="mgmtRowSpan" select="count(ancestor::Management//Worker)"/>
        <td rowspan="{$mgmtRowSpan}">
          <xsl:value-of select="ancestor::Management/ManagementName"/>
        </td>
      </xsl:if>

      <!-- 
        One Upravlinnya cell for all descendant Viddils & Workers
      -->
      <xsl:if test="$firstViddil and $firstWorker">
        <xsl:variable name="upravlinnyaRowSpan" select="count(ancestor::Upravlinnya//Worker)"/>
        <td rowspan="{$upravlinnyaRowSpan}">
          <xsl:value-of select="ancestor::Upravlinnya/UpravlinnyaName"/>
        </td>
      </xsl:if>

      <!-- 
        One Viddil cell for all descendant Workers
      -->
      <xsl:if test="$firstWorker">
        <xsl:variable name="viddilRowSpan" select="count(ancestor::Viddil//Worker)"/>
        <td rowspan="{$viddilRowSpan}">
          <xsl:value-of select="ancestor::Viddil/ViddilName"/>
        </td>
      </xsl:if>

      <!-- Always add cells for a Worker -->
      <td><xsl:value-of select="WorkerName"/></td>
      <td><xsl:value-of select="WorkerSurname"/></td>
      <td><xsl:value-of select="BirthYear"/></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>

Here's how it looks:

enter image description here

  • Related