Completed
Push — master ( 7082f0...55a2b2 )
by Siad
15:25
created

XsltFilter::initialize()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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