Samwilson has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/321215

Change subject: Better tree drawing
......................................................................

Better tree drawing

Change-Id: I9c5fad962b52e0dcb64038935ee84b15c7a1f6fb
---
M Genealogy.i18n.php
M README.md
A person_template.wikitext
M src/Person.php
M src/Traverser.php
M src/Tree.php
M src/Util.php
M tests/phpunit/PersonTest.php
8 files changed, 224 insertions(+), 43 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Genealogy 
refs/changes/15/321215/1

diff --git a/Genealogy.i18n.php b/Genealogy.i18n.php
index db0617a..9a74409 100644
--- a/Genealogy.i18n.php
+++ b/Genealogy.i18n.php
@@ -13,12 +13,14 @@
  * @author Sam Wilson <[email protected]>
  */
 $messages['en'] = [
-       'genealogy'      => "Genealogy",
-       'genealogy-desc' => "Adds a parser function for easier linking between 
genealogical records",
+       'genealogy' => 'Genealogy',
+       'genealogy-desc' => 'Adds a parser function for easier linking between 
genealogical records',
        'genealogy-born' => 'b.',
        'genealogy-died' => 'd.',
-       'genealogy-ancestor'       => 'Ancestor',
-       'genealogy-descendant'     => 'Descendant',
-       'genealogy-ancestors'      => 'Ancestors',
-       'genealogy-descendants'    => 'Descendants',
+       'genealogy-ancestor' => 'Ancestor',
+       'genealogy-ancestors' => 'Ancestors',
+       'genealogy-descendant' => 'Descendant',
+       'genealogy-descendants' => 'Descendants',
+       'genealogy-person-preload' => 'Template:Person/preload',
+       'genealogy-parser-function-not-found' => 'Genealogy parser function 
type not recognised: $1',
 ];
diff --git a/README.md b/README.md
index 0dda15b..49b4935 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,14 @@
-# MediaWiki Genealogy extension
+MediaWiki Genealogy extension
+=============================
 
