<?php
/**
 * Base Report Generator
 *
 * used by the SAX parser to generate reports from the XML report file.
 *
 * phpGedView: Genealogy Viewer
 * Copyright (C) 2002 to 2009  PGV Development Team.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * @package PhpGedView
 * @subpackage Reports
 * @version $Id: class_reportbase.php 5942 2009-07-20 17:45:12Z meliza $
 */

if (!defined('PGV_PHPGEDVIEW')) {
	header('HTTP/1.0 403 Forbidden');
	exit;
}

/**
* @todo add info
*/
define('PGV_CLASS_REPORTBASE_PHP', '');

$ascii_langs = array('english', 'danish', 'dutch', 'french', 'hebrew', 'hungarian', 'german', 'norwegian', 'spanish', 'spanish-ar');

//-- setup special characters array to force embedded fonts
$SpecialOrds = $RTLOrd;
for($i=195; $i<215; $i++) $SpecialOrds[] = $i;

if (!isset($embed_fonts)) {
	if (in_array($LANGUAGE, $ascii_langs)) {
		$embed_fonts = false;
	}
	else $embed_fonts = true;
}

/**
 * Main PGV Report Class
 *
 * Document wide functions and variable defaults that will be inherited of the report modules
 * @package PhpGedView
 * @subpackage Reports
 */
class PGVReportBase {
	/**
	* Left Margin (expressed in points) Default: 18 mm, 0.7086 inch
	* @see PGVRDocSHandler()
	* @var float
	*/
	public $leftmargin = 51.02;
	/**
	* Right Margin (expressed in points) Default: 10 mm, 0. 394 inch
	* @see PGVRDocSHandler()
	* @var float
	*/
	public $rightmargin = 28.4;
	/**
	* Top Margin (expressed in points) Default: 27 mm
	* @see PGVRDocSHandler()
	* @var float
	*/
	public $topmargin = 76.5;
	/**
	* Bottom Margin (expressed in points) Default: 25 mm
	* @see PGVRDocSHandler()
	* @var float
	*/
	public $bottommargin = 70.8;
	/**
	* Header Margin (expressed in points) Default: 5 mm
	* @see PGVRDocSHandler()
	* @var float
	*/
	public $headermargin = 14.2;
	/**
	* Footer Margin (expressed in points) Default: 10 mm, 0.394 inch
	* @see PGVRDocSHandler()
	* @var float
	*/
	public $footermargin = 28.4;

	/**
	* Page orientation (portrait, landscape)
	* @see PGVRDocSHandler()
	* @var string
	*/
	public $orientation = 'portrait';
	/**
	* Page format name
	* @see PGVRDocSHandler()
	* @see PGVReportBase::setup()
	* @var string
	*/
	public $pageFormat = 'A4';
	
	/**
	* Height of page format in points
	* @see PGVRDocSHandler()
	* @see PGVReportBase::setup()
	* @var float
	*/
	public $pageh = 0.0;
	/**
	* Width of page format in points
	* @see PGVRDocSHandler()
	* @see PGVReportBase::setup()
	* @var float
	*/
	public $pagew = 0.0;

	/**
	* An array of the PGVRStyles elements found in the document
	* @see PGVRStyleSHandler()
	 * @var array
	 */
	public $PGVRStyles = array();
	/**
	* The default Report font name
	* @see PGVRStyleSHandler()
	* @var string
	*/
	public $defaultFont = 'dejavusans';
	/**
	* The default Report font size
	* @see PGVRStyleSHandler()
	* @var int
	*/
	public $defaultFontSize = 12;

	/**
	* Header (H), Page header (PH), Body (B) or Footer (F)
	* @var string
	*/
	public $processing = 'H';
	
	/**
	* RTL Language (false=LTR, true=RTL)
	* @see PGVReportBase::setup()
	* @var boolean
	*/
	public $rtl = false;
	
	/**
	* User measure unit.
	* @var string const
	*/
	const unit = 'pt';
	
	/**
	* Character set
	* @see PGVReportBase::setup()
	* @var string
	*/
	public $charset = 'UTF-8';
	
	/**
	* Show the Generated by... (true=show the text)
	* @see PGVRDocSHandler()
	* @var boolean
	*/
	public $showGenText = true;
	/**
	* Generated By... text
	* @see PGVReportBase::setup()
	* @var string
	*/
	public $generatedby = '';
	
	/**
	* PhpGedView URL
	* @var string const
	*/
	const pgv_url = PGV_PHPGEDVIEW_URL;

	/**
	* The report title
	* @see PGVReportBase::addTitle()
	* @var string
	*/
	public $title = '';
	/**
	* Author of the report, the users full name
	* @var string
	* @todo add the author support
	*/
//	var $rauthor = '';
	/**
	* Keywords
	* @see PGVReportBase::setup()
	* @var string
	*/
	public $rkeywords = '';
	/**
	* Report Description / Subject
	* @see PGVReportBase::addDescription()
	* @var string
	*/
	public $rsubject = '';

	/**
	* Initial Setup - PGVReportBase
	*
	* Setting up document wide defaults that will be inherited of the report modules
	* As DEFAULT A4 and Portrait will be used if not set
	*
	* @see PGVRDocSHandler()
	* @todo add page sizes to wiki
	*/
	function setup() {
		global $pgv_lang, $TEXT_DIRECTION, $CHARACTER_SET, $META_KEYWORDS;

		// Set RTL direction
		if ($TEXT_DIRECTION == 'rtl') {
			$this->rtl = true;
		}
		// Set the character setting
		$this->charset = $CHARACTER_SET;
		// Set the Keywords
		$this->rkeywords = $META_KEYWORDS;
		// Generated By...text
		$this->generatedby = ("$pgv_lang[generated_by] ".PGV_PHPGEDVIEW);
		// Only admin should see the version number
		if (PGV_USER_IS_ADMIN) {
			$this->generatedby .= " ".PGV_VERSION_TEXT;
		}

		// For known size pages
		if (($this->pagew == 0) AND ($this->pageh == 0)) {
			/**
			* The current ISO 216 standard was introduced in 1975 and is a direct follow up to the german DIN 476 standard from 1922. ISO 216 is also called EN 20216 in Europe.
			* The ISO paper sizes are based on the metric system so everything else is aproxiamte
			* 
			* The Series A is used for Standard Printing and Stationary.
			* The Series B is used for Posters, Wall-Charts etc.
			* The C series is used for folders, post cards and envelopes. C series envelope is suitable to insert A series sizes.
			* ISO also define format series RA and SRA for untrimmed raw paper, where SRA stands for 'supplementary raw format A'.
			* Japan has adopted the ISO series A sizes, but its series B sizes are slightly different. These sizes are sometimes called JIS B or JB sizes.
			* 	sun was a unit of length used in Japan and is equal to about 3.03 cm or 1.193 inches
			* The United States, Canada, and in part Mexico, are today the only industrialized nations in which the ISO standard paper sizes are not yet widely used. 
			* 
			* A0 & A1		Technical drawings, posters
			* A1 & A2		Flip charts
			* A2 & A3		Drawings, diagrams, large tables
			* A4			Letters, magazines, forms, catalogs, laser printer and copying machine output
			* A5			Note pads
			* A6			Postcards
			* B5, A5, B6  A6	Books
			* C4, C5, C6	Envelopes for A4 letters: unfolded (C4), folded once (C5), folded twice (C6)
			* B4 & A3		Newspapers, supported by most copying machines in addition to A4
			* B8 & A8		Playing cards
			*
			* 1 inch = 72 points
			* 1 mm = 2.8346457 points
			* 1 inch = 25.4 mm
			* 1 point = 0,35278 mm
			*/
			switch ($this->pageFormat) {
				// ISO A series
				case '4A0':	{$sizes = array(4767.86,6740.79);break;}	// ISO 216, 1682 mm x 2378 mm
				case '2A0':	{$sizes = array(3370.39,4767.86);break;}	// ISO 216, 1189 mm x 1682 mm
				case 'A0':	{$sizes = array(2383.94,3370.39);break;}	// ISO 216, 841 mm x 1189mm
				case 'A1':	{$sizes = array(1683.78,2383.94);break;}	// ISO 216, 594 mm x 841 mm
				case 'A2':	{$sizes = array(1190.55,1683.78);break;}	// ISO 216, 420 mm x 594 mm
				case 'A3':	{$sizes = array(841.89,1190.55);break;}		// ISO 216, 297 mm x 420 mm
				case 'A4':	default:{									// For unknown Page Size Name
							$sizes = array(595.28,841.89);				// ISO 216, 210 mm 297 mm
							$this->pageFormat = 'A4';
							break;}
				case 'A5':	{$sizes = array(419.53,595.28);break;}	// ISO 216, 148 mm x 210 mm
				case 'A6':	{$sizes = array(297.64,419.53);break;}	// ISO 216, 105 mm x 148 mm
				case 'A7':	{$sizes = array(209.76,297.64);break;}	// ISO 216, 74 mm x 105 mm
				case 'A8':	{$sizes = array(147.40,209.76);break;}	// ISO 216, 52 mm x 74 mm
				case 'A9':	{$sizes = array(104.88,147.40);break;}	// ISO 216, 37 mm x 52 mm
				case 'A10':	{$sizes = array(73.70,104.88);break;}	// ISO 216, 26 mm x 37 mm
				// ISO B series
				case 'B0':	{$sizes = array(2834.65,4008.19);break;}	// ISO 216, 1000 mm x 1414 mm
				case 'B1':	{$sizes = array(2004.09,2834.65);break;}	// ISO 216, 707 mm x 1000 mm
				case 'B2':	{$sizes = array(1417.32,2004.09);break;}	// ISO 216, 500 mm x 707 mm
				case 'B3':	{$sizes = array(1000.63,1417.32);break;}	// ISO 216, 353 mm x 500 mm
				case 'B4':	{$sizes = array(708.66,1000.63);break;}	// ISO 216, 250 mm x 353 mm
				case 'B5':	{$sizes = array(498.90,708.66);break;}	// ISO 216, 176 mm x 250 mm
				case 'B6':	{$sizes = array(354.33,498.90);break;}	// ISO 216, 125 mm x 176 mm
				case 'B7':	{$sizes = array(249.45,354.33);break;}	// ISO 216, 88 mm x 125 mm
				case 'B8':	{$sizes = array(175.75,249.45);break;}	// ISO 216, 62 mm x 88 mm
				case 'B9':	{$sizes = array(124.72,175.75);break;}	// ISO 216, 44 mm x 62 mm
				case 'B10':	{$sizes = array(87.87,124.72);break;}	// ISO 216, 31 mm x 44 mm
				// ISO C series, Envelope
				case 'C0':	{$sizes = array(2599.37,3676.54);break;}	// ISO 269, 917 mm x 1297 mm, For flat A0 sheet
				case 'C1':	{$sizes = array(1836.85,2599.37);break;}	// ISO 269, 648 mm x 917 mm, For flat A1 sheet
				case 'C2':	{$sizes = array(1298.27,1836.85);break;}	// ISO 269, 458 mm x 648 mm, For flat A2 sheet, A1 folded in half
				case 'C3':	{$sizes = array(918.43,1298.27);break;}	// ISO 269, 324 mm x 458 mm, For flat A3 sheet, A2 folded in half
				case 'C4':	{$sizes = array(649.13,918.43);break;}	// ISO 269, 229 mm x 324 mm, For flat A4 sheet, A3 folded in half
				case 'C5':	{$sizes = array(459.21,649.13);break;}	// ISO 269, 162 mm x 229 mm, For flat A5 sheet, A4 folded in half
				case 'C6/5':{$sizes = array(323.15,649.13);break;}	// ISO 269, 114 mm x 229 mm. A5 folded twice = 1/3 A4. Alternative for the DL envelope
				case 'C6':	{$sizes = array(323.15,459.21);break;}	// ISO 269, 114 mm x 162 mm, For A5 folded in half
				case 'C7/6':{$sizes = array(229.61,459.21);break;}	// ISO 269, 81 mm x 162 mm, For A5 sheet folded in thirds
				case 'C7':	{$sizes = array(229.61,323.15);break;}	// ISO 269, 81 mm x 114 mm, For A5 folded in quarters
				case 'C8':	{$sizes = array(161.57,229.61);break;}	// ISO 269, 57 mm x 81 mm
				case 'C9':	{$sizes = array(113.39,161.57);break;}	// ISO 269, 40 mm x 57 mm
				case 'C10':	{$sizes = array(79.37,113.39);break;}	// ISO 269, 28 mm x 40 mm
				case 'DL':	{$sizes = array(311.81,623.62);break;}	// Original DIN 678 but ISO 269 now has this C6/5 , 110 mm x 220 mm, For A4 sheet folded in thirds, A5 in half
				// Untrimmed stock sizes for the ISO-A Series - ISO primary range
				case 'RA0':	{$sizes = array(2437.80,3458.27);break;}	// ISO 478, 860 mm x 1220 mm
				case 'RA1':	{$sizes = array(1729.13,2437.80);break;}	// ISO 478, 610 mm x 860 mm
				case 'RA2':	{$sizes = array(1218.90,1729.13);break;}	// ISO 478, 430 mm x 610 mm
				case 'RA3':	{$sizes = array(864.57,1218.90);break;}	// ISO 478, 305 mm x 430 mm
				case 'RA4':	{$sizes = array(609.45,864.57);break;}	// ISO 478, 215 mm x 305 mm
				// Untrimmed stock sizes for the ISO-A Series - ISO supplementary range
				case 'SRA0':	{$sizes = array(2551.18,3628.35);break;}	// ISO 593, 900 mm x 1280 mm
				case 'SRA1':	{$sizes = array(1814.17,2551.18);break;}	// ISO 593, 640 mm x 900 mm
				case 'SRA2':	{$sizes = array(1275.59,1814.17);break;}	// ISO 593, 450 mm x 640 mm
				case 'SRA3':	{$sizes = array(907.09,1275.59);break;}	// ISO 593, 320 mm x 450 mm
				case 'SRA4':	{$sizes = array(637.80,907.09);break;}	// ISO 593, 225 mm x 320 mm
				// ISO size variations
				case 'A2EXTRA':	{$sizes = array(1261.42,1754.65);break;}	// ISO 216, 445 mm x 619 mm
				case 'A2SUPER':	{$sizes = array(864.57,1440.00);break;}	// ISO 216, 305 mm x 508 mm
				case 'A3EXTRA':	{$sizes = array(912.76,1261.42);break;}	// ISO 216, 322 mm x 445 mm
				case 'SUPERA3':	{$sizes = array(864.57,1380.47);break;}	// ISO 216, 305 mm x 487 mm
				case 'A4EXTRA':	{$sizes = array(666.14,912.76);break;}	// ISO 216, 235 mm x 322 mm
				case 'A4LONG':	{$sizes = array(595.28,986.46);break;}	// ISO 216, 210 mm x 348 mm
				case 'A4SUPER':	{$sizes = array(649.13,912.76);break;}	// ISO 216, 229 mm x 322 mm
				case 'SUPERA4':	{$sizes = array(643.46,1009.13);break;}	// ISO 216, 227 mm x 356 mm
				case 'A5EXTRA':	{$sizes = array(490.39,666.14);break;}	// ISO 216, 173 mm x 235 mm
				case 'SOB5EXTRA':	{$sizes = array(572.60,782.36);break;}	// ISO 216, 202 mm x 276 mm
				// Japanese version of the ISO 216 B series
				case 'JB0':	{$sizes = array(2919.69,4127.24);break;}	// JIS P 0138-61, 1030 mm x 1456 mm
				case 'JB1':	{$sizes = array(2063.62,2919.69);break;}	// JIS P 0138-61, 728 mm x 1030 mm
				case 'JB2':	{$sizes = array(1459.84,2063.62);break;}	// JIS P 0138-61, 515 mm x 728 mm
				case 'JB3':	{$sizes = array(1031.81,1459.84);break;}	// JIS P 0138-61, 364 mm x 515 mm
				case 'JB4':	{$sizes = array(728.50,1031.81);break;}	// JIS P 0138-61, 257 mm x 364 mm
				case 'JB5':	{$sizes = array(515.91,728.50);break;}	// JIS P 0138-61, 182 mm x 257 mm
				case 'JB6':	{$sizes = array(362.83,515.91);break;}	// JIS P 0138-61, 128 mm x 182 mm
				case 'JB7':	{$sizes = array(257.95,362.83);break;}	// JIS P 0138-61, 91 mm x 128 mm
				case 'JB8':	{$sizes = array(181.42,257.95);break;}	// JIS P 0138-61, 64 mm x 91 mm
				case 'JB9':	{$sizes = array(127.56,181.42);break;}	// JIS P 0138-61, 45 mm x 64 mm
				case 'JB10':{$sizes = array(90.71,127.56);break;}	// JIS P 0138-61, 32 mm x 45 mm
				// US pages
				case 'EXECUTIVE':	{$sizes = array(522.00,756.00);	break;}	// 7.25 in x 10.5 in
				case 'FOLIO':	{$sizes = array(612.00,936.00);		break;}	// 8.5 in x 13 in
				case 'FOOLSCAP':{$sizes = array(972.00,1224.00);	break;}	// 13.5 in x 17 in
				case 'LEDGER':	{$sizes = array(792.00,1224.00);	break;}	// 11 in x 17 in
				case 'LEGAL':	{$sizes = array(612.00,1008.00);	break;}	// 8.5 in x 14 in
				case 'LETTER':	{$sizes = array(612.00,792.00);		break;}	// 8.5 in x 11 in
				case 'QUARTO':	{$sizes = array(609.12,777.5);		break;}	// 8.46 in x 10.8 in
				case 'STATEMENT':	{$sizes = array(396.00,612.00);	break;}	// 5.5 in x 8.5 in
				case 'USGOVT':	{$sizes = array(576.00,792.00);		break;}	// 8 in x 11 in
			}
			$this->pagew = $sizes[0];
			$this->pageh = $sizes[1];
		}
		else {
			if ($this->pagew < 10) {
				die('<strong>REPORT ERROR PGVReportBase::setup(): </strong>For custom size pages you must set \'customwidth\' larger then this in the XML file');
			}
			if ($this->pageh < 10) {
				die('<strong>REPORT ERROR PGVReportBase::setup(): </strong>For custom size pages you must set \'customheight\' larger then this in the XML file');
			}
		}
		return 0;
	}

