Nalezení nepoužívaných definic CSS

Školení, která pořádám

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.

Jakub Vrána, Řešení problému, 23.11.2010, diskuse: 8 (nové: 0)

Diskuse

ikona v6ak:

Nebo podmínečně v šablonách... zlaté statické typování.

Schmutzka:

Chtěl jsem vyzkoušet a hází mi to chybu "file_get_contents(index.phpstyl.css)... nenalezeno" (vstup mám "index.php"), mám tam nějakou chybu, nebo to php nejdřív musím převést do html (chci-li to opravdu použít)?

ikona Jakub Vrána OpenID:

Ve skriptu byla chyba. Opravil jsem ji, díky za upozornění.

Schmutzka:

Nyní mám tento výpis, nebo už je to konečný? Zobrazují se tam nějaké css styly, ale nevím, zda je to ok. Hodil by se nějaký příklad, nebo také "onlinechecker".

Warning: SimpleXMLElement::xpath() [simplexmlelement.xpath]: Invalid expression in D:\www\...\checkoldcss.class.php on line 71

Warning: SimpleXMLElement::xpath() [simplexmlelement.xpath]: xmlXPathEval: evaluation failed in D:\www\...\checkoldcss.class.php on line 71
styl.css:3:a styl.css:4:a:hover styl.css:6:h1 styl.css:7:h2 styl.css:8:.bold styl.css:10:#main styl.css:14:td styl.css:18:#main_table styl.css:19:#footer styl.css:24:.auto styl.css:25:.bg1 styl.css:26:.collapse styl.css:28:.mensi styl.css:32:.nadpis styl.css:33:.novinky include/menu/anylinkcssmenu.css:1:.selectedanchor include/...

ikona Jakub Vrána OpenID:

Ukázka použití je určena ke spouštění z příkazové řádky – pokud se to má spouštět z webu, tak by bylo vhodné výstup nějak zformátovat.

Jinak až na ty dva warningy to vypadá jako platný výstup nepoužitých definic. Warningy budou nejspíš způsobeny použitím nějakého nepodporovaného CSS pravidla.

habendorf:

Já vím, že tento web je o PHP, ale pokud by někdo přece jen hledal rychlé řešení mimo PHP, bude se mu hodit toto rozšíření Fierefoxu: http://www.sitepoint.com/dustmeselectors/

Pokud je k dispozici sitemap (XML nebo HTML), projede spider celý web.

ikona Jan Pejša:

jen přihodím že rozšíření dustmeselectors umí hledat i v případě změn v DOMu (např. webová aplikace, která se mění při používání - po načtení aplikace se použije třeba jen 20% stylů a až při jejím používání se použije třeba 70% stylů - zbylých 30% je pak v tomto případě to "nepoužívané")

Lukas:

Zajímavý, ale nefunguje to.

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.