Reputation: 129
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
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