Passed
Push — master ( 7aef5f...5de379 )
by Josh
02:35
created

PHP::__serialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) The s9e authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Renderers;
9
10
use DOMNode;
11
use DOMXPath;
12
use RuntimeException;
13
use s9e\TextFormatter\Renderer;
14
use s9e\TextFormatter\Utils\XPath;
15
16
abstract class PHP extends Renderer
17
{
18
	/**
19
	* @var array[] Stack of dictionaries used by the Quick renderer [[attrName => attrValue]]
20
	*/
21
	protected $attributes;
22
23
	/**
24
	* @var array Dictionary of replacements used by the Quick renderer [id => [match, replace]]
25
	*/
26
	protected $dynamic;
27
28
	/**
29
	* @var bool Whether to enable the Quick renderer
30
	*/
31
	public $enableQuickRenderer = false;
32
33
	/**
34
	* @var string Renderer's output
35
	*/
36
	protected $out;
37
38
	/**
39
	* @var string Regexp that matches XML elements to be rendered by the quick renderer
40
	*/
41
	protected $quickRegexp = '((?!))';
42
43
	/**
44
	* @var string Regexp that matches nodes that SHOULD NOT be rendered by the quick renderer
45
	*/
46
	protected $quickRenderingTest = '((?<=<)[!?])';
47
48
	/**
49
	* @var array Dictionary of static replacements used by the Quick renderer [id => replacement]
50
	*/
51
	protected $static;
52
53
	/**
54
	* @var DOMXPath XPath object used to query the document being rendered
55
	*/
56
	protected $xpath;
57
58
	/**
59
	* Render given DOMNode
60
	*
61
	* @param  DOMNode $node
62
	* @return void
63
	*/
64
	abstract protected function renderNode(DOMNode $node);
65
66 5
	public function __serialize(): array
67
	{
68 5
		return [
69
			'enableQuickRenderer' => $this->enableQuickRenderer,
70
			'params'              => $this->params
71
		];
72
	}
73
74
	/**
75
	* Render the content of given node
76
	*
77
	* Matches the behaviour of an xsl:apply-templates element
78
	*
79
	* @param  DOMNode $root  Context node
80 382
	* @param  string  $query XPath query used to filter which child nodes to render
81
	* @return void
82 382
	*/
83
	protected function at(DOMNode $root, $query = null)
84
	{
85 198
		if ($root->nodeType === XML_TEXT_NODE)
86
		{
87
			// Text nodes are outputted directly
88
			$this->out .= htmlspecialchars($root->textContent, ENT_NOQUOTES);
89 382
		}
90 382
		else
91
		{
92 382
			$nodes = (isset($query)) ? $this->xpath->query($query, $root) : $root->childNodes;
93
			foreach ($nodes as $node)
94
			{
95
				$this->renderNode($node);
96
			}
97
		}
98
	}
99
100
	/**
101
	* Test whether given XML can be rendered with the Quick renderer
102
	*
103 815
	* @param  string $xml
104
	* @return bool
105 815
	*/
106
	protected function canQuickRender($xml)
107
	{
108
		return ($this->enableQuickRenderer && !preg_match($this->quickRenderingTest, $xml) && substr($xml, -4) === '</r>');
109
	}
110
111
	/**
112
	* Ensure that a tag pair does not contain a start tag of itself
113
	*
114
	* Detects malformed matches such as <X><X></X>
115
	*
116
	* @param  string $id
117 184
	* @param  string $xml
118
	* @return void
119 184
	*/
120
	protected function checkTagPairContent($id, $xml)
121 1
	{
122
		if (strpos($xml, '<' . $id, 1) !== false)
123
		{
124
			throw new RuntimeException;
125
		}
126
	}
127
128
	/**
129
	* Return a parameter's value as an XPath expression
130
	*
131 4
	* @param  string $paramName
132
	* @return string
133 4
	*/
134
	protected function getParamAsXPath($paramName)
135
	{
136
		return (isset($this->params[$paramName])) ? XPath::export($this->params[$paramName]) : "''";
137
	}
138
139
	/**
140
	* Extract the text content from given XML
141
	*
142
	* NOTE: numeric character entities are decoded beforehand, we don't need to decode them here
143
	*
144 10
	* @param  string $xml Original XML
145
	* @return string      Text content, with special characters decoded
146 10
	*/
147
	protected function getQuickTextContent($xml)
148
	{
149
		return htmlspecialchars_decode(strip_tags($xml));
150
	}
151
152
	/**
153
	* Test whether given array has any non-null values
154
	*
155 4
	* @param  array $array
156
	* @return bool
157 4
	*/
158
	protected function hasNonNullValues(array $array)
159 3
	{
160
		foreach ($array as $v)
161 2
		{
162
			if (isset($v))
163
			{
164
				return true;
165 2
			}
166
		}
167
168
		return false;
169
	}
170
171
	/**
172
	* Capture and return the attributes of an XML element
173
	*
174
	* NOTE: XML character entities are left as-is
175
	*
176 266
	* @param  string $xml Element in XML form
177
	* @return array       Dictionary of [attrName => attrValue]
178 266
	*/
179
	protected function matchAttributes($xml)
180 126
	{
181
		if (strpos($xml, '="') === false)
182
		{
183
			return [];
184 183
		}
185
186 183
		// Match all name-value pairs until the first right bracket
187
		preg_match_all('(([^ =]++)="([^"]*))S', substr($xml, 0, strpos($xml, '>')), $m);
188
189
		return array_combine($m[1], $m[2]);
190
	}
191
192
	/**
193
	* Render an intermediate representation using the Quick renderer
194
	*
195 423
	* @param  string $xml Intermediate representation
196
	* @return string
197 423
	*/
198 423
	protected function renderQuick($xml)
199 423
	{
200 423
		$this->attributes = [];
201 423
		$xml = $this->decodeSMP($xml);
202 423
		$html = preg_replace_callback(
203
			$this->quickRegexp,
204
			$this->renderQuickCallback(...),
205 422
			substr($xml, 1 + strpos($xml, '>'), -4)
206
		);
207
208
		return str_replace('<br/>', '<br>', $html);
209
	}
210
211
	/**
212
	* Render a string matched by the Quick renderer
213
	*
214
	* This stub should be overwritten by generated renderers
215
	*
216 413
	* @param  string[] $m
217
	* @return string
218 413
	*/
219
	protected function renderQuickCallback(array $m)
220 77
	{
221
		if (isset($m[3]))
222
		{
223 413
			return $this->renderQuickSelfClosingTag($m);
224
		}
225
226 232
		if (isset($m[2]))
227
		{
228
			// Single tag
229
			$id = $m[2];
230
		}
231 184
		else
232 184
		{
233
			// Tag pair
234
			$id = $m[1];
235 412
			$this->checkTagPairContent($id, $m[0]);
236
		}
237 179
238
		if (isset($this->static[$id]))
239 352
		{
240
			return $this->static[$id];
241 97
		}
242
		if (isset($this->dynamic[$id]))
243
		{
244 266
			return preg_replace($this->dynamic[$id][0], $this->dynamic[$id][1], $m[0], 1);
245
		}
246
247
		return $this->renderQuickTemplate($id, $m[0]);
248
	}
249
250
	/**
251
	* Render a self-closing tag using the Quick renderer
252
	*
253 77
	* @param  string[] $m
254
	* @return string
255 77
	*/
256
	protected function renderQuickSelfClosingTag(array $m)
257 77
	{
258 77
		unset($m[3]);
259
260 77
		$m[0] = substr($m[0], 0, -2) . '>';
261 77
		$html = $this->renderQuickCallback($m);
262 77
263
		$m[0] = '</' . $m[2] . '>';
264 77
		$m[2] = '/' . $m[2];
265
		$html .= $this->renderQuickCallback($m);
266
267
		return $html;
268
	}
269
270
	/**
271
	* Render a string matched by the Quick renderer using a generated PHP template
272
	*
273
	* This stub should be overwritten by generated renderers
274
	*
275
	* @param  integer $id  Tag's ID (tag name optionally preceded by a slash)
276 1
	* @param  string  $xml Tag's XML or tag pair's XML including their content
277
	* @return string       Rendered template
278 1
	*/
279
	protected function renderQuickTemplate($id, $xml)
280
	{
281
		throw new RuntimeException('Not implemented');
282
	}
283
284 815
	/**
285
	* {@inheritdoc}
286 815
	*/
287
	protected function renderRichText($xml)
288
	{
289
		$this->setLocale();
290 815
291
		try
292 423
		{
293 422
			if ($this->canQuickRender($xml))
294
			{
295 814
				$html = $this->renderQuick($xml);
296
				$this->restoreLocale();
297
298 1
				return $html;
299
			}
300
		}
301
		catch (RuntimeException $e)
302
		{
303 393
			// Do nothing
304 382
		}
305 382
306 382
		$dom         = $this->loadXML($xml);
307 382
		$this->out   = '';
308 382
		$this->xpath = new DOMXPath($dom);
309 382
		$this->at($dom->documentElement);
310
		$html        = $this->out;
311 382
		$this->reset();
312
		$this->restoreLocale();
313
314
		return $html;
315
	}
316
317
	/**
318
	* Reset object properties that are populated during rendering
319 382
	*
320
	* @return void
321 382
	*/
322 382
	protected function reset()
323 382
	{
324
		unset($this->attributes);
325
		unset($this->out);
326
		unset($this->xpath);
327
	}
328
}