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 ( abeb2b...6c342c )
by Patrick D
02:19
created

DOMDoc::remove()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 0
cts 12
cp 0
rs 7.551
c 0
b 0
f 0
cc 7
eloc 12
nc 10
nop 1
crap 56
1
<?php namespace BetterDOMDocument;
2
3
use Symfony\Component\CssSelector\CssSelectorConverter;
4
5
/**
6
 * Highwire 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 mixed $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
   * 
25
   * @param mixed $auto_register_namespaces 
26
   *  Auto-register namespaces. All namespaces in the root element will be registered for use in xpath queries.
27
   *  Namespaces that are not declared in the root element will not be auto-registered
28
   *  Defaults to TRUE (Meaning it will auto register all auxiliary namespaces but not the default namespace).
29
   *  Pass a prefix string to automatically register the default namespace.
30
   *  Pass FALSE to disable auto-namespace registeration
31
   * 
32
   * @param bool $error_checking
33
   *  Can be 'strict', 'warning', or 'none. Defaults to 'strict'.
34
   *  'none' supresses all errors
35
   *  'warning' is the default behavior in DOMDocument
36
   *  'strict' corresponds to DOMDocument strictErrorChecking TRUE
37
   */
38 10
  public function __construct($xml = FALSE, $auto_register_namespaces = TRUE, $error_checking = 'strict') {
39 10
    parent::__construct();
40
41 10
    $this->setErrorChecking($error_checking);
42
    
43 10
    if(is_object($xml)){
44 5
      $class = get_class($xml);
45 5
      if ($class == 'DOMElement') {
46 1
        $this->appendChild($this->importNode($xml, true));
47
      }
48 5
      if ($class == 'DOMDocument') {
49 5
        if ($xml->documentElement) {
50 5
          $this->appendChild($this->importNode($xml->documentElement, true));
51
        }
52
      }
53 5
      if ($class == 'BetterDOMDocument\DOMDoc') {
54
        if ($xml->documentElement) {
55
          $this->appendChild($this->importNode($xml->documentElement, true));
56
        }
57
        $this->ns = $xml->ns;
58
      }
59
    }
60
61 10
    if ($xml && is_string($xml)) {
62 8
      if ($this->error_checking == 'none') {
63
        @$this->loadXML($xml, LIBXML_COMPACT);
64
      }
65
      else {
66 8
        if (!$this->loadXML($xml, LIBXML_COMPACT)) {
67
          trigger_error('BetterDOMDocument\DOMDoc: Could not load: ' . htmlspecialchars($xml), E_USER_WARNING);
68
        }
69
      }
70
    }
71 10
    if ($auto_register_namespaces) {
72 10
      $this->AutoRegisterNamespace($auto_register_namespaces);
73
    }
74 10
  }
75
76
  /**
77
   * Register a namespace to be used in xpath queries
78
   *
79
   * @param string $prefix
80
   *  Namespace prefix to register
81
   *
82
   * @param string $url
83
   *  Connonical URL for this namespace prefix
84
   */
85 10
  public function registerNamespace($prefix, $url) {
86 10
    $this->ns[$prefix] = $url;
87 10
  }
88
89
  /**
90
   * Get the list of registered namespaces as an array
91
   */
92 1
  public function getNamespaces() {
93 1
    return $this->ns;
94
  }
95
96
  /**
97
   * Given a namespace URL, get the prefix
98
   * 
99
   * @param string $url
100
   *  Connonical URL for this namespace prefix
101
   * 
102
   * @return string|false
103
   *  The namespace prefix or FALSE if there is no namespace with that URL
104
   */
105
  public function lookupPrefix($url) {
106
    return array_search($url, $this->ns);
1 ignored issue
show
Comprehensibility Best Practice introduced by
The expression array_search($url, $this->ns); of type false|integer|string adds false to the return on line 106 which is incompatible with the return type of the parent method DOMDocument::lookupPrefix of type string. It seems like you forgot to handle an error condition.
Loading history...
107
  }
