Pokud máme hromadu starých HTML dokumentů využívajících hromadu starých stylů, tak můžeme chtít najít pravidla, která nejsou využita. Následující třída se přesně o to postará:
<?php /** Nalezení nepoužívaných definic CSS * @copyright Jakub Vrána, https://php.vrana.cz/ */ class NeedlessCss { protected $csses = array(); protected $rules = array(); private function onlyNewlines($match) { return str_repeat("\n", substr_count($match[0], "\n")); } protected function processCss($file, $href) { $file = preg_replace_callback('~/\\*.*\\*/|"([^\\\\]|\\\\.)*"|\'([^\\\\]|\\\\.)*\'|@.*[;{]~isU', array($this, 'onlyNewlines'), $file); preg_match_all('~(\\S[^{]*)\\{[^}]*}~s', $file, $matches, PREG_OFFSET_CAPTURE); foreach ($matches[1] as $val) { foreach (explode(",", $val[0]) as $rule) { $this->rules[trim($rule)][$href][] = ($val[1] ? substr_count($file, "\n", 0, $val[1]) + 1 : 1); } } } /** Nahraje styl * @param string * @param string základ pro $href * @return array */ function loadCss($href, $relative = "") { if (!strpos($href, ":")) { $href = ($href[0] == "/" ? preg_replace('~^([^/]+//[^/]+)?.*~', '\\1', $relative) : preg_replace('~[^/]*$~', '', $relative)) . $href; } $href = preg_replace('~[^/]*/\.\./~', '', $href); if (!isset($this->csses[$href])) { $this->csses[$href] = array($href => true); $file = file_get_contents($href); preg_match_all('~@import\\s+url\\((.*)\\)~U', $file, $matches); //! quotes foreach ($matches[1] as $val) { $this->csses[$href] += $this->loadCss(trim($val, '\'"'), $href); } $this->processCss($file, $href); } return $this->csses[$href]; } /** Nalezne nepoužité definice CSS * @param array ('filename.html') * @return array ('rule' => array('filename.css' => array($line)) */ function processHtmls($htmls) { $dom = new DOMDocument; foreach ($htmls as $filename) { $dom->loadHTMLFile($filename); $html = simplexml_import_dom($dom); $csses = array($filename => true); foreach ($html->xpath("//link[@rel='stylesheet']") as $link) { $csses += $this->loadCss(strval($link["href"]), $filename); } foreach ($html->xpath("//style") as $style) { $this->processCss(strval($style), $filename); // wrong line numbers } foreach ($this->rules as $rule => $definitions) { foreach ($csses as $href => $foo) { if (isset($definitions[$href])) { // can be cached $xpath = $rule; $xpath = preg_replace('~:[-\\w]+~', '', $xpath); // remove pseudo-classes $xpath = preg_replace('~#([-\\w]+)~', "[@id='\\1']", $xpath); $xpath = preg_replace('~\\.([-\\w]+)~', "[@class='\\1']", $xpath); $xpath = preg_replace('~(^|\\s)\\[~', '\\1*[', $xpath); // use * for no tag $xpath = preg_replace('~\\s+~', '//', $xpath); if (count($html->xpath("//$xpath"))) { unset($this->rules[$rule]); } break; } } } } return $this->rules; } } ?>
Použití je jednoduché:
<?php $needlessCss = new NeedlessCss; foreach ($needlessCss->processHtmls(array("a.html", "b.html")) as $rule => $definitions) { foreach ($definitions as $filename => $lines) { foreach ($lines as $line) { echo "$filename:$line:$rule\n"; } } } ?>
Metoda pracuje tak, že načte pravidla ze všech připojených stylů a převede je na dotazy XPath. Pokud tento dotaz něco najde, tak pravidlo označí jako použité a dál s ním nepracuje. Zbylá pravidla vrátí.
Zpracování stylu je poměrně jednoduché, protože nás nezajímají komentáře ani řetězce, takže je jednoduše odstraníme (zachovají se jen konce řádků, protože metoda vrací číslo řádku). Podotýkám ale, že skript rozpoznává jen pravidla CSS 1. Podporu pro složitější pravidla si můžete dopsat sami (např. >
nebo []
půjde poměrně snadno, jiná pravidla možná trochu obtížněji). Navíc dejte pozor na to, že některé definice se mohou používat v JavaScriptu.
Diskuse je zrušena z důvodu spamu.