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