Completed
Push — master ( d86447...92f61d )
by Josh
19:37
created

XSLT::normalizeElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 9.6666
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2016 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 XSLTProcessor;
11
use s9e\TextFormatter\Renderer;
12
13
class XSLT extends Renderer
14
{
15
	/**
16
	* @var XSLTProcessor The lazy-loaded XSLTProcessor instance used by this renderer
17
	*/
18
	protected $proc;
19
20
	/**
21
	* @var bool Whether parameters need to be reloaded
22
	*/
23
	protected $reloadParams = false;
24
25
	/**
26
	* @var string The stylesheet used by this renderer
27
	*/
28
	protected $stylesheet;
29
30
	/**
31
	* Constructor
32
	*
33
	* @param  string $stylesheet The stylesheet used to render intermediate representations
34
	*/
35 28
	public function __construct($stylesheet)
36
	{
37 28
		$this->stylesheet = $stylesheet;
38
39
		// Capture the parameters' values from the stylesheet
40 28
		preg_match_all('#<xsl:param name="([^"]+)"(?>/>|>([^<]+))#', $stylesheet, $matches);
41 28
		foreach ($matches[1] as $k => $paramName)
42
		{
43 7
			$this->params[$paramName] = (isset($matches[2][$k]))
44 7
			                          ? htmlspecialchars_decode($matches[2][$k])
45 7
			                          : '';
46 28
		}
47 28
	}
48
49
	/**
50
	* Serializer
51
	*
52
	* @return string[] List of properties to serialize
53
	*/
54 4
	public function __sleep()
55
	{
56 4
		$props = get_object_vars($this);
57 4
		unset($props['proc']);
58
59 4
		if (empty($props['reloadParams']))
60 4
		{
61 3
			unset($props['reloadParams']);
62 3
		}
63
64 4
		return array_keys($props);
65
	}
66
67
	/**
68
	* Unserialize helper
69
	*
70
	* Will reload parameters if they were changed between generation and serialization
71
	*
72
	* @return void
73
	*/
74 3
	public function __wakeup()
75
	{
76 3
		if (!empty($this->reloadParams))
77 3
		{
78 1
			$this->setParameters($this->params);
79 1
			$this->reloadParams = false;
80 1
		}
81 3
	}
82
83
	/**
84
	* {@inheritdoc}
85
	*/
86 6
	public function setParameter($paramName, $paramValue)
87
	{
88
		/**
89
		* @link https://bugs.php.net/64137
90
		*/
91 6
		if (strpos($paramValue, '"') !== false && strpos($paramValue, "'") !== false)
92 6
		{
93 1
			$paramValue = str_replace('"', "\xEF\xBC\x82", $paramValue);
94 1
		}
95
		else
96
		{
97 5
			$paramValue = (string) $paramValue;
98
		}
99
100 6
		if (!isset($this->params[$paramName]) || $this->params[$paramName] !== $paramValue)
101 6
		{
102 6
			$this->load();
103 6
			$this->proc->setParameter('', $paramName, $paramValue);
104 6
			$this->params[$paramName] = $paramValue;
105 6
			$this->reloadParams = true;
106 6
		}
107 6
	}
108
109
	/**
110
	* {@inheritdoc}
111
	*/
112 16
	protected function renderRichText($xml)
113
	{
114
		// Load the intermediate representation
115 16
		$dom = $this->loadXML($xml);
116
117
		// Load the stylesheet
118 12
		$this->load();
119
120
		// Perform the transformation and cast it as a string because it may return NULL if the
121
		// transformation didn't output anything
122 12
		$output = (string) $this->proc->transformToXml($dom);
123
124
		// XSLTProcessor does not correctly identify <embed> as a void element. We fix it by
125
		// removing </embed> end tags
126 12
		$output = str_replace('</embed>', '', $output);
127
128
		// Remove the \n that XSL adds at the end of the output, if applicable
129 12
		if (substr($output, -1) === "\n")
130 12
		{
131 12
			$output = substr($output, 0, -1);
132 12
		}
133
134
		// Force HTML attributes to use double quotes to be consistent with the PHP renderer
135 12
		if (strpos($output, "='") !== false)
136 12
		{
137 2
			$output = $this->normalizeAttributes($output);
138 2
		}
139
140 12
		return $output;
141
	}
142
143
	/**
144
	* Create an XSLTProcessor and load the stylesheet
145
	*
146
	* @return void
147
	*/
148 15
	protected function load()
149
	{
150 15
		if (!isset($this->proc))
151 15
		{
152 15
			$xsl = $this->loadXML($this->stylesheet);
153
154 15
			$this->proc = new XSLTProcessor;
155 15
			$this->proc->importStylesheet($xsl);
156 15
		}
157 15
	}
158
159
	/**
160
	* Normalize given attribute's value to use double quotes
161
	*
162
	* @param  string[] $m
163
	* @return string
164
	*/
165 2
	protected function normalizeAttribute(array $m)
166
	{
167 2
		if ($m[0][0] === '"')
168 2
		{
169 2
			return $m[0];
170
		}
171
172 1
		return '"' . str_replace('"', '&quot;', substr($m[0], 1, -1)) . '"';
173
	}
174
175
	/**
176
	* Normalize all attributes in given HTML to use double quotes
177
	*
178
	* @param  string $html
179
	* @return string
180
	*/
181 2
	protected function normalizeAttributes($html)
182
	{
183 2
		return preg_replace_callback('(<\\S++ [^>]++>)', [$this, 'normalizeElement'], $html);
184
	}
185
186
	/**
187
	* Normalize attributes in given element to use double quotes
188
	*
189
	* @param  string[] $m
190
	* @return string
191
	*/
192 2
	protected function normalizeElement(array $m)
193
	{
194 2
		if (strpos($m[0], "='") === false)
195 2
		{
196 1
			return $m[0];
197
		}
198
199 2
		return preg_replace_callback('((?:"[^"]*"|\'[^\']*\'))S', [$this, 'normalizeAttribute'], $m[0]);
200
	}
201
}