File

advagg_css_compress/css-compressor-3.x/src/lib/Selectors.inc

View source
<?php


/**
 * CSS Compressor [VERSION]
 * [DATE]
 * Corey Hart @ http://www.codenothing.com
 */
class CSSCompression_Selectors {
    
    /**
     * Selector patterns
     *
     * @class Control: Compression Controller
     * @param (string) token: Copy of the injection token
     * @param (regex) ridattr: ID Attribute matcher (combined with token)
     * @param (regex) rclassattr: class Attribute matcher (combined with token)
     * @param (array) options: Reference to options
     * @param (regex) rmark: Stop points during selector parsing
     * @param (regex) ridclassend: End of a id/class string
     * @param (regex) rescapedspace: for replacement in class attributes
     * @param (regex) rquote: Checks for the next quote character
     * @param (regex) rcomma: looks for an unescaped comma character
     * @param (regex) rspace: looks for an unescaped space character
     * @param (regex) rid: looks for an unescaped hash character
     * @param (regex) rpseudo: Add space after first-letter|line pseudo selector
     * --- when it occurs before comma or rule set
     */
    private $Control;
    private $token = '';
    private $ridattr = "";
    private $rclassattr = "";
    private $options = array();
    private $rmark = "/(?<!\\\\)(#|\\.|=)/";
    private $ridclassend = "/(?<!\\\\)[:#>~\\[\\+\\*\\. ]/";
    private $rquote = "/(?<!\\\\)(\"|')?\\]/";
    private $rescapedspace = "/\\\\ /";
    private $rcomma = "/(?<!\\\\),/";
    private $rspace = "/(?<!\\\\)\\s/";
    private $rid = "/(?<!\\\\)#/";
    private $rpseudo = "/:first-(letter|line)(,|\$)/i";
    
    /**
     * Stash a reference to the controller on each instantiation
     *
     * @param (class) control: CSSCompression Controller
     */
    public function __construct(CSSCompression_Control $control) {
        $this->Control = $control;
        $this->token = CSSCompression::TOKEN;
        $this->ridattr = "/\\[id={$this->token}(.*?){$this->token}\\]/";
        $this->rclassattr = "/\\[class={$this->token}(.*?){$this->token}\\]/";
        $this->options =& $control->Option->options;
    }
    
    /**
     * Selector specific optimizations
     *
     * @param (array) selectors: Array of selectors
     */
    public function selectors(&$selectors = array()) {
        foreach ($selectors as &$selector) {
            // Auto ignore sections
            if (strpos($selector, $this->token) === 0) {
                continue;
            }
            // Smart casing and token injection
            $selector = $this->parse($selector);
            // Converting attr to shorthanded selectors
            if ($this->options['attr2selector']) {
                // Use id hash instead of id attr
                $selector = $this->idAttribute($selector);
                // Use class notation instead of class attr
                $selector = $this->classAttribute($selector);
            }
            // Remove everything before final id in a selector
            if ($this->options['strict-id']) {
                $selector = $this->strictid($selector);
            }
            // Get rid of possible repeated selectors
            $selector = $this->repeats($selector);
            // Add space after pseudo selectors (so ie6 doesn't complain)
            if ($this->options['pseudo-space']) {
                $selector = $this->pseudoSpace($selector);
            }
        }
        return $selectors;
    }
    
