Subversion Repositories php_utils

Rev

Rev 12 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
4
 * PHP SimpleXML-Supplement
5
 * Copyright 2020 - 2021 Daniel Marschall, ViaThinkSoft
12 daniel-mar 6
 * Revision 2021-06-12
2 daniel-mar 7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
 
21
 
22
// ======== ATTENTION, PLEASE READ ========
23
// This supplement script was created to support rare PHP installations that
24
// do not contain SimpleXML, for example at PHP you need to explicitly
25
// install the package "php-xml" if you want to have SimpleXML (In the PHP
26
// documentation, it is written that SimpleXML is available to all, which is
27
// not true).
28
//
29
// Beware that the supplement behaves differently than the real SimpleXML!
30
// (If you know how to improve this, please feel free to send me a patch)
31
//
32
// Just a few differences towards the original SimpleXML
33
// - print_r() looks different
34
// - The supplement requires that an XML string begins with "<!DOCTYPE" or "<?xml",
35
//   otherwise, the first element will not be stripped away
36
// - The supplement is slow because of regular expressions
37
// - Many functions like "asXML" are not implemented
38
// - There might be other incompatibilities
39
//
40
// So, if you want to use the SimpleXML supplement, then please carefully
41
// test it with your application if it works.
42
// ========================================
43
 
