Completed
Push — master ( 0a4eb9...50d1bf )
by Josh
12:17 queued 02:54
created

PHP   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 2

Test Coverage

Coverage 95.89%

Importance

Changes 0
Metric Value
wmc 28
lcom 3
cbo 2
dl 0
loc 289
ccs 70
cts 73
cp 0.9589
rs 10
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
renderNode() 0 1 ?
A __sleep() 0 4 1
A at() 0 16 4
A canQuickRender() 0 4 2
A getParamAsXPath() 0 4 2
A getQuickTextContent() 0 4 1
A hasNonNullValues() 0 12 3
A matchAttributes() 0 12 2
A renderQuick() 0 16 1
B renderQuickCallback() 0 23 6
A renderQuickSelfClosingTag() 0 13 1
A renderQuickTemplate() 0 4 1
A renderRichText() 0 23 3
A reset() 0 6 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\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[] List 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 array Branching table used by the Quick renderer [id => index]
40
	*/
41
	protected $quickBranches;
42
43
	/**
44
	* @var string Regexp that matches XML elements to be rendered by the quick renderer
45
	*/
46
	protected $quickRegexp = '((?!))';
47
48
	/**
49
	* @var string Regexp that matches nodes that SHOULD NOT be rendered by the quick renderer
50
	*/
51
	protected $quickRenderingTest = '(<[!?])';
52
53
	/**
54
	* @var array Dictionary of static replacements used by the Quick renderer [id => replacement]
55
	*/
56
	protected $static;
57
58
	/**
59
	* @var DOMXPath XPath object used to query the document being rendered
60
	*/
61
	protected $xpath;
62
63
	/**
64
	* Render given DOMNode
65
	*
66
	* @param  DOMNode $node
67
	* @return void
68
	*/
69
	abstract protected function renderNode(DOMNode $node);
70
71 1
	public function __sleep()
72
	{
73 1
		return ['enableQuickRenderer', 'params'];
74
	}
75
76
	/**
77
	* Render the content of given node
78
	*
79
	* Matches the behaviour of an xsl:apply-templates element
80
	*
81
	* @param  DOMNode $root  Context node
82
	* @param  string  $query XPath query used to filter which child nodes to render
83
	* @return void
84
	*/
85 348
	protected function at(DOMNode $root, $query = null)
86
	{
87 348
		if ($root->nodeType === XML_TEXT_NODE)
88
		{
89
			// Text nodes are outputted directly
90 189
			$this->out .= htmlspecialchars($root->textContent,0);
91
		}
92
		else
93
		{
94 348
			$nodes = (isset($query)) ? $this->xpath->query($query, $root) : $root->childNodes;
95 348
			foreach ($nodes as $node)
96
			{
97 348
				$this->renderNode($node);
98
			}
99
		}
100 348
	}
101
102
	/**
103
	* Test whether given XML can be rendered with the Quick renderer
104
	*
105
	* @param  string $xml
106
	* @return bool
107
	*/
108 762
	protected function canQuickRender($xml)
109
	{
110 762
		return ($this->enableQuickRenderer && !preg_match($this->quickRenderingTest, $xml));
111
	}
112
113
	/**
114
	* Return a parameter's value as an XPath expression
115
	*
116
	* @param  string $paramName
117
	* @return string
118
	*/
119 2
	protected function getParamAsXPath($paramName)
120
	{
121 2
		return (isset($this->params[$paramName])) ? XPath::export($this->params[$paramName]) : "''";
122
	}
123
124
	/**
125
	* Extract the text content from given XML
126
	*
127
	* NOTE: numeric character entities are decoded beforehand, we don't need to decode them here
128
	*
129
	* @param  string $xml Original XML
130
	* @return string      Text content, with special characters decoded
131
	*/
132 24
	protected function getQuickTextContent($xml)
133
	{
134 24
		return htmlspecialchars_decode(strip_tags($xml));
135
	}
136
137
	/**
138
	* Test whether given array has any non-null values
139
	*
140
	* @param  array $array
141
	* @return bool
142
	*/
143 4
	protected function hasNonNullValues(array $array)
144
	{
145 4
		foreach ($array as $v)
146
		{
147 3
			if (isset($v))
148
			{
149 3
				return true;
150
			}
151
		}
152
153 2
		return false;
154
	}
