1 : <?php
2 :
3 : /**
4 : * XSLT Benchmarking
5 : * @link https://github.com/masicek/XSLT-Benchmarking
6 : * @author Viktor Mašíček <viktor@masicek.net>
7 : * @license "New" BSD License
8 : */
9 :
10 : namespace XSLTBenchmarking\TestsRunner;
11 :
12 : require_once ROOT . '/Exceptions.php';
13 : require_once ROOT . '/Microtime.php';
14 : require_once LIBS . '/PhpPath/PhpPath.min.php';
15 :
16 : use XSLTBenchmarking\Microtime;
17 : use PhpPath\P;
18 :
19 :
20 : /**
21 : * Class for parse one template in one processor
22 : *
23 : * @author Viktor Mašíček <viktor@masicek.net>
24 : */
25 : class Processor
26 : {
27 :
28 :
29 : /**
30 : * Path of dirctory containing processors drivers
31 : *
32 : * @var string
33 : */
34 : private $driversDir;
35 :
36 : /**
37 : * Namespace of processors drivers
38 : *
39 : * @var string
40 : */
41 : private $driversNamespace;
42 :
43 : /**
44 : * Path of temporary directory
45 : *
46 : * @var string
47 : */
48 : private $tmpDir;
49 :
50 : /**
51 : * Class for measure memory usage of run command
52 : *
53 : * @var MemoryUsage
54 : */
55 : private $memoryUsage;
56 :
57 : /**
58 : * List of available names of processors
59 : *
60 : * @var array
61 : */
62 : private $available = NULL;
63 :
64 : /**
65 : * List of informations about processors
66 : *
67 : * @var array
68 : */
69 : private $informations = NULL;
70 :
71 :
72 : /**
73 : * Configure object
74 : *
75 : * @param string $tmpDir Path of temporary directory
76 : * @param MemoryUsage $memoryUsage Class for measure memory usage of run command
77 : * @param string $driversDir Path of dirctory containing processor drivers
78 : * @param string $driversNamespace Namespace of processors drivers
79 : */
80 : public function __construct(
81 : $tmpDir,
82 : MemoryUsage $memoryUsage,
83 : $driversDir = NULL,
84 : $driversNamespace = '\XSLTBenchmarking\TestsRunner\\'
85 : )
86 : {
87 8 : if (is_null($driversDir))
88 8 : {
89 6 : $driversDir = P::m(__DIR__, 'Drivers');
90 6 : }
91 :
92 8 : $this->tmpDir = P::mcd($tmpDir);
93 7 : $this->memoryUsage = $memoryUsage;
94 7 : $this->driversDir = P::mcd($driversDir);
95 6 : $this->driversNamespace = $driversNamespace;
96 6 : }
97 :
98 :
99 : /**
100 : * Return list of available names of processors
101 : *
102 : * @return array ([name] => AProcessorDriver)
103 : */
104 : public function getAvailable()
105 : {
106 5 : if (!$this->available)
107 5 : {
108 5 : $this->available = $this->detectAvailable();
109 5 : }
110 5 : return $this->available;
111 : }
112 :
113 :
114 : /**
115 : * Return information about processors
116 : *
117 : * @return array
118 : */
119 : public function getInformations()
120 : {
121 1 : if (!$this->informations)
122 1 : {
123 1 : $this->informations = $this->readInformations();
124 1 : }
125 1 : return $this->informations;
126 : }
127 :
128 :
129 : /**
130 : * Run one XSLT transformation in the processor
131 : *
132 : * @param string $processorName Name of used processor
133 : * @param string $templatePath Path of XSLT template
134 : * @param string $xmlInputPath Path of XML input file
135 : * @param string $outputPath Path of generated output file
136 : *
137 : * @return array|string List of spend times on transformation|Error message
138 : */
139 : public function run($processorName, $templatePath, $xmlInputPath, $outputPath, $repeating)
140 : {
141 8 : $processors = $this->getAvailable();
142 8 : if (!isset($processors[$processorName]))
143 8 : {
144 1 : throw new \XSLTBenchmarking\InvalidArgumentException('Unknown processor "' . $processorName . '"');
145 : }
146 :
147 7 : $processor = $processors[$processorName];
148 :
149 7 : P::mcf($templatePath);
150 6 : P::mcf($xmlInputPath);
151 :
152 : // stylesheet for transformation have to be set in input XML file
153 5 : if ($processor->isTemplateSetInInput())
154 5 : {
155 1 : $xmlInputPath = $this->makeInputWithTemplatePath($xmlInputPath, $templatePath);
156 1 : }
157 :
158 5 : $errorPath = P::m($this->tmpDir, 'transformation.err');
159 :
160 5 : $beforeCommand = $this->getCommand($processor->getBeforeCommandTemplate(), $templatePath, $xmlInputPath, $outputPath, $errorPath);
161 5 : $commandBase = $this->getCommand($processor->getCommandTemplate(), $templatePath, $xmlInputPath, $outputPath, $errorPath);
162 5 : $afterCommand = $this->getCommand($processor->getAfterCommandTemplate(), $templatePath, $xmlInputPath, $outputPath, $errorPath);
163 :
164 5 : $times = array();
165 5 : $memoryList = array();
166 5 : for ($repeatingIdx = 0; $repeatingIdx < $repeating; $repeatingIdx++)
167 : {
168 5 : if (is_file($errorPath))
169 5 : {
170 1 : throw new \XSLTBenchmarking\InvalidStateException('Error tmp file should not exist "' . $errorPath . '"');
171 : }
172 :
173 4 : $command = $commandBase;
174 :
175 : // each transformation generate into separate file
176 4 : if ($repeatingIdx != 0)
177 4 : {
178 3 : $outputPathNew = preg_replace('/[.]([^.]*)$/', '-' . $repeatingIdx . '.$1', $outputPath);
179 3 : $command = str_replace($outputPath, $outputPathNew, $command);
180 3 : }
181 :
182 : // preparing command
183 : if ($beforeCommand)
184 4 : {
185 1 : exec($beforeCommand);
186 1 : }
187 :
188 : // memore usage - run
189 4 : $command = $this->memoryUsage->run($command);
190 :
191 : // transformation command
192 4 : $timeStart = Microtime::now();
193 4 : exec($command);
194 4 : $timeEnd = Microtime::now();
195 :
196 : // memore usage - get
197 4 : $memoryList[] = (string)$this->memoryUsage->get();
198 :
199 : // concluding comand
200 : if ($afterCommand)
201 4 : {
202 1 : exec($afterCommand);
203 1 : }
204 :
205 : // check exitence of generated file
206 4 : if ($repeatingIdx == 0 && !is_file($outputPath))
207 4 : {
208 1 : $error = 'Output file was not be generated (' . $outputPath . ')';
209 : // detect generated error
210 1 : if (is_file($errorPath))
211 1 : {
212 1 : $error .= '; ' . file_get_contents($errorPath);
213 1 : unlink($errorPath);
214 1 : }
215 :
216 1 : break;
217 : }
218 3 : if ($repeatingIdx != 0 && !is_file($outputPathNew))
219 3 : {
220 0 : $error = 'Output file (repeated) was not be generated (' . $outputPathNew . ')';
221 : // detect generated error
222 0 : if (is_file($errorPath))
223 0 : {
224 0 : $error .= '; ' . file_get_contents($errorPath);
225 0 : unlink($errorPath);
226 0 : }
227 :
228 0 : break;
229 : }
230 :
231 : // difference between two same transformation
232 3 : if ($repeatingIdx != 0)
233 3 : {
234 3 : if (file_get_contents($outputPath) == file_get_contents($outputPathNew))
235 3 : {
236 3 : unlink($outputPathNew);
237 3 : }
238 : else
239 : {
240 0 : $error = 'Difference between two same transformation: (' . $outputPath . ') != (' . $outputPathNew . ')';
241 0 : break;
242 : }
243 3 : }
244 :
245 : // detect error
246 3 : if (is_file($errorPath))
247 3 : {
248 0 : $error = file_get_contents($errorPath);
249 0 : unlink($errorPath);
250 0 : }
251 : else
252 : {
253 3 : $error = '';
254 : }
255 :
256 : if ($error)
257 3 : {
258 0 : break;
259 : }
260 :
261 : // spend time
262 3 : $times[] = Microtime::substract($timeEnd, $timeStart);
263 3 : }
264 :
265 : if ($error)
266 4 : {
267 1 : return $error;
268 : }
269 : else
270 : {
271 : return array(
272 3 : 'times' => $times,
273 : 'memory' => $memoryList
274 3 : );
275 : }
276 : }
277 :
278 :
279 : // --- PRIVATE FUNCTIONS ---
280 :
281 :
282 : /**
283 : * Detect list of available names of processors
284 : *
285 : * @return array ([name] => AProcessorDriver)
286 : */
287 : private function detectAvailable()
288 : {
289 5 : $driversFiles = scandir($this->driversDir);
290 :
291 5 : $drivers = array();
292 5 : foreach ($driversFiles as $driverFile)
293 : {
294 5 : if (in_array($driverFile, array('AProcessorDriver.php', '.', '..')))
295 5 : {
296 5 : continue;
297 : }
298 :
299 : // driver have to be php file
300 5 : if (substr($driverFile, -4) !== '.php')
301 5 : {
302 4 : continue;
303 : }
304 :
305 5 : require_once P::m($this->driversDir, $driverFile);
306 5 : $className = $this->driversNamespace . substr($driverFile, 0, -4);
307 5 : $driver = new $className;
308 :
309 : // driver have to be instance of AProcessorDriver
310 5 : if (($driver instanceof AProcessorDriver) && ($driver->isAvailable()))
311 5 : {
312 5 : $drivers[$driver->getName()] = $driver;
313 5 : }
314 5 : }
315 :
316 5 : return $drivers;
317 : }
318 :
319 :
320 : /**
321 : * Read information about processors
322 : *
323 : * @return array
324 : */
325 : private function readInformations()
326 : {
327 1 : $informations = array();
328 1 : foreach ($this->getAvailable() as $name => $processorDriver)
329 : {
330 1 : $informations[$name] = $processorDriver->getInformations();
331 1 : }
332 :
333 1 : return $informations;
334 : }
335 :
336 :
337 : /**
338 : * Make command from template
339 : *
340 : * Templates substitutions:
341 : * [XSLT] = path of XSLT template for transformation
342 : * [INPUT] = path of input XML file
343 : * [OUTPUT] = path of generated output XML file
344 : * [ERROR] = path of file for eventual generated error message
345 : * [PROCESSORS] = path of directory containing XSLT processors (libraries, command-line program etc.)
346 : * [LIBS] = path of Libs directory
347 : *
348 : * @param string $commandTemplate Template of command
349 : * @param string $templatePath Path of XSLT template for transformation
350 : * @param string $xmlInputPath Path of input XML file
351 : * @param string $outputPath Path of generated output XML file
352 : * @param string $errorPath Path of file for eventual generated error message
353 : *
354 : * @return string
355 : */
356 : private function getCommand($commandTemplate, $templatePath, $xmlInputPath, $outputPath, $errorPath)
357 : {
358 8 : $command = $commandTemplate;
359 :
360 : // replace substitutions
361 8 : $command = str_replace('[XSLT]', $templatePath, $command);
362 8 : $command = str_replace('[INPUT]', $xmlInputPath, $command);
363 8 : $command = str_replace('[OUTPUT]', $outputPath, $command);
364 8 : $command = str_replace('[ERROR]', $errorPath, $command);
365 8 : $command = str_replace('[PROCESSORS]', P::m(LIBS, 'Processors'), $command);
366 8 : $command = str_replace('[LIBS]', P::m(LIBS), $command);
367 :
368 8 : return $command;
369 : }
370 :
371 :
372 : /**
373 : * Add into input XML path of template by directive "<?xml-stylesheet href="[XSLT]" type="text/xml" ..."
374 : * and return path of generated file (in temporary directory).
375 : *
376 : * @param string $xmlInputPath Path of input XML
377 : * @param string $templatePath Path of template
378 : *
379 : * @return string
380 : */
381 : private function makeInputWithTemplatePath($xmlInputPath, $templatePath)
382 : {
383 1 : $content = file_get_contents($xmlInputPath);
384 :
385 : // add template path
386 1 : $content = str_replace(
387 1 : '<?xml version="1.0" encoding="UTF-8"?>',
388 1 : '<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="' . $templatePath . '" ?>',
389 : $content
390 1 : );
391 :
392 1 : $xmlInputPath = P::m($this->tmpDir, basename($xmlInputPath));
393 1 : file_put_contents($xmlInputPath, $content);
394 :
395 1 : return $xmlInputPath;
396 : }
397 :
398 :
399 : }
|