	/**
	* Process the Header , Page header, Body or Footer - PGVReportBase
	*
	* @param string $p Header (H), Page header (PH), Body (B) or Footer (F)
	*/
	function setProcessing($p) {
		$this->processing = $p;
		return 0;
	}

	/**
	* Add the Title when raw character data is used in PGVRTitle - PGVReportBase
	*
	* @param string $data
	*/
	function addTitle($data) {
		$this->title .= $data;
		return 0;
	}

	/**
	* Add the Description when raw character data is used in PGVRDescription - PGVReportBase
	*
	* @param string $data
	*/
	function addDescription($data) {
		$this->rsubject .= $data;
		return 0;
	}

	/**
	* Add Style to PGVRStyles array - PGVReportBase
	*
	* @see PGVRStyleSHandler()
	* @param array $style
	*/
	function addStyle($style) {
		$this->PGVRStyles[$style['name']] = $style;
		return 0;
	}

	/**
	* Get a style from the PGVRStyles array - PGVReportBase
	*
	* @param string $s Style name
	* @return array
	*/
	function getStyle($s) {
		if (!isset($this->PGVRStyles[$s])) {
			return current($this->PGVRStyles);
		}
		return $this->PGVRStyles[$s];
	}

	// static callback functions to sort data
	static function CompareBirthDate($x, $y) {
		return GedcomDate::Compare($x->getBirthDate(), $y->getBirthDate());
	}
	static function CompareDeathDate($x, $y) {
		return GedcomDate::Compare($x->getDeathDate(), $y->getDeathDate());
	}

	/**
	* @deprecated 
	* @todo Is this a deprecated function or a future feature?
	*
	function get_type() {
		// remove this die line only if it's cousing problem - It confirms that it's not used
		die('<strong>REPORT ERROR PGVReportBase::get_type: </strong> It is used');
		return 'PGVReportBase';
	}
*/
}

/**
 * Main PGV Report Element class that all other page elements are extended from
 *
 * @package PhpGedView
 * @subpackage Reports
 */
class PGVRElement {
	/**
	* @var string
	*/
	public $text = '';

	/**
	* Element renderer
	* @param &$renderer
	*/
	function render(&$renderer) {
//		print "Nothing rendered.  Something bad happened";
//		debug_print_backtrace();
		//-- to be implemented in inherited classes
		return 0;
	}

	function getHeight(&$renderer) {
		return 0;
	}

	function getWidth(&$renderer) {
		return 0;
	}

	function addText($t) {
		global $embed_fonts, $SpecialOrds, $pgvreport, $reportTitle, $reportDescription;

		$found = false;
		foreach($SpecialOrds as $ord) {
			if (strpos($t, chr($ord)) !== false) {
				$found = true;
			}
		}
		if ($found) {
			$embed_fonts = true;
		}
		$t = trim($t, "\r\n\t");
		$t = str_replace(array('<br />', '&nbsp;'), array("\n", ' '), $t);
		$t = strip_tags($t);
		$t = unhtmlentities($t);
		// Tested by Meliza, and it works
		// Disabling this breaks nothing, I'll clean it up later when we are absolutly sure
/*		if ($embed_fonts) {
			$t = bidi_text($t);
		}
*/		
		$this->text .= $t;

		// Adding the title and description to the Document Properties
		if ($reportTitle) {
			$pgvreport->addTitle($t);
		}
		else if ($reportDescription) {
			$pgvreport->addDescription($t);
		}
		return 0;
	}

	function addNewline() {
		$this->text .= "\n";
		return 0;
	}

	function getValue() {
		return $this->text;
	}

	function setWrapWidth($width) {
		return 0;
	}

	function renderFootnote(&$renderer) {
		return false;
		//-- to be implemented in inherited classes
	}

	/**
	* Get the Class name type
	*
	* @return string PGVRElementBase
	*/
	function get_type() {
		return "PGVRElementBase";
	}

	function setText($text) {
		$this->text = $text;
		return 0;
	}
	
} //-- END PGVRElement

/**
* HTML element class
*
* @package PhpGedView
* @subpackage Reports
* @todo add info
*/
class PGVRHtml extends PGVRElement {
	public $tag;
	public $attrs;
	public $elements = array();

	function PGVRHtml($tag, $attrs) {
		$this->tag = $tag;
		$this->attrs = $attrs;
		return 0;
	}

	function getStart() {
		$str = "<".$this->tag." ";
		foreach($this->attrs as $key=>$value) {
			$str .= $key.'="'.$value.'" ';
		}
		$str .= ">";
		return $str;
	}

	function getEnd() {
		return "</".$this->tag.">";
	}

	function addElement(&$element) {
		$this->elements[] = $element;
		return 0;
	}

	/**
	* Get the class type
	* @return string PGVRHtml
	*/
	function get_type() {
		return "PGVRHtml";
	}
}

/**
 * Cell element class
*
* @package PhpGedView
* @subpackage Reports
*/
class PGVRCell extends PGVRElement {
	/**
	* Allows to center or align the text. Possible values are:<ul><li>left or empty string: left align</li><li>center: center align</li><li>right: right align</li><li>justify: justification (default value when $ishtml=false)</li></ul>
	* @var string
	*/
	public $align = '';
	/**
	* Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0.
	* Or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul>
	* @var mixed
	*/
	public $border;
	/**
	* Border color in HTML code
	* @var string
	*/
	public $bocolor;
	/**
	* The HTML color code to fill the background of this cell. 
	* @var string
	*/
	public $bgcolor;
	/**
	* Indicates if the cell background must be painted (1) or transparent (0). Default value: 1.
	* If no background color is set then it will not be painted
	* @var int
	*/
	public $fill;
	/**
	* Cell height DEFAULT 0 (expressed in points)
	* The starting height of this cell. If the text wraps the height will automatically be adjusted.
	* @var int
	*/
	public $height;
	/**
	* Left position in user units (X-position). Default is the current position
	* @var mixed
	*/
	public $left;
	/**
	* Indicates where the current position should go after the call.  Possible values are:<ul><li>0: to the right [DEFAULT]</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
	* @var int
	*/
	public $newline;
	/**
	* The name of the PGVRStyle that should be used to render the text. 
	* @var string
	*/
	public $styleName;
	/**
	* Stretch carachter mode: <ul><li>0 = disabled (default)</li><li>1 = horizontal scaling only if necessary</li><li>2 = forced horizontal scaling</li><li>3 = character spacing only if necessary</li><li>4 = forced character spacing</li></ul>
	* @var int
	*/
	public $stretch;
	/**
	* Text color in HTML code
	* @var string
	*/
	public $tcolor;
	/**
	* Top position in user units (Y-position). Default is the current position
	* @var mixed
	*/
	public $top;
	/**
	* URL address
	* @var string
	*/
	public $url;
	/**
	* Cell width DEFAULT 0 (expressed in points)
	* Setting the width to 0 will make it the width from the current location to the right margin. 
	* @var int
	*/
	public $width;
	
	public $reseth;

	/**
	* CELL - PGVRElement
	*
	* @param int $width cell width (expressed in points)
	* @param int $height cell height (expressed in points)
	* @param mixed $border Border style
	* @param string $align Text alignement
	* @param string $bgcolor Background color code
	* @param string $style The name of the text style
	* @param int $ln Indicates where the current position should go after the call
	* @param mixed $top Y-position
	* @param mixed $left X-position
	* @param int $fill Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
	* @param int $stretch Stretch carachter mode
	* @param string $bocolor Border color
	* @param string $tcolor Text color
	*/
	function PGVRCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth) {
		$this->align = $align;
		$this->border = $border;
		$this->bgcolor = $bgcolor;
		$this->bocolor = $bocolor;
		$this->fill = $fill;
		$this->height = $height;
		$this->left = $left;
		$this->newline = $ln;
		$this->styleName = $style;
		$this->text = '';
		$this->tcolor = $tcolor;
		$this->top = $top;
		$this->url = '';
		$this->stretch = $stretch;
		$this->width = $width;
		$this->reseth = $reseth;
		return 0;
	}
	/**
	* Get the cell height
	* @todo add param
	* @return float
	*/
	function getHeight(&$renderer) {
		return $this->height;
	}
	/**
	* Sets the current cells URL
	* @param string $url The URL address to save
	*/
	function setUrl($url) {
		$this->url = $url;
		return 0;
	}
	/**
	* Get the cell width
	* @todo add param
	* @return  float
	*/
	function getWidth(&$renderer) {
		return $this->width;
	}
	/**
	* Get the class type
	* @return string PGVRCell
	*/
	function get_type() {
		return 'PGVRCell';
	}
}

/**
 * TextBox element class
*
* @package PhpGedView
* @subpackage Reports
* @todo add info
*/
class PGVRTextBox extends PGVRElement {
	/**
	* Array of elements in the TextBox
	* @var array
	*/
	public $elements = array();
	
	/**
	*  Background color in HTML code
	* @var string
	*/
	public $bgcolor;
	/**
	* Whether or not paint the background
	* @var boolean
	*/
	public $fill;
	
	/**
	* Position the left corner of this box on the page(expressed in points). The default is the current position.
	* @var mix
	*/
	public $left;
	/**
	* Position the top corner of this box on the page(expressed in points). the default is the current position
	* @var mix
	*/
	public $top;
	/**
	* 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
	* @var boolean
	*/
	public $newline;
	
	/**
	* @var boolean
	*/
	public $pagecheck;
	
	/**
	* Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0
	* @var boolean
	*/
	public $border;
	/**
	* Style of rendering
	*
	* <ul>
	* <li>D or empty string: Draw (default).</li>
	* <li>F: Fill.</li>
	* <li>DF or FD: Draw and fill.</li>
	* <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
	*<li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
	* </ul>
	* @var string
	*/
	public $style;
	
	/**
	* @var array $borderstyle Border style of rectangle. Array with keys among the following:
	* <ul>
	*	 <li>all: Line style of all borders. Array like for {@link SetLineStyle SetLineStyle}.</li>
	*	 <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for {@link SetLineStyle SetLineStyle}.</li>
	* </ul>
	* Not yet in use
	var $borderstyle;
	*/

	/**
	* The starting height of this cell. If the text wraps the height will automatically be adjusted
	* @var float
	*/
	public $height;
	/**
	* Setting the width to 0 will make it the width from the current location to the right margin
	* @var float
	*/
	public $width;
	/**
	 * Use cell padding or not
	 * @var boolean $padding
	 */
	public $padding;
	/**
	 * Resets this box last height after it's done
	 */
	public $reseth;
	
	/**
	* TextBox - PGVRElement - Base
	*
	* @param float $width Text box width
	* @param float $height Text box height
	* @param boolean $border
	* @param string $bgcolor Background color code in HTML
	* @param boolean $newline
	* @param mixed $left
	* @param mixed $top
	* @param boolean $pagecheck
	* @param string $style
	* @param boolean $fill
	* @param boolean $padding
	* @param boolean $reseth
	*/
	function PGVRTextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth) {
		$this->border = $border;
		$this->bgcolor = $bgcolor;
		$this->fill= $fill;
		$this->height = $height;
		$this->left = $left;
		$this->newline = $newline;
		$this->pagecheck = $pagecheck;
		$this->style = $style;
		$this->top = $top;
		$this->width = $width;
		$this->padding = $padding;
		$this->reseth = $reseth;
		return 0;
	}

	/**
	* Add an element to the TextBox
	* @param object|string &$element
	*/
	function addElement(&$element) {
		$this->elements[] = $element;
		return 0;
	}

	/**
	* Get the class type
	* @return string PGVRTextBox
	*/
	function get_type() {
		return "PGVRTextBox";
	}
}

