Completed
Push — master ( 06f4a4...cfb008 )
by Tom
06:24
created

Content::addContentPseudo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
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\Property;
8
class Content implements \Transphporm\Property {
9
	private $contentPseudo = [];
10
	private $formatter;
11
12
	public function __construct(\Transphporm\Hook\Formatter $formatter) {
13
		$this->formatter = $formatter;
14
	}
15
16
	public function run(array $values, \DomElement $element, array $rules, \Transphporm\Hook\PseudoMatcher $pseudoMatcher, array $properties = []) {
17
		if (!$this->shouldRun($element)) return false;
18
19
		$values = $this->formatter->format($values, $rules);
20
21
		if (!$this->processPseudo($values, $element, $pseudoMatcher)) {
22
			//Remove the current contents
23
			$this->removeAllChildren($element);
24
			//Now make a text node
25
			if ($this->getContentMode($rules) === 'replace') $this->replaceContent($element, $values);
26
			else $this->appendContent($element, $values);
27
		}
28
	}
29
30
	private function shouldRun($element) {
31
		do {
32
			if ($element->getAttribute('transphporm') == 'includedtemplate') return false;
33
		}
34
		while (($element = $element->parentNode) instanceof \DomElement);
35
		return true;
36
	}
37
38
	private function getContentMode($rules) {
39
		return (isset($rules['content-mode'])) ? $rules['content-mode']->read() : 'append';
40
	}
41
42
	public function addContentPseudo($name, ContentPseudo $contentPseudo) {
43
		$this->contentPseudo[$name] = $contentPseudo;
44
	}
45
46
	private function processPseudo($value, $element, $pseudoMatcher) {
47
		foreach ($this->contentPseudo as $pseudoName => $pseudoFunction) {
48
			if ($pseudoMatcher->hasFunction($pseudoName)) {
49
				$pseudoFunction->run($value, $pseudoMatcher->getFuncArgs($pseudoName, $element)[0], $element, $pseudoMatcher);
50
				return true;
51
			}
52
		}
53
		return false;
54
	}
55
56
	public function getNode($node, $document) {
57
		foreach ($node as $n) {
58
			if (is_array($n)) {
59
				foreach ($this->getNode($n, $document) as $new) yield $new;
60
			}
61
			else {
62
				yield $this->convertNode($n, $document);
63
			}
64
		}
65
	}
66
67
	private function convertNode($node, $document) {
68
		if ($node instanceof \DomElement || $node instanceof \DOMComment) {
69
			$new = $document->importNode($node, true);
70
		}
71
		else {
72
			if ($node instanceof \DomText) $node = $node->nodeValue;
73
			$new = $document->createElement('text');
74
75
			$new->appendChild($document->createTextNode($node));
76
			$new->setAttribute('transphporm', 'text');
77
		}
78
		return $new;
79
	}
80
81
82
	private function replaceContent($element, $content) {
83
		if ($element->getAttribute('transphporm') == 'added') return;
84
		//If this rule was cached, the elements that were added last time need to be removed prior to running the rule again.
85
		if ($element->getAttribute('transphporm')) {
86
			$this->replaceCachedContent($element);
87
		}
88
89
		foreach ($this->getNode($content, $element->ownerDocument) as $node) {
90
			if ($node instanceof \DomElement && !$node->getAttribute('transphporm'))  $node->setAttribute('transphporm', 'added');
91
			$element->parentNode->insertBefore($node, $element);
92
		}
93
94
		//Remove the original element from the final output
95
		$element->setAttribute('transphporm', 'remove');
96
	}
97
98
	private function replaceCachedContent($element) {
99
		$el = $element;
100
		while ($el = $el->previousSibling) {
101
			if ($el->nodeType == 1 && $el->getAttribute('transphporm') != 'remove') {
102
				$el->parentNode->removeChild($el);
103
			}
104
		}
105
		$this->fixPreserveWhitespaceRemoveChild($element);
106
	}
107
108
	// $doc->preserveWhiteSpace = false should fix this but it doesn't
109
	// Remove extra whitespace created by removeChild to avoid the cache growing 1 byte every time it's reloaded
110
	// This may need to be moved in future, anywhere elements are being removed and files are cached may need to apply this fix
111
	// Also remove any comments to avoid the comment being re-added every time the cache is reloaded
112
	private function fixPreserveWhitespaceRemoveChild($element) {
113
		if ($element->previousSibling instanceof \DomComment || ($element->previousSibling instanceof \DomText && trim($element->previousSibling->isElementContentWhiteSpace()))) {
114
			$element->parentNode->removeChild($element->previousSibling);
115
		}
116
	}
117
118
	private function appendContent($element, $content) {
119
		foreach ($this->getNode($content, $element->ownerDocument) as $node) {
120
			$element->appendChild($node);
121
		}
122
	}
123
124
	private function removeAllChildren($element) {
125
		while ($element->hasChildNodes()) $element->removeChild($element->firstChild);
126
	}
127
}