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 &lt; #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/<br></td>
<td rowspan="8">1John Howard /SKINNER/<br></td>
<td rowspan="4">2Lee Vernon /SKINNER/<br></td>
<td rowspan="2">3Albert Joshua /SKINNER/<br></td>
<td rowspan="1">4William Harrison /SKINNER/<br></td>
<td rowspan="1">4Anna /FORDYCE/<br></td>
<td rowspan="2">3Pheobe Lucinda /SCOTT/<br></td>
<td rowspan="1">4John M. /SCOTT/<br></td>
<td rowspan="1">4Mary Caroline /DINNELL/<br></td>
<td rowspan="4">2NaDean Adelena /WEHMEYER/<br></td>
<td rowspan="2">3Louis George /WEHMEYER/<br></td>
<td rowspan="1">4August Detrich /WEHMEYER/<br></td>
<td rowspan="1">4Adlina /ELGERT/<br></td>
<td rowspan="2">3Ida Gertrude /KAUFFMAN/<br></td>
<td rowspan="1">4Joseph J. /KAUFFMAN/<br></td>
<td rowspan="1">4Emma /KNAU/<br></td>
<td rowspan="8">1Judy Pauline /JETER/<br></td>
<td rowspan="4">2Killis Walton /JETER/ Jr.<br></td>
<td rowspan="2">3Killis Walton /JETER/<br></td>
<td rowspan="1">4Argulas Beaugard /JETER/<br></td>
<td rowspan="1">4Henrietta Rebeca /COX/<br></td>
<td rowspan="2">3Bertha Maye /OLSEN/<br></td>
<td rowspan="1">4Henry Bertinies /OLSEN/<br></td>
<td rowspan="1">4Martha Ellen /RANEY/<br></td>
<td rowspan="4">2Clara Irene /FARSON/<br></td>
<td rowspan="2">3Harry Herman /FARSON/<br></td>
<td rowspan="1">4Harry Charles /SMITH/<br></td>
<td rowspan="1">4Anna Bell /FARSON/<br></td>
<td rowspan="2">3La Una Jane /LIGHTFRITZ/<br></td>
<td rowspan="1">4/Unknown/<br></td>
<td rowspan="1">4Mary Elizabeth /CONNOR/<br></td>
</table>

I want that output to be grouped like this:

<tr>
<td rowspan="16">0Ian Lee /SKINNER/<br></td>
<td rowspan="8">1John Howard /SKINNER/<br></td>
<td rowspan="4">2Lee Vernon /SKINNER/<br></td>
<td rowspan="2">3Albert Joshua /SKINNER/<br></td>
<td rowspan="1">4William Harrison /SKINNER/<br></td>
</tr>
<tr>
<td rowspan="1">4Anna /FORDYCE/<br></td>
</tr>
<tr>
<td rowspan="2">3Pheobe Lucinda /SCOTT/<br></td>
<td rowspan="1">4John M. /SCOTT/<br></td>
</tr>
<tr>
<td rowspan="1">4Mary Caroline /DINNELL/<br></td>
</tr>
<tr>
<td rowspan="4">2NaDean Adelena /WEHMEYER/<br></td>
<td rowspan="2">3Louis George /WEHMEYER/<br></td>
<td rowspan="1">4August Detrich /WEHMEYER/<br></td>
</tr>
<tr>
<td rowspan="1">4Adlina /ELGERT/<br></td>
</tr>
<tr>
<td rowspan="2">3Ida Gertrude /KAUFFMAN/<br></td>
<td rowspan="1">4Joseph J. /KAUFFMAN/<br></td>
</tr>
<tr>
<td rowspan="1">4Emma /KNAU/<br></td>
</tr>
<tr>
<td rowspan="8">1Judy Pauline /JETER/<br></td>
<td rowspan="4">2Killis Walton /JETER/ Jr.<br></td>
<td rowspan="2">3Killis Walton /JETER/<br></td>
<td rowspan="1">4Argulas Beaugard /JETER/<br></td>
</tr>
<tr>
<td rowspan="1">4Henrietta Rebeca /COX/<br></td>
</tr>
<tr>
<td rowspan="2">3Bertha Maye /OLSEN/<br></td>
<td rowspan="1">4Henry Bertinies /OLSEN/<br></td>
</tr>
<tr>
<td rowspan="1">4Martha Ellen /RANEY/<br></td>
</tr>
<tr>
<td rowspan="4">2Clara Irene /FARSON/<br></td>
<td rowspan="2">3Harry Herman /FARSON/<br></td>
<td rowspan="1">4Harry Charles /SMITH/<br></td>
</tr>
<tr>
<td rowspan="1">4Anna Bell /FARSON/<br></td>
</tr>
<tr>
<td rowspan="2">3La Una Jane /LIGHTFRITZ/<br></td>
<td rowspan="1">4/Unknown/<br></td>
</tr>
<tr>
<td rowspan="1">4Mary Elizabeth /CONNOR/<br></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 &lt; 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 &gt; 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 <tr> 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 &lt; 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 &gt; 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@<br>Ian Lee /SKINNER/</td>
<td rowspan="8">@I005@<br>John Howard /SKINNER/</td>
<td rowspan="4">@I008@<br>Lee Vernon /SKINNER/</td>
<td rowspan="2">@I016@<br>Albert Joshua /SKINNER/</td>
<td rowspan="1">@I040@<br>William Harrison /SKINNER/</td>
</tr>
<tr>
<td rowspan="1">@I041@<br>Anna /FORDYCE/</td>
</tr>
<tr>
<td rowspan="2">@I017@<br>Pheobe Lucinda /SCOTT/</td>
<td rowspan="1">@I052@<br>John M. /SCOTT/</td>
</tr>
<tr>
<td rowspan="1">@I053@<br>Mary Caroline /DINNELL/</td>
</tr>
<tr>
<td rowspan="4">@I009@<br>NaDean Adelena /WEHMEYER/</td>
<td rowspan="2">@I056@<br>Louis George /WEHMEYER/</td>
<td rowspan="1">@I058@<br>August Detrich /WEHMEYER/</td>
</tr>
<tr>
<td rowspan="1">@I059@<br>Adlina /ELGERT/</td>
</tr>
<tr>
<td rowspan="2">@I057@<br>Ida Gertrude /KAUFFMAN/</td>
<td rowspan="1">@I076@<br>Joseph J. /KAUFFMAN/</td>
</tr>
<tr>
<td rowspan="1">@I077@<br>Emma /KNAU/</td>
</tr>
<tr>
<td rowspan="8">@I007@<br>Judy Pauline /JETER/</td>
<td rowspan="4">@I011@<br>Killis Walton /JETER/ Jr.</td>
<td rowspan="2">@I027@<br>Killis Walton /JETER/</td>
<td rowspan="1">@I078@<br>Argulas Beaugard /JETER/</td>
</tr>
<tr>
<td rowspan="1">@I079@<br>Henrietta Rebeca /COX/</td>
</tr>
<tr>
<td rowspan="2">@I028@<br>Bertha Maye /OLSEN/</td>
<td rowspan="1">@I098@<br>Henry Bertinies /OLSEN/</td>
</tr>
<tr>
<td rowspan="1">@I099@<br>Martha Ellen /RANEY/</td>
</tr>
<tr>
<td rowspan="4">@I012@<br>Clara Irene /FARSON/</td>
<td rowspan="2">@I038@<br>Harry Herman /FARSON/</td>
<td rowspan="1">@I106@<br>Harry Charles /SMITH/</td>
</tr>
<tr>
<td rowspan="1">@I107@<br>Anna Bell /FARSON/</td>
</tr>
<tr>
<td rowspan="2">@I039@<br>La Una Jane /LIGHTFRITZ/</td>
<td rowspan="1">@I110@<br>/Unknown/</td>
</tr>
<tr>
<td rowspan="1">@I109@<br>Mary Elizabeth /CONNOR/</td>
</tr>
</table>
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.