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

collectCompatibleBranches()   B

Complexity

Conditions 8
Paths 4

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 8.0515
c 0
b 0
f 0
ccs 17
cts 17
cp 1
cc 8
nc 4
nop 1
crap 8
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
use DOMNode;
12
use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
13
14
/**
15
* Merge xsl:when branches if they have identical content
16
*
17
* NOTE: may fail if branches have identical equality expressions, e.g. "@a=1" and "@a=1"
18
*/
19
class MergeIdenticalConditionalBranches extends AbstractNormalization
20
{
21
	/**
22
	* {@inheritdoc}
23
	*/
24
	protected $queries = ['//xsl:choose'];
25
26
	/**
27
	* Collect consecutive xsl:when elements that share the same kind of equality tests
28
	*
29
	* Will return xsl:when elements that test a constant part (e.g. a literal) against the same
30
	* variable part (e.g. the same attribute)
31
	*
32
	* @param  DOMNode      $node First node to inspect
33
	* @return DOMElement[]
34
	*/
35 7
	protected function collectCompatibleBranches(DOMNode $node)
36
	{
37 7
		$nodes  = [];
38 7
		$key    = null;
39 7
		$values = [];
40
41 7
		while ($node && $this->isXsl($node, 'when'))
42
		{
43 7
			$branch = XPathHelper::parseEqualityExpr($node->getAttribute('test'));
44
45 7
			if ($branch === false || count($branch) !== 1)
46
			{
47
				// The expression is not entirely composed of equalities, or they have a different
48
				// variable part
49 3
				break;
50
			}
51
52 6
			if (isset($key) && key($branch) !== $key)
53
			{
54
				// Not the same variable as our branches
55 1
				break;
56
			}
57
58 6
			if (array_intersect($values, end($branch)))
59
			{
60
				// Duplicate values across branches, e.g. ".=1 or .=2" and ".=2 or .=3"
61 1
				break;
62
			}
63
64 6
			$key    = key($branch);
65 6
			$values = array_merge($values, end($branch));
66
67
			// Record this node then move on to the next sibling
68 6
			$nodes[] = $node;
69 6
			$node    = $node->nextSibling;
70
		}
71
72 7
		return $nodes;
73
	}
74
75
	/**
76
	* Merge identical xsl:when elements from a list
77
	*
78
	* @param  DOMElement[] $nodes
79
	* @return void
80
	*/
81 7
	protected function mergeBranches(array $nodes)
82
	{
83 7
		$sortedNodes = [];
84 7
		foreach ($nodes as $node)
85
		{
86 7
			$outerXML = $node->ownerDocument->saveXML($node);
87 7
			$innerXML = preg_replace('([^>]+>(.*)<[^<]+)s', '$1', $outerXML);
88
89 7
			$sortedNodes[$innerXML][] = $node;
90
		}
91
92 7
		foreach ($sortedNodes as $identicalNodes)
93
		{
94 7
			if (count($identicalNodes) < 2)
95
			{
96 5
				continue;
97
			}
98
99 4
			$expr = [];
100 4
			foreach ($identicalNodes as $i => $node)
101
			{
102 4
				$expr[] = $node->getAttribute('test');
103
104 4
				if ($i > 0)
105
				{
106 4
					$node->parentNode->removeChild($node);
107
				}
108
			}
109
110 4
			$identicalNodes[0]->setAttribute('test', implode(' or ', $expr));
111
		}
112 7
	}
113
114
	/**
115
	* Inspect the branches of an xsl:choose element and merge branches if their content is identical
116
	* and their order does not matter
117
	*
118
	* @param  DOMElement $choose xsl:choose element
119
	* @return void
120
	*/
121 7
	protected function mergeCompatibleBranches(DOMElement $choose)
122
	{
123 7
		$node = $choose->firstChild;
124 7
		while ($node)
125
		{
126 7
			$nodes = $this->collectCompatibleBranches($node);
127
128 7
			if (count($nodes) > 1)
129
			{
130 5
				$node = end($nodes)->nextSibling;
131
132
				// Try to merge branches if there's more than one of them
133 5
				$this->mergeBranches($nodes);
134
			}
135
			else
136
			{
137 5
				$node = $node->nextSibling;
138
			}
139
		}
140 7
	}
141
142
	/**
143
	* Inspect the branches of an xsl:choose element and merge consecutive branches if their content
144
	* is identical
145
	*
146
	* @param  DOMElement $choose xsl:choose element
147
	* @return void
148
	*/
149 7
	protected function mergeConsecutiveBranches(DOMElement $choose)
150
	{
151
		// Try to merge consecutive branches even if their test conditions are not compatible,
152
		// e.g. "@a=1" and "@b=2"
153 7
		$nodes = [];
154 7
		foreach ($choose->childNodes as $node)
155
		{
156 7
			if ($this->isXsl($node, 'when'))
157
			{
158 7
				$nodes[] = $node;
159
			}
160
		}
161
162 7
		$i = count($nodes);
163 7
		while (--$i > 0)
164
		{
165 6
			$this->mergeBranches([$nodes[$i - 1], $nodes[$i]]);
166
		}
167 7
	}
168
169
	/**
170
	* {@inheritdoc}
171
	*/
172 7
	protected function normalizeElement(DOMElement $element)
173
	{
174 7
		$this->mergeCompatibleBranches($element);
175 7
		$this->mergeConsecutiveBranches($element);
176
	}
177
}