-
Notifications
You must be signed in to change notification settings - Fork 1
/
CSSValidator.php
195 lines (165 loc) · 6.11 KB
/
CSSValidator.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
<?php
namespace CSSValidator;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\RetryableHttpClient;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class CSSValidator
{
/**
* URI to the W3C validator.
*
* @see https://github.com/w3c/css-validator
*/
private string $validatorUri = 'https://jigsaw.w3.org/css-validator/validator';
private Options $options;
private HttpClientInterface $httpClient;
public function __construct(Options $options = null, HttpClientInterface $httpClient = null)
{
$this->options = $options ?: new Options();
if (!$httpClient) {
$httpClient = HttpClient::createForBaseUri($this->getValidatorUri(), [
'headers' => [
'User-Agent' => 'gemorroj/cssvalidator',
],
]);
// https://symfony.com/doc/current/http_client.html#retry-failed-requests
$httpClient = new RetryableHttpClient($httpClient);
}
$this->httpClient = $httpClient;
}
public function getOptions(): Options
{
return $this->options;
}
public function setOptions(Options $options): self
{
$this->options = $options;
return $this;
}
public function getValidatorUri(): string
{
return $this->validatorUri;
}
public function setValidatorUri(string $validatorUri): self
{
$this->validatorUri = $validatorUri;
return $this;
}
public function getHttpClient(): HttpClientInterface
{
return $this->httpClient;
}
public function setHttpClient(HttpClientInterface $httpClient): self
{
$this->httpClient = $httpClient;
return $this;
}
/**
* Validates a given URI.
*
* Executes the validator using the current parameters and returns a Response
* object on success.
*
* @param string $uri The address to the page to validate ex: http://example.com/
*
* @throws Exception
* @throws TransportExceptionInterface When a network error occurs
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
* @throws ClientExceptionInterface On a 4xx when $throw is true
* @throws ServerExceptionInterface On a 5xx when $throw is true
*/
public function validateUri(string $uri): Response
{
$response = $this->getHttpClient()->request('GET', '', [
'query' => \array_merge(
$this->getOptions()->buildOptions(),
['uri' => $uri, 'output' => 'soap12']
),
]);
return $this->parseSOAP12Response($response->getContent());
}
/**
* Validates the local file.
*
* Requests validation on the local file, from an instance of the W3C validator.
* The file is posted to the W3C validator using multipart/form-data.
*
* @param string $file file to be validated
*
* @throws Exception
* @throws TransportExceptionInterface When a network error occurs
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
* @throws ClientExceptionInterface On a 4xx when $throw is true
* @throws ServerExceptionInterface On a 5xx when $throw is true
*/
public function validateFile(string $file): Response
{
if (!\file_exists($file)) {
throw new Exception('File not found');
}
if (!\is_readable($file)) {
throw new Exception('File not readable');
}
$data = @\file_get_contents($file);
if (false === $data) {
throw new Exception(\error_get_last()['message']);
}
return $this->validateFragment($data);
}
/**
* Validate an html string.
*
* @param string $css Full css document fragment
*
* @throws Exception
* @throws TransportExceptionInterface When a network error occurs
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
* @throws ClientExceptionInterface On a 4xx when $throw is true
* @throws ServerExceptionInterface On a 5xx when $throw is true
*/
public function validateFragment(string $css): Response
{
$response = $this->getHttpClient()->request('GET', '', [
'body' => $css,
'query' => \array_merge(
$this->getOptions()->buildOptions(),
['text' => $css, 'output' => 'soap12']
),
]);
return $this->parseSOAP12Response($response->getContent());
}
/**
* Parse an XML response from the validator.
*
* This function parses a SOAP 1.2 response xml string from the validator.
*
* @param string $xml the raw soap12 XML response from the validator
*
* @throws Exception
*/
protected function parseSOAP12Response(string $xml): Response
{
$doc = new \DOMDocument('1.0', 'UTF-8');
if (false === @$doc->loadXML($xml, \LIBXML_COMPACT | \LIBXML_NONET)) {
throw new Exception('Failed load xml');
}
$response = new Response();
$cssLevelElement = $doc->getElementsByTagName('csslevel');
$response->setCssLevel($cssLevelElement->item(0)->nodeValue);
$element = $doc->getElementsByTagName('validity');
$response->setValid('true' === $element->item(0)->nodeValue);
$errors = $doc->getElementsByTagName('error');
foreach ($errors as $error) {
$response->addError(new Error($error));
}
$warnings = $doc->getElementsByTagName('warning');
foreach ($warnings as $warning) {
$response->addWarning(new Warning($warning));
}
return $response;
}
}