Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
TreeFormatter
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 6
156
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOutput
n/a
0 / 0
n/a
0 / 0
0
 outputPerson
n/a
0 / 0
n/a
0 / 0
0
 outputJunction
n/a
0 / 0
n/a
0 / 0
0
 outputEdge
n/a
0 / 0
n/a
0 / 0
0
 varId
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 out
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 visit
0.00% covered (danger)
0.00%
0 / 60
0.00% covered (danger)
0.00%
0 / 1
30
 getPersonGroupIdent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Genealogy;
4
5abstract class TreeFormatter {
6
7    /** @var string Unique tree name. */
8    protected $name;
9
10    /** @var Person[] */
11    protected $ancestors = [];
12
13    /** @var Person[] */
14    protected $descendants = [];
15
16    /** @var string[][] */
17    protected $out;
18
19    /**
20     * @param Person[] $ancestors
21     * @param Person[] $descendants
22     */
23    public function __construct( array $ancestors, array $descendants ) {
24        $this->ancestors = $ancestors;
25        $this->descendants = $descendants;
26    }
27
28    /**
29     * Set the tree name.
30     * @param string $name
31     */
32    public function setName( string $name ) {
33        $this->name = $name;
34    }
35
36    /**
37     * Get the full tree output.
38     * @return string
39     */
40    abstract public function getOutput();
41
42    /**
43     * Output tree syntax for a single person.
44     * @param Person $person
45     * @return void
46     */
47    abstract protected function outputPerson( Person $person );
48
49    /**
50     * Output tree syntax for the junction of parents or partners.
51     * @param string $peopleId
52     * @return void
53     */
54    abstract protected function outputJunction( $peopleId );
55
56    /**
57     * Output syntax for a directed edge.
58     * @param string $group The group this line should go in.
59     * @param string $key The line's unique key.
60     * @param string $from The left-hand side of the arrow.
61     * @param string $to The right-hand side of the arrow.
62     * @param bool $towardsJunction Whether this edge is directed towards a junction.
63     * @return void
64     */
65    abstract protected function outputEdge( $group, $key, $from, $to, $towardsJunction = false );
66
67    /**
68     * Create a graph variable name from any string. It will only contain alphanumeric characters
69     * and the underscore.
70     *
71     * @param string $str
72     * @return string
73     */
74    protected function varId( $str ) {
75        $strTrans = transliterator_transliterate( 'Any-Latin; Latin-ASCII', $str );
76        $strTransConv = iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $strTrans );
77        $var = preg_replace( '/[^a-zA-Z0-9_]/', '', str_replace( ' ', '_', $strTransConv ) );
78        // Add a unique three-character suffix in case multiple input strings
79        // normalize to the same output string above.
80        $suffix = '_' . substr( md5( $str ), 0, 3 );
81        return $var . $suffix;
82    }
83
84    /**
85     * Store a single line of Dot source output. This means we can avoid duplicate output lines,
86     * and also group source by different categories ('partner', 'child', etc.).
87     * @param string $group The group this line should go in.
88     * @param string $key The line's unique key.
89     * @param string $line The line of Dot source code.
90     */
91    protected function out( $group, $key, $line ) {
92        if ( !is_array( $this->out ) ) {
93            $this->out = [];
94        }
95        if ( !isset( $this->out[$group] ) ) {
96            $this->out[$group] = [];
97        }
98        $this->out[$group][$key] = $line;
99    }
100
101    /**
102     * When traversing the tree, each node is visited and this method run on the current person.
103     * @param Person $person The current node's person.
104     */
105    public function visit( Person $person ) {
106        $this->outputPerson( $person );
107
108        $personId = $person->getTitle()->getPrefixedText();
109
110        // Output links to parents.
111        if ( $person->getParents() ) {
112            $parentsId = $this->getPersonGroupIdent( $person->getParents() );
113            $this->outputJunction( $parentsId );
114            $this->outputEdge(
115                'child',
116                $parentsId . $personId,
117                $parentsId,
118                $personId
119            );
120            foreach ( $person->getParents() as $parent ) {
121                $parentId = $parent->getTitle()->getPrefixedText();
122                // Add any non-included parent.
123                $this->outputPerson( $parent );
124                $this->outputEdge(
125                    'partner',
126                    $parentId . $parentsId,
127                    $parentId,
128                    $parentsId,
129                    true
130                );
131            }
132        }
133
134        // Output links to partners.
135        foreach ( $person->getPartners() as $partner ) {
136            // Create a point node for each partnership.
137            $partnerId = $partner->getTitle()->getPrefixedText();
138            $partners = [ $personId, $partnerId ];
139            sort( $partners );
140            $partnersId = $this->getPersonGroupIdent( $partners );
141            $this->outputJunction( $partnersId );
142            // Link this person and this partner to that point node.
143            $this->outputEdge(
144                'partner',
145                $personId . $partnersId,
146                $personId,
147                $partnersId,
148                true
149            );
150            $this->outputEdge(
151                'partner',
152                $partnerId . $partnersId,
153                $partnerId,
154                $partnersId,
155                true
156            );
157            // Create a node for any non-included partner.
158            $this->outputPerson( $partner );
159        }
160
161        // Output links to children.
162        foreach ( $person->getChildren() as $child ) {
163            $parentsId = $this->getPersonGroupIdent( $child->getParents() );
164            $this->outputJunction( $parentsId );
165            $this->outputEdge(
166                'partner',
167                $personId . $parentsId,
168                $personId,
169                $parentsId,
170                true
171            );
172            $childId = $child->getTitle()->getPrefixedText();
173            $this->outputEdge(
174                'child',
175                $parentsId . $childId,
176                $parentsId,
177                $childId
178            );
179            // Add this child in case they don't get included directly in this tree.
180            $this->outputPerson( $child );
181        }
182    }
183
184    /**
185     * Create a node ID for a set of people (i.e. partners, parents, or children).
186     * @param string[]|Person[] $group The people to construct the ID out of.
187     * @return string The node ID, with no wrapping characters.
188     */
189    protected function getPersonGroupIdent( $group ) {
190        return implode( ' AND ', $group ) . ' (GROUP)';
191    }
192}