Completed
Branch XPathConvertorRefactor (245ee2)
by Josh
12:06
created

XPathConvertor::substringbefore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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