44
if (!function_exists('simplexml_load_string')) {
45
 
46
        // We cannot store the number 0, 1, 2, ... as items in the SimpleXMLElement, because PHP 7.0 had a bug
47
        // that prevented /get_object_vars() from working correctly
48
        // https://stackoverflow.com/questions/46000541/get-object-vars-returning-different-results-depending-on-php-version
49
        // https://stackoverflow.com/questions/4914376/failed-to-get-dynamic-instance-variables-via-phps-reflection/4914405#comment76610293_4914405
50
        define('SIMPLEXML_SUPPLEMENT_MAGIC', '_SIMPLEXML_SUPPLEMENT_IDX_');
51
 
52
        function _simplexml_supplement_isnumeric($x) {
53
                return substr($x,0,strlen(SIMPLEXML_SUPPLEMENT_MAGIC)) === SIMPLEXML_SUPPLEMENT_MAGIC;
54
        }
55
        function _simplexml_supplement_getnumber($x) {
56
                return (int)substr($x,strlen(SIMPLEXML_SUPPLEMENT_MAGIC));
57
        }
58
        function _simplexml_supplement_addnumberprefix($x) {
59
                return SIMPLEXML_SUPPLEMENT_MAGIC.$x;
60
        }
61
 
62
        // We may not store the fields "position" and "attrs" in the SimpleXMLElement object,
63
        // otherweise the typecast SimpleXMLElement=>array will include them
64
        $_simplexml_supplement_properties = array();
65
 
66
        function simplexml_load_file($file): SimpleXMLElement {
67
                return simplexml_load_string(file_get_contents($file));
68
        }
69
 
70
        function simplexml_load_string($testxml): SimpleXMLElement {
71
                $out = new SimpleXMLElement(); /** @phpstan-ignore-line */
72
 
73
                $testxml = preg_replace('@<!\\-\\-.+\\-\\->@','',$testxml); // remove comments
74
                $testxml = preg_replace('@<([^>\\s]+)\\s*/>@smU','<\\1></\\1>',$testxml); // <x/> => <x></x>
75
 
76
                if ((stripos($testxml, '<?xml') !== false) || (stripos($testxml, '<!doctype') !== false)) {
77
                        $testxml = preg_replace('@<\\?.+\\?>@','',$testxml);
78
                        $testxml = preg_replace('@<!doctype.+>@i','',$testxml);
79
                        $m = array();
80
                        preg_match('@<(\\S+?)[^>]*>(.*)</\\1>@smU',$testxml,$m); // find root element
81
                        $root_element = $m[1];
82
                } else {
83
                        $root_element = null;
84
                }
85
 
86
                $m = array();
87
                preg_match_all('@<(\\S+?)([^>]*)>(.*)</\\1>@smU', $testxml, $m, PREG_SET_ORDER);
88
                foreach ($m as $n) {
89
                        $name = $n[1];
90
                        $other = $n[2];
91
                        $val  = $n[3];
92
 
12 daniel-mar 93
                        // We are using chr(1) to avoid that <x> is parsed as child if it follows CDATA immediately
94
                        $val = str_replace('<![CDATA[', chr(1), $val);
95
                        $val = str_replace(']]>', chr(1), $val);
2 daniel-mar 96
                        $val = trim($val);
97
 
98
                        $new = $out->addChild($name, $val);
99
 
100
                        $m2 = array();
101
                        preg_match_all('@(\S+)=\\"([^\\"]+)\\"@smU', $other, $m2, PREG_SET_ORDER);
102
                        foreach ($m2 as $n2) {
103
                                $att_name = $n2[1];
104
                                $att_val = $n2[2];
105
                                $new->addAttribute($att_name, $att_val);
106
                        }
107
                }
108
 
109
                if (!is_null($root_element)) {
110
                        $out = $out->$root_element;
111
                }
112
 
113
                return $out;
114
        }
115
 
26 daniel-mar 116
        class SimpleXMLElement implements ArrayAccess, Iterator { /** @phpstan-ignore-line */
2 daniel-mar 117
 
118
                function __destruct() {
119
                        global $_simplexml_supplement_properties;
120
                        unset($_simplexml_supplement_properties[spl_object_hash($this)]);
121
                }
122
 
123
                public function addAttribute($name, $val) {
124
                        global $_simplexml_supplement_properties;
125
                        $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'][$name] = $val;
126
                }
127
 
128
                public function attributes() {
129
                        global $_simplexml_supplement_properties;
130
                        return $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'];
131
                }
132
 
133
                public function isSupplement() {
134
                        return true;
135
                }
136
 
137
                public function __construct($val=null) {
138
                        global $_simplexml_supplement_properties;
139
                        $_simplexml_supplement_properties[spl_object_hash($this)] = array(
140
                                "position" => 0,
141
                                "attrs" => array()
142
                        );
143
                        if (!is_null($val)) {
144
                                $this->{_simplexml_supplement_addnumberprefix(0)} = $val;
145
                        }
146
                }
147
 
148
                public function isArray() {
149
                        $vars = get_object_vars($this);
150
                        $max = -1;
151
                        foreach ($vars as $x => $dummy) {
152
                                if (!_simplexml_supplement_isnumeric($x)) {
153
                                        $max = -1;
154
                                        break;
155
                                } else {
156
                                        $num = _simplexml_supplement_getnumber($x);
157
                                        if ($num > $max) $max = $num;
158
                                }
159
                        }
160
                        return $max > 0;
161
                }
162
 
163
                public function addToArray($val) {
164
                        $vars = get_object_vars($this);
165
                        $max = -1;
166
                        foreach ($vars as $x => $dummy) {
167
                                if (!_simplexml_supplement_isnumeric($x)) {
168
                                        $max = -1;
169
                                        break;
170
                                } else {
171
                                        $num = _simplexml_supplement_getnumber($x);
172
                                        if ($num > $max) $max = $num;
173
                                }
174
                        }
175
                        $max++;
176
                        $this->{_simplexml_supplement_addnumberprefix($max)} = $val;
177
                }
178
 
179
                public function __toString() {
180
                        $data = get_object_vars($this);
181
                        if (is_array($data)) {
182
                                if (isset($data[_simplexml_supplement_addnumberprefix(0)])) {
183
                                        return $data[_simplexml_supplement_addnumberprefix(0)];
184
                                } else {
185
                                        return '';
186
                                }
26 daniel-mar 187
                        } else { /** @phpstan-ignore-line */
2 daniel-mar 188
                                return $data;
189
                        }
190
                }
191
 
192
                public function offsetExists($offset) {
193
                        return isset($this->$offset);
194
                }
195
 
196
                public function offsetGet($offset) {
197
                        return $this->$offset;
198
                }
199
 
200
                public function offsetSet($offset, $value) {
201
                        $this->$offset = $value;
202
                }
203
 
204
                public function offsetUnset($offset) {
205
                        unset($this->$offset);
206
                }
207
 
208
                public function __get($name) {
209
                        // Output nothing
210
                        return new SimpleXMLElement(); /** @phpstan-ignore-line */
211
                }
212
 
213
                public function addChild($name, $val=null) {
214
                        global $_simplexml_supplement_properties;
215
 
216
                        if ($val == null) $val = new SimpleXMLElement(); /** @phpstan-ignore-line */
217
 
218
                        if ((substr(trim($val),0,1) === '<') || (trim($val) == '')) {
219
                                $val = simplexml_load_string($val);
220
                        }
221
 
12 daniel-mar 222
                        if (is_string($val)) $val = str_replace(chr(1), '', $val);
223
 
2 daniel-mar 224
                        $data = get_object_vars($this);
225
 
226
                        if (!isset($data[$name])) {
227
                                if ($val instanceof SimpleXMLElement) {
228
                                        $this->$name = $val;
229
                                } else {
230
                                        $this->$name = new SimpleXMLElement($val);
231
                                }
232
                        } else {
233
                                if (!($val instanceof SimpleXMLElement)) {
234
                                        $val = new SimpleXMLElement($val);
235
                                }
236
 
237
                                if ($data[$name]->isArray()) {
238
                                        $data[$name]->addToArray($val);
239
                                } else {
240
                                        $tmp = new SimpleXMLElement(); /** @phpstan-ignore-line */
241
                                        $tmp->addToArray($data[$name]);
242
                                        $tmp->addToArray($val);
243
                                        $this->$name = $tmp;
244
                                        $_simplexml_supplement_properties[spl_object_hash($this)]['attrs'] = array();
245
                                }
246
                                return $val;
247
                        }
248
 
249
                        return $this->$name;
250
                }
251
 
252
                public function rewind() {
253
                        global $_simplexml_supplement_properties;
254
                        $_simplexml_supplement_properties[spl_object_hash($this)]['position'] = 0;
255
                }
256
 
257
                public function current() {
258
                        global $_simplexml_supplement_properties;
259
                        $vars = get_object_vars($this);
260
                        $cnt = 0;
26 daniel-mar 261
                        foreach ($vars as $x => $dummy) { /** @phpstan-ignore-line */
2 daniel-mar 262
                                if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
263
                                        $vars2 = get_object_vars($dummy);
264
                                        foreach ($vars2 as $x2 => $dummy2) {
265
                                                if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) {
266
                                                        if ($dummy2 instanceof SimpleXMLElement) {
26 daniel-mar 267
                                                                return $dummy2; /** @phpstan-ignore-line */
2 daniel-mar 268
                                                        } else {
26 daniel-mar 269
                                                                return new SimpleXMLElement($dummy2); /** @phpstan-ignore-line */
2 daniel-mar 270
                                                        }
271
                                                }
272
                                                $cnt++;
273
                                        }
274
                                } else {
275
                                        if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) {
276
                                                if ($dummy instanceof SimpleXMLElement) {
26 daniel-mar 277
                                                        return $dummy; /** @phpstan-ignore-line */
2 daniel-mar 278
                                                } else {
26 daniel-mar 279
                                                        return new SimpleXMLElement($dummy); /** @phpstan-ignore-line */
2 daniel-mar 280
                                                }
281
                                        }
282
                                        $cnt++;
283
                                }
284
                        }
