GedML

An Applied XML Example for Use in Genealogy

Genealogy is a very popular activity given the information now available via the World Wide Web, home computers, and standard information sources such as libraries and archives.  With the rising popularity of XML for storing data, many genealogy programs are switching to XML format to replace the older gedcom file format.  GedML is only one of many ways to express gedcom data in an XML format.  This format was chosen at random for proof of concept purposes.  Information on the DTD for GedML is available on the web.

First, we need to import the GedML data.  The path below will need to be modified to point to the file on the local system.

DISCLAIMER:  Wolfram Research, Inc. is not responsible for the data contained in the following file.  This data is partially referenced and is a work in progress.

data = Import["Bryan.gedml", "XML"];

Replacement Rules

Off[General::spell];
Off[General::spell1];
\(\(printName = 
        XMLElement["NAME", {}, {fst_, 
              XMLElement["S", {}, {last_}], ___}] :> \ \(Name -> ToString[fst] <> " " <> ToString[last]\);\)\)
\(\(printDate = 
        XMLElement["DATE", {}, {dt_}] :> \ \(EventDate -> ToString[dt]\);\)\)
\(\(printBirth = 
        XMLElement["EVEN", {"EV" -> "BIRT"}, {bt___}] :> \ \(Birth -> {bt}\);\)\)
\(\(printBurial = 
        XMLElement["EVEN", {"EV" -> "BURI"}, {br___}] :> \ \(Burial -> {br}\);\)\)
\(\(printChange = 
        XMLElement["CHAN", {}, {cn___}] :> \ \(Changed -> cn\);\)\)
\(\(printDeath = 
        XMLElement["EVEN", {"EV" -> "DEAT"}, {dt___}] :> \ \(Death -> {dt}\);\)\)
\(\(printMarriage = 
        XMLElement["EVEN", {"EV" -> "MARR"}, {dt___}] :> \ \(Marriage -> {dt}\);\)\)
\(\(printHusband = 
        XMLElement["HUSB", {ref___}, {}] :> \ \(Husband -> {ref}\);\)\)
\(\(printWife = 
        XMLElement["WIFE", {ref___}, {}] :> \ \(Wife -> {ref}\);\)\)
\(\(printChil = 
        XMLElement["CHIL", {ref___}, {}] :> \ \(Child -> {ref}\);\)\)
printFamily = 
        XMLElement["FAM", {ref___}, 
            blah___] :>  Family ->  {ref, 
              blah};
printIndi = 
        XMLElement["INDI", {"ID" -> id___}, 
            blah___] :>  Individual -> {ID -> id, 
              blah};
printPlace = 
        XMLElement["PLAC", {}, {pl_}] :>  Place -> 
            pl;
noteConc = 
        XMLElement["CONC", {}, cnt___] :> 
          ToString[If[cnt ≠ {}, First[cnt], ""]];
noteCont = 
        XMLElement["CONT", {}, tnt___] :> 
          ToString[If[tnt ≠ {}, First[tnt], ""]];
printText = 
        XMLElement["TEXT", {}, {pg___}] :> 
          ToString[pg];
printSour = 
        XMLElement["SOUR", {ref___}, {___}] :> 
          ToString[ref];
\(\(getRef[x_] := 
        First@Cases[data, 
            XMLElement["NOTE", {"ID" -> 
                    x}, {XMLElement["NOTE", {}, {cont___}], ___}] :> \(Notes -> {cont}\), Infinity];\)\)
printNote = 
        XMLElement["NOTE", {}, {ntref_}] :> 
          getRef[StringTake[ntref, {2, Length[ntref] - 2}]];
printFamc = 
        XMLElement["FAMC", {"REF" -> 
                blah_}, {___}] :> Famc -> 
            blah;
printFams = 
        XMLElement["FAMS", {"REF" -> 
                blah_}, {___}] :> Fams -> 
            blah;
\(\(getSourRef[x_] := 
        Module[{temp}, 
          temp = Cases[data, 
              XMLElement["SOUR", {"ID" -> 
                      x}, {XMLElement["TEXT", {}, {cont___}], ___}] :> \(SourceRef -> {cont}\), Infinity]; 
          If[Length[temp] ≠ 0, 
            First@temp]];\)\)
printSourRef = 
        XMLElement["SOUR", {"REF" -> 
                idref_}, ___] :> 
          getSourRef[idref];
getFamRef[x_] := 
        Module[{temp}, 
          temp = Cases[data, 
              XMLElement["FAM", {"ID" -> x}, 
                  cont___] :> FamRef -> cont, Infinity]; 
          If[Length[temp] ≠ 0, 
            First@temp]];
printFamRef = 
        XMLElement["FAM", {"ID" -> idref_}, ___] :> 
          getFamRef[idref];

Retrieving Functions

NumberOfIndividuals[x_] := 
        Length[Cases[x, XMLElement["INDI", {id_}, _], 
            Infinity]];

To make results more presentable, we can apply the defined replacement rules to "tidy-up" the results with the following function.  This function should only be applied as a last step.  The other search functions are not written to work with "tidied-up" results.

Tidy[
          x_] := x /. printName /. 
                                        printBirth /. printDate /. 
                                        printPlace /. printHusband /. 
                                      printWife /. printChil /. 
                                  printMarriage /. printDeath /. 
                              printBurial /. printChange /. printIndi /. 
                        printNote /. printSourRef /. printFamc /. 
                  printFams /. printFamRef /. printText /. noteCont /. 
          noteConc;

