user3724711
user3724711

Reputation: 129

multi-nested loop by XSL to convert a XML to Table not fully loaded

Thanks for multi nested loops by XSL to convert a XML to Table How to use XSLT to convert a XML to Table [CODE UPDATED 11/6] , now I am able to convert xml to table by XSLT, however when I add in one more nested loop, the result is not fully loaded, I am not sure what went wrong.

Data XML

<Records>
    <Person id="756252" date="15-Oct-2014">
        <Gender>Male</Gender>
        <NameDetails>
            <Name NameType="Primary Name">
                <NameValue>
                    <FirstName>Ken</FirstName>
                    <Surname>Wu</Surname>
                </NameValue>
            </Name>
            <Name NameType="AKA">
                <NameValue>
                    <FirstName>Kenneth</FirstName>
                    <Surname>Wu</Surname>
                </NameValue>
            </Name>
            <Name NameType="AKA2">
                <NameValue>
                    <FirstName>CAN</FirstName>
                    <Surname>Wu</Surname>
                </NameValue>
            </Name>
        </NameDetails>
        <Descriptions>
            <Description Description1="11" Description2="12" Description3="13"/>
            <Description Description1="21" Description2="22" Description3="23"/>
            <Description Description1="31" Description2="32" Description3="33"/>
        </Descriptions>
        <RoleDetail>
            <Roles RoleType="Primary">
                <OccTitle SinceDay="17" SinceMonth="Nov" SinceYear="2009" OccCat="6">Thai</OccTitle>
            </Roles>
        </RoleDetail>
        <DateDetails>
        <Date DateType="Date of Birth">
            <DateValue Year="1990" />
            <DateValue Year="1991" />
        </Date>
        <Date DateType="Date of Issue">
            <DateValue Year="2000" />
            <DateValue Year="2001" />
        </Date>
        </DateDetails>
    </Person>
    <Person id="253555" date="14-Oct-2014">
        <Gender>Male</Gender>
        <NameDetails>
            <Name NameType="Primary Name">
                <NameValue>
                    <FirstName>Peter</FirstName>
                    <Surname>Lai</Surname>
                </NameValue>
            </Name>
        </NameDetails>
        <Descriptions>
            <Description Description1="11" Description2="12" Description3="13"/>
            <Description Description1="21" Description2="22"/>
        </Descriptions>
        <Date DateType="Date of Birth">
            <DateValue Year="1992" />
        </Date>
    </Person>
</Records>

XSLT file

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <html>
            <head>
                <title>Records</title>
            </head>
            <body>
                <table border="1">
                    <tr>
                        <th>ID</th>
                        <th>Date</th>
                        <th>Gender</th>
                        <th>NameType</th>
                        <th>FirstName</th>
                        <th>SurName</th>
                        <th>Description1</th>
                        <th>Description2</th>
                        <th>Description3</th>
                        <th>RoleType</th>
                        <th>OccTitle</th>
                        <th>SinceDay</th>
                        <th>SinceMonth</th>
                        <th>DateType</th>
                        <th>Year</th>
                    </tr>
                    <xsl:for-each select="Records/Person/NameDetails/Name">
                        <xsl:variable name="name" select="."/>
                        <xsl:for-each select="../../DateDetails/Date/DateValue">
                            <xsl:variable name="dateval" select="."/>
                            <xsl:for-each select="../../../RoleDetail/Roles/OccTitle">
                                <xsl:variable name="occTitle" select="."/>
                                <xsl:for-each select="../../../Descriptions/Description">
                                    <tr>
                                        <td>
                                            <xsl:value-of select="../../@id"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="../../@date"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="../../Gender"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$name/@NameType"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$name/NameValue/FirstName"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$name/NameValue/Surname"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="@Description1"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="@Description2"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="@Description3"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$occTitle/../@RoleType"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$occTitle"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$occTitle/@SinceDay"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$occTitle/@SinceMonth"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$dateval/../@DateType"/>
                                        </td>
                                        <td>
                                            <xsl:value-of select="$dateval/@Year"/>
                                        </td>
                                    </tr>
                                </xsl:for-each>
                            </xsl:for-each>
                        </xsl:for-each>
                    </xsl:for-each>
                </table>
            </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

my expected outcome

ID     |Date        |Gender |NameType     |FirstName |SurName |Description1 |Description2 |Description3 |RoleType |OccTitle |SinceDay |SinceMonth |DateType      |Year 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
756252 |15-Oct-2014 |Male   |Primary Name |Ken       |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
756252 |15-Oct-2014 |Male   |AKA          |Kenneth   |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Birth |1990 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Birth |1991 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Issue |2000 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |11           |12           |13           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |21           |22           |23           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
756252 |15-Oct-2014 |Male   |AKA2         |CAN       |Wu      |31           |32           |33           |Primary  |Thai     |17       |Nov        |Date of Issue |2001 
253555 |14-Oct-2014 |Male   |Primary Name |Peter     |Lai     |11           |12           |13           |         |         |         |           |Date of Issue |1992
253555 |14-Oct-2014 |Male   |Primary Name |Peter     |Lai     |21           |22           |             |         |         |         |           |Date of Issue |1992

