baseline
[omp.pkp.sfu.ca.git] / lib / pkp / classes / xslt / XSLTransformer.inc.php
blob896495d2128abc0a4a14b3d017b47838ae00b66f
1 <?php
3 /**
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
10 * @ingroup xslt
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 */
23 var $processor;
25 /** @var $externalCommand string containing external XSLT shell command */
26 var $externalCommand;
28 /** @var $parameters array of parameters to pass to XSL (built-in libraries only) */
29 var $parameters;
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 */
35 var $errors;
37 /**
38 * Constructor.
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';
60 } else {
61 // no XSLT support
62 return false;
65 $this->errors = array();
68 /**
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) {
79 case 'External':
80 return $this->_transformExternal($xmlFile, $xslFile);
81 case 'PHP4':
82 return $this->_transformFilePHP4($xmlFile, $xslFile);
83 case 'PHP5':
84 return $this->_transformFilePHP5($xmlFile, $xslFile);
86 // No XSLT processor available
87 return false;
90 /**
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
99 case 'PHP4':
100 return $this->_transformStringPHP4($xml, $xsl);
101 case 'PHP5':
102 return $this->_transformStringPHP5($xml, $xsl);
104 // No XSLT processor available
105 return false;
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) {
124 if ($contents) {
125 $this->addError(implode("\n", $contents));
127 // completed with errors
128 return false;
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);
140 if (!$contents) {
141 $this->addError("Cannot process XSLT document [%d]: %s", xslt_errno($processor), xslt_error($processor));
142 return false;
144 return $contents;
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);
155 if (!$contents) {
156 $this->addError("Cannot process XSLT document [%d]: %s", xslt_errno($processor), xslt_error($processor));
157 return false;
159 return $contents;
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. &nbsp;), 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);
195 return $contents;
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);
223 return $contents;
227 * Add an error to the current error list
228 * @param $error string
230 function addError($error) {
231 array_push($this->errors, $error);