Completed
Branch PHPRenderer (6c13a8)
by Josh
10:40
created

XPathHelper::export()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 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\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
}