108
109
  /**
110
   * Given a namespace prefix, get the URL
111
   * 
112
   * @param string $prefix
113
   *  namespace prefix
114
   * 
115
   * return string|false
116
   *  The namespace URL or FALSE if there is no namespace with that prefix
117
   */
118
  public function lookupURL($prefix) {
119
    if (isset($this->ns[$prefix])) {
120
      return $this->ns[$prefix];
121
    }
122
    else {
123
      return FALSE;
124
    }
125
  }
126
127
  /**
128
   * Given an xpath, get a list of nodes.
129
   * 
130
   * @param string $xpath
131
   *  xpath to be used for query
132
   * 
133
   * @param mixed $context
134
   *  $context can either be an xpath string, or a DOMElement
135
   *  Provides context for the xpath query
136
   * 
137
   * @return DOMList|false
138
   *  A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility.
139
   */
140 8
  public function xpath($xpath, $context = NULL) {
141 8
    $this->createContext($context, 'xpath', FALSE);
142
143 8
    if ($context === FALSE) {
144
      return FALSE;
145
    }
146
    
147 8
    $xob = new \DOMXPath($this);
148
149
    // Register the namespaces
150 8
    foreach ($this->ns as $namespace => $url) {
151 8
      $xob->registerNamespace($namespace, $url);
152
    }
153
154 8
    if ($context) {
155 3
      $result = $xob->query($xpath, $context);
156
    }
157
    else {
158 8
      $result = $xob->query($xpath);
159
    }
160
161 8
    if ($result) {
162 8
      return new DOMList($result, $this);
163
    }
164
    else {
165
      return FALSE;
166
    }
167
  }
168
169
170
  /**
171
   * Given an xpath, get a single node (first one found)
172
   * 
173
   * @param string $xpath
174
   *  xpath to be used for query
175
   * 
176
   * @param mixed $context
177
   *  $context can either be an xpath string, or a DOMElement
178
   *  Provides context for the xpath query
179
   * 
180
   * @return mixed
181
   *  The first node found by the xpath query
182
   */
183 8
  public function xpathSingle($xpath, $context = NULL) {
184 8
    $result = $this->xpath($xpath, $context);
185
    
186 8
    if (empty($result) || !count($result)) {
187
      return FALSE;
188
    }
189
    else {
190 8
      return $result->item(0);
191
    }
192
  }
193
194
195
  /**
196
   * Given an CSS selector, get a list of nodes.
197
   * 
198
   * @param string $css_selector
199
   *  CSS Selector to be used for query
200
   * 
201
   * @param mixed $context
202
   *  $context can either be an xpath string, or a DOMElement
203
   *  Provides context for the CSS selector
204
   * 
205
   * @return DOMList
206
   *  A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility.
207
   */
208
  public function select($css_selector, $context = NULL) {
209
    $converter = new CssSelectorConverter();
210
    $xpath = $converter->toXPath($css_selector);
211
212
    return $this->xpath($xpath, $context);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->xpath($xpath, $context); of type false|BetterDOMDocument\DOMList adds false to the return on line 212 which is incompatible with the return type documented by BetterDOMDocument\DOMDoc::select of type BetterDOMDocument\DOMList. It seems like you forgot to handle an error condition.
Loading history...
213
  }
214
215
  /**
216
   * Given an CSS selector, get a single node.
217
   * 
218
   * @param string $css_selector
219
   *  CSS Selector to be used for query
220
   * 
221
   * @param mixed $context
222
   *  $context can either be an xpath string, or a DOMElement
223
   *  Provides context for the CSS selector
224
   * 
225
   * @return DOMList
226
   *  A DOMList object, which is very similar to a DOMNodeList, but with better iterabilility.
227
   */
228
  public function selectSingle($css_selector, $context = NULL) {
229
    $converter = new CssSelectorConverter();
230
    $xpath = $converter->toXPath($css_selector);
231
232
    return $this->xpathSingle($xpath, $context);
233
  }
