Completed
Push — master ( 4744c5...b1b532 )
by Josh
15:48
created

OptimizeChoose::moveLastChildAfter()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 4
nop 0
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2017 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
9
10
use DOMElement;
11
use DOMNode;
12
13
class OptimizeChoose extends AbstractChooseOptimization
14
{
15
	/**
16
	* Adopt the children of given element's only child
17
	*
18
	* @param  DOMElement $branch
19
	* @return void
20
	*/
21
	protected function adoptChildren(DOMElement $branch)
22
	{
23
		while ($branch->firstChild->firstChild)
24
		{
25
			$branch->appendChild($branch->firstChild->removeChild($branch->firstChild->firstChild));
26
		}
27
		$branch->removeChild($branch->firstChild);
28
	}
29
30
	/**
31
	* Test whether all branches of current xsl:choose element share a common firstChild/lastChild
32
	*
33
	* @param  string $childType Either firstChild or lastChild
34
	* @return bool
35
	*/
36
	protected function matchBranches($childType)
37
	{
38
		$branches = $this->getBranches();
39
		if (!isset($branches[0]->$childType))
40
		{
41
			return false;
42
		}
43
44
		$childNode = $branches[0]->$childType;
45
		foreach ($branches as $branch)
46
		{
47
			if (!isset($branch->$childType) || !$this->isEqualNode($childNode, $branch->$childType))
48
			{
49
				return false;
50
			}
51
		}
52
53
		return true;
54
	}
55
56
	/**
57
	* Test whether all branches of current xsl:choose element have a single child with the same start tag
58
	*
59
	* @return bool
60
	*/
61
	protected function matchOnlyChild()
62
	{
63
		$branches = $this->getBranches();
64
		if (!isset($branches[0]->firstChild))
65
		{
66
			return false;
67
		}
68
69
		$firstChild = $branches[0]->firstChild;
70
		if ($this->isXsl($firstChild, 'choose'))
71
		{
72
			// Abort on xsl:choose because we can't move it without moving its children
73
			return false;
74
		}
75
76
		foreach ($branches as $branch)
77
		{
78
			if ($branch->childNodes->length !== 1 || !($branch->firstChild instanceof DOMElement))
79
			{
80
				return false;
81
			}
82
			if (!$this->isEqualTag($firstChild, $branch->firstChild))
83
			{
84
				return false;
85
			}
86
		}
87
88
		return true;
89
	}
90
91
	/**
92
	* Move the firstChild of each branch before current xsl:choose
93
	*
94
	* @return void
95
	*/
96
	protected function moveFirstChildBefore()
97
	{
98
		$branches = $this->getBranches();
99
		$this->choose->parentNode->insertBefore(array_pop($branches)->firstChild, $this->choose);
100
		foreach ($branches as $branch)
101
		{
102
			$branch->removeChild($branch->firstChild);
103
		}
104
	}
105
106
	/**
107
	* Move the lastChild of each branch after current xsl:choose
108
	*
109
	* @return void
110
	*/
111
	protected function moveLastChildAfter()
112
	{
113
		$branches = $this->getBranches();
114
		$node     = array_pop($branches)->lastChild;
115
		if (isset($this->choose->nextSibling))
116
		{
117
			$this->choose->parentNode->insertBefore($node, $this->choose->nextSibling);
118
		}
119
		else
120
		{
121
			$this->choose->parentNode->appendChild($node);
122
		}
123
		foreach ($branches as $branch)
124
		{
125
			$branch->removeChild($branch->lastChild);
126
		}
127
	}
128
129
	/**
130
	* {@inheritdoc}
131
	*/
132
	protected function optimizeChoose()
133
	{
134
		if ($this->hasOtherwise())
135
		{
136
			$this->optimizeCommonFirstChild();
137
			$this->optimizeCommonLastChild();
138
			$this->optimizeCommonOnlyChild();
139
			$this->optimizeEmptyOtherwise();
140
		}
141
		if (!$this->isEmpty())
142
		{
143
			$this->optimizeSingleBranch();
144
		}
145
	}
146
147
	/**
148
	* Optimize current xsl:choose by moving out the first child of each branch if they match
149
	*
150
	* @return void
151
	*/
152
	protected function optimizeCommonFirstChild()
153
	{
154
		while ($this->matchBranches('firstChild'))
155
		{
156
			$this->moveFirstChildBefore();
157
		}
158
	}
159
160
	/**
161
	* Optimize current xsl:choose by moving out the last child of each branch if they match
162
	*
163
	* @return void
164
	*/
165
	protected function optimizeCommonLastChild()
166
	{
167
		while ($this->matchBranches('lastChild'))
168
		{
169
			$this->moveLastChildAfter();
170
		}
171
	}
172
173
	/**
174
	* Optimize current xsl:choose by moving out only child of each branch if they match
175
	*
176
	* This will reorder xsl:choose/xsl:when/div into div/xsl:choose/xsl:when if every branch has
177
	* the same only child (excluding the child's own descendants)
178
	*
179
	* @return void
180
	*/
181
	protected function optimizeCommonOnlyChild()
182
	{
183
		while ($this->matchOnlyChild())
184
		{
185
			$this->reparentChild();
186
		}
187
	}
188
189
	/**
190
	* Optimize away the xsl:otherwise child of current xsl:choose if it's empty
191
	*
192
	* @return void
193
	*/
194
	protected function optimizeEmptyOtherwise()
195
	{
196
		$query = 'xsl:otherwise[count(node()) = 0]';
197
		foreach ($this->xpath($query, $this->choose) as $otherwise)
198
		{
199
			$this->choose->removeChild($otherwise);
200
		}
201
	}
202
203
	/**
204
	* Replace current xsl:choose with xsl:if if it has only one branch
205
	*
206
	* @return void
207
	*/
208
	protected function optimizeSingleBranch()
209
	{
210
		$query = 'count(xsl:when) = 1 and not(xsl:otherwise)';
211
		if (!$this->xpath->evaluate($query, $this->choose))
212
		{
213
			return;
214
		}
215
		$when = $this->xpath('xsl:when', $this->choose)[0];
216
		$if   = $this->createElement('xsl:if');
217
		$if->setAttribute('test', $when->getAttribute('test'));
218
		while ($when->firstChild)
219
		{
220
			$if->appendChild($when->removeChild($when->firstChild));
221
		}
222
223
		$this->choose->parentNode->replaceChild($if, $this->choose);
224
	}
225
226
	/**
227
	* Reorder the current xsl:choose tree to make it a child of the first child of its first branch
228
	*
229
	* This will reorder xsl:choose/xsl:when/div into div/xsl:choose/xsl:when
230
	*
231
	* @return void
232
	*/
233
	protected function reparentChild()
234
	{
235
		$branches  = $this->getBranches();
236
		$childNode = $branches[0]->firstChild->cloneNode();
237
		$childNode->appendChild($this->choose->parentNode->replaceChild($childNode, $this->choose));
238
239
		foreach ($branches as $branch)
240
		{
241
			$this->adoptChildren($branch);
242
		}
243
	}
244
}