Completed
Branch TemplateNormalizations (ae42e4)
by Josh
33:16
created

MergeIdenticalConditionalBranches   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 2
dl 0
loc 164
rs 10
c 0
b 0
f 0

5 Methods

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