Passed
Push — master ( 80c61e...31ae9a )
by Josh
02:22
created

OptimizeChoose   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 33
eloc 68
c 1
b 0
f 0
dl 0
loc 228
ccs 74
cts 74
cp 1
rs 9.76

12 Methods

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