/**
 * Text element class
*
* @package PhpGedView
* @subpackage Reports
* @todo add info
*/
class PGVRText extends PGVRElement {
	/**
	* Text color in HTML code
	* @var string
	*/
	public $color;
	/**
	* Style name
	* @var string
	*/
	public $styleName;
	/**
	* Remaining width of a cel
	* @var int User unit (points)
	*/
	public $wrapWidthRemaining;
	/**
	* Original width of a cell
	* @var int User unit (points)
	*/
	public $wrapWidthCell;

	/**
	* Create a Text class - Base
	*
	* @param string $style The name of the text style
	* @param string $color HTML color code
	*/
	function PGVRText($style, $color) {
		$this->text = "";
		$this->color = $color;
		$this->wrapWidthRemaining = 0;
		$this->styleName = $style;
		return 0;
	}

	function setWrapWidth($wrapwidth, $cellwidth) {
		$this->wrapWidthCell = $cellwidth;
		if (preg_match("/^\n/", $this->text) > 0) {
			$this->wrapWidthRemaining = $cellwidth;
		}
		else $this->wrapWidthRemaining = $wrapwidth;
		return $this->wrapWidthRemaining;
	}

	function getStyleName() {
		return $this->styleName;
	}

	/**
	* Get the class type
	* @return string PGVRText
	*/
	function get_type() {
		return "PGVRText";
	}
}

/**
 * Footnote element class
*
* @package PhpGedView
* @subpackage Reports
* @todo add info
*/
class PGVRFootnote extends PGVRElement {
	/**
	* The name of the style for this element
	* @var string
	*/
	public $styleName = '';
	/**
	* Numbers for the links
	* @var int
	*/
	public $num;
	/**
	* The text that will be printed with the number
	* @var string
	*/
	public $numText = '';
	/**
	* Remaining width of a cel
	* @var float User unit (points)
	*/
	public $wrapWidthRemaining;
	/**
	* Original width of a cell
	* @var float User unit (points)
	*/
	public $wrapWidthCell;
	public $addlink;

	function PGVRFootnote($style="") {
		$this->text = "";
		if (!empty($style)) {
			$this->styleName = $style;
		}
		else $this->styleName="footnote";
		return 0;
	}

	function rerender(&$renderer) {
		global $footnote_count;
		return false;
	}

	function addText($t) {
		global $embed_fonts, $SpecialOrds;

		$found = false;
		foreach($SpecialOrds as $ord) {
			if (strpos($t, chr($ord)) !== false) {
				$found = true;
			}
		}
		if ($found) {
			$embed_fonts = true;
		}

		$t = trim($t, "\r\n\t");
		$t = str_replace(array('<br />', '&nbsp;'), array("\n", ' '), $t);
		$t = strip_tags($t);
		$t = unhtmlentities($t);
/*		if ($embed_fonts) {
			$t = bidi_text($t);
		}
*/
		$this->text .= $t;
		return 0;
	}

	function setWrapWidth($wrapwidth, $cellwidth) {
		$this->wrapWidthCell = $cellwidth;
		if (preg_match("/^\n/", $this->numText) > 0) {
			$this->wrapWidthRemaining = $cellwidth;
		}
		else $this->wrapWidthRemaining = $wrapwidth;
		return $this->wrapWidthRemaining;
	}

	function setNum($n) {
		$this->num = $n;
		$this->numText = "$n ";
		return 0;
	}

	function setAddlink($a) {
		$this->addlink = $a;
		return 0;
	}

	/**
	* Get the class type
	* @return string PGVRFootnote
	*/
	function get_type() {
		return "PGVRFootnote";
	}
}

/**
 * PageHeader element class
*
* @package PhpGedView
* @subpackage Reports
* @todo add info
*/
class PGVRPageHeader extends PGVRElement {
	public $elements = array();

	function PGVRTextBox() {
		$this->elements = array();
		return 0;
	}

	function PGVRPageHeader() {
		$this->elements = array();
		return 0;
	}

	/**
	* Add element - PGVRPageHeader
	* @param $element
	*/
	function addElement($element) {
		$this->elements[] = $element;
		return 0;
	}

	/**
	* Get the class type
	* @return string PGVRPageHeader
	*/
	function get_type() {
		return "PGVRPageHeader";
	}
}

/**
 * Image element class
*
* @package PhpGedView
* @subpackage Reports
* @todo add info
*/
class PGVRImage extends PGVRElement {
	/**
	* File name of the image
	* @var string
	*/
	public $file;
	/**
	* Height of the image
	* @var float
	*/
	public $height;
	/**
	* Width of the image
	* @var float
	*/
	public $width;
	/**
	* X-position (left) of the image
	* @var float
	*/
	public $x;
	/**
	* Y-position (top) of the image
	* @var float
	*/
	public $y;
	/**
	* Placement fo the image. L: left, C:center, R:right
	* @var string
	*/
	public $align = '';
	/**
	* T:same line, N:next line
	* @var string
	*/
	public $line = '';
	
	/**
	* PGVRImage class function - Base
	*
	* @param string $file File name of the image
	* @param float $x X-position (left) of the image
	* @param float $y Y-position (top) of the image
	* @param float $w Width of the image
	* @param float $h Height of the image
	* @param string $align Placement of the image. L: left, C:center, R:right
	* @param string $ln T:same line, N:next line 
	*/
	function PGVRImage($file, $x, $y, $w, $h, $align, $ln) {
		$this->file = $file;
		$this->width = $w;
		$this->height = $h;
		$this->x = $x;
		$this->y = $y;
		$this->align = $align;
		$this->line = $ln;
		return 0;
	}

	function getHeight(&$pdf) {
		return $this->height;
	}

	function getWidth(&$pdf) {
		return $this->width;
	}

	/**
	* Get the class type
	* @return string PGVRImage
	*/
	function get_type() {
		return "PGVRImage";
	}
}

/**
 * Line element class
*
* @package PhpGedView
* @subpackage Reports
* @todo add info
*/
class PGVRLine extends PGVRElement {
	/**
	* Start horizontal position, current position (default)
	* @var mixed
	*/
	public $x1 = ".";
	/**
	* Start vertical position, current position (default)
	* @var mixed
	*/
	public $y1 = ".";
	/**
	* End horizontal position, maximum width (default)
	* @var mixed
	*/
	public $x2 = ".";
	/**
	* End vertical position
	* @var mixed
	*/
	public $y2 = ".";

	/**
	* Create a line class - Base
	* @param mixed $x1
	* @param mixed $y1
	* @param mixed $x2
	* @param mixed $y2
	*/
	function PGVRLine($x1, $y1, $x2, $y2) {
		$this->x1 = $x1;
		$this->y1 = $y1;
		$this->x2 = $x2;
		$this->y2 = $y2;
		return 0;
	}

	function getHeight(&$pdf) {
		return abs($this->y2 - $this->y1);
	}

	function getWidth(&$pdf) {
		return abs($this->x2 - $this->x1);
	}

	/**
	* Get the class type
	* @return string PGVRLine
	*/
	function get_type() {
		return "PGVRLine";
	}
}

/**
 * element handlers array
 *
 * Converts XML element names into functions
 * @global array $elementHandler
 */
$elementHandler = array();
$elementHandler["br"]["start"]					= "brSHandler";
$elementHandler["PGVRBody"]["start"]			= "PGVRBodySHandler";
$elementHandler["PGVRCell"]["end"]				= "PGVRCellEHandler";
$elementHandler["PGVRCell"]["start"]			= "PGVRCellSHandler";
$elementHandler["PGVRDescription"]["end"]		= "PGVRDescriptionEHandler";
$elementHandler["PGVRDescription"]["start"]		= "PGVRDescriptionSHandler";
$elementHandler["PGVRDoc"]["end"]				= "PGVRDocEHandler";
$elementHandler["PGVRDoc"]["start"]				= "PGVRDocSHandler";
$elementHandler["PGVReport"]["end"]				= "";
$elementHandler["PGVReport"]["start"]			= "";
$elementHandler["PGVRFacts"]["end"]				= "PGVRFactsEHandler";
$elementHandler["PGVRFacts"]["start"]			= "PGVRFactsSHandler";
$elementHandler["PGVRFooter"]["start"]			= "PGVRFooterSHandler";
$elementHandler["PGVRFootnote"]["end"]			= "PGVRFootnoteEHandler";
$elementHandler["PGVRFootnote"]["start"]		= "PGVRFootnoteSHandler";
$elementHandler["PGVRFootnoteTexts"]["start"]	= "PGVRFootnoteTextsSHandler";
$elementHandler["PGVRGedcom"]["end"]			= "PGVRGedcomEHandler";
$elementHandler["PGVRGedcom"]["start"]			= "PGVRGedcomSHandler";
$elementHandler["PGVRGedcomValue"]["start"]		= "PGVRGedcomValueSHandler";
$elementHandler["PGVRGeneration"]["start"]		= "PGVRGenerationSHandler";
$elementHandler["PGVRGetPersonName"]["start"]	= "PGVRGetPersonNameSHandler";
$elementHandler["PGVRHeader"]["start"]			= "PGVRHeaderSHandler";
$elementHandler["PGVRHighlightedImage"]["start"]= "PGVRHighlightedImageSHandler";
$elementHandler["PGVRif"]["end"]				= "PGVRifEHandler";
$elementHandler["PGVRif"]["start"]				= "PGVRifSHandler";
$elementHandler["PGVRImage"]["start"]			= "PGVRImageSHandler";
$elementHandler["PGVRInput"]["end"]				= "";
$elementHandler["PGVRInput"]["start"]			= "";
$elementHandler["PGVRLine"]["start"]			= "PGVRLineSHandler";
$elementHandler["PGVRList"]["end"]				= "PGVRListEHandler";
$elementHandler["PGVRList"]["start"]			= "PGVRListSHandler";
$elementHandler["PGVRListTotal"]["start"]		= "PGVRListTotalSHandler";
$elementHandler["PGVRNewPage"]["start"]			= "PGVRNewPageSHandler";
$elementHandler["PGVRNow"]["start"]				= "PGVRNowSHandler";
$elementHandler["PGVRPageHeader"]["end"]		= "PGVRPageHeaderEHandler";
$elementHandler["PGVRPageHeader"]["start"]		= "PGVRPageHeaderSHandler";
$elementHandler["PGVRPageNum"]["start"]			= "PGVRPageNumSHandler";
$elementHandler["PGVRRelatives"]["end"]			= "PGVRRelativesEHandler";
$elementHandler["PGVRRelatives"]["start"]		= "PGVRRelativesSHandler";
$elementHandler["PGVRRepeatTag"]["end"]			= "PGVRRepeatTagEHandler";
$elementHandler["PGVRRepeatTag"]["start"]		= "PGVRRepeatTagSHandler";
$elementHandler["PGVRSetVar"]["start"]			= "PGVRSetVarSHandler";
$elementHandler["PGVRStyle"]["start"]			= "PGVRStyleSHandler";
$elementHandler["PGVRText"]["end"]				= "PGVRTextEHandler";
$elementHandler["PGVRText"]["start"]			= "PGVRTextSHandler";
$elementHandler["PGVRTextBox"]["end"]			= "PGVRTextBoxEHandler";
$elementHandler["PGVRTextBox"]["start"]			= "PGVRTextBoxSHandler";
$elementHandler["PGVRTitle"]["end"]				= "PGVRTitleEHandler";
$elementHandler["PGVRTitle"]["start"]			= "PGVRTitleSHandler";
$elementHandler["PGVRTotalPages"]["start"]		= "PGVRTotalPagesSHandler";
$elementHandler["PGVRvar"]["start"]				= "PGVRvarSHandler";
$elementHandler["PGVRvarLetter"]["start"]		= "PGVRvarLetterSHandler";
$elementHandler["sp"]["start"]					= "spSHandler";

/**
* A new object of the currently used element class
*
* @global object $currentElement
*/
$currentElement = new PGVRElement();

/**
 * Should character data be printed
 *
 * This variable is turned on or off by the element handlers to tell whether the inner character
 * Data should be printed
 * @global boolean $printData
 */
$printData = false;

/**
* Title collector. Mark it if it has already been used
*
* @global boolean $reportTitle
*/
$reportTitle = false;

/**
* Description collector. Mark it if it has already been used
*
* @global boolean $reportDescription
*/
$reportDescription = false;

/**
 * Print data stack
 *
 * As the XML is being processed there will be times when we need to turn on and off the
 * <var>$printData</var> variable as we encounter entinties in the XML.  The stack allows us to
 * keep track of when to turn <var>$printData</var> on and off.
 * @global array $printDataStack
 */
$printDataStack = array();

/**
* @todo add info
* @global array $pgvreportStack
*/
$pgvreportStack = array();

/**
* @todo add info
* @global array $gedrecStack
*/
$gedrecStack = array();

/**
* @todo add info
* @global array $repeatsStack
*/
$repeatsStack = array();

/**
* @todo add info
* @global array $parserStack
*/
$parserStack = array();

/**
* @todo add info
* @global array $repeats
*/
$repeats = array();

/**
* @todo add info
* @global string $gedrec
*/
$gedrec = "";

/**
* @todo add info
* @global ???? $repeatBytes
*/
$repeatBytes = 0;

/**
* @todo add info
* @global resource $parser
*/
$parser = "";

/**
* @todo add info
* @global int $processRepeats
*/
$processRepeats = 0;

/**
* @todo add info
* @global ???? $processIfs
*/
$processIfs = 0;

/**
* @todo add info
* @global ???? $processGedcoms
*/
$processGedcoms = 0;

/**
 *XML start element handler
 *
 * This function is called whenever a starting element is reached
 * The element handler will be called if found, otherwise it must be HTML
 *
 * @param resource $parser the resource handler for the XML parser
 * @param string $name the name of the XML element parsed
 * @param array $attrs an array of key value pairs for the attributes
 * @see endElement()
 */
function startElement($parser, $name, $attrs) {
	global $elementHandler, $processIfs, $processGedcoms, $processRepeats, $vars, $cti;

	$newattrs = array();
//	$temp = "";

	foreach($attrs as $key=>$value) {
		$match = array();
		$ct = preg_match("/^\\$(\w+)$/", $value, $match);
		if ($ct>0) {
			if ((isset($vars[$match[1]]["id"]))&&(!isset($vars[$match[1]]["gedcom"]))) $value = $vars[$match[1]]["id"];
		}
		$newattrs[$key] = $value;
	}
	$attrs = $newattrs;
	if (($processIfs==0 || $name=="PGVRif")&&($processGedcoms==0 || $name=="PGVRGedcom")&&($processRepeats==0 || $name=="PGVRFacts" || $name=="PGVRRepeatTag")) {
		if (isset($elementHandler[$name]["start"])) {
			if ($elementHandler[$name]["start"] != "") call_user_func($elementHandler[$name]["start"], $attrs);
		}
		else if (!isset($elementHandler[$name]["end"])) HTMLSHandler($name, $attrs);
	}
}

