1
|
|
|
<?php |
2
|
|
|
namespace Agavi\Renderer; |
3
|
|
|
|
4
|
|
|
// +---------------------------------------------------------------------------+ |
5
|
|
|
// | This file is part of the Agavi package. | |
6
|
|
|
// | Copyright (c) 2005-2011 the Agavi Project. | |
7
|
|
|
// | | |
8
|
|
|
// | For the full copyright and license information, please view the LICENSE | |
9
|
|
|
// | file that was distributed with this source code. You can also view the | |
10
|
|
|
// | LICENSE file online at http://www.agavi.org/LICENSE.txt | |
11
|
|
|
// | vi: set noexpandtab: | |
12
|
|
|
// | Local Variables: | |
13
|
|
|
// | indent-tabs-mode: t | |
14
|
|
|
// | End: | |
15
|
|
|
// +---------------------------------------------------------------------------+ |
16
|
|
|
use Agavi\Exception\RenderException; |
17
|
|
|
use Agavi\Util\ArrayPathDefinition; |
18
|
|
|
use Agavi\View\TemplateLayer; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* XsltRenderer uses an XML Stylesheet Language Template to render the |
22
|
|
|
* given input (an XML document in $inner). |
23
|
|
|
* |
24
|
|
|
* @package agavi |
25
|
|
|
* @subpackage renderer |
26
|
|
|
* |
27
|
|
|
* @author David Zülke <[email protected]> |
28
|
|
|
* @copyright Authors |
29
|
|
|
* @copyright The Agavi Project |
30
|
|
|
* |
31
|
|
|
* @since 1.0.4 |
32
|
|
|
* |
33
|
|
|
* @version $Id$ |
34
|
|
|
*/ |
35
|
|
|
class XsltRenderer extends Renderer implements ReusableRendererInterface |
36
|
|
|
{ |
37
|
|
|
const ENVELOPE_XMLNS = 'http://agavi.org/agavi/renderer/xslt/envelope/1.0'; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var string A string with the default template file extension, |
41
|
|
|
* including the dot. |
42
|
|
|
*/ |
43
|
|
|
protected $defaultExtension = '.xsl'; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Load an XML document from a string, return a DOMDocument and return errors |
47
|
|
|
* in case something went wrong. |
48
|
|
|
* |
49
|
|
|
* @param string $source The XML source to load. |
50
|
|
|
* @param int $options libxml option flags for loading. |
51
|
|
|
* |
52
|
|
|
* @return \DOMDocument The parsed XML document. |
53
|
|
|
* |
54
|
|
|
* @author David Zülke <[email protected]> |
55
|
|
|
* @since 1.0.4 |
56
|
|
|
*/ |
57
|
|
View Code Duplication |
protected function loadDomDocumentXml($source, $options = 0) |
|
|
|
|
58
|
|
|
{ |
59
|
|
|
$luie = libxml_use_internal_errors(true); |
60
|
|
|
libxml_clear_errors(); |
61
|
|
|
|
62
|
|
|
$result = new \DOMDocument(); |
63
|
|
|
$loaded = @$result->loadXML($source, $options); |
64
|
|
|
|
65
|
|
|
if (libxml_get_last_error() !== false || !$loaded) { |
66
|
|
|
$errors = array(); |
67
|
|
|
foreach (libxml_get_errors() as $error) { |
68
|
|
|
$errors[] = sprintf('[%s #%d] Line %d: %s', $error->level == LIBXML_ERR_WARNING ? 'Warning' : ($error->level == LIBXML_ERR_ERROR ? 'Error' : 'Fatal'), $error->code, $error->line, $error->message); |
69
|
|
|
} |
70
|
|
|
libxml_clear_errors(); |
71
|
|
|
libxml_use_internal_errors($luie); |
72
|
|
|
|
73
|
|
|
if (!$errors) { |
|
|
|
|
74
|
|
|
$errors = array('Unknown error (document empty?)'); |
75
|
|
|
} |
76
|
|
|
throw new \DOMException( |
77
|
|
|
sprintf( |
78
|
|
|
'Error%s occurred while parsing the document: ' . "\n\n%s", |
79
|
|
|
count($errors) > 1 ? 's' : '', |
80
|
|
|
implode("\n", $errors) |
81
|
|
|
) |
82
|
|
|
); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
libxml_use_internal_errors($luie); |
86
|
|
|
|
87
|
|
|
return $result; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Load an XML document from a file, return a DOMDocument and return errors in |
92
|
|
|
* case something went wrong. |
93
|
|
|
* |
94
|
|
|
* @param string $source The XML source to load. |
95
|
|
|
* @param int $options libxml option flags for loading. |
96
|
|
|
* |
97
|
|
|
* @return \DOMDocument The parsed XML document. |
98
|
|
|
* |
99
|
|
|
* @author David Zülke <[email protected]> |
100
|
|
|
* @since 1.0.4 |
101
|
|
|
*/ |
102
|
|
View Code Duplication |
protected function loadDomDocument($source, $options = 0) |
|
|
|
|
103
|
|
|
{ |
104
|
|
|
$luie = libxml_use_internal_errors(true); |
105
|
|
|
libxml_clear_errors(); |
106
|
|
|
|
107
|
|
|
$result = new \DOMDocument(); |
108
|
|
|
$result->load($source, $options); |
109
|
|
|
|
110
|
|
|
if (libxml_get_last_error() !== false) { |
111
|
|
|
$errors = array(); |
112
|
|
|
foreach (libxml_get_errors() as $error) { |
113
|
|
|
$errors[] = sprintf('[%s #%d] Line %d: %s', $error->level == LIBXML_ERR_WARNING ? 'Warning' : ($error->level == LIBXML_ERR_ERROR ? 'Error' : 'Fatal'), $error->code, $error->line, $error->message); |
114
|
|
|
} |
115
|
|
|
libxml_clear_errors(); |
116
|
|
|
libxml_use_internal_errors($luie); |
117
|
|
|
|
118
|
|
|
if (!$errors) { |
|
|
|
|
119
|
|
|
$errors = array('Unknown error (document empty?)'); |
120
|
|
|
} |
121
|
|
|
throw new \DOMException( |
122
|
|
|
sprintf( |
123
|
|
|
'Error%s occurred while parsing the document: ' . "\n\n%s", |
124
|
|
|
count($errors) > 1 ? 's' : '', |
125
|
|
|
implode("\n", $errors) |
126
|
|
|
) |
127
|
|
|
); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
libxml_use_internal_errors($luie); |
131
|
|
|
|
132
|
|
|
return $result; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Render the presentation and return the result. |
137
|
|
|
* |
138
|
|
|
* @param TemplateLayer $layer The template layer to render. |
139
|
|
|
* @param array $attributes The template variables. |
140
|
|
|
* @param array $slots The slots. |
141
|
|
|
* @param array $moreAssigns Associative array of additional assigns. |
142
|
|
|
* |
143
|
|
|
* @return string A rendered result. |
144
|
|
|
* |
145
|
|
|
* @author David Zülke <[email protected]> |
146
|
|
|
* @since 1.1.0 |
147
|
|
|
*/ |
148
|
|
|
public function render(TemplateLayer $layer, array &$attributes = array(), array &$slots = array(), array &$moreAssigns = array()) |
149
|
|
|
{ |
150
|
|
|
if ($this->getParameter('envelope', true)) { |
151
|
|
|
if (!($moreAssigns['inner'] instanceof \DOMDocument)) { |
152
|
|
|
// plain text, load it as a document |
153
|
|
|
try { |
154
|
|
|
$inner = $this->loadDomDocumentXml($moreAssigns['inner']); |
155
|
|
|
} catch (\DOMException $e) { |
156
|
|
|
throw new RenderException(sprintf("Unable to load input document for layer '%s'.\n\n%s", $layer->getName(), $e->getMessage()), 0, $e); |
|
|
|
|
157
|
|
|
} |
158
|
|
|
} else { |
159
|
|
|
$inner = $moreAssigns['inner']; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
// construct envelope |
163
|
|
|
$doc = new \DOMDocument(); |
164
|
|
|
$doc->appendChild($doc->createElementNS(self::ENVELOPE_XMLNS, 'envelope')); |
165
|
|
|
|
166
|
|
|
// inner content container |
167
|
|
|
$doc->documentElement->appendChild($innerWrapper = $doc->createElementNS(self::ENVELOPE_XMLNS, 'inner')); |
168
|
|
|
$innerWrapper->appendChild($doc->importNode($inner->documentElement, true)); |
169
|
|
|
|
170
|
|
|
// slots container |
171
|
|
|
$slotsWrapper = $doc->createElementNS(self::ENVELOPE_XMLNS, 'slots'); |
172
|
|
|
$doc->documentElement->appendChild($slotsWrapper); |
173
|
|
|
|
174
|
|
|
// flatten slots, iterate and wrap them each |
175
|
|
|
$flattenedSlots = ArrayPathDefinition::flatten($slots); |
176
|
|
|
foreach ($flattenedSlots as $slotName => $slotContent) { |
177
|
|
|
if (!($slotContent instanceof \DOMDocument)) { |
178
|
|
|
try { |
179
|
|
|
$slot = $this->loadDomDocumentXml($slotContent); |
180
|
|
|
} catch (\Exception $e) { |
181
|
|
|
throw new RenderException(sprintf("Unable to load contents for slot '%s'.\n\n%s", $slotName, $e->getMessage()), 0, $e); |
182
|
|
|
} |
183
|
|
|
} else { |
184
|
|
|
$slot = $slotContent; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
$slotWrapper = $doc->createElementNS(self::ENVELOPE_XMLNS, 'slot'); |
188
|
|
|
$slotWrapper->setAttribute('name', $slotName); |
189
|
|
|
$slotWrapper->appendChild($doc->importNode($slot->documentElement, true)); |
190
|
|
|
|
191
|
|
|
$slotsWrapper->appendChild($slotWrapper); |
192
|
|
|
} |
193
|
|
|
} else { |
194
|
|
|
if (!($moreAssigns['inner'] instanceof \DOMDocument)) { |
195
|
|
|
// plain text, load it as a document |
196
|
|
|
$doc = $this->loadDomDocumentXml($moreAssigns['inner']); |
197
|
|
|
} else { |
198
|
|
|
$doc = $moreAssigns['inner']; |
199
|
|
|
} |
200
|
|
|
// This will pretty much never work, so we're not doing it. Users must enable the envelope feature to use slots. |
201
|
|
|
// Warning: XSLTProcessor::transformToXml() [xsltprocessor.transformtoxml]: Cannot create XPath expression (string contains both quote and double-quotes) |
202
|
|
|
// $flattenedSlots = AgaviArrayPathDefinition::flatten($slots); |
|
|
|
|
203
|
|
|
// foreach($flattenedSlots as $slotName => $slotContent) { |
|
|
|
|
204
|
|
|
// if($slotContent instanceof DOMDocument) { |
|
|
|
|
205
|
|
|
// $slotContent = $slotContent->saveXML(); |
|
|
|
|
206
|
|
|
// } |
207
|
|
|
// $xsl->setParameter('', 'slot:' . $slotName, addslashes($slotContent)); |
|
|
|
|
208
|
|
|
// } |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
try { |
212
|
|
|
$xslt = $this->loadDomDocument($layer->getResourceStreamIdentifier()); |
213
|
|
|
} catch (\DOMException $e) { |
214
|
|
|
throw new RenderException(sprintf("Unable to load template '%s'.\n\n%s", $layer->getResourceStreamIdentifier(), $e->getMessage()), 0, $e); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
$xsl = new \XSLTProcessor(); |
218
|
|
|
$xsl->importStylesheet($xslt); |
219
|
|
|
foreach ($attributes as $name => $attribute) { |
220
|
|
|
if (is_scalar($attribute) || (is_object($attribute) && method_exists($attribute, '__toString'))) { |
221
|
|
|
$xsl->setParameter('', $name, $attribute); |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
return $xsl->transformToXML($doc); |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.