xref: /dokuwiki/vendor/simplepie/simplepie/src/XML/Declaration/Parser.php (revision 8e88a29b81301f78509349ab1152bb09c229123e)
1<?php
2
3// SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue
4// SPDX-License-Identifier: BSD-3-Clause
5
6declare(strict_types=1);
7
8namespace SimplePie\XML\Declaration;
9
10/**
11 * Parses the XML Declaration
12 */
13class Parser
14{
15    /**
16     * XML Version
17     *
18     * @access public
19     * @var string
20     */
21    public $version = '1.0';
22
23    /**
24     * Encoding
25     *
26     * @access public
27     * @var string
28     */
29    public $encoding = 'UTF-8';
30
31    /**
32     * Standalone
33     *
34     * @access public
35     * @var bool
36     */
37    public $standalone = false;
38
39    private const STATE_BEFORE_VERSION_NAME = 'before_version_name';
40
41    private const STATE_VERSION_NAME = 'version_name';
42
43    private const STATE_VERSION_EQUALS = 'version_equals';
44
45    private const STATE_VERSION_VALUE = 'version_value';
46
47    private const STATE_ENCODING_NAME = 'encoding_name';
48
49    private const STATE_EMIT = 'emit';
50
51    private const STATE_ENCODING_EQUALS = 'encoding_equals';
52
53    private const STATE_STANDALONE_NAME = 'standalone_name';
54
55    private const STATE_ENCODING_VALUE = 'encoding_value';
56
57    private const STATE_STANDALONE_EQUALS = 'standalone_equals';
58
59    private const STATE_STANDALONE_VALUE = 'standalone_value';
60
61    private const STATE_ERROR = false;
62
63    /**
64     * Current state of the state machine
65     *
66     * @access private
67     * @var self::STATE_*
68     */
69    public $state = self::STATE_BEFORE_VERSION_NAME;
70
71    /**
72     * Input data
73     *
74     * @access private
75     * @var string
76     */
77    public $data = '';
78
79    /**
80     * Input data length (to avoid calling strlen() everytime this is needed)
81     *
82     * @access private
83     * @var int
84     */
85    public $data_length = 0;
86
87    /**
88     * Current position of the pointer
89     *
90     * @var int
91     * @access private
92     */
93    public $position = 0;
94
95    /**
96     * Create an instance of the class with the input data
97     *
98     * @access public
99     * @param string $data Input data
100     */
101    public function __construct(string $data)
102    {
103        $this->data = $data;
104        $this->data_length = strlen($this->data);
105    }
106
107    /**
108     * Parse the input data
109     *
110     * @access public
111     * @return bool true on success, false on failure
112     */
113    public function parse(): bool
114    {
115        while ($this->state && $this->state !== self::STATE_EMIT && $this->has_data()) {
116            $state = $this->state;
117            $this->$state();
118        }
119        $this->data = '';
120        if ($this->state === self::STATE_EMIT) {
121            return true;
122        }
123
124        // Reset the parser state.
125        $this->version = '1.0';
126        $this->encoding = 'UTF-8';
127        $this->standalone = false;
128        return false;
129    }
130
131    /**
132     * Check whether there is data beyond the pointer
133     *
134     * @access private
135     * @return bool true if there is further data, false if not
136     */
137    public function has_data(): bool
138    {
139        return (bool) ($this->position < $this->data_length);
140    }
141
142    /**
143     * Advance past any whitespace
144     *
145     * @return int Number of whitespace characters passed
146     */
147    public function skip_whitespace()
148    {
149        $whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position);
150        $this->position += $whitespace;
151        return $whitespace;
152    }
153
154    /**
155     * Read value
156     *
157     * @return string|false
158     */
159    public function get_value()
160    {
161        $quote = substr($this->data, $this->position, 1);
162        if ($quote === '"' || $quote === "'") {
163            $this->position++;
164            $len = strcspn($this->data, $quote, $this->position);
165            if ($this->has_data()) {
166                $value = substr($this->data, $this->position, $len);
167                $this->position += $len + 1;
168                return $value;
169            }
170        }
171        return false;
172    }
173
174    public function before_version_name(): void
175    {
176        if ($this->skip_whitespace()) {
177            $this->state = self::STATE_VERSION_NAME;
178        } else {
179            $this->state = self::STATE_ERROR;
180        }
181    }
182
183    public function version_name(): void
184    {
185        if (substr($this->data, $this->position, 7) === 'version') {
186            $this->position += 7;
187            $this->skip_whitespace();
188            $this->state = self::STATE_VERSION_EQUALS;
189        } else {
190            $this->state = self::STATE_ERROR;
191        }
192    }
193
194    public function version_equals(): void
195    {
196        if (substr($this->data, $this->position, 1) === '=') {
197            $this->position++;
198            $this->skip_whitespace();
199            $this->state = self::STATE_VERSION_VALUE;
200        } else {
201            $this->state = self::STATE_ERROR;
202        }
203    }
204
205    public function version_value(): void
206    {
207        if ($version = $this->get_value()) {
208            $this->version = $version;
209            $this->skip_whitespace();
210            if ($this->has_data()) {
211                $this->state = self::STATE_ENCODING_NAME;
212            } else {
213                $this->state = self::STATE_EMIT;
214            }
215        } else {
216            $this->state = self::STATE_ERROR;
217        }
218    }
219
220    public function encoding_name(): void
221    {
222        if (substr($this->data, $this->position, 8) === 'encoding') {
223            $this->position += 8;
224            $this->skip_whitespace();
225            $this->state = self::STATE_ENCODING_EQUALS;
226        } else {
227            $this->state = self::STATE_STANDALONE_NAME;
228        }
229    }
230
231    public function encoding_equals(): void
232    {
233        if (substr($this->data, $this->position, 1) === '=') {
234            $this->position++;
235            $this->skip_whitespace();
236            $this->state = self::STATE_ENCODING_VALUE;
237        } else {
238            $this->state = self::STATE_ERROR;
239        }
240    }
241
242    public function encoding_value(): void
243    {
244        if ($encoding = $this->get_value()) {
245            $this->encoding = $encoding;
246            $this->skip_whitespace();
247            if ($this->has_data()) {
248                $this->state = self::STATE_STANDALONE_NAME;
249            } else {
250                $this->state = self::STATE_EMIT;
251            }
252        } else {
253            $this->state = self::STATE_ERROR;
254        }
255    }
256
257    public function standalone_name(): void
258    {
259        if (substr($this->data, $this->position, 10) === 'standalone') {
260            $this->position += 10;
261            $this->skip_whitespace();
262            $this->state = self::STATE_STANDALONE_EQUALS;
263        } else {
264            $this->state = self::STATE_ERROR;
265        }
266    }
267
268    public function standalone_equals(): void
269    {
270        if (substr($this->data, $this->position, 1) === '=') {
271            $this->position++;
272            $this->skip_whitespace();
273            $this->state = self::STATE_STANDALONE_VALUE;
274        } else {
275            $this->state = self::STATE_ERROR;
276        }
277    }
278
279    public function standalone_value(): void
280    {
281        if ($standalone = $this->get_value()) {
282            switch ($standalone) {
283                case 'yes':
284                    $this->standalone = true;
285                    break;
286
287                case 'no':
288                    $this->standalone = false;
289                    break;
290
291                default:
292                    $this->state = self::STATE_ERROR;
293                    return;
294            }
295
296            $this->skip_whitespace();
297            if ($this->has_data()) {
298                $this->state = self::STATE_ERROR;
299            } else {
300                $this->state = self::STATE_EMIT;
301            }
302        } else {
303            $this->state = self::STATE_ERROR;
304        }
305    }
306}
307
308class_alias('SimplePie\XML\Declaration\Parser', 'SimplePie_XML_Declaration_Parser');
309