Ignoring the absolute madness of my classes and names (this is a desperate mess from me trying 10000 different ways to structure this, so it needs refactoring/cleaning/rewriting!)
php code:
<?php
class GedComDom extends DOMDocument {
private $gedcom;
private $linesraw;
private $root;
private $path;
public $xpath;
public function __construct ($path){
parent::__construct();
$this->path = $path;
$this->xpath = new DOMXPath($this);
$this->registerNodeClass('DOMDocument', 'GedComDom');
$this->registerNodeClass('DOMElement', 'GedComDomElement');
$this->root = $this->setRoot('GEDCOM');
$this->individuals = $this->addCollection("INDIVIDUALS");
$this->families = $this->addCollection("FAMILIES");
$this->gedcom = file_get_contents($this->path);
$this->linesraw = explode("\r\n", $this->gedcom);
$currentobj = null;
for ($i=0; $i<sizeof($this->linesraw); $i++){
$linedata = self::SplitGedLine($this->linesraw[$i]);
if ($linedata["level"] == 0){
$currentobj = $this->create($linedata);
}else if ($linedata["level"] > $currentobj->level){
$currentobj = $currentobj->addData($linedata);
}else if ($linedata["level"] < $currentobj->level){
$difference = abs($linedata["level"]-$currentobj->level)+1;
for ($a=0; $a<$difference; $a++){
$currentobj = $currentobj->parentNode;
}
$currentobj = $currentobj->addData($linedata);
}else {
$currentobj = $currentobj->parentNode;
$currentobj = $currentobj->addData($linedata);
}
}
}
function addCollection ($name){
return $this->root->appendChild(new DOMElement($name));
}
function create($linedata, $class = null){
$node = new GedComDomElement($linedata["tag"]);
if ($node->obj != null){
if ($node->obj instanceof Individual){
$this->individuals->appendChild($node);
}else if ($node->obj instanceof Family){
$this->families->appendChild($node);
}
}else {
$this->root->appendChild($node);
}
$node->level = $linedata["level"];
$node->addUpdateAttributes($linedata);
return $node;
}
function setRoot($name) {
return $this->appendChild(new GedComDomElement($name));
}
static function SplitGedLine ($linetext){
preg_match_all("|^(\d+)\s*(@\S+@)?\s*(\S+)\s*(.*)|", mb_convert_encoding($linetext, "UTF-8"), $out, PREG_PATTERN_ORDER);
$level = trim($out[1][0]);
$id = trim($out[2][0]);
$tag = trim(strtoupper($out[3][0]));
$text = trim($out[4][0]);
return array("level"=>$level, "id"=>$id, "tag"=>$tag, "text"=>$text);
}
public function GetFamilyById($id){
return $this->getElementById($id)->obj;
}
public function GetIndividualById($id){
return $this->getElementById($id)->obj;
}
}
class GedComDomElement extends DOMElement {
public $obj;
public function __construct($name){
switch($name){
case "INDI":
$this->obj = new Individual($this);
break;
case "FAM":
$this->obj = new Family($this);
break;
}
parent::__construct($name);
}
function addData ($linedata){
$node = new GedComDomElement($linedata["tag"]);
$node->level = $linedata["level"];
$this->appendChild($node);
$node->addUpdateAttributes($linedata);
return $node;
}
function addUpdateAttributes($linedata){
if (!empty($linedata["id"])){
$this->setAttribute("id", $linedata["id"]);
$this->setIdAttribute("id", true);
}
if (!empty($linedata["text"])){
$this->setAttribute("text", $linedata["text"]);
}
}
function __destruct (){
$this->obj = null;
}
}
abstract class GedComDomQueryObject {
protected $node;
public function __construct($node){
$this->node = $node;
}
public function GetNodeAttribute($nodename, $attrname){
return $this->query($nodename."/@".$attrname, $this->node);
}
private function query($query, $context = null){
return $this->node->ownerDocument->xpath->query($query, $context);
}
public function __destruct(){
$this->node = null;
}
}
class Individual extends GedComDomQueryObject{
private $name = false;
private $sex = false;
private $famcid = false;
private $famsid = false;
public function GetName(){
if ($this->name === false){
$namenodes = $this->GetNodeAttribute("NAME", "text");
$this->name = $namenodes->length > 0 ? $namenodes->item(0)->value : null;
}
return $this->name;
}
public function GetSex(){
if ($this->sex === false){
$sexnodes = $this->GetNodeAttribute("SEX", "text");
$this->sex = $sexnodes->length > 0 ? $sexnodes->item(0)->value : null;
}
return $this->sex;
}
private function getFamilyS (){
if ($this->famsid === false){
$this->famsid = array();
$famsid = $this->GetNodeAttribute("FAMS", "text");
for ($i=0; $i<$famsid->length; $i++){
$this->famsid[] = $famsid->item($i)->value;
}
}
return $this->famsid;
}
private function getFamilyC (){
if ($this->famcid === false){
$famcid = $this->GetNodeAttribute("FAMC", "text");
$this->famcid = $famcid->length > 0 ? $famcid->item(0)->value : null;
}
return $this->famcid;
}
public function GetWives(){
$fams = $this->getFamilyS();
$wives = array();
for ($i=0; $i<sizeof($fams); $i++){
$wives[] = $this->node->ownerDocument->GetFamilyById($fams[$i])->GetWife();
}
return $wives;
}
public function GetMother(){
if ($this->getFamilyC() !== null){
return $this->node->ownerDocument->GetFamilyById($this->getFamilyC())->GetWife();
}
return null;
}
}
class Family extends GedComDomQueryObject{
private $wife = false;
public function GetWife(){
if ($this->wife === false){
$wives = $this->GetNodeAttribute("WIFE", "text");
$this->wife = $wives->length > 0 ? $this->node->ownerDocument->GetIndividualById($wives->item(0)->value) : null;
}
return $this->wife;
}
}
Run that against a gedcom file like this:
code:
0 HEAD
1 SOUR Reunion
2 VERS V8.0
2 CORP Leister Productions
1 DEST Reunion
1 DATE 11 FEB 2006
1 FILE test
1 GEDC
2 VERS 5.5
1 CHAR MACINTOSH
0 @I1@ INDI
1 NAME Bob /Cox/
1 SEX M
1 FAMS @F1@
1 CHAN
2 DATE 11 FEB 2006
0 @I2@ INDI
1 NAME Joann /Para/
1 SEX F
1 FAMS @F1@
1 CHAN
2 DATE 11 FEB 2006
0 @I3@ INDI
1 NAME Bobby Jo /Cox/
1 SEX M
1 FAMC @F1@
1 CHAN
2 DATE 11 FEB 2006
0 @F1@ FAM
1 HUSB @I1@
1 WIFE @I2@
1 MARR
1 CHIL @I3@
0 TRLR
Then you can do stuff like this:
php code:
$ged = new GedComDom("test_family_tree.ged");
echo $ged->GetIndividualById("@P3206597029@")->GetMother()->GetMother()->GetFather()->GetName();
or just spit out the XML using the normal DOM functions:
php code:
$ged = new GedComDom("test_family_tree.ged");
echo $ged->saveXML();
Bit of a nightmare but with the relationships the way they are, it was the only niceish way of converting the gedcom into a nice format then doing fun stuff with it.
Next I want to be able to automatically say which generation each person is in, relative to the oldest generation in the tree. This will be a nightmare because of how the trees spread out and each person has two parents.
Then, I want to write a method within Individual where you can do something like this:
php code:
Individual {
public function GetRelationToOther ($individual){
find relation between $this and $individual;
// return "great great great great great 6nd cousin 4 times removed"
}
}
which'll be uh.. fun!
|