Read XSLT 2.0 and XPath 2.0 Programmer's Reference, 4th Edition Online
Authors: Michael Kay
This shows all the details of one individual, with links to related individuals so that you can browse around the family tree. Of course one could attempt many more ambitious ways of displaying this data, and I would encourage you to do so: you can start with the small Kennedy data set included in the download for this book, and then continue with any other GEDCOM data set, perhaps one of your own family tree.
Because we will have one HTML page for each individual in the file, we have to think about how to create multiple HTML pages from a single XML input document. There are at least three ways of doing this:
Unfortunately, at the time of writing the two major browsers (Firefox and Internet Explorer) both support XSLT 1.0 transformations, but neither yet supports XSLT 2.0. To get around this problem, I use a fallback stylesheet for this case that uses XSLT 1.0 only.
Another disadvantage is security; you have no way of filtering the data, for example to remove details of living persons, and you have no way to stop your entire XML file being copied by the user (for example, the user can View Source or can poke around in the browser cache).
The only real difference between the three cases, as far as the stylesheet is concerned, is that the hyperlinks will be generated differently.
We'll handle the differences by writing a generic stylesheet module containing all the common code for the three cases and then importing this into stylesheets that handle the variations. But we'll start by writing a stylesheet that displays one individual on one HTML page, and then we'll worry about the hyperlinks later.
The Stylesheet
We're ready to write a stylesheet,
person.xsl
that generates an HTML page showing the information relevant to a particular individual. This stylesheet will need to accept the
Id
of the required individual as a stylesheet parameter. If no value is supplied, we'll choose the first
xmlns:xsl=“http://www.w3.org/1999/XSL/Transform”
xmlns:xs=“http://www.w3.org/2001/XMLSchema”
xmlns:ged=“http://www.wrox.com/569090/gedcom”
xmlns=“http://www.w3.org/1999/xhtml”
exclude-result-prefixes=“xs ged”>
schema-location=“http://www.w3.org/2002/08/xhtml/xhtml1-transitional.xsd”/>
The stylesheet defines four namespaces: the XSLT namespace, the schema namespace, a local namespace which is used only for the functions defined in this stylesheet module, and the XHTML namespace for the result tree. The schema and
ged
namespaces aren't needed in the output file, so the
exclude-result-prefixes
attribute is set to prevent them appearing.
I've chosen to generate the output in XHTML, so I've specified
method=“xhtml”
in the
very
strict indeed, and extra work would be needed on this stylesheet to make its output conform.
There's now a fair bit of preamble before we do any useful work. This is all designed to make the subsequent processing easier and faster. First we define some keys:
use=“element(*,ParentType)/Link/@Ref”/>
The main purpose of the keys is to make navigation around the structure faster. For a data model like GEDCOM, with many cross-references from one record to another, this can make a big difference. The first two keys allow records to be found given their unique identifiers (they are indexed on their
Id
attributes). The other three keys are there essentially to follow inverse relationships: a family contains links to the children in the family, and the first key enables us quickly to find the family with a link to a given child (in our data there will never be more than one, though GEDCOM allows it; for example, a child may be linked both to her birth parents and to her adoptive parents).
Having defined these keys, we now define some functions to make it easier to navigate around the data.
select=“if ($couple/HusbFath and $couple/WifeMoth)
then (ged:events-for-person(
$couple/key(‘indi’, $couple/HusbFath/Link/@Ref))
intersect
ged:events-for-person(
$couple/key(‘indi’, $couple/WifeMoth/Link/@Ref)))
else ()”/>
This checks that the family record does indeed identify a couple (both parents are present), and then finds all the events in which both parties participate—note the use of the
intersect
operator to find the nodes that are present in two given node-sets.
select=“ged:events-for-person($person)[@Type=‘birth’]”/>
select=“ged:events-for-person($person)[@VitalType=‘birth’]”/>
This function is trying to accommodate some of the variety possible in the model. It first finds the person's birth event (there may be more than one if it has been recorded more than once). Then it selects the events whose
VitalType
is
birth
: this might include records such as baptism or the civil registration of birth (which careful genealogists will distinguish from the birth event itself). It returns the first one of these events that has a date associated with it.
as= “schema-element(EventRec)*”
select= “ged:events-for-couple($couple)[@VitalType=‘marriage’]”/>
as= “schema-element(EventRec)*”
select= “$marriage-vitals[@Type=‘marriage’]”/>
as= “element(*, DateType)?”
select= “($marriage/Date, $marriage-vitals/Date)[1]”/>
then $marriage-date
else ged:sort-dates($couple/Child/Link/@Ref/key(‘indi’,.)/
ged:birth-date(.))[1]”/>