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.
Passed
Push — 2.x-dev ( 100c60...613aa4 )
by Patrick D
05:21 queued 03:24
created

DOMDoc::out()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7.2269

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 10
cts 12
cp 0.8333
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 13
nc 7
nop 1
crap 7.2269
1
<?php namespace BetterDOMDocument;
2
3
use Symfony\Component\CssSelector\CssSelectorConverter;
4
5
/**
6
 * Better DOM Document.
7
 *
8
 * Copyright (c) 2010-2011 Board of Trustees, Leland Stanford Jr. University
9
 * This software is open-source licensed under the GNU Public License Version 2 or later.
10
 */
11
class DOMDoc extends \DOMDocument {
12
13
  private $auto_ns = FALSE;
14
  public  $ns = array();
15
  public  $default_ns = FALSE;
16
  public  $error_checking = 'strict'; // Can be 'strict', 'warning', 'none' / FALSE
17
18
  /**
19
   * Create a new DOMDoc.
20
   *
21
   * @param null|false|object|string $xml
22
   *   $xml can either be an XML string, a DOMDocument, or a DOMElement.
23
   *   You can also pass FALSE or NULL (or omit it) and load XML later using loadXML or loadHTML.
24
   * @param bool|string $auto_register_namespaces 
25
   *   Auto-register namespaces. All namespaces in the root element will be registered for use in xpath queries.
26
   *   Namespaces that are not declared in the root element will not be auto-registered
27
   *   Defaults to TRUE (Meaning it will auto register all auxiliary namespaces but not the default namespace).
28
   *   Pass a prefix string to automatically register the default namespace.
29
   *   Pass FALSE to disable auto-namespace registeration.
30
   * @param bool|string $error_checking
31
   *   Can be 'strict', 'warning', or 'none. Defaults to 'strict'.
32
   *   'none' supresses all errors.
33
   *   'warning' is the default behavior in DOMDocument.
34
   *   'strict' corresponds to DOMDocument strictErrorChecking TRUE.
35
   */
36 25
  public function __construct($xml = FALSE, $auto_register_namespaces = TRUE, $error_checking = 'strict') {
37 25
    parent::__construct();
38
39 25
    $this->setErrorChecking($error_checking);
40
41 25
    if (is_object($xml)) {
42 17
      if (is_a($xml, 'DOMElement')) {
43 8
        $this->appendChild($this->importNode($xml, true));
44
      }
45 17
      elseif (is_a($xml, 'BetterDOMDocument\DOMDoc')) {
46
        if ($xml->documentElement) {
47
          $this->appendChild($this->importNode($xml->documentElement, true));
48
        }
49
        $this->ns = $xml->ns;
50
      }
51 17
      elseif (is_a($xml, 'DOMDocument')) {
52 17
        if ($xml->documentElement) {
53 17
          $this->appendChild($this->importNode($xml->documentElement, true));
54
        }
55
      }
56
      elseif (method_exists($xml, '__toString')) {
57
        $this->loadFromString($xml->__toString());
58
      }
59
    }
60 18
    elseif (is_string($xml) && !empty($xml)) {
61 15
      $this->loadFromString($xml);
62
    }
63
64 25
    if ($auto_register_namespaces) {
65 25
      $this->AutoRegisterNamespace($auto_register_namespaces);
66
    }
67 25
  }
68
69
  /**
70
   * Register a namespace to be used in xpath queries.
71
   *
72
   * @param string $prefix
73
   *   Namespace prefix to register.
74
   * @param string $url
75
   *   Connonical URL for this namespace prefix.
76
   */
77 14
  public function registerNamespace($prefix, $url) {
78 14
    $this->ns[$prefix] = $url;
79 14
  }
80
81
  /**
82
   * Get the list of registered namespaces as an array.
83
   * 
84
   * @return array
85
   *   An array in form ['prefix' => 'namespace-uri']
86
   */
87 7
  public function getNamespaces() {
88 7
    return $this->ns;
89
  }
90
91
  /**
92
   * Given a namespace URL, get the prefix.
93
   *
94
   * @param string $url
95
   *   Connonical URL for this namespace prefix.
96
   *
97
   * @return string|false
98
   *   The namespace prefix or FALSE if there is no namespace with that URL.
99
   */
100 1
  public function lookupPrefix($url) {
101 1
    return array_search($url, $this->ns);
102
  }
103
104
  /**
105
   * Given a namespace prefix, get the URL.
106
   *
107
   * @param string $prefix
108
   *   Namespace prefix.
109
   *
110
   * @return string|false
111
   *   The namespace URL or FALSE if there is no namespace with that prefix
112
   */
113 1
  public function lookupURL($prefix) {
114 1
    if (isset($this->ns[$prefix])) {
115 1
      return $this->ns[$prefix];
116
    }
117
    else {
118
      return FALSE;
119
    }
120
  }
121
122
  /**
123
   * Given an xpath, get a list of nodes.
124
   *
125
   * @param string $xpath
126
   *   XPath to be used for query.
127
   *
128
   * @param mixed $context
129
   *   Can either be an xpath string, or a DOMElement.
130
   *   Provides context for the xpath query.
131
   *
132
   * @return DOMList|false
133
   *   A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility.
134
   */
135 19
  public function xpath($xpath, $context = NULL) {
136 19
    $this->createContext($context, 'xpath', FALSE);
137
138 19
    if ($context === FALSE) {
139
      return FALSE;
140
    }
141
142 19
    $xob = new \DOMXPath($this);
143
144
    // Register the namespaces
145 19
    foreach ($this->ns as $namespace => $url) {
146 10
      $xob->registerNamespace($namespace, $url);
147
    }
148
149 19
    if ($context) {
150 3
      $result = $xob->query($xpath, $context);
151
    }
152
    else {
153 19
      $result = $xob->query($xpath);
154
    }
155
156 19
    if ($result) {
157 19
      return new DOMList($result, $this);
158
    }
159
    else {
160
      return FALSE;
161
    }
162
  }
163
164
165
  /**
166
   * Given an xpath, get a single node (first one found)
167
   * 
168
   * @param string $xpath
169
   *  xpath to be used for query
170
   * 
171
   * @param mixed $context
172
   *  $context can either be an xpath string, or a DOMElement
173
   *  Provides context for the xpath query
174
   * 
175
   * @return mixed
176
   *  The first node found by the xpath query
177
   */
178 18
  public function xpathSingle($xpath, $context = NULL) {
179 18
    $result = $this->xpath($xpath, $context);
180
    
181 18
    if (empty($result) || !count($result)) {
182 2
      return FALSE;
183
    }
184
    else {
185 17
      return $result->item(0);
186
    }
187
  }
188
189
190
  /**
191
   * Given an CSS selector, get a list of nodes.
192
   * 
193
   * @param string $css_selector
194
   *  CSS Selector to be used for query
195
   * 
196
   * @param mixed $context
197
   *  $context can either be an xpath string, or a DOMElement
198
   *  Provides context for the CSS selector
199
   * 
200
   * @return DOMList|false
201
   *  A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility.
202
   */
203 1
  public function select($css_selector, $context = NULL) {
204 1
    $converter = new CssSelectorConverter();
205 1
    $xpath = $converter->toXPath($css_selector);
206
207 1
    return $this->xpath($xpath, $context);
208
  }
209
210
  /**
211
   * Given an CSS selector, get a single node.
212
   * 
213
   * @param string $css_selector
214
   *  CSS Selector to be used for query
215
   * 
216
   * @param mixed $context
217
   *  $context can either be an xpath string, or a DOMElement
218
   *  Provides context for the CSS selector
219
   * 
220
   * @return DOMList
221
   *  A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility.
222
   */
223 1
  public function selectSingle($css_selector, $context = NULL) {
224 1
    $converter = new CssSelectorConverter();
225 1
    $xpath = $converter->toXPath($css_selector);
226
227 1
    return $this->xpathSingle($xpath, $context);
228
  }
229
230
  /**
231
   * Get the document (or an element) as an array
232
   *
233
   * @param string $raw
234
   *  Can be either FALSE, 'full', or 'inner'. Defaults to FALSE.
235
   *  When set to 'full' every node's full XML is also attached to the array
236
   *  When set to 'inner' every node's inner XML is attached to the array.
237
   * 
238
   * @param mixed $context 
239
   *  Optional context node. Can pass an DOMElement object or an xpath string.
240
   *  If passed, only the given node will be used when generating the array 
241
   */
242 1
  public function getArray($raw = FALSE, $context = NULL) {
243 1
    $array = false;
244
245 1
    $this->createContext($context, 'xpath', FALSE);
246
    
247 1
    if ($context) {
248 1
      if ($raw == 'full') {
249 1
        $array['#raw'] = $this->saveXML($context);
250
      }
251 1
      if ($raw == 'inner') {
252 1
        $array['#raw'] = $this->innerText($context);
253
      }
254 1
      if ($context->hasAttributes()) {
255 1
        foreach ($context->attributes as $attr) {
256 1
          $array['@'.$attr->nodeName] = $attr->nodeValue;
257
        }
258
      }
259
  
260 1
      if ($context->hasChildNodes()) {
261 1
        if ($context->childNodes->length == 1 && $context->firstChild->nodeType == XML_TEXT_NODE) {
262 1
          $array['#text'] = $context->firstChild->nodeValue;
263
        }
264
        else {
265 1
          foreach ($context->childNodes as $childNode) {
266 1
            if ($childNode->nodeType == XML_ELEMENT_NODE) {
267 1
              $array[$childNode->nodeName][] = $this->getArray($raw, $childNode);
268
            }
269 1
            elseif ($childNode->nodeType == XML_CDATA_SECTION_NODE) {
270 1
              $array['#text'] = $childNode->textContent;
271
            }
272
          }
273
        }
274
      }
275
    }
276
    // Else no node was passed, which means we are processing the entire domDocument
277
    else {
278 1
      foreach ($this->childNodes as $childNode) {
279 1
        if ($childNode->nodeType == XML_ELEMENT_NODE) {
280 1
          $array[$childNode->nodeName][] = $this->getArray($raw, $childNode);
281
        }
282
      }
283
    }
284
285 1
    return $array;
286
  }
287
  
288
  /**
289
   * Get the inner text of an element
290
   * 
291
   * @param mixed $context 
292
   *  Optional context node. Can pass an DOMElement object or an xpath string.
293
   */
294 1
  public function innerText($context = NULL) {
295 1
    $this->createContext($context, 'xpath');
296
    
297 1
    $pattern = "/<".preg_quote($context->nodeName)."\b[^>]*>(.*)<\/".preg_quote($context->nodeName).">/s";
298 1
    $matches = array();
299 1
    if (preg_match($pattern, $this->saveXML($context), $matches)) {
300 1
      return $matches[1];
301
    }
302
    else {
303 1
      return '';
304
    }
305
  }
306
307
  /**
308
   * Create an DOMElement from XML and attach it to the DOMDocument
309
   * 
310
   * Note that this does not place it anywhere in the dom tree, it merely imports it.
311
   * 
312
   * @param string $xml 
313
   *  XML string to import
314
   */
315 5
  public function createElementFromXML($xml) {
316
    
317
    // To make thing easy and make sure namespaces work properly, we add the root namespace delcarations if it is not declared
318 5
    $namespaces = $this->ns;
319 5
    $xml = preg_replace_callback('/<[^\?^!].+?>/s', function($root_match) use ($namespaces) {
320 5
      preg_match('/<([^ <>]+)[\d\s]?.*?>/s', $root_match[0], $root_tag);
321 5
      $new_root = $root_tag[1];
322 5
      if (strpos($new_root, ':')) {
323
        $parts = explode(':', $new_root);
324
        $prefix = $parts[0]; 
325
        if (isset($namespaces[$prefix])) {
326
          if (!strpos($root_match[0], "xmlns:$prefix")) {
327
            $new_root .= " xmlns:$prefix='" . $namespaces[$prefix] . "'";             
328
          }
329
        }
330
      }
331 5
      return str_replace($root_tag[1], $new_root, $root_match[0]);
332 5
    }, $xml, 1);
333
334 5
    $dom = new DOMDoc($xml, $this->auto_ns);
335 5
    if (!$dom->documentElement) {
336
      trigger_error('BetterDomDocument\DOMDoc Error: Invalid XML: ' . $xml);
337
    }
338 5
    $element = $dom->documentElement;
339
    
340
    // Merge the namespaces
341 5
    foreach ($dom->getNamespaces() as $prefix => $url) {
342
      $this->registerNamespace($prefix, $url);
343
    }
344
    
345 5
    return $this->importNode($element, true);
346
  }
347
348
  /**
349
   * Append a child to the context node, make it the last child
350
   * 
351
   * @param mixed $newnode
352
   *  $newnode can either be an XML string, a DOMDocument, or a DOMElement. 
353
   * 
354
   * @param mixed $context
355
   *  $context can either be an xpath string, or a DOMElement
356
   *  Omiting $context results in using the root document element as the context
357
   * 
358
   * @return DOMElement|false
359
   *  The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement
360
   *  then you should replace your DOMElement with the returned one.
361
   */
362 1 View Code Duplication
  public function append($newnode, $context = NULL) {
363 1
    $this->createContext($newnode, 'xml');
364 1
    $this->createContext($context, 'xpath');
365
    
366 1
    if (!$context || !$newnode) {
367
      return FALSE;
368
    }
369
370 1
    return $context->appendChild($newnode);
371
  }
372
  
373
  /**
374
   * Append a child to the context node, make it the first child
375
   * 
376
   * @param mixed $newnode
377
   *  $newnode can either be an XML string, a DOMDocument, or a DOMElement. 
378
   * 
379
   * @param mixed $context
380
   *  $context can either be an xpath string, or a DOMElement
381
   *  Omiting $context results in using the root document element as the context
382
   *
383
   * @return DOMElement|false
384
   *  The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement
385
   *  then you should replace your DOMElement with the returned one.
386
   */
387 1 View Code Duplication
  public function prepend($newnode, $context = NULL) {
388 1
    $this->createContext($newnode, 'xml');
389 1
    $this->createContext($context, 'xpath');
390
    
391 1
    if (!$context || !$newnode) {
392
      return FALSE;
393
    }
394
    
395 1
    return $context->insertBefore($newnode, $context->firstChild);
396
  }
397
398
  /**
399
   * Prepend a sibling to the context node, put it just before the context node
400
   * 
401
   * @param mixed $newnode
402
   *  $newnode can either be an XML string, a DOMDocument, or a DOMElement. 
403
   * 
404
   * @param mixed $context
405
   *  $context can either be an xpath string, or a DOMElement
406
   *  Omiting $context results in using the root document element as the context 
407
   * 
408
   * @return DOMElement|false
409
   *  The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement
410
   *  then you should replace your DOMElement with the returned one.
411
   */
412 1 View Code Duplication
  public function prependSibling($newnode, $context = NULL) {
413 1
    $this->createContext($newnode, 'xml');
414 1
    $this->createContext($context, 'xpath');
415
    
416 1
    if (!$context || !$newnode) {
417
      return FALSE;
418
    }
419
    
420 1
    return $context->parentNode->insertBefore($newnode, $context);
421
  }
422
  
423
  /**
424
   * Append a sibling to the context node, put it just after the context node
425
   * 
426
   * @param mixed $newnode
427
   *  $newnode can either be an XML string, a DOMDocument, or a DOMElement. 
428
   * 
429
   * @param mixed $context
430
   *  $context can either be an xpath string, or a DOMElement
431
   *  Omiting $context results in using the root document element as the context 
432
   * 
433
   * @return DOMElement|false
434
   *  The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement
435
   *  then you should replace your DOMElement with the returned one.
436
   */
437 1
  public function appendSibling($newnode, $context) {
438 1
    $this->createContext($newnode, 'xml');
439 1
    $this->createContext($context, 'xpath');
440
    
441 1
    if (!$context){
442
      return FALSE;
443
    }
444
    
445 1
    if ($context->nextSibling) { 
446
      // $context has an immediate sibling : insert newnode before this one 
447 1
      return $context->parentNode->insertBefore($newnode, $context->nextSibling); 
448
    }
449
    else { 
450
      return $context->parentNode->appendChild($newnode); 
451
    }
452
  }
453
  
454
  /**
455
   * Given an xpath or DOMElement, return a new DOMDoc.
456
   * 
457
   * @param mixed $node
458
   *  $node can either be an xpath string or a DOMElement. 
459
   * 
460
   * @return DOMDoc
461
   *  A new DOMDoc created from the xpath or DOMElement
462
   */
463 7
  public function extract($node, $auto_register_namespaces = TRUE, $error_checking = 'none') {
464 7
    $this->createContext($node, 'xpath');
465 7
    $dom = new DOMDoc($node, $auto_register_namespaces, $error_checking);
466 7
    $dom->ns = $this->ns;
467 7
    return $dom;
468
  }
469
  
470
  /**
471
   * Given a pair of nodes, replace the first with the second
472
   * 
473
   * @param mixed $node
474
   *  Node to be replaced. Can either be an xpath string or a DOMDocument (or even a DOMNode).
475
   * 
476
   * @param mixed $replace
477
   *  Replace $node with $replace. Replace can be an XML string, or a DOMNode
478
   * 
479
   * @return mixed
480
   *   The overwritten / replaced node.
481
   */
482 2
  public function replace($node, $replace) {
483 2
    $this->createContext($node, 'xpath');
484 2
    $this->createContext($replace, 'xml');
485
    
486 2
    if (!$node || !$replace) {
487
      return FALSE;
488
    }
489
        
490 2
    if (!$replace->ownerDocument->documentElement->isSameNode($this->documentElement)) {
491
      $replace = $this->importNode($replace, true);
492
    }
493 2
    $node->parentNode->replaceChild($replace, $node);
494 2
    $node = $replace;
495 2
    return $node;
496
  }
497
498
  /**
499
   * Given a node(s), remove / delete them
500
   * 
501
   * @param mixed $node
502
   *  Can pass a DOMNode, a NodeList, DOMNodeList, an xpath string, or an array of any of these.
503
   */
504 1
  public function remove($node) {
505
    // We can't use createContext here because we want to use the entire nodeList (not just a single element)
506 1
    if (is_string($node)) {
507 1
      $node = $this->xpath($node);
508
    }
509
    
510 1
    if ($node) {
511 1
      if (is_array($node) || get_class($node) == 'BetterDOMDocument\DOMList') {
512 1
        foreach($node as $item) {
513 1
          $this->remove($item);
514
        }
515
      }
516 1
      else if (get_class($node) == 'DOMNodeList') {
517
        $this->remove(new DOMList($node, $this));
518
      }
519
      else {
520 1
        $parent = $node->parentNode;
521 1
        $parent->removeChild($node);
522
      }
523
    }
524 1
  }
525
  
526
  /**
527
   * Given an XSL string, transform the DOMDoc (or a passed context node)
528
   * 
529
   * @param string $xsl
530
   *   XSL Transormation
531
   * 
532
   * @param mixed $context
533
   *   $context can either be an xpath string, or a DOMElement. Ommiting it
534
   *   results in transforming the entire document
535
   * 
536
   * @return a new DOMDoc
537
   */
538 4
  public function tranform($xsl, $context = NULL) {
539 4
    if (!$context) {
540 2
      $doc = $this;
541
    }
542
    else {
543 2
      if (is_string($context)) {
544 1
        $context = $this->xpathSingle($context);
545
      }
546 2
      $doc = new DOMDoc($context);
547
    }
548
    
549 4
    $xslDoc = new DOMDoc($xsl);
550 4
    $xslt = new \XSLTProcessor();
551 4
    $xslt->importStylesheet($xslDoc);
552
    
553 4
    return new DOMDoc($xslt->transformToDoc($doc));
554
  }
555
556
  /**
557
   * Given a node, change it's namespace to the specified namespace in situ
558
   * 
559
   * @param mixed $node
560
   *  Node to be changed. Can either be an xpath string or a DOMElement.
561
   * 
562
   * @param mixed $prefix
563
   *   prefix for the new namespace
564
   * 
565
   * @param mixed $url
566
   *   The URL for the new namespace
567
   * 
568
   * @return mixed
569
   *   The node with the new namespace. The node will also be changed in-situ in the document as well.
570
   */
571 1
  public function changeNamespace($node, $prefix, $url) {
572 1
    $this->createContext($node, 'xpath');
573
    
574 1
    if (!$node) {
575
      return FALSE;
576
    }
577
    
578 1
    $this->registerNamespace($prefix, $url);
579
580 1
    if (get_class($node) == 'DOMElement') {
581
      $xsl = '
582
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
583
          <xsl:template match="*">
584 1
            <xsl:element name="' . $prefix . ':{local-name()}" namespace="' . $url . '">
585
             <xsl:copy-of select="@*"/>
586
             <xsl:apply-templates/>
587
            </xsl:element>
588
          </xsl:template>
589
        </xsl:stylesheet>';
590
591 1
      $transformed = $this->tranform($xsl, $node);
592 1
      return $this->replace($node, $transformed->documentElement);   
593
    }
594
    else {
595
      // @@TODO: Report the correct calling file and number
596
      throw new \Exception("Changing the namespace of a " . get_class($node) . " is not supported");
597
    }
598
  }
599
600
  /**
601
   * Get a lossless HTML representation of the XML
602
   *
603
   * Transforms the document (or passed context) into a set of HTML spans.
604
   * The element name becomes the class, all other attributes become HTML5
605
   * "data-" attributes.
606
   * 
607
   * @param mixed $context
608
   *   $context can either be an xpath string, or a DOMElement. Ommiting it
609
   *   results in transforming the entire document
610
   * 
611
   * @param array $options
612
   *   Options for transforming the HTML into XML. The following options are supported:
613
   *   'xlink' => {TRUE or xpath}
614
   *     Transform xlink links into <a href> elements. If you specify 'xlink' => TRUE then 
615
   *     it will transform all elements with xlink:type = simple into a <a href> element. 
616
   *     Alternatively you may specify your own xpath for selecting which elements get transformed
617
   *     into <a href> tags. 
618
   * @return HTML string
619
   */  
620 3
  public function asHTML($context = NULL, $options = array()) {
621 3
    $xslSimple = '
622
      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
623
      <xsl:template match="*">
624
        <span class="{translate(name(.),\':\',\'-\')}">
625
          <xsl:for-each select="./@*">
626
            <xsl:attribute name="data-{translate(name(.),\':\',\'-\')}">
627
              <xsl:value-of select="." />
628
            </xsl:attribute>
629
          </xsl:for-each>
630
          <xsl:apply-templates/>
631
        </span>
632
      </xsl:template>
633
      </xsl:stylesheet>';
634
635 3
    $xslOptions = '
636
      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xlink="http://www.w3.org/1999/xlink" ||namespaces||>
637
      <xsl:template match="*">
638
        <xsl:choose>
639
          <xsl:when test="||xlink||">
640
            <a class="{translate(name(.),\':\',\'-\')}">
641
              <xsl:for-each select="./@*">
642
                <xsl:attribute name="data-{translate(name(.),\':\',\'-\')}">
643
                  <xsl:value-of select="."/>
644
                </xsl:attribute>
645
              </xsl:for-each>
646
              <xsl:attribute name="href">
647
                <xsl:value-of select="@xlink:href"/>
648
              </xsl:attribute>
649
              <xsl:apply-templates/>
650
            </a>
651
          </xsl:when>
652
          <xsl:otherwise>
653
            <span class="{translate(name(.),\':\',\'-\')}">
654
              <xsl:for-each select="./@*">
655
                <xsl:attribute name="data-{translate(name(.),\':\',\'-\')}">
656
                  <xsl:value-of select="." />
657
                </xsl:attribute>
658
              </xsl:for-each>
659
              <xsl:apply-templates/>
660
            </span>
661
          </xsl:otherwise>
662
        </xsl:choose>
663
      </xsl:template>
664
      </xsl:stylesheet>';
665
666 3
    if (!empty($options)) {
667
      // Add in the namespaces
668 1
      foreach ($this->getNamespaces() as $prefix => $url) {
669 1
        $namespaces = '';
670 1
        if ($prefix != 'xsl' && $prefix != 'xlink') {
671 1
          $namespaces .= 'xmlns:' . $prefix . '="' . $url. '" ';
672
        }
673 1
        $xslOptions = str_replace("||namespaces||", $namespaces, $xslOptions);
674
      }
675
676
      // Add in xlink options
677 1
      if ($options['xlink'] === TRUE) {
678 1
        $options['xlink'] = "@xlink:type = 'simple'";
679
      }
680
      else if (empty($options['xlink'])) {
681
        $options['xlink'] = "false()";
682
      }
683 1
      $xslOptions = str_replace("||xlink||", $options['xlink'], $xslOptions);
684 1
      $transformed = $this->tranform($xslOptions, $context);
685
    }
686
    else {
687 2
      $transformed = $this->tranform($xslSimple, $context);
688
    }
689
    
690 3
    return $transformed->out();
691
  }
692
693
  /**
694
   * Output the DOMDoc as an XML string
695
   * 
696
   * @param mixed $context
697
   *   $context can either be an xpath string, or a DOMElement. Ommiting it
698
   *   results in outputting the entire document
699
   * 
700
   * @return XML string
701
   */  
702 13
  public function out($context = NULL) {
703 13
    $this->createContext($context, 'xpath');
704 13
    if (!$context) {
705 2
      return '';
706
    }
707
708
    // Copy namespace prefixes
709 11
    foreach ($this->ns as $prefix => $namespace) {
710
      if (!empty($namespace) && !$context->hasAttribute('xmlns:' . $prefix)) {
711
        $context->setAttribute('xmlns:' . $prefix, $namespace);
712
      }
713
    }
714
    
715
    // Check to seee if it's HTML, if it is we need to fix broken html void elements.
716 11
    if ($this->documentElement->lookupNamespaceURI(NULL) == 'http://www.w3.org/1999/xhtml' || $this->documentElement->tagName == 'html') {
717 2
      $output = $this->saveXML($context, LIBXML_NOEMPTYTAG);
718
      // The types listed are html "void" elements. 
719
      // Find any of these elements that have no child nodes and are therefore candidates for self-closing, replace them with a self-closed version. 
720 2
      $pattern = '<(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)(\b[^<]*)><\/\1>';
721 2
      return preg_replace('/' . $pattern . '/', '<$1$2/>', $output);
722
    }
723
    else {
724 9
      return $this->saveXML($context, LIBXML_NOEMPTYTAG);
725
    }
726
  }
727
728
  /**
729
   * Magic method for casting a DOMDoc as a string
730
   */ 
731 1
  public function __toString() {
732 1
    return $this->out();
733
  }
734
735
  /**
736
   * Magic method to get good debug info view var_dump
737
   */ 
738
  public function __debugInfo() {
739
    return array(
740
      'auto_ns' => $this->auto_ns,
741
      'ns' => $this->ns,
742
      'default_ns' => $this->default_ns,
743
      'error_checking' => $this->error_checking, 
744
      'content' => $this->out(),
745
    );
746
  }
747
748 25
  public function setErrorChecking($error_checking) {
749
    // Check up error-checking
750 25
    if ($error_checking == FALSE) {
751
      $this->error_checking = 'none';
752
    }
753
    else {
754 25
      $this->error_checking = $error_checking;
755
    }
756 25
    if ($this->error_checking != 'strict') {
757 7
      $this->strictErrorChecking = FALSE;
758
    }
759 25
  }
760
761 14
  public static function loadFile($file_or_url, $auto_register_namespaces = TRUE) {
762 14
    $dom = @parent::load($file_or_url, LIBXML_COMPACT);
763 14
    if (empty($dom)) {
764
      return FALSE;
765
    }
766
767 14
    return new DOMDoc($dom, $auto_register_namespaces);
768
  }
769
770 1
  public function loadHTML($source, $options = NULL) {
771 1
    $success = parent::loadHTML($source, $options);
772 1
    $this->AutoRegisterNamespace(TRUE);
773
774 1
    return boolval($success);
775
  }
776
777 15
  public function loadXML($source, $options = NULL) {
778 15
    $success = parent::loadXML($source, $options);
779 15
    $this->AutoRegisterNamespace(TRUE);
780
    
781 15
    return boolval($success);
782
  }
783
784
  /**
785
   * Removes a namespace from the document, moving the
786
   * namespaced nodes to the default namespace.
787
   * 
788
   * @param string $prefix
789
   *   Namespace prefix.
790
   */ 
791 1
  public function removeNamespace($prefix) {
792 1
    $ns_uri = $this->ns[$prefix];
793 1
    if (empty($ns_uri)) {
794
      throw new \Exception("Unknown prefix $prefix. Try calling registerNamespace() first.");
795
    }
796 1
    $nodes = $this->xpath("//*[namespace::{$prefix} and not(../namespace::{$prefix})]");
797 1
    foreach ($nodes as $node) {
0 ignored issues
show
Bug introduced by
The expression $nodes of type false|object<BetterDOMDocument\DOMList> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
798 1
      $node->removeAttributeNS($ns_uri, $prefix);
799
    }
800 1
    unset($this->ns[$prefix]);
801 1
  }
802
803 25
  protected function AutoRegisterNamespace($auto_register_namespaces) {
804 25
    $this->auto_ns = TRUE;
805
806
    // If it's an "XML" document, then get namespaces via xpath
807 25
    $xpath = new \DOMXPath($this);
808 25
    foreach($xpath->query('namespace::*') as $namespace) {
809 24
      if (!empty($namespace->prefix)) {
810 24
        if ($namespace->prefix != 'xml' && $namespace->nodeValue != 'http://www.w3.org/XML/1998/namespace') {
811 11
          $this->registerNamespace($namespace->prefix, $namespace->nodeValue);
812
        }
813
      }
814 View Code Duplication
      else {
815 3
        $this->default_ns = $namespace->nodeValue;
816 3
        if (is_string($auto_register_namespaces)) {
817
          $this->registerNamespace($auto_register_namespaces, $namespace->nodeValue);
818
        }
819
        // Otherwise, automatically set-up the root element tag name as the prefix for the default namespace
820
        else {
821 3
          $tagname = $this->documentElement->tagName;
822 3
          if (empty($this->ns[$tagname])) {
823 3
            $this->registerNamespace($tagname, $this->documentElement->getAttribute('xmlns'));
824
          }
825
        }
826
      }
827
    }
828
829
    // If it's an "HTML" document, we get namespaces via attributes
830 25
    if (empty($this->ns) && !empty($this->documentElement)) {
831 15
      foreach ($this->documentElement->attributes as $attr) {
832 4
        if ($attr->name == 'xmlns') {
833 1
          $this->default_ns = $attr->value;
834
          // If auto_register_namespaces is a prefix string, then we register the default namespace to that string
835 1 View Code Duplication
          if (is_string($auto_register_namespaces)) {
836
            $this->registerNamespace($auto_register_namespaces, $attr->value);
837
          }
838
          // Otherwise, automatically set-up the root element tag name as the prefix for the default namespace
839
          else {
840 1
            $tagname = $this->documentElement->tagName;
841 1
            if (empty($this->ns[$tagname])) {
842 1
              $this->registerNamespace($tagname, $attr->value);
843
            } 
844
          }
845
        }
846 3
        else if (substr($attr->name,0,6) == 'xmlns:') {
847
          $prefix = substr($attr->name,6);
848
          $this->registerNamespace($prefix, $attr->value); 
849
        }
850
      }
851
    }
852 25
  }
853
  
854 23
  protected function createContext(&$context, $type = 'xpath', $createDocument = TRUE) {
855 23
    if (!$context && $createDocument) {
856 12
      $context = $this->documentElement;
857 12
      return;
858
    }
859
860 20
    if (!$context) {
861 20
      return FALSE;
862
    }
863
864 13
    if ($context && is_string($context)) {
865 12
      if ($type == 'xpath') {
866 12
        $context = $this->xpathSingle($context);
867 12
        return;
868
      }
869 5
      if ($type == 'xml') {
870 5
        $context = $this->createElementFromXML($context);
871 5
        return;
872
      }
873
    }
874
875 2
    if (is_object($context) && is_a($context, 'DOMNode')) {
876 2
      if ($context->ownerDocument === $this) {
877 2
        return;
878
      }
879 1
      if (is_a($context, 'BetterDOMDocument\DOMDoc')) {
880
        foreach ($context->ns as $prefix => $uri) {
881
          $this->registerNamespace($prefix, $uri);
882
        }
883
        $context = $this->importNode($context->documentElement, TRUE);
884
        return;
885
      }
886 1
      else if (is_a($context, 'DOMDocument')) {
887
        $context = $this->importNode($context->documentElement, TRUE);
888
        return;
889
      }
890
      else {
891 1
        $context = $this->importNode($context, TRUE);
892 1
        return;
893
      }
894
    }
895
896
    return FALSE;
897
  }
898
899 15
  protected function loadFromString($xml) {
900 15
    if ($this->error_checking == 'none') {
901
      @$this->loadXML($xml, LIBXML_COMPACT);
902
    }
903 15
    else if (!$this->loadXML($xml, LIBXML_COMPACT)) {
904
      trigger_error('BetterDOMDocument\DOMDoc: Could not load: ' . htmlspecialchars($xml), E_USER_WARNING);
905
    }
906 15
  }
907
}
908