Completed
Push — master ( 25559c...472c56 )
by Josh
20:25
created

XPathHelper::parseEqualityExpr()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 53
rs 7.5251
c 0
b 0
f 0
cc 7
eloc 30
nc 5
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Helpers;
9
10
use RuntimeException;
11
use s9e\TextFormatter\Utils\XPath;
12
13
abstract class XPathHelper
14
{
15
	/**
16
	* Return the list of variables used in a given XPath expression
17
	*
18
	* @param  string $expr XPath expression
19
	* @return array        Alphabetically sorted list of unique variable names
20
	*/
21
	public static function getVariables($expr)
22
	{
23
		// First, remove strings' contents to prevent false-positives
24
		$expr = preg_replace('/(["\']).*?\\1/s', '$1$1', $expr);
25
26
		// Capture all the variable names
27
		preg_match_all('/\\$(\\w+)/', $expr, $matches);
28
29
		// Dedupe and sort names
30
		$varNames = array_unique($matches[1]);
31
		sort($varNames);
32
33
		return $varNames;
34
	}
35
36
	/**
37
	* Determine whether given XPath expression definitely evaluates to a number
38
	*
39
	* @param  string $expr XPath expression
40
	* @return bool         Whether given XPath expression definitely evaluates to a number
41
	*/
42
	public static function isExpressionNumeric($expr)
43
	{
44
		// Trim the expression and remove parentheses that are not part of a function call. PCRE
45
		// does not support lookbehind assertions of variable length so we have to flip the string.
46
		// We exclude the XPath operator "div" (flipped into "vid") to avoid false positives
47
		$expr = strrev(preg_replace('(\\((?!\\s*(?!vid(?!\\w))\\w))', ' ', strrev($expr)));
48
		$expr = str_replace(')', ' ', $expr);
49
		if (preg_match('(^\\s*([$@][-\\w]++|-?\\.\\d++|-?\\d++(?:\\.\\d++)?)(?>\\s*(?>[-+*]|div)\\s*(?1))++\\s*$)', $expr))
50
		{
51
			return true;
52
		}
53
54
		return false;
55
	}
56
57
	/**
58
	* Remove extraneous space in a given XPath expression
59
	*
60
	* @param  string $expr Original XPath expression
61
	* @return string       Minified XPath expression
62
	*/
63
	public static function minify($expr)
64
	{
65
		$old     = $expr;
66
		$strings = [];
67
68
		// Trim the surrounding whitespace then temporarily remove literal strings
69
		$expr = preg_replace_callback(
70
			'/"[^"]*"|\'[^\']*\'/',
71
			function ($m) use (&$strings)
72
			{
73
				$uniqid = '(' . sha1(uniqid()) . ')';
74
				$strings[$uniqid] = $m[0];
75
76
				return $uniqid;
77
			},
78
			trim($expr)
79
		);
80
81
		if (preg_match('/[\'"]/', $expr))
82
		{
83
			throw new RuntimeException("Cannot parse XPath expression '" . $old . "'");
84
		}
85
86
		// Normalize whitespace to a single space
87
		$expr = preg_replace('/\\s+/', ' ', $expr);
88
89
		// Remove the space between a non-word character and a word character
90
		$expr = preg_replace('/([-a-z_0-9]) ([^-a-z_0-9])/i', '$1$2', $expr);
91
		$expr = preg_replace('/([^-a-z_0-9]) ([-a-z_0-9])/i', '$1$2', $expr);
92
93
		// Remove the space between two non-word characters as long as they're not two -
94
		$expr = preg_replace('/(?!- -)([^-a-z_0-9]) ([^-a-z_0-9])/i', '$1$2', $expr);
95
96
		// Remove the space between a - and a word character, as long as there's a space before -
97
		$expr = preg_replace('/ - ([a-z_0-9])/i', ' -$1', $expr);
98
99
		// Remove the spaces between a number and the div operator and the next token
100
		$expr = preg_replace('/((?:^|[ \\(])\\d+) div ?/', '$1div', $expr);
101
102
		// Remove the space between the div operator the next token
103
		$expr = preg_replace('/([^-a-z_0-9]div) (?=[$0-9@])/', '$1', $expr);
104
105
		// Restore the literals
106
		$expr = strtr($expr, $strings);
107
108
		return $expr;
109
	}
110
111
	/**
112
	* Parse an XPath expression that is composed entirely of equality tests between a variable part
113
	* and a constant part
114
	*
115
	* @param  string      $expr
116
	* @return array|false
117
	*/
118
	public static function parseEqualityExpr($expr)
119
	{
120
		// Match an equality between a variable and a literal or the concatenation of strings
121
		$eq = '(?<equality>'
122
		    . '(?<key>@[-\\w]+|\\$\\w+|\\.)'
123
		    . '(?<operator>\\s*=\\s*)'
124
		    . '(?:'
125
		    . '(?<literal>(?<string>"[^"]*"|\'[^\']*\')|0|[1-9][0-9]*)'
126
		    . '|'
127
		    . '(?<concat>concat\\(\\s*(?&string)\\s*(?:,\\s*(?&string)\\s*)+\\))'
128
		    . ')'
129
		    . '|'
130
		    . '(?:(?<literal>(?&literal))|(?<concat>(?&concat)))(?&operator)(?<key>(?&key))'
131
		    . ')';
132
133
		// Match a string that is entirely composed of equality checks separated with "or"
134
		$regexp = '(^(?J)\\s*' . $eq . '\\s*(?:or\\s*(?&equality)\\s*)*$)';
135
136
		if (!preg_match($regexp, $expr))
137
		{
138
			return false;
139
		}
140
141
		preg_match_all("((?J)$eq)", $expr, $matches, PREG_SET_ORDER);
142
143
		$map = [];
144
		foreach ($matches as $m)
145
		{
146
			$key = $m['key'];
147
			if (!empty($m['concat']))
148
			{
149
				preg_match_all('(\'[^\']*\'|"[^"]*")', $m['concat'], $strings);
150
151
				$value = '';
152
				foreach ($strings[0] as $string)
153
				{
154
					$value .= substr($string, 1, -1);
155
				}
156
			}
157
			else
158
			{
159
				$value = $m['literal'];
160
				if ($value[0] === "'" || $value[0] === '"')
161
				{
162
					$value = substr($value, 1, -1);
163
				}
164
			}
165
166
			$map[$key][] = $value;
167
		}
168
169
		return $map;
170
	}
171
}