Completed
Push — master ( eeb5ec...507cda )
by Josh
04:12
created

FoldConstantXPathExpressions::evaluate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 Exception;
11
use s9e\TextFormatter\Utils\XPath;
12
13
class FoldConstantXPathExpressions extends AbstractConstantFolding
14
{
15
	/**
16
	* @var string[] List of supported XPath functions
17
	*/
18
	protected $supportedFunctions = [
19
		'boolean',
20
		'ceiling',
21
		'concat',
22
		'contains',
23
		'floor',
24
		'normalize-space',
25
		'not',
26
		'number',
27
		'round',
28
		'starts-with',
29
		'string',
30
		'string-length',
31
		'substring',
32
		'substring-after',
33
		'substring-before',
34
		'translate'
35
	];
36
37
	/**
38
	* {@inheritdoc}
39
	*/
40 35
	protected function getOptimizationPasses()
41
	{
42
		return [
43 35
			'(^(?:"[^"]*"|\'[^\']*\'|\\.[0-9]|[^"$&\'./:@[\\]])++$)' => 'foldConstantXPathExpression'
44
		];
45
	}
46
47
	/**
48
	* Evaluate given expression without raising any warnings
49
	*
50
	* @param  string $expr
51
	* @return mixed
52
	*/
53 20
	protected function evaluate($expr)
54
	{
55 20
		$useErrors = libxml_use_internal_errors(true);
56 20
		$result    = $this->xpath->evaluate($expr);
57 20
		libxml_use_internal_errors($useErrors);
58
59 20
		return $result;
60
	}
61
62
	/**
63
	* Evaluate and replace a constant XPath expression
64
	*
65
	* @param  array  $m
66
	* @return string
67
	*/
68 30
	protected function foldConstantXPathExpression(array $m)
69
	{
70 30
		$expr = $m[0];
71 30
		if ($this->isConstantExpression($expr))
72
		{
73
			try
74
			{
75 20
				$result     = $this->evaluate($expr);
76 20
				$foldedExpr = XPath::export($result);
77 19
				$expr       = $this->selectReplacement($expr, $foldedExpr);
78
			}
79 1
			catch (Exception $e)
80
			{
81
				// Do nothing
82
			}
83
		}
84
85 30
		return $expr;
86
	}
87
88
	/**
89
	* Test whether given expression seems to be constant
90
	*
91
	* @param  string $expr
92
	* @return bool
93
	*/
94 30
	protected function isConstantExpression($expr)
95
	{
96
		// Replace strings to avoid false-positives
97 30
		$expr = preg_replace('("[^"]*"|\'[^\']*\')', '0', $expr);
98
99
		// Match function calls against the list of supported functions
100 30
		preg_match_all('(\\w[-\\w]+(?=\\())', $expr, $m);
101 30
		if (count(array_diff($m[0], $this->supportedFunctions)) > 0)
102
		{
103 13
			return false;
104
		}
105
106
		// Match unsupported characters and keywords, as well as function calls without arguments
107 26
		return !preg_match('([^\\s!\\-0-9<=>a-z\\(-.]|\\.(?![0-9])|\\b[-a-z](?![-\\w]+\\()|\\(\\s*\\))i', $expr);
108
	}
109
110
	/**
111
	* Select the best replacement for given expression
112
	*
113
	* @param  string $expr       Original expression
114
	* @param  string $foldedExpr Folded expression
115
	* @return string
116
	*/
117 19
	protected function selectReplacement($expr, $foldedExpr)
118
	{
119
		// Use the folded expression if it's smaller or it's a boolean
120 19
		if (strlen($foldedExpr) < strlen($expr) || $foldedExpr === 'false()' || $foldedExpr === 'true()')
121
		{
122 18
			return $foldedExpr;
123
		}
124
125 10
		return $expr;
126
	}
127
}