234
235
  /**
236
   * Get the document (or an element) as an array
237
   *
238
   * @param string $raw
239
   *  Can be either FALSE, 'full', or 'inner'. Defaults to FALSE.
240
   *  When set to 'full' every node's full XML is also attached to the array
241
   *  When set to 'inner' every node's inner XML is attached to the array.
242
   * 
243
   * @param mixed $context 
244
   *  Optional context node. Can pass an DOMElement object or an xpath string.
245
   *  If passed, only the given node will be used when generating the array 
246
   */
247
  public function getArray($raw = FALSE, $context = NULL) {
248
    $array = false;
249
250
    $this->createContext($context, 'xpath', FALSE);
251
    
252
    if ($context) {
253
      if ($raw == 'full') {
254
        $array['#raw'] = $this->saveXML($context);
255
      }
256
      if ($raw == 'inner') {
257
        $array['#raw'] = $this->innerText($context);
258
      }
259
      if ($context->hasAttributes()) {
260
        foreach ($context->attributes as $attr) {
261
          $array['@'.$attr->nodeName] = $attr->nodeValue;
262
        }
263
      }
264
  
265
      if ($context->hasChildNodes()) {
266
        if ($context->childNodes->length == 1 && $context->firstChild->nodeType == XML_TEXT_NODE) {
267
          $array['#text'] = $context->firstChild->nodeValue;
268
        }
269
        else {
270
          foreach ($context->childNodes as $childNode) {
271
            if ($childNode->nodeType == XML_ELEMENT_NODE) {
272
              $array[$childNode->nodeName][] = $this->getArray($raw, $childNode);
273
            }
274
            elseif ($childNode->nodeType == XML_CDATA_SECTION_NODE) {
275
              $array['#text'] = $childNode->textContent;
276
            }
277
          }
278
        }
279
      }
280
    }
281
    // Else no node was passed, which means we are processing the entire domDocument
282
    else {
283
      foreach ($this->childNodes as $childNode) {
284
        if ($childNode->nodeType == XML_ELEMENT_NODE) {
285
          $array[$childNode->nodeName][] = $this->getArray($raw, $childNode);
286
        }
287
      }
288
    }
289
290
    return $array;
291
  }
292
  
293
  /**
294
   * Get the inner text of an element
295
   * 
296
   * @param mixed $context 
297
   *  Optional context node. Can pass an DOMElement object or an xpath string.
298
   */
299
  public function innerText($context = NULL) {
300
    $this->createContext($context, 'xpath');
301
    
302
    $pattern = "/<".preg_quote($context->nodeName)."\b[^>]*>(.*)<\/".preg_quote($context->nodeName).">/s";
303
    $matches = array();
304
    if (preg_match($pattern, $this->saveXML($context), $matches)) {
305
      return $matches[1];
306
    }
307
    else {
308
      return '';
309
    }
310
  }
311
312
  /**
313
   * Create an DOMElement from XML and attach it to the DOMDocument
314
   * 
315
   * Note that this does not place it anywhere in the dom tree, it merely imports it.
316
   * 
317
   * @param string $xml 
318
   *  XML string to import
319
   */
320
  public function createElementFromXML($xml) {
321
    
322
    // To make thing easy and make sure namespaces work properly, we add the root namespace delcarations if it is not declared
323
    $namespaces = $this->ns;
324
    $xml = preg_replace_callback('/<[^\?^!].+?>/s', function($root_match) use ($namespaces) {
325
      preg_match('/<([^ <>]+)[\d\s]?.*?>/s', $root_match[0], $root_tag);
326
      $new_root = $root_tag[1];
327
      if (strpos($new_root, ':')) {
328
        $parts = explode(':', $new_root);
329
        $prefix = $parts[0]; 
330
        if (isset($namespaces[$prefix])) {
331
          if (!strpos($root_match[0], "xmlns:$prefix")) {
332
            $new_root .= " xmlns:$prefix='" . $namespaces[$prefix] . "'";             
333
          }
334
        }
335
      }
336
      return str_replace($root_tag[1], $new_root, $root_match[0]);
337
    }, $xml, 1);
338
    
339
    $dom = new DOMDoc($xml, $this->auto_ns);
340
    if (!$dom->documentElement) {
341
      trigger_error('BetterDomDocument\DOMDoc Error: Invalid XML: ' . $xml);
342
    }
343
    $element = $dom->documentElement;
344
    
345
    // Merge the namespaces
346
    foreach ($dom->getNamespaces() as $prefix => $url) {
347
      $this->registerNamespace($prefix, $url);
348
    }
349
    
350
    return $this->importNode($element, true);
351
  }