-## Usage
+All details:
+[mediawiki.org/wiki/Extension:Genealogy](https://mediawiki.org/wiki/Extension:Genealogy)
 
-There is only one parser function, `{{#genealogy:}}`.
-Its first two parameters are unnamed (i.e. don't have equals signs),
-but all others must be (dates, etc.).
+## Usage summary
+
+This extension creates one parser function: `{{#genealogy: … }}`.
+Its first two parameters are unnamed (i.e. don't have equals signs)
+but all others are (the dates, etc.).
 
 The following functions are supported, three for defining data and four for
 reporting data:
@@ -25,10 +29,24 @@
    `{{#genealogy:tree|ancestors=List|descendants=List}}`
    where each `List` is a newline-separated list of page titles.
 
+## Templates
+
+**Example:**
+For an example template that makes use of these parser functions,
+see [`person_template.wikitext`](https://github.com/samwilson/Genealogy)
+
+**Preload:**
+When this extension creates a link to a page that doesn't yet exist,
+the text of `[[Template:Person/preload]]` is preloaded.
+The location of this preload text can be customised
+by modifying the `[[MediaWiki:genealogy-person-preload]]` system message.
+
 ## Development
 
 The *Genealogy* extension is developed by Sam Wilson and released under version
 3 of the GPL (see `LICENSE.txt` for details).
 
+You can see this extension in use on [ArchivesWiki](https://archives.org.au).
+
 Please report all bugs via the GitHub issue tracker at
 https://github.com/samwilson/Genealogy/issues
diff --git a/person_template.wikitext b/person_template.wikitext
new file mode 100644
index 0000000..0717ecb
--- /dev/null
+++ b/person_template.wikitext
@@ -0,0 +1,43 @@
+{|class=wikitable
+|+ {{PAGENAME}}
+|-
+! Birth:
+| {{{birth_date|}}} {{{birth_place|}}}
+|-
+! Death:
+| {{{death_date|}}} {{{death_place|}}}
+|-
+! Parents:
+|
+{{#if: {{{parent1|}}} | * {{#genealogy:parent | {{{parent1}}} }} }}
+{{#if: {{{parent2|}}} | * {{#genealogy:parent | {{{parent2}}} }} }}
+{{#if: {{{parent3|}}} | * {{#genealogy:parent | {{{parent3}}} }} }}
+|-
+! Siblings:
+| {{#genealogy:siblings}}
+|-
+! Partners:
+| {{#genealogy:partners}}
+{{#if: {{{partner1|}}} | {{#genealogy:partner | {{{partner1}}} }} }}
+{{#if: {{{partner2|}}} | {{#genealogy:partner | {{{partner2}}} }} }}
+{{#if: {{{partner3|}}} | {{#genealogy:partner | {{{partner3}}} }} }}
+|-
+! Children:
+| {{#genealogy:children}}
+|}[[Category:People]]<noinclude>
+This template is used to define and display a summary table on a biography 
article.
+
+It adds articles to the [[:Category:People|People]] category.
+
+== Usage ==
+
+<pre><nowiki>
+{{person
+ | parent1 = Person 1 Name
+ | parent2 = Person 2 Name
+ | parent3 = Person 3 Name
+ | partner1 = Person 4 Name
+ | partner2 = Person 5 Name
+ | partner3 = Person 6 Name
+}}
+</nowiki></pre>
diff --git a/src/Person.php b/src/Person.php
index 905fe3f..b1b5242 100644
--- a/src/Person.php
+++ b/src/Person.php
@@ -2,7 +2,9 @@
 
 namespace Samwilson\Genealogy;
 
+use Linker;
 use MagicWord;
+use MediaWiki\Linker\LinkRenderer;
 use Parser;
 use Title;
 use WikiPage;
@@ -145,11 +147,9 @@
         * @return Person[] An array of parents, possibly empty.
         */
        public function getParents() {
-               if ( is_array( $this->parents ) ) {
-                       return $this->parents;
-               }
-               $this->parents = $this->getPropMulti( 'parent' );
-               return $this->parents;
+               $parents = $this->getPropMulti( 'parent' );
+               //ksort( $parents, SORT_REGULAR );
+               return $parents;
        }
 
        /**
@@ -247,7 +247,8 @@
                        'pp_page' => $articleIds,
                        "pp_propname LIKE 'genealogy $type %'"
                        ],
-                       __METHOD__
+                       __METHOD__,
+                       [ 'ORDER BY' => 'pp_value' ]
                );
                foreach ( $results as $result ) {
                        $title = Title::newFromText( $result->pp_value );
diff --git a/src/Traverser.php b/src/Traverser.php
index 9f393b6..7ce38ef 100644
--- a/src/Traverser.php
+++ b/src/Traverser.php
@@ -20,10 +20,16 @@
        }
 
        public function ancestors( Person $person, $depth = null ) {
+               // Visit this person and their partners.
                $this->visit( $person );
+               foreach ( $person->getPartners() as $partner ) {
+                       $this->visit( $partner );
+               }
+               // Give up if we're being limited.
                if ( $this->ancestor_depth > $depth ) {
                        return;
                }
+               // Carry on to their ancestors.
                foreach ( $person->getParents() as $parent ) {
                        $this->ancestors( $parent );
                }
@@ -31,10 +37,16 @@
        }
 
        public function descendants( Person $person, $depth = null ) {
+               // Visit this person and their partners.
                $this->visit( $person );
+               foreach ( $person->getPartners() as $partner ) {
+                       $this->visit( $partner );
+               }
+               // Give up if we're being limited.
                if ( $this->descendant_depth > $depth ) {
                        return;
                }
+               // Carry on to their descendants.
                foreach ( $person->getChildren() as $parent ) {
                        $this->descendants( $parent );
                }
diff --git a/src/Tree.php b/src/Tree.php
index 38aea60..394151c 100644
--- a/src/Tree.php
+++ b/src/Tree.php
@@ -48,7 +48,7 @@
 
        public function getGraphviz() {
                $this->out( 'top', 'digraph GenealogyTree {' );
-               $this->out( 'top', 'graph [rankdir=LR, splines=ortho]' );
+               $this->out( 'top', 'graph [rankdir=LR]' );
                $this->out( 'top', 'edge [arrowhead=none]' );
 
                $traverser = new Traverser();
@@ -68,10 +68,16 @@
                }
 
                // 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}";
+               $out = join( "\n", $this->dot_source['top'] ) . "\n\n"
+                      . "node [ shape=plaintext ]\n"
+                      . join( "\n", $this->dot_source['person'] ) . "\n\n";
+               if (isset($this->dot_source['partner'])) {
+                       $out .= join( "\n", $this->dot_source['partner'] ) . 
"\n\n";
+               }
+               if (isset($this->dot_source['child'])) {
+                       $out .= join( "\n", $this->dot_source['child'] ) . 
"\n\n";
+               }
+               return $out . "}";
        }
 
        public function visit( Person $person ) {
@@ -86,20 +92,76 @@
                } else {
                        $date = '';
                }
-               $personId = $this->esc( $person->getTitle()->getDBkey() );
-               $url = $person->getTitle()->getFullURL();
+               $personId = $this->esc( $person->getTitle()->getText() );
+               $query = $person->getTitle()->exists()
+                       ? []
+                       : [ 'preload' => wfMessage( 'genealogy-person-preload' 
), 'action' => 'edit' ];
+               $url = $person->getTitle()->getFullURL( $query );
                $title = $person->getTitle();
-               $line = $personId." [label=\"$title$date\", shape=plaintext, 
URL=\"$url\", "
-                       . "tooltip=\"$title\"]";
+               $line = $personId." [label=\"$title$date\", 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() ) );
+
+               $partnerStyle = 'dashed';
+
+               // Output links to parents.
+               if ( $person->getParents() ) {
+                       $parentsNode = $this->esc( join( '', 
$person->getParents() ) );
+                       $this->out( 'partner', $parentsNode . ' [label="", 
shape="point"]' );
+                       $this->out( 'child', $parentsNode . ' -> ' . $personId 
);
+                       foreach ( $person->getParents() as $parent ) {
+                               $parentId = $this->esc( 
$parent->getTitle()->getText() );
+                               // If the parent doesn't exist, create its node 
now.
+                               if ( !$parent->getTitle()->exists() ) {
+                                       $this->out( 'person', $parentId . ' 
[style=plaintext, fontcolor=red]' );
+                               }
+                               $this->out(
+                                       'partner',
+                                       $parentId . ' -> ' . $parentsNode . " 
[style=$partnerStyle]"
+                               );
+                       }
                }
+
+               // Output links to partners.
+               foreach ( $person->getPartners() as $partner ) {
+                       // Create a point node for each partnership.
+                       $partnerId = $this->esc( 
$partner->getTitle()->getDBkey() );
+                       $partners = [ $personId, $partnerId ];
+                       sort( $partners );
+                       $partnersNode = $this->esc( join( '', $partners ) );
+                       $this->out( 'partner', $partnersNode.' [label="", 
shape="point"]' );
+                       // Link this person and this partner to that point node.
+                       $this->out( 'partner', $personId .' -> '. 
$partnersNode." [style=$partnerStyle, "
+                                              ." decorate=true]" );
+                       $this->out( 'partner', $partnerId .' -> '. 
$partnersNode." [style=$partnerStyle, "
+                       ." decorate=true]" );
+                       // Create a node for a non-existing partner.
+                       if ( !$partner->getTitle()->exists() ) {
+                               $this->out( 'person', $partnerId . ' 
[shape=plaintext, fontcolor=red]' );
+                       }
+               }
+
+               // Output links to children.
+               foreach ( $person->getChildren() as $child ) {
+                       $parentsNode = $this->esc( join( '', 
$child->getParents() ) );
+                       $this->out( 'partner', $parentsNode.' [label="", 
shape="point"]' );
+                       $this->out( 'partner', $personId.' -> '.$parentsNode." 
[style=$partnerStyle]" );
+                       $childId = $this->esc( $child->getTitle()->getDBkey() );
+                       $this->out( 'child', $parentsNode.' -> ' . $childId );
+                       // Add this child if they don't exist.
+                       if ( !$child->getTitle()->exists() ) {
+                               $this->out( 'person', $childId . ' 
[shape=plaintext, fontcolor=red]' );
+                       }
+               }
+
        }
 
+       /**
+        * Store a single line of Dot source output. This means we can avoid 
duplicate output lines,
+        * and also group source by different categories ('partner', 'child', 
etc.).
+        * @param string $group
+        * @param string $line The line of Dot source code.
+        * @param boolean $permit_dupes Add this line even if it's already 
there.
+        */
        private function out( $group, $line, $permit_dupes = false ) {
                if ( !is_array( $this->dot_source ) ) {
                        $this->dot_source = [];
@@ -112,6 +174,13 @@
                }
        }
 
+       /**
+        * Create a Dot-compatible variable name from any string (replace 
parentheses, spaces, and
+        * hyphens with underscores).
+        * @todo Complete the disallowed character list.
+        * @param string $title
+        * @return string
+        */
        private function esc( $title ) {
                return strtr( $title, '( )-', '____' );
        }
diff --git a/src/Util.php b/src/Util.php
index 787b657..0f7e49b 100644
--- a/src/Util.php
+++ b/src/Util.php
@@ -3,8 +3,11 @@
 namespace Samwilson\Genealogy;
 
 use EditPage;
+use Linker;
+use MediaWiki\Linker\LinkRendererFactory;
 use Parser;
 use Title;
+use Xml;
 
 class Util {
 
@@ -78,6 +81,7 @@
                        }
                }
                $out = ''; // "<pre>".print_r($params, true)."</pre>";
+               $isHtml = false;
                switch ( $type ) {
                        case 'person':
                                if ( isset( $params['birth date'] ) ) {
@@ -91,29 +95,30 @@
                                break;
                        case 'parent':
                                $parentTitle = Title::newFromText( $params[0] );
-                               if ( $parentTitle and $parentTitle->exists() ) {
+                               if ( $parentTitle && $parentTitle->exists() ) {
                                        $parent = new Person( $parentTitle );
                                        $out .= $parent->getWikiLink();
                                } else {
-                                       $out .= "[[" . $params[0] . "]]";
+                                       $query = [ 'preload' => wfMessage( 
'genealogy-person-preload' ) ];
+                                       $out .= Linker::link( $parentTitle, 
null, [], $query );
+                                       $isHtml = true;
                                }
                                self::saveProp( $parser, 'parent', $params[0] );
                                break;
                        case 'siblings':
                                $person = new Person( $parser->getTitle() );
-                               $out .= self::PeopleList( 
$person->getSiblings() );
+                               $out .= self::peopleList( 
$person->getSiblings() );
                                break;
                        case 'partner':
-                               // $out .= "[[".$params[0]."]]";
                                self::saveProp( $parser, 'partner', $params[0] 
);
                                break;
                        case 'partners':
                                $person = new Person( $parser->getTitle() );
-                               $out .= self::PeopleList( 
$person->getPartners() );
+                               $out .= self::peopleList( 
$person->getPartners() );
                                break;
                        case 'children':
                                $person = new Person( $parser->getTitle() );
-                               $out .= self::PeopleList( 
$person->getChildren() );
+                               $out .= self::peopleList( 
$person->getChildren() );
                                break;
                        case 'tree':
                                $tree = new Tree();
@@ -127,15 +132,17 @@
                                // 
$tree->setDescendantDepth($params['descendant depth']);
                                $graphviz = $tree->getGraphviz();
                                $out .= $parser->recursiveTagParse( 
"<graphviz>\n$graphviz\n</graphviz>" );
-                               $out .= "<pre>$graphviz</pre>";
+                               $out .= $parser->recursiveTagParse(
+                                       "<syntaxhighlight 
lines='1'>$graphviz</syntaxhighlight>"
+                               );
                                break;
                        default:
-                               $out .= '<span class="error">'
-                                       . 'Genealogy parser function type not 
recognised: "' . $type . '".'
-                                       . '</span>';
+                               $msg = 
wfMessage('genealogy-parser-function-not-found', [ $type ] );
+                               $out .= "<span class='error'>$msg</span>";
                                break;
                }
-               return $out;
+               // Return format is documented in Parser::setFunctionHook().
+               return $isHtml ? [ 0 => $out, 'isHTML' => true ] : $out;
        }
 
        /**
@@ -170,7 +177,7 @@
         * @param Person[] $people
         * @return string Wikitext list of people.
         */
-       public static function PeopleList( $people ) {
+       public static function peopleList( $people ) {
                $out = '';
                foreach ( $people as $person ) {
                        $out .= "* " . $person->getWikiLink() . "\n";
diff --git a/tests/phpunit/PersonTest.php b/tests/phpunit/PersonTest.php
index 5247e3e..d214556 100644
--- a/tests/phpunit/PersonTest.php
+++ b/tests/phpunit/PersonTest.php
@@ -7,6 +7,21 @@
  */
 class PersonTest extends MediaWikiTestCase {
 
+       /**
+        * Set the wikitext contents of a test page.
+        * @param string|Title $title The title of the page.
+        * @param string $wikitext The page contents.
+        * @return WikiPage
+        */
+       protected function setPageContent( $title, $wikitext ) {
+               if ( is_string( $title ) ) {
+                       $title = Title::newFromText( $title );
+               }
+               $page = new WikiPage( $title );
+               $page->doEditContent( new WikitextContent( $wikitext ), '' );
+               return $page;
+       }
+
        public function testCreatePerson() {
                $charlesTitle = Title::newFromText( 'Charles' );
                $page = new WikiPage( $charlesTitle );
@@ -31,6 +46,20 @@
                $this->assertEquals( '1890', $person->getDateYear( 'c. 1890' ) 
);
        }
 
+       public function testParentsInAlphabeticalOrder() {
+               $alice = new Person( Title::newFromText( 'Alice' ) );
+               $this->setPageContent( 'Alice', 
'{{#genealogy:parent|Clara}}{{#genealogy:parent|Bob}}' );
+               $parents = $alice->getParents();
+               $this->assertEquals( [ 'Bob', 'Clara' ], array_keys( $parents ) 
);
+       }
+
+       public function testPartnersInAlphabeticalOrder() {
+               $alice = new Person( Title::newFromText( 'Alice' ) );
+               $this->setPageContent( 'Alice', 
'{{#genealogy:parent|Clara}}{{#genealogy:parent|Bob}}' );
+               $parents = $alice->getParents();
+               $this->assertEquals( [ 'Bob', 'Clara' ], array_keys( $parents ) 
);
+       }
+
        public function testRedirectPartner() {
                // Create Charles.
                $charlesTitle = Title::newFromText( 'Charles' );

-- 
To view, visit https://gerrit.wikimedia.org/r/321215
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I9c5fad962b52e0dcb64038935ee84b15c7a1f6fb
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Genealogy
Gerrit-Branch: master
Gerrit-Owner: Samwilson <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to