xref: /webtrees/app/Report/ReportParserGenerate.php (revision 76156db136ac145349654158bdaf94a058f1848e)
1a6f13a4aSGreg Roach<?php
2a6f13a4aSGreg Roach/**
3a6f13a4aSGreg Roach * webtrees: online genealogy
4369c0ce6SGreg Roach * Copyright (C) 2016 webtrees development team
5a6f13a4aSGreg Roach * This program is free software: you can redistribute it and/or modify
6a6f13a4aSGreg Roach * it under the terms of the GNU General Public License as published by
7a6f13a4aSGreg Roach * the Free Software Foundation, either version 3 of the License, or
8a6f13a4aSGreg Roach * (at your option) any later version.
9a6f13a4aSGreg Roach * This program is distributed in the hope that it will be useful,
10a6f13a4aSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
11a6f13a4aSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12a6f13a4aSGreg Roach * GNU General Public License for more details.
13a6f13a4aSGreg Roach * You should have received a copy of the GNU General Public License
14a6f13a4aSGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
15a6f13a4aSGreg Roach */
1676692c8bSGreg Roachnamespace Fisharebest\Webtrees\Report;
1776692c8bSGreg Roach
18a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Auth;
19a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Database;
20a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Date;
21a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Family;
22a4d703aeSGreg Roachuse Fisharebest\Webtrees\Filter;
233d7a8a4cSGreg Roachuse Fisharebest\Webtrees\Functions\Functions;
243d7a8a4cSGreg Roachuse Fisharebest\Webtrees\Functions\FunctionsDate;
25a6f13a4aSGreg Roachuse Fisharebest\Webtrees\GedcomRecord;
26a6f13a4aSGreg Roachuse Fisharebest\Webtrees\GedcomTag;
27a6f13a4aSGreg Roachuse Fisharebest\Webtrees\I18N;
28a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Individual;
29d1286247SGreg Roachuse Fisharebest\Webtrees\Log;
30a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Media;
31729ce104SGreg Roachuse Fisharebest\Webtrees\Note;
32a6f13a4aSGreg Roachuse Fisharebest\Webtrees\Place;
33a6f13a4aSGreg Roach
34a6f13a4aSGreg Roach/**
35a6f13a4aSGreg Roach * Class ReportParserGenerate - parse a report.xml file and generate the report.
36a6f13a4aSGreg Roach */
37a6f13a4aSGreg Roachclass ReportParserGenerate extends ReportParserBase {
38a6f13a4aSGreg Roach	/** @var bool Are we collecting data from <Footnote> elements  */
39a6f13a4aSGreg Roach	private $process_footnote = true;
40a6f13a4aSGreg Roach
41a6f13a4aSGreg Roach	/** @var bool Are we currently outputing data? */
42a6f13a4aSGreg Roach	private $print_data = false;
43a6f13a4aSGreg Roach
44a6f13a4aSGreg Roach	/** @var bool[] Push-down stack of $print_data */
45a6f13a4aSGreg Roach	private $print_data_stack = array();
46a6f13a4aSGreg Roach
4776692c8bSGreg Roach	/** @var int Are we processing GEDCOM data */
48a6f13a4aSGreg Roach	private $process_gedcoms = 0;
49a6f13a4aSGreg Roach
5076692c8bSGreg Roach	/** @var int Are we processing conditionals */
51a6f13a4aSGreg Roach	private $process_ifs = 0;
52a6f13a4aSGreg Roach
5376692c8bSGreg Roach	/** @var int Are we processing repeats*/
54a6f13a4aSGreg Roach	private $process_repeats = 0;
55a6f13a4aSGreg Roach
56a6f13a4aSGreg Roach	/** @var int Quantity of data to repeat during loops */
57a6f13a4aSGreg Roach	private $repeat_bytes = 0;
58a6f13a4aSGreg Roach
59a6f13a4aSGreg Roach	/** @var array[] Repeated data when iterating over loops */
60a6f13a4aSGreg Roach	private $repeats = array();
61a6f13a4aSGreg Roach
62a6f13a4aSGreg Roach	/** @var array[] Nested repeating data */
63a6f13a4aSGreg Roach	private $repeats_stack = array();
64a6f13a4aSGreg Roach
65e8e7866bSGreg Roach	/** @var ReportBase[] Nested repeating data */
66e8e7866bSGreg Roach	private $wt_report_stack = array();
67e8e7866bSGreg Roach
68e8e7866bSGreg Roach	/** @var resource Nested repeating data */
69e8e7866bSGreg Roach	private $parser;
70e8e7866bSGreg Roach
71e8e7866bSGreg Roach	/** @var resource[] Nested repeating data */
72e8e7866bSGreg Roach	private $parser_stack = array();
73e8e7866bSGreg Roach
74a6f13a4aSGreg Roach	/** @var string The current GEDCOM record */
75a6f13a4aSGreg Roach	private $gedrec = '';
76a6f13a4aSGreg Roach
77a6f13a4aSGreg Roach	/** @var string[] Nested GEDCOM records */
78a6f13a4aSGreg Roach	private $gedrec_stack = array();
79a6f13a4aSGreg Roach
80a6f13a4aSGreg Roach	/** @var ReportBaseElement The currently processed element */
81a6f13a4aSGreg Roach	private $current_element;
82a6f13a4aSGreg Roach
83a6f13a4aSGreg Roach	/** @var ReportBaseElement The currently processed element */
84a6f13a4aSGreg Roach	private $footnote_element;
85a6f13a4aSGreg Roach
86a6f13a4aSGreg Roach	/** @var string The GEDCOM fact currently being processed */
87a6f13a4aSGreg Roach	private $fact = '';
88a6f13a4aSGreg Roach
89a6f13a4aSGreg Roach	/** @var string The GEDCOM value currently being processed */
90a6f13a4aSGreg Roach	private $desc = '';
91a6f13a4aSGreg Roach
92a6f13a4aSGreg Roach	/** @var string The GEDCOM type currently being processed */
93a6f13a4aSGreg Roach	private $type = '';
94a6f13a4aSGreg Roach
95a6f13a4aSGreg Roach	/** @var int The current generational level */
96a6f13a4aSGreg Roach	private $generation = 1;
97a6f13a4aSGreg Roach
98a6f13a4aSGreg Roach	/** @var array Source data for processing lists */
99a6f13a4aSGreg Roach	private $list = array();
100a6f13a4aSGreg Roach
101a6f13a4aSGreg Roach	/** @var int Number of items in lists */
102a6f13a4aSGreg Roach	private $list_total = 0;
103a6f13a4aSGreg Roach
104a6f13a4aSGreg Roach	/** @var int Number of items filtered from lists */
105a6f13a4aSGreg Roach	private $list_private = 0;
106a6f13a4aSGreg Roach
107e8e7866bSGreg Roach	/** @var ReportBase A factory for creating report elements */
108e8e7866bSGreg Roach	private $report_root;
109e8e7866bSGreg Roach
110e8e7866bSGreg Roach	/** @var ReportBase Nested report elements */
111e8e7866bSGreg Roach	private $wt_report;
112e8e7866bSGreg Roach
113aa22a75eSGreg Roach	/** @todo This attribute is public to support the PHP5.3 closure workaround. */
114d1286247SGreg Roach	/** @var string[][] Variables defined in the report at run-time */
115aa22a75eSGreg Roach	public $vars;
116d1286247SGreg Roach
11776692c8bSGreg Roach	/**
11876692c8bSGreg Roach	 * Create a parser for a report
11976692c8bSGreg Roach	 *
12076692c8bSGreg Roach	 * @param string     $report     The XML filename
12176692c8bSGreg Roach	 * @param ReportBase $report_root
12276692c8bSGreg Roach	 * @param string[][] $vars
12376692c8bSGreg Roach	 */
12482759250SGreg Roach	public function __construct($report, ReportBase $report_root = null, array $vars = array()) {
125e8e7866bSGreg Roach		$this->report_root     = $report_root;
126e8e7866bSGreg Roach		$this->wt_report       = $report_root;
127a6f13a4aSGreg Roach		$this->current_element = new ReportBaseElement;
128d1286247SGreg Roach		$this->vars            = $vars;
129a6f13a4aSGreg Roach		parent::__construct($report);
130a6f13a4aSGreg Roach	}
131a6f13a4aSGreg Roach
132a6f13a4aSGreg Roach	/**
133a6f13a4aSGreg Roach	 * XML start element handler
134a6f13a4aSGreg Roach	 *
135a6f13a4aSGreg Roach	 * This function is called whenever a starting element is reached
136a6f13a4aSGreg Roach	 * The element handler will be called if found, otherwise it must be HTML
137a6f13a4aSGreg Roach	 *
138a6f13a4aSGreg Roach	 * @param resource $parser the resource handler for the XML parser
139a6f13a4aSGreg Roach	 * @param string   $name   the name of the XML element parsed
140a6f13a4aSGreg Roach	 * @param array    $attrs  an array of key value pairs for the attributes
141a6f13a4aSGreg Roach	 */
1428edd1043SGreg Roach	protected function startElement($parser, $name, $attrs) {
143a6f13a4aSGreg Roach		$newattrs = array();
144a6f13a4aSGreg Roach
145a6f13a4aSGreg Roach		foreach ($attrs as $key => $value) {
146a6f13a4aSGreg Roach			if (preg_match("/^\\$(\w+)$/", $value, $match)) {
147d1286247SGreg Roach				if ((isset($this->vars[$match[1]]['id'])) && (!isset($this->vars[$match[1]]['gedcom']))) {
148d1286247SGreg Roach					$value = $this->vars[$match[1]]['id'];
149a6f13a4aSGreg Roach				}
150a6f13a4aSGreg Roach			}
151a6f13a4aSGreg Roach			$newattrs[$key] = $value;
152a6f13a4aSGreg Roach		}
153a6f13a4aSGreg Roach		$attrs = $newattrs;
154a6f13a4aSGreg Roach		if ($this->process_footnote && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag")) {
155a6f13a4aSGreg Roach			$start_method = $name . 'StartHandler';
156a6f13a4aSGreg Roach			$end_method   = $name . 'EndHandler';
157a6f13a4aSGreg Roach			if (method_exists($this, $start_method)) {
158a6f13a4aSGreg Roach				$this->$start_method($attrs);
159a6f13a4aSGreg Roach			} elseif (!method_exists($this, $end_method)) {
160a6f13a4aSGreg Roach				$this->htmlStartHandler($name, $attrs);
161a6f13a4aSGreg Roach			}
162a6f13a4aSGreg Roach		}
163a6f13a4aSGreg Roach	}
164a6f13a4aSGreg Roach
165a6f13a4aSGreg Roach	/**
166a6f13a4aSGreg Roach	 * XML end element handler
167a6f13a4aSGreg Roach	 *
168a6f13a4aSGreg Roach	 * This function is called whenever an ending element is reached
169a6f13a4aSGreg Roach	 * The element handler will be called if found, otherwise it must be HTML
170a6f13a4aSGreg Roach	 *
171a6f13a4aSGreg Roach	 * @param resource $parser the resource handler for the XML parser
172a6f13a4aSGreg Roach	 * @param string   $name   the name of the XML element parsed
173a6f13a4aSGreg Roach	 */
1748edd1043SGreg Roach	protected function endElement($parser, $name) {
175a6f13a4aSGreg Roach		if (($this->process_footnote || $name === "Footnote") && ($this->process_ifs === 0 || $name === "if") && ($this->process_gedcoms === 0 || $name === "Gedcom") && ($this->process_repeats === 0 || $name === "Facts" || $name === "RepeatTag" || $name === "List" || $name === "Relatives")) {
176a6f13a4aSGreg Roach			$start_method = $name . 'StartHandler';
177a6f13a4aSGreg Roach			$end_method   = $name . 'EndHandler';
178a6f13a4aSGreg Roach			if (method_exists($this, $end_method)) {
179a6f13a4aSGreg Roach				$this->$end_method();
180a6f13a4aSGreg Roach			} elseif (!method_exists($this, $start_method)) {
181a6f13a4aSGreg Roach				$this->htmlEndHandler($name);
182a6f13a4aSGreg Roach			}
183a6f13a4aSGreg Roach		}
184a6f13a4aSGreg Roach	}
185a6f13a4aSGreg Roach
186a6f13a4aSGreg Roach	/**
187a6f13a4aSGreg Roach	 * XML character data handler
188a6f13a4aSGreg Roach	 *
189a6f13a4aSGreg Roach	 * @param resource $parser the resource handler for the XML parser
190a6f13a4aSGreg Roach	 * @param string   $data   the name of the XML element parsed
191a6f13a4aSGreg Roach	 */
1928edd1043SGreg Roach	protected function characterData($parser, $data) {
193e8e7866bSGreg Roach		if ($this->print_data && $this->process_gedcoms === 0 && $this->process_ifs === 0 && $this->process_repeats === 0) {
194a6f13a4aSGreg Roach			$this->current_element->addText($data);
195a6f13a4aSGreg Roach		}
196a6f13a4aSGreg Roach	}
197a6f13a4aSGreg Roach
198a6f13a4aSGreg Roach	/**
19976692c8bSGreg Roach	 * XML <style>
200a6f13a4aSGreg Roach	 *
201a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
202a6f13a4aSGreg Roach	 */
2038edd1043SGreg Roach	private function styleStartHandler($attrs) {
204a6f13a4aSGreg Roach		if (empty($attrs['name'])) {
205a6f13a4aSGreg Roach			throw new \DomainException('REPORT ERROR Style: The "name" of the style is missing or not set in the XML file.');
206a6f13a4aSGreg Roach		}
207a6f13a4aSGreg Roach
208a6f13a4aSGreg Roach		// array Style that will be passed on
209a6f13a4aSGreg Roach		$s = array();
210a6f13a4aSGreg Roach
211a6f13a4aSGreg Roach		// string Name af the style
212a6f13a4aSGreg Roach		$s['name'] = $attrs['name'];
213a6f13a4aSGreg Roach
214a6f13a4aSGreg Roach		// string Name of the DEFAULT font
215e8e7866bSGreg Roach		$s['font'] = $this->wt_report->defaultFont;
216a6f13a4aSGreg Roach		if (!empty($attrs['font'])) {
217a6f13a4aSGreg Roach			$s['font'] = $attrs['font'];
218a6f13a4aSGreg Roach		}
219a6f13a4aSGreg Roach
220a6f13a4aSGreg Roach		// int The size of the font in points
221e8e7866bSGreg Roach		$s['size'] = $this->wt_report->defaultFontSize;
222a6f13a4aSGreg Roach		if (!empty($attrs['size'])) {
223a6f13a4aSGreg Roach			$s['size'] = (int) $attrs['size'];
224a6f13a4aSGreg Roach		} // Get it as int to ignore all decimal points or text (if any text then int(0))
225a6f13a4aSGreg Roach
226a6f13a4aSGreg Roach		// string B: bold, I: italic, U: underline, D: line trough, The default value is regular.
227a6f13a4aSGreg Roach		$s['style'] = "";
228a6f13a4aSGreg Roach		if (!empty($attrs['style'])) {
229a6f13a4aSGreg Roach			$s['style'] = $attrs['style'];
230a6f13a4aSGreg Roach		}
231a6f13a4aSGreg Roach
232e8e7866bSGreg Roach		$this->wt_report->addStyle($s);
233a6f13a4aSGreg Roach	}
234a6f13a4aSGreg Roach
235a6f13a4aSGreg Roach	/**
23676692c8bSGreg Roach	 * XML <Doc>
237a6f13a4aSGreg Roach	 *
238a6f13a4aSGreg Roach	 * Sets up the basics of the document proparties
239a6f13a4aSGreg Roach	 *
240a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
241a6f13a4aSGreg Roach	 */
2428edd1043SGreg Roach	private function docStartHandler($attrs) {
243e8e7866bSGreg Roach		$this->parser = $this->xml_parser;
244a6f13a4aSGreg Roach
245a6f13a4aSGreg Roach		// Custom page width
246a6f13a4aSGreg Roach		if (!empty($attrs['customwidth'])) {
247e8e7866bSGreg Roach			$this->wt_report->pagew = (int) $attrs['customwidth'];
248a6f13a4aSGreg Roach		} // Get it as int to ignore all decimal points or text (if any text then int(0))
249a6f13a4aSGreg Roach		// Custom Page height
250a6f13a4aSGreg Roach		if (!empty($attrs['customheight'])) {
251e8e7866bSGreg Roach			$this->wt_report->pageh = (int) $attrs['customheight'];
252a6f13a4aSGreg Roach		} // Get it as int to ignore all decimal points or text (if any text then int(0))
253a6f13a4aSGreg Roach
254a6f13a4aSGreg Roach		// Left Margin
255a6f13a4aSGreg Roach		if (isset($attrs['leftmargin'])) {
256a6f13a4aSGreg Roach			if ($attrs['leftmargin'] === "0") {
257e8e7866bSGreg Roach				$this->wt_report->leftmargin = 0;
258a6f13a4aSGreg Roach			} elseif (!empty($attrs['leftmargin'])) {
259e8e7866bSGreg Roach				$this->wt_report->leftmargin = (int) $attrs['leftmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
260a6f13a4aSGreg Roach			}
261a6f13a4aSGreg Roach		}
262a6f13a4aSGreg Roach		// Right Margin
263a6f13a4aSGreg Roach		if (isset($attrs['rightmargin'])) {
264a6f13a4aSGreg Roach			if ($attrs['rightmargin'] === "0") {
265e8e7866bSGreg Roach				$this->wt_report->rightmargin = 0;
266a6f13a4aSGreg Roach			} elseif (!empty($attrs['rightmargin'])) {
267e8e7866bSGreg Roach				$this->wt_report->rightmargin = (int) $attrs['rightmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
268a6f13a4aSGreg Roach			}
269a6f13a4aSGreg Roach		}
270a6f13a4aSGreg Roach		// Top Margin
271a6f13a4aSGreg Roach		if (isset($attrs['topmargin'])) {
272a6f13a4aSGreg Roach			if ($attrs['topmargin'] === "0") {
273e8e7866bSGreg Roach				$this->wt_report->topmargin = 0;
274a6f13a4aSGreg Roach			} elseif (!empty($attrs['topmargin'])) {
275e8e7866bSGreg Roach				$this->wt_report->topmargin = (int) $attrs['topmargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
276a6f13a4aSGreg Roach			}
277a6f13a4aSGreg Roach		}
278a6f13a4aSGreg Roach		// Bottom Margin
279a6f13a4aSGreg Roach		if (isset($attrs['bottommargin'])) {
280a6f13a4aSGreg Roach			if ($attrs['bottommargin'] === "0") {
281e8e7866bSGreg Roach				$this->wt_report->bottommargin = 0;
282a6f13a4aSGreg Roach			} elseif (!empty($attrs['bottommargin'])) {
283e8e7866bSGreg Roach				$this->wt_report->bottommargin = (int) $attrs['bottommargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
284a6f13a4aSGreg Roach			}
285a6f13a4aSGreg Roach		}
286a6f13a4aSGreg Roach		// Header Margin
287a6f13a4aSGreg Roach		if (isset($attrs['headermargin'])) {
288a6f13a4aSGreg Roach			if ($attrs['headermargin'] === "0") {
289e8e7866bSGreg Roach				$this->wt_report->headermargin = 0;
290a6f13a4aSGreg Roach			} elseif (!empty($attrs['headermargin'])) {
291e8e7866bSGreg Roach				$this->wt_report->headermargin = (int) $attrs['headermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
292a6f13a4aSGreg Roach			}
293a6f13a4aSGreg Roach		}
294a6f13a4aSGreg Roach		// Footer Margin
295a6f13a4aSGreg Roach		if (isset($attrs['footermargin'])) {
296a6f13a4aSGreg Roach			if ($attrs['footermargin'] === "0") {
297e8e7866bSGreg Roach				$this->wt_report->footermargin = 0;
298a6f13a4aSGreg Roach			} elseif (!empty($attrs['footermargin'])) {
299e8e7866bSGreg Roach				$this->wt_report->footermargin = (int) $attrs['footermargin']; // Get it as int to ignore all decimal points or text (if any text then int(0))
300a6f13a4aSGreg Roach			}
301a6f13a4aSGreg Roach		}
302a6f13a4aSGreg Roach
303a6f13a4aSGreg Roach		// Page Orientation
304a6f13a4aSGreg Roach		if (!empty($attrs['orientation'])) {
305a6f13a4aSGreg Roach			if ($attrs['orientation'] == "landscape") {
306e8e7866bSGreg Roach				$this->wt_report->orientation = "landscape";
307a6f13a4aSGreg Roach			} elseif ($attrs['orientation'] == "portrait") {
308e8e7866bSGreg Roach				$this->wt_report->orientation = "portrait";
309a6f13a4aSGreg Roach			}
310a6f13a4aSGreg Roach		}
311a6f13a4aSGreg Roach		// Page Size
312a6f13a4aSGreg Roach		if (!empty($attrs['pageSize'])) {
313e8e7866bSGreg Roach			$this->wt_report->pageFormat = strtoupper($attrs['pageSize']);
314a6f13a4aSGreg Roach		}
315a6f13a4aSGreg Roach
316a6f13a4aSGreg Roach		// Show Generated By...
317a6f13a4aSGreg Roach		if (isset($attrs['showGeneratedBy'])) {
318a6f13a4aSGreg Roach			if ($attrs['showGeneratedBy'] === "0") {
319e8e7866bSGreg Roach				$this->wt_report->showGenText = false;
320a6f13a4aSGreg Roach			} elseif ($attrs['showGeneratedBy'] === "1") {
321e8e7866bSGreg Roach				$this->wt_report->showGenText = true;
322a6f13a4aSGreg Roach			}
323a6f13a4aSGreg Roach		}
324a6f13a4aSGreg Roach
325e8e7866bSGreg Roach		$this->wt_report->setup();
326a6f13a4aSGreg Roach	}
327a6f13a4aSGreg Roach
328a6f13a4aSGreg Roach	/**
32976692c8bSGreg Roach	 * XML </Doc>
330a6f13a4aSGreg Roach	 */
3318edd1043SGreg Roach	private function docEndHandler() {
332e8e7866bSGreg Roach		$this->wt_report->run();
333a6f13a4aSGreg Roach	}
334a6f13a4aSGreg Roach
335a6f13a4aSGreg Roach	/**
33676692c8bSGreg Roach	 * XML <Header>
337a6f13a4aSGreg Roach	 */
3388edd1043SGreg Roach	private function headerStartHandler() {
339a6f13a4aSGreg Roach		// Clear the Header before any new elements are added
340e8e7866bSGreg Roach		$this->wt_report->clearHeader();
341e8e7866bSGreg Roach		$this->wt_report->setProcessing("H");
342a6f13a4aSGreg Roach	}
343a6f13a4aSGreg Roach
344a6f13a4aSGreg Roach	/**
34576692c8bSGreg Roach	 * XML <PageHeader>
346a6f13a4aSGreg Roach	 */
3478edd1043SGreg Roach	private function pageHeaderStartHandler() {
348a6f13a4aSGreg Roach		array_push($this->print_data_stack, $this->print_data);
349a6f13a4aSGreg Roach		$this->print_data = false;
350e8e7866bSGreg Roach		array_push($this->wt_report_stack, $this->wt_report);
351e8e7866bSGreg Roach		$this->wt_report = $this->report_root->createPageHeader();
352a6f13a4aSGreg Roach	}
353a6f13a4aSGreg Roach
354a6f13a4aSGreg Roach	/**
35576692c8bSGreg Roach	 * XML <pageHeaderEndHandler>
356a6f13a4aSGreg Roach	 */
3578edd1043SGreg Roach	private function pageHeaderEndHandler() {
358a6f13a4aSGreg Roach		$this->print_data        = array_pop($this->print_data_stack);
359e8e7866bSGreg Roach		$this->current_element   = $this->wt_report;
360e8e7866bSGreg Roach		$this->wt_report         = array_pop($this->wt_report_stack);
361e8e7866bSGreg Roach		$this->wt_report->addElement($this->current_element);
362a6f13a4aSGreg Roach	}
363a6f13a4aSGreg Roach
364a6f13a4aSGreg Roach	/**
36576692c8bSGreg Roach	 * XML <bodyStartHandler>
366a6f13a4aSGreg Roach	 */
3678edd1043SGreg Roach	private function bodyStartHandler() {
368e8e7866bSGreg Roach		$this->wt_report->setProcessing("B");
369a6f13a4aSGreg Roach	}
370a6f13a4aSGreg Roach
371a6f13a4aSGreg Roach	/**
37276692c8bSGreg Roach	 * XML <footerStartHandler>
373a6f13a4aSGreg Roach	 */
3748edd1043SGreg Roach	private function footerStartHandler() {
375e8e7866bSGreg Roach		$this->wt_report->setProcessing("F");
376a6f13a4aSGreg Roach	}
377a6f13a4aSGreg Roach
378a6f13a4aSGreg Roach	/**
37976692c8bSGreg Roach	 * XML <Cell>
380a6f13a4aSGreg Roach	 *
381a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
382a6f13a4aSGreg Roach	 */
3838edd1043SGreg Roach	private function cellStartHandler($attrs) {
384a6f13a4aSGreg Roach		// string The text alignment of the text in this box.
385a6f13a4aSGreg Roach		$align = "";
386a6f13a4aSGreg Roach		if (!empty($attrs['align'])) {
387a6f13a4aSGreg Roach			$align = $attrs['align'];
388a6f13a4aSGreg Roach			// RTL supported left/right alignment
389a6f13a4aSGreg Roach			if ($align == "rightrtl") {
390e8e7866bSGreg Roach				if ($this->wt_report->rtl) {
391a6f13a4aSGreg Roach					$align = "left";
392a6f13a4aSGreg Roach				} else {
393a6f13a4aSGreg Roach					$align = "right";
394a6f13a4aSGreg Roach				}
395a6f13a4aSGreg Roach			} elseif ($align == "leftrtl") {
396e8e7866bSGreg Roach				if ($this->wt_report->rtl) {
397a6f13a4aSGreg Roach					$align = "right";
398a6f13a4aSGreg Roach				} else {
399a6f13a4aSGreg Roach					$align = "left";
400a6f13a4aSGreg Roach				}
401a6f13a4aSGreg Roach			}
402a6f13a4aSGreg Roach		}
403a6f13a4aSGreg Roach
404a6f13a4aSGreg Roach		// string The color to fill the background of this cell
405a6f13a4aSGreg Roach		$bgcolor = "";
406a6f13a4aSGreg Roach		if (!empty($attrs['bgcolor'])) {
407a6f13a4aSGreg Roach			$bgcolor = $attrs['bgcolor'];
408a6f13a4aSGreg Roach		}
409a6f13a4aSGreg Roach
410a6f13a4aSGreg Roach		// int Whether or not the background should be painted
411a6f13a4aSGreg Roach		$fill = 1;
412a6f13a4aSGreg Roach		if (isset($attrs['fill'])) {
413a6f13a4aSGreg Roach			if ($attrs['fill'] === "0") {
414a6f13a4aSGreg Roach				$fill = 0;
415a6f13a4aSGreg Roach			} elseif ($attrs['fill'] === "1") {
416a6f13a4aSGreg Roach				$fill = 1;
417a6f13a4aSGreg Roach			}
418a6f13a4aSGreg Roach		}
419a6f13a4aSGreg Roach
420a6f13a4aSGreg Roach		$reseth = true;
421a6f13a4aSGreg Roach		// boolean   if true reset the last cell height (default true)
422a6f13a4aSGreg Roach		if (isset($attrs['reseth'])) {
423a6f13a4aSGreg Roach			if ($attrs['reseth'] === "0") {
424a6f13a4aSGreg Roach				$reseth = false;
425a6f13a4aSGreg Roach			} elseif ($attrs['reseth'] === "1") {
426a6f13a4aSGreg Roach				$reseth = true;
427a6f13a4aSGreg Roach			}
428a6f13a4aSGreg Roach		}
429a6f13a4aSGreg Roach
430a6f13a4aSGreg Roach		// mixed Whether or not a border should be printed around this box
431a6f13a4aSGreg Roach		$border = 0;
432a6f13a4aSGreg Roach		if (!empty($attrs['border'])) {
433a6f13a4aSGreg Roach			$border = $attrs['border'];
434a6f13a4aSGreg Roach		}
435a6f13a4aSGreg Roach		// string Border color in HTML code
436a6f13a4aSGreg Roach		$bocolor = "";
437a6f13a4aSGreg Roach		if (!empty($attrs['bocolor'])) {
438a6f13a4aSGreg Roach			$bocolor = $attrs['bocolor'];
439a6f13a4aSGreg Roach		}
440a6f13a4aSGreg Roach
441a6f13a4aSGreg Roach		// int Cell height (expressed in points) The starting height of this cell. If the text wraps the height will automatically be adjusted.
442a6f13a4aSGreg Roach		$height = 0;
443a6f13a4aSGreg Roach		if (!empty($attrs['height'])) {
444a6f13a4aSGreg Roach			$height = (int) $attrs['height'];
445a6f13a4aSGreg Roach		}
446a6f13a4aSGreg Roach		// int Cell width (expressed in points) Setting the width to 0 will make it the width from the current location to the right margin.
447a6f13a4aSGreg Roach		$width = 0;
448a6f13a4aSGreg Roach		if (!empty($attrs['width'])) {
449a6f13a4aSGreg Roach			$width = (int) $attrs['width'];
450a6f13a4aSGreg Roach		}
451a6f13a4aSGreg Roach
452a6f13a4aSGreg Roach		// int Stretch carachter mode
453a6f13a4aSGreg Roach		$stretch = 0;
454a6f13a4aSGreg Roach		if (!empty($attrs['stretch'])) {
455a6f13a4aSGreg Roach			$stretch = (int) $attrs['stretch'];
456a6f13a4aSGreg Roach		}
457a6f13a4aSGreg Roach
458a6f13a4aSGreg Roach		// mixed Position the left corner of this box on the page. The default is the current position.
459a6f13a4aSGreg Roach		$left = ".";
460a6f13a4aSGreg Roach		if (isset($attrs['left'])) {
461a6f13a4aSGreg Roach			if ($attrs['left'] === ".") {
462a6f13a4aSGreg Roach				$left = ".";
463a6f13a4aSGreg Roach			} elseif (!empty($attrs['left'])) {
464a6f13a4aSGreg Roach				$left = (int) $attrs['left'];
465a6f13a4aSGreg Roach			} elseif ($attrs['left'] === "0") {
466a6f13a4aSGreg Roach				$left = 0;
467a6f13a4aSGreg Roach			}
468a6f13a4aSGreg Roach		}
469a6f13a4aSGreg Roach		// mixed Position the top corner of this box on the page. the default is the current position
470a6f13a4aSGreg Roach		$top = ".";
471a6f13a4aSGreg Roach		if (isset($attrs['top'])) {
472a6f13a4aSGreg Roach			if ($attrs['top'] === ".") {
473a6f13a4aSGreg Roach				$top = ".";
474a6f13a4aSGreg Roach			} elseif (!empty($attrs['top'])) {
475a6f13a4aSGreg Roach				$top = (int) $attrs['top'];
476a6f13a4aSGreg Roach			} elseif ($attrs['top'] === "0") {
477a6f13a4aSGreg Roach				$top = 0;
478a6f13a4aSGreg Roach			}
479a6f13a4aSGreg Roach		}
480a6f13a4aSGreg Roach
481a6f13a4aSGreg Roach		// string The name of the Style that should be used to render the text.
482a6f13a4aSGreg Roach		$style = "";
483a6f13a4aSGreg Roach		if (!empty($attrs['style'])) {
484a6f13a4aSGreg Roach			$style = $attrs['style'];
485a6f13a4aSGreg Roach		}
486a6f13a4aSGreg Roach
487a6f13a4aSGreg Roach		// string Text color in html code
488a6f13a4aSGreg Roach		$tcolor = "";
489a6f13a4aSGreg Roach		if (!empty($attrs['tcolor'])) {
490a6f13a4aSGreg Roach			$tcolor = $attrs['tcolor'];
491a6f13a4aSGreg Roach		}
492a6f13a4aSGreg Roach
493a6f13a4aSGreg Roach		// int Indicates where the current position should go after the call.
494a6f13a4aSGreg Roach		$ln = 0;
495a6f13a4aSGreg Roach		if (isset($attrs['newline'])) {
496a6f13a4aSGreg Roach			if (!empty($attrs['newline'])) {
497a6f13a4aSGreg Roach				$ln = (int) $attrs['newline'];
498a6f13a4aSGreg Roach			} elseif ($attrs['newline'] === "0") {
499a6f13a4aSGreg Roach				$ln = 0;
500a6f13a4aSGreg Roach			}
501a6f13a4aSGreg Roach		}
502a6f13a4aSGreg Roach
503a6f13a4aSGreg Roach		if ($align == "left") {
504a6f13a4aSGreg Roach			$align = "L";
505a6f13a4aSGreg Roach		} elseif ($align == "right") {
506a6f13a4aSGreg Roach			$align = "R";
507a6f13a4aSGreg Roach		} elseif ($align == "center") {
508a6f13a4aSGreg Roach			$align = "C";
509a6f13a4aSGreg Roach		} elseif ($align == "justify") {
510a6f13a4aSGreg Roach			$align = "J";
511a6f13a4aSGreg Roach		}
512a6f13a4aSGreg Roach
513a6f13a4aSGreg Roach		array_push($this->print_data_stack, $this->print_data);
514a6f13a4aSGreg Roach		$this->print_data = true;
515a6f13a4aSGreg Roach
516e8e7866bSGreg Roach		$this->current_element = $this->report_root->createCell(
517a6f13a4aSGreg Roach			$width,
518a6f13a4aSGreg Roach			$height,
519a6f13a4aSGreg Roach			$border,
520a6f13a4aSGreg Roach			$align,
521a6f13a4aSGreg Roach			$bgcolor,
522a6f13a4aSGreg Roach			$style,
523a6f13a4aSGreg Roach			$ln,
524a6f13a4aSGreg Roach			$top,
525a6f13a4aSGreg Roach			$left,
526a6f13a4aSGreg Roach			$fill,
527a6f13a4aSGreg Roach			$stretch,
528a6f13a4aSGreg Roach			$bocolor,
529a6f13a4aSGreg Roach			$tcolor,
530a6f13a4aSGreg Roach			$reseth
531a6f13a4aSGreg Roach		);
532a6f13a4aSGreg Roach	}
533a6f13a4aSGreg Roach
534a6f13a4aSGreg Roach	/**
53576692c8bSGreg Roach	 * XML </Cell>
536a6f13a4aSGreg Roach	 */
5378edd1043SGreg Roach	private function cellEndHandler() {
538a6f13a4aSGreg Roach		$this->print_data = array_pop($this->print_data_stack);
539e8e7866bSGreg Roach		$this->wt_report->addElement($this->current_element);
540a6f13a4aSGreg Roach	}
541a6f13a4aSGreg Roach
542a6f13a4aSGreg Roach	/**
543a6f13a4aSGreg Roach	 * XML <Now /> element handler
544a6f13a4aSGreg Roach	 */
5458edd1043SGreg Roach	private function nowStartHandler() {
5463d7a8a4cSGreg Roach		$g = FunctionsDate::timestampToGedcomDate(WT_TIMESTAMP + WT_TIMESTAMP_OFFSET);
547a6f13a4aSGreg Roach		$this->current_element->addText($g->display());
548a6f13a4aSGreg Roach	}
549a6f13a4aSGreg Roach
550a6f13a4aSGreg Roach	/**
551a6f13a4aSGreg Roach	 * XML <PageNum /> element handler
552a6f13a4aSGreg Roach	 */
5538edd1043SGreg Roach	private function pageNumStartHandler() {
554a6f13a4aSGreg Roach		$this->current_element->addText("#PAGENUM#");
555a6f13a4aSGreg Roach	}
556a6f13a4aSGreg Roach
557a6f13a4aSGreg Roach	/**
558a6f13a4aSGreg Roach	 * XML <TotalPages /> element handler
559a6f13a4aSGreg Roach	 */
5608edd1043SGreg Roach	private function totalPagesStartHandler() {
561a6f13a4aSGreg Roach		$this->current_element->addText("{{:ptp:}}");
562a6f13a4aSGreg Roach	}
563a6f13a4aSGreg Roach
564a6f13a4aSGreg Roach	/**
565a6f13a4aSGreg Roach	 * Called at the start of an element.
566a6f13a4aSGreg Roach	 *
567a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
568a6f13a4aSGreg Roach	 */
5698edd1043SGreg Roach	private function gedcomStartHandler($attrs) {
570d1286247SGreg Roach		global $WT_TREE;
571a6f13a4aSGreg Roach
572a6f13a4aSGreg Roach		if ($this->process_gedcoms > 0) {
573a6f13a4aSGreg Roach			$this->process_gedcoms++;
574a6f13a4aSGreg Roach
575a6f13a4aSGreg Roach			return;
576a6f13a4aSGreg Roach		}
577a6f13a4aSGreg Roach
578a6f13a4aSGreg Roach		$tag       = $attrs['id'];
579a6f13a4aSGreg Roach		$tag       = str_replace("@fact", $this->fact, $tag);
580a6f13a4aSGreg Roach		$tags      = explode(":", $tag);
581a6f13a4aSGreg Roach		$newgedrec = '';
582a6f13a4aSGreg Roach		if (count($tags) < 2) {
583a6f13a4aSGreg Roach			$tmp       = GedcomRecord::getInstance($attrs['id'], $WT_TREE);
584a6f13a4aSGreg Roach			$newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
585a6f13a4aSGreg Roach		}
586a6f13a4aSGreg Roach		if (empty($newgedrec)) {
587a6f13a4aSGreg Roach			$tgedrec   = $this->gedrec;
588a6f13a4aSGreg Roach			$newgedrec = '';
589a6f13a4aSGreg Roach			foreach ($tags as $tag) {
590a6f13a4aSGreg Roach				if (preg_match("/\\$(.+)/", $tag, $match)) {
591d1286247SGreg Roach					if (isset($this->vars[$match[1]]['gedcom'])) {
592d1286247SGreg Roach						$newgedrec = $this->vars[$match[1]]['gedcom'];
593a6f13a4aSGreg Roach					} else {
594a6f13a4aSGreg Roach						$tmp       = GedcomRecord::getInstance($match[1], $WT_TREE);
595a6f13a4aSGreg Roach						$newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
596a6f13a4aSGreg Roach					}
597a6f13a4aSGreg Roach				} else {
598a6f13a4aSGreg Roach					if (preg_match("/@(.+)/", $tag, $match)) {
599a6f13a4aSGreg Roach						$gmatch = array();
600a6f13a4aSGreg Roach						if (preg_match("/\d $match[1] @([^@]+)@/", $tgedrec, $gmatch)) {
601a6f13a4aSGreg Roach							$tmp       = GedcomRecord::getInstance($gmatch[1], $WT_TREE);
602a6f13a4aSGreg Roach							$newgedrec = $tmp ? $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE)) : '';
603a6f13a4aSGreg Roach							$tgedrec   = $newgedrec;
604a6f13a4aSGreg Roach						} else {
605a6f13a4aSGreg Roach							$newgedrec = '';
606a6f13a4aSGreg Roach							break;
607a6f13a4aSGreg Roach						}
608a6f13a4aSGreg Roach					} else {
609a6f13a4aSGreg Roach						$temp      = explode(" ", trim($tgedrec));
610a6f13a4aSGreg Roach						$level     = $temp[0] + 1;
6113d7a8a4cSGreg Roach						$newgedrec = Functions::getSubRecord($level, "$level $tag", $tgedrec);
612a6f13a4aSGreg Roach						$tgedrec   = $newgedrec;
613a6f13a4aSGreg Roach					}
614a6f13a4aSGreg Roach				}
615a6f13a4aSGreg Roach			}
616a6f13a4aSGreg Roach		}
617a6f13a4aSGreg Roach		if (!empty($newgedrec)) {
618a6f13a4aSGreg Roach			array_push($this->gedrec_stack, array($this->gedrec, $this->fact, $this->desc));
619a6f13a4aSGreg Roach			$this->gedrec = $newgedrec;
620a6f13a4aSGreg Roach			if (preg_match("/(\d+) (_?[A-Z0-9]+) (.*)/", $this->gedrec, $match)) {
621a6f13a4aSGreg Roach				$this->fact = $match[2];
622a6f13a4aSGreg Roach				$this->desc = trim($match[3]);
623a6f13a4aSGreg Roach			}
624a6f13a4aSGreg Roach		} else {
625a6f13a4aSGreg Roach			$this->process_gedcoms++;
626a6f13a4aSGreg Roach		}
627a6f13a4aSGreg Roach	}
628a6f13a4aSGreg Roach
629a6f13a4aSGreg Roach	/**
630a6f13a4aSGreg Roach	 * Called at the end of an element.
631a6f13a4aSGreg Roach	 */
6328edd1043SGreg Roach	private function gedcomEndHandler() {
633a6f13a4aSGreg Roach		if ($this->process_gedcoms > 0) {
634a6f13a4aSGreg Roach			$this->process_gedcoms--;
635a6f13a4aSGreg Roach		} else {
636a6f13a4aSGreg Roach			list($this->gedrec, $this->fact, $this->desc) = array_pop($this->gedrec_stack);
637a6f13a4aSGreg Roach		}
638a6f13a4aSGreg Roach	}
639a6f13a4aSGreg Roach
640a6f13a4aSGreg Roach	/**
64176692c8bSGreg Roach	 * XML <textBoxStartHandler>
642a6f13a4aSGreg Roach	 *
643a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
644a6f13a4aSGreg Roach	 */
6458edd1043SGreg Roach	private function textBoxStartHandler($attrs) {
646a6f13a4aSGreg Roach		// string Background color code
647a6f13a4aSGreg Roach		$bgcolor = "";
648a6f13a4aSGreg Roach		if (!empty($attrs['bgcolor'])) {
649a6f13a4aSGreg Roach			$bgcolor = $attrs['bgcolor'];
650a6f13a4aSGreg Roach		}
651a6f13a4aSGreg Roach
652a6f13a4aSGreg Roach		// boolean Wether or not fill the background color
653a6f13a4aSGreg Roach		$fill = true;
654a6f13a4aSGreg Roach		if (isset($attrs['fill'])) {
655a6f13a4aSGreg Roach			if ($attrs['fill'] === "0") {
656a6f13a4aSGreg Roach				$fill = false;
657a6f13a4aSGreg Roach			} elseif ($attrs['fill'] === "1") {
658a6f13a4aSGreg Roach				$fill = true;
659a6f13a4aSGreg Roach			}
660a6f13a4aSGreg Roach		}
661a6f13a4aSGreg Roach
662a6f13a4aSGreg Roach		// var boolean Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0
663a6f13a4aSGreg Roach		$border = false;
664a6f13a4aSGreg Roach		if (isset($attrs['border'])) {
665a6f13a4aSGreg Roach			if ($attrs['border'] === "1") {
666a6f13a4aSGreg Roach				$border = true;
667a6f13a4aSGreg Roach			} elseif ($attrs['border'] === "0") {
668a6f13a4aSGreg Roach				$border = false;
669a6f13a4aSGreg Roach			}
670a6f13a4aSGreg Roach		}
671a6f13a4aSGreg Roach
672a6f13a4aSGreg Roach		// int The starting height of this cell. If the text wraps the height will automatically be adjusted
673a6f13a4aSGreg Roach		$height = 0;
674a6f13a4aSGreg Roach		if (!empty($attrs['height'])) {
675a6f13a4aSGreg Roach			$height = (int) $attrs['height'];
676a6f13a4aSGreg Roach		}
677a6f13a4aSGreg Roach		// int Setting the width to 0 will make it the width from the current location to the margin
678a6f13a4aSGreg Roach		$width = 0;
679a6f13a4aSGreg Roach		if (!empty($attrs['width'])) {
680a6f13a4aSGreg Roach			$width = (int) $attrs['width'];
681a6f13a4aSGreg Roach		}
682a6f13a4aSGreg Roach
683a6f13a4aSGreg Roach		// mixed Position the left corner of this box on the page. The default is the current position.
684a6f13a4aSGreg Roach		$left = ".";
685a6f13a4aSGreg Roach		if (isset($attrs['left'])) {
686a6f13a4aSGreg Roach			if ($attrs['left'] === ".") {
687a6f13a4aSGreg Roach				$left = ".";
688a6f13a4aSGreg Roach			} elseif (!empty($attrs['left'])) {
689a6f13a4aSGreg Roach				$left = (int) $attrs['left'];
690a6f13a4aSGreg Roach			} elseif ($attrs['left'] === "0") {
691a6f13a4aSGreg Roach				$left = 0;
692a6f13a4aSGreg Roach			}
693a6f13a4aSGreg Roach		}
694a6f13a4aSGreg Roach		// mixed Position the top corner of this box on the page. the default is the current position
695a6f13a4aSGreg Roach		$top = ".";
696a6f13a4aSGreg Roach		if (isset($attrs['top'])) {
697a6f13a4aSGreg Roach			if ($attrs['top'] === ".") {
698a6f13a4aSGreg Roach				$top = ".";
699a6f13a4aSGreg Roach			} elseif (!empty($attrs['top'])) {
700a6f13a4aSGreg Roach				$top = (int) $attrs['top'];
701a6f13a4aSGreg Roach			} elseif ($attrs['top'] === "0") {
702a6f13a4aSGreg Roach				$top = 0;
703a6f13a4aSGreg Roach			}
704a6f13a4aSGreg Roach		}
705a6f13a4aSGreg Roach		// boolean After this box is finished rendering, should the next section of text start immediately after the this box or should it start on a new line under this box. 0 = no new line, 1 = force new line. Default is 0
706a6f13a4aSGreg Roach		$newline = false;
707a6f13a4aSGreg Roach		if (isset($attrs['newline'])) {
708a6f13a4aSGreg Roach			if ($attrs['newline'] === "1") {
709a6f13a4aSGreg Roach				$newline = true;
710a6f13a4aSGreg Roach			} elseif ($attrs['newline'] === "0") {
711a6f13a4aSGreg Roach				$newline = false;
712a6f13a4aSGreg Roach			}
713a6f13a4aSGreg Roach		}
714a6f13a4aSGreg Roach		// boolean
715a6f13a4aSGreg Roach		$pagecheck = true;
716a6f13a4aSGreg Roach		if (isset($attrs['pagecheck'])) {
717a6f13a4aSGreg Roach			if ($attrs['pagecheck'] === "0") {
718a6f13a4aSGreg Roach				$pagecheck = false;
719a6f13a4aSGreg Roach			} elseif ($attrs['pagecheck'] === "1") {
720a6f13a4aSGreg Roach				$pagecheck = true;
721a6f13a4aSGreg Roach			}
722a6f13a4aSGreg Roach		}
723a6f13a4aSGreg Roach		// boolean Cell padding
724a6f13a4aSGreg Roach		$padding = true;
725a6f13a4aSGreg Roach		if (isset($attrs['padding'])) {
726a6f13a4aSGreg Roach			if ($attrs['padding'] === "0") {
727a6f13a4aSGreg Roach				$padding = false;
728a6f13a4aSGreg Roach			} elseif ($attrs['padding'] === "1") {
729a6f13a4aSGreg Roach				$padding = true;
730a6f13a4aSGreg Roach			}
731a6f13a4aSGreg Roach		}
732a6f13a4aSGreg Roach		// boolean Reset this box Height
733a6f13a4aSGreg Roach		$reseth = false;
734a6f13a4aSGreg Roach		if (isset($attrs['reseth'])) {
735a6f13a4aSGreg Roach			if ($attrs['reseth'] === "1") {
736a6f13a4aSGreg Roach				$reseth = true;
737a6f13a4aSGreg Roach			} elseif ($attrs['reseth'] === "0") {
738a6f13a4aSGreg Roach				$reseth = false;
739a6f13a4aSGreg Roach			}
740a6f13a4aSGreg Roach		}
741a6f13a4aSGreg Roach
742a6f13a4aSGreg Roach		// string Style of rendering
743a6f13a4aSGreg Roach		$style = "";
744a6f13a4aSGreg Roach
745a6f13a4aSGreg Roach		array_push($this->print_data_stack, $this->print_data);
746a6f13a4aSGreg Roach		$this->print_data = false;
747a6f13a4aSGreg Roach
748e8e7866bSGreg Roach		array_push($this->wt_report_stack, $this->wt_report);
749e8e7866bSGreg Roach		$this->wt_report = $this->report_root->createTextBox(
750a6f13a4aSGreg Roach			$width,
751a6f13a4aSGreg Roach			$height,
752a6f13a4aSGreg Roach			$border,
753a6f13a4aSGreg Roach			$bgcolor,
754a6f13a4aSGreg Roach			$newline,
755a6f13a4aSGreg Roach			$left,
756a6f13a4aSGreg Roach			$top,
757a6f13a4aSGreg Roach			$pagecheck,
758a6f13a4aSGreg Roach			$style,
759a6f13a4aSGreg Roach			$fill,
760a6f13a4aSGreg Roach			$padding,
761a6f13a4aSGreg Roach			$reseth
762a6f13a4aSGreg Roach		);
763a6f13a4aSGreg Roach	}
764a6f13a4aSGreg Roach
765a6f13a4aSGreg Roach	/**
76676692c8bSGreg Roach	 * XML <textBoxEndHandler>
767a6f13a4aSGreg Roach	 */
7688edd1043SGreg Roach	private function textBoxEndHandler() {
769a6f13a4aSGreg Roach		$this->print_data      = array_pop($this->print_data_stack);
770e8e7866bSGreg Roach		$this->current_element = $this->wt_report;
771e8e7866bSGreg Roach		$this->wt_report       = array_pop($this->wt_report_stack);
772e8e7866bSGreg Roach		$this->wt_report->addElement($this->current_element);
773a6f13a4aSGreg Roach	}
774a6f13a4aSGreg Roach
775a6f13a4aSGreg Roach	/**
77676692c8bSGreg Roach	 * XLM <Text>.
77776692c8bSGreg Roach	 *
778a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
779a6f13a4aSGreg Roach	 */
7808edd1043SGreg Roach	private function textStartHandler($attrs) {
781a6f13a4aSGreg Roach		array_push($this->print_data_stack, $this->print_data);
782a6f13a4aSGreg Roach		$this->print_data = true;
783a6f13a4aSGreg Roach
784a6f13a4aSGreg Roach		// string The name of the Style that should be used to render the text.
785a6f13a4aSGreg Roach		$style = "";
786a6f13a4aSGreg Roach		if (!empty($attrs['style'])) {
787a6f13a4aSGreg Roach			$style = $attrs['style'];
788a6f13a4aSGreg Roach		}
789a6f13a4aSGreg Roach
790a6f13a4aSGreg Roach		// string  The color of the text - Keep the black color as default
791a6f13a4aSGreg Roach		$color = "";
792a6f13a4aSGreg Roach		if (!empty($attrs['color'])) {
793a6f13a4aSGreg Roach			$color = $attrs['color'];
794a6f13a4aSGreg Roach		}
795a6f13a4aSGreg Roach
796e8e7866bSGreg Roach		$this->current_element = $this->report_root->createText($style, $color);
797a6f13a4aSGreg Roach	}
798a6f13a4aSGreg Roach
799a6f13a4aSGreg Roach	/**
80076692c8bSGreg Roach	 * XML </Text>
801a6f13a4aSGreg Roach	 */
8028edd1043SGreg Roach	private function textEndHandler() {
803a6f13a4aSGreg Roach		$this->print_data = array_pop($this->print_data_stack);
804e8e7866bSGreg Roach		$this->wt_report->addElement($this->current_element);
805a6f13a4aSGreg Roach	}
806a6f13a4aSGreg Roach
807a6f13a4aSGreg Roach	/**
80876692c8bSGreg Roach	 * XML <GetPersonName/>
80976692c8bSGreg Roach	 *
810a6f13a4aSGreg Roach	 * Get the name
811a6f13a4aSGreg Roach	 * 1. id is empty - current GEDCOM record
812a6f13a4aSGreg Roach	 * 2. id is set with a record id
813a6f13a4aSGreg Roach	 *
814a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
815a6f13a4aSGreg Roach	 */
8168edd1043SGreg Roach	private function getPersonNameStartHandler($attrs) {
817d1286247SGreg Roach		global $WT_TREE;
818a6f13a4aSGreg Roach
819a6f13a4aSGreg Roach		$id    = "";
820a6f13a4aSGreg Roach		$match = array();
821a6f13a4aSGreg Roach		if (empty($attrs['id'])) {
822a6f13a4aSGreg Roach			if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
823a6f13a4aSGreg Roach				$id = $match[1];
824a6f13a4aSGreg Roach			}
825a6f13a4aSGreg Roach		} else {
826a6f13a4aSGreg Roach			if (preg_match("/\\$(.+)/", $attrs['id'], $match)) {
827d1286247SGreg Roach				if (isset($this->vars[$match[1]]['id'])) {
828d1286247SGreg Roach					$id = $this->vars[$match[1]]['id'];
829a6f13a4aSGreg Roach				}
830a6f13a4aSGreg Roach			} else {
831a6f13a4aSGreg Roach				if (preg_match("/@(.+)/", $attrs['id'], $match)) {
832a6f13a4aSGreg Roach					$gmatch = array();
833a6f13a4aSGreg Roach					if (preg_match("/\d $match[1] @([^@]+)@/", $this->gedrec, $gmatch)) {
834a6f13a4aSGreg Roach						$id = $gmatch[1];
835a6f13a4aSGreg Roach					}
836a6f13a4aSGreg Roach				} else {
837a6f13a4aSGreg Roach					$id = $attrs['id'];
838a6f13a4aSGreg Roach				}
839a6f13a4aSGreg Roach			}
840a6f13a4aSGreg Roach		}
841a6f13a4aSGreg Roach		if (!empty($id)) {
842a6f13a4aSGreg Roach			$record = GedcomRecord::getInstance($id, $WT_TREE);
843a6f13a4aSGreg Roach			if (is_null($record)) {
844a6f13a4aSGreg Roach				return;
845a6f13a4aSGreg Roach			}
846a6f13a4aSGreg Roach			if (!$record->canShowName()) {
847a6f13a4aSGreg Roach				$this->current_element->addText(I18N::translate('Private'));
848a6f13a4aSGreg Roach			} else {
849a6f13a4aSGreg Roach				$name = $record->getFullName();
850a6f13a4aSGreg Roach				$name = preg_replace(
851a6f13a4aSGreg Roach					array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'),
852a6f13a4aSGreg Roach					array('«', '', '»'),
853a6f13a4aSGreg Roach					$name
854a6f13a4aSGreg Roach				);
855a6f13a4aSGreg Roach				$name = strip_tags($name);
856a6f13a4aSGreg Roach				if (!empty($attrs['truncate'])) {
857a6f13a4aSGreg Roach					if (mb_strlen($name) > $attrs['truncate']) {
858a6f13a4aSGreg Roach						$name  = preg_replace("/\(.*\) ?/", '', $name); //removes () and text inbetween - what about ", [ and { etc?
859a6f13a4aSGreg Roach						$words = preg_split('/[, -]+/', $name); // names separated with space, comma or hyphen - any others?
860a6f13a4aSGreg Roach						$name  = $words[count($words) - 1];
861a6f13a4aSGreg Roach						for ($i = count($words) - 2; $i >= 0; $i--) {
862a6f13a4aSGreg Roach							$len = mb_strlen($name);
863a6f13a4aSGreg Roach							for ($j = count($words) - 3; $j >= 0; $j--) {
864a6f13a4aSGreg Roach								$len += mb_strlen($words[$j]);
865a6f13a4aSGreg Roach							}
866a6f13a4aSGreg Roach							if ($len > $attrs['truncate']) {
867a6f13a4aSGreg Roach								$first_letter = mb_substr($words[$i], 0, 1);
868a6f13a4aSGreg Roach								// Do not show " of nick-names
869a6f13a4aSGreg Roach								if ($first_letter != "\"") {
870a6f13a4aSGreg Roach									$name = mb_substr($words[$i], 0, 1) . '. ' . $name;
871a6f13a4aSGreg Roach								}
872a6f13a4aSGreg Roach							} else {
873a6f13a4aSGreg Roach								$name = $words[$i] . ' ' . $name;
874a6f13a4aSGreg Roach							}
875a6f13a4aSGreg Roach						}
876a6f13a4aSGreg Roach					}
877a6f13a4aSGreg Roach				} else {
878a6f13a4aSGreg Roach					$addname = $record->getAddName();
879a6f13a4aSGreg Roach					$addname = preg_replace(
880a6f13a4aSGreg Roach						array('/<span class="starredname">/', '/<\/span><\/span>/', '/<\/span>/'),
881a6f13a4aSGreg Roach						array('«', '', '»'),
882a6f13a4aSGreg Roach						$addname
883a6f13a4aSGreg Roach					);
884a6f13a4aSGreg Roach					$addname = strip_tags($addname);
885a6f13a4aSGreg Roach					if (!empty($addname)) {
886a6f13a4aSGreg Roach						$name .= " " . $addname;
887a6f13a4aSGreg Roach					}
888a6f13a4aSGreg Roach				}
889a6f13a4aSGreg Roach				$this->current_element->addText(trim($name));
890a6f13a4aSGreg Roach			}
891a6f13a4aSGreg Roach		}
892a6f13a4aSGreg Roach	}
893a6f13a4aSGreg Roach
894a6f13a4aSGreg Roach	/**
89576692c8bSGreg Roach	 * XML <GedcomValue/>
896a6f13a4aSGreg Roach	 *
897a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
898a6f13a4aSGreg Roach	 */
8998edd1043SGreg Roach	private function gedcomValueStartHandler($attrs) {
900a6f13a4aSGreg Roach		global $WT_TREE;
901a6f13a4aSGreg Roach
902a6f13a4aSGreg Roach		$id    = "";
903a6f13a4aSGreg Roach		$match = array();
904a6f13a4aSGreg Roach		if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
905a6f13a4aSGreg Roach			$id = $match[1];
906a6f13a4aSGreg Roach		}
907a6f13a4aSGreg Roach
908a6f13a4aSGreg Roach		if (isset($attrs['newline']) && $attrs['newline'] == "1") {
909a6f13a4aSGreg Roach			$useBreak = "1";
910a6f13a4aSGreg Roach		} else {
911a6f13a4aSGreg Roach			$useBreak = "0";
912a6f13a4aSGreg Roach		}
913a6f13a4aSGreg Roach
914a6f13a4aSGreg Roach		$tag = $attrs['tag'];
915a6f13a4aSGreg Roach		if (!empty($tag)) {
916a6f13a4aSGreg Roach			if ($tag == "@desc") {
917a6f13a4aSGreg Roach				$value = $this->desc;
918a6f13a4aSGreg Roach				$value = trim($value);
919a6f13a4aSGreg Roach				$this->current_element->addText($value);
920a6f13a4aSGreg Roach			}
921a6f13a4aSGreg Roach			if ($tag == "@id") {
922a6f13a4aSGreg Roach				$this->current_element->addText($id);
923a6f13a4aSGreg Roach			} else {
924a6f13a4aSGreg Roach				$tag = str_replace("@fact", $this->fact, $tag);
925a6f13a4aSGreg Roach				if (empty($attrs['level'])) {
926a6f13a4aSGreg Roach					$temp  = explode(" ", trim($this->gedrec));
927a6f13a4aSGreg Roach					$level = $temp[0];
928a6f13a4aSGreg Roach					if ($level == 0) {
929a6f13a4aSGreg Roach						$level++;
930a6f13a4aSGreg Roach					}
931a6f13a4aSGreg Roach				} else {
932a6f13a4aSGreg Roach					$level = $attrs['level'];
933a6f13a4aSGreg Roach				}
934a6f13a4aSGreg Roach				$tags  = preg_split('/[: ]/', $tag);
9353d7a8a4cSGreg Roach				$value = $this->getGedcomValue($tag, $level, $this->gedrec);
936a6f13a4aSGreg Roach				switch (end($tags)) {
937a6f13a4aSGreg Roach					case 'DATE':
938a6f13a4aSGreg Roach						$tmp   = new Date($value);
939a6f13a4aSGreg Roach						$value = $tmp->display();
940a6f13a4aSGreg Roach						break;
941a6f13a4aSGreg Roach					case 'PLAC':
942a6f13a4aSGreg Roach						$tmp   = new Place($value, $WT_TREE);
943a6f13a4aSGreg Roach						$value = $tmp->getShortName();
944a6f13a4aSGreg Roach						break;
945a6f13a4aSGreg Roach				}
946a6f13a4aSGreg Roach				if ($useBreak == "1") {
947a6f13a4aSGreg Roach					// Insert <br> when multiple dates exist.
948a6f13a4aSGreg Roach					// This works around a TCPDF bug that incorrectly wraps RTL dates on LTR pages
949a6f13a4aSGreg Roach					$value = str_replace('(', '<br>(', $value);
950a6f13a4aSGreg Roach					$value = str_replace('<span dir="ltr"><br>', '<br><span dir="ltr">', $value);
951a6f13a4aSGreg Roach					$value = str_replace('<span dir="rtl"><br>', '<br><span dir="rtl">', $value);
952a6f13a4aSGreg Roach					if (substr($value, 0, 6) == '<br>') {
953a6f13a4aSGreg Roach						$value = substr($value, 6);
954a6f13a4aSGreg Roach					}
955a6f13a4aSGreg Roach				}
956d4d660b7SGreg Roach				$tmp = explode(':', $tag);
957d4d660b7SGreg Roach				if (in_array(end($tmp),  array('NOTE', 'TEXT'))) {
958a4d703aeSGreg Roach					$value = Filter::formatText($value, $WT_TREE); // We'll strip HTML in addText()
959a4d703aeSGreg Roach				}
960a6f13a4aSGreg Roach				$this->current_element->addText($value);
961a6f13a4aSGreg Roach			}
962a6f13a4aSGreg Roach		}
963a6f13a4aSGreg Roach	}
964a6f13a4aSGreg Roach
965a6f13a4aSGreg Roach	/**
96676692c8bSGreg Roach	 * XML <RepeatTag>
967a6f13a4aSGreg Roach	 *
968a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
969a6f13a4aSGreg Roach	 */
9708edd1043SGreg Roach	private function repeatTagStartHandler($attrs) {
971a6f13a4aSGreg Roach		$this->process_repeats++;
972a6f13a4aSGreg Roach		if ($this->process_repeats > 1) {
973a6f13a4aSGreg Roach			return;
974a6f13a4aSGreg Roach		}
975a6f13a4aSGreg Roach
976a6f13a4aSGreg Roach		array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
977a6f13a4aSGreg Roach		$this->repeats      = array();
978e8e7866bSGreg Roach		$this->repeat_bytes = xml_get_current_line_number($this->parser);
979a6f13a4aSGreg Roach
980a6f13a4aSGreg Roach		$tag = "";
981a6f13a4aSGreg Roach		if (isset($attrs['tag'])) {
982a6f13a4aSGreg Roach			$tag = $attrs['tag'];
983a6f13a4aSGreg Roach		}
984a6f13a4aSGreg Roach		if (!empty($tag)) {
985a6f13a4aSGreg Roach			if ($tag == "@desc") {
986a6f13a4aSGreg Roach				$value = $this->desc;
987a6f13a4aSGreg Roach				$value = trim($value);
988a6f13a4aSGreg Roach				$this->current_element->addText($value);
989a6f13a4aSGreg Roach			} else {
990a6f13a4aSGreg Roach				$tag   = str_replace("@fact", $this->fact, $tag);
991a6f13a4aSGreg Roach				$tags  = explode(":", $tag);
992a6f13a4aSGreg Roach				$temp  = explode(" ", trim($this->gedrec));
993a6f13a4aSGreg Roach				$level = $temp[0];
994a6f13a4aSGreg Roach				if ($level == 0) {
995a6f13a4aSGreg Roach					$level++;
996a6f13a4aSGreg Roach				}
997a6f13a4aSGreg Roach				$subrec = $this->gedrec;
998a6f13a4aSGreg Roach				$t      = $tag;
999a6f13a4aSGreg Roach				$count  = count($tags);
1000a6f13a4aSGreg Roach				$i      = 0;
1001a6f13a4aSGreg Roach				while ($i < $count) {
1002a6f13a4aSGreg Roach					$t = $tags[$i];
1003a6f13a4aSGreg Roach					if (!empty($t)) {
1004a6f13a4aSGreg Roach						if ($i < ($count - 1)) {
10053d7a8a4cSGreg Roach							$subrec = Functions::getSubRecord($level, "$level $t", $subrec);
1006a6f13a4aSGreg Roach							if (empty($subrec)) {
1007a6f13a4aSGreg Roach								$level--;
10083d7a8a4cSGreg Roach								$subrec = Functions::getSubRecord($level, "@ $t", $this->gedrec);
1009a6f13a4aSGreg Roach								if (empty($subrec)) {
1010a6f13a4aSGreg Roach									return;
1011a6f13a4aSGreg Roach								}
1012a6f13a4aSGreg Roach							}
1013a6f13a4aSGreg Roach						}
1014a6f13a4aSGreg Roach						$level++;
1015a6f13a4aSGreg Roach					}
1016a6f13a4aSGreg Roach					$i++;
1017a6f13a4aSGreg Roach				}
1018a6f13a4aSGreg Roach				$level--;
1019a6f13a4aSGreg Roach				$count = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER);
1020a6f13a4aSGreg Roach				$i     = 0;
1021a6f13a4aSGreg Roach				while ($i < $count) {
10223d7a8a4cSGreg Roach					$this->repeats[] = Functions::getSubRecord($level, "$level $t", $subrec, $i + 1);
1023a6f13a4aSGreg Roach					$i++;
1024a6f13a4aSGreg Roach				}
1025a6f13a4aSGreg Roach			}
1026a6f13a4aSGreg Roach		}
1027a6f13a4aSGreg Roach	}
1028a6f13a4aSGreg Roach
1029a6f13a4aSGreg Roach	/**
103076692c8bSGreg Roach	 * XML </ RepeatTag>
1031a6f13a4aSGreg Roach	 */
10328edd1043SGreg Roach	private function repeatTagEndHandler() {
1033e8e7866bSGreg Roach		global $report;
1034a6f13a4aSGreg Roach
1035a6f13a4aSGreg Roach		$this->process_repeats--;
1036a6f13a4aSGreg Roach		if ($this->process_repeats > 0) {
1037a6f13a4aSGreg Roach			return;
1038a6f13a4aSGreg Roach		}
1039a6f13a4aSGreg Roach
1040a6f13a4aSGreg Roach		// Check if there is anything to repeat
1041a6f13a4aSGreg Roach		if (count($this->repeats) > 0) {
1042a6f13a4aSGreg Roach			// No need to load them if not used...
1043a6f13a4aSGreg Roach
1044a6f13a4aSGreg Roach			$lineoffset = 0;
1045a6f13a4aSGreg Roach			foreach ($this->repeats_stack as $rep) {
1046a6f13a4aSGreg Roach				$lineoffset += $rep[1];
1047a6f13a4aSGreg Roach			}
1048a6f13a4aSGreg Roach			//-- read the xml from the file
1049a6f13a4aSGreg Roach			$lines = file($report);
1050a6f13a4aSGreg Roach			while (strpos($lines[$lineoffset + $this->repeat_bytes], "<RepeatTag") === false) {
1051a6f13a4aSGreg Roach				$lineoffset--;
1052a6f13a4aSGreg Roach			}
1053a6f13a4aSGreg Roach			$lineoffset++;
1054a6f13a4aSGreg Roach			$reportxml = "<tempdoc>\n";
1055a6f13a4aSGreg Roach			$line_nr   = $lineoffset + $this->repeat_bytes;
1056a6f13a4aSGreg Roach			// RepeatTag Level counter
1057a6f13a4aSGreg Roach			$count = 1;
1058a6f13a4aSGreg Roach			while (0 < $count) {
1059a6f13a4aSGreg Roach				if (strstr($lines[$line_nr], "<RepeatTag") !== false) {
1060a6f13a4aSGreg Roach					$count++;
1061a6f13a4aSGreg Roach				} elseif (strstr($lines[$line_nr], "</RepeatTag") !== false) {
1062a6f13a4aSGreg Roach					$count--;
1063a6f13a4aSGreg Roach				}
1064a6f13a4aSGreg Roach				if (0 < $count) {
1065a6f13a4aSGreg Roach					$reportxml .= $lines[$line_nr];
1066a6f13a4aSGreg Roach				}
1067a6f13a4aSGreg Roach				$line_nr++;
1068a6f13a4aSGreg Roach			}
1069a6f13a4aSGreg Roach			// No need to drag this
1070a6f13a4aSGreg Roach			unset($lines);
1071a6f13a4aSGreg Roach			$reportxml .= "</tempdoc>\n";
1072a6f13a4aSGreg Roach			// Save original values
1073e8e7866bSGreg Roach			array_push($this->parser_stack, $this->parser);
1074a6f13a4aSGreg Roach			$oldgedrec = $this->gedrec;
1075a6f13a4aSGreg Roach			foreach ($this->repeats as $gedrec) {
1076a6f13a4aSGreg Roach				$this->gedrec  = $gedrec;
1077a6f13a4aSGreg Roach				$repeat_parser = xml_parser_create();
1078e8e7866bSGreg Roach				$this->parser  = $repeat_parser;
1079a6f13a4aSGreg Roach				xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
1080a6f13a4aSGreg Roach				xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
1081a6f13a4aSGreg Roach				xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
1082a6f13a4aSGreg Roach				if (!xml_parse($repeat_parser, $reportxml, true)) {
1083a6f13a4aSGreg Roach					throw new \DomainException(sprintf(
1084a6f13a4aSGreg Roach						'RepeatTagEHandler XML error: %s at line %d',
1085a6f13a4aSGreg Roach						xml_error_string(xml_get_error_code($repeat_parser)),
1086a6f13a4aSGreg Roach						xml_get_current_line_number($repeat_parser)
1087a6f13a4aSGreg Roach					));
1088a6f13a4aSGreg Roach				}
1089a6f13a4aSGreg Roach				xml_parser_free($repeat_parser);
1090a6f13a4aSGreg Roach			}
1091a6f13a4aSGreg Roach			// Restore original values
1092a6f13a4aSGreg Roach			$this->gedrec = $oldgedrec;
1093e8e7866bSGreg Roach			$this->parser = array_pop($this->parser_stack);
1094a6f13a4aSGreg Roach		}
1095a6f13a4aSGreg Roach		list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
1096a6f13a4aSGreg Roach	}
1097a6f13a4aSGreg Roach
1098a6f13a4aSGreg Roach	/**
1099a6f13a4aSGreg Roach	 * Variable lookup
1100a6f13a4aSGreg Roach	 *
1101a6f13a4aSGreg Roach	 * Retrieve predefined variables :
1102a6f13a4aSGreg Roach	 *
1103a6f13a4aSGreg Roach	 * @ desc GEDCOM fact description, example:
1104a6f13a4aSGreg Roach	 *        1 EVEN This is a description
1105a6f13a4aSGreg Roach	 * @ fact GEDCOM fact tag, such as BIRT, DEAT etc.
1106a6f13a4aSGreg Roach	 * $ I18N::translate('....')
1107a6f13a4aSGreg Roach	 * $ language_settings[]
1108a6f13a4aSGreg Roach	 *
1109a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1110a6f13a4aSGreg Roach	 */
11118edd1043SGreg Roach	private function varStartHandler($attrs) {
1112a6f13a4aSGreg Roach		if (empty($attrs['var'])) {
1113e8e7866bSGreg Roach			throw new \DomainException('REPORT ERROR var: The attribute "var=" is missing or not set in the XML file on line: ' . xml_get_current_line_number($this->parser));
1114a6f13a4aSGreg Roach		}
1115a6f13a4aSGreg Roach
1116a6f13a4aSGreg Roach		$var = $attrs['var'];
1117a6f13a4aSGreg Roach		// SetVar element preset variables
1118d1286247SGreg Roach		if (!empty($this->vars[$var]['id'])) {
1119d1286247SGreg Roach			$var = $this->vars[$var]['id'];
1120a6f13a4aSGreg Roach		} else {
1121a6f13a4aSGreg Roach			$tfact = $this->fact;
1122a6f13a4aSGreg Roach			if (($this->fact === "EVEN" || $this->fact === "FACT") && $this->type !== " ") {
1123a6f13a4aSGreg Roach				// Use :
1124a6f13a4aSGreg Roach				// n TYPE This text if string
1125a6f13a4aSGreg Roach				$tfact = $this->type;
1126a6f13a4aSGreg Roach			}
1127a6f13a4aSGreg Roach			$var = str_replace(array("@fact", "@desc"), array(GedcomTag::getLabel($tfact), $this->desc), $var);
1128a6f13a4aSGreg Roach			if (preg_match('/^I18N::number\((.+)\)$/', $var, $match)) {
1129a6f13a4aSGreg Roach				$var = I18N::number($match[1]);
1130a6f13a4aSGreg Roach			} elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $var, $match)) {
1131a6f13a4aSGreg Roach				$var = I18N::translate($match[1]);
1132a4956c0eSGreg Roach			} elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $var, $match)) {
1133a6f13a4aSGreg Roach				$var = I18N::translateContext($match[1], $match[2]);
1134a6f13a4aSGreg Roach			}
1135a6f13a4aSGreg Roach		}
1136a6f13a4aSGreg Roach		// Check if variable is set as a date and reformat the date
1137a6f13a4aSGreg Roach		if (isset($attrs['date'])) {
1138a6f13a4aSGreg Roach			if ($attrs['date'] === "1") {
1139a6f13a4aSGreg Roach				$g   = new Date($var);
1140a6f13a4aSGreg Roach				$var = $g->display();
1141a6f13a4aSGreg Roach			}
1142a6f13a4aSGreg Roach		}
1143a6f13a4aSGreg Roach		$this->current_element->addText($var);
11442836aa05SGreg Roach		$this->text = $var; // Used for title/descriptio
1145a6f13a4aSGreg Roach	}
1146a6f13a4aSGreg Roach
1147a6f13a4aSGreg Roach	/**
114876692c8bSGreg Roach	 * XML <Facts>
114976692c8bSGreg Roach	 *
1150a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1151a6f13a4aSGreg Roach	 */
11528edd1043SGreg Roach	private function factsStartHandler($attrs) {
1153d1286247SGreg Roach		global $WT_TREE;
1154a6f13a4aSGreg Roach
1155a6f13a4aSGreg Roach		$this->process_repeats++;
1156a6f13a4aSGreg Roach		if ($this->process_repeats > 1) {
1157a6f13a4aSGreg Roach			return;
1158a6f13a4aSGreg Roach		}
1159a6f13a4aSGreg Roach
1160a6f13a4aSGreg Roach		array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
1161a6f13a4aSGreg Roach		$this->repeats      = array();
1162e8e7866bSGreg Roach		$this->repeat_bytes = xml_get_current_line_number($this->parser);
1163a6f13a4aSGreg Roach
1164a6f13a4aSGreg Roach		$id    = "";
1165a6f13a4aSGreg Roach		$match = array();
1166a6f13a4aSGreg Roach		if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1167a6f13a4aSGreg Roach			$id = $match[1];
1168a6f13a4aSGreg Roach		}
1169a6f13a4aSGreg Roach		$tag = "";
1170a6f13a4aSGreg Roach		if (isset($attrs['ignore'])) {
1171a6f13a4aSGreg Roach			$tag .= $attrs['ignore'];
1172a6f13a4aSGreg Roach		}
1173a6f13a4aSGreg Roach		if (preg_match("/\\$(.+)/", $tag, $match)) {
1174d1286247SGreg Roach			$tag = $this->vars[$match[1]]['id'];
1175a6f13a4aSGreg Roach		}
1176a6f13a4aSGreg Roach
1177a6f13a4aSGreg Roach		$record = GedcomRecord::getInstance($id, $WT_TREE);
1178a6f13a4aSGreg Roach		if (empty($attrs['diff']) && !empty($id)) {
1179a6f13a4aSGreg Roach			$facts = $record->getFacts();
11803d7a8a4cSGreg Roach			Functions::sortFacts($facts);
1181a6f13a4aSGreg Roach			$this->repeats  = array();
1182a6f13a4aSGreg Roach			$nonfacts       = explode(',', $tag);
1183a6f13a4aSGreg Roach			foreach ($facts as $event) {
1184a6f13a4aSGreg Roach				if (!in_array($event->getTag(), $nonfacts)) {
1185a6f13a4aSGreg Roach					$this->repeats[] = $event->getGedcom();
1186a6f13a4aSGreg Roach				}
1187a6f13a4aSGreg Roach			}
1188a6f13a4aSGreg Roach		} else {
1189a6f13a4aSGreg Roach			foreach ($record->getFacts() as $fact) {
1190a6f13a4aSGreg Roach				if ($fact->isPendingAddition() && $fact->getTag() !== 'CHAN') {
1191a6f13a4aSGreg Roach					$this->repeats[] = $fact->getGedcom();
1192a6f13a4aSGreg Roach				}
1193a6f13a4aSGreg Roach			}
1194a6f13a4aSGreg Roach		}
1195a6f13a4aSGreg Roach	}
1196a6f13a4aSGreg Roach
1197a6f13a4aSGreg Roach	/**
119876692c8bSGreg Roach	 * XML </Facts>
1199a6f13a4aSGreg Roach	 */
12008edd1043SGreg Roach	private function factsEndHandler() {
1201e8e7866bSGreg Roach		global $report;
1202a6f13a4aSGreg Roach
1203a6f13a4aSGreg Roach		$this->process_repeats--;
1204a6f13a4aSGreg Roach		if ($this->process_repeats > 0) {
1205a6f13a4aSGreg Roach			return;
1206a6f13a4aSGreg Roach		}
1207a6f13a4aSGreg Roach
1208a6f13a4aSGreg Roach		// Check if there is anything to repeat
1209a6f13a4aSGreg Roach		if (count($this->repeats) > 0) {
1210a6f13a4aSGreg Roach
1211e8e7866bSGreg Roach			$line       = xml_get_current_line_number($this->parser) - 1;
1212a6f13a4aSGreg Roach			$lineoffset = 0;
1213a6f13a4aSGreg Roach			foreach ($this->repeats_stack as $rep) {
1214a6f13a4aSGreg Roach				$lineoffset += $rep[1];
1215a6f13a4aSGreg Roach			}
1216a6f13a4aSGreg Roach
1217a6f13a4aSGreg Roach			//-- read the xml from the file
1218a6f13a4aSGreg Roach			$lines = file($report);
1219a6f13a4aSGreg Roach			while ($lineoffset + $this->repeat_bytes > 0 && strpos($lines[$lineoffset + $this->repeat_bytes], '<Facts ') === false) {
1220a6f13a4aSGreg Roach				$lineoffset--;
1221a6f13a4aSGreg Roach			}
1222a6f13a4aSGreg Roach			$lineoffset++;
1223a6f13a4aSGreg Roach			$reportxml = "<tempdoc>\n";
1224a6f13a4aSGreg Roach			$i         = $line + $lineoffset;
1225a6f13a4aSGreg Roach			$line_nr   = $this->repeat_bytes + $lineoffset;
1226a6f13a4aSGreg Roach			while ($line_nr < $i) {
1227a6f13a4aSGreg Roach				$reportxml .= $lines[$line_nr];
1228a6f13a4aSGreg Roach				$line_nr++;
1229a6f13a4aSGreg Roach			}
1230a6f13a4aSGreg Roach			// No need to drag this
1231a6f13a4aSGreg Roach			unset($lines);
1232a6f13a4aSGreg Roach			$reportxml .= "</tempdoc>\n";
1233a6f13a4aSGreg Roach			// Save original values
1234e8e7866bSGreg Roach			array_push($this->parser_stack, $this->parser);
1235a6f13a4aSGreg Roach			$oldgedrec = $this->gedrec;
1236a6f13a4aSGreg Roach			$count     = count($this->repeats);
1237a6f13a4aSGreg Roach			$i         = 0;
1238a6f13a4aSGreg Roach			while ($i < $count) {
1239a6f13a4aSGreg Roach				$this->gedrec = $this->repeats[$i];
1240a6f13a4aSGreg Roach				$this->fact   = '';
1241a6f13a4aSGreg Roach				$this->desc   = '';
1242a6f13a4aSGreg Roach				if (preg_match('/1 (\w+)(.*)/', $this->gedrec, $match)) {
1243a6f13a4aSGreg Roach					$this->fact = $match[1];
1244a6f13a4aSGreg Roach					if ($this->fact === 'EVEN' || $this->fact === 'FACT') {
1245a6f13a4aSGreg Roach						$tmatch = array();
1246a6f13a4aSGreg Roach						if (preg_match('/2 TYPE (.+)/', $this->gedrec, $tmatch)) {
1247a6f13a4aSGreg Roach							$this->type = trim($tmatch[1]);
1248a6f13a4aSGreg Roach						} else {
1249a6f13a4aSGreg Roach							$this->type = ' ';
1250a6f13a4aSGreg Roach						}
1251a6f13a4aSGreg Roach					}
1252a6f13a4aSGreg Roach					$this->desc = trim($match[2]);
12533d7a8a4cSGreg Roach					$this->desc .= Functions::getCont(2, $this->gedrec);
1254a6f13a4aSGreg Roach				}
1255a6f13a4aSGreg Roach				$repeat_parser = xml_parser_create();
1256e8e7866bSGreg Roach				$this->parser  = $repeat_parser;
1257a6f13a4aSGreg Roach				xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
1258a6f13a4aSGreg Roach				xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
1259a6f13a4aSGreg Roach				xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
1260a6f13a4aSGreg Roach				if (!xml_parse($repeat_parser, $reportxml, true)) {
1261a6f13a4aSGreg Roach					throw new \DomainException(sprintf(
1262a6f13a4aSGreg Roach						'FactsEHandler XML error: %s at line %d',
1263a6f13a4aSGreg Roach						xml_error_string(xml_get_error_code($repeat_parser)),
1264a6f13a4aSGreg Roach						xml_get_current_line_number($repeat_parser)
1265a6f13a4aSGreg Roach					));
1266a6f13a4aSGreg Roach				}
1267a6f13a4aSGreg Roach				xml_parser_free($repeat_parser);
1268a6f13a4aSGreg Roach				$i++;
1269a6f13a4aSGreg Roach			}
1270a6f13a4aSGreg Roach			// Restore original values
1271e8e7866bSGreg Roach			$this->parser = array_pop($this->parser_stack);
1272a6f13a4aSGreg Roach			$this->gedrec = $oldgedrec;
1273a6f13a4aSGreg Roach		}
1274a6f13a4aSGreg Roach		list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
1275a6f13a4aSGreg Roach	}
1276a6f13a4aSGreg Roach
1277a6f13a4aSGreg Roach	/**
1278a6f13a4aSGreg Roach	 * Setting upp or changing variables in the XML
1279d1286247SGreg Roach	 * The XML variable name and value is stored in $this->vars
1280a6f13a4aSGreg Roach	 *
1281a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1282a6f13a4aSGreg Roach	 */
12838edd1043SGreg Roach	private function setVarStartHandler($attrs) {
1284a6f13a4aSGreg Roach		if (empty($attrs['name'])) {
1285a6f13a4aSGreg Roach			throw new \DomainException('REPORT ERROR var: The attribute "name" is missing or not set in the XML file');
1286a6f13a4aSGreg Roach		}
1287a6f13a4aSGreg Roach
1288a6f13a4aSGreg Roach		$name  = $attrs['name'];
1289a6f13a4aSGreg Roach		$value = $attrs['value'];
1290a6f13a4aSGreg Roach		$match = array();
1291a6f13a4aSGreg Roach		// Current GEDCOM record strings
1292a6f13a4aSGreg Roach		if ($value == "@ID") {
1293a6f13a4aSGreg Roach			if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1294a6f13a4aSGreg Roach				$value = $match[1];
1295a6f13a4aSGreg Roach			}
1296a6f13a4aSGreg Roach		} elseif ($value == "@fact") {
1297a6f13a4aSGreg Roach			$value = $this->fact;
1298a6f13a4aSGreg Roach		} elseif ($value == "@desc") {
1299a6f13a4aSGreg Roach			$value = $this->desc;
1300a6f13a4aSGreg Roach		} elseif ($value == "@generation") {
1301a6f13a4aSGreg Roach			$value = $this->generation;
1302a6f13a4aSGreg Roach		} elseif (preg_match("/@(\w+)/", $value, $match)) {
1303a6f13a4aSGreg Roach			$gmatch = array();
1304a6f13a4aSGreg Roach			if (preg_match("/\d $match[1] (.+)/", $this->gedrec, $gmatch)) {
1305a6f13a4aSGreg Roach				$value = str_replace("@", "", trim($gmatch[1]));
1306a6f13a4aSGreg Roach			}
1307a6f13a4aSGreg Roach		}
1308a6f13a4aSGreg Roach		if (preg_match("/\\$(\w+)/", $name, $match)) {
1309d1286247SGreg Roach			$name = $this->vars["'" . $match[1] . "'"]['id'];
1310a6f13a4aSGreg Roach		}
1311a6f13a4aSGreg Roach		$count = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER);
1312a6f13a4aSGreg Roach		$i     = 0;
1313a6f13a4aSGreg Roach		while ($i < $count) {
1314d1286247SGreg Roach			$t     = $this->vars[$match[$i][1]]['id'];
1315a6f13a4aSGreg Roach			$value = preg_replace("/\\$" . $match[$i][1] . "/", $t, $value, 1);
1316a6f13a4aSGreg Roach			$i++;
1317a6f13a4aSGreg Roach		}
1318a6f13a4aSGreg Roach		if (preg_match('/^I18N::number\((.+)\)$/', $value, $match)) {
1319a6f13a4aSGreg Roach			$value = I18N::number($match[1]);
1320a6f13a4aSGreg Roach		} elseif (preg_match('/^I18N::translate\(\'(.+)\'\)$/', $value, $match)) {
1321a6f13a4aSGreg Roach			$value = I18N::translate($match[1]);
1322a4956c0eSGreg Roach		} elseif (preg_match('/^I18N::translateContext\(\'(.+)\', *\'(.+)\'\)$/', $value, $match)) {
1323a6f13a4aSGreg Roach			$value = I18N::translateContext($match[1], $match[2]);
1324a6f13a4aSGreg Roach		}
1325a6f13a4aSGreg Roach		// Arithmetic functions
1326a6f13a4aSGreg Roach		if (preg_match("/(\d+)\s*([\-\+\*\/])\s*(\d+)/", $value, $match)) {
1327a6f13a4aSGreg Roach			switch ($match[2]) {
1328a6f13a4aSGreg Roach				case "+":
1329a6f13a4aSGreg Roach					$t     = $match[1] + $match[3];
1330a6f13a4aSGreg Roach					$value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1331a6f13a4aSGreg Roach					break;
1332a6f13a4aSGreg Roach				case "-":
1333a6f13a4aSGreg Roach					$t     = $match[1] - $match[3];
1334a6f13a4aSGreg Roach					$value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1335a6f13a4aSGreg Roach					break;
1336a6f13a4aSGreg Roach				case "*":
1337a6f13a4aSGreg Roach					$t     = $match[1] * $match[3];
1338a6f13a4aSGreg Roach					$value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1339a6f13a4aSGreg Roach					break;
1340a6f13a4aSGreg Roach				case "/":
1341a6f13a4aSGreg Roach					$t     = $match[1] / $match[3];
1342a6f13a4aSGreg Roach					$value = preg_replace("/" . $match[1] . "\s*([\-\+\*\/])\s*" . $match[3] . "/", $t, $value);
1343a6f13a4aSGreg Roach					break;
1344a6f13a4aSGreg Roach			}
1345a6f13a4aSGreg Roach		}
1346a6f13a4aSGreg Roach		if (strpos($value, "@") !== false) {
1347a6f13a4aSGreg Roach			$value = "";
1348a6f13a4aSGreg Roach		}
1349d1286247SGreg Roach		$this->vars[$name]['id'] = $value;
1350a6f13a4aSGreg Roach	}
1351a6f13a4aSGreg Roach
1352a6f13a4aSGreg Roach	/**
1353a6f13a4aSGreg Roach	 * XML <if > start element
1354a6f13a4aSGreg Roach	 *
1355a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1356a6f13a4aSGreg Roach	 */
13578edd1043SGreg Roach	private function ifStartHandler($attrs) {
1358a6f13a4aSGreg Roach		if ($this->process_ifs > 0) {
1359a6f13a4aSGreg Roach			$this->process_ifs++;
1360a6f13a4aSGreg Roach
1361a6f13a4aSGreg Roach			return;
1362a6f13a4aSGreg Roach		}
1363a6f13a4aSGreg Roach
1364a6f13a4aSGreg Roach		$condition = $attrs['condition'];
136582759250SGreg Roach		$condition = $this->substituteVars($condition, true);
1366a6f13a4aSGreg Roach		$condition = str_replace(array(" LT ", " GT "), array("<", ">"), $condition);
1367a6f13a4aSGreg Roach		// Replace the first accurance only once of @fact:DATE or in any other combinations to the current fact, such as BIRT
1368a6f13a4aSGreg Roach		$condition = str_replace("@fact", $this->fact, $condition);
1369a6f13a4aSGreg Roach		$match     = array();
1370a6f13a4aSGreg Roach		$count     = preg_match_all("/@([\w:\.]+)/", $condition, $match, PREG_SET_ORDER);
1371a6f13a4aSGreg Roach		$i         = 0;
1372a6f13a4aSGreg Roach		while ($i < $count) {
1373a6f13a4aSGreg Roach			$id    = $match[$i][1];
1374a6f13a4aSGreg Roach			$value = '""';
1375a6f13a4aSGreg Roach			if ($id == "ID") {
1376a6f13a4aSGreg Roach				if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1377a6f13a4aSGreg Roach					$value = "'" . $match[1] . "'";
1378a6f13a4aSGreg Roach				}
1379a6f13a4aSGreg Roach			} elseif ($id === "fact") {
1380a6f13a4aSGreg Roach				$value = '"' . $this->fact . '"';
1381a6f13a4aSGreg Roach			} elseif ($id === "desc") {
1382a6f13a4aSGreg Roach				$value = '"' . addslashes($this->desc) . '"';
1383a6f13a4aSGreg Roach			} elseif ($id === "generation") {
1384a6f13a4aSGreg Roach				$value = '"' . $this->generation . '"';
1385a6f13a4aSGreg Roach			} else {
1386a6f13a4aSGreg Roach
1387a6f13a4aSGreg Roach				$temp  = explode(" ", trim($this->gedrec));
1388a6f13a4aSGreg Roach				$level = $temp[0];
1389a6f13a4aSGreg Roach				if ($level == 0) {
1390a6f13a4aSGreg Roach					$level++;
1391a6f13a4aSGreg Roach				}
13923d7a8a4cSGreg Roach				$value = $this->getGedcomValue($id, $level, $this->gedrec);
1393a6f13a4aSGreg Roach				if (empty($value)) {
1394a6f13a4aSGreg Roach					$level++;
13953d7a8a4cSGreg Roach					$value = $this->getGedcomValue($id, $level, $this->gedrec);
1396a6f13a4aSGreg Roach				}
13975e8c88c1SGreg Roach				$value = preg_replace('/^@(' . WT_REGEX_XREF . ')@$/', '$1', $value);
13985e8c88c1SGreg Roach				$value = '"' . addslashes($value) . '"';
1399a6f13a4aSGreg Roach			}
1400a6f13a4aSGreg Roach			$condition = str_replace("@$id", $value, $condition);
1401a6f13a4aSGreg Roach			$i++;
1402a6f13a4aSGreg Roach		}
14035e8c88c1SGreg Roach		$ret = eval("return (bool) ($condition);");
1404a6f13a4aSGreg Roach		if (!$ret) {
1405a6f13a4aSGreg Roach			$this->process_ifs++;
1406a6f13a4aSGreg Roach		}
1407a6f13a4aSGreg Roach	}
1408a6f13a4aSGreg Roach
1409a6f13a4aSGreg Roach	/**
1410a6f13a4aSGreg Roach	 * XML <if /> end element
1411a6f13a4aSGreg Roach	 */
14128edd1043SGreg Roach	private function ifEndHandler() {
1413a6f13a4aSGreg Roach		if ($this->process_ifs > 0) {
1414a6f13a4aSGreg Roach			$this->process_ifs--;
1415a6f13a4aSGreg Roach		}
1416a6f13a4aSGreg Roach	}
1417a6f13a4aSGreg Roach
1418a6f13a4aSGreg Roach	/**
1419a6f13a4aSGreg Roach	 * XML <Footnote > start element
1420a6f13a4aSGreg Roach	 * Collect the Footnote links
1421a6f13a4aSGreg Roach	 * GEDCOM Records that are protected by Privacy setting will be ignore
1422a6f13a4aSGreg Roach	 *
1423a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1424a6f13a4aSGreg Roach	 */
14258edd1043SGreg Roach	private function footnoteStartHandler($attrs) {
1426e8e7866bSGreg Roach		global $WT_TREE;
1427a6f13a4aSGreg Roach
1428a6f13a4aSGreg Roach		$id = "";
1429a6f13a4aSGreg Roach		if (preg_match("/[0-9] (.+) @(.+)@/", $this->gedrec, $match)) {
1430a6f13a4aSGreg Roach			$id = $match[2];
1431a6f13a4aSGreg Roach		}
14327820e4d7SGreg Roach		$record = GedcomRecord::getInstance($id, $WT_TREE);
1433a6f13a4aSGreg Roach		if ($record && $record->canShow()) {
1434a6f13a4aSGreg Roach			array_push($this->print_data_stack, $this->print_data);
1435a6f13a4aSGreg Roach			$this->print_data = true;
1436a6f13a4aSGreg Roach			$style            = "";
1437a6f13a4aSGreg Roach			if (!empty($attrs['style'])) {
1438a6f13a4aSGreg Roach				$style = $attrs['style'];
1439a6f13a4aSGreg Roach			}
1440a6f13a4aSGreg Roach			$this->footnote_element = $this->current_element;
1441e8e7866bSGreg Roach			$this->current_element  = $this->report_root->createFootnote($style);
1442a6f13a4aSGreg Roach		} else {
1443a6f13a4aSGreg Roach			$this->print_data       = false;
1444a6f13a4aSGreg Roach			$this->process_footnote = false;
1445a6f13a4aSGreg Roach		}
1446a6f13a4aSGreg Roach	}
1447a6f13a4aSGreg Roach
1448a6f13a4aSGreg Roach	/**
1449a6f13a4aSGreg Roach	 * XML <Footnote /> end element
1450a6f13a4aSGreg Roach	 * Print the collected Footnote data
1451a6f13a4aSGreg Roach	 */
14528edd1043SGreg Roach	private function footnoteEndHandler() {
1453a6f13a4aSGreg Roach		if ($this->process_footnote) {
1454a6f13a4aSGreg Roach			$this->print_data = array_pop($this->print_data_stack);
1455a6f13a4aSGreg Roach			$temp             = trim($this->current_element->getValue());
1456a6f13a4aSGreg Roach			if (strlen($temp) > 3) {
1457e8e7866bSGreg Roach				$this->wt_report->addElement($this->current_element);
1458a6f13a4aSGreg Roach			}
1459a6f13a4aSGreg Roach			$this->current_element = $this->footnote_element;
1460a6f13a4aSGreg Roach		} else {
1461a6f13a4aSGreg Roach			$this->process_footnote = true;
1462a6f13a4aSGreg Roach		}
1463a6f13a4aSGreg Roach	}
1464a6f13a4aSGreg Roach
1465a6f13a4aSGreg Roach	/**
1466a6f13a4aSGreg Roach	 * XML <FootnoteTexts /> element
1467a6f13a4aSGreg Roach	 */
14688edd1043SGreg Roach	private function footnoteTextsStartHandler() {
1469a6f13a4aSGreg Roach		$temp = "footnotetexts";
1470e8e7866bSGreg Roach		$this->wt_report->addElement($temp);
1471a6f13a4aSGreg Roach	}
1472a6f13a4aSGreg Roach
1473a6f13a4aSGreg Roach	/**
1474a6f13a4aSGreg Roach	 * XML <AgeAtDeath /> element handler
1475a6f13a4aSGreg Roach	 */
14768edd1043SGreg Roach	private function ageAtDeathStartHandler() {
14773d7a8a4cSGreg Roach		// This duplicates functionality in FunctionsPrint::format_fact_date()
1478a6f13a4aSGreg Roach		global $factrec, $WT_TREE;
1479a6f13a4aSGreg Roach
1480a6f13a4aSGreg Roach		$match = array();
1481a6f13a4aSGreg Roach		if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1482a6f13a4aSGreg Roach			$person = Individual::getInstance($match[1], $WT_TREE);
1483a6f13a4aSGreg Roach			// Recorded age
1484a6f13a4aSGreg Roach			if (preg_match('/\n2 AGE (.+)/', $factrec, $match)) {
1485a6f13a4aSGreg Roach				$fact_age = $match[1];
1486a6f13a4aSGreg Roach			} else {
1487a6f13a4aSGreg Roach				$fact_age = '';
1488a6f13a4aSGreg Roach			}
1489a6f13a4aSGreg Roach			if (preg_match('/\n2 HUSB\n3 AGE (.+)/', $factrec, $match)) {
1490a6f13a4aSGreg Roach				$husb_age = $match[1];
1491a6f13a4aSGreg Roach			} else {
1492a6f13a4aSGreg Roach				$husb_age = '';
1493a6f13a4aSGreg Roach			}
1494a6f13a4aSGreg Roach			if (preg_match('/\n2 WIFE\n3 AGE (.+)/', $factrec, $match)) {
1495a6f13a4aSGreg Roach				$wife_age = $match[1];
1496a6f13a4aSGreg Roach			} else {
1497a6f13a4aSGreg Roach				$wife_age = '';
1498a6f13a4aSGreg Roach			}
1499a6f13a4aSGreg Roach
1500a6f13a4aSGreg Roach			// Calculated age
1501a6f13a4aSGreg Roach			$birth_date = $person->getBirthDate();
1502a6f13a4aSGreg Roach			// Can't use getDeathDate(), as this also gives BURI/CREM events, which
1503a6f13a4aSGreg Roach			// wouldn't give the correct "days after death" result for people with
1504a6f13a4aSGreg Roach			// no DEAT.
1505a6f13a4aSGreg Roach			$death_event = $person->getFirstFact('DEAT');
1506a6f13a4aSGreg Roach			if ($death_event) {
1507a6f13a4aSGreg Roach				$death_date = $death_event->getDate();
1508a6f13a4aSGreg Roach			} else {
1509a6f13a4aSGreg Roach				$death_date = new Date('');
1510a6f13a4aSGreg Roach			}
1511a6f13a4aSGreg Roach			$value = '';
1512a6f13a4aSGreg Roach			if (Date::compare($birth_date, $death_date) <= 0 || !$person->isDead()) {
1513a6f13a4aSGreg Roach				$age = Date::getAgeGedcom($birth_date, $death_date);
1514a6f13a4aSGreg Roach				// Only show calculated age if it differs from recorded age
1515a6f13a4aSGreg Roach				if ($age != '' && $age != "0d") {
1516a6f13a4aSGreg Roach					if ($fact_age != '' && $fact_age != $age || $fact_age == '' && $husb_age == '' && $wife_age == '' || $husb_age != '' && $person->getSex() == 'M' && $husb_age != $age || $wife_age != '' && $person->getSex() == 'F' && $wife_age != $age
1517a6f13a4aSGreg Roach					) {
15183d7a8a4cSGreg Roach						$value  = FunctionsDate::getAgeAtEvent($age, false);
1519a6f13a4aSGreg Roach						$abbrev = substr($value, 0, strpos($value, ' ') + 5);
1520a6f13a4aSGreg Roach						if ($value !== $abbrev) {
1521a6f13a4aSGreg Roach							$value = $abbrev . '.';
1522a6f13a4aSGreg Roach						}
1523a6f13a4aSGreg Roach					}
1524a6f13a4aSGreg Roach				}
1525a6f13a4aSGreg Roach			}
1526a6f13a4aSGreg Roach			$this->current_element->addText($value);
1527a6f13a4aSGreg Roach		}
1528a6f13a4aSGreg Roach	}
1529a6f13a4aSGreg Roach
1530a6f13a4aSGreg Roach	/**
1531a6f13a4aSGreg Roach	 * XML element Forced line break handler - HTML code
1532a6f13a4aSGreg Roach	 */
15338edd1043SGreg Roach	private function brStartHandler() {
1534a6f13a4aSGreg Roach		if ($this->print_data && $this->process_gedcoms === 0) {
1535a6f13a4aSGreg Roach			$this->current_element->addText('<br>');
1536a6f13a4aSGreg Roach		}
1537a6f13a4aSGreg Roach	}
1538a6f13a4aSGreg Roach
1539a6f13a4aSGreg Roach	/**
1540a6f13a4aSGreg Roach	 * XML <sp />element Forced space handler
1541a6f13a4aSGreg Roach	 */
15428edd1043SGreg Roach	private function spStartHandler() {
1543a6f13a4aSGreg Roach		if ($this->print_data && $this->process_gedcoms === 0) {
1544a6f13a4aSGreg Roach			$this->current_element->addText(' ');
1545a6f13a4aSGreg Roach		}
1546a6f13a4aSGreg Roach	}
1547a6f13a4aSGreg Roach
1548a6f13a4aSGreg Roach	/**
154976692c8bSGreg Roach	 * XML <HighlightedImage/>
155076692c8bSGreg Roach	 *
1551a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1552a6f13a4aSGreg Roach	 */
15538edd1043SGreg Roach	private function highlightedImageStartHandler($attrs) {
1554e8e7866bSGreg Roach		global $WT_TREE;
1555a6f13a4aSGreg Roach
1556a6f13a4aSGreg Roach		$id    = '';
1557a6f13a4aSGreg Roach		$match = array();
1558a6f13a4aSGreg Roach		if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
1559a6f13a4aSGreg Roach			$id = $match[1];
1560a6f13a4aSGreg Roach		}
1561a6f13a4aSGreg Roach
1562a6f13a4aSGreg Roach		// mixed Position the top corner of this box on the page. the default is the current position
1563a6f13a4aSGreg Roach		$top = '.';
1564a6f13a4aSGreg Roach		if (isset($attrs['top'])) {
1565a6f13a4aSGreg Roach			if ($attrs['top'] === '0') {
1566a6f13a4aSGreg Roach				$top = 0;
1567a6f13a4aSGreg Roach			} elseif ($attrs['top'] === '.') {
1568a6f13a4aSGreg Roach				$top = '.';
1569a6f13a4aSGreg Roach			} elseif (!empty($attrs['top'])) {
1570a6f13a4aSGreg Roach				$top = (int) $attrs['top'];
1571a6f13a4aSGreg Roach			}
1572a6f13a4aSGreg Roach		}
1573a6f13a4aSGreg Roach
1574a6f13a4aSGreg Roach		// mixed Position the left corner of this box on the page. the default is the current position
1575a6f13a4aSGreg Roach		$left = '.';
1576a6f13a4aSGreg Roach		if (isset($attrs['left'])) {
1577a6f13a4aSGreg Roach			if ($attrs['left'] === '0') {
1578a6f13a4aSGreg Roach				$left = 0;
1579a6f13a4aSGreg Roach			} elseif ($attrs['left'] === '.') {
1580a6f13a4aSGreg Roach				$left = '.';
1581a6f13a4aSGreg Roach			} elseif (!empty($attrs['left'])) {
1582a6f13a4aSGreg Roach				$left = (int) $attrs['left'];
1583a6f13a4aSGreg Roach			}
1584a6f13a4aSGreg Roach		}
1585a6f13a4aSGreg Roach
1586a6f13a4aSGreg Roach		// string Align the image in left, center, right
1587a6f13a4aSGreg Roach		$align = '';
1588a6f13a4aSGreg Roach		if (!empty($attrs['align'])) {
1589a6f13a4aSGreg Roach			$align = $attrs['align'];
1590a6f13a4aSGreg Roach		}
1591a6f13a4aSGreg Roach
1592a6f13a4aSGreg Roach		// string Next Line should be T:next to the image, N:next line
1593a6f13a4aSGreg Roach		$ln = '';
1594a6f13a4aSGreg Roach		if (!empty($attrs['ln'])) {
1595a6f13a4aSGreg Roach			$ln = $attrs['ln'];
1596a6f13a4aSGreg Roach		}
1597a6f13a4aSGreg Roach
1598a6f13a4aSGreg Roach		$width  = 0;
1599a6f13a4aSGreg Roach		$height = 0;
1600a6f13a4aSGreg Roach		if (!empty($attrs['width'])) {
1601a6f13a4aSGreg Roach			$width = (int) $attrs['width'];
1602a6f13a4aSGreg Roach		}
1603a6f13a4aSGreg Roach		if (!empty($attrs['height'])) {
1604a6f13a4aSGreg Roach			$height = (int) $attrs['height'];
1605a6f13a4aSGreg Roach		}
1606a6f13a4aSGreg Roach
1607a6f13a4aSGreg Roach		$person      = Individual::getInstance($id, $WT_TREE);
1608a6f13a4aSGreg Roach		$mediaobject = $person->findHighlightedMedia();
1609a6f13a4aSGreg Roach		if ($mediaobject) {
1610a6f13a4aSGreg Roach			$attributes = $mediaobject->getImageAttributes('thumb');
1611a6f13a4aSGreg Roach			if (in_array(
1612a6f13a4aSGreg Roach					$attributes['ext'],
1613a6f13a4aSGreg Roach					array(
1614a6f13a4aSGreg Roach						'GIF',
1615a6f13a4aSGreg Roach						'JPG',
1616a6f13a4aSGreg Roach						'PNG',
1617a6f13a4aSGreg Roach						'SWF',
1618a6f13a4aSGreg Roach						'PSD',
1619a6f13a4aSGreg Roach						'BMP',
1620a6f13a4aSGreg Roach						'TIFF',
1621a6f13a4aSGreg Roach						'TIFF',
1622a6f13a4aSGreg Roach						'JPC',
1623a6f13a4aSGreg Roach						'JP2',
1624a6f13a4aSGreg Roach						'JPX',
1625a6f13a4aSGreg Roach						'JB2',
1626a6f13a4aSGreg Roach						'SWC',
1627a6f13a4aSGreg Roach						'IFF',
1628a6f13a4aSGreg Roach						'WBMP',
1629a6f13a4aSGreg Roach						'XBM',
1630a6f13a4aSGreg Roach					)
1631a6f13a4aSGreg Roach				) && $mediaobject->canShow() && $mediaobject->fileExists('thumb')
1632a6f13a4aSGreg Roach			) {
1633a6f13a4aSGreg Roach				if ($width > 0 && $height == 0) {
1634a6f13a4aSGreg Roach					$perc   = $width / $attributes['adjW'];
1635a6f13a4aSGreg Roach					$height = round($attributes['adjH'] * $perc);
1636a6f13a4aSGreg Roach				} elseif ($height > 0 && $width == 0) {
1637a6f13a4aSGreg Roach					$perc  = $height / $attributes['adjH'];
1638a6f13a4aSGreg Roach					$width = round($attributes['adjW'] * $perc);
1639a6f13a4aSGreg Roach				} else {
1640a6f13a4aSGreg Roach					$width  = $attributes['adjW'];
1641a6f13a4aSGreg Roach					$height = $attributes['adjH'];
1642a6f13a4aSGreg Roach				}
1643e8e7866bSGreg Roach				$image = $this->report_root->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln);
1644e8e7866bSGreg Roach				$this->wt_report->addElement($image);
1645a6f13a4aSGreg Roach			}
1646a6f13a4aSGreg Roach		}
1647a6f13a4aSGreg Roach	}
1648a6f13a4aSGreg Roach
1649a6f13a4aSGreg Roach	/**
165076692c8bSGreg Roach	 * XML <Image/>
165176692c8bSGreg Roach	 *
1652a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1653a6f13a4aSGreg Roach	 */
16548edd1043SGreg Roach	private function imageStartHandler($attrs) {
1655e8e7866bSGreg Roach		global $WT_TREE;
1656a6f13a4aSGreg Roach
1657a6f13a4aSGreg Roach		// mixed Position the top corner of this box on the page. the default is the current position
1658a6f13a4aSGreg Roach		$top = '.';
1659a6f13a4aSGreg Roach		if (isset($attrs['top'])) {
1660a6f13a4aSGreg Roach			if ($attrs['top'] === "0") {
1661a6f13a4aSGreg Roach				$top = 0;
1662a6f13a4aSGreg Roach			} elseif ($attrs['top'] === '.') {
1663a6f13a4aSGreg Roach				$top = '.';
1664a6f13a4aSGreg Roach			} elseif (!empty($attrs['top'])) {
1665a6f13a4aSGreg Roach				$top = (int) $attrs['top'];
1666a6f13a4aSGreg Roach			}
1667a6f13a4aSGreg Roach		}
1668a6f13a4aSGreg Roach
1669a6f13a4aSGreg Roach		// mixed Position the left corner of this box on the page. the default is the current position
1670a6f13a4aSGreg Roach		$left = '.';
1671a6f13a4aSGreg Roach		if (isset($attrs['left'])) {
1672a6f13a4aSGreg Roach			if ($attrs['left'] === '0') {
1673a6f13a4aSGreg Roach				$left = 0;
1674a6f13a4aSGreg Roach			} elseif ($attrs['left'] === '.') {
1675a6f13a4aSGreg Roach				$left = '.';
1676a6f13a4aSGreg Roach			} elseif (!empty($attrs['left'])) {
1677a6f13a4aSGreg Roach				$left = (int) $attrs['left'];
1678a6f13a4aSGreg Roach			}
1679a6f13a4aSGreg Roach		}
1680a6f13a4aSGreg Roach
1681a6f13a4aSGreg Roach		// string Align the image in left, center, right
1682a6f13a4aSGreg Roach		$align = '';
1683a6f13a4aSGreg Roach		if (!empty($attrs['align'])) {
1684a6f13a4aSGreg Roach			$align = $attrs['align'];
1685a6f13a4aSGreg Roach		}
1686a6f13a4aSGreg Roach
1687a6f13a4aSGreg Roach		// string Next Line should be T:next to the image, N:next line
1688a6f13a4aSGreg Roach		$ln = 'T';
1689a6f13a4aSGreg Roach		if (!empty($attrs['ln'])) {
1690a6f13a4aSGreg Roach			$ln = $attrs['ln'];
1691a6f13a4aSGreg Roach		}
1692a6f13a4aSGreg Roach
1693a6f13a4aSGreg Roach		$width  = 0;
1694a6f13a4aSGreg Roach		$height = 0;
1695a6f13a4aSGreg Roach		if (!empty($attrs['width'])) {
1696a6f13a4aSGreg Roach			$width = (int) $attrs['width'];
1697a6f13a4aSGreg Roach		}
1698a6f13a4aSGreg Roach		if (!empty($attrs['height'])) {
1699a6f13a4aSGreg Roach			$height = (int) $attrs['height'];
1700a6f13a4aSGreg Roach		}
1701a6f13a4aSGreg Roach
1702a6f13a4aSGreg Roach		$file = '';
1703a6f13a4aSGreg Roach		if (!empty($attrs['file'])) {
1704a6f13a4aSGreg Roach			$file = $attrs['file'];
1705a6f13a4aSGreg Roach		}
1706a6f13a4aSGreg Roach		if ($file == "@FILE") {
1707a6f13a4aSGreg Roach			$match = array();
1708a6f13a4aSGreg Roach			if (preg_match("/\d OBJE @(.+)@/", $this->gedrec, $match)) {
1709a6f13a4aSGreg Roach				$mediaobject = Media::getInstance($match[1], $WT_TREE);
1710a6f13a4aSGreg Roach				$attributes  = $mediaobject->getImageAttributes('thumb');
1711a6f13a4aSGreg Roach				if (in_array(
1712a6f13a4aSGreg Roach						$attributes['ext'],
1713a6f13a4aSGreg Roach						array(
1714a6f13a4aSGreg Roach							'GIF',
1715a6f13a4aSGreg Roach							'JPG',
1716a6f13a4aSGreg Roach							'PNG',
1717a6f13a4aSGreg Roach							'SWF',
1718a6f13a4aSGreg Roach							'PSD',
1719a6f13a4aSGreg Roach							'BMP',
1720a6f13a4aSGreg Roach							'TIFF',
1721a6f13a4aSGreg Roach							'TIFF',
1722a6f13a4aSGreg Roach							'JPC',
1723a6f13a4aSGreg Roach							'JP2',
1724a6f13a4aSGreg Roach							'JPX',
1725a6f13a4aSGreg Roach							'JB2',
1726a6f13a4aSGreg Roach							'SWC',
1727a6f13a4aSGreg Roach							'IFF',
1728a6f13a4aSGreg Roach							'WBMP',
1729a6f13a4aSGreg Roach							'XBM',
1730a6f13a4aSGreg Roach						)
1731a6f13a4aSGreg Roach					) && $mediaobject->canShow() && $mediaobject->fileExists('thumb')
1732a6f13a4aSGreg Roach				) {
1733a6f13a4aSGreg Roach					if ($width > 0 && $height == 0) {
1734a6f13a4aSGreg Roach						$perc   = $width / $attributes['adjW'];
1735a6f13a4aSGreg Roach						$height = round($attributes['adjH'] * $perc);
1736a6f13a4aSGreg Roach					} elseif ($height > 0 && $width == 0) {
1737a6f13a4aSGreg Roach						$perc  = $height / $attributes['adjH'];
1738a6f13a4aSGreg Roach						$width = round($attributes['adjW'] * $perc);
1739a6f13a4aSGreg Roach					} else {
1740a6f13a4aSGreg Roach						$width  = $attributes['adjW'];
1741a6f13a4aSGreg Roach						$height = $attributes['adjH'];
1742a6f13a4aSGreg Roach					}
1743e8e7866bSGreg Roach					$image = $this->report_root->createImageFromObject($mediaobject, $left, $top, $width, $height, $align, $ln);
1744e8e7866bSGreg Roach					$this->wt_report->addElement($image);
1745a6f13a4aSGreg Roach				}
1746a6f13a4aSGreg Roach			}
1747a6f13a4aSGreg Roach		} else {
1748a6f13a4aSGreg Roach			if (file_exists($file) && preg_match("/(jpg|jpeg|png|gif)$/i", $file)) {
1749a6f13a4aSGreg Roach				$size = getimagesize($file);
1750a6f13a4aSGreg Roach				if ($width > 0 && $height == 0) {
1751a6f13a4aSGreg Roach					$perc   = $width / $size[0];
1752a6f13a4aSGreg Roach					$height = round($size[1] * $perc);
1753a6f13a4aSGreg Roach				} elseif ($height > 0 && $width == 0) {
1754a6f13a4aSGreg Roach					$perc  = $height / $size[1];
1755a6f13a4aSGreg Roach					$width = round($size[0] * $perc);
1756a6f13a4aSGreg Roach				} else {
1757a6f13a4aSGreg Roach					$width  = $size[0];
1758a6f13a4aSGreg Roach					$height = $size[1];
1759a6f13a4aSGreg Roach				}
1760e8e7866bSGreg Roach				$image = $this->report_root->createImage($file, $left, $top, $width, $height, $align, $ln);
1761e8e7866bSGreg Roach				$this->wt_report->addElement($image);
1762a6f13a4aSGreg Roach			}
1763a6f13a4aSGreg Roach		}
1764a6f13a4aSGreg Roach	}
1765a6f13a4aSGreg Roach
1766a6f13a4aSGreg Roach	/**
1767a6f13a4aSGreg Roach	 * XML <Line> element handler
1768a6f13a4aSGreg Roach	 *
1769a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1770a6f13a4aSGreg Roach	 */
17718edd1043SGreg Roach	private function lineStartHandler($attrs) {
1772a6f13a4aSGreg Roach		// Start horizontal position, current position (default)
1773a6f13a4aSGreg Roach		$x1 = ".";
1774a6f13a4aSGreg Roach		if (isset($attrs['x1'])) {
1775a6f13a4aSGreg Roach			if ($attrs['x1'] === "0") {
1776a6f13a4aSGreg Roach				$x1 = 0;
1777a6f13a4aSGreg Roach			} elseif ($attrs['x1'] === ".") {
1778a6f13a4aSGreg Roach				$x1 = ".";
1779a6f13a4aSGreg Roach			} elseif (!empty($attrs['x1'])) {
1780a6f13a4aSGreg Roach				$x1 = (int) $attrs['x1'];
1781a6f13a4aSGreg Roach			}
1782a6f13a4aSGreg Roach		}
1783a6f13a4aSGreg Roach		// Start vertical position, current position (default)
1784a6f13a4aSGreg Roach		$y1 = ".";
1785a6f13a4aSGreg Roach		if (isset($attrs['y1'])) {
1786a6f13a4aSGreg Roach			if ($attrs['y1'] === "0") {
1787a6f13a4aSGreg Roach				$y1 = 0;
1788a6f13a4aSGreg Roach			} elseif ($attrs['y1'] === ".") {
1789a6f13a4aSGreg Roach				$y1 = ".";
1790a6f13a4aSGreg Roach			} elseif (!empty($attrs['y1'])) {
1791a6f13a4aSGreg Roach				$y1 = (int) $attrs['y1'];
1792a6f13a4aSGreg Roach			}
1793a6f13a4aSGreg Roach		}
1794a6f13a4aSGreg Roach		// End horizontal position, maximum width (default)
1795a6f13a4aSGreg Roach		$x2 = ".";
1796a6f13a4aSGreg Roach		if (isset($attrs['x2'])) {
1797a6f13a4aSGreg Roach			if ($attrs['x2'] === "0") {
1798a6f13a4aSGreg Roach				$x2 = 0;
1799a6f13a4aSGreg Roach			} elseif ($attrs['x2'] === ".") {
1800a6f13a4aSGreg Roach				$x2 = ".";
1801a6f13a4aSGreg Roach			} elseif (!empty($attrs['x2'])) {
1802a6f13a4aSGreg Roach				$x2 = (int) $attrs['x2'];
1803a6f13a4aSGreg Roach			}
1804a6f13a4aSGreg Roach		}
1805a6f13a4aSGreg Roach		// End vertical position
1806a6f13a4aSGreg Roach		$y2 = ".";
1807a6f13a4aSGreg Roach		if (isset($attrs['y2'])) {
1808a6f13a4aSGreg Roach			if ($attrs['y2'] === "0") {
1809a6f13a4aSGreg Roach				$y2 = 0;
1810a6f13a4aSGreg Roach			} elseif ($attrs['y2'] === ".") {
1811a6f13a4aSGreg Roach				$y2 = ".";
1812a6f13a4aSGreg Roach			} elseif (!empty($attrs['y2'])) {
1813a6f13a4aSGreg Roach				$y2 = (int) $attrs['y2'];
1814a6f13a4aSGreg Roach			}
1815a6f13a4aSGreg Roach		}
1816a6f13a4aSGreg Roach
1817e8e7866bSGreg Roach		$line = $this->report_root->createLine($x1, $y1, $x2, $y2);
1818e8e7866bSGreg Roach		$this->wt_report->addElement($line);
1819a6f13a4aSGreg Roach	}
1820a6f13a4aSGreg Roach
1821a6f13a4aSGreg Roach	/**
182276692c8bSGreg Roach	 * XML <List>
1823a6f13a4aSGreg Roach	 *
1824a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
1825a6f13a4aSGreg Roach	 */
18268edd1043SGreg Roach	private function listStartHandler($attrs) {
1827d1286247SGreg Roach		global $WT_TREE;
1828a6f13a4aSGreg Roach
1829a6f13a4aSGreg Roach		$this->process_repeats++;
1830a6f13a4aSGreg Roach		if ($this->process_repeats > 1) {
1831a6f13a4aSGreg Roach			return;
1832a6f13a4aSGreg Roach		}
1833a6f13a4aSGreg Roach
1834a6f13a4aSGreg Roach		$match = array();
1835a6f13a4aSGreg Roach		if (isset($attrs['sortby'])) {
1836a6f13a4aSGreg Roach			$sortby = $attrs['sortby'];
1837a6f13a4aSGreg Roach			if (preg_match("/\\$(\w+)/", $sortby, $match)) {
1838d1286247SGreg Roach				$sortby = $this->vars[$match[1]]['id'];
1839a6f13a4aSGreg Roach				$sortby = trim($sortby);
1840a6f13a4aSGreg Roach			}
1841a6f13a4aSGreg Roach		} else {
1842a6f13a4aSGreg Roach			$sortby = "NAME";
1843a6f13a4aSGreg Roach		}
1844a6f13a4aSGreg Roach
1845a6f13a4aSGreg Roach		if (isset($attrs['list'])) {
1846a6f13a4aSGreg Roach			$listname = $attrs['list'];
1847a6f13a4aSGreg Roach		} else {
1848a6f13a4aSGreg Roach			$listname = "individual";
1849a6f13a4aSGreg Roach		}
1850a6f13a4aSGreg Roach		// Some filters/sorts can be applied using SQL, while others require PHP
1851a6f13a4aSGreg Roach		switch ($listname) {
1852a6f13a4aSGreg Roach			case "pending":
1853a6f13a4aSGreg Roach				$rows = Database::prepare(
18545d0bc43dSGreg Roach					"SELECT xref, CASE new_gedcom WHEN '' THEN old_gedcom ELSE new_gedcom END AS gedcom" .
18555d0bc43dSGreg Roach					" FROM `##change`" . " WHERE (xref, change_id) IN (" .
18565d0bc43dSGreg Roach					"  SELECT xref, MAX(change_id)" .
18575d0bc43dSGreg Roach					"  FROM `##change`" .
18585d0bc43dSGreg Roach					"  WHERE status = 'pending' AND gedcom_id = :tree_id" .
18595d0bc43dSGreg Roach					"  GROUP BY xref" .
18605d0bc43dSGreg Roach					" )"
18615d0bc43dSGreg Roach				)->execute(array(
186276692c8bSGreg Roach					'tree_id' => $WT_TREE->getTreeId(),
18635d0bc43dSGreg Roach				))->fetchAll();
1864a6f13a4aSGreg Roach				$this->list = array();
1865a6f13a4aSGreg Roach				foreach ($rows as $row) {
1866a6f13a4aSGreg Roach					$this->list[] = GedcomRecord::getInstance($row->xref, $WT_TREE, $row->gedcom);
1867a6f13a4aSGreg Roach				}
1868a6f13a4aSGreg Roach				break;
1869a6f13a4aSGreg Roach			case 'individual':
1870*76156db1SGreg Roach				$sql_select   = "SELECT i_id AS xref, i_gedcom AS gedcom FROM `##individuals` ";
1871a6f13a4aSGreg Roach				$sql_join     = "";
1872825006d2SGreg Roach				$sql_where    = " WHERE i_file = :tree_id";
1873a6f13a4aSGreg Roach				$sql_order_by = "";
1874825006d2SGreg Roach				$sql_params   = array('tree_id' => $WT_TREE->getTreeId());
1875a6f13a4aSGreg Roach				foreach ($attrs as $attr => $value) {
1876a6f13a4aSGreg Roach					if (strpos($attr, 'filter') === 0 && $value) {
187782759250SGreg Roach						$value = $this->substituteVars($value, false);
1878a6f13a4aSGreg Roach						// Convert the various filters into SQL
1879a6f13a4aSGreg Roach						if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
1880a6f13a4aSGreg Roach							$sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=i_file AND {$attr}.d_gid=i_id)";
1881b0d2e743SGreg Roach							$sql_where .= " AND {$attr}.d_fact = :{$attr}fact";
18825d0bc43dSGreg Roach							$sql_params[$attr . 'fact'] = $match[1];
1883a6f13a4aSGreg Roach							$date                       = new Date($match[3]);
1884a6f13a4aSGreg Roach							if ($match[2] == "LTE") {
18855d0bc43dSGreg Roach								$sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date";
18865d0bc43dSGreg Roach								$sql_params[$attr . 'date'] = $date->maximumJulianDay();
1887a6f13a4aSGreg Roach							} else {
18885d0bc43dSGreg Roach								$sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date";
18895d0bc43dSGreg Roach								$sql_params[$attr . 'date'] = $date->minimumJulianDay();
1890a6f13a4aSGreg Roach							}
1891a6f13a4aSGreg Roach							if ($sortby == $match[1]) {
1892a6f13a4aSGreg Roach								$sortby = "";
1893a6f13a4aSGreg Roach								$sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1";
1894a6f13a4aSGreg Roach							}
1895a6f13a4aSGreg Roach							unset($attrs[$attr]); // This filter has been fully processed
1896a6f13a4aSGreg Roach						} elseif (preg_match('/^NAME CONTAINS (.*)$/', $value, $match)) {
1897a6f13a4aSGreg Roach							// Do nothing, unless you have to
1898a6f13a4aSGreg Roach							if ($match[1] != '' || $sortby == 'NAME') {
1899a6f13a4aSGreg Roach								$sql_join .= " JOIN `##name` AS {$attr} ON (n_file=i_file AND n_id=i_id)";
1900a6f13a4aSGreg Roach								// Search the DB only if there is any name supplied
1901a6f13a4aSGreg Roach								if ($match[1] != "") {
1902a6f13a4aSGreg Roach									$names = explode(" ", $match[1]);
19035d0bc43dSGreg Roach									foreach ($names as $n => $name) {
19045d0bc43dSGreg Roach										$sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')";
19055d0bc43dSGreg Roach										$sql_params[$attr . 'name' . $n] = $name;
1906a6f13a4aSGreg Roach									}
1907a6f13a4aSGreg Roach								}
1908a6f13a4aSGreg Roach								// Let the DB do the name sorting even when no name was entered
1909a6f13a4aSGreg Roach								if ($sortby == "NAME") {
1910a6f13a4aSGreg Roach									$sortby = "";
1911a6f13a4aSGreg Roach									$sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort";
1912a6f13a4aSGreg Roach								}
1913a6f13a4aSGreg Roach							}
1914a6f13a4aSGreg Roach							unset($attrs[$attr]); // This filter has been fully processed
1915a6f13a4aSGreg Roach						} elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) {
19165d0bc43dSGreg Roach							$sql_where .= " AND i_gedcom REGEXP :{$attr}gedcom";
1917b4e512fdSGreg Roach							// PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT"
1918b4e512fdSGreg Roach							$sql_params[$attr . 'gedcom'] = str_replace('\n', "\n", $match[1]);
1919a6f13a4aSGreg Roach							unset($attrs[$attr]); // This filter has been fully processed
1920a6f13a4aSGreg Roach						} elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
1921a6f13a4aSGreg Roach							$sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file = i_file)";
1922a6f13a4aSGreg Roach							$sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file = {$attr}b.pl_file AND {$attr}b.pl_p_id = {$attr}a.p_id AND {$attr}b.pl_gid = i_id)";
19235d0bc43dSGreg Roach							$sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')";
19245d0bc43dSGreg Roach							$sql_params[$attr . 'place'] = $match[1];
1925a6f13a4aSGreg Roach							// Don't unset this filter. This is just initial filtering
1926a6f13a4aSGreg Roach						} elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) {
19275d0bc43dSGreg Roach							$sql_where .= " AND i_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')";
19285d0bc43dSGreg Roach							$sql_params[$attr . 'contains1'] = $match[1];
19295d0bc43dSGreg Roach							$sql_params[$attr . 'contains2'] = $match[2];
19305d0bc43dSGreg Roach							$sql_params[$attr . 'contains3'] = $match[3];
1931a6f13a4aSGreg Roach							// Don't unset this filter. This is just initial filtering
1932a6f13a4aSGreg Roach						}
1933a6f13a4aSGreg Roach					}
1934a6f13a4aSGreg Roach				}
1935a6f13a4aSGreg Roach
1936a6f13a4aSGreg Roach				$this->list = array();
1937a6f13a4aSGreg Roach				$rows       = Database::prepare(
1938a6f13a4aSGreg Roach					$sql_select . $sql_join . $sql_where . $sql_order_by
19395d0bc43dSGreg Roach				)->execute($sql_params)->fetchAll();
1940a6f13a4aSGreg Roach
1941a6f13a4aSGreg Roach				foreach ($rows as $row) {
1942*76156db1SGreg Roach					$this->list[$row->xref] = Individual::getInstance($row->xref, $WT_TREE, $row->gedcom);
1943a6f13a4aSGreg Roach				}
1944a6f13a4aSGreg Roach				break;
1945a6f13a4aSGreg Roach
1946a6f13a4aSGreg Roach			case 'family':
1947*76156db1SGreg Roach				$sql_select   = "SELECT f_id AS xref, f_gedcom AS gedcom FROM `##families`";
1948a6f13a4aSGreg Roach				$sql_join     = "";
1949825006d2SGreg Roach				$sql_where    = " WHERE f_file = :tree_id";
1950a6f13a4aSGreg Roach				$sql_order_by = "";
1951825006d2SGreg Roach				$sql_params   = array('tree_id' => $WT_TREE->getTreeId());
1952a6f13a4aSGreg Roach				foreach ($attrs as $attr => $value) {
1953a6f13a4aSGreg Roach					if (strpos($attr, 'filter') === 0 && $value) {
195482759250SGreg Roach						$value = $this->substituteVars($value, false);
1955a6f13a4aSGreg Roach						// Convert the various filters into SQL
1956a6f13a4aSGreg Roach						if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
1957a9eb55f8SGreg Roach							$sql_join .= " JOIN `##dates` AS {$attr} ON ({$attr}.d_file=f_file AND {$attr}.d_gid=f_id)";
1958b0d2e743SGreg Roach							$sql_where .= " AND {$attr}.d_fact = :{$attr}fact";
19595d0bc43dSGreg Roach							$sql_params[$attr . 'fact'] = $match[1];
1960a6f13a4aSGreg Roach							$date                       = new Date($match[3]);
1961a6f13a4aSGreg Roach							if ($match[2] == "LTE") {
19625d0bc43dSGreg Roach								$sql_where .= " AND {$attr}.d_julianday2 <= :{$attr}date";
19635d0bc43dSGreg Roach								$sql_params[$attr . 'date'] = $date->maximumJulianDay();
1964a6f13a4aSGreg Roach							} else {
19655d0bc43dSGreg Roach								$sql_where .= " AND {$attr}.d_julianday1 >= :{$attr}date";
19665d0bc43dSGreg Roach								$sql_params[$attr . 'date'] = $date->minimumJulianDay();
1967a6f13a4aSGreg Roach							}
1968a6f13a4aSGreg Roach							if ($sortby == $match[1]) {
1969a6f13a4aSGreg Roach								$sortby = "";
1970a6f13a4aSGreg Roach								$sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.d_julianday1";
1971a6f13a4aSGreg Roach							}
1972a6f13a4aSGreg Roach							unset($attrs[$attr]); // This filter has been fully processed
1973a6f13a4aSGreg Roach						} elseif (preg_match('/^REGEXP \/(.+)\//', $value, $match)) {
19745d0bc43dSGreg Roach							$sql_where .= " AND f_gedcom REGEXP :{$attr}gedcom";
1975b4e512fdSGreg Roach							// PDO helpfully escapes backslashes for us, preventing us from matching "\n1 FACT"
1976b4e512fdSGreg Roach							$sql_params[$attr . 'gedcom'] = str_replace('\n', "\n", $match[1]);
1977a6f13a4aSGreg Roach							unset($attrs[$attr]); // This filter has been fully processed
1978a6f13a4aSGreg Roach						} elseif (preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) {
19795d0bc43dSGreg Roach							// Do nothing, unless you have to
19805d0bc43dSGreg Roach							if ($match[1] != '' || $sortby == 'NAME') {
19815d0bc43dSGreg Roach								$sql_join .= " JOIN `##name` AS {$attr} ON n_file = f_file AND n_id IN (f_husb, f_wife)";
19825d0bc43dSGreg Roach								// Search the DB only if there is any name supplied
19835d0bc43dSGreg Roach								if ($match[1] != "") {
19845d0bc43dSGreg Roach									$names = explode(" ", $match[1]);
19855d0bc43dSGreg Roach									foreach ($names as $n => $name) {
19865d0bc43dSGreg Roach										$sql_where .= " AND {$attr}.n_full LIKE CONCAT('%', :{$attr}name{$n}, '%')";
19875d0bc43dSGreg Roach										$sql_params[$attr . 'name' . $n] = $name;
19885d0bc43dSGreg Roach									}
19895d0bc43dSGreg Roach								}
19905d0bc43dSGreg Roach								// Let the DB do the name sorting even when no name was entered
1991a6f13a4aSGreg Roach								if ($sortby == "NAME") {
1992a6f13a4aSGreg Roach									$sortby = "";
1993a6f13a4aSGreg Roach									$sql_order_by .= ($sql_order_by ? ", " : " ORDER BY ") . "{$attr}.n_sort";
1994a6f13a4aSGreg Roach								}
19955d0bc43dSGreg Roach							}
1996a6f13a4aSGreg Roach							unset($attrs[$attr]); // This filter has been fully processed
19975d0bc43dSGreg Roach
1998a6f13a4aSGreg Roach						} elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
1999a6f13a4aSGreg Roach							$sql_join .= " JOIN `##places` AS {$attr}a ON ({$attr}a.p_file=f_file)";
2000a6f13a4aSGreg Roach							$sql_join .= " JOIN `##placelinks` AS {$attr}b ON ({$attr}a.p_file={$attr}b.pl_file AND {$attr}b.pl_p_id={$attr}a.p_id AND {$attr}b.pl_gid=f_id)";
20015d0bc43dSGreg Roach							$sql_where .= " AND {$attr}a.p_place LIKE CONCAT('%', :{$attr}place, '%')";
20025d0bc43dSGreg Roach							$sql_params[$attr . 'place'] = $match[1];
2003a6f13a4aSGreg Roach							// Don't unset this filter. This is just initial filtering
2004a6f13a4aSGreg Roach						} elseif (preg_match('/^(\w*):*(\w*) CONTAINS (.+)$/', $value, $match)) {
20055d0bc43dSGreg Roach							$sql_where .= " AND f_gedcom LIKE CONCAT('%', :{$attr}contains1, '%', :{$attr}contains2, '%', :{$attr}contains3, '%')";
20065d0bc43dSGreg Roach							$sql_params[$attr . 'contains1'] = $match[1];
20075d0bc43dSGreg Roach							$sql_params[$attr . 'contains2'] = $match[2];
20085d0bc43dSGreg Roach							$sql_params[$attr . 'contains3'] = $match[3];
2009a6f13a4aSGreg Roach							// Don't unset this filter. This is just initial filtering
2010a6f13a4aSGreg Roach						}
2011a6f13a4aSGreg Roach					}
2012a6f13a4aSGreg Roach				}
2013a6f13a4aSGreg Roach
2014a6f13a4aSGreg Roach				$this->list = array();
2015a6f13a4aSGreg Roach				$rows       = Database::prepare(
2016a6f13a4aSGreg Roach					$sql_select . $sql_join . $sql_where . $sql_order_by
20175d0bc43dSGreg Roach				)->execute($sql_params)->fetchAll();
2018a6f13a4aSGreg Roach
2019a6f13a4aSGreg Roach				foreach ($rows as $row) {
2020*76156db1SGreg Roach					$this->list[$row->xref] = Family::getInstance($row->xref, $WT_TREE, $row->gedcom);
2021a6f13a4aSGreg Roach				}
2022a6f13a4aSGreg Roach				break;
2023a6f13a4aSGreg Roach
2024a6f13a4aSGreg Roach			default:
2025a6f13a4aSGreg Roach				throw new \DomainException('Invalid list name: ' . $listname);
2026a6f13a4aSGreg Roach		}
2027a6f13a4aSGreg Roach
2028a6f13a4aSGreg Roach		$filters  = array();
2029a6f13a4aSGreg Roach		$filters2 = array();
2030a6f13a4aSGreg Roach		if (isset($attrs['filter1']) && count($this->list) > 0) {
2031a6f13a4aSGreg Roach			foreach ($attrs as $key => $value) {
2032a6f13a4aSGreg Roach				if (preg_match("/filter(\d)/", $key)) {
2033a6f13a4aSGreg Roach					$condition = $value;
2034a6f13a4aSGreg Roach					if (preg_match("/@(\w+)/", $condition, $match)) {
2035a6f13a4aSGreg Roach						$id    = $match[1];
2036a6f13a4aSGreg Roach						$value = "''";
2037a6f13a4aSGreg Roach						if ($id == "ID") {
2038a6f13a4aSGreg Roach							if (preg_match("/0 @(.+)@/", $this->gedrec, $match)) {
2039a6f13a4aSGreg Roach								$value = "'" . $match[1] . "'";
2040a6f13a4aSGreg Roach							}
2041a6f13a4aSGreg Roach						} elseif ($id == "fact") {
2042a6f13a4aSGreg Roach							$value = "'" . $this->fact . "'";
2043a6f13a4aSGreg Roach						} elseif ($id == "desc") {
2044a6f13a4aSGreg Roach							$value = "'" . $this->desc . "'";
2045a6f13a4aSGreg Roach						} else {
2046a6f13a4aSGreg Roach							if (preg_match("/\d $id (.+)/", $this->gedrec, $match)) {
2047a6f13a4aSGreg Roach								$value = "'" . str_replace("@", "", trim($match[1])) . "'";
2048a6f13a4aSGreg Roach							}
2049a6f13a4aSGreg Roach						}
2050a6f13a4aSGreg Roach						$condition = preg_replace("/@$id/", $value, $condition);
2051a6f13a4aSGreg Roach					}
2052a6f13a4aSGreg Roach					//-- handle regular expressions
2053a6f13a4aSGreg Roach					if (preg_match("/([A-Z:]+)\s*([^\s]+)\s*(.+)/", $condition, $match)) {
2054a6f13a4aSGreg Roach						$tag  = trim($match[1]);
2055a6f13a4aSGreg Roach						$expr = trim($match[2]);
2056a6f13a4aSGreg Roach						$val  = trim($match[3]);
2057a6f13a4aSGreg Roach						if (preg_match("/\\$(\w+)/", $val, $match)) {
2058d1286247SGreg Roach							$val = $this->vars[$match[1]]['id'];
2059a6f13a4aSGreg Roach							$val = trim($val);
2060a6f13a4aSGreg Roach						}
2061a6f13a4aSGreg Roach						if ($val) {
2062a6f13a4aSGreg Roach							$searchstr = "";
2063a6f13a4aSGreg Roach							$tags      = explode(":", $tag);
2064a6f13a4aSGreg Roach							//-- only limit to a level number if we are specifically looking at a level
2065a6f13a4aSGreg Roach							if (count($tags) > 1) {
2066a6f13a4aSGreg Roach								$level = 1;
2067a6f13a4aSGreg Roach								foreach ($tags as $t) {
2068a6f13a4aSGreg Roach									if (!empty($searchstr)) {
2069a6f13a4aSGreg Roach										$searchstr .= "[^\n]*(\n[2-9][^\n]*)*\n";
2070a6f13a4aSGreg Roach									}
2071a6f13a4aSGreg Roach									//-- search for both EMAIL and _EMAIL... silly double gedcom standard
2072a6f13a4aSGreg Roach									if ($t == "EMAIL" || $t == "_EMAIL") {
2073a6f13a4aSGreg Roach										$t = "_?EMAIL";
2074a6f13a4aSGreg Roach									}
2075a6f13a4aSGreg Roach									$searchstr .= $level . " " . $t;
2076a6f13a4aSGreg Roach									$level++;
2077a6f13a4aSGreg Roach								}
2078a6f13a4aSGreg Roach							} else {
2079a6f13a4aSGreg Roach								if ($tag == "EMAIL" || $tag == "_EMAIL") {
2080a6f13a4aSGreg Roach									$tag = "_?EMAIL";
2081a6f13a4aSGreg Roach								}
2082a6f13a4aSGreg Roach								$t         = $tag;
2083a6f13a4aSGreg Roach								$searchstr = "1 " . $tag;
2084a6f13a4aSGreg Roach							}
2085a6f13a4aSGreg Roach							switch ($expr) {
2086a6f13a4aSGreg Roach								case "CONTAINS":
2087a6f13a4aSGreg Roach									if ($t == "PLAC") {
2088a6f13a4aSGreg Roach										$searchstr .= "[^\n]*[, ]*" . $val;
2089a6f13a4aSGreg Roach									} else {
2090a6f13a4aSGreg Roach										$searchstr .= "[^\n]*" . $val;
2091a6f13a4aSGreg Roach									}
2092a6f13a4aSGreg Roach									$filters[] = $searchstr;
2093a6f13a4aSGreg Roach									break;
2094a6f13a4aSGreg Roach								default:
2095a6f13a4aSGreg Roach									$filters2[] = array("tag" => $tag, "expr" => $expr, "val" => $val);
2096a6f13a4aSGreg Roach									break;
2097a6f13a4aSGreg Roach							}
2098a6f13a4aSGreg Roach						}
2099a6f13a4aSGreg Roach					}
2100a6f13a4aSGreg Roach				}
2101a6f13a4aSGreg Roach			}
2102a6f13a4aSGreg Roach		}
2103a6f13a4aSGreg Roach		//-- apply other filters to the list that could not be added to the search string
2104a6f13a4aSGreg Roach		if ($filters) {
2105a6f13a4aSGreg Roach			foreach ($this->list as $key => $record) {
2106a6f13a4aSGreg Roach				foreach ($filters as $filter) {
2107a6f13a4aSGreg Roach					if (!preg_match("/" . $filter . "/i", $record->privatizeGedcom(Auth::accessLevel($WT_TREE)))) {
2108a6f13a4aSGreg Roach						unset($this->list[$key]);
2109a6f13a4aSGreg Roach						break;
2110a6f13a4aSGreg Roach					}
2111a6f13a4aSGreg Roach				}
2112a6f13a4aSGreg Roach			}
2113a6f13a4aSGreg Roach		}
2114a6f13a4aSGreg Roach		if ($filters2) {
2115a6f13a4aSGreg Roach			$mylist = array();
2116a6f13a4aSGreg Roach			foreach ($this->list as $indi) {
2117a6f13a4aSGreg Roach				$key  = $indi->getXref();
2118a6f13a4aSGreg Roach				$grec = $indi->privatizeGedcom(Auth::accessLevel($WT_TREE));
2119a6f13a4aSGreg Roach				$keep = true;
2120a6f13a4aSGreg Roach				foreach ($filters2 as $filter) {
2121a6f13a4aSGreg Roach					if ($keep) {
2122a6f13a4aSGreg Roach						$tag  = $filter['tag'];
2123a6f13a4aSGreg Roach						$expr = $filter['expr'];
2124a6f13a4aSGreg Roach						$val  = $filter['val'];
2125a6f13a4aSGreg Roach						if ($val == "''") {
2126a6f13a4aSGreg Roach							$val = "";
2127a6f13a4aSGreg Roach						}
2128a6f13a4aSGreg Roach						$tags = explode(":", $tag);
2129a6f13a4aSGreg Roach						$t    = end($tags);
21303d7a8a4cSGreg Roach						$v    = $this->getGedcomValue($tag, 1, $grec);
2131a6f13a4aSGreg Roach						//-- check for EMAIL and _EMAIL (silly double gedcom standard :P)
2132a6f13a4aSGreg Roach						if ($t == "EMAIL" && empty($v)) {
2133a6f13a4aSGreg Roach							$tag  = str_replace("EMAIL", "_EMAIL", $tag);
2134a6f13a4aSGreg Roach							$tags = explode(":", $tag);
2135a6f13a4aSGreg Roach							$t    = end($tags);
21363d7a8a4cSGreg Roach							$v    = Functions::getSubRecord(1, $tag, $grec);
2137a6f13a4aSGreg Roach						}
2138a6f13a4aSGreg Roach
2139a6f13a4aSGreg Roach						switch ($expr) {
2140a6f13a4aSGreg Roach							case "GTE":
2141a6f13a4aSGreg Roach								if ($t == "DATE") {
2142a6f13a4aSGreg Roach									$date1 = new Date($v);
2143a6f13a4aSGreg Roach									$date2 = new Date($val);
2144a6f13a4aSGreg Roach									$keep  = (Date::compare($date1, $date2) >= 0);
2145a6f13a4aSGreg Roach								} elseif ($val >= $v) {
2146a6f13a4aSGreg Roach									$keep = true;
2147a6f13a4aSGreg Roach								}
2148a6f13a4aSGreg Roach								break;
2149a6f13a4aSGreg Roach							case "LTE":
2150a6f13a4aSGreg Roach								if ($t == "DATE") {
2151a6f13a4aSGreg Roach									$date1 = new Date($v);
2152a6f13a4aSGreg Roach									$date2 = new Date($val);
2153a6f13a4aSGreg Roach									$keep  = (Date::compare($date1, $date2) <= 0);
2154a6f13a4aSGreg Roach								} elseif ($val >= $v) {
2155a6f13a4aSGreg Roach									$keep = true;
2156a6f13a4aSGreg Roach								}
2157a6f13a4aSGreg Roach								break;
2158a6f13a4aSGreg Roach							default:
2159a6f13a4aSGreg Roach								if ($v == $val) {
2160a6f13a4aSGreg Roach									$keep = true;
2161a6f13a4aSGreg Roach								} else {
2162a6f13a4aSGreg Roach									$keep = false;
2163a6f13a4aSGreg Roach								}
2164a6f13a4aSGreg Roach								break;
2165a6f13a4aSGreg Roach						}
2166a6f13a4aSGreg Roach					}
2167a6f13a4aSGreg Roach				}
2168a6f13a4aSGreg Roach				if ($keep) {
2169a6f13a4aSGreg Roach					$mylist[$key] = $indi;
2170a6f13a4aSGreg Roach				}
2171a6f13a4aSGreg Roach			}
2172a6f13a4aSGreg Roach			$this->list = $mylist;
2173a6f13a4aSGreg Roach		}
2174a6f13a4aSGreg Roach
2175a6f13a4aSGreg Roach		switch ($sortby) {
2176a6f13a4aSGreg Roach			case 'NAME':
2177a6f13a4aSGreg Roach				uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare');
2178a6f13a4aSGreg Roach				break;
2179a6f13a4aSGreg Roach			case 'CHAN':
2180a6f13a4aSGreg Roach				uasort($this->list, function (GedcomRecord $x, GedcomRecord $y) {
2181a6f13a4aSGreg Roach					return $y->lastChangeTimestamp(true) - $x->lastChangeTimestamp(true);
2182a6f13a4aSGreg Roach				});
2183a6f13a4aSGreg Roach				break;
2184a6f13a4aSGreg Roach			case 'BIRT:DATE':
2185a6f13a4aSGreg Roach				uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate');
2186a6f13a4aSGreg Roach				break;
2187a6f13a4aSGreg Roach			case 'DEAT:DATE':
2188a6f13a4aSGreg Roach				uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate');
2189a6f13a4aSGreg Roach				break;
2190a6f13a4aSGreg Roach			case 'MARR:DATE':
21915d0bc43dSGreg Roach				uasort($this->list, '\Fisharebest\Webtrees\Family::compareMarrDate');
2192a6f13a4aSGreg Roach				break;
2193a6f13a4aSGreg Roach			default:
2194a6f13a4aSGreg Roach				// unsorted or already sorted by SQL
2195a6f13a4aSGreg Roach				break;
2196a6f13a4aSGreg Roach		}
2197a6f13a4aSGreg Roach
2198a6f13a4aSGreg Roach		array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
2199e8e7866bSGreg Roach		$this->repeat_bytes = xml_get_current_line_number($this->parser) + 1;
2200a6f13a4aSGreg Roach	}
2201a6f13a4aSGreg Roach
2202a6f13a4aSGreg Roach	/**
220376692c8bSGreg Roach	 * XML <List>
2204a6f13a4aSGreg Roach	 */
22058edd1043SGreg Roach	private function listEndHandler() {
2206e8e7866bSGreg Roach		global $report;
2207a6f13a4aSGreg Roach
2208a6f13a4aSGreg Roach		$this->process_repeats--;
2209a6f13a4aSGreg Roach		if ($this->process_repeats > 0) {
2210a6f13a4aSGreg Roach			return;
2211a6f13a4aSGreg Roach		}
2212a6f13a4aSGreg Roach
2213a6f13a4aSGreg Roach		// Check if there is any list
2214a6f13a4aSGreg Roach		if (count($this->list) > 0) {
2215a6f13a4aSGreg Roach			$lineoffset = 0;
2216a6f13a4aSGreg Roach			foreach ($this->repeats_stack as $rep) {
2217a6f13a4aSGreg Roach				$lineoffset += $rep[1];
2218a6f13a4aSGreg Roach			}
2219a6f13a4aSGreg Roach			//-- read the xml from the file
2220a6f13a4aSGreg Roach			$lines = file($report);
2221a6f13a4aSGreg Roach			while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<List") === false) && (($lineoffset + $this->repeat_bytes) > 0)) {
2222a6f13a4aSGreg Roach				$lineoffset--;
2223a6f13a4aSGreg Roach			}
2224a6f13a4aSGreg Roach			$lineoffset++;
2225a6f13a4aSGreg Roach			$reportxml = "<tempdoc>\n";
2226a6f13a4aSGreg Roach			$line_nr   = $lineoffset + $this->repeat_bytes;
2227a6f13a4aSGreg Roach			// List Level counter
2228a6f13a4aSGreg Roach			$count = 1;
2229a6f13a4aSGreg Roach			while (0 < $count) {
2230a6f13a4aSGreg Roach				if (strpos($lines[$line_nr], "<List") !== false) {
2231a6f13a4aSGreg Roach					$count++;
2232a6f13a4aSGreg Roach				} elseif (strpos($lines[$line_nr], "</List") !== false) {
2233a6f13a4aSGreg Roach					$count--;
2234a6f13a4aSGreg Roach				}
2235a6f13a4aSGreg Roach				if (0 < $count) {
2236a6f13a4aSGreg Roach					$reportxml .= $lines[$line_nr];
2237a6f13a4aSGreg Roach				}
2238a6f13a4aSGreg Roach				$line_nr++;
2239a6f13a4aSGreg Roach			}
2240a6f13a4aSGreg Roach			// No need to drag this
2241a6f13a4aSGreg Roach			unset($lines);
2242a6f13a4aSGreg Roach			$reportxml .= "</tempdoc>";
2243a6f13a4aSGreg Roach			// Save original values
2244e8e7866bSGreg Roach			array_push($this->parser_stack, $this->parser);
2245a6f13a4aSGreg Roach			$oldgedrec = $this->gedrec;
2246a6f13a4aSGreg Roach
2247a6f13a4aSGreg Roach			$this->list_total   = count($this->list);
2248a6f13a4aSGreg Roach			$this->list_private = 0;
2249a6f13a4aSGreg Roach			foreach ($this->list as $record) {
2250a6f13a4aSGreg Roach				if ($record->canShow()) {
2251a6f13a4aSGreg Roach					$this->gedrec = $record->privatizeGedcom(Auth::accessLevel($record->getTree()));
2252a6f13a4aSGreg Roach					//-- start the sax parser
2253a6f13a4aSGreg Roach					$repeat_parser = xml_parser_create();
2254e8e7866bSGreg Roach					$this->parser  = $repeat_parser;
2255a6f13a4aSGreg Roach					xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
2256a6f13a4aSGreg Roach					xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
2257a6f13a4aSGreg Roach					xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
2258a6f13a4aSGreg Roach					if (!xml_parse($repeat_parser, $reportxml, true)) {
2259a6f13a4aSGreg Roach						throw new \DomainException(sprintf(
2260a6f13a4aSGreg Roach							'ListEHandler XML error: %s at line %d',
2261a6f13a4aSGreg Roach							xml_error_string(xml_get_error_code($repeat_parser)),
2262a6f13a4aSGreg Roach							xml_get_current_line_number($repeat_parser)
2263a6f13a4aSGreg Roach						));
2264a6f13a4aSGreg Roach					}
2265a6f13a4aSGreg Roach					xml_parser_free($repeat_parser);
2266a6f13a4aSGreg Roach				} else {
2267a6f13a4aSGreg Roach					$this->list_private++;
2268a6f13a4aSGreg Roach				}
2269a6f13a4aSGreg Roach			}
2270a6f13a4aSGreg Roach			$this->list   = array();
2271e8e7866bSGreg Roach			$this->parser = array_pop($this->parser_stack);
2272a6f13a4aSGreg Roach			$this->gedrec = $oldgedrec;
2273a6f13a4aSGreg Roach		}
2274a6f13a4aSGreg Roach		list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
2275a6f13a4aSGreg Roach	}
2276a6f13a4aSGreg Roach
2277a6f13a4aSGreg Roach	/**
2278a6f13a4aSGreg Roach	 * XML <ListTotal> element handler
2279a6f13a4aSGreg Roach	 *
2280a6f13a4aSGreg Roach	 * Prints the total number of records in a list
2281a6f13a4aSGreg Roach	 * The total number is collected from
2282a6f13a4aSGreg Roach	 * List and Relatives
2283a6f13a4aSGreg Roach	 */
22848edd1043SGreg Roach	private function listTotalStartHandler() {
2285a6f13a4aSGreg Roach		if ($this->list_private == 0) {
2286a6f13a4aSGreg Roach			$this->current_element->addText($this->list_total);
2287a6f13a4aSGreg Roach		} else {
2288a6f13a4aSGreg Roach			$this->current_element->addText(($this->list_total - $this->list_private) . " / " . $this->list_total);
2289a6f13a4aSGreg Roach		}
2290a6f13a4aSGreg Roach	}
2291a6f13a4aSGreg Roach
2292a6f13a4aSGreg Roach	/**
229376692c8bSGreg Roach	 * XML <Relatives>
229476692c8bSGreg Roach	 *
2295a6f13a4aSGreg Roach	 * @param array $attrs an array of key value pairs for the attributes
2296a6f13a4aSGreg Roach	 */
22978edd1043SGreg Roach	private function relativesStartHandler($attrs) {
2298d1286247SGreg Roach		global $WT_TREE;
2299a6f13a4aSGreg Roach
2300a6f13a4aSGreg Roach		$this->process_repeats++;
2301a6f13a4aSGreg Roach		if ($this->process_repeats > 1) {
2302a6f13a4aSGreg Roach			return;
2303a6f13a4aSGreg Roach		}
2304a6f13a4aSGreg Roach
2305a6f13a4aSGreg Roach		$sortby = "NAME";
2306a6f13a4aSGreg Roach		if (isset($attrs['sortby'])) {
2307a6f13a4aSGreg Roach			$sortby = $attrs['sortby'];
2308a6f13a4aSGreg Roach		}
2309a6f13a4aSGreg Roach		$match = array();
2310a6f13a4aSGreg Roach		if (preg_match("/\\$(\w+)/", $sortby, $match)) {
2311d1286247SGreg Roach			$sortby = $this->vars[$match[1]]['id'];
2312a6f13a4aSGreg Roach			$sortby = trim($sortby);
2313a6f13a4aSGreg Roach		}
2314a6f13a4aSGreg Roach
2315a6f13a4aSGreg Roach		$maxgen = -1;
2316a6f13a4aSGreg Roach		if (isset($attrs['maxgen'])) {
2317a6f13a4aSGreg Roach			$maxgen = $attrs['maxgen'];
2318a6f13a4aSGreg Roach		}
2319a6f13a4aSGreg Roach		if ($maxgen == "*") {
2320a6f13a4aSGreg Roach			$maxgen = -1;
2321a6f13a4aSGreg Roach		}
2322a6f13a4aSGreg Roach
2323a6f13a4aSGreg Roach		$group = "child-family";
2324a6f13a4aSGreg Roach		if (isset($attrs['group'])) {
2325a6f13a4aSGreg Roach			$group = $attrs['group'];
2326a6f13a4aSGreg Roach		}
2327a6f13a4aSGreg Roach		if (preg_match("/\\$(\w+)/", $group, $match)) {
2328d1286247SGreg Roach			$group = $this->vars[$match[1]]['id'];
2329a6f13a4aSGreg Roach			$group = trim($group);
2330a6f13a4aSGreg Roach		}
2331a6f13a4aSGreg Roach
2332a6f13a4aSGreg Roach		$id = "";
2333a6f13a4aSGreg Roach		if (isset($attrs['id'])) {
2334a6f13a4aSGreg Roach			$id = $attrs['id'];
2335a6f13a4aSGreg Roach		}
2336a6f13a4aSGreg Roach		if (preg_match("/\\$(\w+)/", $id, $match)) {
2337d1286247SGreg Roach			$id = $this->vars[$match[1]]['id'];
2338a6f13a4aSGreg Roach			$id = trim($id);
2339a6f13a4aSGreg Roach		}
2340a6f13a4aSGreg Roach
2341a6f13a4aSGreg Roach		$this->list = array();
2342a6f13a4aSGreg Roach		$person     = Individual::getInstance($id, $WT_TREE);
2343a6f13a4aSGreg Roach		if (!empty($person)) {
2344a6f13a4aSGreg Roach			$this->list[$id] = $person;
2345a6f13a4aSGreg Roach			switch ($group) {
2346a6f13a4aSGreg Roach				case "child-family":
2347a6f13a4aSGreg Roach					foreach ($person->getChildFamilies() as $family) {
2348a6f13a4aSGreg Roach						$husband = $family->getHusband();
2349a6f13a4aSGreg Roach						$wife    = $family->getWife();
2350a6f13a4aSGreg Roach						if (!empty($husband)) {
2351a6f13a4aSGreg Roach							$this->list[$husband->getXref()] = $husband;
2352a6f13a4aSGreg Roach						}
2353a6f13a4aSGreg Roach						if (!empty($wife)) {
2354a6f13a4aSGreg Roach							$this->list[$wife->getXref()] = $wife;
2355a6f13a4aSGreg Roach						}
2356a6f13a4aSGreg Roach						$children = $family->getChildren();
2357a6f13a4aSGreg Roach						foreach ($children as $child) {
2358a6f13a4aSGreg Roach							if (!empty($child)) {
2359a6f13a4aSGreg Roach								$this->list[$child->getXref()] = $child;
2360a6f13a4aSGreg Roach							}
2361a6f13a4aSGreg Roach						}
2362a6f13a4aSGreg Roach					}
2363a6f13a4aSGreg Roach					break;
2364a6f13a4aSGreg Roach				case "spouse-family":
2365a6f13a4aSGreg Roach					foreach ($person->getSpouseFamilies() as $family) {
2366a6f13a4aSGreg Roach						$husband = $family->getHusband();
2367a6f13a4aSGreg Roach						$wife    = $family->getWife();
2368a6f13a4aSGreg Roach						if (!empty($husband)) {
2369a6f13a4aSGreg Roach							$this->list[$husband->getXref()] = $husband;
2370a6f13a4aSGreg Roach						}
2371a6f13a4aSGreg Roach						if (!empty($wife)) {
2372a6f13a4aSGreg Roach							$this->list[$wife->getXref()] = $wife;
2373a6f13a4aSGreg Roach						}
2374a6f13a4aSGreg Roach						$children = $family->getChildren();
2375a6f13a4aSGreg Roach						foreach ($children as $child) {
2376a6f13a4aSGreg Roach							if (!empty($child)) {
2377a6f13a4aSGreg Roach								$this->list[$child->getXref()] = $child;
2378a6f13a4aSGreg Roach							}
2379a6f13a4aSGreg Roach						}
2380a6f13a4aSGreg Roach					}
2381a6f13a4aSGreg Roach					break;
2382a6f13a4aSGreg Roach				case "direct-ancestors":
23833d7a8a4cSGreg Roach					$this->addAncestors($this->list, $id, false, $maxgen);
2384a6f13a4aSGreg Roach					break;
2385a6f13a4aSGreg Roach				case "ancestors":
23863d7a8a4cSGreg Roach					$this->addAncestors($this->list, $id, true, $maxgen);
2387a6f13a4aSGreg Roach					break;
2388a6f13a4aSGreg Roach				case "descendants":
2389a6f13a4aSGreg Roach					$this->list[$id]->generation = 1;
23903d7a8a4cSGreg Roach					$this->addDescendancy($this->list, $id, false, $maxgen);
2391a6f13a4aSGreg Roach					break;
2392a6f13a4aSGreg Roach				case "all":
23933d7a8a4cSGreg Roach					$this->addAncestors($this->list, $id, true, $maxgen);
23943d7a8a4cSGreg Roach					$this->addDescendancy($this->list, $id, true, $maxgen);
2395a6f13a4aSGreg Roach					break;
2396a6f13a4aSGreg Roach			}
2397a6f13a4aSGreg Roach		}
2398a6f13a4aSGreg Roach
2399a6f13a4aSGreg Roach		switch ($sortby) {
2400a6f13a4aSGreg Roach			case 'NAME':
2401a6f13a4aSGreg Roach				uasort($this->list, '\Fisharebest\Webtrees\GedcomRecord::compare');
2402a6f13a4aSGreg Roach				break;
2403a6f13a4aSGreg Roach			case 'BIRT:DATE':
2404a6f13a4aSGreg Roach				uasort($this->list, '\Fisharebest\Webtrees\Individual::compareBirthDate');
2405a6f13a4aSGreg Roach				break;
2406a6f13a4aSGreg Roach			case 'DEAT:DATE':
2407a6f13a4aSGreg Roach				uasort($this->list, '\Fisharebest\Webtrees\Individual::compareDeathDate');
2408a6f13a4aSGreg Roach				break;
2409a6f13a4aSGreg Roach			case 'generation':
2410a6f13a4aSGreg Roach				$newarray = array();
2411a6f13a4aSGreg Roach				reset($this->list);
2412a6f13a4aSGreg Roach				$genCounter = 1;
2413a6f13a4aSGreg Roach				while (count($newarray) < count($this->list)) {
2414a6f13a4aSGreg Roach					foreach ($this->list as $key => $value) {
2415a6f13a4aSGreg Roach						$this->generation = $value->generation;
2416a6f13a4aSGreg Roach						if ($this->generation == $genCounter) {
2417a6f13a4aSGreg Roach							$newarray[$key]             = new \stdClass;
2418a6f13a4aSGreg Roach							$newarray[$key]->generation = $this->generation;
2419a6f13a4aSGreg Roach						}
2420a6f13a4aSGreg Roach					}
2421a6f13a4aSGreg Roach					$genCounter++;
2422a6f13a4aSGreg Roach				}
2423a6f13a4aSGreg Roach				$this->list = $newarray;
2424a6f13a4aSGreg Roach				break;
2425a6f13a4aSGreg Roach			default:
2426a6f13a4aSGreg Roach				// unsorted
2427a6f13a4aSGreg Roach				break;
2428a6f13a4aSGreg Roach		}
2429a6f13a4aSGreg Roach		array_push($this->repeats_stack, array($this->repeats, $this->repeat_bytes));
2430e8e7866bSGreg Roach		$this->repeat_bytes = xml_get_current_line_number($this->parser) + 1;
2431a6f13a4aSGreg Roach	}
2432a6f13a4aSGreg Roach
2433a6f13a4aSGreg Roach	/**
243476692c8bSGreg Roach	 * XML </ Relatives>
2435a6f13a4aSGreg Roach	 */
24368edd1043SGreg Roach	private function relativesEndHandler() {
2437e8e7866bSGreg Roach		global $report, $WT_TREE;
2438a6f13a4aSGreg Roach
2439a6f13a4aSGreg Roach		$this->process_repeats--;
2440a6f13a4aSGreg Roach		if ($this->process_repeats > 0) {
2441a6f13a4aSGreg Roach			return;
2442a6f13a4aSGreg Roach		}
2443a6f13a4aSGreg Roach
2444a6f13a4aSGreg Roach		// Check if there is any relatives
2445a6f13a4aSGreg Roach		if (count($this->list) > 0) {
2446a6f13a4aSGreg Roach			$lineoffset = 0;
2447a6f13a4aSGreg Roach			foreach ($this->repeats_stack as $rep) {
2448a6f13a4aSGreg Roach				$lineoffset += $rep[1];
2449a6f13a4aSGreg Roach			}
2450a6f13a4aSGreg Roach			//-- read the xml from the file
2451a6f13a4aSGreg Roach			$lines = file($report);
2452a6f13a4aSGreg Roach			while ((strpos($lines[$lineoffset + $this->repeat_bytes], "<Relatives") === false) && (($lineoffset + $this->repeat_bytes) > 0)) {
2453a6f13a4aSGreg Roach				$lineoffset--;
2454a6f13a4aSGreg Roach			}
2455a6f13a4aSGreg Roach			$lineoffset++;
2456a6f13a4aSGreg Roach			$reportxml = "<tempdoc>\n";
2457a6f13a4aSGreg Roach			$line_nr   = $lineoffset + $this->repeat_bytes;
2458a6f13a4aSGreg Roach			// Relatives Level counter
2459a6f13a4aSGreg Roach			$count = 1;
2460a6f13a4aSGreg Roach			while (0 < $count) {
2461a6f13a4aSGreg Roach				if (strpos($lines[$line_nr], "<Relatives") !== false) {
2462a6f13a4aSGreg Roach					$count++;
2463a6f13a4aSGreg Roach				} elseif (strpos($lines[$line_nr], "</Relatives") !== false) {
2464a6f13a4aSGreg Roach					$count--;
2465a6f13a4aSGreg Roach				}
2466a6f13a4aSGreg Roach				if (0 < $count) {
2467a6f13a4aSGreg Roach					$reportxml .= $lines[$line_nr];
2468a6f13a4aSGreg Roach				}
2469a6f13a4aSGreg Roach				$line_nr++;
2470a6f13a4aSGreg Roach			}
2471a6f13a4aSGreg Roach			// No need to drag this
2472a6f13a4aSGreg Roach			unset($lines);
2473a6f13a4aSGreg Roach			$reportxml .= "</tempdoc>\n";
2474a6f13a4aSGreg Roach			// Save original values
2475e8e7866bSGreg Roach			array_push($this->parser_stack, $this->parser);
2476a6f13a4aSGreg Roach			$oldgedrec = $this->gedrec;
2477a6f13a4aSGreg Roach
2478a6f13a4aSGreg Roach			$this->list_total   = count($this->list);
2479a6f13a4aSGreg Roach			$this->list_private = 0;
2480a6f13a4aSGreg Roach			foreach ($this->list as $key => $value) {
2481a6f13a4aSGreg Roach				if (isset($value->generation)) {
2482a6f13a4aSGreg Roach					$this->generation = $value->generation;
2483a6f13a4aSGreg Roach				}
2484a6f13a4aSGreg Roach				$tmp          = GedcomRecord::getInstance($key, $WT_TREE);
2485a6f13a4aSGreg Roach				$this->gedrec = $tmp->privatizeGedcom(Auth::accessLevel($WT_TREE));
2486a6f13a4aSGreg Roach
2487a6f13a4aSGreg Roach				$repeat_parser = xml_parser_create();
2488e8e7866bSGreg Roach				$this->parser  = $repeat_parser;
2489a6f13a4aSGreg Roach				xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
2490a6f13a4aSGreg Roach				xml_set_element_handler($repeat_parser, array($this, 'startElement'), array($this, 'endElement'));
2491a6f13a4aSGreg Roach				xml_set_character_data_handler($repeat_parser, array($this, 'characterData'));
2492a6f13a4aSGreg Roach
2493a6f13a4aSGreg Roach				if (!xml_parse($repeat_parser, $reportxml, true)) {
2494a6f13a4aSGreg Roach					throw new \DomainException(sprintf("RelativesEHandler XML error: %s at line %d", xml_error_string(xml_get_error_code($repeat_parser)), xml_get_current_line_number($repeat_parser)));
2495a6f13a4aSGreg Roach				}
2496a6f13a4aSGreg Roach				xml_parser_free($repeat_parser);
2497a6f13a4aSGreg Roach			}
2498a6f13a4aSGreg Roach			// Clean up the list array
2499a6f13a4aSGreg Roach			$this->list   = array();
2500e8e7866bSGreg Roach			$this->parser = array_pop($this->parser_stack);
2501a6f13a4aSGreg Roach			$this->gedrec = $oldgedrec;
2502a6f13a4aSGreg Roach		}
2503a6f13a4aSGreg Roach		list($this->repeats, $this->repeat_bytes) = array_pop($this->repeats_stack);
2504a6f13a4aSGreg Roach	}
2505a6f13a4aSGreg Roach
2506a6f13a4aSGreg Roach	/**
2507a6f13a4aSGreg Roach	 * XML <Generation /> element handler
2508a6f13a4aSGreg Roach	 *
2509a6f13a4aSGreg Roach	 * Prints the number of generations
2510a6f13a4aSGreg Roach	 */
25118edd1043SGreg Roach	private function generationStartHandler() {
2512a6f13a4aSGreg Roach		$this->current_element->addText($this->generation);
2513a6f13a4aSGreg Roach	}
2514a6f13a4aSGreg Roach
2515a6f13a4aSGreg Roach	/**
2516a6f13a4aSGreg Roach	 * XML <NewPage /> element handler
2517a6f13a4aSGreg Roach	 *
2518a6f13a4aSGreg Roach	 * Has to be placed in an element (header, pageheader, body or footer)
2519a6f13a4aSGreg Roach	 */
25208edd1043SGreg Roach	private function newPageStartHandler() {
2521a6f13a4aSGreg Roach		$temp = "addpage";
2522e8e7866bSGreg Roach		$this->wt_report->addElement($temp);
2523a6f13a4aSGreg Roach	}
2524a6f13a4aSGreg Roach
2525a6f13a4aSGreg Roach	/**
252676692c8bSGreg Roach	 * XML <html>
252776692c8bSGreg Roach	 *
2528a6f13a4aSGreg Roach	 * @param string  $tag   HTML tag name
252976692c8bSGreg Roach	 * @param array[] $attrs an array of key value pairs for the attributes
2530a6f13a4aSGreg Roach	 */
25318edd1043SGreg Roach	private function htmlStartHandler($tag, $attrs) {
2532a6f13a4aSGreg Roach		if ($tag === "tempdoc") {
2533a6f13a4aSGreg Roach			return;
2534a6f13a4aSGreg Roach		}
2535e8e7866bSGreg Roach		array_push($this->wt_report_stack, $this->wt_report);
2536e8e7866bSGreg Roach		$this->wt_report       = $this->report_root->createHTML($tag, $attrs);
2537e8e7866bSGreg Roach		$this->current_element = $this->wt_report;
2538a6f13a4aSGreg Roach
2539a6f13a4aSGreg Roach		array_push($this->print_data_stack, $this->print_data);
2540a6f13a4aSGreg Roach		$this->print_data = true;
2541a6f13a4aSGreg Roach	}
2542a6f13a4aSGreg Roach
2543a6f13a4aSGreg Roach	/**
254476692c8bSGreg Roach	 * XML </html>
254576692c8bSGreg Roach	 *
2546a6f13a4aSGreg Roach	 * @param string $tag
2547a6f13a4aSGreg Roach	 */
25488edd1043SGreg Roach	private function htmlEndHandler($tag) {
2549a6f13a4aSGreg Roach		if ($tag === "tempdoc") {
2550a6f13a4aSGreg Roach			return;
2551a6f13a4aSGreg Roach		}
2552a6f13a4aSGreg Roach
2553a6f13a4aSGreg Roach		$this->print_data      = array_pop($this->print_data_stack);
2554e8e7866bSGreg Roach		$this->current_element = $this->wt_report;
2555e8e7866bSGreg Roach		$this->wt_report       = array_pop($this->wt_report_stack);
2556e8e7866bSGreg Roach		if (!is_null($this->wt_report)) {
2557e8e7866bSGreg Roach			$this->wt_report->addElement($this->current_element);
2558a6f13a4aSGreg Roach		} else {
2559e8e7866bSGreg Roach			$this->wt_report = $this->current_element;
2560a6f13a4aSGreg Roach		}
2561a6f13a4aSGreg Roach	}
2562a6f13a4aSGreg Roach
2563a6f13a4aSGreg Roach	/**
2564a6f13a4aSGreg Roach	 * Handle <Input>
2565a6f13a4aSGreg Roach	 */
25668edd1043SGreg Roach	private function inputStartHandler() {
2567a6f13a4aSGreg Roach		// Dummy function, to prevent the default HtmlStartHandler() being called
2568a6f13a4aSGreg Roach	}
2569a6f13a4aSGreg Roach
2570a6f13a4aSGreg Roach	/**
2571a6f13a4aSGreg Roach	 * Handle </Input>
2572a6f13a4aSGreg Roach	 */
25738edd1043SGreg Roach	private function inputEndHandler() {
2574a6f13a4aSGreg Roach		// Dummy function, to prevent the default HtmlEndHandler() being called
2575a6f13a4aSGreg Roach	}
2576a6f13a4aSGreg Roach
2577a6f13a4aSGreg Roach	/**
2578a6f13a4aSGreg Roach	 * Handle <Report>
2579a6f13a4aSGreg Roach	 */
25808edd1043SGreg Roach	private function reportStartHandler() {
2581a6f13a4aSGreg Roach		// Dummy function, to prevent the default HtmlStartHandler() being called
2582a6f13a4aSGreg Roach	}
2583a6f13a4aSGreg Roach
2584a6f13a4aSGreg Roach	/**
2585a6f13a4aSGreg Roach	 * Handle </Report>
2586a6f13a4aSGreg Roach	 */
25878edd1043SGreg Roach	private function reportEndHandler() {
2588a6f13a4aSGreg Roach		// Dummy function, to prevent the default HtmlEndHandler() being called
2589a6f13a4aSGreg Roach	}
2590a6f13a4aSGreg Roach
2591a6f13a4aSGreg Roach	/**
259276692c8bSGreg Roach	 * XML </titleEndHandler>
2593a6f13a4aSGreg Roach	 */
25948edd1043SGreg Roach	private function titleEndHandler() {
25952836aa05SGreg Roach		$this->report_root->addTitle($this->text);
2596a6f13a4aSGreg Roach	}
2597a6f13a4aSGreg Roach
2598a6f13a4aSGreg Roach	/**
259976692c8bSGreg Roach	 * XML </descriptionEndHandler>
2600a6f13a4aSGreg Roach	 */
26018edd1043SGreg Roach	private function descriptionEndHandler() {
26022836aa05SGreg Roach		$this->report_root->addDescription($this->text);
2603a6f13a4aSGreg Roach	}
2604729ce104SGreg Roach
2605729ce104SGreg Roach	/**
260676692c8bSGreg Roach	 * Create a list of all descendants.
260776692c8bSGreg Roach	 *
2608729ce104SGreg Roach	 * @param string[] $list
2609729ce104SGreg Roach	 * @param string   $pid
2610729ce104SGreg Roach	 * @param bool  $parents
2611729ce104SGreg Roach	 * @param int  $generations
2612729ce104SGreg Roach	 */
261382759250SGreg Roach	private function addDescendancy(&$list, $pid, $parents = false, $generations = -1) {
2614729ce104SGreg Roach		global $WT_TREE;
2615729ce104SGreg Roach
2616729ce104SGreg Roach		$person = Individual::getInstance($pid, $WT_TREE);
2617729ce104SGreg Roach		if ($person === null) {
2618729ce104SGreg Roach			return;
2619729ce104SGreg Roach		}
2620729ce104SGreg Roach		if (!isset($list[$pid])) {
2621729ce104SGreg Roach			$list[$pid] = $person;
2622729ce104SGreg Roach		}
2623729ce104SGreg Roach		if (!isset($list[$pid]->generation)) {
2624729ce104SGreg Roach			$list[$pid]->generation = 0;
2625729ce104SGreg Roach		}
2626729ce104SGreg Roach		foreach ($person->getSpouseFamilies() as $family) {
2627729ce104SGreg Roach			if ($parents) {
2628729ce104SGreg Roach				$husband = $family->getHusband();
2629729ce104SGreg Roach				$wife    = $family->getWife();
2630729ce104SGreg Roach				if ($husband) {
2631729ce104SGreg Roach					$list[$husband->getXref()] = $husband;
2632729ce104SGreg Roach					if (isset($list[$pid]->generation)) {
2633729ce104SGreg Roach						$list[$husband->getXref()]->generation = $list[$pid]->generation - 1;
2634729ce104SGreg Roach					} else {
2635729ce104SGreg Roach						$list[$husband->getXref()]->generation = 1;
2636729ce104SGreg Roach					}
2637729ce104SGreg Roach				}
2638729ce104SGreg Roach				if ($wife) {
2639729ce104SGreg Roach					$list[$wife->getXref()] = $wife;
2640729ce104SGreg Roach					if (isset($list[$pid]->generation)) {
2641729ce104SGreg Roach						$list[$wife->getXref()]->generation = $list[$pid]->generation - 1;
2642729ce104SGreg Roach					} else {
2643729ce104SGreg Roach						$list[$wife->getXref()]->generation = 1;
2644729ce104SGreg Roach					}
2645729ce104SGreg Roach				}
2646729ce104SGreg Roach			}
2647729ce104SGreg Roach			$children = $family->getChildren();
2648729ce104SGreg Roach			foreach ($children as $child) {
2649729ce104SGreg Roach				if ($child) {
2650729ce104SGreg Roach					$list[$child->getXref()] = $child;
2651729ce104SGreg Roach					if (isset($list[$pid]->generation)) {
2652729ce104SGreg Roach						$list[$child->getXref()]->generation = $list[$pid]->generation + 1;
2653729ce104SGreg Roach					} else {
2654729ce104SGreg Roach						$list[$child->getXref()]->generation = 2;
2655729ce104SGreg Roach					}
2656729ce104SGreg Roach				}
2657729ce104SGreg Roach			}
2658729ce104SGreg Roach			if ($generations == -1 || $list[$pid]->generation + 1 < $generations) {
2659729ce104SGreg Roach				foreach ($children as $child) {
26603d7a8a4cSGreg Roach					$this->addDescendancy($list, $child->getXref(), $parents, $generations); // recurse on the childs family
2661729ce104SGreg Roach				}
2662729ce104SGreg Roach			}
2663729ce104SGreg Roach		}
2664729ce104SGreg Roach	}
2665729ce104SGreg Roach
2666729ce104SGreg Roach	/**
266776692c8bSGreg Roach	 * Create a list of all ancestors.
266876692c8bSGreg Roach	 *
2669729ce104SGreg Roach	 * @param string[] $list
2670729ce104SGreg Roach	 * @param string   $pid
2671729ce104SGreg Roach	 * @param bool  $children
2672729ce104SGreg Roach	 * @param int  $generations
2673729ce104SGreg Roach	 */
267482759250SGreg Roach	private function addAncestors(&$list, $pid, $children = false, $generations = -1) {
2675729ce104SGreg Roach		global $WT_TREE;
2676729ce104SGreg Roach
2677729ce104SGreg Roach		$genlist                = array($pid);
2678729ce104SGreg Roach		$list[$pid]->generation = 1;
2679729ce104SGreg Roach		while (count($genlist) > 0) {
2680729ce104SGreg Roach			$id = array_shift($genlist);
2681729ce104SGreg Roach			if (strpos($id, 'empty') === 0) {
2682729ce104SGreg Roach				continue; // id can be something like “empty7”
2683729ce104SGreg Roach			}
2684729ce104SGreg Roach			$person = Individual::getInstance($id, $WT_TREE);
2685729ce104SGreg Roach			foreach ($person->getChildFamilies() as $family) {
2686729ce104SGreg Roach				$husband = $family->getHusband();
2687729ce104SGreg Roach				$wife    = $family->getWife();
2688729ce104SGreg Roach				if ($husband) {
2689729ce104SGreg Roach					$list[$husband->getXref()]             = $husband;
2690729ce104SGreg Roach					$list[$husband->getXref()]->generation = $list[$id]->generation + 1;
2691729ce104SGreg Roach				}
2692729ce104SGreg Roach				if ($wife) {
2693729ce104SGreg Roach					$list[$wife->getXref()]             = $wife;
2694729ce104SGreg Roach					$list[$wife->getXref()]->generation = $list[$id]->generation + 1;
2695729ce104SGreg Roach				}
2696729ce104SGreg Roach				if ($generations == -1 || $list[$id]->generation + 1 < $generations) {
2697729ce104SGreg Roach					if ($husband) {
2698729ce104SGreg Roach						array_push($genlist, $husband->getXref());
2699729ce104SGreg Roach					}
2700729ce104SGreg Roach					if ($wife) {
2701729ce104SGreg Roach						array_push($genlist, $wife->getXref());
2702729ce104SGreg Roach					}
2703729ce104SGreg Roach				}
2704729ce104SGreg Roach				if ($children) {
2705729ce104SGreg Roach					foreach ($family->getChildren() as $child) {
2706729ce104SGreg Roach						$list[$child->getXref()] = $child;
2707729ce104SGreg Roach						if (isset($list[$id]->generation)) {
2708729ce104SGreg Roach							$list[$child->getXref()]->generation = $list[$id]->generation;
2709729ce104SGreg Roach						} else {
2710729ce104SGreg Roach							$list[$child->getXref()]->generation = 1;
2711729ce104SGreg Roach						}
2712729ce104SGreg Roach					}
2713729ce104SGreg Roach				}
2714729ce104SGreg Roach			}
2715729ce104SGreg Roach		}
2716729ce104SGreg Roach	}
2717729ce104SGreg Roach
2718729ce104SGreg Roach	/**
2719729ce104SGreg Roach	 * get gedcom tag value
2720729ce104SGreg Roach	 *
2721729ce104SGreg Roach	 * @param string  $tag    The tag to find, use : to delineate subtags
2722729ce104SGreg Roach	 * @param int $level  The gedcom line level of the first tag to find, setting level to 0 will cause it to use 1+ the level of the incoming record
2723729ce104SGreg Roach	 * @param string  $gedrec The gedcom record to get the value from
2724729ce104SGreg Roach	 *
2725729ce104SGreg Roach	 * @return string the value of a gedcom tag from the given gedcom record
2726729ce104SGreg Roach	 */
272782759250SGreg Roach	private function getGedcomValue($tag, $level, $gedrec) {
2728729ce104SGreg Roach		global $WT_TREE;
2729729ce104SGreg Roach
2730729ce104SGreg Roach		if (empty($gedrec)) {
2731729ce104SGreg Roach			return '';
2732729ce104SGreg Roach		}
2733729ce104SGreg Roach		$tags      = explode(':', $tag);
2734729ce104SGreg Roach		$origlevel = $level;
2735729ce104SGreg Roach		if ($level == 0) {
2736729ce104SGreg Roach			$level = $gedrec{0} + 1;
2737729ce104SGreg Roach		}
2738729ce104SGreg Roach
2739729ce104SGreg Roach		$subrec = $gedrec;
2740729ce104SGreg Roach		foreach ($tags as $t) {
2741729ce104SGreg Roach			$lastsubrec = $subrec;
27423d7a8a4cSGreg Roach			$subrec     = Functions::getSubRecord($level, "$level $t", $subrec);
2743729ce104SGreg Roach			if (empty($subrec) && $origlevel == 0) {
2744729ce104SGreg Roach				$level--;
27453d7a8a4cSGreg Roach				$subrec = Functions::getSubRecord($level, "$level $t", $lastsubrec);
2746729ce104SGreg Roach			}
2747729ce104SGreg Roach			if (empty($subrec)) {
2748729ce104SGreg Roach				if ($t == "TITL") {
27493d7a8a4cSGreg Roach					$subrec = Functions::getSubRecord($level, "$level ABBR", $lastsubrec);
2750729ce104SGreg Roach					if (!empty($subrec)) {
2751729ce104SGreg Roach						$t = "ABBR";
2752729ce104SGreg Roach					}
2753729ce104SGreg Roach				}
2754729ce104SGreg Roach				if (empty($subrec)) {
2755729ce104SGreg Roach					if ($level > 0) {
2756729ce104SGreg Roach						$level--;
2757729ce104SGreg Roach					}
27583d7a8a4cSGreg Roach					$subrec = Functions::getSubRecord($level, "@ $t", $gedrec);
2759729ce104SGreg Roach					if (empty($subrec)) {
2760729ce104SGreg Roach						return '';
2761729ce104SGreg Roach					}
2762729ce104SGreg Roach				}
2763729ce104SGreg Roach			}
2764729ce104SGreg Roach			$level++;
2765729ce104SGreg Roach		}
2766729ce104SGreg Roach		$level--;
2767729ce104SGreg Roach		$ct = preg_match("/$level $t(.*)/", $subrec, $match);
2768729ce104SGreg Roach		if ($ct == 0) {
2769729ce104SGreg Roach			$ct = preg_match("/$level @.+@ (.+)/", $subrec, $match);
2770729ce104SGreg Roach		}
2771729ce104SGreg Roach		if ($ct == 0) {
2772729ce104SGreg Roach			$ct = preg_match("/@ $t (.+)/", $subrec, $match);
2773729ce104SGreg Roach		}
2774729ce104SGreg Roach		if ($ct > 0) {
2775729ce104SGreg Roach			$value = trim($match[1]);
2776729ce104SGreg Roach			if ($t == 'NOTE' && preg_match('/^@(.+)@$/', $value, $match)) {
2777729ce104SGreg Roach				$note = Note::getInstance($match[1], $WT_TREE);
2778729ce104SGreg Roach				if ($note) {
2779729ce104SGreg Roach					$value = $note->getNote();
2780729ce104SGreg Roach				} else {
2781729ce104SGreg Roach					//-- set the value to the id without the @
2782729ce104SGreg Roach					$value = $match[1];
2783729ce104SGreg Roach				}
2784729ce104SGreg Roach			}
2785729ce104SGreg Roach			if ($level != 0 || $t != "NOTE") {
27863d7a8a4cSGreg Roach				$value .= Functions::getCont($level + 1, $subrec);
2787729ce104SGreg Roach			}
2788729ce104SGreg Roach
2789729ce104SGreg Roach			return $value;
2790729ce104SGreg Roach		}
2791729ce104SGreg Roach
2792729ce104SGreg Roach		return "";
2793729ce104SGreg Roach	}
2794d1286247SGreg Roach
2795d1286247SGreg Roach	/**
2796d1286247SGreg Roach	 * Replace variable identifiers with their values.
2797d1286247SGreg Roach	 *
2798d1286247SGreg Roach	 * @param string $expression An expression such as "$foo == 123"
279982759250SGreg Roach	 * @param bool   $quote      Whether to add quotation marks
2800d1286247SGreg Roach	 *
2801d1286247SGreg Roach	 * @return string
2802d1286247SGreg Roach	 */
280382759250SGreg Roach	private function substituteVars($expression, $quote) {
28045217901bSGreg Roach		$that = $this; // PHP5.3 cannot access $this inside a closure
2805d1286247SGreg Roach		return preg_replace_callback(
2806d1286247SGreg Roach			'/\$(\w+)/',
28075217901bSGreg Roach			function ($matches) use ($that, $quote) {
28085217901bSGreg Roach				if (isset($that->vars[$matches[1]]['id'])) {
280982759250SGreg Roach					if ($quote) {
28105217901bSGreg Roach						return "'" . addcslashes($that->vars[$matches[1]]['id'], "'") . "'";
281182759250SGreg Roach					} else {
28125217901bSGreg Roach						return $that->vars[$matches[1]]['id'];
281382759250SGreg Roach					}
2814d1286247SGreg Roach				} else {
2815d1286247SGreg Roach					Log::addErrorLog(sprintf('Undefined variable $%s in report', $matches[1]));
28163d7a8a4cSGreg Roach
2817d1286247SGreg Roach					return '$' . $matches[1];
2818d1286247SGreg Roach				}
2819d1286247SGreg Roach			},
2820d1286247SGreg Roach			$expression
2821d1286247SGreg Roach		);
2822d1286247SGreg Roach	}
2823a6f13a4aSGreg Roach}
2824