xref: /webtrees/app/Report/ReportParserBase.php (revision 46ee52088e7429650f7ae3b1b0b4fe2c9d275437)
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 XMLParser;
25
26use function call_user_func;
27use function fclose;
28use function feof;
29use function fopen;
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 resource|XMLParser The XML parser */
51    protected $xml_parser;
52
53    /** @var string Text contents of tags */
54    protected $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 = fopen($report, 'rb');
87
88        if ($fp === false) {
89            throw new Exception('Cannot open ' . $report);
90        }
91
92        while ($data = fread($fp, 4096)) {
93            if (!xml_parse($this->xml_parser, $data, feof($fp))) {
94                throw new DomainException(sprintf(
95                    'XML error: %s at line %d',
96                    xml_error_string(xml_get_error_code($this->xml_parser)),
97                    xml_get_current_line_number($this->xml_parser)
98                ));
99            }
100        }
101
102        fclose($fp);
103
104        xml_parser_free($this->xml_parser);
105    }
106
107    /**
108     * XML handler for an opening (or self-closing) tag.
109     *
110     * @param resource $parser The resource handler for the xml parser
111     * @param string   $name   The name of the xml element parsed
112     * @param string[] $attrs  An array of key value pairs for the attributes
113     *
114     * @return void
115     */
116    protected function startElement($parser, string $name, array $attrs): void
117    {
118        $method = $name . 'StartHandler';
119
120        if (method_exists($this, $method)) {
121            call_user_func([$this, $method], $attrs);
122        }
123    }
124
125    /**
126     * XML handler for a closing tag.
127     *
128     * @param resource $parser the resource handler for the xml parser
129     * @param string   $name   the name of the xml element parsed
130     *
131     * @return void
132     */
133    protected function endElement($parser, string $name): void
134    {
135        $method = $name . 'EndHandler';
136
137        if (method_exists($this, $method)) {
138            call_user_func([$this, $method]);
139        }
140    }
141
142    /**
143     * XML handler for character data.
144     *
145     * @param resource $parser The resource handler for the xml parser
146     * @param string   $data   The name of the xml element parsed
147     *
148     * @return void
149     */
150    protected function characterData($parser, string $data): void
151    {
152        $this->text .= $data;
153    }
154}
155