I've been playing with some genealogical files in XML lately and wanted to create a transform that would list, for each person, the person's name, the count of ancestors, and the list of ancestors. Here's what I came up with, but I'd like to see if there's a better way to do this.
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<xsl:output method="text"/>
<xsl:template match="/people">
<xsl:apply-templates select="person"/>
</xsl:template>
<!-- for each person, print the name and ancestor count -->
<xsl:template match="person">
<xsl:value-of select="@name"/>
<xsl:text>(</xsl:text>
<xsl:apply-templates select="." mode="count-kin"/>
<xsl:text>): </xsl:text>
<!-- now name all of the ancestors -->
<xsl:apply-templates select="." mode="name-kin"/>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- recursively name the ancestors -->
<xsl:template match="parent" mode="name-kin">
<xsl:value-of select="concat(@name,' ')"/>
<xsl:variable name="myname" select="@name"/>
<xsl:apply-templates select="/people/person[@name=$myname]" mode="name-kin"/>
</xsl:template>
<!-- recursively name the ancestors -->
<xsl:template match="person" mode="name-kin">
<xsl:apply-templates select="parent" mode="name-kin"/>
</xsl:template>
<!-- recursively count the number of ancestors -->
<xsl:template match="parent" mode="count-kin">
<xsl:variable name="myname" select="@name"/>
<xsl:variable name="ancestor-count">
<xsl:apply-templates select="/people/person[@name=$myname]" mode="count-kin"/>
</xsl:variable>
<xsl:value-of select="$ancestor-count"/>
</xsl:template>
<!-- recursively count the number of ancestors -->
<xsl:template match="person" mode="count-kin">
<xsl:choose>
<!-- only count ancestors if there are any -->
<xsl:when test="count(parent)">
<xsl:variable name="ancestor-count">
<xsl:apply-templates select="parent" mode="count-kin"/>
</xsl:variable>
<xsl:value-of select="number($ancestor-count)+count(parent)"/>
</xsl:when>
<!-- no parents -->
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Here's a sample input file:
<?xml version="1.0" encoding="iso-8859-1"?>
<people>
<person name="adam"/>
<person name="eve"/>
<person name="cain">
<parent name="adam"/>
<parent name="eve"/>
</person>
<person name="henoch">
<parent name="cain"/>
</person>
<person name="gladys"/>
<person name="frank">
<parent name="henoch"/>
</person>
<person name="jose">
<parent name="gladys"/>
<parent name="frank"/>
</person>
</people>
Here's sample output from xsltproc
on Linux:
adam(0):
eve(0):
cain(2): adam eve
henoch(3): cain adam eve
gladys(0):
frank(4): henoch cain adam eve
jose(6): gladys frank henoch cain adam eve