Completed
Push — master ( 3755ed...f1a382 )
by Josh
15:22
created

PHP::getRenderer()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 26
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 11
nc 4
nop 1
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2017 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Configurator\RendererGenerators;
9
10
use DOMElement;
11
use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
12
use s9e\TextFormatter\Configurator\Helpers\TemplateParser;
13
use s9e\TextFormatter\Configurator\RendererGenerator;
14
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\ControlStructuresOptimizer;
15
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\Optimizer;
16
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\Quick;
17
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\Serializer;
18
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\SwitchStatement;
19
use s9e\TextFormatter\Configurator\Rendering;
20
21
class PHP implements RendererGenerator
22
{
23
	/**
24
	* XSL namespace
25
	*/
26
	const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
27
28
	/**
29
	* @var string Directory where the renderer's source is automatically saved if set, and if filepath is not set
30
	*/
31
	public $cacheDir;
32
33
	/**
34
	* @var string Name of the class to be created. If null, a random name will be generated
35
	*/
36
	public $className;
37
38
	/**
39
	* @var ControlStructuresOptimizer Control structures optimizer
40
	*/
41
	public $controlStructuresOptimizer;
42
43
	/**
44
	* @var string Prefix used when generating a default class name
45
	*/
46
	public $defaultClassPrefix = 'Renderer_';
47
48
	/**
49
	* @var bool Whether to enable the Quick renderer
50
	*/
51
	public $enableQuickRenderer = true;
52
53
	/**
54
	* @var string If set, path to the file where the renderer will be saved
55
	*/
56
	public $filepath;
57
58
	/**
59
	* @var string Name of the last class generated
60
	*/
61
	public $lastClassName;
62
63
	/**
64
	* @var string Path to the last file saved
65
	*/
66
	public $lastFilepath;
67
68
	/**
69
	* @var Optimizer Optimizer
70
	*/
71
	public $optimizer;
72
73
	/**
74
	* @var Serializer Serializer
75
	*/
76
	public $serializer;
77
78
	/**
79
	* @var bool Whether to use the mbstring functions as a replacement for XPath expressions
80
	*/
81
	public $useMultibyteStringFunctions;
82
83
	/**
84
	* Constructor
85
	*
86
	* @param string $cacheDir If set, path to the directory where the renderer will be saved
87
	*/
88
	public function __construct($cacheDir = null)
89
	{
90
		$this->cacheDir = (isset($cacheDir)) ? $cacheDir : sys_get_temp_dir();
91
		if (extension_loaded('tokenizer'))
92
		{
93
			$this->controlStructuresOptimizer = new ControlStructuresOptimizer;
94
			$this->optimizer = new Optimizer;
95
		}
96
		$this->useMultibyteStringFunctions = extension_loaded('mbstring');
97
		$this->serializer = new Serializer;
98
	}
99
100
	/**
101
	* {@inheritdoc}
102
	*/
103
	public function getRenderer(Rendering $rendering)
104
	{
105
		// Generate the source file
106
		$php = $this->generate($rendering);
107
108
		// Save the file if applicable
109
		if (isset($this->filepath))
110
		{
111
			$filepath = $this->filepath;
112
		}
113
		else
114
		{
115
			$filepath = $this->cacheDir . '/' . str_replace('\\', '_', $this->lastClassName) . '.php';
116
		}
117
118
		file_put_contents($filepath, "<?php\n" . $php);
119
		$this->lastFilepath = realpath($filepath);
120
121
		// Execute the source to create the class if it doesn't exist
122
		if (!class_exists($this->lastClassName, false))
123
		{
124
			include $filepath;
125
		}
126
127
		return new $this->lastClassName;
128
	}
129
130
	/**
131
	* Generate the source for a PHP class that renders an intermediate representation according to
132
	* given rendering configuration
133
	*
134
	* @param  Rendering $rendering
135
	* @return string
136
	*/
137
	public function generate(Rendering $rendering)
138
	{
139
		// Copy some options to the serializer
140
		$this->serializer->useMultibyteStringFunctions = $this->useMultibyteStringFunctions;
141
142
		// Compile the templates to PHP
143
		$compiledTemplates = array_map([$this, 'compileTemplate'], $rendering->getTemplates());
144
145
		// Start the code right after the class name, we'll prepend the header when we're done
146
		$php = [];
147
		$php[] = ' extends \\s9e\\TextFormatter\\Renderers\\PHP';
148
		$php[] = '{';
149
		$php[] = '	protected $params=' . self::export($rendering->getAllParameters()) . ';';
150
		$php[] = '	protected function renderNode(\\DOMNode $node)';
151
		$php[] = '	{';
152
		$php[] = '		' . SwitchStatement::generate('$node->nodeName', $compiledTemplates, '$this->at($node);');
153
		$php[] = '	}';
154
155
		// Append the Quick renderer if applicable
156
		if ($this->enableQuickRenderer)
157
		{
158
			$php[] = Quick::getSource($compiledTemplates);
159
		}
160
161
		// Close the class definition
162
		$php[] = '}';
163
164
		// Assemble the source
165
		$php = implode("\n", $php);
166
167
		// Finally, optimize the control structures
168
		if (isset($this->controlStructuresOptimizer))
169
		{
170
			$php = $this->controlStructuresOptimizer->optimize($php);
171
		}
172
173
		// Generate a name for that class if necessary, and save it
174
		$className = (isset($this->className))
175
		           ? $this->className
176
		           : $this->defaultClassPrefix . sha1($php);
177
		$this->lastClassName = $className;
178
179
		// Prepare the header
180
		$header = "\n"
181
		        . "/**\n"
182
		        . "* @package   s9e\TextFormatter\n"
183
		        . "* @copyright Copyright (c) 2010-2017 The s9e Authors\n"
184
		        . "* @license   http://www.opensource.org/licenses/mit-license.php The MIT License\n"
185
		        . "*/\n";
186
187
		// Declare the namespace and class name
188
		$pos = strrpos($className, '\\');
189
		if ($pos !== false)
190
		{
191
			$header .= 'namespace ' . substr($className, 0, $pos) . ";\n\n";
192
			$className = substr($className, 1 + $pos);
193
		}
194
195
		// Prepend the header and the class name
196
		$php = $header . 'class ' . $className . $php;
197
198
		return $php;
199
	}
200
201
	/**
202
	* Export given array as PHP code
203
	*
204
	* @param  array  $value Original value
205
	* @return string        PHP code
206
	*/
207
	protected static function export(array $value)
208
	{
209
		$pairs = [];
210
		foreach ($value as $k => $v)
211
		{
212
			$pairs[] = var_export($k, true) . '=>' . var_export($v, true);
213
		}
214
215
		return '[' . implode(',', $pairs) . ']';
216
	}
217
218
	/**
219
	* Compile a template to PHP
220
	*
221
	* @param  string $template Original template
222
	* @return string           Compiled template
223
	*/
224
	protected function compileTemplate($template)
225
	{
226
		// Parse the template
227
		$ir = TemplateParser::parse($template);
228
229
		// Serialize the representation to PHP
230
		$php = $this->serializer->serialize($ir->documentElement);
231
		if (isset($this->optimizer))
232
		{
233
			$php = $this->optimizer->optimize($php);
234
		}
235
236
		return $php;
237
	}
238
}