Completed
Branch PHPRenderer (6c13a8)
by Josh
10:40
created

PHP::renderQuickCallback()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.5906
c 0
b 0
f 0
ccs 11
cts 11
cp 1
cc 6
eloc 11
nc 9
nop 1
crap 6
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\Renderers;
9
10
use DOMNode;
11
use DOMXPath;
12
use RuntimeException;
13
use s9e\TextFormatter\Renderer;
14
15
abstract class PHP extends Renderer
16
{
17
	/**
18
	* @var array[] List of dictionaries used by the Quick renderer [[attrName => attrValue]]
19
	*/
20
	protected $attributes;
21
22
	/**
23
	* @var array Dictionary of replacements used by the Quick renderer [id => [match, replace]]
24
	*/
25
	protected $dynamic;
26
27
	/**
28
	* @var bool Whether to enable the Quick renderer
29
	*/
30
	public $enableQuickRenderer = false;
31
32
	/**
33
	* @var string Renderer's output
34
	*/
35
	protected $out;
36
37
	/**
38
	* @var array Branching table used by the Quick renderer [id => index]
39
	*/
40
	protected $quickBranches;
41
42
	/**
43
	* @var string Regexp that matches XML elements to be rendered by the quick renderer
44
	*/
45
	protected $quickRegexp = '((?!))';
46
47
	/**
48
	* @var string Regexp that matches nodes that SHOULD NOT be rendered by the quick renderer
49
	*/
50
	protected $quickRenderingTest = '(<[!?])';
51
52
	/**
53
	* @var array Dictionary of static replacements used by the Quick renderer [id => replacement]
54
	*/
55
	protected $static;
56
57
	/**
58
	* @var DOMXPath XPath object used to query the document being rendered
59
	*/
60
	protected $xpath;
61
62
	/**
63
	* Render given DOMNode
64
	*
65
	* @param  DOMNode $node
66
	* @return void
67
	*/
68
	abstract protected function renderNode(DOMNode $node);
69
70 1
	public function __sleep()
71
	{
72 1
		$this->reset();
73 1
		$props = get_object_vars($this);
74 1
		unset($props['dynamic']);
75 1
		unset($props['metaElementsRegexp']);
76 1
		unset($props['quickBranches']);
77 1
		unset($props['quickRegexp']);
78 1
		unset($props['quickRenderingTest']);
79 1
		unset($props['static']);
80
81 1
		return array_keys($props);
82
	}
83
84
	/**
85
	* Render the content of given node
86
	*
87
	* Matches the behaviour of an xsl:apply-templates element
88
	*
89
	* @param  DOMNode $root  Context node
90
	* @param  string  $query XPath query used to filter which child nodes to render
91
	* @return void
92
	*/
93 346
	protected function at(DOMNode $root, $query = null)
94
	{
95 346
		if ($root->nodeType === XML_TEXT_NODE)
96
		{
97
			// Text nodes are outputted directly
98 189
			$this->out .= htmlspecialchars($root->textContent,0);
99
		}
100
		else
101
		{
102 346
			$nodes = (isset($query)) ? $this->xpath->query($query, $root) : $root->childNodes;
103 346
			foreach ($nodes as $node)
104
			{
105 346
				$this->renderNode($node);
106
			}
107
		}
108 346
	}
109
110
	/**
111
	* Test whether given XML can be rendered with the Quick renderer
112
	*
113
	* @param  string $xml
114
	* @return bool
115
	*/
116 756
	protected function canQuickRender($xml)
117
	{
118 756
		return ($this->enableQuickRenderer && !preg_match($this->quickRenderingTest, $xml));
119
	}
120
121
	/**
122
	* Extract the text content from given XML
123
	*
124
	* NOTE: numeric character entities are decoded beforehand, we don't need to decode them here
125
	*
126
	* @param  string $xml Original XML
127
	* @return string      Text content, with special characters decoded
128
	*/
129 24
	protected function getQuickTextContent($xml)
130
	{
131 24
		return htmlspecialchars_decode(strip_tags($xml));
132
	}
133
134
	/**
135
	* Test whether given array has any non-null values
136
	*
137
	* @param  array $array
138
	* @return bool
139
	*/
140 4
	protected function hasNonNullValues(array $array)
141
	{
142 4
		foreach ($array as $v)
143
		{
144 3
			if (isset($v))
145
			{
146 3
				return true;
147
			}
148
		}
149
150 2
		return false;
151
	}
152
153
	/**
154
	* Capture and return the attributes of an XML element
155
	*
156
	* NOTE: XML character entities are left as-is
157
	*
158
	* @param  string $xml Element in XML form
159
	* @return array       Dictionary of [attrName => attrValue]
160
	*/
161 245
	protected function matchAttributes($xml)
162
	{
163 245
		if (strpos($xml, '="') === false)
164
		{
165 66
			return [];
166
		}
167
168
		// Match all name-value pairs until the first right bracket
169 193
		preg_match_all('(([^ =]++)="([^"]*))S', substr($xml, 0, strpos($xml, '>')), $m);
170
171 193
		return array_combine($m[1], $m[2]);
172
	}
173
174
	/**
175
	* Render an intermediate representation using the Quick renderer
176
	*
177
	* @param  string $xml Intermediate representation
178
	* @return void
179
	*/
180 406
	protected function renderQuick($xml)
181
	{
182 406
		$this->attributes = [];
183 406
		$xml = $this->decodeSMP($xml);
184 406
		$html = preg_replace_callback(
185 406
			$this->quickRegexp,
186 406
			[$this, 'renderQuickCallback'],
187 406
			preg_replace(
188 406
				'(<[eis]>[^<]*</[eis]>)',
189 406
				'',
190 406
				substr($xml, 1 + strpos($xml, '>'), -4)
191
			)
192
		);
193
194 406
		return str_replace('<br/>', '<br>', $html);
195
	}
196
197
	/**
198
	* Render a string matched by the Quick renderer
199
	*
200
	* This stub should be overwritten by generated renderers
201
	*
202
	* @param  string[] $m
203
	* @return void
204
	*/
205 402
	protected function renderQuickCallback(array $m)
206
	{
207 402
		if (isset($m[3]))
208
		{
209 61
			return $this->renderQuickSelfClosingTag($m);
210
		}
211
212 402
		$id = (isset($m[2])) ? $m[2] : $m[1];
213 402
		if (isset($this->static[$id]))
214
		{
215 166
			return $this->static[$id];
216
		}
217 345
		if (isset($this->dynamic[$id]))
218
		{
219 94
			return preg_replace($this->dynamic[$id][0], $this->dynamic[$id][1], $m[0], 1);
220
		}
221 260
		if (isset($this->quickBranches[$id]))
222
		{
223 245
			return $this->renderQuickTemplate($this->quickBranches[$id], $m[0]);
224
		}
225
226 59
		return '';
227
	}
228
229
	/**
230
	* Render a self-closing tag using the Quick renderer
231
	*
232
	* @param  string[] $m
233
	* @return string
234
	*/
235 61
	protected function renderQuickSelfClosingTag(array $m)
236
	{
237 61
		unset($m[3]);
238
239 61
		$m[0] = substr($m[0], 0, -2) . '>';
240 61
		$html = $this->renderQuickCallback($m);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $html is correct as $this->renderQuickCallback($m) (which targets s9e\TextFormatter\Render...::renderQuickCallback()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
241
242 61
		$m[0] = '</' . $m[2] . '>';
243 61
		$m[2] = '/' . $m[2];
244 61
		$html .= $this->renderQuickCallback($m);
245
246 61
		return $html;
247
	}
248
249
	/**
250
	* Render a string matched by the Quick renderer using a generated PHP template
251
	*
252
	* This stub should be overwritten by generated renderers
253
	*
254
	* @param  integer $qb  Template's index in the quick branch table
255
	* @param  string  $xml
256
	* @return string
257
	*/
258
	protected function renderQuickTemplate($qb, $xml)
259
	{
260
		throw new RuntimeException('Not implemented');
261
	}
262
263
	/**
264
	* {@inheritdoc}
265
	*/
266 756
	protected function renderRichText($xml)
267
	{
268
		try
269
		{
270 756
			if ($this->canQuickRender($xml))
271
			{
272 756
				return $this->renderQuick($xml);
273
			}
274
		}
275
		catch (RuntimeException $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
276
		{
277
		}
278
279 350
		$dom         = $this->loadXML($xml);
280 346
		$this->out   = '';
281 346
		$this->xpath = new DOMXPath($dom);
282 346
		$this->at($dom->documentElement);
283 346
		$html        = $this->out;
284 346
		$this->reset();
285
286 346
		return $html;
287
	}
288
289
	/**
290
	* Reset object properties that are populated during rendering
291
	*
292
	* @return void
293
	*/
294 347
	protected function reset()
295
	{
296 347
		unset($this->attributes);
297 347
		unset($this->out);
298 347
		unset($this->xpath);
299
	}
300
}