352
353
  /**
354
   * Append a child to the context node, make it the last child
355
   * 
356
   * @param mixed $newnode
357
   *  $newnode can either be an XML string, a DOMDocument, or a DOMElement. 
358
   * 
359
   * @param mixed $context
360
   *  $context can either be an xpath string, or a DOMElement
361
   *  Omiting $context results in using the root document element as the context
362
   * 
363
   * @return DOMElement|false
364
   *  The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement
365
   *  then you should replace your DOMElement with the returned one.
366
   */
367
  public function append($newnode, $context = NULL) {
368
    $this->createContext($newnode, 'xml');
369
    $this->createContext($context, 'xpath');
370
    
371
    if (!$context || !$newnode) {
372
      return FALSE;
373
    }
374
  
375 View Code Duplication
    if ($newnode->ownerDocument === $this) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
      $appendnode = $newnode;
377
    }
378
    else {
379
      $appendnode = $this->importNode($newnode, true);
380
    }
381
382
    return $context->appendChild($appendnode);
383
  }
384
  
385
  /**
386
   * Append a child to the context node, make it the first child
387
   * 
388
   * @param mixed $newnode
389
   *  $newnode can either be an XML string, a DOMDocument, or a DOMElement. 
390
   * 
391
   * @param mixed $context
392
   *  $context can either be an xpath string, or a DOMElement
393
   *  Omiting $context results in using the root document element as the context
394
   *
395
   * @return DOMElement|false
396
   *  The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement
397
   *  then you should replace your DOMElement with the returned one.
398
   */
399 View Code Duplication
  public function prepend($newnode, $context = NULL) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
400
    $this->createContext($newnode, 'xml');
401
    $this->createContext($context, 'xpath');
402
    
403
    if (!$context || !$newnode) {
404
      return FALSE;
405
    }
406
    
407
    return $context->insertBefore($newnode, $context->firstChild);
408
  }
409
410
  /**
411
   * Prepend a sibling to the context node, put it just before the context node
412
   * 
413
   * @param mixed $newnode
414
   *  $newnode can either be an XML string, a DOMDocument, or a DOMElement. 
415
   * 
416
   * @param mixed $context
417
   *  $context can either be an xpath string, or a DOMElement
418
   *  Omiting $context results in using the root document element as the context 
419
   * 
420
   * @return DOMElement|false
421
   *  The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement
422
   *  then you should replace your DOMElement with the returned one.
423
   */
424 View Code Duplication
  public function prependSibling($newnode, $context = NULL) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
425
    $this->createContext($newnode, 'xml');
426
    $this->createContext($context, 'xpath');
427
    
428
    if (!$context || !$newnode) {
429
      return FALSE;
430
    }
431
    
432
    return $context->parentNode->insertBefore($newnode, $context);
433
  }
434
  
435
  /**
436
   * Append a sibling to the context node, put it just after the context node
437
   * 
438
   * @param mixed $newnode
439
   *  $newnode can either be an XML string, a DOMDocument, or a DOMElement. 
440
   * 
441
   * @param mixed $context
442
   *  $context can either be an xpath string, or a DOMElement
443
   *  Omiting $context results in using the root document element as the context 
444
   * 
445
   * @return DOMElement|false
446
   *  The $newnode, properly attached to DOMDocument. If you passed $newnode as a DOMElement
447
   *  then you should replace your DOMElement with the returned one.
448
   */
