Passed
Push — main ( 221f6d...f8c128 )
by Siad
05:28
created

src/Phing/Filter/XsltFilter.php (1 issue)

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Filter;
22
23
use DOMDocument;
24
use Phing\Exception\BuildException;
25
use Phing\Io\BufferedReader;
26
use Phing\Io\File;
27
use Phing\Io\FileReader;
28
use Phing\Io\FilterReader;
29
use Phing\Io\IOException;
30
use Phing\Io\Reader;
31
use Phing\Project;
32
use XSLTProcessor;
33
34
/**
35
 * Applies XSL stylesheet to incoming text.
36
 *
37
 * Uses PHP XSLT support (libxslt).
38
 *
39
 * @author Hans Lellelid <[email protected]>
40
 * @author Yannick Lecaillez <[email protected]>
41
 * @author Andreas Aderhold <[email protected]>
42
 *
43
 * @see     FilterReader
44
 */
45
class XsltFilter extends BaseParamFilterReader implements ChainableReader
46
{
47
    /**
48
     * Path to XSL stylesheet.
49
     *
50
     * @var File
51
     */
52
    private $xslFile;
53
54
    /**
55
     * Whether XML file has been transformed.
56
     *
57
     * @var bool
58
     */
59
    private $processed = false;
60
61
    /**
62
     * XSLT Params.
63
     *
64
     * @var XsltParam[]
65
     */
66
    private $xsltParams = [];
67
68
    /**
69
     * Whether to use loadHTML() to parse the input XML file.
70
     */
71
    private $html = false;
72
73
    /**
74
     * Whether to resolve entities in the XML document (see
75
     * {@link http://www.php.net/manual/en/class.domdocument.php#domdocument.props.resolveexternals}
76
     * for more details).
77
     *
78
     * @var bool
79
     *
80
     * @since 2.4
81
     */
82
    private $resolveDocumentExternals = false;
83
84
    /**
85
     * Whether to resolve entities in the stylesheet.
86
     *
87
     * @var bool
88
     *
89
     * @since 2.4
90
     */
91
    private $resolveStylesheetExternals = false;
92
93
    /**
94
     * Create new XSLT Param object, to handle the <param/> nested element.
95
     *
96
     * @return XsltParam
97
     */
98
    public function createParam()
99
    {
100
        $num = array_push($this->xsltParams, new XsltParam());
101
102
        return $this->xsltParams[$num - 1];
103
    }
104
105
    /**
106
     * Sets the XSLT params for this class.
107
     * This is used to "clone" this class, in the chain() method.
108
     *
109
     * @param array $params
110
     */
111
    public function setParams($params)
112
    {
113
        $this->xsltParams = $params;
114
    }
115
116
    /**
117
     * Returns the XSLT params set for this class.
118
     * This is used to "clone" this class, in the chain() method.
119
     *
120
     * @return array
121
     */
122
    public function getParams()
123
    {
124
        return $this->xsltParams;
125
    }
126
127
    /**
128
     * Set the XSLT stylesheet.
129
     *
130
     * @param mixed $file phingFile object or path
131
     */
132
    public function setStyle(File $file)
133
    {
134
        $this->xslFile = $file;
135
    }
136
137
    /**
138
     * Whether to use HTML parser for the XML.
139
     * This is supported in libxml2 -- Yay!
140
     *
141
     * @return bool
142
     */
143
    public function getHtml()
144
    {
145
        return $this->html;
146
    }
147
148
    /**
149
     * Whether to use HTML parser for XML.
150
     *
151
     * @param bool $b
152
     */
153
    public function setHtml($b)
154
    {
155
        $this->html = (bool) $b;
156
    }
157
158
    /**
159
     * Get the path to XSLT stylesheet.
160
     *
161
     * @return file XSLT stylesheet path
162
     */
163
    public function getStyle()
164
    {
165
        return $this->xslFile;
166
    }
167
168
    /**
169
     * Whether to resolve entities in document.
170
     *
171
     * @since 2.4
172
     */
173
    public function setResolveDocumentExternals(bool $resolveExternals)
174
    {
175
        $this->resolveDocumentExternals = $resolveExternals;
176
    }
177
178
    /**
179
     * @return bool
180
     *
181
     * @since 2.4
182
     */
183
    public function getResolveDocumentExternals()
184
    {
185
        return $this->resolveDocumentExternals;
186
    }
187
188
    /**
189
     * Whether to resolve entities in stylesheet.
190
     *
191
     * @since 2.4
192
     */
193
    public function setResolveStylesheetExternals(bool $resolveExternals)
194
    {
195
        $this->resolveStylesheetExternals = $resolveExternals;
196
    }
197
198
    /**
199
     * @return bool
200
     *
201
     * @since 2.4
202
     */
203
    public function getResolveStylesheetExternals()
204
    {
205
        return $this->resolveStylesheetExternals;
206
    }
207
208
    /**
209
     * Reads stream, applies XSLT and returns resulting stream.
210
     *
211
     * @param int $len
212
     *
213
     * @throws BuildException
214
     *
215
     * @return string transformed buffer
216
     */
217
    public function read($len = null)
218
    {
219
        if (!class_exists('XSLTProcessor')) {
220
            throw new BuildException('Could not find the XSLTProcessor class. Make sure PHP has been compiled/configured to support XSLT.');
221
        }
222
223
        if (true === $this->processed) {
224
            return -1; // EOF
225
        }
226
227
        if (!$this->getInitialized()) {
228
            $this->initialize();
229
            $this->setInitialized(true);
230
        }
231
232
        // Read XML
233
        $_xml = null;
234
        while (($data = $this->in->read($len)) !== -1) {
235
            $_xml .= $data;
236
        }
237
238
        if (null === $_xml) { // EOF?
239
            return -1;
240
        }
241
242
        if (empty($_xml)) {
243
            $this->log('XML file is empty!', Project::MSG_WARN);
244
245
            return ''; // return empty string, don't attempt to apply XSLT
246
        }
247
248
        // Read XSLT
249
        $_xsl = null;
250
        $br = new BufferedReader(new FileReader($this->xslFile));
251
        $_xsl = $br->read();
252
253
        $this->log(
254
            'Tranforming XML ' . $this->in->getResource() . ' using style ' . $this->xslFile->getPath(),
255
            Project::MSG_VERBOSE
256
        );
257
258
        $out = '';
259
260
        try {
261
            $out = $this->process($_xml, $_xsl);
262
            $this->processed = true;
263
        } catch (IOException $e) {
264
            throw new BuildException($e);
265
        }
266
267
        return $out;
268
    }
269
270
    /**
271
     * Creates a new XsltFilter using the passed in
272
     * Reader for instantiation.
273
     *
274
     * @param Reader A Reader object providing the underlying stream.
275
     *               Must not be <code>null</code>.
276
     *
277
     * @return XsltFilter A new filter based on this configuration, but filtering
278
     *                    the specified reader
279
     */
280
    public function chain(Reader $reader): Reader
281
    {
282
        $newFilter = new XsltFilter($reader);
283
        $newFilter->setProject($this->getProject());
284
        $newFilter->setStyle($this->getStyle());
285
        $newFilter->setInitialized(true);
286
        $newFilter->setParams($this->getParams());
287
        $newFilter->setHtml($this->getHtml());
288
289
        return $newFilter;
290
    }
291
292
    // {{{ method _ProcessXsltTransformation($xml, $xslt) throws BuildException
293
294
    /**
295
     * Try to process the XSLT transformation.
296
     *
297
     * @param string $xml XML to process
298
     * @param string $xsl XSLT sheet to use for the processing
299
     *
300
     * @throws BuildException On XSLT errors
301
     *
302
     * @return string
303
     */
304
    protected function process($xml, $xsl)
305
    {
306
        $processor = new XSLTProcessor();
307
308
        // Create and setup document.
309
        $xmlDom = new DOMDocument();
310
        $xmlDom->resolveExternals = $this->resolveDocumentExternals;
311
312
        // Create and setup stylesheet.
313
        $xslDom = new DOMDocument();
314
        $xslDom->resolveExternals = $this->resolveStylesheetExternals;
315
316
        if ($this->html) {
317
            $result = @$xmlDom->loadHTML($xml);
318
        } else {
319
            $result = @$xmlDom->loadXML($xml);
320
        }
321
322
        if (false === $result) {
323
            throw new BuildException('Invalid syntax detected.');
324
        }
325
326
        $xslDom->loadXML($xsl);
327
328
        if (defined('XSL_SECPREF_WRITE_FILE')) {
329
            $processor->setSecurityPrefs(XSL_SECPREF_WRITE_FILE | XSL_SECPREF_CREATE_DIRECTORY);
330
        }
331
        $processor->importStylesheet($xslDom);
332
333
        // ignoring param "type" attrib, because
334
        // we're only supporting direct XSL params right now
335
        foreach ($this->xsltParams as $param) {
336
            $this->log('Setting XSLT param: ' . $param->getName() . '=>' . $param->getExpression(), Project::MSG_DEBUG);
337
            $processor->setParameter(null, $param->getName(), $param->getExpression());
338
        }
339
340
        $errorlevel = error_reporting();
341
        error_reporting($errorlevel & ~E_WARNING);
342
        @$result = $processor->transformToXml($xmlDom);
343
        error_reporting($errorlevel);
344
345
        if (false === $result) {
346
            //$errno = xslt_errno($processor);
347
            //$err   = xslt_error($processor);
348
            throw new BuildException('XSLT Error');
349
        }
350
351
        return $result;
352
    }
353
354
    /**
355
     * Parses the parameters to get stylesheet path.
356
     */
357
    private function initialize()
358
    {
359
        $params = $this->getParameters();
360
        if (null !== $params) {
0 ignored issues
show
The condition null !== $params is always true.
Loading history...
361
            for ($i = 0, $_i = count($params); $i < $_i; ++$i) {
362
                if (null === $params[$i]->getType()) {
363
                    if ('style' === $params[$i]->getName()) {
364
                        $this->setStyle($params[$i]->getValue());
365
                    }
366
                } elseif ('param' == $params[$i]->getType()) {
367
                    $xp = new XsltParam();
368
                    $xp->setName($params[$i]->getName());
369
                    $xp->setExpression($params[$i]->getValue());
370
                    $this->xsltParams[] = $xp;
371
                }
372
            }
373
        }
374
    }
375
}
376