Completed
Push — master ( d91fed...fd66aa )
by Josh
17:36
created

OptimizeChoose::optimizeCommonOnlyChild()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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