xref: /webtrees/app/Report/ReportParserBase.php (revision 81b514b4672980e5db010e9d89b55eaf131e798f)
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, false);
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