Completed
Push — master ( 27f709...a3e52e )
by Josh
03:54
created

XPathConvertor::getDefaultParser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2

Importance

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