Samwilson has submitted this change and it was merged. Change subject: Beginnings of a tree-drawing system (with GraphViz). ......................................................................
Beginnings of a tree-drawing system (with GraphViz). Change-Id: If783bc530f0fd1e152871863be62cd7655282532 --- M Core.php M Genealogy.i18n.php M Genealogy.php M Person.php M README.md A Traverser.php A Tree.php 7 files changed, 197 insertions(+), 9 deletions(-) Approvals: Samwilson: Verified; Looks good to me, approved diff --git a/Core.php b/Core.php index ba5b80d..9ecc40a 100644 --- a/Core.php +++ b/Core.php @@ -47,7 +47,7 @@ break; case 'parent': $parentTitle = Title::newFromText($params[0]); - if ($parentTitle->exists()) { + if ($parentTitle and $parentTitle->exists()) { $person = new GenealogyPerson($parentTitle); $out .= $person->getWikiLink(); } else { @@ -71,6 +71,19 @@ $person = new GenealogyPerson($parser->getTitle()); $out .= self::PeopleList($person->getChildren()); break; + case 'tree': + $tree = new GenealogyTree(); + if (isset($params['ancestors'])) { + $tree->addAncestors(explode("\n", $params['ancestors'])); + } + //$tree->setAncestorDepth($params['ancestor depth']); + if (isset($params['descendants'])) { + $tree->addDescendants(explode("\n", $params['descendants'])); + } + //$tree->setDescendantDepth($params['descendant depth']); + $graphviz = $tree->getGraphviz(); + $out .= $parser->recursiveTagParse("<graphviz>\n$graphviz\n</graphviz>"); + break; default: $out .= '<span class="error">' . 'Genealogy parser function type not recognised: "' . $type . '".' diff --git a/Genealogy.i18n.php b/Genealogy.i18n.php index 07784f9..44c2ba8 100644 --- a/Genealogy.i18n.php +++ b/Genealogy.i18n.php @@ -13,6 +13,10 @@ * @author Sam Wilson <s...@samwilson.id.au> */ $messages['en'] = array( - 'genealogy' => "Genealogy", + 'genealogy' => "Genealogy", 'genealogy-desc' => "Adds a parser function for easier linking between genealogical records", + 'ancestor' => 'Ancestor', + 'descendant' => 'Descendant', + 'ancestors' => 'Ancestors', + 'descendants' => 'Descendants', ); diff --git a/Genealogy.php b/Genealogy.php index 49e8308..9c1f17d 100644 --- a/Genealogy.php +++ b/Genealogy.php @@ -34,13 +34,13 @@ $wgExtensionMessagesFiles['GenealogyMagic'] = __DIR__ . '/Genealogy.i18n.magic.php'; /** - * Class loading and the Special page + * Class loading */ -$wgAutoloadClasses['Genealogy'] = __FILE__; -$wgAutoloadClasses['GenealogyPerson'] = __DIR__ . '/Person.php'; -$wgAutoloadClasses['GenealogySpecial'] = __DIR__ . '/Special.php'; -$wgAutoloadClasses['GenealogyCore'] = __DIR__ . '/Core.php'; -$wgSpecialPages['Genealogy'] = 'GenealogySpecial'; +$wgAutoloadClasses['GenealogyPerson'] = __DIR__ . '/Person.php'; +$wgAutoloadClasses['GenealogySpecial'] = __DIR__ . '/Special.php'; +$wgAutoloadClasses['GenealogyCore'] = __DIR__ . '/Core.php'; +$wgAutoloadClasses['GenealogyTree'] = __DIR__ . '/Tree.php'; +$wgAutoloadClasses['GenealogyTraverser'] = __DIR__ . '/Traverser.php'; /** * Parser function diff --git a/Person.php b/Person.php index 33ebabe..e3e4c9f 100644 --- a/Person.php +++ b/Person.php @@ -19,6 +19,15 @@ } /** + * Get some basic info about this person. + * @todo Add dates. + * @return string + */ + public function __toString() { + return $this->getTitle()->getPrefixedText(); + } + + /** * Get this person's wiki title. * * @return Title diff --git a/README.md b/README.md index 6412e98..e3d98e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Its first two parameters are unnamed (i.e. don't have equals signs), but all others must be (dates, etc.). -The following functions are supported, three for defining data and three for +The following functions are supported, three for defining data and four for reporting data: 1. Define this person's dates. @@ -21,6 +21,9 @@ `{{#genealogy:partners}}` 6. List all children: `{{#genealogy:children}}` +7. Display a tree (a connected graph): + `{{#genealogy:tree|ancestors=List|descendants=List}}` + where each `List` is a newline-separated list of page titles. ## Development diff --git a/Traverser.php b/Traverser.php new file mode 100644 index 0000000..7d06ff4 --- /dev/null +++ b/Traverser.php @@ -0,0 +1,49 @@ +<?php + +class GenealogyTraverser { + + private $callbacks; + + private $ancestor_depth = 0; + + private $descendant_depth = 0; + + /** + * Callbacks will be called for each page crawled. + * + * @param callable $callback The callable function etc. + */ + public function register($callback) { + $this->callbacks[] = $callback; + } + + public function ancestors(GenealogyPerson $person, $depth = null) { + $this->visit($person); + if ($this->ancestor_depth > $depth) { + return; + } + foreach ($person->getParents() as $parent) { + $this->ancestors($parent); + } + $this->ancestor_depth++; + } + + public function descendants(GenealogyPerson $person, $depth = null) { + $this->visit($person); + if ($this->descendant_depth > $depth) { + return; + } + foreach ($person->getChildren() as $parent) { + $this->descendants($parent); + } + $this->descendant_depth++; + } + + private function visit($person) { + // Call each callback + foreach ($this->callbacks as $callback) { + call_user_func($callback, $person); + } + } + +} diff --git a/Tree.php b/Tree.php new file mode 100644 index 0000000..3b0dadd --- /dev/null +++ b/Tree.php @@ -0,0 +1,110 @@ +<?php + +class GenealogyTree { + + private $dot_source; + + private $ancestors = array(); + + private $descendants = array(); + + private $ancestor_depth; + + private $descendant_depth; + + public function setAncestorDepth($ancestor_depth) { + $this->ancestor_depth = $ancestor_depth; + } + + public function setDescendantDepth($descendant_depth) { + $this->descendant_depth = $descendant_depth; + } + + public function addAncestors($ancestors) { + $this->addAncestorsOrDescendants('ancestors', $ancestors); + } + + public function addDescendants($descendants) { + $this->addAncestorsOrDescendants('descendants', $descendants); + } + + private function addAncestorsOrDescendants($type, $list) { + foreach ($list as $a) { + $title = Title::newFromText($a); + if ($title and $title->exists()) { + $person = new GenealogyPerson($title); + $this->{$type}[] = $person; + } + } + } + + public function getGraphviz() { + $this->out('top', 'digraph GenealogyTree {'); + $this->out('top', 'graph [rankdir=LR, splines=ortho]'); + $this->out('top', 'edge [arrowhead=none]'); + + $traverser = new GenealogyTraverser(); + $traverser->register(array($this, 'visit')); + + foreach ($this->ancestors as $ancestor) { + $traverser->ancestors($ancestor, $this->ancestor_depth); + } + + foreach ($this->descendants as $descendant) { + $traverser->descendants($descendant, $this->descendant_depth); + } + + // Do nothing if there are no people listed. + if (!isset($this->dot_source['person'])) { + return 'No people found'; + } + + // Combine all parts of the graph output. + return join("\n", $this->dot_source['top']) . "\n\n" + .join("\n", $this->dot_source['person']) . "\n\n" + .join("\n", $this->dot_source['partner']) . "\n\n" + .join("\n", $this->dot_source['child']) . "\n}"; + } + + public function visit(GenealogyPerson $person) { + $birthYear = $person->getBirthDate('Y'); + $deathYear = $person->getDeathDate('Y'); + if (!empty($birthYear) && !empty($deathYear)) { + $date = '\n'.$birthYear.' – '.$deathYear; + } elseif (!empty($birthYear)) { + $date = '\nb. '.$birthYear; + } elseif (!empty($deathYear)) { + $date = '\nd. '.$deathYear; + } else { + $date = ''; + } + $personId = $this->esc($person->getTitle()->getDBkey()); + $url = $person->getTitle()->getFullURL(); + $title = $person->getTitle(); + $line = $personId." [label=\"$title$date\", shape=plaintext, URL=\"$url\", tooltip=\"$title\"]"; + $this->out('person', $line); + foreach ($person->getChildren() as $child) { + $parents = 'parents_'.$this->esc(join('', $child->getParents())); + $this->out('partner', $parents.' [label="", shape="point"]'); + $this->out('partner', $personId.' -> '.$parents.' [style=dotted]'); + $this->out('child', $parents.' -> '.$this->esc($child->getTitle()->getDBkey())); + } + } + + private function out($group, $line, $permit_dupes = false) { + if (!is_array($this->dot_source)) { + $this->dot_source = array(); + } + if (!isset($this->dot_source[$group])) { + $this->dot_source[$group] = array(); + } + if (!in_array($line, $this->dot_source[$group]) || $permit_dupes) { + $this->dot_source[$group][] = $line; + } + } + + private function esc($title) { + return strtr($title, '( )', '___'); + } + +} -- To view, visit https://gerrit.wikimedia.org/r/321206 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: If783bc530f0fd1e152871863be62cd7655282532 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Genealogy Gerrit-Branch: master Gerrit-Owner: Samwilson <s...@samwilson.id.au> Gerrit-Reviewer: Samwilson <s...@samwilson.id.au> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits