GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 0e9349...d2bdfd )
by Nicolas
08:54 queued 04:10
created

symphony/lib/toolkit/class.xmlelement.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * @package toolkit
4
 */
5
/**
6
 * `XMLElement` is a class used to simulate PHP's `DOMElement`
7
 * class. Each object is a representation of a HTML element
8
 * and can store it's children in an array. When an `XMLElement`
9
 * is generated, it is output as an XML string.
10
 */
11
12
class XMLElement implements IteratorAggregate
13
{
14
    /**
15
     * This is an array of HTML elements that are self closing.
16
     * @var array
17
     */
18
    protected static $no_end_tags = array(
19
        'area', 'base', 'br', 'col', 'hr', 'img', 'input', 'link', 'meta', 'param'
20
    );
21
22
    /**
23
     * The name of the HTML Element, eg. 'p'
24
     * @var string
25
     */
26
    private $_name;
27
28
    /**
29
     * The value of this `XMLElement` as a string
30
     * @var string
31
     */
32
    private $_value = array();
33
34
    /**
35
     * Any additional attributes can be included in an associative array
36
     * with the key being the name and the value being the value of the
37
     * attribute.
38
     * @var array
39
     */
40
    private $_attributes = array();
41
42
    /**
43
     * Children of this `XMLElement`, which will also be `XMLElement`'s
44
     * @var array
45
     */
46
    private $_children = array();
47
48
    /**
49
     * Any processing instructions that the XSLT should know about when a
50
     * `XMLElement` is generated
51
     * @var array
52
     */
53
    private $_processingInstructions = array();
54
55
    /**
56
     * The DTD the should be output when a `XMLElement` is generated, defaults to null.
57
     * @var string
58
     */
59
    private $_dtd = null;
60
61
    /**
62
     * The encoding of the `XMLElement`, defaults to 'utf-8'
63
     * @var string
64
     */
65
    private $_encoding = 'utf-8';
66
67
    /**
68
     * The version of the XML that is used for generation, defaults to '1.0'
69
     * @var string
70
     */
71
    private $_version = '1.0';
72
73
    /**
74
     * The type of element, defaults to 'xml'. Used when determining the style
75
     * of end tag for this element when generated
76
     * @var string
77
     */
78
    private $_elementStyle = 'xml';
79
80
    /**
81
     * When set to true this will include the XML declaration will be
82
     * output when the `XMLElement` is generated. Defaults to `false`.
83
     * @var boolean
84
     */
85
    private $_includeHeader = false;
86
87
    /**
88
     * Specifies whether this HTML element has an closing element, or if
89
     * it self closing. Defaults to `true`.
90
     *  eg. `<p></p>` or `<input />`
91
     * @var boolean
92
     */
93
    private $_selfclosing = true;
94
95
    /**
96
     * Specifies whether attributes need to have a value or if they can
97
     * be shorthand. Defaults to `true`. An example of this would be:
98
     *  `<option selected>Value</option>`
99
     * @var boolean
100
     */
101
    private $_allowEmptyAttributes = true;
102
103
    /**
104
     * The constructor for the `XMLElement`
105
     *
106
     * @param string $name
107
     *  The name of the `XMLElement`, 'p'.
108
     * @param string|XMLElement $value (optional)
109
     *  The value of this `XMLElement`, it can be a string
110
     *  or another `XMLElement` object.
111
     * @param array $attributes (optional)
112
     *  Any additional attributes can be included in an associative array with
113
     *  the key being the name and the value being the value of the attribute.
114
     *  Attributes set from this array will override existing attributes
115
     *  set by previous params.
116
     * @param boolean $createHandle
117
     *  Whether this function should convert the `$name` to a handle. Defaults to
118
     *  `false`.
119
     */
120
    public function __construct($name, $value = null, array $attributes = array(), $createHandle = false)
121
    {
122
        $this->setName($name, $createHandle);
123
        $this->setValue($value);
0 ignored issues
show
It seems like $value defined by parameter $value on line 120 can also be of type null; however, XMLElement::setValue() does only seem to accept string|object<XMLElement>|array, maybe add an additional type check?

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.

Loading history...
124
125
        if (is_array($attributes) && !empty($attributes)) {
126
            $this->setAttributeArray($attributes);
127
        }
128
    }
129
130
    /**
131
     * Accessor for `$_name`
132
     *
133
     * @return string
134
     */
135
    public function getName()
136
    {
137
        return $this->_name;
138
    }
139
140
    /**
141
     * Accessor for `$_value`
142
     *
143
     * @return string|XMLElement
144
     */
145
    public function getValue()
146
    {
147
        $value = '';
148
149
        if (is_array($this->_value)) {
150
            foreach ($this->_value as $v) {
151
                if ($v instanceof XMLElement) {
152
                    $value .= $v->generate();
153
                } else {
154
                    $value .= $v;
155
                }
156
            }
157
        } elseif (!is_null($this->_value)) {
158
            $value = $this->_value;
159
        }
160
161
        return $value;
162
    }
163
164
    /**
165
     * Retrieves the value of an attribute by name
166
     *
167
     * @param string $name
168
     * @return string
169
     */
170
    public function getAttribute($name)
171
    {
172
        if (!isset($this->_attributes[$name])) {
173
            return null;
174
        }
175
176
        return $this->_attributes[$name];
177
    }
178
179
    /**
180
     * Accessor for `$this->_attributes`
181
     *
182
     * @return array
183
     */
184
    public function getAttributes()
185
    {
186
        return $this->_attributes;
187
    }
188
189
    /**
190
     * Retrieves a child-element by position
191
     *
192
     * @since Symphony 2.3
193
     * @param integer $position
194
     * @return XMLElement
195
     */
196
    public function getChild($position)
197
    {
198
        if (!isset($this->_children[$this->getRealIndex($position)])) {
199
            return null;
200
        }
201
202
        return $this->_children[$this->getRealIndex($position)];
203
    }
204
205
    /**
206
     * Accessor for `$this->_children`
207
     *
208
     * @return array
209
     */
210
    public function getChildren()
211
    {
212
        return $this->_children;
213
    }
214
215
    /**
216
     * Accessor for `$this->_children`, returning only `XMLElement` children,
217
     * not text nodes.
218
     *
219
     * @return XMLElementChildrenFilter
220
     */
221
    public function getIterator()
222
    {
223
        return new XMLElementChildrenFilter(new ArrayIterator($this->_children));
224
    }
225
226
    /**
227
     * Retrieves child-element by name and position. If no child is found,
228
     * `NULL` will be returned.
229
     *
230
     * @since Symphony 2.3
231
     * @param string $name
232
     * @param integer $position
233
     * @return XMLElement
234
     */
235
    public function getChildByName($name, $position)
236
    {
237
        $result = array_values($this->getChildrenByName($name));
238
239
        if (!isset($result[$position])) {
240
            return null;
241
        }
242
243
        return $result[$position];
244
    }
245
246
    /**
247
     * Accessor to return an associative array of all `$this->_children`
248
     * whose's name matches the given `$name`. If no children are found,
249
     * an empty array will be returned.
250
     *
251
     * @since Symphony 2.2.2
252
     * @param string $name
253
     * @return array
254
     *  An associative array where the key is the `$index` of the child
255
     *  in `$this->_children`
256
     */
257
    public function getChildrenByName($name)
258
    {
259
        $result = array();
260
261
        foreach ($this as $i => $child) {
262
            if ($child->getName() != $name) {
263
                continue;
264
            }
265
266
            $result[$i] = $child;
267
        }
268
269
        return $result;
270
    }
271
272
    /**
273
     * Adds processing instructions to this `XMLElement`
274
     *
275
     * @param string $pi
276
     */
277
    public function addProcessingInstruction($pi)
278
    {
279
        $this->_processingInstructions[] = $pi;
280
    }
281
282
    /**
283
     * Sets the DTD for this `XMLElement`
284
     *
285
     * @param string $dtd
286
     */
287
    public function setDTD($dtd)
288
    {
289
        $this->_dtd = $dtd;
290
    }
291
292
    /**
293
     * Sets the encoding for this `XMLElement` for when
294
     * it's generated.
295
     *
296
     * @param string $value
297
     */
298
    public function setEncoding($value)
299
    {
300
        $this->_encoding = $value;
301
    }
302
303
    /**
304
     * Sets the version for the XML declaration of this
305
     * `XMLElement`
306
     *
307
     * @param string $value
308
     */
309
    public function setVersion($value)
310
    {
311
        $this->_version = $value;
312
    }
313
314
    /**
315
     * Sets the style of the `XMLElement`. Used when the
316
     * `XMLElement` is being generated to determine whether
317
     * needs to be closed, is self closing or is standalone.
318
     *
319
     * @param string $style (optional)
320
     *  Defaults to 'xml', any other value will trigger the
321
     *  XMLElement to be closed by itself or left standalone
322
     *  if it is in the `XMLElement::no_end_tags`.
323
     */
324
    public function setElementStyle($style = 'xml')
325
    {
326
        $this->_elementStyle = $style;
327
    }
328
329
    /**
330
     * Sets whether this `XMLElement` needs to output an
331
     * XML declaration or not. This normally is only set to
332
     * true for the parent `XMLElement`, eg. 'html'.
333
     *
334
     * @param bool|string $value (optional)
335
     *  Defaults to false
336
     */
337
    public function setIncludeHeader($value = false)
338
    {
339
        $this->_includeHeader = $value;
340
    }
341
342
    /**
343
     * Sets whether this `XMLElement` is self closing or not.
344
     *
345
     * @param bool|string $value (optional)
346
     *  Defaults to true
347
     */
348
    public function setSelfClosingTag($value = true)
349
    {
350
        $this->_selfclosing = $value;
351
    }
352
353
    /**
354
     * Specifies whether attributes need to have a value
355
     * or if they can be shorthand on this `XMLElement`.
356
     *
357
     * @param bool|string $value (optional)
358
     *  Defaults to true
359
     */
360
    public function setAllowEmptyAttributes($value = true)
361
    {
362
        $this->_allowEmptyAttributes = $value;
363
    }
364
365
    /**
366
     * Sets the name of this `XMLElement`, ie. 'p' => <p />
367
     *
368
     * @since Symphony 2.3.2
369
     * @param string $name
370
     *  The name of the `XMLElement`, 'p'.
371
     * @param boolean $createHandle
372
     *  Whether this function should convert the `$name` to a handle. Defaults to
373
     *  `false`.
374
     */
375
    public function setName($name, $createHandle = false)
376
    {
377
        $this->_name = ($createHandle) ? Lang::createHandle($name) : $name;
378
    }
379
380
    /**
381
     * Sets the value of the `XMLElement`. Checks to see
382
     * whether the value should be prepended or appended
383
     * to the children.
384
     *
385
     * @param string|XMLElement|array $value
386
     *  Defaults to true.
387
     */
388
    public function setValue($value)
389
    {
390
        if (is_array($value)) {
391
            $value = implode(', ', $value);
392
        }
393
394
        if (!is_null($value)) {
395
            $this->_value = $value;
396
            $this->appendChild($value);
397
        }
398
    }
399
400
    /**
401
     * This function will remove all text attributes from the `XMLElement` node
402
     * and replace them with the given value.
403
     *
404
     * @since Symphony 2.4
405
     * @param string|XMLElement|array $value
406
     */
407
    public function replaceValue($value)
408
    {
409
        foreach ($this->_children as $i => $child) {
410
            if ($child instanceof XMLElement) {
411
                continue;
412
            }
413
414
            unset($this->_children[$i]);
415
        }
416
417
        $this->setValue($value);
418
    }
419
420
    /**
421
     * Sets an attribute
422
     *
423
     * @param string $name
424
     *  The name of the attribute
425
     * @param string $value
426
     *  The value of the attribute
427
     */
428
    public function setAttribute($name, $value)
429
    {
430
        $this->_attributes[$name] = $value;
431
    }
432
433
    /**
434
     * A convenience method to quickly add multiple attributes to
435
     * an `XMLElement`
436
     *
437
     * @param array $attributes
438
     *  Associative array with the key being the name and
439
     *  the value being the value of the attribute.
440
     */
441
    public function setAttributeArray(array $attributes = null)
442
    {
443
        if (!is_array($attributes) || empty($attributes)) {
444
            return;
445
        }
446
447
        foreach ($attributes as $name => $value) {
448
            $this->setAttribute($name, $value);
449
        }
450
    }
451
452
    /**
453
     * A convenience method that encapsulate validation of a child node.
454
     * This should prevent generate errors by catching them earlier.
455
     *
456
     * @since Symphony 2.5.0
457
     * @param XMLElement $child
458
     *  The child to validate
459
     *
460
     */
461
    private function validateChild($child)
462
    {
463
        if ($this === $child) {
464
            throw new Exception(__('Can not add the element itself as one of its child'));
465
        }
466
    }
467
468
    /**
469
     * This function expects an array of `XMLElement` that will completely
470
     * replace the contents of `$this->_children`. Take care when using
471
     * this function.
472
     *
473
     * @since Symphony 2.2.2
474
     * @param array $children
475
     *  An array of XMLElement's to act as the children for the current
476
     *  XMLElement instance
477
     * @return boolean
478
     */
479
    public function setChildren(array $children = null)
480
    {
481
        foreach ($children as $child) {
482
            $this->validateChild($child);
483
        }
484
        $this->_children = $children;
485
486
        return true;
487
    }
488
489
    /**
490
     * Adds an `XMLElement` to the children array
491
     *
492
     * @param XMLElement|string $child
493
     * @return boolean
494
     */
495
    public function appendChild($child)
496
    {
497
        $this->validateChild($child);
0 ignored issues
show
It seems like $child defined by parameter $child on line 495 can also be of type string; however, XMLElement::validateChild() does only seem to accept object<XMLElement>, maybe add an additional type check?

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.

Loading history...
498
        $this->_children[] = $child;
499
500
        return true;
501
    }
502
503
    /**
504
     * A convenience method to add children to an `XMLElement`
505
     * quickly.
506
     *
507
     * @param array $children
508
     */
509
    public function appendChildArray(array $children = null)
510
    {
511
        if (is_array($children) && !empty($children)) {
512
            foreach ($children as $child) {
513
                $this->appendChild($child);
514
            }
515
        }
516
    }
517
518
    /**
519
     * Adds an `XMLElement` to the start of the children
520
     * array, this will mean it is output before any other
521
     * children when the `XMLElement` is generated
522
     *
523
     * @param XMLElement $child
524
     */
525
    public function prependChild(XMLElement $child)
526
    {
527
        array_unshift($this->_children, $child);
528
    }
529
530
    /**
531
     * A convenience method to quickly add a CSS class to this `XMLElement`'s
532
     * existing class attribute. If the attribute does not exist, it will
533
     * be created.
534
     *
535
     * @since Symphony 2.2.2
536
     * @param string $class
537
     *  The CSS classname to add to this `XMLElement`
538
     */
539 View Code Duplication
    public function addClass($class)
540
    {
541
        $current = preg_split('%\s+%', $this->getAttribute('class'), 0, PREG_SPLIT_NO_EMPTY);
542
        $added = preg_split('%\s+%', $class, 0, PREG_SPLIT_NO_EMPTY);
543
        $current = array_merge($current, $added);
544
        $classes = implode(' ', $current);
545
546
        $this->setAttribute('class', $classes);
547
    }
548
549
    /**
550
     * A convenience method to quickly remove a CSS class from an
551
     * `XMLElement`'s existing class attribute. If the attribute does not
552
     * exist, this method will do nothing.
553
     *
554
     * @since Symphony 2.2.2
555
     * @param string $class
556
     *  The CSS classname to remove from this `XMLElement`
557
     */
558 View Code Duplication
    public function removeClass($class)
559
    {
560
        $classes = preg_split('%\s+%', $this->getAttribute('class'), 0, PREG_SPLIT_NO_EMPTY);
561
        $removed = preg_split('%\s+%', $class, 0, PREG_SPLIT_NO_EMPTY);
562
        $classes = array_diff($classes, $removed);
563
        $classes = implode(' ', $classes);
564
565
        $this->setAttribute('class', $classes);
566
    }
567
568
    /**
569
     * Returns the number of children this `XMLElement` has.
570
     * @return integer
571
     */
572
    public function getNumberOfChildren()
573
    {
574
        return count($this->_children);
575
    }
576
577
    /**
578
     * Given the position of the child in the `$this->_children`,
579
     * this function will unset the child at that position. This function
580
     * is not reversible. This function does not alter the key's of
581
     * `$this->_children` after removing a child
582
     *
583
     * @since Symphony 2.2.2
584
     * @param integer $index
585
     *  The index of the child to be removed. If the index given is negative
586
     *  it will be calculated from the end of `$this->_children`.
587
     * @return boolean
588
     *  True if child was successfully removed, false otherwise.
589
     */
590
    public function removeChildAt($index)
591
    {
592
        if (!is_numeric($index)) {
593
            return false;
594
        }
595
596
        $index = $this->getRealIndex($index);
597
598
        if (!isset($this->_children[$index])) {
599
            return false;
600
        }
601
602
        unset($this->_children[$index]);
603
604
        return true;
605
    }
606
607
    /**
608
     * Given a desired index, and an `XMLElement`, this function will insert
609
     * the child at that index in `$this->_children` shuffling all children
610
     * greater than `$index` down one. If the `$index` given is greater then
611
     * the number of children for this `XMLElement`, the `$child` will be
612
     * appended to the current `$this->_children` array.
613
     *
614
     * @since Symphony 2.2.2
615
     * @param integer $index
616
     *  The index where the `$child` should be inserted. If this is negative
617
     *  the index will be calculated from the end of `$this->_children`.
618
     * @param XMLElement $child
619
     *  The XMLElement to insert at the desired `$index`
620
     * @return boolean
621
     */
622
    public function insertChildAt($index, XMLElement $child = null)
623
    {
624
        if (!is_numeric($index)) {
625
            return false;
626
        }
627
        
628
        $this->validateChild($child);
0 ignored issues
show
It seems like $child defined by parameter $child on line 622 can be null; however, XMLElement::validateChild() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
629
630
        if ($index >= $this->getNumberOfChildren()) {
631
            return $this->appendChild($child);
0 ignored issues
show
It seems like $child defined by parameter $child on line 622 can be null; however, XMLElement::appendChild() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
632
        }
633
634
        $start = array_slice($this->_children, 0, $index);
635
        $end = array_slice($this->_children, $index);
636
637
        $merge = array_merge($start, array(
638
            $index => $child
639
        ), $end);
640
641
        return $this->setChildren($merge);
642
    }
643
644
    /**
645
     * Given the position of the child to replace, and an `XMLElement`
646
     * of the replacement child, this function will replace one child
647
     * with another
648
     *
649
     * @since Symphony 2.2.2
650
     * @param integer $index
651
     *  The index of the child to be replaced. If the index given is negative
652
     *  it will be calculated from the end of `$this->_children`.
653
     * @param XMLElement $child
654
     *  An XMLElement of the new child
655
     * @return boolean
656
     */
657
    public function replaceChildAt($index, XMLElement $child = null)
658
    {
659
        if (!is_numeric($index)) {
660
            return false;
661
        }
662
663
        $this->validateChild($child);
0 ignored issues
show
It seems like $child defined by parameter $child on line 657 can be null; however, XMLElement::validateChild() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
664
665
        $index = $this->getRealIndex($index);
666
667
        if (!isset($this->_children[$index])) {
668
            return false;
669
        }
670
671
        $this->_children[$index] = $child;
672
673
        return true;
674
    }
675
676
    /**
677
     * Given an `$index`, return the real index in `$this->_children`
678
     * depending on if the value is negative or not. Negative values
679
     * will work from the end of an array.
680
     *
681
     * @since Symphony 2.2.2
682
     * @param integer $index
683
     *  Positive indexes are returned as is, negative indexes are deducted
684
     *  from the end of `$this->_children`
685
     * @return integer
686
     */
687
    private function getRealIndex($index)
688
    {
689
        if ($index >= 0) {
690
            return $index;
691
        }
692
693
        return $this->getNumberOfChildren() + $index;
694
    }
695
696
    /**
697
     * This function strips characters that are not allowed in XML
698
     *
699
     * @since Symphony 2.3
700
     * @link http://www.w3.org/TR/xml/#charsets
701
     * @link http://www.phpedit.net/snippet/Remove-Invalid-XML-Characters
702
     * @param string $value
703
     * @return string
704
     */
705
    public static function stripInvalidXMLCharacters($value)
706
    {
707
        if (Lang::isUnicodeCompiled()) {
708
            return preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $value);
709
        } else {
710
            $ret = '';
711
712
            if (empty($value)) {
713
                return $ret;
714
            }
715
716
            $length = strlen($value);
717
718
            for ($i=0; $i < $length; $i++) {
719
                $current = ord($value{$i});
720
                if (($current == 0x9) ||
721
                    ($current == 0xA) ||
722
                    ($current == 0xD) ||
723
                    (($current >= 0x20) && ($current <= 0xD7FF)) ||
724
                    (($current >= 0xE000) && ($current <= 0xFFFD)) ||
725
                    (($current >= 0x10000) && ($current <= 0x10FFFF))) {
726
                    $ret .= chr($current);
727
                }
728
            }
729
730
            return $ret;
731
        }
732
    }
733
734
    /**
735
     * This function will turn the `XMLElement` into a string
736
     * representing the element as it would appear in the markup.
737
     * The result is valid XML.
738
     *
739
     * @param boolean $indent
740
     *  Defaults to false
741
     * @param integer $tab_depth
742
     *  Defaults to 0, indicates the number of tabs (\t) that this
743
     *  element should be indented by in the output string
744
     * @param boolean $has_parent
745
     *  Defaults to false, set to true when the children are being
746
     *  generated. Only the parent will output an XML declaration
747
     *  if `$this->_includeHeader` is set to true.
748
     * @return string
749
     */
750
    public function generate($indent = false, $tab_depth = 0, $has_parent = false)
751
    {
752
        $result = null;
753
        $newline = ($indent ? PHP_EOL : null);
754
755
        if (!$has_parent) {
756
            if ($this->_includeHeader) {
757
                $result .= sprintf(
758
                    '<?xml version="%s" encoding="%s" ?>%s',
759
                    $this->_version,
760
                    $this->_encoding,
761
                    $newline
762
                );
763
            }
764
765
            if ($this->_dtd) {
766
                $result .= $this->_dtd . $newline;
767
            }
768
769
            if (is_array($this->_processingInstructions) && !empty($this->_processingInstructions)) {
770
                $result .= implode(PHP_EOL, $this->_processingInstructions);
771
            }
772
        }
773
774
        $result .= ($indent ? str_repeat("\t", $tab_depth) : null) . '<' . $this->getName();
775
776
        $attributes = $this->getAttributes();
777
778
        if (!empty($attributes)) {
779
            foreach ($attributes as $attribute => $value) {
780
                $length = strlen($value);
781
                if ($length !== 0 || $length === 0 && $this->_allowEmptyAttributes) {
782
                    $result .= sprintf(' %s="%s"', $attribute, $value);
783
                }
784
            }
785
        }
786
787
        $value = $this->getValue();
788
        $added_newline = false;
789
790
        if ($this->getNumberOfChildren() > 0 || strlen($value) !== 0 || !$this->_selfclosing) {
791
            $result .= '>';
792
793
            foreach ($this->_children as $i => $child) {
794
                if (!($child instanceof XMLElement)) {
795
                    $result .= $child;
796
                } else {
797
                    if ($added_newline === false) {
798
                        $added_newline = true;
799
                        $result .= $newline;
800
                    }
801
802
                    $child->setElementStyle($this->_elementStyle);
803
                    $result .= $child->generate($indent, $tab_depth + 1, true);
804
                }
805
            }
806
807
            $result .= sprintf(
808
                "%s</%s>%s",
809
                ($indent && $added_newline ? str_repeat("\t", $tab_depth) : null),
810
                $this->getName(),
811
                $newline
812
            );
813
814
            // Empty elements:
815
        } else {
816
            if ($this->_elementStyle === 'xml') {
817
                $result .= ' />';
818
            } elseif (in_array($this->_name, XMLElement::$no_end_tags) || (substr($this->getName(), 0, 3) === '!--')) {
819
                $result .= '>';
820
            } else {
821
                $result .= sprintf("></%s>", $this->getName());
822
            }
823
824
            $result .= $newline;
825
        }
826
827
        return $result;
828
    }
829
830
    /**
831
     * Given a string of XML, this function will convert it to an `XMLElement`
832
     * object and return the result.
833
     *
834
     * @since Symphony 2.4
835
     * @param string $root_element
836
     * @param string $xml
837
     *  A string of XML
838
     * @return XMLElement
839
     */
840
    public static function convertFromXMLString($root_element, $xml)
841
    {
842
        $doc = new DOMDocument('1.0', 'utf-8');
843
        $doc->loadXML($xml);
844
845
        return self::convertFromDOMDocument($root_element, $doc);
846
    }
847
848
    /**
849
     * Given a `DOMDocument`, this function will convert it to an `XMLElement`
850
     * object and return the result.
851
     *
852
     * @since Symphony 2.4
853
     * @param string $root_element
854
     * @param DOMDOcument $doc
855
     * @return XMLElement
856
     */
857
    public static function convertFromDOMDocument($root_element, DOMDocument $doc)
858
    {
859
        $xpath = new DOMXPath($doc);
860
        $root = new XMLElement($root_element);
861
862
        foreach ($xpath->query('.') as $node) {
863
            self::convertNode($root, $node);
864
        }
865
866
        return $root;
867
    }
868
869
    /**
870
     * This helper function is used by `XMLElement::convertFromDOMDocument`
871
     * to recursively convert `DOMNode` into an `XMLElement` structure
872
     *
873
     * @since Symphony 2.4
874
     * @param XMLElement $root
875
     * @param DOMNOde $node
876
     * @return XMLElement
877
     */
878
    private static function convert(XMLElement $root = null, DOMNode $node)
879
    {
880
        $el = new XMLElement($node->tagName);
881
882
        self::convertNode($el, $node);
883
884
        if (is_null($root)) {
885
            return $el;
886
        } else {
887
            $root->appendChild($el);
888
        }
889
    }
890
891
    /**
892
     * Given a DOMNode, this function will help replicate it as an
893
     * XMLElement object
894
     *
895
     * @since Symphony 2.5.2
896
     * @param XMLElement $element
897
     * @param DOMNode $node
898
     */
899
    private static function convertNode(XMLElement $element, DOMNode $node)
900
    {
901
        if ($node->hasAttributes()) {
902
            foreach ($node->attributes as $name => $attrEl) {
903
                $element->setAttribute($name, General::sanitize($attrEl->value));
904
            }
905
        }
906
907
        if ($node->hasChildNodes()) {
908
            foreach ($node->childNodes as $childNode) {
909
                if ($childNode instanceof DOMCdataSection) {
910
                    $element->setValue(General::wrapInCDATA($childNode->data));
911
                } elseif ($childNode instanceof DOMText) {
912
                    if ($childNode->isWhitespaceInElementContent() === false) {
913
                        $element->setValue(General::sanitize($childNode->data));
914
                    }
915
                } elseif ($childNode instanceof DOMElement) {
916
                    self::convert($element, $childNode);
917
                }
918
            }
919
        }
920
    }
921
}
922
923
/**
924
 * Creates a filter that only returns XMLElement items
925
 */
926
class XMLElementChildrenFilter extends FilterIterator
927
{
928
    public function accept()
929
    {
930
        $item = $this->getInnerIterator()->current();
931
932
        return $item instanceof XMLElement;
933
    }
934
}
935