449
  public function appendSibling($newnode, $context) {
450
    $this->createContext($newnode, 'xml');
451
    $this->createContext($context, 'xpath');
452
    
453
    if (!$context){
454
      return FALSE;
455
    }
456
    
457
    if ($context->nextSibling) { 
458
      // $context has an immediate sibling : insert newnode before this one 
459
      return $context->parentNode->insertBefore($newnode, $context->nextSibling); 
460
    }
461
    else { 
462
      // $context has no sibling next to it : insert newnode as last child of it's parent 
463 View Code Duplication
      if ($newnode->ownerDocument === $this) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
464
        $appendnode = $newnode;
465
      }
466
      else {
467
        $appendnode = $this->importNode($newnode, true);
468
      }
469
      return $context->parentNode->appendChild($appendnode); 
470
    }
471
  }
472
  
473
  /**
474
   * Given an xpath or DOMElement, return a new DOMDoc.
475
   * 
476
   * @param mixed $node
477
   *  $node can either be an xpath string or a DOMElement. 
478
   * 
479
   * @return DOMDoc
480
   *  A new DOMDoc created from the xpath or DOMElement
481
   */
482
  public function extract($node, $auto_register_namespaces = TRUE, $error_checking = 'none') {
483
    $this->createContext($node, 'xpath');
484
    $dom = new DOMDoc($node, $auto_register_namespaces, $error_checking);
485
    $dom->ns = $this->ns;
486
    return $dom;
487
  }
488
  
489
  /**
490
   * Given a pair of nodes, replace the first with the second
491
   * 
492
   * @param mixed $node
493
   *  Node to be replaced. Can either be an xpath string or a DOMDocument (or even a DOMNode).
494
   * 
495
   * @param mixed $replace
496
   *  Replace $node with $replace. Replace can be an XML string, or a DOMNode
497
   * 
498
   * @return mixed
499
   *   The overwritten / replaced node.
500
   */
501
  public function replace($node, $replace) {
502
    $this->createContext($node, 'xpath');
503
    $this->createContext($replace, 'xml');
504
    
505
    if (!$node || !$replace) {
506
      return FALSE;
507
    }
508
        
509
    if (!$replace->ownerDocument->documentElement->isSameNode($this->documentElement)) {
510
      $replace = $this->importNode($replace, true);
511
    }
512
    $node->parentNode->replaceChild($replace, $node);
513
    $node = $replace;
514
    return $node;
515
  }
516
517
  /**
518
   * Given a node(s), remove / delete them
519
   * 
520
   * @param mixed $node
521
   *  Can pass a DOMNode, a NodeList, DOMNodeList, an xpath string, or an array of any of these.
522
   */
523
  public function remove($node) {
524
    // We can't use createContext here because we want to use the entire nodeList (not just a single element)
525
    if (is_string($node)) {
526
      $node = $this->xpath($node);
527
    }
528
    
529
    if ($node) {
530
      if (is_array($node) || get_class($node) == 'BetterDOMDocument\DOMList') {
531
        foreach($node as $item) {
532
          $this->remove($item);
533
        }
534
      }
535
      else if (get_class($node) == 'DOMNodeList') {
536
        $this->remove(new DOMList($node, $this));
537
      }
538
      else {
539
        $parent = $node->parentNode;
540
        $parent->removeChild($node);
541
      }
542
    }
543
  }
544
  
545
  /**
546
   * Given an XSL string, transform the DOMDoc (or a passed context node)
547
   * 
548
   * @param string $xsl
549
   *   XSL Transormation
550
   * 
551
   * @param mixed $context
552
   *   $context can either be an xpath string, or a DOMElement. Ommiting it
553
   *   results in transforming the entire document
554
   * 
555
   * @return a new DOMDoc
556
   */
