-
Notifications
You must be signed in to change notification settings - Fork 7
/
functions.php
146 lines (146 loc) · 4.18 KB
/
functions.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
<?php
namespace Functions;
use const \Consts\{
VERSION,
ENCODING,
XMLNS,
REQUIRED_ATTRS,
INVALID_ATTRS,
INVALID_TAGS,
EXTS
};
use \FilesystemIterator;
use \RecursiveDirectoryIterator as Directory;
use \DOMDocument as SVG;
use \DOMElement as Element;
use \DOMNodelist as NodeList;
use \SplFileObject as File;
use \Throwable;
use \Error;
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'consts.php');
/**
* Lint an SVG that has been loaded into a `DOMDocument`
* @param SVG $svg SVG loaded as a `DomDocument`
* @return Bool Whether or not it is valid
*/
function lint_svg(SVG $svg, String $name): Bool
{
$valid = true;
try {
if ($svg->documentElement->tagName !== 'svg') {
throw new Error('Not a valid SVG');
} elseif (! has_required_attrs($svg->documentElement)) {
throw new Error(sprintf('Does not have all required attributes: [%s]', join(', ', REQUIRED_ATTRS)));
} else {
lint_nodes($svg->getElementsByTagName('*'));
}
} catch (Throwable $e) {
$valid = false;
echo "{$e->getMessage()} in '{$name}'" . PHP_EOL;
} finally {
return $valid;
}
}
/**
* Check that an `<svg>` has all required attributes
* @param SVG $svg The `<svg>` / documentElement
* @return Bool Whether or not it is has all required attributes
*/
function has_required_attrs(Element $svg, Array $attrs = REQUIRED_ATTRS): Bool
{
$valid = true;
foreach ($attrs as $attr) {
if (! $svg->hasAttribute($attr)) {
$valid = false;
Throw new Error("Missing '{$attr}' attribute");
break;
}
}
return $valid;
}
/**
* Check that all elements of SVG are valid
* @param NodeList $nodes All elements of an `<svg>`
* @return Bool Whether or not they are valid
*/
function lint_nodes(NodeList $nodes): Bool
{
$valid = true;
foreach ($nodes as $node) {
if (! lint_node($node)) {
$valid = false;
break;
}
}
return $valid;
}
/**
* Checks that an element in an SVG is a valid element and does not have invalid attributes
* @param Element $node An element from an `<svg>`
* @return Bool Whether or not the element is valid
*/
function lint_node(Element $node): Bool
{
$valid = true;
if (in_array($node->tagName, INVALID_TAGS)) {
throw new Error("Invalid element <{$node->tagName}>");
$valid = false;
} else {
foreach (INVALID_ATTRS as $attr) {
if (is_string($attr) and $node->hasAttribute($attr)) {
// $node->removeAttribute($attr);
// echo "Removing attribute {$attr}" . PHP_EOL;
throw new Error("<{$node->tagName}> has invalid attribute, '{$attr}'");
$valid = false;
break;
} elseif (is_array($attr) and $node->hasAttributeNS($attr[0], $attr[1])) {
throw new Error("<{$node->tagName}> has invalid attribute, '{$attr[0]}:{$attr[1]}'");
$valid = false;
break;
}
}
}
return $valid;
}
/**
* Recursively lint a directory
* @param String $dir Directory to lint
* @param Array $exts Array of extensions to lint in directory
* @param Array $ignore_dirs Ignore directories in this array
* @param Callable $error_callback Callback to call when linting fails
* @return Bool Whether or not all files linted without errors
* @see https://secure.php.net/manual/en/class.recursiveiteratoriterator.php
*/
function lint_dir(
String $dir = __DIR__,
Array $exts = EXTS,
Array $ignore_dirs = ['.git', 'node_modules'],
Callable $error_callback = null
): Bool
{
$path = new Directory($dir, Directory::SKIP_DOTS);
$valid = true;
while ($path->valid()) {
if ($path->isFile() and in_array($path->getExtension(), $exts)) {
try {
$svg = new SVG(VERSION, ENCODING);
$svg->load($path->getPathname());
if(! lint_svg($svg, $path->getPathName())) {
$valid = false;
}
} catch (Throwable $e) {
echo "{$e->getMessage()} in {$path->getPathname()}" . PHP_EOL;
$valid = false;
}
} elseif ($path->isDir() and ! in_array($path, $ignore_dirs)) {
// So long as $dir is the first argument of the function, this will
// always work, even if the name of the function changes.
$args = array_slice(func_get_args(), 1);
if (! call_user_func(__FUNCTION__, $path->getPathName(), ...$args)) {
$valid = false;
}
}
$path->next();
}
return $valid;
}