Passed
Push — main ( cb6a7c...966a82 )
by Siad
05:32
created

XmlLintTask::lint()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 36
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 11.5396

Importance

Changes 0
Metric Value
eloc 27
c 0
b 0
f 0
dl 0
loc 36
rs 8.4444
ccs 13
cts 21
cp 0.619
cc 8
nc 8
nop 1
crap 11.5396
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\Task\System;
22
23
use DOMDocument;
24
use Phing\Exception\BuildException;
25
use Phing\Io\File;
26
use Phing\Project;
27
use Phing\Task;
28
use Phing\Type\Element\FileSetAware;
29
30
/**
31
 * A XML lint task. Checking syntax of one or more XML files against an XML Schema using the DOM extension.
32
 *
33
 * @author  Knut Urdalen <[email protected]>
34
 */
35
class XmlLintTask extends Task
36
{
37
    use FileSetAware;
38
39
    protected $file; // the source file (from xml attribute)
40
    protected $schema; // the schema file (from xml attribute)
41
    protected $useRNG = false;
42
43
    protected $haltonfailure = true;
44
45
    /**
46
     * File to be performed syntax check on.
47
     */
48 1
    public function setFile(File $file): void
49
    {
50 1
        $this->file = $file;
51 1
    }
52
53
    /**
54
     * XML Schema Description file to validate against.
55
     * @param File $schema
56
     */
57 1
    public function setSchema(File $schema): void
58
    {
59 1
        $this->schema = $schema;
60 1
    }
61
62
    /**
63
     * Use RNG instead of DTD schema validation.
64
     *
65
     * @param bool $bool
66
     */
67 1
    public function setUseRNG(bool $bool): void
68
    {
69 1
        $this->useRNG = $bool;
70 1
    }
71
72
    /**
73
     * Sets the haltonfailure attribute.
74
     * @param bool $haltonfailure
75
     */
76 1
    public function setHaltonfailure(bool $haltonfailure): void
77
    {
78 1
        $this->haltonfailure = $haltonfailure;
79 1
    }
80
81
    /**
82
     * Execute lint check against PhingFile or a FileSet.
83
     *
84
     * {@inheritdoc}
85
     *
86
     * @throws BuildException
87
     */
88 1
    public function main()
89
    {
90 1
        libxml_use_internal_errors(true);
91 1
        if (isset($this->schema) && !file_exists($this->schema->getPath())) {
92
            throw new BuildException('Schema file not found: ' . $this->schema->getPath());
93
        }
94 1
        if (!isset($this->file) && 0 === count($this->filesets)) {
95
            throw new BuildException("Missing either a nested fileset or attribute 'file' set");
96
        }
97
98 1
        set_error_handler([$this, 'errorHandler']);
99 1
        if ($this->file instanceof File) {
100 1
            $this->lint($this->file->getPath());
101
        } else { // process filesets
102
            $project = $this->getProject();
103
            foreach ($this->filesets as $fs) {
104
                $ds = $fs->getDirectoryScanner($project);
105
                $files = $ds->getIncludedFiles();
106
                $dir = $fs->getDir($this->project)->getPath();
107
                foreach ($files as $file) {
108
                    $this->lint($dir . DIRECTORY_SEPARATOR . $file);
109
                }
110
            }
111
        }
112 1
        restore_error_handler();
113 1
    }
114
115
    /**
116
     * Local error handler to catch validation errors and log them through Phing.
117
     *
118
     * @param int    $level
119
     * @param string $message
120
     * @param string $file
121
     * @param int    $line
122
     * @param mixed  $context
123
     */
124
    public function errorHandler($level, $message, $file, $line, $context): void
0 ignored issues
show
Unused Code introduced by
The parameter $line is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

124
    public function errorHandler($level, $message, $file, /** @scrutinizer ignore-unused */ $line, $context): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

124
    public function errorHandler($level, $message, $file, $line, /** @scrutinizer ignore-unused */ $context): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $file is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

124
    public function errorHandler($level, $message, /** @scrutinizer ignore-unused */ $file, $line, $context): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
125
    {
126
        $matches = [];
127
        preg_match('/^.*\(\): (.*)$/', $message, $matches);
128
        $this->log($matches[1], Project::MSG_ERR);
129
    }
130
131
    /**
132
     * @param $message
133
     *
134
     * @throws BuildException
135
     */
136 1
    protected function logError($message): void
137
    {
138 1
        if ($this->haltonfailure) {
139
            throw new BuildException($message);
140
        }
141
142 1
        $this->log($message, Project::MSG_ERR);
143 1
    }
144
145
    /**
146
     * Performs validation.
147
     *
148
     * @param string $file
149
     */
150 1
    protected function lint($file): void
151
    {
152 1
        if (file_exists($file)) {
153 1
            if (is_readable($file)) {
154 1
                $dom = new DOMDocument();
155 1
                if (false === $dom->load($file)) {
156
                    $this->libxmlGetErrors();
157
                    $this->logError($file . ' is not well-formed (See messages above)');
158
                } else {
159 1
                    if (isset($this->schema)) {
160 1
                        if ($this->useRNG) {
161 1
                            if ($dom->relaxNGValidate($this->schema->getPath())) {
162
                                $this->log($file . ' validated with RNG grammar');
163
                            } else {
164 1
                                $this->libxmlGetErrors();
165 1
                                $this->logError($file . ' fails to validate (See messages above)');
166
                            }
167
                        } else {
168
                            if ($dom->schemaValidate($this->schema->getPath())) {
169
                                $this->log($file . ' validated with schema');
170
                            } else {
171
                                $this->libxmlGetErrors();
172 1
                                $this->logError($file . ' fails to validate (See messages above)');
173
                            }
174
                        }
175
                    } else {
176
                        $this->log(
177 1
                            $file . ' is well-formed (not validated due to missing schema specification)'
178
                        );
179
                    }
180
                }
181
            } else {
182 1
                $this->logError('Permission denied to read file: ' . $file);
183
            }
184
        } else {
185
            $this->logError('File not found: ' . $file);
186
        }
187 1
    }
188
189 1
    private function libxmlGetErrors(): void
190
    {
191 1
        $errors = libxml_get_errors();
192 1
        foreach ($errors as $error) {
193 1
            [$severity, $message] = $this->libxmlGetError($error);
194 1
            $this->log($message, $severity === 'error' ? Project::MSG_ERR : Project::MSG_WARN);
195
        }
196 1
        libxml_clear_errors();
197 1
    }
198
199 1
    private function libxmlGetError($error): array
200
    {
201 1
        $return = '';
202 1
        $severity = '';
203 1
        switch ($error->level) {
204 1
            case LIBXML_ERR_WARNING:
205
                $return .= "Warning $error->code: ";
206
                $severity = 'warn';
207
                break;
208 1
            case LIBXML_ERR_ERROR:
209 1
                $return .= "Error $error->code: ";
210 1
                $severity = 'error';
211 1
                break;
212
            case LIBXML_ERR_FATAL:
213
                $return .= "Fatal Error $error->code: ";
214
                $severity = 'error';
215
                break;
216
        }
217 1
        $return .= trim($error->message);
218 1
        if ($error->file) {
219 1
            $return .=    " in $error->file";
220
        }
221 1
        $return .= " on line $error->line";
222
223 1
        return [$severity, $return];
224
    }
225
}
226