557 3
  public function tranform($xsl, $context = NULL) {
558 3
    if (!$context) {
559 2
      $doc = $this;
560
    }
561
    else {
562 1
      if (is_string($context)) {
563 1
        $context = $this->xpathSingle($context);
564
      }
565 1
      $doc = new DOMDoc($context);
566
    }
567
    
568 3
    $xslDoc = new DOMDoc($xsl);
569 3
    $xslt = new \XSLTProcessor();
570 3
    $xslt->importStylesheet($xslDoc);
571
    
572 3
    return new DOMDoc($xslt->transformToDoc($doc));
573
  }
574
575
  /**
576
   * Given a node, change it's namespace to the specified namespace in situ
577
   * 
578
   * @param mixed $node
579
   *  Node to be changed. Can either be an xpath string or a DOMElement.
580
   * 
581
   * @param mixed $prefix
582
   *   prefix for the new namespace
583
   * 
584
   * @param mixed $url
585
   *   The URL for the new namespace
586
   * 
587
   * @return mixed
588
   *   The node with the new namespace. The node will also be changed in-situ in the document as well.
589
   */
590
  public function changeNamespace($node, $prefix, $url) {
591
    $this->createContext($node, 'xpath');
592
    
593
    if (!$node) {
594
      return FALSE;
595
    }
596
    
597
    $this->registerNamespace($prefix, $url);
598
599
    if (get_class($node) == 'DOMElement') {
600
      $elemname = array_pop(explode(':', $node->tagName));
0 ignored issues
show
Bug introduced by
explode(':', $node->tagName) cannot be passed to array_pop() as the parameter $array expects a reference.
Loading history...
Unused Code introduced by
$elemname is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
601
602
      $xsl = '
603
        <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
604
          <xsl:template match="*">
605
            <xsl:element name="' . $prefix . ':{local-name()}" namespace="' . $url . '">
606
             <xsl:copy-of select="@*"/>
607
             <xsl:apply-templates/>
608
            </xsl:element>
609
          </xsl:template>
610
        </xsl:stylesheet>';
611
612
      $transformed = $this->tranform($xsl, $node);
613
      return $this->replace($node, $transformed->documentElement);   
614
    }
615
    else {
616
      // @@TODO: Report the correct calling file and number
617
      throw new Exception("Changing the namespace of a " . get_class($node) . " is not supported");
618
    }
619
  }
620
621
  /**
622
   * Get a lossless HTML representation of the XML
623
   *
624
   * Transforms the document (or passed context) into a set of HTML spans.
625
   * The element name becomes the class, all other attributes become HTML5
626
   * "data-" attributes.
627
   * 
628
   * @param mixed $context
629
   *   $context can either be an xpath string, or a DOMElement. Ommiting it
630
   *   results in transforming the entire document
631
   * 
632
   * @param array $options
633
   *   Options for transforming the HTML into XML. The following options are supported:
634
   *   'xlink' => {TRUE or xpath}
635
   *     Transform xlink links into <a href> elements. If you specify 'xlink' => TRUE then 
636
   *     it will transform all elements with xlink:type = simple into a <a href> element. 
637
   *     Alternatively you may specify your own xpath for selecting which elements get transformed
638
   *     into <a href> tags. 
639
   * @return HTML string
640
   */  
