1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 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 XMLParser; 25 26use function fclose; 27use function feof; 28use function fread; 29use function method_exists; 30use function sprintf; 31use function xml_error_string; 32use function xml_get_current_line_number; 33use function xml_get_error_code; 34use function xml_parse; 35use function xml_parser_create; 36use function xml_parser_free; 37use function xml_parser_set_option; 38use function xml_set_character_data_handler; 39use function xml_set_element_handler; 40 41use const XML_OPTION_CASE_FOLDING; 42 43/** 44 * Class ReportParserBase 45 */ 46class ReportParserBase 47{ 48 // The XML parser 49 protected XMLParser $xml_parser; 50 51 /** @var string Text contents of tags */ 52 protected string $text = ''; 53 54 /** 55 * Create a parser for a report 56 * 57 * @param string $report The XML filename 58 * 59 * @throws Exception 60 */ 61 public function __construct(string $report) 62 { 63 $this->xml_parser = xml_parser_create(); 64 65 xml_parser_set_option($this->xml_parser, XML_OPTION_CASE_FOLDING, 0); 66 67 xml_set_element_handler( 68 $this->xml_parser, 69 function ($parser, string $name, array $attrs): void { 70 $this->startElement($parser, $name, $attrs); 71 }, 72 function ($parser, string $name): void { 73 $this->endElement($parser, $name); 74 } 75 ); 76 77 xml_set_character_data_handler( 78 $this->xml_parser, 79 function ($parser, string $data): void { 80 $this->characterData($parser, $data); 81 } 82 ); 83 84 $fp = fopen($report, 'rb'); 85 86 while ($data = fread($fp, 4096)) { 87 if (!xml_parse($this->xml_parser, $data, feof($fp))) { 88 throw new DomainException(sprintf( 89 'XML error: %s at line %d', 90 xml_error_string(xml_get_error_code($this->xml_parser)), 91 xml_get_current_line_number($this->xml_parser) 92 )); 93 } 94 } 95 96 fclose($fp); 97 98 xml_parser_free($this->xml_parser); 99 } 100 101 /** 102 * XML handler for an opening (or self-closing) tag. 103 * 104 * @param resource $parser The resource handler for the xml parser 105 * @param string $name The name of the xml element parsed 106 * @param array<string> $attrs An array of key value pairs for the attributes 107 * 108 * @return void 109 */ 110 protected function startElement($parser, string $name, array $attrs): void 111 { 112 $method = $name . 'StartHandler'; 113 114 if (method_exists($this, $method)) { 115 $this->$method($attrs); 116 } 117 } 118 119 /** 120 * XML handler for a closing tag. 121 * 122 * @param resource $parser the resource handler for the xml parser 123 * @param string $name the name of the xml element parsed 124 * 125 * @return void 126 */ 127 protected function endElement($parser, string $name): void 128 { 129 $method = $name . 'EndHandler'; 130 131 if (method_exists($this, $method)) { 132 $this->$method(); 133 } 134 } 135 136 /** 137 * XML handler for character data. 138 * 139 * @param resource $parser The resource handler for the xml parser 140 * @param string $data The name of the xml element parsed 141 * 142 * @return void 143 */ 144 protected function characterData($parser, string $data): void 145 { 146 $this->text .= $data; 147 } 148} 149