/**
 * XML end element handler
 *
 * This function is called whenever an ending element is reached
 * The element handler will be called if found, otherwise it must be HTML
 *
 * @param resource $parser the resource handler for the XML parser
 * @param string $name the name of the XML element parsed
 * @see startElement()
 */
function endElement($parser, $name) {
	global $elementHandler, $processIfs, $processGedcoms, $processRepeats;

	if (($processIfs==0 || $name=="PGVRif")&&($processGedcoms==0 || $name=="PGVRGedcom")&&($processRepeats==0 || $name=="PGVRFacts" || $name=="PGVRRepeatTag" || $name=="PGVRList" || $name=="PGVRRelatives")) {
		if (isset($elementHandler[$name]["end"])) {
			if ($elementHandler[$name]["end"]!="") call_user_func($elementHandler[$name]["end"]);
		}
		else if (!isset($elementHandler[$name]["start"])) HTMLEHandler($name);
	}
}

/**
 * XML character data handler
 *
 * This function is called whenever raw character data is reached
 * just print it to the screen
 * @param resource $parser the resource handler for the XML parser
 * @param string $data the name of the XML element parsed
 * @todo check this
 */
function characterData($parser, $data) {
	global $printData, $currentElement, $processGedcoms, $processIfs, $reportTitle, $pgvreport, $reportDescription;
	
	if ($printData && ($processGedcoms==0) && ($processIfs==0)) $currentElement->addText($data);
	else if ($reportTitle) $pgvreport->addTitle($data);
	else if ($reportDescription) $pgvreport->addDescription($data);
}

/**
* XML <PGVRStyleSHandler /> elemnt handler
*
* @param array $attrs an array of key value pairs for the attributes
* @see PGVReportBase::$defaultFont
* @see PGVReportBase::$defaultFontSize
* @see PGVReportBase::addStyle()
* @todo add info - update wiki
*/
function PGVRStyleSHandler($attrs) {
	global $pgvreport;

	if (empty($attrs['name'])) {
		die('<strong>REPORT ERROR PGVRStyle: </strong> The name of the style is missing or not set in the XML file');
	}

	// array Style that will be passed on
	$s = array();

	// string Name af the style
	$s['name'] = $attrs['name'];

	// string Name of the DEFAULT font
	$s['font'] = $pgvreport->defaultFont;
	if (!empty($attrs['font'])) $s['font'] = $attrs['font'];

	// int The size of the font in points
	$s['size'] = $pgvreport->defaultFontSize;
	if (!empty($attrs['size'])) $s['size'] = (int)$attrs['size'];	// Get it as int to ignore all decimal points or text (if any text then int(0))

	// string B: bold, I: italic, U: underline, D: line trough, The default value is regular.
	$s['style'] = '';
	if (!empty($attrs['style'])) $s['style'] = $attrs['style'];

	$pgvreport->addStyle($s);
}

/**
* XML <PGVRDoc> start elemnt handler
*
* Sets up the basics of the document proparties
* @param array $attrs an array of key value pairs for the attributes
* @see PGVRDocEHandler()
* @see PGVReportBase::setup()
* @todo add showGeneratedBy, height, width param to wiki and update the defaults
*/
function PGVRDocSHandler($attrs) {
	global $parser, $xml_parser, $pgvreport;

	$parser = $xml_parser;

	// Custom page width
	if (!empty($attrs['customwidth'])) $pgvreport->pagew = (int)$attrs['customwidth'];	// Get it as int to ignore all decimal points or text (if any text then int(0))
	// Custom Page height
	if (!empty($attrs['customheight'])) $pgvreport->pageh = (int)$attrs['customheight'];	// Get it as int to ignore all decimal points or text (if any text then int(0))

	// Left Margin
	if (isset($attrs['leftmargin'])) {
		if ($attrs['leftmargin'] === '0') $pgvreport->leftmargin = 0;
		else if (!empty($attrs['leftmargin'])) {
			$pgvreport->leftmargin = (int)$attrs['leftmargin'];	// Get it as int to ignore all decimal points or text (if any text then int(0))
		}
	}
	// Right Margin
	if (isset($attrs['rightmargin'])) {
		if ($attrs['rightmargin'] === '0') $pgvreport->rightmargin = 0;
		else if (!empty($attrs['rightmargin'])) {
			$pgvreport->rightmargin = (int)$attrs['rightmargin'];	// Get it as int to ignore all decimal points or text (if any text then int(0))
		}
	}
	// Top Margin
	if (isset($attrs['topmargin'])) {
		if ($attrs['topmargin'] === '0') $pgvreport->topmargin = 0;
		else if (!empty($attrs['topmargin'])) {
			$pgvreport->topmargin = (int)$attrs['topmargin'];	// Get it as int to ignore all decimal points or text (if any text then int(0))
		}
	}
	// Bottom Margin
	if (isset($attrs['bottommargin'])) {
		if ($attrs['bottommargin'] === '0') $pgvreport->bottommargin = 0;
		else if (!empty($attrs['bottommargin'])) {
			$pgvreport->bottommargin = (int)$attrs['bottommargin'];	// Get it as int to ignore all decimal points or text (if any text then int(0))
		}
	}
	// Header Margin
	if (isset($attrs['headermargin'])) {
		if ($attrs['headermargin'] === '0') $pgvreport->headermargin = 0;
		else if (!empty($attrs['headermargin'])) {
			$pgvreport->headermargin = (int)$attrs['headermargin'];	// Get it as int to ignore all decimal points or text (if any text then int(0))
		}
	}
	// Footer Margin
	if (isset($attrs['footermargin'])) {
		if ($attrs['footermargin'] === '0') $pgvreport->footermargin = 0;
		else if (!empty($attrs['footermargin'])) {
			$pgvreport->footermargin = (int)$attrs['footermargin'];	// Get it as int to ignore all decimal points or text (if any text then int(0))
		}
	}

	// Page Orientation
	if (!empty($attrs['orientation'])) {
		if ($attrs['orientation'] == 'landscape') $pgvreport->orientation = 'landscape';
		else if ($attrs['orientation'] == 'portrait') {
			$pgvreport->orientation = 'portrait';
		}
	}
	// Page Size
	if (!empty($attrs['pageSize'])) $pgvreport->pageFormat = strtoupper($attrs['pageSize']);

	// Show Generated By...
	if (isset($attrs['showGeneratedBy'])) {
		if ($attrs['showGeneratedBy'] === '0') $pgvreport->showGenText = false;
		else if ($attrs['showGeneratedBy'] === '1') {
			$pgvreport->showGenText = true;
		}
	}

	$pgvreport->setup();
}

/**
* XML </PGVRDoc> end elemnt handler
*
* @see PGVRDocSHandler()
*/
function PGVRDocEHandler() {
	global $pgvreport;
	$pgvreport->run();
}

/**
* XML <PGVRHeader> start elemnt handler
*
* @see PGVReportBase::setProcessing()
*/
function PGVRHeaderSHandler() {
	global $pgvreport;

	// Clear the Header before any new elements are added
	$pgvreport->clearHeader();
	$pgvreport->setProcessing('H');
}

/**
* XML <PGVRPageHeader> start elemnt handler
*
* @param array $attrs an array of key value pairs for the attributes
* @see PGVRPageHeaderEHandler()
*/
function PGVRPageHeaderSHandler($attrs) {
	global $printDataStack, $printData, $pgvreportStack, $pgvreport, $PGVReportRoot;

	array_push($printDataStack, $printData);
	$printData = false;
	array_push($pgvreportStack, $pgvreport);
	$pgvreport = $PGVReportRoot->createPageHeader();
}

/**
* XML <PGVRPageHeaderEHandler> end elemnt handler
*
* @see PGVRPageHeaderSHandler()
*/
function PGVRPageHeaderEHandler() {
	global $printData, $printDataStack, $pgvreport, $currentElement, $pgvreportStack;

	$printData = array_pop($printDataStack);
	$currentElement = $pgvreport;
	$pgvreport = array_pop($pgvreportStack);
	$pgvreport->addElement($currentElement);
}

/**
* XML <PGVRBodySHandler> start elemnt handler
*/
function PGVRBodySHandler() {
	global $pgvreport;
	$pgvreport->setProcessing('B');
}

/**
* XML <PGVRFooterSHandler> start elemnt handler
*/
function PGVRFooterSHandler() {
	global $pgvreport;
	$pgvreport->setProcessing('F');
}

/**
* XML <PGVRCell> start elemnt handler
*
* @param array $attrs an array of key value pairs for the attributes
* @see PGVRCellEHandler()
* @see PGVRCell
* @todo check the align why it's formated for pdf? Should be in reportpdf. Check also reporthtml, it's probably reformated there.
* @todo defaults to wiki
*/
function PGVRCellSHandler($attrs) {
	global $printData, $printDataStack, $currentElement, $PGVReportRoot, $pgvreport;

	// string The text alignment of the text in this box.
	$align= '';
	if (!empty($attrs['align'])) $align = $attrs['align'];

	// string The color to fill the background of this cell
	$bgcolor = '';
	if (!empty($attrs['bgcolor'])) $bgcolor = $attrs['bgcolor'];

	// int Whether or not the background should be painted
	$fill = 1;
	if (isset($attrs['fill'])) {
		if ($attrs['fill'] === '0') $fill = 0;
		else if ($attrs['fill'] === '1') {
			$fill = 1;
		}
	}

	$reseth = true;
	// boolean  	if true reset the last cell height (default true)
	if (isset($attrs['reseth'])) {
		if ($attrs['reseth'] === '0') $pgvreport->showGenText = false;
		else if ($attrs['reseth'] === '1') {
			$pgvreport->showGenText = true;
		}
	}

	// mixed Whether or not a border should be printed around this box
	$border = 0;
	if (!empty($attrs['border'])) $border = $attrs['border'];
	// string Border color in HTML code
	$bocolor = '';
	if (!empty($attrs['bocolor'])) $bocolor = $attrs['bocolor'];

	// int Cell height (expressed in points) The starting height of this cell. If the text wraps the height will automatically be adjusted.
	$height= 0;
	if (!empty($attrs['height'])) $height = (int)$attrs['height'];
	// int Cell width (expressed in points) Setting the width to 0 will make it the width from the current location to the right margin. 
	$width = 0;
	if (!empty($attrs['width'])) $width = (int)$attrs['width'];

	// int Stretch carachter mode
	$stretch= 0;
	if (!empty($attrs['stretch'])) $stretch = (int)$attrs['stretch'];

	// mixed Position the left corner of this box on the page. The default is the current position.
	$left = '.';
	if (isset($attrs['left'])) {
		if ($attrs['left'] === '.') $left = '.';
		else if (!empty($attrs['left'])) {
			$left = (int)$attrs['left'];
		}
		else if ($attrs['left'] === '0') {
			$left = 0;
		}
	}
	// mixed Position the top corner of this box on the page. the default is the current position
	$top = '.';
	if (isset($attrs['top'])) {
		if ($attrs['top'] === '.') $top = '.';
		else if (!empty($attrs['top'])) {
			$top = (int)$attrs['top'];
		}
		else if ($attrs['top'] === '0') {
			$top = 0;
		}
	}

	// string The name of the PGVRStyle that should be used to render the text. 
	$style = '';
	if (!empty($attrs['style'])) $style = $attrs['style'];
	// string Tex color in html code
	$tcolor = '';
	if (!empty($attrs['tcolor'])) $tcolor = $attrs['tcolor'];

	// int Indicates where the current position should go after the call.
	$ln = 0;
	if (isset($attrs['newline'])) {
		if (!empty($attrs['newline'])) $ln = (int)$attrs['newline'];
		else if ($attrs['newline'] === '0') $ln = 0;
	}

	if ($align=='left') {
		$align='L';
	}
	else if ($align=='right') {
		$align='R';
	}
	else if ($align=='center') {
		$align='C';
	}
	else if ($align=='justify') {
		$align='J';
	}

	array_push($printDataStack, $printData);
	$printData = true;

	$currentElement = $PGVReportRoot->createCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth);
}

/**
* XML </PGVRCell> end elemnt handler
*
* @see PGVRCellSHandler()
* @final
*/
function PGVRCellEHandler() {
	global $printData, $printDataStack, $currentElement, $pgvreport;

	$printData = array_pop($printDataStack);
	$pgvreport->addElement($currentElement);
}

/**
* XML <PGVRNow /> elemnt handler
*
* @see PGVRElement::addText()
* @final
*/
function PGVRNowSHandler() {
	global $currentElement;

	$g = timestamp_to_gedcom_date(client_time());
	$currentElement->addText($g->Display());
}

/**
* XML <PGVRPageNum /> elemnt handler
*
* @see PGVRElement::addText()
* @final
*/
function PGVRPageNumSHandler() {
	global $currentElement;
	$currentElement->addText('#PAGENUM#');
}

/**
* XML <PGVRTotalPages /> elemnt handler
*
* @see PGVRElement::addText()
* @final
*/
function PGVRTotalPagesSHandler() {
	global $currentElement;
	$currentElement->addText('{{nb}}');
}

/**
* @see PGVRGedcomEHandler()
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRGedcomSHandler($attrs) {
	global $vars, $gedrec, $gedrecStack, $processGedcoms, $fact, $desc, $ged_level;

	if ($processGedcoms>0) {
		$processGedcoms++;
		return;
	}

	$id = "";
	$gmatch = array();
	$gt = preg_match("/0 @(.+)@/", $gedrec, $gmatch);
	if ($gt > 0) {
		$id = $gmatch[1];
	}

	$tag = $attrs["id"];
	$tag = preg_replace("/@fact/", $fact, $tag);
	//print "[$tag]";
	$tags = explode(':', $tag);
	$newgedrec = "";
	if (count($tags)<2) {
		$newgedrec = find_gedcom_record($attrs["id"]);
	}
	if (empty($newgedrec)) {
		$tgedrec = $gedrec;
		$newgedrec = "";
		foreach($tags as $tag) {
			//print "[$tag]";
			$match = array();
			$ct = preg_match("/\\$(.+)/", $tag, $match);
			if ($ct>0) {
				if (isset($vars[$match[1]]["gedcom"])) $newgedrec = $vars[$match[1]]["gedcom"];
				else $newgedrec = find_gedcom_record($match[1]);
			}
			else {
				$ct = preg_match("/@(.+)/", $tag, $match);
				if ($ct>0) {
					$gt = preg_match("/\d $match[1] @([^@]+)@/", $tgedrec, $gmatch);
					//print $gt;
					if ($gt > 0) {
						//print "[".$gmatch[1]."]";
						$newgedrec = find_gedcom_record($gmatch[1]);
						//print $newgedrec;
						$tgedrec = $newgedrec;
					}
					else {
						//print "[$tgedrec]";
						$newgedrec = "";
						break;
					}
				}
				else {
					//$newgedrec = find_gedcom_record($gmatch[1]);
					$temp = explode(' ', trim($tgedrec));
					$level = $temp[0] + 1;
					if (showFact($tag, $id)&&showFactDetails($tag,$id)) {
						$newgedrec = get_sub_record($level, "$level $tag", $tgedrec);
						$tgedrec = $newgedrec;
					}
					else {
						$newgedrec = "";
						break;
					}
				}
			}
		}
	}
	if (!empty($newgedrec)) {
		//$newgedrec = privatize_gedcom($newgedrec);
		$gedObj = new GedcomRecord($newgedrec);
		array_push($gedrecStack, array($gedrec, $fact, $desc));
		//print "[$newgedrec]";
		$gedrec = $gedObj->getGedcomRecord();
		$ct = preg_match("/(\d+) (_?[A-Z0-9]+) (.*)/", $gedrec, $match);
		if ($ct>0) {
			$ged_level = $match[1];
			$fact = $match[2];
			$desc = trim($match[3]);
		}
	}
	else {
		$processGedcoms++;
	}
}

/**
* @see PGVRGedcomSHandler()
* @todo add info
*/
function PGVRGedcomEHandler() {
	global $gedrec, $gedrecStack, $processGedcoms, $fact, $desc;

	if ($processGedcoms>0) {
		$processGedcoms--;
	}
	else {
		$temp = array_pop($gedrecStack);
		$gedrec = $temp[0];
		$fact = $temp[1];
		$desc = $temp[2];
	}
}