641 3
  public function asHTML($context = NULL, $options = array()) {
642
    $xslSimple = '
643
      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
644
      <xsl:template match="*">
645
        <span class="{translate(name(.),\':\',\'-\')}">
646
          <xsl:for-each select="./@*">
647
            <xsl:attribute name="data-{translate(name(.),\':\',\'-\')}">
648
              <xsl:value-of select="." />
649
            </xsl:attribute>
650
          </xsl:for-each>
651
          <xsl:apply-templates/>
652
        </span>
653
      </xsl:template>
654 3
      </xsl:stylesheet>';
655
656
    $xslOptions = '
657
      <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xlink="http://www.w3.org/1999/xlink" ||namespaces||>
658
      <xsl:template match="*">
659
        <xsl:choose>
660
          <xsl:when test="||xlink||">
661
            <a class="{translate(name(.),\':\',\'-\')}">
662
              <xsl:for-each select="./@*">
663
                <xsl:attribute name="data-{translate(name(.),\':\',\'-\')}">
664
                  <xsl:value-of select="."/>
665
                </xsl:attribute>
666
              </xsl:for-each>
667
              <xsl:attribute name="href">
668
                <xsl:value-of select="@xlink:href"/>
669
              </xsl:attribute>
670
              <xsl:apply-templates/>
671
            </a>
672
          </xsl:when>
673
          <xsl:otherwise>
674
            <span class="{translate(name(.),\':\',\'-\')}">
675
              <xsl:for-each select="./@*">
676
                <xsl:attribute name="data-{translate(name(.),\':\',\'-\')}">
677
                  <xsl:value-of select="." />
678
                </xsl:attribute>
679
              </xsl:for-each>
680
              <xsl:apply-templates/>
681
            </span>
682
          </xsl:otherwise>
683
        </xsl:choose>
684
      </xsl:template>
685 3
      </xsl:stylesheet>';
686
687 3
    if (!empty($options)) {
688
      // Add in the namespaces
689 1
      foreach ($this->getNamespaces() as $prefix => $url) {
690 1
        $namespaces = '';
691 1
        if ($prefix != 'xsl' && $prefix != 'xlink') {
692 1
          $namespaces .= 'xmlns:' . $prefix . '="' . $url. '" ';
693
        }
694 1
        $xslOptions = str_replace("||namespaces||", $namespaces, $xslOptions);
695
      }
696
697
      // Add in xlink options
698 1
      if ($options['xlink'] === TRUE) {
699 1
        $options['xlink'] = "@xlink:type = 'simple'";
700
      }
701
      else if (empty($options['xlink'])) {
702
        $options['xlink'] = "false()";
703
      }
704 1
      $xslOptions = str_replace("||xlink||", $options['xlink'], $xslOptions);
705 1
      $transformed = $this->tranform($xslOptions, $context);
706
    }
707
    else {
708 2
      $transformed = $this->tranform($xslSimple, $context);
709
    }
710
    
711 3
    return $transformed->out();
712
  }
713
714
  /**
715
   * Output the DOMDoc as an XML string
716
   * 
717
   * @param mixed $context
718
   *   $context can either be an xpath string, or a DOMElement. Ommiting it
719
   *   results in outputting the entire document
720
   * 
721
   * @return XML string
722
   */  
723 3
  public function out($context = NULL) {
724 3
    $this->createContext($context, 'xpath');
725 3
    if (!$context) {
726
      return '';
727
    }
728
729
    // Copy namespace prefixes
730 3
    foreach ($this->ns as $prefix => $namespace) {
731
      if (!$context->hasAttribute('xmlns:' . $prefix)) {
732
        $context->setAttribute('xmlns:' . $prefix, $namespace);
733
      }
734
    }
735
    
736
    // Check to seee if it's HTML, if it is we need to fix broken html void elements.
737 3
    if ($this->documentElement->lookupNamespaceURI(NULL) == 'http://www.w3.org/1999/xhtml' || $this->documentElement->tagName == 'html') {
738
      $output = $this->saveXML($context, LIBXML_NOEMPTYTAG);
739
      // The types listed are html "void" elements. 
740
      // Find any of these elements that have no child nodes and are therefore candidates for self-closing, replace them with a self-closed version. 
741
      $pattern = '<(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)(\b[^<]*)><\/\1>';
742
      return preg_replace('/' . $pattern . '/', '<$1$2/>', $output);
743
    }
744
    else {
745 3
      return $this->saveXML($context, LIBXML_NOEMPTYTAG);
746
    }
747
  }
748
749
  /**
750
   * Magic method for casting a DOMDoc as a string
751
   */ 
752
  public function __toString() {
753
    return $this->out();
754
  }