    /**
     * Converts selectors like BODY => body, DIV => div
     * and injects tokens wrappers for attribute values
     *
     * @param (string) selector: CSS Selector
     */
    private function parse($selector) {
        $clean = '';
        $substr = '';
        $pos = 0;
        while (preg_match($this->rmark, $selector, $match, PREG_OFFSET_CAPTURE, $pos)) {
            $substr = substr($selector, $pos, $match[0][1] + 1 - $pos);
            $clean .= $this->options['lowercase-selectors'] ? strtolower($substr) : $substr;
            $pos = $match[0][1] + strlen($match[1][0]);
            // Class or id match
            if ($match[1][0] == '#' || $match[1][0] == '.') {
                if (preg_match($this->ridclassend, $selector, $m, PREG_OFFSET_CAPTURE, $pos)) {
                    $clean .= substr($selector, $pos, $m[0][1] - $pos);
                    $pos = $m[0][1];
                }
                else {
                    $clean .= substr($selector, $pos);
                    $pos = strlen($selector);
                    break;
                }
            }
            else {
                if (preg_match($this->rquote, $selector, $m, PREG_OFFSET_CAPTURE, $pos)) {
                    if ($selector[$pos] == "\"" || $selector[$pos] == "'") {
                        $pos++;
                    }
                    $clean .= $this->token . substr($selector, $pos, $m[0][1] - $pos) . $this->token . ']';
                    $pos = $m[0][1] + strlen($m[0][0]);
                }
                else {
                    $clean .= substr($selector, $pos);
                    $pos = strlen($selector);
                    break;
                }
            }
        }
        return $clean . ($this->options['lowercase-selectors'] ? strtolower(substr($selector, $pos)) : substr($selector, $pos));
    }
    
    /**
     * Convert [id=blah] attribute selectors into id form selector (#blah)
     *
     * @param (string) selector: CSS Selector
     */
    private function idAttribute($selector) {
        $pos = 0;
        while (preg_match($this->ridattr, $selector, $match, PREG_OFFSET_CAPTURE, $pos)) {
            // Don't convert if space found (not valid hash selector)
            if (strpos($match[1][0], ' ') !== false) {
                $pos = $match[0][1] + strlen($match[0][0]);
                continue;
            }
            $selector = substr_replace($selector, '#' . $match[1][0], $match[0][1], strlen($match[0][0]));
            $pos = $match[0][1] + strlen($match[1][0]) + 1;
        }
        return $selector;
    }
    
    /**
     * Convert [class=blah] attribute selectors into class form selector (.blah)
     *
     * @param (string) selector: CSS Selector
     */
    private function classAttribute($selector) {
        $pos = 0;
        while (preg_match($this->rclassattr, $selector, $match, PREG_OFFSET_CAPTURE, $pos)) {
            // Don't convert if prescense of dot separator found
            if (strpos($match[1][0], '.') !== false) {
                $pos = $match[0][1] + strlen($match[0][0]);
                continue;
            }
            $replace = '.' . preg_replace($this->rescapedspace, ".", $match[1][0]);
            $selector = substr_replace($selector, $replace, $match[0][1], strlen($match[0][0]));
            $pos = $match[0][1] + strlen($match[1][0]) + 1;
        }
        return $selector;
    }
    
    /**
     * Promotes nested id's to the front of the selector
     *
     * @param (string) selector: CSS Selector
     */
    private function strictid($selector) {
        $parts = preg_split($this->rcomma, $selector);
        foreach ($parts as &$s) {
            if (preg_match($this->rid, $s)) {
                $p = preg_split($this->rid, $s);
                $s = '#' . array_pop($p);
            }
        }
        return implode(',', $parts);
    }
    
    /**
     * Removes repeated selectors that have been comma separated
     *
     * @param (string) selector: CSS Selector
     */
    private function repeats($selector) {
        $parts = preg_split($this->rcomma, $selector);
        $parts = array_flip($parts);
        $parts = array_flip($parts);
        return implode(',', $parts);
    }
    
    /**
     * Adds space after pseudo selector for ie6 like a:first-child{ => a:first-child {
     *
     * @param (string) selector: CSS Selector
     */
    private function pseudoSpace($selector) {
        return preg_replace($this->rpseudo, ":first-\$1 \$2", $selector);
    }
    
    /**
     * Access to private methods for testing
     *
     * @param (string) method: Method to be called
     * @param (array) args: Array of paramters to be passed in
     */
    public function access($method, $args) {
        if (method_exists($this, $method)) {
            if ($method == 'selectors') {
                return $this->selectors($args[0]);
            }
            else {
                return call_user_func_array(array(
                    $this,
                    $method,
                ), $args);
            }
        }
        else {
            throw new CSSCompression_Exception("Unknown method in Selectors Class - " . $method);
        }
    }

}

Classes

Title Deprecated Summary
CSSCompression_Selectors CSS Compressor [VERSION] [DATE] Corey Hart @ http://www.codenothing.com