155
156
	/**
157
	* Capture and return the attributes of an XML element
158
	*
159
	* NOTE: XML character entities are left as-is
160
	*
161
	* @param  string $xml Element in XML form
162
	* @return array       Dictionary of [attrName => attrValue]
163
	*/
164 245
	protected function matchAttributes($xml)
165
	{
166 245
		if (strpos($xml, '="') === false)
167
		{
168 66
			return [];
169
		}
170
171
		// Match all name-value pairs until the first right bracket
172 193
		preg_match_all('(([^ =]++)="([^"]*))S', substr($xml, 0, strpos($xml, '>')), $m);
173
174 193
		return array_combine($m[1], $m[2]);
175
	}
176
177
	/**
178
	* Render an intermediate representation using the Quick renderer
179
	*
180
	* @param  string $xml Intermediate representation
181
	* @return string
182
	*/
183 408
	protected function renderQuick($xml)
184
	{
185 408
		$this->attributes = [];
186 408
		$xml = $this->decodeSMP($xml);
187 408
		$html = preg_replace_callback(
188 408
			$this->quickRegexp,
189 408
			[$this, 'renderQuickCallback'],
190 408
			preg_replace(
191 408
				'(<[eis]>[^<]*</[eis]>)',
192 408
				'',
193 408
				substr($xml, 1 + strpos($xml, '>'), -4)
194
			)
195
		);
196
197 408
		return str_replace('<br/>', '<br>', $html);
198
	}
199
200
	/**
201
	* Render a string matched by the Quick renderer
202
	*
203
	* This stub should be overwritten by generated renderers
204
	*
205
	* @param  string[] $m
206
	* @return string
207
	*/
208 404
	protected function renderQuickCallback(array $m)
209
	{
210 404
		if (isset($m[3]))
211
		{
212 62
			return $this->renderQuickSelfClosingTag($m);
213
		}
214
215 404
		$id = (isset($m[2])) ? $m[2] : $m[1];
216 404
		if (isset($this->static[$id]))
217
		{
218 166
			return $this->static[$id];
219
		}
220 347
		if (isset($this->dynamic[$id]))
221
		{
222 94
			return preg_replace($this->dynamic[$id][0], $this->dynamic[$id][1], $m[0], 1);
223
		}
224 262
		if (isset($this->quickBranches[$id]))
225
		{
226 245
			return $this->renderQuickTemplate($this->quickBranches[$id], $m[0]);
227
		}
228
229 61
		return '';
230
	}
231
232
	/**
233
	* Render a self-closing tag using the Quick renderer
234
	*
235
	* @param  string[] $m
236
	* @return string
237
	*/
238 62
	protected function renderQuickSelfClosingTag(array $m)
239
	{
240 62
		unset($m[3]);
241
242 62
		$m[0] = substr($m[0], 0, -2) . '>';
243 62
		$html = $this->renderQuickCallback($m);
244
245 62
		$m[0] = '</' . $m[2] . '>';
246 62
		$m[2] = '/' . $m[2];
247 62
		$html .= $this->renderQuickCallback($m);
248
249 62
		return $html;
250
	}
251
252
	/**
253
	* Render a string matched by the Quick renderer using a generated PHP template
254
	*
255
	* This stub should be overwritten by generated renderers
256
	*
257
	* @param  integer $qb  Template's index in the quick branch table
258
	* @param  string  $xml
259
	* @return string
260
	*/
261
	protected function renderQuickTemplate($qb, $xml)
262
	{
263
		throw new RuntimeException('Not implemented');
264
	}
265
266
	/**
267
	* {@inheritdoc}
268
	*/
269 762
	protected function renderRichText($xml)
270
	{
271
		try
272
		{
273 762
			if ($this->canQuickRender($xml))
274
			{
275 762
				return $this->renderQuick($xml);
276
			}
277
		}
278
		catch (RuntimeException $e)
279
		{
280
			// Do nothing
281
		}
282
283 354
		$dom         = $this->loadXML($xml);
284 348
		$this->out   = '';
285 348
		$this->xpath = new DOMXPath($dom);
286 348
		$this->at($dom->documentElement);
287 348
		$html        = $this->out;
288 348
		$this->reset();
289
290 348
		return $html;
291
	}
292
293
	/**
294
	* Reset object properties that are populated during rendering
295
	*
296
	* @return void
297
	*/
298 348
	protected function reset()
299
	{
300 348
		unset($this->attributes);
301 348
		unset($this->out);
302 348
		unset($this->xpath);
303
	}
304
}