Search code examples
xsltforeachhtml-tableparentchildren

Building a table with the values of children whose parents only have siblings with particular attribute values


I apologize for all of the code, but I couldn't eliminate any more without muddling the clarity of the question.

Suppose I have this XML:

<Dataset>
    <Rec RN="FOO">
        <Fld FN="ID">ID_1</Fld>
        <Fld FN="OPT">0</Fld>
        <Fld FN="DESCRIPTION">DESCRIPTION_1</Fld>
    </Rec>
    <Rec RN="BAR" RC="3">
        <Fld FN="ID">ID_2</Fld>
        <Fld FN="TYPE">TYPE_1</Fld>
    </Rec>
    <Rec RN="BAR">
        <Fld FN="ID">ID_3</Fld>
        <Fld FN="TYPE">TYPE_2</Fld>
    </Rec>
    <Rec RN="FOO">
        <Fld FN="ID">ID_4</Fld>
        <Fld FN="OPT">1</Fld>
        <Fld FN="DESCRIPTION">DESCRIPTION_2</Fld>
    </Rec>
    <Rec RN="BAR" RC="3">
        <Fld FN="ID">ID_5</Fld>
        <Fld FN="TYPE">TYPE_4</Fld>
    </Rec>
    <Rec RN="BAR">
        <Fld FN="ID">ID_6</Fld>
        <Fld FN="TYPE">TYPE_5</Fld>
    </Rec>
    <Rec RN="SPAM">
        <Fld FN="CLASS">CLASS_1</Fld>
    </Rec>
</Dataset>

And the following XSLT:

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

   <xsl:template match="Dataset">
      <html>
         <head>
            <link rel="stylesheet" type="text/css" href="On-line Styles.css"/>
         </head>
         <body>
            <p class="subheading">
               Commands
            </p>
            <p>
               <xsl:choose>
                  <xsl:when test="Rec[@RN='FOO']">
                     <xsl:apply-templates select="Rec[@RN='FOO']"/>
                  </xsl:when>
                  <xsl:otherwise>
                     None
                  </xsl:otherwise>
               </xsl:choose>
            </p>
         </body>
      </html>
   </xsl:template>

   <xsl:template match="Rec[@RN='FOO']">
      <xsl:for-each select=".">
         <xsl:value-of select="Fld[@FN='ID']"/>
         <xsl:if test="Fld[@FN='OPT'] = 1">
            (Optional)
         </xsl:if>
         -
         <xsl:value-of select="Fld[@FN='DESCRIPTION']"/>
         <p>
            <xsl:choose>
               <xsl:when test="../Rec[@RN='FOO']">
                  <table style="border-collapse: separate;" cellspacing="4" border="2"
                  bordercolorlight="#c0c0c0" bordercolordark="#c0c0c0">
                     <tr bgcolor="#C0C0C0">
                        <td style="width: 35%; padding: 6px;" valign="top">
                           <p class="Tableheader" style="margin-bottom: 0;">
                              <span style="font-weight: bold;">ID</span>
                           </p>
                        </td>
                        <td style="width: 45%; padding: 6px;" valign="top">
                           <p class="Tableheader" style="margin-bottom: 0;">
                              <span style="font-weight: bold;">TYPE</span>
                           </p>
                        </td>
                     </tr>
                     <xsl:for-each select="../Rec[@RN='BAR']">
                        <xsl:if test="preceding-sibling::Rec/@RN='FOO'">
                           <tr>
                              <td style="padding: 6px;" valign="top">
                                 <p style="margin-bottom: 0;">
                                    <xsl:choose>
                                       <xsl:when test="Fld[@FN='ID']">
                                          <xsl:value-of select="Fld[@FN='ID']"/>
                                       </xsl:when>
                                       <xsl:otherwise>
                                          <td>&#xA0;</td>
                                       </xsl:otherwise>
                                    </xsl:choose>
                                 </p>
                              </td>
                              <td style="padding: 6px;" valign="top">
                                 <p style="margin-bottom: 0;">
                                    <xsl:choose>
                                       <xsl:when test="Fld[@FN='TYPE']">
                                          <xsl:value-of select="Fld[@FN='TYPE']"/>
                                       </xsl:when>
                                       <xsl:otherwise>
                                          <td>&#xA0;</td>
                                       </xsl:otherwise>
                                    </xsl:choose>
                                 </p>
                              </td>
                           </tr>
                        </xsl:if>
                     </xsl:for-each>
                  </table>
               </xsl:when>
            </xsl:choose>
         </p>
      </xsl:for-each>
   </xsl:template>
</xsl:stylesheet>

I get the following output for the tables:

Table 1

<table style="border-collapse: separate;" cellspacing="4" border="2" bordercolorlight="#c0c0c0" bordercolordark="#c0c0c0">
  <tr bgcolor="#C0C0C0">
    <td style="width: 35%; padding: 6px;" valign="top">
      <p class="Tableheader" style="margin-bottom: 0;">
        <span style="font-weight: bold;">ID</span>
      </p>
    </td>
    <td style="width: 45%; padding: 6px;" valign="top">
      <p class="Tableheader" style="margin-bottom: 0;">
        <span style="font-weight: bold;">TYPE</span>
      </p>
    </td>
  </tr>
  <tr>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">ID_2</p>
    </td>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">TYPE_1</p>
    </td>
  </tr>
  <tr>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">ID_3</p>
    </td>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">TYPE_2</p>
    </td>
  </tr>
  <tr>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">ID_5</p>
    </td>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">TYPE_4</p>
    </td>
  </tr>
  <tr>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">ID_6</p>
    </td>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">TYPE_5</p>
    </td>
  </tr>
