Completed
Branch XPathConvertorRefactor (0e46c7)
by Josh
09:34
created

XPathConvertor::bool()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 3
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\RendererGenerators\PHP;
9
10
use RuntimeException;
11
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Runner;
12
13
class XPathConvertor
14
{
15
	/**
16
	* @var Runner
17
	*/
18
	protected $runner;
19
20
	/**
21
	* Constructor
22
	*/
23
	public function __construct(Runner $runner = null)
24
	{
25
		$this->runner = $runner ?: new Runner;
26
	}
27
28
	/**
29
	* Convert an XPath expression (used in a condition) into PHP code
30
	*
31
	* This method is similar to convertXPath() but it selectively replaces some simple conditions
32
	* with the corresponding DOM method for performance reasons
33
	*
34
	* @param  string $expr XPath expression
35
	* @return string       PHP code
36
	*/
37
	public function convertCondition($expr)
38
	{
39
		// Replace @attr with boolean(@attr) in boolean expressions
40
		$expr = preg_replace(
41
			'((^|\\(\\s*|\\b(?:and|or)\\s*)([\\(\\s]*)([$@][-\\w]+|@\\*)([\\)\\s]*)(?=$|\\s+(?:and|or)))',
42
			'$1$2boolean($3)$4',
43
			trim($expr)
44
		);
45
46
		// Replace not(boolean(@attr)) with not(@attr)
47
		$expr = preg_replace(
48
			'(not\\(boolean\\(([$@][-\\w]+)\\)\\))',
49
			'not($1)',
50
			$expr
51
		);
52
53
		try
54
		{
55
			return $this->runner->convert($expr);
56
		}
57
		catch (RuntimeException $e)
58
		{
59
			// Do nothing
60
		}
61
62
		// If the condition does not seem to contain a relational expression, or start with a
63
		// function call, we wrap it inside of a boolean() call
64
		if (!preg_match('([=<>]|\\bor\\b|\\band\\b|^[-\\w]+\\s*\\()', $expr))
65
		{
66
			$expr = 'boolean(' . $expr . ')';
67
		}
68
69
		return '$this->xpath->evaluate(' . $this->exportXPath($expr) . ',$node)';
70
	}
71
72
	/**
73
	* Convert an XPath expression (used as value) into PHP code
74
	*
75
	* @param  string $expr XPath expression
76
	* @return string       PHP code
77
	*/
78
	public function convertXPath($expr)
79
	{
80
		$expr = trim($expr);
81
		try
82
		{
83
			return $this->runner->convert($expr);
84
		}
85
		catch (RuntimeException $e)
86
		{
87
			// Do nothing
88
		}
89
90
		// Make sure the expression evaluates as a string
91
		if (!preg_match('(^[-\\w]*s(?:pace|tring)[-\\w]*\\()', $expr))
92
		{
93
			$expr = 'string(' . $expr . ')';
94
		}
95
		$expr = $this->exportXPath($expr);
96
97
		return '$this->xpath->evaluate(' . $expr . ',$node)';
98
	}
99
100
	/**
101
	* Export an XPath expression as PHP with special consideration for XPath variables
102
	*
103
	* Will return PHP source representing the XPath expression, with special consideration for XPath
104
	* variables which are returned as a method call to XPath::export()
105
	*
106
	* @param  string $expr XPath expression
107
	* @return string       PHP representation of the expression
108
	*/
109
	protected function exportXPath($expr)
110
	{
111
		$phpTokens = [];
112
		foreach ($this->tokenizeXPathForExport($expr) as list($type, $content))
113
		{
114
			$methodName  = 'exportXPath' . ucfirst($type);
115
			$phpTokens[] = $this->$methodName($content);
116
		}
117
118
		return implode('.', $phpTokens);
119
	}
120
121
	/**
122
	* Convert a "current()" XPath expression to its PHP source representation
123
	*
124
	* @return string
125
	*/
126
	protected function exportXPathCurrent()
127
	{
128
		return '$node->getNodePath()';
129
	}
130
131
	/**
132
	* Convert a fragment of an XPath expression to its PHP source representation
133
	*
134
	* @param  string $fragment
135
	* @return string
136
	*/
137
	protected function exportXPathFragment($fragment)
138
	{
139
		return var_export($fragment, true);
140
	}
141
142
	/**
143
	* Convert an XSLT parameter to its PHP source representation
144
	*
145
	* @param  string $param Parameter, including the leading $
146
	* @return string
147
	*/
148
	protected function exportXPathParam($param)
149
	{
150
		$paramName = ltrim($param, '$');
151
152
		return '$this->getParamAsXPath(' . var_export($paramName, true) . ')';
153
	}
154
155
	/**
156
	* Match the relevant components of an XPath expression
157
	*
158
	* @param  string $expr XPath expression
159
	* @return array
160
	*/
161
	protected function matchXPathForExport($expr)
162
	{
163
		$tokenExprs = [
164
			'(?<current>\\bcurrent\\(\\))',
165
			'(?<param>\\$\\w+)',
166
			'(?<fragment>"[^"]*"|\'[^\']*\'|.)'
167
		];
168
		preg_match_all('(' . implode('|', $tokenExprs) . ')s', $expr, $matches, PREG_SET_ORDER);
169
170
		// Merge fragment tokens
171
		$i = count($matches);
172
		while (--$i > 0)
173
		{
174
			if (isset($matches[$i]['fragment'], $matches[$i - 1]['fragment']))
175
			{
176
				$matches[$i - 1]['fragment'] .= $matches[$i]['fragment'];
177
				unset($matches[$i]);
178
			}
179
		}
180
181
		return array_values($matches);
182
	}
183
184
	/**
185
	* Tokenize an XPath expression for use in PHP
186
	*
187
	* @param  string $expr XPath expression
188
	* @return array
189
	*/
190
	protected function tokenizeXPathForExport($expr)
191
	{
192
		$tokens = [];
193
		foreach ($this->matchXPathForExport($expr) as $match)
194
		{
195
			foreach (array_reverse($match) as $k => $v)
196
			{
197
				// Use the last non-numeric match
198
				if (!is_numeric($k))
199
				{
200
					$tokens[] = [$k, $v];
201
					break;
202
				}
203
			}
204
		}
205
206
		return $tokens;
207
	}
208
}