Now the question is, the last TWO lines is not showing due to RoleDetail missing in the second person

for data, the below node may or may not come in (XML data file), if this node is not coming in XML, just leave it empty, otherwise it should be loop also.

<RoleDetail>
    <Roles RoleType="Primary">
        <OccTitle SinceDay="17" SinceMonth="Nov" SinceYear="2009" OccCat="6">Thai</OccTitle>
    </Roles>
</RoleDetail>

**It will have around 35 optional elements like this.. thanks a lot

Upvotes: 1

Views: 108

Answers (1)

Marcus Rickert
Marcus Rickert

Reputation: 4238

As @michael.hor257k already pointed out in the answer to your previous question: what you actually have as result is a matrix spanned by your <for-each> loops. If at least one of the matrix dimensions is empty the product will be empty.

So, we have to "cheat" a little bit. The basic idea is the following: for each dimension of your matrix we provide a select expression selecting the dimension elements if they exist PLUS a kind of dummy entry if not a single dimension entry exists. In your case the Person is suitable for this since we know that we have exactly one occurrence of this entity for each matrix. We create a variable person as a reference to this base element.

Instead of writing, e.g.

<xsl:for-each select="../../DateDetails/Date/DateValue">

we write

<xsl:for-each select="$person/DateDetails/Date/DateValue|$person[not ($person/DateDetails/Date/DateValue)]">

The first part before the | will return all existing dates. If no date exists this set will be empty. The second part after the | will return the <Person> node (exactly one!) if we did not find any dates. So in case there are no dates, instead of a zero size dimension you will get a dimension of size one.

But, why does this work in case of a zero size dimension? It works, because the subsequent attempts to extract e.g. the date values data for the dummy element will result in empty strings which happens to be the desired default value. In order to make sure we don't obtain any other white space I added the normalize-space() calls.

Note that you could actually also provide default values for a zero size dimension by replacing the variable assignments by <choose> blocks.

This is the XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
  <xsl:template match="/">
    <html>
      <head>
        <title>Records</title>
      </head>
      <body>
        <table border="1">
          <tr>
            <th>ID</th>
            <th>Date</th>
            <th>Gender</th>
            <th>NameType</th>
            <th>FirstName</th>
            <th>SurName</th>
            <th>Description1</th>
            <th>Description2</th>
            <th>Description3</th>
            <th>RoleType</th>
            <th>OccTitle</th>
            <th>SinceDay</th>
            <th>SinceMonth</th>
            <th>DateType</th>
            <th>Year</th>
          </tr>
          <xsl:for-each select="Records/Person">
            <xsl:variable name="person" select="."/>
            <xsl:for-each select="$person/NameDetails/Name">
              <xsl:variable name="name" select="."/>
              <xsl:for-each select="$person/DateDetails/Date/DateValue|$person[not ($person/DateDetails/Date/DateValue)]">
                <xsl:variable name="dateval" select="."/>
                <xsl:for-each select="$person/RoleDetail/Roles/OccTitle|$person[not ($person/RoleDetail/Roles/OccTitle)]">
                  <xsl:variable name="occTitle" select="self::OccTitle"/>
                  <xsl:for-each select="$person/Descriptions/Description|$person[not ($person/Descriptions/Description)]">
                    <tr>
                      <td>
                        <xsl:value-of select="normalize-space(../../@id)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space(../../@date)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space(../../Gender)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($name/@NameType)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($name/NameValue/FirstName)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($name/NameValue/Surname)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space(@Description1)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space(@Description2)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space(@Description3)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($occTitle/../@RoleType)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($occTitle)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($occTitle/@SinceDay)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($occTitle/@SinceMonth)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($dateval/../@DateType)"/>
                      </td>
                      <td>
                        <xsl:value-of select="normalize-space($dateval/@Year)"/>
                      </td>
                    </tr>
                  </xsl:for-each>
                </xsl:for-each>
              </xsl:for-each>
            </xsl:for-each>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

EDIT

As suggested by @michael.hor257k (Thanks!) something needs to be done about not accidentally outputting the text nodes of a $person in case no titles are found. The problem is due to the fact that the extraction of the title name (<xsl:value-of select="normalize-space($occTitle)"/>) is the only one that does not contain any further selection by node names or attributes names. In order to fix this I replaced the statement

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

by

<xsl:variable name="occTitle" select="self::OccTitle"/>

Upvotes: 2

Related Questions