755
756 10
  public function setErrorChecking($error_checking) {
757
    // Check up error-checking
758 10
    if ($error_checking == FALSE) {
759
      $this->error_checking = 'none';
760
    }
761
    else {
762 10
      $this->error_checking = $error_checking;
763
    }
764 10
    if ($this->error_checking != 'strict') {
765
      $this->strictErrorChecking = FALSE;
766
    }
767 10
  }
768
769 2
  public static function loadFile($file_or_url, $auto_register_namespaces = TRUE) {
770 2
    $dom = @parent::load($file_or_url, LIBXML_COMPACT);
771 2
    if (empty($dom)) {
772
      return FALSE;
773
    }
774
775 2
    return new DOMDoc($dom, $auto_register_namespaces);
776
  }
777
778 10
  private function AutoRegisterNamespace($auto_register_namespaces) {
779 10
    $this->auto_ns = TRUE;
780
781
    // If it's an "XML" document, then get namespaces via xpath
782 10
    $xpath = new \DOMXPath($this);
783 10
    foreach($xpath->query('namespace::*') as $namespace) {
784 10
      if (!empty($namespace->prefix)) {
785 10
        if ($namespace->prefix != 'xml' && $namespace->nodeValue != 'http://www.w3.org/XML/1998/namespace') {
786 10
          $this->registerNamespace($namespace->prefix, $namespace->nodeValue);
787
        }
788
      }
789 View Code Duplication
      else {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
790 1
        $this->default_ns = $namespace->nodeValue;
791 1
        if (is_string($auto_register_namespaces)) {
792
          $this->registerNamespace($auto_register_namespaces, $namespace->nodeValue);
793
        }
794
        // Otherwise, automatically set-up the root element tag name as the prefix for the default namespace
795
        else {
796 1
          $tagname = $this->documentElement->tagName;
797 1
          if (empty($this->ns[$tagname])) {
798 10
            $this->registerNamespace($tagname, $this->documentElement->getAttribute('xmlns'));
799
          }
800
        }
801
      }
802
    }
803
804
    // If it's an "HTML" document, we get namespaces via attributes
805 10
    if (empty($this->ns)) {
806 4
      foreach ($this->documentElement->attributes as $attr) {
807 3
        if ($attr->name == 'xmlns') {
808
          $this->default_ns = $attr->value;
809
          // If auto_register_namespaces is a prefix string, then we register the default namespace to that string
810 View Code Duplication
          if (is_string($auto_register_namespaces)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
811
            $this->registerNamespace($auto_register_namespaces, $attr->value);
812
          }
813
          // Otherwise, automatically set-up the root element tag name as the prefix for the default namespace
814
          else {
815
            $tagname = $this->documentElement->tagName;
816
            if (empty($this->ns[$tagname])) {
817
              $this->registerNamespace($tagname, $attr->value);
818
            } 
819
          }
820
        }
821 3
        else if (substr($attr->name,0,6) == 'xmlns:') {
822
          $prefix = substr($attr->name,6);
823 3
          $this->registerNamespace($prefix, $attr->value); 
824
        }
825
      }
826
    }
827 10
  }
828
  
829 10
  private function createContext(&$context, $type = 'xpath', $createDocument = TRUE) {
830 10
    if (!$context && $createDocument) {
831 3
      $context = $this->documentElement;
832 3
      return;
833
    }
834
835 8
    if (!$context) {
836 8
      return FALSE;
837
    }
838
839 3
    if ($context && is_string($context)) {
840 3
      if ($type == 'xpath') {
841 3
        $context = $this->xpathSingle($context);
842 3
        return;
843
      }
844
      if ($type == 'xml') {
845
        $context = $this->createElementFromXML($context);
846
        return;
847
      }
848
    }
849
850
    if (is_object($context)) {
851
      if (is_a($context, 'DOMElement')) {
852
        return $context;
853
      }
854
      if (is_a($context, 'DOMDocument')) {
855
        return $context->documentElement;
856
      }
857
    }
858
  }
859
}
860
861
862
863