views:

179

answers:

2

I have been messing with xslt off and on since I became a sharepoint administrator, it uses xslt alot for displaying list data. I have recently started using it to transform database results that I have converted to xml using an extension method. I am trying to produce clean html.

My first attempt, worked fine. However I used for-each all over the place, I have since read that is a bad thing to do. I read a bunch of stuff about using keys, but I couldn't understand that or get it to work. So I rewrote this style sheet, below to the one that is below it. It uses templates with no for-each.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

  <xsl:template match="/">


<html>
  <head>
    <link rel="Stylesheet" type="text/css" href="../styles/BoxReportStyle.css" />
  </head>
  <body>


    <span class="BoxReport">
      <h2>Checked Out Boxes by Department with Transaction History</h2>

      Count=<xsl:value-of select="count( /CheckedOutBoxes/row ) "/>

    <!-- Get the divisions, since we are groing to group by division-->
    <xsl:variable name="DivisionList" select="/CheckedOutBoxes/row[ not( Division = preceding-sibling::row/Division ) ]/Division" />

    <xsl:for-each select="$DivisionList">

      <xsl:variable name="DivisionName" select="." />

      <h3>
        <xsl:value-of disable-output-escaping="yes" select="$DivisionName "/>
      </h3>

      <!-- Get the list of departments, so we can group by department -->
      <xsl:variable name="DepartmentList" select="/CheckedOutBoxes/row[ Division = $DivisionName and not( Department = preceding-sibling::row/Department) ]/Department" />

      <xsl:for-each select="$DepartmentList">
        <xsl:variable name="DepartmentName" select="." />

        <h4>
          <xsl:value-of disable-output-escaping="yes" select="$DepartmentName"/>
        </h4>

        <xsl:variable name="Rows" select="/CheckedOutBoxes/row[ Division = $DivisionName and Department = $DepartmentName ]" />

        <!-- Start displaying the checked out box information for this division and department -->
        <table>
          <th>Box Number</th>
          <th>Status Name</th>
          <th>Entry Date</th>
          <th>Description</th>

          <xsl:for-each select="$Rows">

            <tr>
              <td>
                <xsl:value-of select="BoxNumber"/>
              </td>
              <td>
                <xsl:value-of select="StatusName"/>
              </td>
              <td>
                <xsl:value-of select="EntryDate"/>
              </td>
              <td width="200px">
                <xsl:value-of disable-output-escaping="yes" select="Description"/>
              </td>

            </tr>

            <!-- Now display the transaction history if there is any-->
            <xsl:if test=" count( Transaction ) > 0 ">
              <tr>
                <td></td> <!-- One blank row to shift things over-->
                <td colspan="3">
                  <!-- Display transaction table-->
                  <table class="SubTable">
                    <th>Transaction Date</th>
                    <th>Requestor</th>
                    <th>Comments</th>

                    <xsl:for-each select="Transaction" >
                      <tr>
                        <td>
                          <xsl:value-of select="TransactionDate"/>
                        </td>
                        <td>
                          <xsl:value-of select="Requestor"/>
                        </td>
                        <td width="200px">
                          <xsl:value-of disable-output-escaping="yes" select="Comments"/>
                        </td>
                      </tr>
                    </xsl:for-each>
                  </table>
                </td>
              </tr>
            </xsl:if>
          </xsl:for-each>
        </table>

      </xsl:for-each>

    </xsl:for-each>
    </span>


  </body>

</html>


  </xsl:template>



</xsl:stylesheet>

I have now rewrote that to this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;



 <xsl:template match="/">
<html>
  <head>
    <link rel="Stylesheet" type="text/css" href="../styles/BoxReportStyle.css" />
  </head>
  <body>
    <span class="BoxReport">

      <h2>Checked Out Boxes by Department with Transaction History</h2>

      Count=<xsl:value-of select="count( /CheckedOutBoxes/row ) "/>

      <xsl:apply-templates mode="Division" select="/CheckedOutBoxes/row[ not( Division = preceding-sibling::row/Division ) ]"></xsl:apply-templates>


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

 <xsl:template mode="Division" match="row">
<h3>
  <xsl:value-of select="Division" disable-output-escaping="yes"/>
</h3>

<xsl:variable name="DivisionName" select="Division" />

<xsl:apply-templates mode="Department" select="/CheckedOutBoxes/row[ Division = $DivisionName and not( Department = preceding-sibling::row/Department ) ]"></xsl:apply-templates>

</xsl:template>

 <xsl:template mode="Department" match="row">
<h4>
  <xsl:value-of select="Department" disable-output-escaping="yes"/>
</h4>

<xsl:variable name="DivisionName" select="Division" />
<xsl:variable name="DepartmentName" select="Department" />

