tags:

views:

544

answers:

3

Given this xml

<Documents>
<Section>
<SectionName>Green</SectionName>
  <Document>
    <FileName>Tier 1 Schedules</FileName>     
  </Document>
  <Document>
    <FileName>Tier 3 Schedules</FileName>      
  </Document>
  <Document>
    <FileName>Setback Schedule</FileName>    
  </Document>
  <Document>
    <FileName>Tier 2 Governance</FileName>    
  </Document>
</Section>
<Section>
<SectionName>MRO/Refurb</SectionName>
  <Document>
    <FileName>Tier 2 Governance</FileName>    
  </Document>
</Section>
</Documents>

What would the xslt be to output this html

<table>
  <tr>
    <td>Green</td>
  </tr>
  <tr>
    <td>Tier 1 Schedules</td>
  </tr>
  <tr>
    <td>Tier 3 Schedules</td>
  </tr>
  <tr>
    <td>Setback Schedule</td>
  </tr>
  <tr>
    <td>Tier 2 Governance</td>
  </tr>
  <tr>
    <td>MRO/Refurb</td>
  </tr>
  <tr>
    <td>Tier 2 Governance</td>
  </tr>
</table>

I could do this in asp.net but not sure how to do the loops in xslt.

Thanks, Al

+1  A: 

Something like that should do the trick:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/">
      <table>
        <xsl:copy>
            <xsl:apply-templates />
        </xsl:copy>
      </table>
    </xsl:template>

  <xsl:template match="SectionName">
    <tr>
      <td>
        <xsl:value-of select="."/>
      </td>
    </tr>
  </xsl:template>

  <xsl:template match="Document">
    <tr>
      <td>
        <xsl:value-of select="FileName"/>
      </td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

Marc

marc_s
A nitpicking remark: Can you say what you included the <copy> element for? :) Maybe your expectations on it's effect are wrong.
Tomalak
Oh sorry - that's a remnant of the Visual Studio default template.... shouldn't be there.
marc_s
+3  A: 
<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />

  <xsl:template match="/">
    <table>
      <xsl:apply-templates select="//SectionName | //FileName" />
    </table>
  </xsl:template>

  <xsl:template match="SectionName | FileName">
    <tr>
      <td><xsl:value-of select="." /></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>
Tomalak
+1 - darn - your answer is precise and even more concise (again!) :-)
marc_s
+2  A: 

Actually, there is a slightly simpler solution than the one proposed by Tomalak:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:strip-space elements="*"/>

    <xsl:template match="/">
     <table>
      <xsl:apply-templates/>
     </table>
    </xsl:template>

    <xsl:template match="SectionName | FileName">
     <tr>
      <td>
       <xsl:value-of select="." />
      </td>
     </tr>
    </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the originally provided XML document, the wanted result is produced:

<table>
   <tr>
      <td>Green</td>
   </tr>
   <tr>
      <td>Tier 1 Schedules</td>
   </tr>
   <tr>
      <td>Tier 3 Schedules</td>
   </tr>
   <tr>
      <td>Setback Schedule</td>
   </tr>
   <tr>
      <td>Tier 2 Governance</td>
   </tr>
   <tr>
      <td>MRO/Refurb</td>
   </tr>
   <tr>
      <td>Tier 2 Governance</td>
   </tr>
</table>

Do note:

  1. The <xsl:apply-templates> instruction doesn't explicitly specify a nodelist that must be processed. Here we rely on the fact that the only non-white-space nodes in the document are the ones that are children of nodes for which we have a matching template.

  2. For this particular XML document we could even change the match pattern of <xsl:template match="SectionName | FileName"> to just: <xsl:template match="text()"> and the transformation would still produce the wanted result.

  3. The use of the <xsl:strip-space> directive.

Dimitre Novatchev
I thought explicitly selecting the nodes one wants to apply templates to would result in better performance. In your solution all nodes are visited, not just the ones of interest. Is that true or does this make no difference? I'm unsure, maybe my "//SectionName | //FileName" is the bigger performance killer.
Tomalak
@Tomalak If an XSLT processor does not optimize well, selecting the nodes specified by: "//SectionName | //FileName" can cause two complete scans of the whole XML document. It is always good to avoid the "//" abbreviation, if possible.
Dimitre Novatchev
I see. Let's assume for a second the source document contained many more nodes than we would want for the output. How would you go about ignoring them? Like this: <xsl:template match="*" />?
Tomalak
@Tomalak I would rather use: <xsl:template match="*[not self:SectionName or self::FileName]/text()"/>If there are many elements that could have the text we are interested in, in XSLT 1.0 I would have to use an <xsl:if> in the body of the template. In XSLT 2.0 one can use a variable reference inside the match pattern.
Dimitre Novatchev