Completed
Branch XPathConvertorRefactor (13c9f8)
by Josh
08:27
created

Runner::setDefaultConvertors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 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\XPathConvertor;
9
10
use RuntimeException;
11
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\AbstractConvertor;
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 Runner
22
{
23
	/**
24
	* @var array
25
	*/
26
	protected $callbacks;
27
28
	/**
29
	* @var array
30
	*/
31
	protected $groups;
32
33
	/**
34
	* @var string
35
	*/
36
	protected $regexp = '((?!))';
37
38
	/**
39
	* @var array
40
	*/
41
	protected $regexps;
42
43
	/**
44
	* Constructor
45
	*
46
	* @return void
47
	*/
48
	public function __construct()
49
	{
50
		$this->setConvertors($this->getDefaultConvertors());
51
	}
52
53
	/**
54
	* Convert given XPath expression to PHP
55
	*
56
	* @param  string $expr
57
	* @return string
58
	*/
59
	public function convert($expr)
60
	{
61
		if (preg_match($this->regexp, $expr, $m))
62
		{
63
			foreach (array_reverse(array_keys($m)) as $name)
64
			{
65
				if (isset($this->callbacks[$name]))
66
				{
67
					return call_user_func_array($this->callbacks[$name], $this->getArguments($m, $name));
68
				}
69
			}
70
		}
71
72
		throw new RuntimeException("Cannot convert '" . $expr . "'");
73
	}
74
75
	/**
76
	* Set the list of convertors used by this instance
77
	*
78
	* @param  AbstractConvertor[] $convertors
79
	* @return void
80
	*/
81
	public function setConvertors(array $convertors)
82
	{
83
		$this->callbacks = [];
84
		$this->groups    = [];
85
		$this->regexps   = [];
86
		foreach ($convertors as $convertor)
87
		{
88
			$this->addConvertor($convertor);
89
		}
90
91
		// Sort regexps by length to keep their order consistent
92
		$this->sortRegexps();
93
94
		// Add regexp groups
95
		foreach ($this->groups as $group => $captures)
96
		{
97
			sort($captures);
98
			$this->regexps[$group] = '(?<' . $group . '>' . implode('|', $captures) . ')';
99
		}
100
101
		$this->regexp = '(^(?:' . implode('|', $this->regexps) . ')$)';
102
	}
103
104
	/**
105
	* Add a convertor to the list used by this instance
106
	*
107
	* @param  AbstractConvertor $convertor
108
	* @return void
109
	*/
110
	protected function addConvertor(AbstractConvertor $convertor)
111
	{
112
		foreach ($convertor->getRegexpGroups() as $name => $group)
113
		{
114
			$this->groups[$group][] = '(?&' . $name . ')';
115
		}
116
117
		foreach ($convertor->getRegexps() as $name => $regexp)
118
		{
119
			$regexp = $this->insertCaptureNames($name, $regexp);
120
			$regexp = str_replace(' ', '\\s*', $regexp);
121
			$regexp = '(?<' . $name . '>' . $regexp . ')';
122
123
			$this->callbacks[$name] = [$convertor, 'convert' . $name];
124
			$this->regexps[$name]   = $regexp;
125
		}
126
	}
127
128
	/**
129
	* Get the list of arguments produced by a regexp's match
130
	*
131
	* @param  string[] $matches Regexp matches
132
	* @param  string   $name    Regexp name
133
	* @return string[]
134
	*/
135
	protected function getArguments(array $matches, $name)
136
	{
137
		$args = [];
138
		$i    = 0;
139
		while (isset($matches[$name . $i]))
140
		{
141
			$args[] = $matches[$name . $i];
142
			++$i;
143
		}
144
145
		return $args;
146
	}
147
148
	/**
149
	* Return the default list of convertors
150
	*
151
	* @return AbstractConvertor[]
152
	*/
153
	protected function getDefaultConvertors()
154
	{
155
		$convertors   = [];
156
		$convertors[] = new BooleanFunctions($this);
157
		$convertors[] = new BooleanOperators($this);
158
		$convertors[] = new Comparisons($this);
159
		$convertors[] = new Core($this);
160
		$convertors[] = new Math($this);
161
		if (extension_loaded('mbstring'))
162
		{
163
			$convertors[] = new MultiByteStringManipulation($this);
164
		}
165
		$convertors[] = new SingleByteStringFunctions($this);
166
		$convertors[] = new SingleByteStringManipulation($this);
167
168
		return $convertors;
169
	}
170
171
	/**
172
	* Insert capture names into given regexp
173
	*
174
	* @param  string $name   Name of the regexp, used to name captures
175
	* @param  string $regexp Original regexp
176
	* @return string         Modified regexp
177
	*/
178
	protected function insertCaptureNames($name, $regexp)
179
	{
180
		$i = 0;
181
182
		return preg_replace_callback(
183
			'((?<!\\\\)\\((?!\\?))',
184
			function ($m) use (&$i, $name)
185
			{
186
				return '(?<' . $name . $i++ . '>';
187
			},
188
			$regexp
189
		);
190
	}
191
192
	/**
193
	* Sort regexps by length
194
	*
195
	* @return void
196
	*/
197
	protected function sortRegexps()
198
	{
199
		uasort(
200
			$this->regexps,
201
			function ($a, $b)
202
			{
203
				return strlen($b) - strlen($a);
204
			}
205
		);
206
	}
207
}