Template::loadDocument()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 21
rs 9.584
cc 2
nc 2
nop 1
1
<?php
2
/* @description     Transformation Style Sheets - Revolutionising PHP templating    *
3
 * @author          Tom Butler [email protected]                                             *
4
 * @copyright       2017 Tom Butler <[email protected]> | https://r.je/                      *
5
 * @license         http://www.opensource.org/licenses/bsd-license.php  BSD License *
6
 * @version         1.2                                                             */
7
namespace Transphporm;
8
/** Loads an XML string into a DomDocument and allows searching for specific elements using xpath based hooks */
9
class Template {
10
	private $hooks = [];
11
	private $document;
12
	private $xpath;
13
	private $prefix = '';
14
	private $save;
15
16
	/** Takes an XML string and loads it into a DomDocument object */
17
	public function __construct($doc) {
18
		$this->document = new \DomDocument;
19
		//This should remove whitespace left behind after ->removeChild but it doesn't
20
		$this->document->preserveWhiteSpace = false;
21
		$this->loadDocument($doc);
22
23
		$this->xpath = new \DomXPath($this->document);
24
		$this->xpath->registerNamespace('php', 'http://php.net/xpath');
25
		$this->xpath->registerPhpFunctions();
26
27
		if ($this->document->documentElement->namespaceURI !== null) {
28
			$this->xpath->registerNamespace('nsprefix', $this->document->documentElement->namespaceURI);
29
			$this->prefix = 'nsprefix:';
30
		}
31
32
	}
33
34
	/** Loads a HTML or XML document */
35
	private function loadDocument($doc) {
36
		libxml_use_internal_errors(true);
37
		if (strpos(trim($doc), '<?xml') === false) {
38
			//If HTML is loaded, make sure the document is also saved as HTML
39
			$this->save = function($content = null) {
40
				return $this->document->saveHtml($content);
41
			};
42
			$this->document->loadHtml('<' . '?xml encoding="UTF-8">' . $doc,  LIBXML_HTML_NODEFDTD | LIBXML_HTML_NOIMPLIED);
43
44
		}
45
		else {
46
			$this->document->loadXml($doc);
47
			//XML was loaded, save as XML.
48
			$this->save = function($content = null) {
49
				return $this->document->saveXml($content, LIBXML_NOEMPTYTAG);
50
			};
51
		}
52
53
54
		libxml_clear_errors();
55
	}
56
57
	/** Returns the document's XML prefix */
58
	public function getPrefix() {
59
		return $this->prefix;
60
	}
61
62
	/** Assigns a $hook which will be run on any element that matches the given $xpath query */
63
	public function addHook($xpath, $hook) {
64
		$this->hooks[] = [$xpath, $hook];
65
	}
66
67
	/** Loops through all assigned hooks, runs the Xpath query and calls the hook */
68
	private function processHooks() {
69
		foreach ($this->hooks as list($query, $hook)) {
70
			foreach ($this->xpath->query($query) as $element) {
71
				if ($element->getAttribute('transphporm') !== 'immutable' || $hook->runOnImmutableElements()) {
72
					$hook->run($element);
73
				}
74
			}
75
		}
76
		$this->hooks = [];
77
	}
78
79
	/** Prints out the current DomDocument as HTML */
80
	private function printDocument() {
81
		$output = '';
82
		foreach ($this->document->documentElement->childNodes as $node) $output .= call_user_func($this->save, $node);
83
		return $output;
84
	}
85
86
	/** Outputs the template's header/body. Returns an array containing both parts */
87
	public function output($document = false) {
88
		//Process all hooks
89
		 $this->processHooks();
90
91
		//Generate the document by taking only the childnodes of the template, ignoring the <template> and </template> tags
92
		//TODO: Is there a faster way of doing this without string manipulation on the output or this loop through childnodes?
93
		 //Either return a whole DomDocument or return the output HTML
94
		if ($document) return $this->document;
95
96
		//Print the doctype... saveHtml inexplicably does not support $doc->doctype as an argument
97
		$output = ($this->document->doctype) ? $this->document->saveXml($this->document->doctype) . "\n" : '';
98
99
		if ($this->document->documentElement->tagName !== 'template') $output .= call_user_func($this->save, $this->document->documentElement);
100
		else $output = $this->printDocument();
101
102
		//repair empty tags. Browsers break on <script /> and <div /> so can't avoid LIBXML_NOEMPTYTAG but they also break on <base></base> so repair them
103
		$output = str_replace(['></img>', '></br>', '></meta>', '></base>', '></link>', '></hr>', '></input>'], ' />', $output);
104
		return trim($output);
105
	}
106
107
}
108