Completed
Branch master (872421)
by Josh
02:07
created

OptimizeChoose   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 1
dl 0
loc 261
ccs 87
cts 87
cp 1
rs 9.36
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A matchBranches() 0 19 5
B matchOnlyChild() 0 29 7
A adoptChildren() 0 8 2
A moveFirstChildBefore() 0 9 2
A moveLastChildAfter() 0 17 3
A optimizeChoose() 0 19 3
A optimizeCommonFirstChild() 0 7 2
A optimizeCommonLastChild() 0 7 2
A optimizeCommonOnlyChild() 0 7 2
A optimizeEmptyBranch() 0 18 3
A optimizeEmptyOtherwise() 0 8 2
A optimizeSingleBranch() 0 17 3
A reparentChild() 0 11 2
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2019 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 2
	protected function adoptChildren(DOMElement $branch)
21
	{
22 2
		while ($branch->firstChild->firstChild)
23
		{
24 2
			$branch->appendChild($branch->firstChild->removeChild($branch->firstChild->firstChild));
25
		}
26 2
		$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 15
	protected function matchBranches($childType)
36
	{
37 15
		$branches = $this->getBranches();
38 15
		if (!isset($branches[0]->$childType))
39
		{
40 4
			return false;
41
		}
42
43 13
		$childNode = $branches[0]->$childType;
44 13
		foreach ($branches as $branch)
45
		{
46 13
			if (!isset($branch->$childType) || !$this->isEqualNode($childNode, $branch->$childType))
47
			{
48 12
				return false;
49
			}
50
		}
51
52 6
		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 15
	protected function matchOnlyChild()
61
	{
62 15
		$branches = $this->getBranches();
63 15
		if (!isset($branches[0]->firstChild))
64
		{
65 4
			return false;
66
		}
67
68 12
		$firstChild = $branches[0]->firstChild;
69 12
		if ($this->isXsl($firstChild, 'choose'))
70
		{
71
			// Abort on xsl:choose because we can't move it without moving its children
72 1
			return false;
73
		}
74
75 12
		foreach ($branches as $branch)
76
		{
77 12
			if ($branch->childNodes->length !== 1 || !($branch->firstChild instanceof DOMElement))
78
			{
79 9
				return false;
80
			}
81 5
			if (!$this->isEqualTag($firstChild, $branch->firstChild))
82
			{
83 4
				return false;
84
			}
85
		}
86
87 2
		return true;
88
	}
89
90
	/**
91
	* Move the firstChild of each branch before current xsl:choose
92
	*
93
	* @return void
94
	*/
95 4
	protected function moveFirstChildBefore()
96
	{
97 4
		$branches = $this->getBranches();
98 4
		$this->choose->parentNode->insertBefore(array_pop($branches)->firstChild, $this->choose);
99 4
		foreach ($branches as $branch)
100
		{
101 4
			$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 2
	protected function moveLastChildAfter()
111
	{
112 2
		$branches = $this->getBranches();
113 2
		$node     = array_pop($branches)->lastChild;
114 2
		if (isset($this->choose->nextSibling))
115
		{
116 1
			$this->choose->parentNode->insertBefore($node, $this->choose->nextSibling);
117
		}
118
		else
119
		{
120 2
			$this->choose->parentNode->appendChild($node);
121
		}
122 2
		foreach ($branches as $branch)
123
		{
124 2
			$branch->removeChild($branch->lastChild);
125
		}
126
	}
127
128
	/**
129
	* {@inheritdoc}
130
	*/
131 16
	protected function optimizeChoose()
132
	{
133 16
		if ($this->hasOtherwise())
134
		{
135 15
			$this->optimizeCommonFirstChild();
136 15
			$this->optimizeCommonLastChild();
137 15
			$this->optimizeCommonOnlyChild();
138 15
			$this->optimizeEmptyBranch();
139 15
			$this->optimizeEmptyOtherwise();
140
		}
141 16
		if ($this->isEmpty())
142
		{
143 3
			$this->choose->parentNode->removeChild($this->choose);
144
		}
145
		else
146
		{
147 14
			$this->optimizeSingleBranch();
148
		}
149
	}
150
151
	/**
152
	* Optimize current xsl:choose by moving out the first child of each branch if they match
153
	*
154
	* @return void
155
	*/
156 15
	protected function optimizeCommonFirstChild()
157
	{
158 15
		while ($this->matchBranches('firstChild'))
159
		{
160 4
			$this->moveFirstChildBefore();
161
		}
162
	}
163
164
	/**
165
	* Optimize current xsl:choose by moving out the last child of each branch if they match
166
	*
167
	* @return void
168
	*/
169 15
	protected function optimizeCommonLastChild()
170
	{
171 15
		while ($this->matchBranches('lastChild'))
172
		{
173 2
			$this->moveLastChildAfter();
174
		}
175
	}
176
177
	/**
178
	* Optimize current xsl:choose by moving out only child of each branch if they match
179
	*
180
	* This will reorder xsl:choose/xsl:when/div into div/xsl:choose/xsl:when if every branch has
181
	* the same only child (excluding the child's own descendants)
182
	*
183
	* @return void
184
	*/
185 15
	protected function optimizeCommonOnlyChild()
186
	{
187 15
		while ($this->matchOnlyChild())
188
		{
189 2
			$this->reparentChild();
190
		}
191
	}
192
193
	/**
194
	* Switch the logic of an xsl:otherwise if the only other branch is empty
195
	*
196
	* @return void
197
	*/
198 15
	protected function optimizeEmptyBranch()
199
	{
200 15
		$query = 'count(xsl:when) = 1 and count(xsl:when/node()) = 0 and xsl:otherwise';
201 15
		if (!$this->xpath->evaluate($query, $this->choose))
202
		{
203 12
			return;
204
		}
205
206
		// test="@foo" becomes test="not(@foo)"
207 4
		$when = $this->xpath('xsl:when', $this->choose)[0];
208 4
		$when->setAttribute('test', 'not(' . $when->getAttribute('test') . ')');
209
210 4
		$otherwise = $this->xpath('xsl:otherwise', $this->choose)[0];
211 4
		while ($otherwise->firstChild)
212
		{
213 1
			$when->appendChild($otherwise->removeChild($otherwise->firstChild));
214
		}
215
	}
216
217
	/**
218
	* Optimize away the xsl:otherwise child of current xsl:choose if it's empty
219
	*
220
	* @return void
221
	*/
222 15
	protected function optimizeEmptyOtherwise()
223
	{
224 15
		$query = 'xsl:otherwise[count(node()) = 0]';
225 15
		foreach ($this->xpath($query, $this->choose) as $otherwise)
226
		{
227 5
			$this->choose->removeChild($otherwise);
228
		}
229
	}
230
231
	/**
232
	* Replace current xsl:choose with xsl:if if it has only one branch
233
	*
234
	* @return void
235
	*/
236 14
	protected function optimizeSingleBranch()
237
	{
238 14
		$query = 'count(xsl:when) = 1 and not(xsl:otherwise)';
239 14
		if (!$this->xpath->evaluate($query, $this->choose))
240
		{
241 11
			return;
242
		}
243 3
		$when = $this->xpath('xsl:when', $this->choose)[0];
244 3
		$if   = $this->createElement('xsl:if');
245 3
		$if->setAttribute('test', $when->getAttribute('test'));
246 3
		while ($when->firstChild)
247
		{
248 3
			$if->appendChild($when->removeChild($when->firstChild));
249
		}
250
251 3
		$this->choose->parentNode->replaceChild($if, $this->choose);
252
	}
253
254
	/**
255
	* Reorder the current xsl:choose tree to make it a child of the first child of its first branch
256
	*
257
	* This will reorder xsl:choose/xsl:when/div into div/xsl:choose/xsl:when
258
	*
259
	* @return void
260
	*/
261 2
	protected function reparentChild()
262
	{
263 2
		$branches  = $this->getBranches();
264 2
		$childNode = $branches[0]->firstChild->cloneNode();
265 2
		$childNode->appendChild($this->choose->parentNode->replaceChild($childNode, $this->choose));
266
267 2
		foreach ($branches as $branch)
268
		{
269 2
			$this->adoptChildren($branch);
270
		}
271
	}
272
}