This question has already been solved
You
From help I received yesterday I have this code that will parse through an XML file generating an output from various nodes based on the relationships between them.
Example XML
<gedcom>
<INDI ID=@I001@>
<FAMC>@F001@</FAMC>
...
</INDI>
<INDI ID=@I002@>
<FAMC>@F002@</FAMC>
...
</INDI>
<INDI ID=@I003@>
<FAMC>@F003@</FAMC>
...
</INDI>
...
<FAM ID=@F001@>
<HUSB>@I002@</HUSB>
<WIFE>@I003@</WIFE>
...
</FAM>
<FAM ID=@F002@>
<HUSB>@I004@</HUSB>
<WIFE>@I005@</WIFE>
...
</FAM>
<FAM ID=@F003@>
<HUSB>@I006@</HUSB>
<WIFE>@I007@</WIFE>
...
</FAM>
...
</gedcom>
The way this data works is an individual [INDI] record is related to a family [FAM] record by the id value of its family child [FAMC] node. The family record then relates to two other individual records by the id value of its husband [HUSB] and wife [WIFE] nodes.
This XSLT code will parse through that XML data and properly list the individual records.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://exslt.org/math"
exclude-result-prefixes="math">
<xsl:include href="math.power.xsl"/>
<xsl:output indent="yes" method="html"/>
<xsl:template match="/">
<html>
<head>
<title>Pedigree Chart</title>
</head>
<body>
<table>
<xsl:apply-templates select="/gedcom/INDI[@ID='@I001@']">
<xsl:with-param name="generation">0</xsl:with-param>
</xsl:apply-templates>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="INDI">
<xsl:param name="generation"/>
<td>
<xsl:attribute name="rowspan">
<xsl:call-template name="math:power">
<xsl:with-param name="base">2</xsl:with-param>
<xsl:with-param name="power"><xsl:value-of select="#url.gens - 1# - $generation"/></xsl:with-param>
</xsl:call-template>
</xsl:attribute>
<xsl:value-of select="$generation"/>
<xsl:value-of select="./NAME/text()"/><br/>
</td>
<xsl:apply-templates select="FAMC">
<xsl:with-param name="generation"><xsl:value-of select="$generation"/></xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="FAMC">
<xsl:param name="generation"/>
<xsl:if test="$generation < #url.gens-1#">
<xsl:variable name="data" select="."/>
<xsl:apply-templates select="../../FAM[@ID=$data]">
<xsl:with-param name="generation"><xsl:value-of select="$generation + 1"/></xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="FAM">
<xsl:param name="generation"/>
<xsl:apply-templates select="HUSB|WIFE">
<xsl:with-param name="generation"><xsl:value-of select="$generation"/></xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="HUSB|WIFE">
<xsl:param name="generation"/>
<xsl:variable name="data" select="."/>
<xsl:apply-templates select="../../INDI[@ID=$data]">
<xsl:with-param name="generation"><xsl:value-of select="$generation"/></xsl:with-param>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
This will produce output like this:
<table>
<td rowspan="16">0Ian Lee /SKINNER/</td>
<td rowspan="8">1John Howard /SKINNER/</td>
<td rowspan="4">2Lee Vernon /SKINNER/</td>
<td rowspan="2">3Albert Joshua /SKINNER/</td>
<td rowspan="1">4William Harrison /SKINNER/</td>
<td rowspan="1">4Anna /FORDYCE/</td>
<td rowspan="2">3Pheobe Lucinda /SCOTT/</td>
<td rowspan="1">4John M. /SCOTT/</td>
<td rowspan="1">4Mary Caroline /DINNELL/</td>
<td rowspan="4">2NaDean Adelena /WEHMEYER/</td>
<td rowspan="2">3Louis George /WEHMEYER/</td>
<td rowspan="1">4August Detrich /WEHMEYER/</td>
<td rowspan="1">4Adlina /ELGERT/</td>
<td rowspan="2">3Ida Gertrude /KAUFFMAN/</td>
<td rowspan="1">4Joseph J. /KAUFFMAN/</td>
<td rowspan="1">4Emma /KNAU/</td>
<td rowspan="8">1Judy Pauline /JETER/</td>
<td rowspan="4">2Killis Walton /JETER/ Jr.</td>
<td rowspan="2">3Killis Walton /JETER/</td>
<td rowspan="1">4Argulas Beaugard /JETER/</td>
<td rowspan="1">4Henrietta Rebeca /COX/</td>
<td rowspan="2">3Bertha Maye /OLSEN/</td>
<td rowspan="1">4Henry Bertinies /OLSEN/</td>
<td rowspan="1">4Martha Ellen /RANEY/</td>
<td rowspan="4">2Clara Irene /FARSON/</td>
<td rowspan="2">3Harry Herman /FARSON/</td>
<td rowspan="1">4Harry Charles /SMITH/</td>
<td rowspan="1">4Anna Bell /FARSON/</td>
<td rowspan="2">3La Una Jane /LIGHTFRITZ/</td>
<td rowspan="1">4/Unknown/</td>
<td rowspan="1">4Mary Elizabeth /CONNOR/</td>
</table>
I want that output to be grouped like this:
<tr>
<td rowspan="16">0Ian Lee /SKINNER/</td>
<td rowspan="8">1John Howard /SKINNER/</td>
<td rowspan="4">2Lee Vernon /SKINNER/</td>
<td rowspan="2">3Albert Joshua /SKINNER/</td>
<td rowspan="1">4William Harrison /SKINNER/</td>
</tr>
<tr>
<td rowspan="1">4Anna /FORDYCE/</td>
</tr>
<tr>
<td rowspan="2">3Pheobe Lucinda /SCOTT/</td>
<td rowspan="1">4John M. /SCOTT/</td>
</tr>
<tr>
<td rowspan="1">4Mary Caroline /DINNELL/</td>
</tr>
<tr>
<td rowspan="4">2NaDean Adelena /WEHMEYER/</td>
<td rowspan="2">3Louis George /WEHMEYER/</td>
<td rowspan="1">4August Detrich /WEHMEYER/</td>
</tr>
<tr>
<td rowspan="1">4Adlina /ELGERT/</td>
</tr>
<tr>
<td rowspan="2">3Ida Gertrude /KAUFFMAN/</td>
<td rowspan="1">4Joseph J. /KAUFFMAN/</td>
</tr>
<tr>
<td rowspan="1">4Emma /KNAU/</td>
</tr>
<tr>
<td rowspan="8">1Judy Pauline /JETER/</td>
<td rowspan="4">2Killis Walton /JETER/ Jr.</td>
<td rowspan="2">3Killis Walton /JETER/</td>
<td rowspan="1">4Argulas Beaugard /JETER/</td>
</tr>
<tr>
<td rowspan="1">4Henrietta Rebeca /COX/</td>
</tr>
<tr>
<td rowspan="2">3Bertha Maye /OLSEN/</td>
<td rowspan="1">4Henry Bertinies /OLSEN/</td>
</tr>
<tr>
<td rowspan="1">4Martha Ellen /RANEY/</td>
</tr>
<tr>
<td rowspan="4">2Clara Irene /FARSON/</td>
<td rowspan="2">3Harry Herman /FARSON/</td>
<td rowspan="1">4Harry Charles /SMITH/</td>
</tr>
<tr>
<td rowspan="1">4Anna Bell /FARSON/</td>
</tr>
<tr>
<td rowspan="2">3La Una Jane /LIGHTFRITZ/</td>
<td rowspan="1">4/Unknown/</td>
</tr>
<tr>
<td rowspan="1">4Mary Elizabeth /CONNOR/</td>
</tr>
I have this demonstration xslt that will create the desired grouping without relation to the XML.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://exslt.org/math"
exclude-result-prefixes="math">
<xsl:include href="math.power.xsl"/>
<xsl:template match="/">
<p>Recursive Table Loop</p>
<table border="1" cellspacing="0" cellpadding="5">
<xsl:call-template name="rows">
<xsl:with-param name="row" select="0"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="rows">
<xsl:param name="row"/>
<tr>
<xsl:call-template name="cells">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="cell" select="4"/>
</xsl:call-template>
</tr>
<xsl:if test="$row < 15">
<xsl:call-template name="rows">
<xsl:with-param name="row" select="$row + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="cells">
<xsl:param name="row"/>
<xsl:param name="cell"/>
<xsl:variable name="outputCell">
<xsl:call-template name="math:power">
<xsl:with-param name="base">2</xsl:with-param>
<xsl:with-param name="power"><xsl:value-of select="$cell"/></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:if test="$row mod $outputCell = 0">
<td>
<xsl:attribute name="rowspan">
<xsl:value-of select="$outputCell"/>
</xsl:attribute>
<xsl:value-of select="$row"/> :
<xsl:value-of select="$cell"/> :
<xsl:value-of select="$outputCell"/>
</td>
</xsl:if>
<xsl:if test="$cell > 0">
<xsl:call-template name="cells">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="cell" select="$cell - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This will produce a table with the desired row spanning cells like this.
p>Recursive Table Loop</p>
<table cellspacing="0" cellpadding="5" border="1">
<tr>
<td rowspan="16">0 : 4 : 16</td>
<td rowspan="8">0 : 3 : 8</td>
<td rowspan="4">0 : 2 : 4</td>
<td rowspan="2">0 : 1 : 2</td>
<td rowspan="1">0 : 0 : 1</td>
</tr>
<tr>
<td rowspan="1">1 : 0 : 1</td>
</tr>
<tr>
<td rowspan="2">2 : 1 : 2</td>
<td rowspan="1">2 : 0 : 1</td>
</tr>
<tr>
<td rowspan="1">3 : 0 : 1</td>
</tr>
<tr>
<td rowspan="4">4 : 2 : 4</td>
<td rowspan="2">4 : 1 : 2</td>
<td rowspan="1">4 : 0 : 1</td>
</tr>
<tr>
<td rowspan="1">5 : 0 : 1</td>
</tr>
<tr>
<td rowspan="2">6 : 1 : 2</td>
<td rowspan="1">6 : 0 : 1</td>
</tr>
<tr>
<td rowspan="1">7 : 0 : 1</td>
</tr>
<tr>
<td rowspan="8">8 : 3 : 8</td>
<td rowspan="4">8 : 2 : 4</td>
<td rowspan="2">8 : 1 : 2</td>
<td rowspan="1">8 : 0 : 1</td>
</tr>
<tr>
<td rowspan="1">9 : 0 : 1</td>
</tr>
<tr>
<td rowspan="2">10 : 1 : 2</td>
<td rowspan="1">10 : 0 : 1</td>
</tr>
<tr>
<td rowspan="1">11 : 0 : 1</td>
</tr>
<tr>
<td rowspan="4">12 : 2 : 4</td>
<td rowspan="2">12 : 1 : 2</td>
<td rowspan="1">12 : 0 : 1</td>
</tr>
<tr>
<td rowspan="1">13 : 0 : 1</td>
</tr>
<tr>
<td rowspan="2">14 : 1 : 2</td>
<td rowspan="1">14 : 0 : 1</td>
</tr>
<tr>
<td rowspan="1">15 : 0 : 1</td>
</tr>
</table>
But I can not figure out how to marry the two XSLT codes together. The trouble is that in the first code the INDI is the base of a multiple function recursive loop. And I just can not figure out how to break out of that loop at the relevant points to wrap the content in table row tags.
It took a good deal of trial and error, but I finally figured out how to generate an HTML table representation of a pedigree chart from an XML version of a gedcom file.
Here is the XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://exslt.org/math"
exclude-result-prefixes="math">
<!-- include math.power for the power functions -->
<xsl:include href="math.power.xsl"/>
<!-- We are outputing an HTML table representation of the pedigree chart -->
<xsl:output indent="yes" method="html"/>
<!-- Base template function -->
<xsl:template match="/">
<html>
<head>
<title>Pedigree Chart</title>
</head>
<body>
<p>Pedigree Chart</p>
<table border="1" cellspacing="0" cellpadding="5">
<!-- call rows function with the ID of the first individual in the pedigree chart -->
<xsl:call-template name="rows">
<xsl:with-param name="INDI_ID" select="'@I001@'"/>
</xsl:call-template>
</table>
</body>
</html>
</xsl:template>
<xsl:template name="rows">
<xsl:param name="row" select="0"/>
<xsl:param name="INDI_ID"/>
<tr>
<!-- call the cells function -->
<xsl:call-template name="cells">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="cell" select="4"/>
<xsl:with-param name="INDI_ID" select="$INDI_ID"/>
</xsl:call-template>
</tr>
<!-- if all the rows have not been output, call the next row function -->
<xsl:if test="$row < 15">
<xsl:call-template name="rows">
<xsl:with-param name="row" select="$row + 1"/>
<xsl:with-param name="INDI_ID" select="$INDI_ID"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="cells">
<xsl:param name="row"/>
<xsl:param name="cell"/>
<xsl:param name="INDI_ID"/>
<!-- get the node for the current individual by id -->
<xsl:variable name="data" select="/gedcom/INDI[@ID=$INDI_ID]"/>
<!-- calculate the power of this cell -->
<xsl:variable name="cellPower">
<xsl:call-template name="math:power">
<xsl:with-param name="base">2</xsl:with-param>
<xsl:with-param name="power"><xsl:value-of select="$cell"/></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<!-- determine the next cells value (must be kept greater than or equal to zero -->
<xsl:variable name="power">
<xsl:choose>
<xsl:when test="$cell > 0">
<xsl:value-of select="$cell - 1"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- calculate the power of the next cell -->
<xsl:variable name="nextCellPower">
<xsl:call-template name="math:power">
<xsl:with-param name="base">2</xsl:with-param>
<xsl:with-param name="power" select="$power"/>
</xsl:call-template>
</xsl:variable>
<!-- get the integer of the current row divided by the next cell power -->
<xsl:variable name="genderRow" select="floor($row div $nextCellPower)"/>
<!-- if gender row value is even then next cell will be a male ancestor, otherwise a female ancestor -->
<xsl:variable name="gender">
<xsl:choose>
<xsl:when test="$genderRow mod 2 = 0">M</xsl:when>
<xsl:otherwise>F</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- if the remainder of the current row divided by the current cell power is 0, output a cell -->
<xsl:if test="$row mod $cellPower = 0">
<td>
<!-- the current cell will span a number of rows equal to its power -->
<xsl:attribute name="rowspan">
<xsl:value-of select="$cellPower"/>
</xsl:attribute>
<xsl:value-of select="$INDI_ID"/><br/>
<xsl:value-of select="$data/NAME/text()"/>
</td>
</xsl:if>
<!-- if there are still cells to process call the next cell function -->
<xsl:if test="$cell > 0">
<xsl:call-template name="cells">
<xsl:with-param name="row" select="$row"/>
<xsl:with-param name="cell" select="$cell - 1"/>
<xsl:with-param name="INDI_ID">
<!-- to determine the id of the next individual call the INDI function -->
<xsl:apply-templates select="/gedcom/INDI[@ID=$INDI_ID]">
<!-- pass the gender value to determine if we want the male or female ancestor -->
<xsl:with-param name="gender" select="$gender"/>
</xsl:apply-templates>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="INDI">
<!-- call the FAMC function to get the id of the family node for the current individual's parents -->
<xsl:param name="gender"/>
<xsl:apply-templates select="FAMC">
<xsl:with-param name="gender" select="$gender"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="FAMC">
<!-- fetch the family node to get the ids to the parent's individual nodes -->
<xsl:param name="gender"/>
<xsl:variable name="data" select="."/>
<xsl:apply-templates select="../../FAM[@ID=$data]">
<xsl:with-param name="gender" select="$gender"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="FAM">
<!-- fetch the individual id for the current individual's father or mother depending on what gender was requested -->
<xsl:param name="gender"/>
<xsl:choose>
<xsl:when test="$gender = 'M'">
<xsl:apply-templates select="HUSB"/>
</xsl:when>
<xsl:when test="$gender = 'F'">
<xsl:apply-templates select="WIFE"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="HUSB|WIFE">
<!-- return the id value -->
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
And that will output this HTML, making a nice simple pedigree chart:
<table cellspacing="0" cellpadding="5" border="1">
<tr>
<td rowspan="16">@I001@Ian Lee /SKINNER/</td>
<td rowspan="8">@I005@John Howard /SKINNER/</td>
<td rowspan="4">@I008@Lee Vernon /SKINNER/</td>
<td rowspan="2">@I016@Albert Joshua /SKINNER/</td>
<td rowspan="1">@I040@William Harrison /SKINNER/</td>
</tr>
<tr>
<td rowspan="1">@I041@Anna /FORDYCE/</td>
</tr>
<tr>
<td rowspan="2">@I017@Pheobe Lucinda /SCOTT/</td>
<td rowspan="1">@I052@John M. /SCOTT/</td>
</tr>
<tr>
<td rowspan="1">@I053@Mary Caroline /DINNELL/</td>
</tr>
<tr>
<td rowspan="4">@I009@NaDean Adelena /WEHMEYER/</td>
<td rowspan="2">@I056@Louis George /WEHMEYER/</td>
<td rowspan="1">@I058@August Detrich /WEHMEYER/</td>
</tr>
<tr>
<td rowspan="1">@I059@Adlina /ELGERT/</td>
</tr>
<tr>
<td rowspan="2">@I057@Ida Gertrude /KAUFFMAN/</td>
<td rowspan="1">@I076@Joseph J. /KAUFFMAN/</td>
</tr>
<tr>
<td rowspan="1">@I077@Emma /KNAU/</td>
</tr>
<tr>
<td rowspan="8">@I007@Judy Pauline /JETER/</td>
<td rowspan="4">@I011@Killis Walton /JETER/ Jr.</td>
<td rowspan="2">@I027@Killis Walton /JETER/</td>
<td rowspan="1">@I078@Argulas Beaugard /JETER/</td>
</tr>
<tr>
<td rowspan="1">@I079@Henrietta Rebeca /COX/</td>
</tr>
<tr>
<td rowspan="2">@I028@Bertha Maye /OLSEN/</td>
<td rowspan="1">@I098@Henry Bertinies /OLSEN/</td>
</tr>
<tr>
<td rowspan="1">@I099@Martha Ellen /RANEY/</td>
</tr>
<tr>
<td rowspan="4">@I012@Clara Irene /FARSON/</td>
<td rowspan="2">@I038@Harry Herman /FARSON/</td>
<td rowspan="1">@I106@Harry Charles /SMITH/</td>
</tr>
<tr>
<td rowspan="1">@I107@Anna Bell /FARSON/</td>
</tr>
<tr>
<td rowspan="2">@I039@La Una Jane /LIGHTFRITZ/</td>
<td rowspan="1">@I110@/Unknown/</td>
</tr>
<tr>
<td rowspan="1">@I109@Mary Elizabeth /CONNOR/</td>
</tr>
</table>