/**
* XML <PGVRTextBoxSHandler> start elemnt handler
*
* @param array $attrs an array of key value pairs for the attributes
* @see PGVRTextBoxEHandler()
* @todo defaults to wiki
*/
function PGVRTextBoxSHandler($attrs) {
	global $printData, $printDataStack, $elemct, $pgvreport, $currentElement, $pgvreportStack, $PGVReportRoot;

	// string Background color code
	$bgcolor = '';
	if (!empty($attrs['bgcolor'])) $bgcolor = $attrs['bgcolor'];
	
	// boolean Wether or not fill the background color
	$fill = true;
	if (isset($attrs['fill'])) {
		if ($attrs['fill'] === '0') $fill = false;
		else if ($attrs['fill'] === '1') {
			$fill = true;
		}
	}

	// var boolean Whether or not a border should be printed around this box. 0 = no border, 1 = border. Default is 0
	$border = false;
	if (isset($attrs['border'])) {
		if ($attrs['border'] === '1') {
			$border = true;
		}
		else if ($attrs['border'] === '0') {
			$border = false;
		}
	}

	/**
	* Border style of rectangle. Array with keys among the following
	* <ul><li>L, T, R, B or combinations: Line style of left, top, right or bottom border.</li></ul>
	* @var string
	*/
	/** not yet in use
	$borderstyle = '';
	if (!empty($attrs['borderstyle'])) $borderstyle = $attrs['borderstyle'];
	*/
	
	// int The starting height of this cell. If the text wraps the height will automatically be adjusted
	$height = 0;
	if (!empty($attrs['height'])) $height = (int)$attrs['height'];
	// int Setting the width to 0 will make it the width from the current location to the margin
	$width = 0;
	if (!empty($attrs['width'])) $width = (int)$attrs['width'];
	
	// mixed Position the left corner of this box on the page. The default is the current position.
	$left = '.';
	if (isset($attrs['left'])) {
		if ($attrs['left'] === '.') $left = '.';
		else if (!empty($attrs['left'])) {
			$left = (int)$attrs['left'];
		}
		else if ($attrs['left'] === '0') {
			$left = 0;
		}
	}
	// mixed Position the top corner of this box on the page. the default is the current position
	$top = '.';
	if (isset($attrs['top'])) {
		if ($attrs['top'] === '.') $top = '.';
		else if (!empty($attrs['top'])) {
			$top = (int)$attrs['top'];
		}
		else if ($attrs['top'] === '0') {
			$top = 0;
		}
	}

	// 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
	$newline = false;
	if (isset($attrs['newline'])) {
		if ($attrs['newline'] === '1') $newline = true;
		else if ($attrs['newline'] === '0') {
			$newline = false;
		}
	}

	// boolean
	$pagecheck = true;
	if (isset($attrs['pagecheck'])) {
		if ($attrs['pagecheck'] === '0') $pagecheck = false;
		else if ($attrs['pagecheck'] === '1') {
			$pagecheck = true;
		}
	}
	// boolean Cell padding
	$padding = true;
	if (isset($attrs['padding'])) {
		if ($attrs['padding'] === '0') $padding = false;
		else if ($attrs['padding'] === '1') {
			$padding = true;
		}
	}
	// boolean Reset this box Height
	$reseth = false;
	if (isset($attrs['reseth'])) {
		if ($attrs['reseth'] === '1') $reseth = true;
		else if ($attrs['reseth'] === '0') {
			$reseth = false;
		}
	}

	// string Style of rendering
	$style = '';
	// fill and border is enought for now for user input
//	if (!empty($attrs['style'])) $style = $attrs['style'];

	array_push($printDataStack, $printData);
	$printData = false;

	array_push($pgvreportStack, $pgvreport);
	$pgvreport = $PGVReportRoot->createTextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth);
}

/**
* XML <PGVRTextBoxEHandler> end elemnt handler
*
* @see PGVRTextBoxSHandler()
*/
function PGVRTextBoxEHandler() {
	global $printData, $printDataStack, $pgvreport, $currentElement, $pgvreportStack;

	$printData = array_pop($printDataStack);
	$currentElement = $pgvreport;
	$pgvreport = array_pop($pgvreportStack);
	$pgvreport->addElement($currentElement);
}