285
 
286
 
287
                }
288
 
289
                public function key() {
290
                        global $_simplexml_supplement_properties;
291
                        $vars = get_object_vars($this);
292
                        $cnt = 0;
26 daniel-mar 293
                        foreach ($vars as $x => $dummy) { /** @phpstan-ignore-line */
2 daniel-mar 294
                                if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
295
                                        $vars2 = get_object_vars($dummy);
296
                                        foreach ($vars2 as $x2 => $dummy2) {
297
                                                if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) return $x/*sic*/;
298
                                                $cnt++;
299
                                        }
300
                                } else {
301
                                        if ($cnt == $_simplexml_supplement_properties[spl_object_hash($this)]['position']) return $x;
302
                                        $cnt++;
303
                                }
304
                        }
305
                }
306
 
307
                public function next() {
308
                        global $_simplexml_supplement_properties;
309
                        ++$_simplexml_supplement_properties[spl_object_hash($this)]['position'];
310
                }
311
 
312
                public function valid() {
313
                        global $_simplexml_supplement_properties;
314
 
315
                        $vars = get_object_vars($this);
316
                        $cnt = 0;
317
                        foreach ($vars as $x => $dummy) {
318
                                if (($dummy instanceof SimpleXMLElement) && !_simplexml_supplement_isnumeric($x) && $dummy->isArray()) {
319
                                        $vars2 = get_object_vars($dummy);
320
                                        foreach ($vars2 as $x2 => $dummy2) {
321
                                                $cnt++;
322
                                        }
323
                                } else {
324
                                        $cnt++;
325
                                }
326
                        }
327
 
328
                        return $_simplexml_supplement_properties[spl_object_hash($this)]['position'] < $cnt;
329
                }
330
 
331
        }
332
}