</table>

Table 2

<table style="border-collapse: separate;" cellspacing="4" border="2" bordercolorlight="#c0c0c0" bordercolordark="#c0c0c0">
  <tr bgcolor="#C0C0C0">
    <td style="width: 35%; padding: 6px;" valign="top">
      <p class="Tableheader" style="margin-bottom: 0;">
        <span style="font-weight: bold;">ID</span>
      </p>
    </td>
    <td style="width: 45%; padding: 6px;" valign="top">
      <p class="Tableheader" style="margin-bottom: 0;">
        <span style="font-weight: bold;">TYPE</span>
      </p>
    </td>
  </tr>
  <tr>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">ID_2</p>
    </td>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">TYPE_1</p>
    </td>
  </tr>
  <tr>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">ID_3</p>
    </td>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">TYPE_2</p>
    </td>
  </tr>
  <tr>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">ID_5</p>
    </td>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">TYPE_4</p>
    </td>
  </tr>
  <tr>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">ID_6</p>
    </td>
    <td style="padding: 6px;" valign="top">
      <p style="margin-bottom: 0;">TYPE_5</p>
    </td>
  </tr>
</table>

What I need is for each table to only display the ID and TYPE text values for only the <Rec RN="BAR">s following the <Rec RN="FOO"> and before the next <Rec RN="FOO"> or <Rec> with another @RN value. There can be 1 to n number of <Rec RN="BAR"> following a <Rec RN="FOO">. The following example shows the output I need:

Table 1

    <table style="border-collapse: separate;" cellspacing="4" border="2" bordercolorlight="#c0c0c0" bordercolordark="#c0c0c0">
      <tr bgcolor="#C0C0C0">
        <td style="width: 35%; padding: 6px;" valign="top">
          <p class="Tableheader" style="margin-bottom: 0;">
            <span style="font-weight: bold;">ID</span>
          </p>
        </td>
        <td style="width: 45%; padding: 6px;" valign="top">
          <p class="Tableheader" style="margin-bottom: 0;">
            <span style="font-weight: bold;">TYPE</span>
          </p>
        </td>
      </tr>
      <tr>
        <td style="padding: 6px;" valign="top">
          <p style="margin-bottom: 0;">ID_2</p>
        </td>
        <td style="padding: 6px;" valign="top">
          <p style="margin-bottom: 0;">TYPE_1</p>
        </td>
      </tr>
      <tr>
        <td style="padding: 6px;" valign="top">
          <p style="margin-bottom: 0;">ID_3</p>
        </td>
        <td style="padding: 6px;" valign="top">
          <p style="margin-bottom: 0;">TYPE_2</p>
        </td>
      </tr>
   </table>

Table 2

    <table style="border-collapse: separate;" cellspacing="4" border="2" bordercolorlight="#c0c0c0" bordercolordark="#c0c0c0">
      <tr bgcolor="#C0C0C0">
        <td style="width: 35%; padding: 6px;" valign="top">
          <p class="Tableheader" style="margin-bottom: 0;">
            <span style="font-weight: bold;">ID</span>
          </p>
        </td>
        <td style="width: 45%; padding: 6px;" valign="top">
          <p class="Tableheader" style="margin-bottom: 0;">
            <span style="font-weight: bold;">TYPE</span>
          </p>
        </td>
      </tr>
      <tr>
        <td style="padding: 6px;" valign="top">
          <p style="margin-bottom: 0;">ID_5</p>
        </td>
        <td style="padding: 6px;" valign="top">
          <p style="margin-bottom: 0;">TYPE_4</p>
        </td>
      </tr>
      <tr>
        <td style="padding: 6px;" valign="top">
          <p style="margin-bottom: 0;">ID_6</p>
        </td>
        <td style="padding: 6px;" valign="top">
          <p style="margin-bottom: 0;">TYPE_5</p>
        </td>
      </tr>
    </table>

Solution

  • If I understand this correctly (which is not at all certain), you want to do something like this:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:key name="rows" match="Rec[@RN='BAR']" use="generate-id(preceding-sibling::Rec[@RN='FOO'][1])" />
    
    <xsl:template match="/Dataset">
        <html>
            <body>
                <xsl:apply-templates select="Rec[@RN='FOO']"/>
            </body>
        </html>
    </xsl:template>
    
    <xsl:template match="Rec[@RN='FOO']">
        <table border="1">
            <tr>
                <th>ID</th>
                <th>TYPE</th>
            </tr>   
            <xsl:apply-templates select="key('rows', generate-id())"/>
        </table>
        <p/>
    </xsl:template>
    
    <xsl:template match="Rec[@RN='BAR']">
        <tr>
            <td>
                <xsl:value-of select="Fld[@FN='ID']"/>
            </td>
            <td>
                <xsl:value-of select="Fld[@FN='TYPE']"/>
            </td>
        </tr>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Applied to your example input, the result will be:

    <html>
       <body>
          <table border="1">
             <tr>
                <th>ID</th>
                <th>TYPE</th>
             </tr>
             <tr>
                <td>ID_2</td>
                <td>TYPE_1</td>
             </tr>
             <tr>
                <td>ID_3</td>
                <td>TYPE_2</td>
             </tr>
          </table>
          <p></p>
          <table border="1">
             <tr>
                <th>ID</th>
                <th>TYPE</th>
             </tr>
             <tr>
                <td>ID_5</td>
                <td>TYPE_4</td>
             </tr>
             <tr>
                <td>ID_6</td>
                <td>TYPE_5</td>
             </tr>
          </table>
          <p></p>
       </body>
    </html>
    

    rendered as:

    enter image description here