1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Nucleus - XMPP Library for PHP |
4
|
|
|
* |
5
|
|
|
* Copyright (C) 2016, Some rights reserved. |
6
|
|
|
* |
7
|
|
|
* @author Kacper "Kadet" Donat <[email protected]> |
8
|
|
|
* |
9
|
|
|
* Contact with author: |
10
|
|
|
* Xmpp: [email protected] |
11
|
|
|
* E-mail: [email protected] |
12
|
|
|
* |
13
|
|
|
* From Kadet with love. |
14
|
|
|
*/ |
15
|
|
|
|
16
|
|
|
namespace Kadet\Xmpp\Xml; |
17
|
|
|
|
18
|
|
|
use Interop\Container\ContainerInterface; |
19
|
|
|
use Kadet\Xmpp\Exception\InvalidArgumentException; |
20
|
|
|
use Kadet\Xmpp\Utils\Accessors; |
21
|
|
|
use Kadet\Xmpp\Utils\filter; |
22
|
|
|
use function Kadet\Xmpp\Utils\filter\not; |
23
|
|
|
use Kadet\Xmpp\Utils\helper; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Class XmlElement |
27
|
|
|
* @package Kadet\Xmpp\Xml |
28
|
|
|
* |
29
|
|
|
* @property string $localName Tag name without prefix |
30
|
|
|
* @property string $namespace XML Namespace URI |
31
|
|
|
* @property string $prefix Tag prefix |
32
|
|
|
* @property string $fullName Full tag name prefix:local-name |
33
|
|
|
* |
34
|
|
|
* @property XmlElement|null $parent Element's parent or null if root node. |
35
|
|
|
* @property XmlElement[] parents All element's parents in chronological order (from youngest to oldest) |
36
|
|
|
* @property XmlElement[] $children All element's child nodes |
37
|
|
|
* |
38
|
|
|
* @property array $attributes Element's attributes, without xmlns definitions |
39
|
|
|
* @property array $namespaces Element's namespaces |
40
|
|
|
* |
41
|
|
|
* @property string $innerXml Inner XML content |
42
|
|
|
*/ |
43
|
|
|
class XmlElement implements ContainerInterface |
44
|
|
|
{ |
45
|
|
|
use Accessors; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Settings for tiding up XML output |
49
|
|
|
* |
50
|
|
|
* @var array |
51
|
|
|
*/ |
52
|
|
|
public static $tidy = [ |
53
|
|
|
'indent' => true, |
54
|
|
|
'input-xml' => true, |
55
|
|
|
'output-xml' => true, |
56
|
|
|
'drop-empty-paras' => false, |
57
|
|
|
'wrap' => 0 |
58
|
|
|
]; |
59
|
|
|
|
60
|
|
|
/** @var string */ |
61
|
|
|
private $_localName; |
62
|
|
|
/** @var null|string|false */ |
63
|
|
|
private $_prefix = null; |
64
|
|
|
|
65
|
|
|
/** @var array */ |
66
|
|
|
private $_namespaces = []; |
67
|
|
|
/** @var array */ |
68
|
|
|
private $_attributes = []; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @var XmlElement |
72
|
|
|
*/ |
73
|
|
|
private $_parent; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @var XmlElement[] |
77
|
|
|
*/ |
78
|
|
|
private $_children = []; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Initializes element with given name and URI |
82
|
|
|
* |
83
|
|
|
* @param string $name Element name, including prefix if needed |
84
|
|
|
* @param string $uri Namespace URI of element |
85
|
|
|
*/ |
86
|
25 |
|
protected function init(string $name, string $uri = null) |
87
|
|
|
{ |
88
|
25 |
|
list($name, $prefix) = self::resolve($name); |
89
|
|
|
|
90
|
25 |
|
$this->_localName = $name; |
91
|
25 |
|
$this->_prefix = $prefix; |
92
|
|
|
|
93
|
25 |
|
if ($uri !== null) { |
94
|
11 |
|
$this->namespace = $uri; |
95
|
|
|
} |
96
|
25 |
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* XmlElement constructor |
100
|
|
|
* |
101
|
|
|
* @param string $name Element name, including prefix if needed |
102
|
|
|
* @param string $uri Namespace URI of element |
103
|
|
|
* @param array $options { |
104
|
|
|
* @var mixed $content Content of element |
105
|
|
|
* @var array $attributes Element attributes |
106
|
|
|
* } |
107
|
|
|
*/ |
108
|
21 |
|
public function __construct(string $name, string $uri = null, array $options = []) |
109
|
|
|
{ |
110
|
21 |
|
$this->init($name, $uri); |
111
|
|
|
|
112
|
21 |
|
$this->applyOptions($options); |
113
|
21 |
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Elements named constructor, same for every subclass. |
117
|
|
|
* It's used for factory creation. |
118
|
|
|
* |
119
|
|
|
* @param string $name Element name, including prefix if needed |
120
|
|
|
* @param string $uri Namespace URI of element |
121
|
|
|
* |
122
|
|
|
* @return static |
123
|
|
|
*/ |
124
|
4 |
|
public static function plain(string $name, string $uri = null) |
125
|
|
|
{ |
126
|
|
|
/** @var XmlElement $element */ |
127
|
4 |
|
$element = (new \ReflectionClass(static::class))->newInstanceWithoutConstructor(); |
128
|
4 |
|
$element->init($name, $uri); |
129
|
|
|
|
130
|
4 |
|
return $element; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* @see $innerXml |
135
|
|
|
* @return string |
136
|
|
|
*/ |
137
|
3 |
|
public function getInnerXml() |
138
|
|
|
{ |
139
|
|
|
return implode('', array_map(function ($element) { |
140
|
3 |
|
if (is_string($element)) { |
141
|
2 |
|
return htmlspecialchars($element); |
142
|
|
|
} elseif ($element instanceof XmlElement) { |
143
|
1 |
|
return $element->xml(false); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
return (string)$element; |
147
|
3 |
|
}, $this->_children)); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
public function setInnerXml($value) |
151
|
|
|
{ |
152
|
|
|
$this->_children = []; |
153
|
|
|
|
154
|
|
|
$this->append($value); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
public function getContent() |
158
|
|
|
{ |
159
|
|
|
return $this->children; |
160
|
|
|
} |
161
|
|
|
|
162
|
1 |
|
public function setContent($value) |
163
|
|
|
{ |
164
|
1 |
|
$this->_children = []; |
165
|
1 |
|
$this->append($value); |
166
|
1 |
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Returns XML representation of element |
170
|
|
|
* |
171
|
|
|
* @param bool $clean Result will be cleaned if set to true |
172
|
|
|
* |
173
|
|
|
* @return string |
174
|
|
|
*/ |
175
|
4 |
|
public function xml(bool $clean = true): string |
176
|
|
|
{ |
177
|
4 |
|
if ($this->namespace && $this->_prefix === null) { |
178
|
1 |
|
$this->_prefix = $this->lookupPrefix($this->namespace); |
179
|
|
|
} |
180
|
|
|
|
181
|
4 |
|
$attributes = $this->attributes(); |
182
|
|
|
|
183
|
4 |
|
$result = "<{$this->fullName}"; |
184
|
|
|
$result .= ' ' . implode(' ', array_map(function ($key, $value) { |
185
|
2 |
|
return $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"'; |
186
|
4 |
|
}, array_keys($attributes), array_values($attributes))); |
187
|
|
|
|
188
|
4 |
|
if (!empty($this->_children)) { |
189
|
1 |
|
$result .= ">{$this->innerXml}</{$this->fullName}>"; |
190
|
|
|
} else { |
191
|
4 |
|
$result .= "/>"; |
192
|
|
|
} |
193
|
|
|
|
194
|
4 |
|
return $clean && function_exists('tidy_repair_string') ? tidy_repair_string($result, self::$tidy) : $result; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Looks up prefix associated with given URI |
199
|
|
|
* |
200
|
|
|
* @param string|null $uri |
201
|
|
|
* @return string|false |
202
|
|
|
*/ |
203
|
13 |
|
public function lookupPrefix(string $uri = null) |
204
|
|
|
{ |
205
|
13 |
|
return $this->getNamespaces()[ $uri ] ?? false; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Looks up URI associated with given prefix |
210
|
|
|
* |
211
|
|
|
* @param string|null $prefix |
212
|
|
|
* @return string|false |
213
|
|
|
*/ |
214
|
19 |
|
public function lookupUri(string $prefix = null) |
215
|
|
|
{ |
216
|
19 |
|
return array_search($prefix, $this->getNamespaces()) ?: false; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Returns element's namespaces |
221
|
|
|
* |
222
|
|
|
* @param bool $parent Include namespaces from parent? |
223
|
|
|
* @return array |
224
|
|
|
*/ |
225
|
21 |
|
public function getNamespaces($parent = true): array |
226
|
|
|
{ |
227
|
21 |
|
if (!$this->_parent) { |
228
|
21 |
|
return $this->_namespaces; |
229
|
|
|
} |
230
|
|
|
|
231
|
12 |
|
if ($parent) { |
232
|
12 |
|
return array_merge($this->_namespaces, $this->_parent->getNamespaces()); |
233
|
|
|
} else { |
234
|
2 |
|
return array_diff_assoc($this->_namespaces, $this->_parent->getNamespaces()); |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Sets XML attribute of element |
240
|
|
|
* |
241
|
|
|
* @param string $attribute Attribute name, optionally with prefix |
242
|
|
|
* @param mixed $value Attribute value |
243
|
|
|
* @param string|null $uri XML Namespace URI of attribute, prefix will be automatically looked up |
244
|
|
|
*/ |
245
|
6 |
|
public function setAttribute(string $attribute, $value, string $uri = null) |
246
|
|
|
{ |
247
|
6 |
|
$attribute = $this->_prefix($attribute, $uri); |
248
|
5 |
|
if ($value === null) { |
249
|
1 |
|
unset($this->_attributes[ $attribute ]); |
250
|
|
|
|
251
|
1 |
|
return; |
252
|
|
|
} |
253
|
|
|
|
254
|
5 |
|
$this->_attributes[ $attribute ] = $value; |
255
|
5 |
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Returns value of specified attribute. |
259
|
|
|
* |
260
|
|
|
* @param string $attribute Attribute name, optionally with prefix |
261
|
|
|
* @param string|null $uri XML Namespace URI of attribute, prefix will be automatically looked up |
262
|
|
|
* @return bool|mixed |
263
|
|
|
*/ |
264
|
4 |
|
public function getAttribute(string $attribute, string $uri = null) |
265
|
|
|
{ |
266
|
4 |
|
return $this->_attributes[ $this->_prefix($attribute, $uri) ] ?? false; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Checks if attribute exists |
271
|
|
|
* |
272
|
|
|
* @param string $attribute Attribute name, optionally with prefix |
273
|
|
|
* @param string|null $uri XML Namespace URI of attribute, prefix will be automatically looked up |
274
|
|
|
* |
275
|
|
|
* @return bool |
276
|
|
|
*/ |
277
|
3 |
|
public function hasAttribute(string $attribute, string $uri = null) |
278
|
|
|
{ |
279
|
3 |
|
return isset($this->_attributes[ $this->_prefix($attribute, $uri) ]); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Returns all element's parents in order, oldest ancestor is the last element in returned array. |
284
|
|
|
* @return XmlElement[] |
285
|
|
|
*/ |
286
|
|
|
public function getParents() |
287
|
|
|
{ |
288
|
|
|
return $this->_parent ? array_merge([ $this->_parent ], $this->_parent->getParents()) : []; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Returns element's parent |
293
|
|
|
* @return XmlElement|null |
294
|
|
|
*/ |
295
|
3 |
|
public function getParent() |
296
|
|
|
{ |
297
|
3 |
|
return $this->_parent; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Sets element's parent |
302
|
|
|
* @param XmlElement $parent |
303
|
|
|
*/ |
304
|
12 |
|
protected function setParent(XmlElement $parent) |
305
|
|
|
{ |
306
|
12 |
|
if (!$this->_prefix && ($prefix = $parent->lookupPrefix($this->namespace)) !== false) { |
307
|
1 |
|
$this->_namespaces[ $this->namespace ] = $prefix; |
308
|
1 |
|
$this->_prefix = $prefix; |
309
|
|
|
} |
310
|
|
|
|
311
|
12 |
|
$this->_parent = $parent; |
312
|
12 |
|
if ($this->namespace === false) { |
313
|
7 |
|
$this->namespace = $parent->namespace; |
314
|
|
|
} |
315
|
12 |
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Appends child to element |
319
|
|
|
* |
320
|
|
|
* @param XmlElement|string $element |
321
|
|
|
* |
322
|
|
|
* @return XmlElement|string Same as $element |
323
|
|
|
*/ |
324
|
16 |
|
public function append($element) |
325
|
|
|
{ |
326
|
16 |
|
if (empty($element)) { |
327
|
1 |
|
return false; |
328
|
|
|
} |
329
|
|
|
|
330
|
15 |
|
if(is_array($element)) { |
331
|
2 |
|
array_walk($element, [$this, 'appendChild']); |
332
|
2 |
|
return $element; |
333
|
|
|
} |
334
|
|
|
|
335
|
13 |
|
return $this->appendChild($element); |
336
|
|
|
} |
337
|
|
|
|
338
|
1 |
|
public function remove($element) |
339
|
|
|
{ |
340
|
1 |
|
if(!$element instanceof \Closure) { |
341
|
1 |
|
$element = is_array($element) ? filter\in($element) : filter\same($element); |
342
|
|
|
} |
343
|
1 |
|
$old = $this->_children; |
344
|
1 |
|
$this->_children = array_filter($this->_children, not($element)); |
345
|
|
|
|
346
|
1 |
|
foreach (array_diff($old, $this->_children) as $removed) { |
347
|
1 |
|
if($removed instanceof XmlElement) { |
348
|
1 |
|
$removed->_parent = null; |
349
|
|
|
} |
350
|
|
|
} |
351
|
1 |
|
} |
352
|
|
|
|
353
|
15 |
|
protected function appendChild($element) { |
354
|
15 |
|
if (!is_string($element) && !$element instanceof XmlElement) { |
355
|
1 |
|
throw new InvalidArgumentException(helper\format( |
356
|
1 |
|
'$element should be either string or object of {class} class. or array of given types, {type} given', [ |
357
|
1 |
|
'class' => XmlElement::class, |
358
|
1 |
|
'type' => helper\typeof($element) |
359
|
|
|
] |
360
|
|
|
)); |
361
|
|
|
} |
362
|
|
|
|
363
|
14 |
|
if ($element instanceof XmlElement) { |
364
|
12 |
|
$element->parent = $this; |
365
|
|
|
} |
366
|
|
|
|
367
|
14 |
|
return $this->_children[] = $element; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Returns namespace URI associated with element or specified prefix |
372
|
|
|
* |
373
|
|
|
* @param string|bool|null $prefix |
374
|
|
|
* @return false|string |
375
|
|
|
*/ |
376
|
19 |
|
public function getNamespace($prefix = false) |
377
|
|
|
{ |
378
|
19 |
|
if ($prefix === false) { |
379
|
19 |
|
$prefix = $this->prefix; |
380
|
|
|
} |
381
|
|
|
|
382
|
19 |
|
return $this->lookupUri($prefix); |
|
|
|
|
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* Adds namespace to element, and associates it with prefix. |
387
|
|
|
* |
388
|
|
|
* @param string $uri Namespace URI |
389
|
|
|
* @param string|bool|null $prefix Prefix which will be used for namespace, false for using element's prefix |
390
|
|
|
* and null for no prefix |
391
|
|
|
*/ |
392
|
17 |
|
public function setNamespace(string $uri, $prefix = false) |
393
|
|
|
{ |
394
|
17 |
|
if ($prefix === false) { |
395
|
15 |
|
$prefix = $this->_prefix; |
396
|
|
|
} |
397
|
|
|
|
398
|
17 |
|
$this->_namespaces[ $uri ] = $prefix; |
399
|
17 |
|
} |
400
|
|
|
|
401
|
8 |
|
public function getFullName() |
402
|
|
|
{ |
403
|
8 |
|
return ($this->_prefix ? $this->prefix . ':' : null) . $this->localName; |
404
|
|
|
} |
405
|
|
|
|
406
|
8 |
|
public function getChildren() |
407
|
|
|
{ |
408
|
8 |
|
return $this->_children; |
409
|
|
|
} |
410
|
|
|
|
411
|
19 |
|
public function getPrefix() |
412
|
|
|
{ |
413
|
19 |
|
return $this->_prefix; |
414
|
|
|
} |
415
|
|
|
|
416
|
13 |
|
public function getLocalName() |
417
|
|
|
{ |
418
|
13 |
|
return $this->_localName; |
419
|
|
|
} |
420
|
|
|
|
421
|
7 |
|
public function getAttributes() |
422
|
|
|
{ |
423
|
7 |
|
return $this->_attributes; |
424
|
|
|
} |
425
|
|
|
|
426
|
2 |
|
protected function setAttributes(array $attributes) |
427
|
|
|
{ |
428
|
2 |
|
$this->_attributes = []; |
429
|
|
|
|
430
|
2 |
|
foreach ($attributes as $attribute => $value) { |
431
|
2 |
|
$this->setAttribute($attribute, $value); |
432
|
|
|
} |
433
|
2 |
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* Returns one element at specified index (for default the first one). |
437
|
|
|
* |
438
|
|
|
* @param string $name Requested element tag name |
439
|
|
|
* @param string $uri Requested element namespace |
440
|
|
|
* @param int $index Index of element to retrieve |
441
|
|
|
* |
442
|
|
|
* @return XmlElement|false Retrieved element |
443
|
|
|
*/ |
444
|
1 |
|
public function element(string $name, string $uri = null, int $index = 0) |
445
|
|
|
{ |
446
|
1 |
|
return array_values($this->elements($name, $uri))[ $index ] ?? false; |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* Retrieves array of matching elements |
451
|
|
|
* |
452
|
|
|
* @param string $name Requested element tag name |
453
|
|
|
* @param string|null $uri Requested element namespace |
454
|
|
|
* |
455
|
|
|
* @return XmlElement[] Found Elements |
456
|
|
|
*/ |
457
|
2 |
|
public function elements($name, $uri = null) : array |
458
|
|
|
{ |
459
|
2 |
|
$predicate = filter\element\name($name); |
460
|
2 |
|
if ($uri !== null) { |
461
|
1 |
|
$predicate = filter\all($predicate, filter\element\xmlns($uri)); |
462
|
|
|
} |
463
|
|
|
|
464
|
2 |
|
return $this->all($predicate); |
465
|
|
|
} |
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Filters element with given predicate |
469
|
|
|
* |
470
|
|
|
* @param callable|string $predicate Predicate or class name |
471
|
|
|
* |
472
|
|
|
* @return XmlElement[] |
473
|
|
|
*/ |
474
|
2 |
|
public function all($predicate) |
475
|
|
|
{ |
476
|
2 |
|
return array_values(array_filter($this->_children, filter\predicate($predicate))); |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* Iterates over matching elements |
481
|
|
|
* |
482
|
|
|
* @param callable|string $predicate Predicate or class name |
483
|
|
|
* |
484
|
|
|
* @return XmlElement|false |
485
|
|
|
*/ |
486
|
2 |
|
public function get($predicate) |
487
|
|
|
{ |
488
|
2 |
|
$predicate = filter\predicate($predicate); |
489
|
2 |
|
foreach ($this->_children as $index => $child) { |
490
|
2 |
|
if ($predicate($child)) { |
491
|
2 |
|
return $child; |
492
|
|
|
} |
493
|
|
|
} |
494
|
|
|
|
495
|
2 |
|
return false; |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* Checks if any element matching predicate exists |
500
|
|
|
* |
501
|
|
|
* @param callable|string $predicate Predicate or class name |
502
|
|
|
* |
503
|
|
|
* @return bool |
504
|
|
|
*/ |
505
|
1 |
|
public function has($predicate) |
506
|
|
|
{ |
507
|
1 |
|
return $this->get($predicate) !== false; |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
/** |
511
|
|
|
* @param string|null $query |
512
|
|
|
* @return XPathQuery |
513
|
|
|
*/ |
514
|
1 |
|
public function query(string $query = null) |
515
|
|
|
{ |
516
|
1 |
|
return new XPathQuery($query, $this); |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
/** |
520
|
|
|
* Helper for retrieving all arguments (including namespaces) |
521
|
|
|
* |
522
|
|
|
* @return array |
523
|
|
|
*/ |
524
|
4 |
|
private function attributes(): array |
525
|
|
|
{ |
526
|
4 |
|
$namespaces = $this->getNamespaces(false); |
527
|
4 |
|
$namespaces = array_map(function ($prefix, $uri) { |
528
|
2 |
|
return [$prefix ? "xmlns:{$prefix}" : 'xmlns', $uri]; |
529
|
4 |
|
}, array_values($namespaces), array_keys($namespaces)); |
530
|
|
|
|
531
|
4 |
|
return array_merge( |
532
|
4 |
|
$this->_attributes, |
533
|
4 |
|
array_combine(array_column($namespaces, 0), array_column($namespaces, 1)) |
534
|
|
|
); |
535
|
|
|
} |
536
|
|
|
|
537
|
|
|
/** |
538
|
|
|
* Prefixes $name with attribute associated with $uri |
539
|
|
|
* |
540
|
|
|
* @param string $name Name to prefix |
541
|
|
|
* @param string $uri Namespace URI |
542
|
|
|
* |
543
|
|
|
* @return string |
544
|
|
|
*/ |
545
|
6 |
|
protected function _prefix(string $name, string $uri = null): string |
546
|
|
|
{ |
547
|
6 |
|
if ($uri === null) { |
548
|
5 |
|
return $name; |
549
|
|
|
} |
550
|
|
|
|
551
|
3 |
|
if (($prefix = $this->lookupPrefix($uri)) === false) { |
552
|
1 |
|
throw new InvalidArgumentException(helper\format('URI "{uri}" is not a registered namespace', ['uri' => $uri])); |
553
|
|
|
} |
554
|
|
|
|
555
|
2 |
|
return "{$prefix}:{$name}"; |
556
|
|
|
} |
557
|
|
|
|
558
|
3 |
|
public function __toString() |
559
|
|
|
{ |
560
|
3 |
|
return trim($this->xml(true)); |
561
|
|
|
} |
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* Splits name into local-name and prefix |
565
|
|
|
* |
566
|
|
|
* @param $name |
567
|
|
|
* @return array [$name, $prefix] |
568
|
|
|
*/ |
569
|
25 |
|
public static function resolve($name) |
570
|
|
|
{ |
571
|
25 |
|
$prefix = null; |
572
|
25 |
|
if (($pos = strpos($name, ':')) !== false) { |
573
|
2 |
|
$prefix = substr($name, 0, $pos); |
574
|
2 |
|
$name = substr($name, $pos + 1); |
575
|
|
|
} |
576
|
|
|
|
577
|
25 |
|
return [$name, $prefix]; |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
/** |
581
|
|
|
* Casts XML Element object to another class, it's not recommended but should work, as child classes should |
582
|
|
|
* only decorate parent with additional getters and setters for accessing data. |
583
|
|
|
* |
584
|
|
|
* @param XmlElement $element |
585
|
|
|
* @return static |
586
|
|
|
*/ |
587
|
1 |
|
public static function cast(XmlElement $element) |
588
|
|
|
{ |
589
|
|
|
/** @var static $return */ |
590
|
1 |
|
$return = (new \ReflectionClass(static::class))->newInstanceWithoutConstructor(); |
591
|
1 |
|
foreach (get_object_vars($element) as $property => $value) { |
592
|
1 |
|
$return->$property = $value; |
593
|
|
|
} |
594
|
|
|
|
595
|
1 |
|
return $return; |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
/** |
599
|
|
|
* When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties. |
600
|
|
|
* Any properties that are references to other variables, will remain references. |
601
|
|
|
* Once the cloning is complete, if a __clone() method is defined, |
602
|
|
|
* then the newly created object's __clone() method will be called, to allow any necessary properties that need to be changed. |
603
|
|
|
* NOT CALLABLE DIRECTLY. |
604
|
|
|
* |
605
|
|
|
* @link http://php.net/manual/en/language.oop5.cloning.php |
606
|
|
|
*/ |
607
|
|
|
public function __clone() |
608
|
|
|
{ |
609
|
|
|
$children = $this->_children; |
610
|
|
|
$this->_children = []; |
611
|
|
|
$this->_parent = null; |
612
|
|
|
|
613
|
|
|
foreach ($children as $child) { |
614
|
|
|
$this->append(is_object($child) ? clone $child : $child); |
615
|
|
|
} |
616
|
|
|
} |
617
|
|
|
} |
618
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.