<table>
  <th>Box Number</th>
  <th>Status Name</th>
  <th>Entry Date</th>
  <th>Description</th>

  <xsl:apply-templates mode="row" select="/CheckedOutBoxes/row[ Division = $DivisionName and Department = $DepartmentName ]" ></xsl:apply-templates>

  </table>



</xsl:template>

<xsl:template mode="row" match="row">

<tr>
  <td>
    <xsl:value-of select="BoxNumber"/>
  </td>
  <td>
    <xsl:value-of select="StatusName"/>
  </td>
  <td>
    <xsl:value-of select="EntryDate"/>
  </td>
  <td width="200px">
    <xsl:value-of disable-output-escaping="yes" select="Description"/>
  </td>

</tr>

<!-- Display Transaction stuff as another row if we have any -->
<xsl:if test=" count( Transaction ) > 0 ">
  <tr>
    <td></td><!-- Shift the transaction over-->
    <td colspan="3">
      <!-- Start Transaction Table -->
      <table class="SubTable">
        <th>Transaction Date</th>
        <th>Requestor</th>
        <th>Comments</th>

        <xsl:apply-templates select="Transaction">
          <xsl:sort order="descending" select="TransactionDate"/>
        </xsl:apply-templates>
      </table>
    </td>
  </tr>
</xsl:if>

</xsl:template>


<xsl:template match="Transaction">
<tr>
  <td>
    <xsl:value-of select="TransactionDate"/>
  </td>
  <td>
    <xsl:value-of select="Requestor"/>
  </td>
  <td width="200px">
    <xsl:value-of disable-output-escaping="yes" select="Comments"/>
  </td>
</tr>
  </xsl:template>

</xsl:stylesheet>

I did not include sample input and output, as that is all auto generated. If it is needed I can take alot of time, and try to produce something.

My question is, is this a better way to do it? Also if the key way is better could someone explain it, or provide a link to a good explanation?

+1  A: 

Basically the question of using for-each vs. templates boils down to creating reusable, more generic transformations.

By using templates, all matching nodes - no only those explicitly used in the for-each - can benefit from the template, which helps avoiding duplicate code and at the same time breaks the sheet into smaller units which are easier to manage. It's actually pretty much the same as having a huge procedure or smaller procedures calling each other in imperative programming.

While some people suggest that using templates may be performing better in some engines, I believe that this is not going to really make a difference.

That said, you may want to learn about the muenchian method (which uses keys) for actually grouping data where you have repeated keys. The use of the preceding-sibling axis is very slow on some engines, so that it it best to avoid it when not absolutely required.

Something like this should do the trick for the divisions (not tested):

<xsl:key name="divisions" match="/CheckedOutBoxes/row/Division" use="." />

...

<xsl:apply-templates mode="Division" select="/CheckedOutBoxes/Division[generate-id(.)=generate-id(key('divisions', .))]" />
Lucero
Does key get all the non duplicate divisions? Maybe I could better understand it if that is what it is doing. Then I could just make another key for department that was basically the same as divisions. Then the select statement of the apply template would make sure I'm doing the departments for the current division, instead of all of them.
Anonymous Coward
The `key()` function in this case basically returns a node-set with all matching `Divisions`. The "magic" here is the `generate-id()` function, which returns a unique identification ID for the first node in the node-set passed into it. Therefore, each `Division` node ID is compared against the ID of the first node with the same content (that's why we use the `key()` function), which will therefore only be matching once per distinct content.
Lucero
Well I think after doing just a little bit of work with the templates to make it where the Division and Department templates matched Division and Department instead of row, and implementing the key stuff I have gotten that to work instead of the preceding-sibling. I understand it all just a little bit right now, but hopefully I will begin to better understand it after studying.
Anonymous Coward
Why are you using an absolute XPath expression as a match pattern in `<xsl:key>`? Please, correct to something more recommendable.
Dimitre Novatchev
@Dimitre, I took the pattern so that the relation to the original source was recognizable.
Lucero
+1  A: 

The "for-each" template is a good feature of XSLT.

The advice to use "templates" instead of "for-each" is mostly about the possible misuse of the XSLT processing model.

In your example is clear: a single naive "template" and many "for-each" that guide the process.

Key usage in XSLT itself is about performance. Its usefulness lies in replacing XPath expressions that involve repetitive travel many nodes of the input tree. Muenchian method of grouping is a special use for keys. Simple grouping can be optimally without the use of keys.

On the other hand, population is a special case of transformation. I think it's better for maintain to separate the XHTML semantic from XSLT transformation. Check www.aranedabienesraices.com.ar as an example.

Alejandro
I think I see where the templates, do break things up. It's almost like function calls. So with the match "/" I can see the overall layout, then I can follow the apply of templates to step through the transformation. I'll probably keep both style sheets.
Anonymous Coward