getNames[x_] := 
        Map[(ToString[Part[#, 3, 1]] <> " " <> 
                ToString[Part[#, 3, 2, 3, 1]]) &, 
          Cases[x, XMLElement["NAME", ___], Infinity]];

\(\(getNames[x_, {surname_, givenname_}] := 
        Map[List, 
          Cases[x, 
            XMLElement["INDI", ___, {XMLElement["NAME", {}, {givenname, XMLElement["S", {}, {surname}, ___], ___}, ___], ___}], 
            Infinity]];\)\)

\(\(getBirthDate[x_, birthdate_] := 
        Module[{temp1, temp2}, 
          temp1 = Cases[x, 
              XMLElement["INDI", {_}, {___, 
                    XMLElement["EVEN", {"EV" -> "BIRT"}, {XMLElement["DATE", {}, {bd_}], ___}, ___], ___}] :> bd, 
              Infinity]; 
          temp2 = Select[
              temp1, \((StringPosition[#, birthdate] ≠ \ {})\) &]; 
          Cases[x, 
            XMLElement["INDI", {_}, {___, 
                XMLElement["EVEN", {"EV" -> "BIRT"}, {XMLElement["DATE", {}, temp2], ___}, ___], ___}], 
            Infinity]];\)\)

getFamily[x_] := 
        Cases[data, XMLElement["FAM", {"ID" -> x}, ___], 
          Infinity];

getIndividual[x_] := 
        Cases[data,  XMLElement["INDI", {"ID" -> x}, ___], 
          Infinity];

getWife[indi_] := 
        Module[{temp1}, temp1 = getFamily[indi]; 
          temp2 = Cases[temp1, XMLElement["WIFE", ___, {}], Infinity]; 
          getIndividual["REF" /. temp2[[1, 2]]]];

getChildren[indi_] := 
        Module[{temp1}, temp1 = getFamily[indi]; 
          temp2 = Cases[temp1, 
              XMLElement["CHIL", {"REF" -> 
                      id_}, {}] :> id, Infinity]; 
          Map[getIndividual, temp2]];

\(\(getBirthSpan[x_] := 
        Module[{temp}, 
          temp = Map[\((Flatten[{getNames[#], 
                      Cases[#, 
                        XMLElement["INDI", {_}, {___, 
                              XMLElement["EVEN", {"EV" -> "BIRT"}, {XMLElement["DATE", {}, {birthdate_}], ___}, ___], ___}] :> StringTake[birthdate, \(-4\)], Infinity], 
                      Cases[#, 
                        XMLElement["INDI", {_}, {___, 
                              XMLElement["EVEN", {"EV" -> "DEAT"}, {XMLElement["DATE", {}, {deathdate_}], ___}, ___], ___}] :> StringTake[deathdate, \(-4\)], Infinity]}])\) &, x]; 
          Select[temp, Length[#] == 3 &]];\)\)

lifeRangePlot[x_, opts___] := 
        Module[{size}, size = Length[x]; 
          Show[Graphics[
              Map[{{RGBColor[1, 0, 0], 
                      Rectangle[
                        Flatten[{ToExpression[#[[2]]], 
                            Position[x, #] - 1/size}], 
                        Flatten[{ToExpression[#[[3]]], 
                            1/size + Position[x, #]}]]}, 
                    Text[#[[1]], 
                      Flatten[{ToExpression[#[[3]]], ((Position[
                                      x, #] - 1/size) + (1/size + 
                                    Position[x, #]))/2}], {-1, 
                        0}]} &, x]], opts, Axes -> False, 
            Frame -> {True, False, True, False}, 
            PlotLabel -> "Birth to Death Ranges", PlotRange -> All, 
            TextStyle -> {FontFamily -> "Times", 
                FontWeight -> "Bold"}, 
            ImageSize -> 500]];

Examples

This function finds the number of individuals in a given GedML data set.

NumberOfIndividuals[data]

We can get the names of all of the individuals using the following function.

getNames[data]

We can also define a function to search for a specified name.

mynames = getNames[data, {"Bryan", "Morgan"}];

The Tidy[] function can be used to format the search results as Mathematica rules rather than as lists of XMLElements.  This function should only be used for display purposes.  Attempting to use the results of Tidy[] in conjunction with other search functions will result in failed search attempts.

Tidy[mynames]

Searching for individuals with a specified birth date is also useful.

Tidy[getBirthDate[mynames, "1729"]]

As the examples above show, there are often references to other elements, such as family references.  The getFamily[] function takes this reference and returns the appropriate family record.

Tidy[getFamily["F381"][[1, 3]]]

Similarly, the getIndividual[] function takes an individual reference and returns the corresponding individual record.

Tidy[getIndividual["I1089"]]

The getWife[] function takes a family reference as an argument and returns the individual that matches the "WIFE" descriptor.

Tidy[getWife["F381"]]

Similarly, getChildren[] takes a family reference as an argument and returns the individuals that match the "CHIL" descriptor.

mykids = getChildren["F383"];

Once the children have been extracted, it is useful to compare the life range for those children in order to graphically see which ones were born first or last, and similarly when each individual died.  The first step is to extract the necessary information, which is done via the getBirthSpan[ ] function.  Only those individuals that have known names, birth dates, and death dates are included in the results.

mydat = getBirthSpan[mykids]

This information can be plotted graphically using the lifeRangePlot[] function.

lifeRangePlot[mydat];