1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2021 webtrees development team 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18declare(strict_types=1); 19 20namespace Fisharebest\Webtrees\Report; 21 22use DomainException; 23use Exception; 24use Fisharebest\Webtrees\Registry; 25use XMLParser; 26 27use function call_user_func; 28use function fclose; 29use function feof; 30use function fread; 31use function method_exists; 32use function sprintf; 33use function xml_error_string; 34use function xml_get_current_line_number; 35use function xml_get_error_code; 36use function xml_parse; 37use function xml_parser_create; 38use function xml_parser_free; 39use function xml_parser_set_option; 40use function xml_set_character_data_handler; 41use function xml_set_element_handler; 42 43use const XML_OPTION_CASE_FOLDING; 44 45/** 46 * Class ReportParserBase 47 */ 48class ReportParserBase 49{ 50 /** @var XMLParser (resource before PHP 8.0) The XML parser */ 51 protected $xml_parser; 52 53 /** @var string Text contents of tags */ 54 protected string $text = ''; 55 56 /** 57 * Create a parser for a report 58 * 59 * @param string $report The XML filename 60 * 61 * @throws Exception 62 */ 63 public function __construct(string $report) 64 { 65 $this->xml_parser = xml_parser_create(); 66 67 xml_parser_set_option($this->xml_parser, XML_OPTION_CASE_FOLDING, false); 68 69 xml_set_element_handler( 70 $this->xml_parser, 71 function ($parser, string $name, array $attrs): void { 72 $this->startElement($parser, $name, $attrs); 73 }, 74 function ($parser, string $name): void { 75 $this->endElement($parser, $name); 76 } 77 ); 78 79 xml_set_character_data_handler( 80 $this->xml_parser, 81 function ($parser, string $data): void { 82 $this->characterData($parser, $data); 83 } 84 ); 85 86 $fp = Registry::filesystem()->root()->readStream($report); 87 88 while ($data = fread($fp, 4096)) { 89 if (!xml_parse($this->xml_parser, $data, feof($fp))) { 90 throw new DomainException(sprintf( 91 'XML error: %s at line %d', 92 xml_error_string(xml_get_error_code($this->xml_parser)), 93 xml_get_current_line_number($this->xml_parser) 94 )); 95 } 96 } 97 98 fclose($fp); 99 100 xml_parser_free($this->xml_parser); 101 } 102 103 /** 104 * XML handler for an opening (or self-closing) tag. 105 * 106 * @param resource $parser The resource handler for the xml parser 107 * @param string $name The name of the xml element parsed 108 * @param string[] $attrs An array of key value pairs for the attributes 109 * 110 * @return void 111 */ 112 protected function startElement($parser, string $name, array $attrs): void 113 { 114 $method = $name . 'StartHandler'; 115 116 if (method_exists($this, $method)) { 117 call_user_func([$this, $method], $attrs); 118 } 119 } 120 121 /** 122 * XML handler for a closing tag. 123 * 124 * @param resource $parser the resource handler for the xml parser 125 * @param string $name the name of the xml element parsed 126 * 127 * @return void 128 */ 129 protected function endElement($parser, string $name): void 130 { 131 $method = $name . 'EndHandler'; 132 133 if (method_exists($this, $method)) { 134 call_user_func([$this, $method]); 135 } 136 } 137 138 /** 139 * XML handler for character data. 140 * 141 * @param resource $parser The resource handler for the xml parser 142 * @param string $data The name of the xml element parsed 143 * 144 * @return void 145 */ 146 protected function characterData($parser, string $data): void 147 { 148 $this->text .= $data; 149 } 150} 151