xref: /webtrees/app/Report/ReportParserBase.php (revision 09482a558a7989d76059e7f9911605cf836b77ba)
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 array<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