4 * @file classes/xslt/XSLTransformer.inc.php
6 * Copyright (c) 2000-2009 John Willinsky
7 * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING.
9 * @class XSLTransformer
12 * @brief Wrapper class for running XSL transformations using PHP 4.x or 5.x
15 // $Id: XSLTransformer.inc.php,v 1.1 2009/04/27 19:18:30 mj Exp $
17 // The default character encoding
18 define('XSLT_PROCESSOR_ENCODING', Config
::getVar('i18n', 'client_charset'));
20 class XSLTransformer
{
22 /** @var $processor string determining the XSLT processor to use for this object */
25 /** @var $externalCommand string containing external XSLT shell command */
28 /** @var $parameters array of parameters to pass to XSL (built-in libraries only) */
31 /** @var $registerPHPFunctions array of PHP functions to allow in XSL (PHP5 built-in only) */
32 var $registerPHPFunctions;
34 /** @var $errors array List of error strings */
39 * Initialize transformer and set parser options.
40 * @return boolean returns false if no XSLT processor could be created
42 function XSLTransformer() {
43 $this->externalCommand
= Config
::getVar('general', 'xslt_command');
45 // Determine the appropriate XSLT processor for the system
46 if ($this->externalCommand
) {
47 // check the external command to check for %xsl and %xml parameter substitution
48 if ( strpos($this->externalCommand
, '%xsl') === false ) return false;
49 if ( strpos($this->externalCommand
, '%xml') === false ) return false;
50 $this->processor
= 'External';
52 } elseif ( checkPhpVersion('5.0.0') && extension_loaded('xsl') && extension_loaded('dom') ) {
53 // PHP5.x with XSL/DOM modules present
54 $this->processor
= 'PHP5';
56 } elseif ( checkPhpVersion('4.1.0') && extension_loaded('xslt') ) {
57 // PHP4.x with XSLT module present
58 $this->processor
= 'PHP4';
65 $this->errors
= array();
69 * Apply an XSLT transform to a given XML and XSL source files
70 * @param $xmlFile absolute pathname to the XML source file
71 * @param $xslFile absolute pathname to the XSL stylesheet
72 * @return string containing the transformed XML output, or false on error
74 function transformFiles($xmlFile, $xslFile) {
75 // if either XML or XSL file don't exist, then fail without trying to process XSLT
76 if (!FileManager
::fileExists($xmlFile) ||
!FileManager
::fileExists($xslFile)) return false;
78 switch ($this->processor
) {
80 return $this->_transformExternal($xmlFile, $xslFile);
82 return $this->_transformFilePHP4($xmlFile, $xslFile);
84 return $this->_transformFilePHP5($xmlFile, $xslFile);
86 // No XSLT processor available
91 * Apply an XSLT transform to a given XML and XSL strings
92 * @param $xml string containing source XML
93 * @param $xsl string containing source XSL
94 * @return string containing the transformed XML output, or false on error
96 function transformStrings($xml, $xsl) {
97 switch ($this->processor
) {
98 // TODO: External requires saving strings to temporary files
100 return $this->_transformStringPHP4($xml, $xsl);
102 return $this->_transformStringPHP5($xml, $xsl);
104 // No XSLT processor available
108 function _transformExternal($xmlFile, $xslFile) {
109 // check the external command to check for %xsl and %xml parameter substitution
110 if ( strpos($this->externalCommand
, '%xsl') === false ) return false;
111 if ( strpos($this->externalCommand
, '%xml') === false ) return false;
113 // perform %xsl and %xml replacements for fully-qualified shell command
114 $xsltCommand = str_replace(array('%xsl', '%xml'), array($xslFile, $xmlFile), $this->externalCommand
);
116 // check for safe mode and escape the shell command
117 if( !ini_get('safe_mode') ) $xsltCommand = escapeshellcmd($xsltCommand);
119 // run the shell command and get the results
120 exec($xsltCommand . ' 2>&1', $contents, $status);
122 // if there is an error state, copy result to error property
123 if ($status != false) {
125 $this->addError(implode("\n", $contents));
127 // completed with errors
131 return implode("\n", $contents);
134 function _transformFilePHP4($xmlFile, $xslFile) {
135 $processor = xslt_create();
136 xslt_set_encoding($processor, XSLT_PROCESSOR_ENCODING
);
138 $contents = xslt_process($processor, $xmlFile, $xslFile, null, null, $this->parameters
);
141 $this->addError("Cannot process XSLT document [%d]: %s", xslt_errno($processor), xslt_error($processor));
147 function _transformStringPHP4($xml, $xsl) {
148 $arguments = array('/_xml' => $xml, '/_xsl' => $xsl);
150 $processor = xslt_create();
151 xslt_set_encoding($processor, XSLT_PROCESSOR_ENCODING
);
153 $contents = xslt_process($processor, 'arg:/_xml', 'arg:/_xsl', null, $arguments, $this->parameters
);
156 $this->addError("Cannot process XSLT document [%d]: %s", xslt_errno($processor), xslt_error($processor));
162 function _transformFilePHP5($xmlFile, $xslFile) {
163 $processor = new XSLTProcessor();
165 // NB: this can open potential security issues; see FAQ/README
166 if ($this->registerPHPFunctions
) {
167 $processor->registerPHPFunctions($this->registerPHPFunctions
);
170 if (!empty($this->parameters
) && is_array($this->parameters
)) {
171 foreach ($this->parameters
as $param => $value) {
172 $processor->setParameter(null, $param, $value);
176 // load the XML file as a domdocument
177 $xmlDOM = new DOMDocument('1.0', XSLT_PROCESSOR_ENCODING
);
179 // These are required for external entity resolution (eg. ), but can slow processing
180 // substantially (20-100x), often up to 60s. This can be solved by use of local catalogs, ie.
181 // putenv("XML_CATALOG_FILES=/path/to/catalog.ent");
183 // see: http://www.whump.com/moreLikeThis/link/03815
184 $xmlDOM->recover
= true;
185 $xmlDOM->substituteEntities
= true;
186 $xmlDOM->resolveExternals
= true;
187 $xmlDOM->load($xmlFile);
189 // create the processor and import the stylesheet
190 $xslDOM = new DOMDocument('1.0', XSLT_PROCESSOR_ENCODING
);
191 $xslDOM->load($xslFile);
192 $processor->importStylesheet($xslDOM);
193 $contents = $processor->transformToXML($xmlDOM);
198 function _transformStringPHP5($xml, $xsl) {
199 $processor = new XSLTProcessor();
201 // NB: this can open potential security issues; see FAQ/README
202 if ($this->registerPHPFunctions
) {
203 $processor->registerPHPFunctions($this->registerPHPFunctions
);
206 foreach ($this->parameters
as $param => $value) {
207 $processor->setParameter(null, $param, $value);
210 // load the XML file as a domdocument
211 $xmlDOM = new DOMDocument('1.0', XSLT_PROCESSOR_ENCODING
);
212 $xmlDOM->recover
= true;
213 $xmlDOM->substituteEntities
= true;
214 $xmlDOM->resolveExternals
= true;
215 $xmlDOM->loadXML($xml);
217 // create the processor and import the stylesheet
218 $xslDOM = new DOMDocument('1.0', XSLT_PROCESSOR_ENCODING
);
219 $xslDOM->loadXML($xsl);
220 $processor->importStylesheet($xslDOM);
221 $contents = $processor->transformToXML($xmlDOM);
227 * Add an error to the current error list
228 * @param $error string
230 function addError($error) {
231 array_push($this->errors
, $error);