/**
* @see PGVRTextEHandler()
* @todo add info to wiki about 'color'
* @todo more variables in PGVRText class, check it out
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRTextSHandler($attrs) {
	global $printData, $printDataStack, $pgvreport, $currentElement, $PGVReportRoot;

	array_push($printDataStack, $printData);
	$printData = true;

	// string The name of the PGVRStyle that should be used to render the text. 
	$style = '';
	if (!empty($attrs['style'])) $style = $attrs['style'];

	// string  The color of the text - Keep the black color as default
	$color = '';
	if (!empty($attrs['color'])) $color = $attrs['color'];

	$currentElement = $PGVReportRoot->createText($style, $color);
}

/**
* @see PGVRTextSHandler()
*/
function PGVRTextEHandler() {
	global $printData, $printDataStack, $pgvreport, $currentElement;

	$printData = array_pop($printDataStack);
	$pgvreport->addElement($currentElement);
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRGetPersonNameSHandler($attrs) {
	global $currentElement, $vars, $gedrec, $gedrecStack, $pgv_lang, $SHOW_ID_NUMBERS;

	$id = "";
	if (empty($attrs["id"])) {
		$match = array();
		$ct = preg_match("/0 @(.+)@/", $gedrec, $match);
		if ($ct>0) $id = $match[1];
	}
	else {
		$match = array();
		$ct = preg_match("/\\$(.+)/", $attrs["id"], $match);
		if ($ct>0) {
			if (isset($vars[$match[1]]["id"])) {
				$id = $vars[$match[1]]["id"];
			}
		}
		else {
			$ct = preg_match("/@(.+)/", $attrs["id"], $match);
			if ($ct>0) {
				$gmatch = array();
				$gt = preg_match("/\d $match[1] @([^@]+)@/", $gedrec, $gmatch);
				//print $gt;
				if ($gt > 0) {
					$id = $gmatch[1];
					//print "[$id]";
				}
			}
			else {
				$id = $attrs["id"];
			}
		}
	}
	if (!empty($id)) {
		$record = GedcomRecord::getInstance($id);
		if (is_null($record)) return;
		if (!$record->canDisplayDetails()) $currentElement->addText($pgv_lang["private"]);
		else {
			$name = strip_tags($record->getFullName());
			if (!empty($attrs["truncate"])) {
				//short-circuit with the faster strlen
				if (strlen($name)>$attrs["truncate"] && UTF8_strlen($name)>$attrs["truncate"]) {
				$name = preg_replace("/\(.*\) ?/", "", $name); //removes () and text inbetween - what about ", [ and { etc?
				}
				if (strlen($name)>$attrs["truncate"] && UTF8_strlen($name)>$attrs["truncate"]) {
					$words = explode(' ', $name);
					$name = $words[count($words)-1];
					for($i=count($words)-2; $i>=0; $i--) {
						$len = UTF8_strlen($name);
						for($j=count($words)-3; $j>=0; $j--) {
							$len += UTF8_strlen($words[$j]);
						}
						if ($len>$attrs["truncate"]) {
							$first_letter = get_first_letter($words[$i]);
							//do not show " of nick-names
							if ($first_letter != '"') $name = get_first_letter($words[$i]).". ".$name;
						}	
						else $name = $words[$i]." ".$name;
					}
				}
			}
			else {
				$addname = strip_tags($record->getAddName());
				if (!empty($addname)) {
					$name .= " ".$addname;
				}
			}
			$currentElement->addText(trim($name));
		}
	}
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRGedcomValueSHandler($attrs) {
	global $currentElement, $vars, $gedrec, $gedrecStack, $fact, $desc, $type, $SHOW_PEDIGREE_PLACES, $pgv_lang;

	$id = "";
	$gmatch = array();
	$gt = preg_match("/0 @(.+)@/", $gedrec, $gmatch);
	if ($gt > 0) {
		$id = $gmatch[1];
	}

	$tag = $attrs["tag"];
	// print $tag;
	if (!empty($tag)) {
		if ($tag=="@desc") {
			if (showFact($fact, $id) && showFactDetails($fact,$id)) {
				$value = $desc;
			}
			else $value = "";
			$value = trim($value);
			$currentElement->addText($value);
		}
		if ($tag=="@id") {
			$currentElement->addText($id);
		}
		else {
			$tag = preg_replace("/@fact/", $fact, $tag);
			if (empty($attrs["level"])) {
				$temp = explode(' ', trim($gedrec));
				$level = $temp[0];
				if ($level==0) {
					$level++;
				}
			}
			else $level = $attrs["level"];
			$truncate = "";
			if (isset($attrs["truncate"])) {
				$truncate=$attrs["truncate"];
			}
			$tags = explode(':', $tag);
			//-- check all of the tags for privacy
			foreach($tags as $subtag) {
				if (!empty($subtag)) {
					if (!showFact($tag, $id)||!showFactDetails($tag,$id)) {
						return;
					}
				}
			}
			$value = get_gedcom_value($tag, $level, $gedrec, $truncate);
			if (showFact($fact, $id)&&showFactDetails($fact,$id)) {
				$currentElement->addText($value);
			}
		}
	}
}

/**
* @see PGVRRepeatTagEHandler()
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRRepeatTagSHandler($attrs) {
	global $repeats, $repeatsStack, $gedrec, $repeatBytes, $parser, $parserStack, $processRepeats, $fact, $desc;

	$processRepeats++;
	if ($processRepeats>1) return;

	$id = "";
	$gmatch = array();
	$gt = preg_match("/0 @(.+)@/", $gedrec, $gmatch);
	if ($gt > 0) {
		$id = $gmatch[1];
	}

	array_push($repeatsStack, array($repeats, $repeatBytes));
	$repeats = array();
	$repeatBytes = xml_get_current_line_number($parser);

	$tag = "";
	if (isset($attrs["tag"])) $tag = $attrs["tag"];
	if (!empty($tag)) {
		if ($tag=="@desc") {
			if (showFact($fact, $id) and showFactDetails($fact,$id)) $value = $desc;
			else $value = "";
			$value = trim($value);
			$currentElement->addText($value);
		}
		else {
			$tag = preg_replace("/@fact/", $fact, $tag);
			$tags = explode(':', $tag);
			$temp = explode(' ', trim($gedrec));
			$level = $temp[0];
			if ($level==0) $level++;
			$subrec = $gedrec;
			$t = $tag;
			for($i=0; $i<count($tags); $i++) {
				$t = $tags[$i];
				if (!empty($t)) {
				if ($level==1 && strstr("CHIL,FAMS,FAMC", $t)===false and (!showFact($t, $id) or !showFactDetails($t,$id))) return;
				if ($i<count($tags)-1) {
					$subrec = get_sub_record($level, "$level $t", $subrec);
					if (empty($subrec)) {
						$level--;
						$subrec = get_sub_record($level, "@ $t", $gedrec);
						if (empty($subrec)) return;
					}
				}
				//print "[$level $t] ";
				$level++;
			}
			}
			$level--;
			if ($level!=1 || strstr("CHIL,FAMS,FAMC", $t)!==false || (showFact($t, $id) && showFactDetails($t,$id))) {
				$match = array();
				$ct = preg_match_all("/$level $t(.*)/", $subrec, $match, PREG_SET_ORDER);
				//print "$ct $subrec";
				for($i=0; $i<$ct; $i++) {
					$rec = get_sub_record($level, "$level $t", $gedrec, $i+1);
					$repeats[] = $rec;
				}
				//$repeats = array_reverse($repeats);
				//print_r($repeats);
			}
		}
	}
}

/**
* @see PGVRRepeatTagSHandler()
* @todo add info
*/
function PGVRRepeatTagEHandler() {
	global $repeats, $repeatsStack, $repeatBytes, $parser, $parserStack, $report, $pgvreport, $gedrec, $processRepeats;

	$processRepeats--;
	if ($processRepeats>0) return;

	$line = xml_get_current_line_number($parser)-1;
	$lineoffset = 0;
	for($i=0; $i<count($repeatsStack); $i++) {
		$p = $repeatsStack[$i];
		$l = $p[1];
		$lineoffset += $l;
	}
	//-- read the xml from the file
	$lines = file($report);
	$reportxml = "<tempdoc>\n";
	while(strstr($lines[$lineoffset+$repeatBytes], "<PGVRRepeatTag")===false) $lineoffset--;
	$lineoffset++;
	$line1 = $repeatBytes;
	$ct = 1;
	while(($ct>0)&&($line1<$line+2)) {
		if (strstr($lines[$lineoffset+$line1], "<PGVRRepeatTag")!==false) $ct++;
		if (strstr($lines[$lineoffset+$line1], "</PGVRRepeatTag")!==false) $ct--;
		$line1++;
	}
	$line = $line1-1;
	for($i=$repeatBytes+$lineoffset; $i<$line+$lineoffset; $i++) $reportxml .= $lines[$i];
	$reportxml .= "</tempdoc>\n";
	//print $reportxml."\n";
	array_push($parserStack, $parser);

	$oldgedrec = $gedrec;
	for($i=0; $i<count($repeats); $i++) {
		$gedrec = $repeats[$i];
		//-- start the sax parser
		$repeat_parser = xml_parser_create();
		$parser = $repeat_parser;
		//-- make sure everything is case sensitive
		xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
		//-- set the main element handler functions
		xml_set_element_handler($repeat_parser, "startElement", "endElement");
		//-- set the character data handler
		xml_set_character_data_handler($repeat_parser, "characterData");

		if (!xml_parse($repeat_parser, $reportxml, true)) {
			printf($reportxml."\nPGVRRepeatTagEHandler XML error: %s at line %d", xml_error_string(xml_get_error_code($repeat_parser)), xml_get_current_line_number($repeat_parser));
			print_r($repeatsStack);
			debug_print_backtrace();
			exit;
		}
		xml_parser_free($repeat_parser);
	}
	$parser = array_pop($parserStack);

	$gedrec = $oldgedrec;
	$temp = array_pop($repeatsStack);
	$repeats = $temp[0];
	$repeatBytes = $temp[1];
}

/**
* Variable lookap
*
* @param array $attrs an array of key value pairs for the attributes
* @see PGVRSetVarSHandler()
* @todo check the date attribute, what is this?
*/
function PGVRvarSHandler($attrs) {
	global $currentElement, $vars, $gedrec, $gedrecStack, $pgv_lang, $language_settings, $factarray, $fact, $desc, $type;

	$var = $attrs["var"];
	if (!empty($var)) {
		if (!empty($vars[$var]['id'])) {
			$var = $vars[$var]['id'];
		}
		else {
			$tfact = $fact;
			if ($fact=="EVEN" || $fact=="FACT") $tfact = $type;
			$var = preg_replace(array("/\[/","/\]/","/@fact/","/@desc/"), array("['","']",$tfact,$desc), $var);
			eval("if (!empty(\$$var)) \$var = \$$var;");
			$match = array();
			$ct = preg_match("/factarray\['(.*)'\]/", $var, $match);
			if ($ct>0) $var = $match[1];
		}
		if (!empty($attrs["date"])) {
			$g = new GedcomDate($var);
			$var = $g->Display();
		}
		$currentElement->addText($var);
	}
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRvarLetterSHandler($attrs) {
	global $currentElement, $factarray, $fact, $desc;

	$var = $attrs["var"];
	if (!empty($var)) {
		$tfact = $fact;
		$var = preg_replace(array("/\[/","/\]/","/@fact/","/@desc/"), array("['","']",$tfact,$desc), $var);
		eval("if (!empty(\$$var)) \$var = \$$var;");

		$letter = get_first_letter($var);
		$currentElement->addText($letter);
	}
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
* @see PGVRFactsEHandler()
*/
function PGVRFactsSHandler($attrs) {
	global $repeats, $repeatsStack, $gedrec, $parser, $parserStack, $repeatBytes, $processRepeats, $vars;

	$processRepeats++;
	if ($processRepeats>1) return;

	$families = 1;
	if (isset($attrs["families"])) $families = $attrs["families"];

	array_push($repeatsStack, array($repeats, $repeatBytes));
	$repeats = array();
	$repeatBytes = xml_get_current_line_number($parser);

	$id = "";
	$gmatch = array();
	$gt = preg_match("/0 @(.+)@/", $gedrec, $gmatch);
	if ($gt > 0) {
		$id = $gmatch[1];
	}

	$tag = "";
	if (isset($attrs["ignore"])) $tag .= $attrs["ignore"];
	$match = array();
	$ct = preg_match("/\\$(.+)/", $tag, $match);
	if ($ct>0) {
		$tag = $vars[$match[1]]["id"];
	}

	if (empty($attrs["diff"]) && !empty($id)) {
		$record = GedcomRecord::getInstance($id);
		$facts = $record->getFacts(explode(",",$tag));
		if (!is_array($facts)) $facts = array($facts);
		sort_facts($facts);
		$repeats = array();
		foreach($facts as $event) {
			if (strpos($tag.",",$event->getTag())===false) $repeats[]=$event->getGedComRecord();
		}
	} else {
		global $nonfacts;
		$nonfacts = preg_split("/[\s,;:]/", $tag);
		$record = new GedcomRecord($gedrec);
		switch ($record->getType()) {
			case 'INDI':
				$record=new Person($gedrec);
				break;
			case 'FAM':
				$record=new Family($gedrec);
				break;
			case 'SOUR':
				$record=new Source($gedrec);
				break;
			case 'REPO':
				$record=new Repository($gedrec);
				break;
			case 'NOTE':
				$record=new Note($gedrec);
				break;
		}
		$oldrecord = GedcomRecord::getInstance($record->getXref());
		$oldrecord->diffMerge($record);
		$facts = $oldrecord->getFacts();
		foreach ($facts as $fact) {
			if (strstr($fact->getGedcomRecord(), "PGV_NEW")!==false) {
				$repeats[]=$fact->getGedcomRecord();
			}
		}
	}
}

/**
* @todo add info
* @see PGVRFactsSHandler()
*/
function PGVRFactsEHandler() {
	global $repeats, $repeatsStack, $repeatBytes, $parser, $parserStack, $report, $gedrec, $fact, $desc, $type, $processRepeats;

	$processRepeats--;
	if ($processRepeats>0) return;

	$line = xml_get_current_line_number($parser)-1;
	$lineoffset = 0;
	for($i=0; $i<count($repeatsStack); $i++) {
		$p = $repeatsStack[$i];
		$l = $p[1];
		$lineoffset += $l;
	}
	//-- read the xml from the file
	$lines = file($report);
	$reportxml = "<tempdoc>\n";
	while($lineoffset+$repeatBytes>0 && strstr($lines[$lineoffset+$repeatBytes], "<PGVRFacts ")===false) $lineoffset--;
	$lineoffset++;
//	var_dump($lineoffset);
	for($i=$repeatBytes+$lineoffset; $i<$line+$lineoffset; $i++) {
//		print $i." ".htmlentities($lines[$i],ENT_COMPAT,'UTF-8');
		$reportxml .= $lines[$i];
	}
	$reportxml .= "</tempdoc>\n";

	array_push($parserStack, $parser);
	$oldgedrec = $gedrec;
	for($i=0; $i<count($repeats); $i++) {
		$gedrec = $repeats[$i];
		$match = array();
		$ft = preg_match("/1 (\w+)(.*)/", $gedrec, $match);
		$fact = "";
		$desc = "";
		if ($ft > 0) {
			$fact = $match[1];
			if ($fact=="EVEN" || $fact=="FACT") {
				$tmatch = array();
				$tt = preg_match("/2 TYPE (.+)/", $gedrec, $tmatch);
				if ($tt>0) {
					$type = trim($tmatch[1]);
				}
			}
			$desc = trim($match[2]);
//			print $fact."[".$desc."]";
			$desc .= get_cont(2, $gedrec);
		}
		//-- start the sax parser
		$repeat_parser = xml_parser_create();
		$parser = $repeat_parser;
		//-- make sure everything is case sensitive
		xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
		//-- set the main element handler functions
		xml_set_element_handler($repeat_parser, "startElement", "endElement");
		//-- set the character data handler
		xml_set_character_data_handler($repeat_parser, "characterData");

		if (!xml_parse($repeat_parser, $reportxml, true)) {
			die(sprintf($reportxml."\nPGVRFactsEHandler XML error: %s at line %d", xml_error_string(xml_get_error_code($repeat_parser)), xml_get_current_line_number($repeat_parser)));
			debug_print_backtrace();
		}
		xml_parser_free($repeat_parser);
	}
	$parser = array_pop($parserStack);
	$gedrec = $oldgedrec;
	$temp = array_pop($repeatsStack);
	$repeats = $temp[0];
	$repeatBytes = $temp[1];
}

/**
* Setting upp or changing variables in the XML
* The XML variable name and value is stored in the global variable $vars
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRSetVarSHandler($attrs) {
	global $vars, $gedrec, $gedrecStack, $pgv_lang, $factarray, $fact, $desc, $type, $generation;

	$name = $attrs["name"];
	$value = $attrs["value"];

	$match = array();
	// Current GEDCOM record strings
	if ($value == "@ID") {
		$ct = preg_match("/0 @(.+)@/", $gedrec, $match);
		if ($ct > 0) {
			$value = $match[1];
		}
	}
	else if ($value == "@fact") {
		$value = $fact;
	}
	else if ($value == "@desc") {
		$value = $desc;
	}
	else if ($value == "@generation") {
		$value = $generation;
	}
	else {
		$ct = preg_match("/@(\w+)/", $value, $match);
		if ($ct > 0) {
			$gmatch = array();
			$gt = preg_match("/\d $match[1] (.+)/", $gedrec, $gmatch);
			if ($gt > 0) {
				$value = preg_replace("/@/", "", trim($gmatch[1]));
			}
		}
	}

	$ct = preg_match("/\\$(\w+)/", $name, $match);
	if ($ct>0) {
		$name = $vars["'".$match[1]."'"]["id"];
	}

	if ((substr($value, 0, 10) == "\$pgv_lang[") || (substr($value, 0, 11) == "\$factarray[")) {
		$var = preg_replace(array("/\[/","/\]/"), array("['","']"), $value);
		eval("\$value = $var;");
	}

	$ct = preg_match_all("/\\$(\w+)/", $value, $match, PREG_SET_ORDER);
	for($i=0; $i<$ct; $i++) {
		$t = $vars[$match[$i][1]]["id"];
		$value = preg_replace("/\\$".$match[$i][1]."/", $t, $value, 1);
	}

	// Arithmetic functions
	$ct = preg_match("/(\d+)\s*([\-\+\*\/])\s*(\d+)/", $value, $match);
	if ($ct > 0) {
		switch($match[2]) {
			case '-':
				$t = $match[1] - $match[3];
				$value = preg_replace("/".$match[1]."\s*([\-\+\*\/])\s*".$match[3]."/", $t, $value);
				break;
			case '+':
				$t = $match[1] + $match[3];
				$value = preg_replace("/".$match[1]."\s*([\-\+\*\/])\s*".$match[3]."/", $t, $value);
				break;
			case '*':
				$t = $match[1] * $match[3];
				$value = preg_replace("/".$match[1]."\s*([\-\+\*\/])\s*".$match[3]."/", $t, $value);
				break;
			case '/':
				$t = $match[1] / $match[3];
				$value = preg_replace("/".$match[1]."\s*([\-\+\*\/])\s*".$match[3]."/", $t, $value);
				break;
		}
	}
	if (strstr($value, "@")!==false) {
		$value="";
	}
	$vars[$name]["id"]=$value;
}

/**
* @todo add info
* @see PGVRifEHandler()
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRifSHandler($attrs) {
	global $vars, $gedrec, $processIfs, $fact, $desc, $generation, $POSTAL_CODE;

	if ($processIfs>0) {
		$processIfs++;
		return;
	}

	$vars['POSTAL_CODE']['id'] = $POSTAL_CODE;
	$condition = $attrs["condition"];
	$condition = preg_replace("/\\$(\w+)/", "\$vars[\"$1\"][\"id\"]", $condition);
	$condition = preg_replace(array("/ LT /", "/ GT /"), array("<", ">"), $condition);
	$match = array();
	$ct = preg_match_all("/@([\w:\.]+)/", $condition, $match, PREG_SET_ORDER);
	for($i=0; $i<$ct; $i++) {
		$id = $match[$i][1];
		$value="\"\"";
		if ($id=="ID") {
			$ct = preg_match("/0 @(.+)@/", $gedrec, $match);
			if ($ct>0) $value = "'".$match[1]."'";
		}
		else if ($id=="fact") {
			$value = "\"$fact\"";
		}
		else if ($id=="desc") {
			$value = "\"$desc\"";
		}
		else if ($id=="generation") {
			$value = "\"$generation\"";
		}
		else {
			$temp = explode(' ', trim($gedrec));
			$level = $temp[0];
			if ($level==0) $level++;
			$value = get_gedcom_value($id, $level, $gedrec, "", false);
			//print "level:$level id:$id value:$value ";
			if (empty($value)) {
				$level++;
				$value = get_gedcom_value($id, $level, $gedrec, "", false);
				//print "level:$level id:$id value:$value gedrec:$gedrec<br />\n";
			}
			$value = "\"".preg_replace("/'/", "\\'", $value)."\"";
		}
		$condition = preg_replace("/@$id/", $value, $condition);
	}
	$condition = "if ($condition) return true; else return false;";
	$ret = @eval($condition);
	if (!$ret) {
		$processIfs++;
	}
}

/**
* @todo add info
* @see PGVRifSHandler()
*/
function PGVRifEHandler() {
	global $processIfs;
	if ($processIfs>0) $processIfs--;
}

/**
* @todo add info
* @todo check $style what it does
* @param array $attrs an array of key value pairs for the attributes
* @see PGVRFootnoteEHandler()
*/
function PGVRFootnoteSHandler($attrs) {
	global $printData, $printDataStack, $currentElement, $footnoteElement, $PGVReportRoot;

	array_push($printDataStack, $printData);
	$printData = true;

	// string
	$style = '';
	if (!empty($attrs['style'])) $style=$attrs['style'];
	
	$footnoteElement = $currentElement;
	$currentElement = $PGVReportRoot->createFootnote($style);
}

/**
* @todo add info
* @see PGVRFootnoteSHandler()
*/
function PGVRFootnoteEHandler() {
	global $printData, $printDataStack, $pgvreport, $currentElement, $footnoteElement;

	$printData = array_pop($printDataStack);
	$temp = trim($currentElement->getValue());
	if (strlen($temp)>3) $pgvreport->addElement($currentElement);
	$currentElement = $footnoteElement;
}

/**
* XML <PGVRFootnoteTexts /> element
*
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRFootnoteTextsSHandler($attrs) {
	global $pgvreport;

	$temp = 'footnotetexts';
	$pgvreport->addElement($temp);
}

/**
* XML element Forced line break handler - HTML code
*
* @todo br must be lovercase?
*/
function brSHandler() {
	global $printData, $currentElement, $processGedcoms;
	if ($printData && ($processGedcoms==0)) $currentElement->addText("<br />");
}

/**
* XML <sp />element Forced space handler
*
* @todo sp must be lovercase?
* @todo add info to wiki - missing function
*/
function spSHandler() {
	global $printData, $currentElement, $processGedcoms;
	if ($printData && ($processGedcoms==0)) $currentElement->addText(" ");
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRHighlightedImageSHandler($attrs) {
	global $gedrec, $pgvreport, $PGVReportRoot;

	$id = "";
	$gmatch = array();
	$gt = preg_match("/0 @(.+)@/", $gedrec, $gmatch);
	if ($gt > 0) {
		$id = $gmatch[1];
	}

	// mixed Position the top corner of this box on the page. the default is the current position
	$top = '.';
	if (isset($attrs['top'])) {
		if ($attrs['top'] === '0') $top = 0;
		else if ($attrs['top'] === '.') {
			$top = '.';
		}
		else if (!empty($attrs['top'])) {
			$top = (int)$attrs['top'];
		}
	}

	// mixed Position the left corner of this box on the page. the default is the current position
	$left = '.';
	if (isset($attrs['left'])) {
		if ($attrs['left'] === '0') $left = 0;
		else if ($attrs['left'] === '.') {
			$left = '.';
		}
		else if (!empty($attrs['left'])) {
			$left = (int)$attrs['left'];
		}
	}

	// string Align the image in left, center, right
	$align = '';
	if (!empty($attrs["align"])) $align = $attrs["align"];

	// string Next Line should be T:next to the image, N:next line
	$ln = '';
	if (!empty($attrs["ln"])) $ln = $attrs["ln"];

	$width = 0;
	$height = 0;
	if (!empty($attrs["width"])) $width = (int)$attrs["width"];
	if (!empty($attrs["height"])) $height = (int)$attrs["height"];

	if (showFact("OBJE", $id)) {
		$media = find_highlighted_object($id, $gedrec);
		if (!empty($media["file"])) {
			if (preg_match("/(jpg)|(jpeg)|(png)$/i", $media["file"])>0) {
				if (file_exists($media["file"])) {
					$size = findImageSize($media["file"]);
					if (($width>0)&&($height==0)) {
						$perc = $width / $size[0];
						$height= round($size[1]*$perc);
					}
					else if (($height>0)&&($width==0)) {
						$perc = $height / $size[1];
						$width= round($size[0]*$perc);
					}
					else {
						$width = $size[0];
						$height = $size[1];
					}
					$image = $PGVReportRoot->createImage($media["file"], $left, $top, $width, $height, $align, $ln);
					$pgvreport->addElement($image);
				}
			}
		}
	}
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRImageSHandler($attrs) {
	global $gedrec, $pgvreport, $MEDIA_DIRECTORY, $PGVReportRoot;

	// mixed Position the top corner of this box on the page. the default is the current position
	$top = '.';
	if (isset($attrs['top'])) {
		if ($attrs['top'] === '0') $top = 0;
		else if ($attrs['top'] === '.') {
			$top = '.';
		}
		else if (!empty($attrs['top'])) {
			$top = (int)$attrs['top'];
		}
	}

	// mixed Position the left corner of this box on the page. the default is the current position
	$left = '.';
	if (isset($attrs['left'])) {
		if ($attrs['left'] === '0') $left = 0;
		else if ($attrs['left'] === '.') {
			$left = '.';
		}
		else if (!empty($attrs['left'])) {
			$left = (int)$attrs['left'];
		}
	}

	// string Align the image in left, center, right
	$align = '';
	if (!empty($attrs["align"])) $align = $attrs["align"];

	// string Next Line should be T:next to the image, N:next line
	$ln = 'T';
	if (!empty($attrs["ln"])) $ln = $attrs["ln"];

	$width = 0;
	$height = 0;
	if (!empty($attrs["width"])) $width = (int)$attrs["width"];
	if (!empty($attrs["height"])) $height = (int)$attrs["height"];
	
	$file = "";
	if (!empty($attrs["file"])) $file = $attrs["file"];
	
	if ($file=="@FILE") {
		$match = array();
		$ct = preg_match("/\d OBJE @(.+)@/", $gedrec, $match);
		if ($ct > 0) $orec = find_gedcom_record($match[1]);
		else $orec = $gedrec;
		if (!empty($orec)) {
			$fullpath = extract_fullpath($orec);
			$filename = "";
			$filename = extract_filename($fullpath);
			$filename = $MEDIA_DIRECTORY.$filename;
			$filename = trim($filename);
			if (!empty($filename)) {
				if (preg_match("/(jpg)|(jpeg)|(png)$/i", $filename)>0) {
					if (file_exists($filename)) {
						$size = findImageSize($filename);
						if (($width > 0) and ($height == 0)) {
							$perc = $width / $size[0];
							$height= round($size[1]*$perc);
						}
						else if (($height > 0) and ($width == 0)) {
							$perc = $height / $size[1];
							$width= round($size[0]*$perc);
						}
						else {
							$width = $size[0];
							$height = $size[1];
						}
						$image = $PGVReportRoot->createImage($filename, $left, $top, $width, $height, $align, $ln);
						$pgvreport->addElement($image);
					}
				}
			}
		}
	}
	else {
		$filename = $file;
		if (preg_match("/(jpg)|(jpeg)|(png)|(gif)$/i", $filename)>0) {
			if (file_exists($filename)) {
				$size = findImageSize($filename);
				if (($width>0)&&($height==0)) {
					$perc = $width / $size[0];
					$height= round($size[1]*$perc);
				}
				else if (($height>0)&&($width==0)) {
					$perc = $height / $size[1];
					$width= round($size[0]*$perc);
				}
				else {
					$width = $size[0];
					$height = $size[1];
				}
				$image = $PGVReportRoot->createImage($filename, $left, $top, $width, $height, $align, $ln);
				$pgvreport->addElement($image);
			}
		}
	}
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRLineSHandler($attrs) {
	global $pgvreport,$PGVReportRoot;

	// Start horizontal position, current position (default)
	$x1 = '.';
	if (isset($attrs['x1'])) {
		if ($attrs['x1'] === '0') $x1 = 0;
		else if ($attrs['x1'] === '.') {
			$x1 = '.';
		}
		else if (!empty($attrs['x1'])) {
			$x1 = (int)$attrs['x1'];
		}
	}
	// Start vertical position, current position (default)
	$y1 = '.';
	if (isset($attrs['y1'])) {
		if ($attrs['y1'] === '0') $y1 = 0;
		else if ($attrs['y1'] === '.') {
			$y1 = '.';
		}
		else if (!empty($attrs['y1'])) {
			$y1 = (int)$attrs['y1'];
		}
	}
	// End horizontal position, maximum width (default)
	$x2 = '.';
	if (isset($attrs['x2'])) {
		if ($attrs['x2'] === '0') $x2 = 0;
		else if ($attrs['x2'] === '.') {
			$x2 = '.';
		}
		else if (!empty($attrs['x2'])) {
			$x2 = (int)$attrs['x2'];
		}
	}
	// End vertical position
	$y2 = '.';
	if (isset($attrs['y2'])) {
		if ($attrs['y2'] === '0') $y2 = 0;
		else if ($attrs['y2'] === '.') {
			$y2 = '.';
		}
		else if (!empty($attrs['y2'])) {
			$y2 = (int)$attrs['y2'];
		}
	}

	$line = $PGVReportRoot->createLine($x1, $y1, $x2, $y2);
	$pgvreport->addElement($line);
}

/**
* @todo add info
* @see PGVRListEHandler()
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRListSHandler($attrs) {
	global $pgvreport, $gedrec, $repeats, $repeatBytes, $list, $repeatsStack, $processRepeats, $parser, $vars, $sortby;
	global $pgv_changes, $GEDCOM, $TBLPREFIX;

	$processRepeats++;
	if ($processRepeats > 1) return;

	if (isset($attrs["sortby"])) {
		$sortby = $attrs["sortby"];
		$vmatch = array();
		if (preg_match("/\\$(\w+)/", $sortby, $vmatch) > 0) {
			$sortby = $vars[$vmatch[1]]["id"];
			$sortby = trim($sortby);
		}
	} else {
		$sortby = "NAME";
	}

	if (isset($attrs["list"])) {
		$listname=$attrs["list"];
	} else {
		$listname = "individual";
	}

	// Some filters/sorts can be applied using SQL, while others require PHP
	switch ($listname) {
	case 'pending':
		$list=array();
		foreach ($pgv_changes as $changes) {
			$change=end($changes);
			if ($change['gedcom']==$GEDCOM) {
				$list[]=new GedcomRecord($change['undo']);
			}
		}
		break;
	case 'individual':
	case 'family':
		$sql_col_prefix=substr($listname,0,1).'_'; // i_ for individual, f_ for family, etc.
		$sql_join=array();
		$sql_where=array($sql_col_prefix."file=".PGV_GED_ID);
		$sql_order_by=array();
		foreach ($attrs as $attr=>$value) {
			if (strpos($attr, 'filter')===0 && $value) {
				// Substitute global vars
				$value=preg_replace('/\$(\w+)/e', '$vars["\\1"]["id"]', $value);
				// Convert the various filters into SQL
				$match = array();
				if (preg_match('/^(\w+):DATE (LTE|GTE) (.+)$/', $value, $match)) {
					$sql_join[]="JOIN {$TBLPREFIX}dates AS {$attr} ON ({$attr}.d_file={$sql_col_prefix}file AND {$attr}.d_gid={$sql_col_prefix}id)";
					$sql_where[]="{$attr}.d_fact='{$match[1]}'";
					$date=new GedcomDate($match[3]);
					if ($match[2]=='LTE') {
						$sql_where[]="{$attr}.d_julianday2<=".$date->minJD();
					} else {
						$sql_where[]="{$attr}.d_julianday1>=".$date->minJD();
					}
					if ($sortby==$match[1]) {
						$sortby='';
						$sql_order_by[]="{$attr}.d_julianday1";
					}
					unset($attrs[$attr]); // This filter has been fully processed
				} elseif ($listname=='individual' && preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) {
					$sql_join[]="JOIN {$TBLPREFIX}name AS {$attr} ON (n_file={$sql_col_prefix}file AND n_id={$sql_col_prefix}id)";
					$sql_where[]="{$attr}.n_sort ".PGV_DB::$LIKE." ".PGV_DB::quote(UTF8_strtoupper("%{$match[1]}%"));
					if ($sortby=='NAME') {
						$sortby='';
						$sql_order_by[]="{$attr}.n_sort";
					}
					unset($attrs[$attr]); // This filter has been fully processed
				} elseif ($listname=='family' && preg_match('/^NAME CONTAINS (.+)$/', $value, $match)) {
					// Eventually, family "names" will be stored in pgv_name.  Until then, an extra is needed....
					$sql_join[]="JOIN {$TBLPREFIX}link AS {$attr}a ON ({$attr}a.l_file={$sql_col_prefix}file AND {$attr}a.l_from={$sql_col_prefix}id)";
					$sql_join[]="JOIN {$TBLPREFIX}name AS {$attr}b ON ({$attr}b.n_file={$sql_col_prefix}file AND n_id={$sql_col_prefix}id)";
					$sql_where[]="{$attr}a.l_type=IN ('HUSB, 'WIFE')";
					$sql_where[]="{$attr}.n_sort ".PGV_DB::$LIKE." ".PGV_DB::quote(UTF8_strtoupper("%{$match[1]}%"));
					if ($sortby=='NAME') {
						$sortby='';
						$sql_order_by[]="{$attr}.n_sort";
					}
					unset($attrs[$attr]); // This filter has been fully processed
//returns all the record with the filter place - does not verify that we speak of a BIRT record for the birth report value=BIRT:PLAC etc. 
/* 				} elseif (preg_match('/^(?:\w+):PLAC CONTAINS (.+)$/', $value, $match)) {
					$sql_join[]="JOIN {$TBLPREFIX}places AS {$attr}a ON ({$attr}a.p_file={$sql_col_prefix}file)";
					$sql_join[]="JOIN {$TBLPREFIX}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={$sql_col_prefix}id)";
					$sql_where[]="{$attr}a.p_place ".PGV_DB::$LIKE." ".PGV_DB::quote(UTF8_strtoupper("%{$match[1]}%"));
					unset($attrs[$attr]); // This filter has been fully processed
*/
				} else {
					// TODO: what other filters can we apply in SQL?
					//var_dump($value);
				}
			}
		}
		if ($listname=='family') {
			$list=search_fams_custom($sql_join, $sql_where, $sql_order_by);
		} else {
			$list=search_indis_custom($sql_join, $sql_where, $sql_order_by);
		}
		break;
	default:
		die("Invalid list name: $listname");
	}

	$filters = array();
	$filters2 = array();
	if (isset($attrs["filter1"])) {
		$j=0;
		foreach($attrs as $key=>$value) {
			$ct = preg_match("/filter(\d)/", $key, $match);
			if ($ct>0) {
				$condition = $value;
				$ct = preg_match("/@(\w+)/", $condition, $match);
				if ($ct > 0) {
					$id = $match[1];
					$value="''";
					if ($id=="ID") {
						$ct = preg_match("/0 @(.+)@/", $gedrec, $match);
						if ($ct>0) $value = "'".$match[1]."'";
					}
					else if ($id=="fact") {
						$value = "'$fact'";
					}
					else if ($id=="desc") {
						$value = "'$desc'";
					}
					else {
						$ct = preg_match("/\d $id (.+)/", $gedrec, $match);
						if ($ct>0) $value = "'".preg_replace("/@/", "", trim($match[1]))."'";
					}
					$condition = preg_replace("/@$id/", $value, $condition);
				}
				//-- handle regular expressions
				$ct = preg_match("/([A-Z:]+)\s*([^\s]+)\s*(.+)/", $condition, $match);
				if ($ct>0) {
					$tag = trim($match[1]);
					$expr = trim($match[2]);
					$val = trim($match[3]);
					if (preg_match("/\\$(\w+)/", $val, $vmatch)>0) {
						$val = $vars[$vmatch[1]]["id"];
						$val = trim($val);
					}
					$searchstr = "";
					$tags = explode(':', $tag);
					//-- only limit to a level number if we are specifically looking at a level
					if (count($tags)>1) {
						$level = 1;
						foreach($tags as $t) {
							if (!empty($searchstr)) $searchstr.="[^\n]*(\n[2-9][^\n]*)*\n";
							//-- search for both EMAIL and _EMAIL... silly double gedcom standard
							if ($t=="EMAIL" || $t=="_EMAIL") $t="_?EMAIL";
							$searchstr .= $level." ".$t;
							$level++;
						}
					}
					else {
						if ($tag=="EMAIL" || $tag=="_EMAIL") $tag="_?EMAIL";
						$t = $tag;
						$searchstr = "1 ".$tag;
					}
					switch ($expr) {
						case "CONTAINS":
							if ($t=="PLAC") $searchstr.="[^\n]*[, ]".$val;
							else $searchstr.="[^\n]*".$val;
							$filters[] = $searchstr;
							break;
						default:
							if (!empty($val)) {
								$filters2[] = array("tag"=>$tag, "expr"=>$expr, "val"=>$val);
							}
							break;
					}
				}
			}
			$j++;
		}
	}
	//-- apply other filters to the list that could not be added to the search string
	if ($filters) {
		foreach ($list as $key=>$record) {
			foreach ($filters as $filter) {
				if (!preg_match('/'.$filter.'/i', $record->getGedcomRecord())) {
					unset($list[$key]);
					break;
				}
			}
		}
	}
	if ($filters2) {
		$mylist = array();
		foreach($list as $indi) {
			$key=$indi->getXref();
			$value=$indi->getGedcomRecord();
			$keep = true;
			foreach($filters2 as $filter) {
				if ($keep) {
					$tag = $filter["tag"];
					$expr = $filter["expr"];
					$val = $filter["val"];
					if ($val=="''") $val = "";
					$tags = explode(':', $tag);
					$t = end($tags);
					$v = get_gedcom_value($tag, 1, $value["gedcom"], '', false);
					//-- check for EMAIL and _EMAIL (silly double gedcom standard :P)
					if ($t=="EMAIL" && empty($v)) {
						$tag = preg_replace("/EMAIL/", "_EMAIL", $tag);
						$tags = explode(':', $tag);
						$t = end($tags);
						$v = get_sub_record(1, $tag, $value["gedcom"]);
					}


					$level = count($tags);
					switch ($expr) {
						case "GTE":
								if ($t=="DATE") {
									$date1 = new GedcomDate($v);
									$date2 = new GedcomDate($val);
									$keep = (GedcomDate::Compare($date1, $date2)>=0);
								}
								else if ($val >= $v) $keep=true;
							break;
						case "LTE":
								if ($t=="DATE") {
									$date1 = new GedcomDate($v);
									$date2 = new GedcomDate($val);
									$keep = (GedcomDate::Compare($date1, $date2)<=0);
								}
								else if ($val >= $v) $keep=true;
							break;
						case "SUBCONTAINS":
							$v = get_sub_record($level, $level." ".$tag, $value["gedcom"]);
							if (empty($v) && $tag=="ADDR") $v = get_sub_record($level+1, ($level+1)." ".$tag, $value["gedcom"]);
							$ct = preg_match("/$val\b/i", $v);
							if ($ct>0) $keep = true;
							else $keep = false;
							break;
						default:
							if ($v==$val) $keep=true;
							else $keep = false;
							break;
					}
				}
			}
			if ($keep) $mylist[$key]=$indi;
		}
		$list = $mylist;
	}
	
	switch ($sortby) {
	case 'NAME':
		uasort($list, array('GedcomRecord', 'Compare'));
		break;
	case 'ID':
		uasort($list, array('GedcomRecord', 'CompareId'));
		break;
	case 'CHAN':
		uasort($list, array('GedcomRecord', 'CompareChanDate'));
		break;
	case 'BIRT:DATE':
		uasort($list, array('Person', 'CompareBirtDate'));
		break;
	case 'DEAT:DATE':
		uasort($list, array('Person', 'CompareDeatDate'));
		break;
	case 'MARR:DATE':
		uasort($list, array('Family', 'CompareMarrDate'));
		break;
	default:
		// unsorted or already sorted by SQL
		break;
	}

	array_push($repeatsStack, array($repeats, $repeatBytes));
	$repeatBytes = xml_get_current_line_number($parser)+1;
}

/**
* @todo add info
* @see PGVRListSHandler()
*/
function PGVRListEHandler() {
	global $currentElement, $list, $repeats, $repeatsStack, $repeatBytes, $parser, $parserStack, $report, $pgvreport, $gedrec, $processRepeats, $list_total, $list_private;
	$processRepeats--;
	if ($processRepeats>0) return;

	//-- reset any text that may have been added parsing to the end of the loop
	// This also removes PGVRCell::text from the PageHeader, so I disable this for now
//	$currentElement->setText("");
	
	$line = xml_get_current_line_number($parser)-1;
	$lineoffset = 0;
	for($i=0; $i<count($repeatsStack); $i++) {
		$p = $repeatsStack[$i];
		$l = $p[1];
		$lineoffset += $l;
	}
	//-- read the xml from the file
	$lines = file($report);
	$reportxml = "<tempdoc>\n";
	while(strstr($lines[$lineoffset+$repeatBytes], "<PGVRList")===false && $lineoffset+$repeatBytes>0) $lineoffset--;
	$lineoffset++;
	$line1 = $repeatBytes;
	$ct = 1;
	while(($ct>0)&&($line1<$line+2)) {
		if (strstr($lines[$lineoffset+$line1], "<PGVRList")!==false) $ct++;
		if (strstr($lines[$lineoffset+$line1], "</PGVRList")!==false) $ct--;
		$line1++;
	}
	$line = $line1-1;
	for($i=$repeatBytes+$lineoffset; $i<$line+$lineoffset; $i++) $reportxml .= $lines[$i];
	$reportxml .= "</tempdoc>\n";
	//print htmlentities($reportxml,ENT_COMPAT,'UTF-8')."\n";
	array_push($parserStack, $parser);

	$oldgedrec = $gedrec;
	$list_total = count($list);
	$list_private = 0;
	foreach ($list as $record) {
		if ($record->canDisplayDetails()) {
			$gedrec=$record->getGedcomRecord();
			//-- start the sax parser
			$repeat_parser = xml_parser_create();
			$parser = $repeat_parser;
			//-- make sure everything is case sensitive
			xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
			//-- set the main element handler functions
			xml_set_element_handler($repeat_parser, "startElement", "endElement");
			//-- set the character data handler
			xml_set_character_data_handler($repeat_parser, "characterData");

			if (!xml_parse($repeat_parser, $reportxml, true)) {
				printf($reportxml."\nPGVRRepeatTagEHandler XML error: %s at line %d", xml_error_string(xml_get_error_code($repeat_parser)), xml_get_current_line_number($repeat_parser));
				print_r($repeatsStack);
				debug_print_backtrace();
				exit;
			}
			xml_parser_free($repeat_parser);
		}
		else $list_private++;
	}
	$parser = array_pop($parserStack);

	$gedrec = $oldgedrec;
	$temp = array_pop($repeatsStack);
	$repeats = $temp[0];
	$repeatBytes = $temp[1];
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
*/
function PGVRListTotalSHandler($attrs) {
	global $list_total, $list_private, $currentElement;

	if (empty($list_total)) $list_total = 0;

	if ($list_private==0) {
		$currentElement->addText($list_total);
	} else {
		$currentElement->addText(($list_total - $list_private)." / ".$list_total);
	}
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
* @see PGVRRelativesEHandler()
*/
function PGVRRelativesSHandler($attrs) {
	global $pgvreport, $gedrec, $repeats, $repeatBytes, $list, $repeatsStack, $processRepeats, $parser, $vars, $sortby;

	$processRepeats++;
	if ($processRepeats>1) return;

	$sortby = "NAME";
	if (isset($attrs["sortby"])) $sortby = $attrs["sortby"];
	$vmatch = array();
	if (preg_match("/\\$(\w+)/", $sortby, $vmatch)>0) {
		$sortby = $vars[$vmatch[1]]["id"];
		$sortby = trim($sortby);
	}

	$maxgen = -1;
	if (isset($attrs["maxgen"])) $maxgen = $attrs["maxgen"];
	if ($maxgen=="*") $maxgen = -1;

	$group = "child-family";
	if (isset($attrs["group"])) $group = $attrs["group"];
	if (preg_match("/\\$(\w+)/", $group, $vmatch)>0) {
		$group = $vars[$vmatch[1]]["id"];
		$group = trim($group);
	}

	$id = "";
	if (isset($attrs["id"])) $id = $attrs["id"];
	if (preg_match("/\\$(\w+)/", $id, $vmatch)>0) {
		$id = $vars[$vmatch[1]]["id"];
		$id = trim($id);
	}

	$showempty = false;
	if (isset($attrs["showempty"])) $showempty = $attrs["showempty"];
	if (preg_match("/\\$(\w+)/", $showempty, $vmatch)>0) {
		$showempty = $vars[$vmatch[1]]["id"];
		$showempty = trim($showempty);
	}


	$list = array();
	$person = Person::getInstance($id);
	if (!empty($person)) {
		$list[$id] = $person;
		switch ($group) {
			case "child-family":
				$famids = $person->getChildFamilies();
				foreach($famids as $family) {
					$husband = $family->getHusband();
					$wife = $family->getWife();
					if (!empty($husband)) {
						$list[$husband->getXref()] = $husband;
					}
					if (!empty($wife)) {
						$list[$wife->getXref()] = $wife;
					}
					$children = $family->getChildren();
					foreach($children as $child) {
						if (!empty($child)) $list[$child->getXref()] = $child;
					}
				}
				break;
			case "spouse-family":
				$famids = $person->getSpouseFamilies();
				foreach($famids as $family) {
				$husband = $family->getHusband();
					$wife = $family->getWife();
					if (!empty($husband)) {
						$list[$husband->getXref()] = $husband;
					}
					if (!empty($wife)) {
						$list[$wife->getXref()] = $wife;
					}
					$children = $family->getChildren();
					foreach($children as $child) {
						if (!empty($child)) $list[$child->getXref()] = $child;
					}
				}
				break;
			case "direct-ancestors":
				add_ancestors($list, $id,false, $maxgen, $showempty);
				break;
			case "ancestors":
				add_ancestors($list, $id,true, $maxgen, $showempty);
				break;
			case "descendants":
				$list[$id]->generation = 1;
				add_descendancy($list, $id, false, $maxgen);
				break;
			case "all":
				add_ancestors($list, $id, true, $maxgen, $showempty);
				add_descendancy($list, $id, true, $maxgen);
				break;
		}
	}

	switch ($sortby) {
	case 'NAME':
		uasort($list, array('GedcomRecord', 'Compare'));
		break;
	case 'ID':
		uasort($list, array('GedcomRecord', 'CompareId'));
		break;
	case 'BIRT:DATE':
		uasort($list, array('PGVReportBase', 'CompareBirthDate'));
		break;
	case 'DEAT:DATE':
		uasort($list, array('PGVReportBase', 'CompareDeathDate'));
		break;
	case 'generation':
		$newarray = array();
		reset($list);
		$genCounter = 1;
		while (count($newarray) < count($list)) {
			foreach ($list as $key => $value) {
				$generation = $value->generation;
				if ($generation == $genCounter) {
					$newarray[$key]->generation=$generation;
				}
			}
			$genCounter++;
		}
		$list = $newarray;
		break;
	default:
		// unsorted
		break;
	}
	array_push($repeatsStack, array($repeats, $repeatBytes));
	$repeatBytes = xml_get_current_line_number($parser)+1;
}

/**
* @todo add info
* @see PGVRRelativesSHandler()
*/
function PGVRRelativesEHandler() {
	global $list, $repeats, $repeatsStack, $repeatBytes, $parser, $parserStack, $report, $pgvreport, $gedrec, $processRepeats, $list_total, $list_private, $generation;

	$processRepeats--;
	if ($processRepeats>0) return;

	$line = xml_get_current_line_number($parser)-1;
	$lineoffset = 0;
	for($i=0; $i<count($repeatsStack); $i++) {
		$p = $repeatsStack[$i];
		$l = $p[1];
		$lineoffset += $l;
	}
	//-- read the xml from the file
	$lines = file($report);
	$reportxml = "<tempdoc>\n";
	while(strstr($lines[$lineoffset+$repeatBytes], "<PGVRRelatives")===false && $lineoffset+$repeatBytes>0) $lineoffset--;
	$lineoffset++;
	$line1 = $repeatBytes;
	$ct = 1;
	while(($ct>0)&&($line1<$line+2)) {
		if (strstr($lines[$lineoffset+$line1], "<PGVRRelatives")!==false) $ct++;
		if (strstr($lines[$lineoffset+$line1], "</PGVRRelatives")!==false) $ct--;
		$line1++;
	}
	$line = $line1-1;
	for($i=$repeatBytes+$lineoffset; $i<$line+$lineoffset; $i++) $reportxml .= $lines[$i];
	$reportxml .= "</tempdoc>\n";
//	print htmlentities($reportxml,ENT_COMPAT,'UTF-8')."\n";
	array_push($parserStack, $parser);

	$oldgedrec = $gedrec;
	$list_total = count($list);
	$list_private = 0;
	foreach($list as $key=>$value) {
		if (isset($value->generation)) $generation = $value->generation;
//KN		if (displayDetailsById($key)) {
			if (strpos($key,"empty")===0) continue; // key can be something like "empty7"
			$tmp=GedcomRecord::getInstance($key);	$gedrec = $tmp->getGedcomRecord();
			//-- start the sax parser
			$repeat_parser = xml_parser_create();
			$parser = $repeat_parser;
			//-- make sure everything is case sensitive
			xml_parser_set_option($repeat_parser, XML_OPTION_CASE_FOLDING, false);
			//-- set the main element handler functions
			xml_set_element_handler($repeat_parser, "startElement", "endElement");
			//-- set the character data handler
			xml_set_character_data_handler($repeat_parser, "characterData");

			if (!xml_parse($repeat_parser, $reportxml, true)) {
				printf($reportxml."\nPGVRRelativesEHandler XML error: %s at line %d", xml_error_string(xml_get_error_code($repeat_parser)), xml_get_current_line_number($repeat_parser));
				print_r($repeatsStack);
				debug_print_backtrace();
				exit;
			}
			xml_parser_free($repeat_parser);
//KN		}
//KN		else $list_private++;
	}
	$parser = array_pop($parserStack);

	$gedrec = $oldgedrec;
	$temp = array_pop($repeatsStack);
	$repeats = $temp[0];
	$repeatBytes = $temp[1];
}

/**
* @todo add info
* @todo no info on wiki
* @see PGVRElement::addText()
*/
function PGVRGenerationSHandler() {
	global $generation, $currentElement;

	if (empty($generation)) $generation = 1;

	$currentElement->addText($generation);
}

/**
* XML <PGVRNewPage /> elemnt handler
*
* Has to be placed in an element (header, pageheader, body or footer)
* @final
* @todo update wiki, this element is missing
*/
function PGVRNewPageSHandler() {
	global $pgvreport;

	$temp = 'addpage';
	$pgvreport->addElement($temp);
}

/**
* @todo add info
* @todo not on wiki
* @param array $attrs an array of key value pairs for the attributes
* @param string $tag HTML tag name
* @see HTMLEHandler()
* @see PGVReportBase::createHTML()
*/
function HTMLSHandler($tag, $attrs) {
	global $printData, $printDataStack, $pgvreportStack, $pgvreport, $currentElement, $PGVReportRoot;

	if ($tag=="tempdoc") return;
	array_push($pgvreportStack, $pgvreport);
	$pgvreport = $PGVReportRoot->createHTML($tag, $attrs);
	$currentElement = $pgvreport;

	array_push($printDataStack, $printData);
	$printData = true;
}

/**
* @todo add info
* @param array $attrs an array of key value pairs for the attributes
* @see HTMLSHandler()
*/
function HTMLEHandler($tag) {
	global $printData, $printDataStack, $pgvreport, $currentElement, $pgvreportStack;
	if ($tag=="tempdoc") return;

	$printData = array_pop($printDataStack);
	$currentElement = $pgvreport;
	$pgvreport = array_pop($pgvreportStack);
	if (!is_null($pgvreport)) $pgvreport->addElement($currentElement);
	else $pgvreport = $currentElement;
}

/**
* XML <PGVRTitleSHandler> start elemnt handler
*
* @todo add to wiki
* @see PGVRTitleEHandler()
* @final
*/
function PGVRTitleSHandler() {
	global $reportTitle;
	$reportTitle = true;
}

/**
* XML </PGVRTitleEHandler> end elemnt handler
*
* @see PGVRTitleSHandler()
* @final
*/
function PGVRTitleEHandler() {
	global $reportTitle;
	$reportTitle = false;
}

/**
* XML <PGVRDescriptionSHandler> start elemnt handler
*
* @todo add to wiki
* @see PGVRDescriptionEHandler()
* @final
*/
function PGVRDescriptionSHandler() {
	global $reportDescription;
	$reportDescription = true;
}

/**
* XML </PGVRDescriptionEHandler> end elemnt handler
*
* @see PGVRDescriptionSHandler()
* @final
*/
function PGVRDescriptionEHandler() {
	global $reportDescription;
	$reportDescription = false;
}
?>
