lib/Ajde/Resource/Local/Compressor/lib/Cssmin.php

Summary

Maintainability
F
1 mo
Test Coverage
<?php

class Ajde_Resource_Local_Compressor_Css_Cssmin extends CssMin
{
    private $_contents;

    public function __construct($contents)
    {
        $this->_contents = $contents;
    }

    public function compress()
    {
        $compressed = $this->minify($this->_contents);
        if (CssMin::hasErrors()) {
            $errors = CssMin::getErrors();
            Ajde_Log::log(var_export($errors, true));
        }

        return $compressed;
    }
}

/**
 * CssMin - A (simple) css minifier with benefits.
 *
 * --
 * Copyright (c) 2011 Joe Scylla <joe.scylla@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * --
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */

/**
 * Abstract definition of a CSS token class.
 *
 * Every token has to extend this class.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssToken
{
    /**
     * Returns the token as string.
     *
     * @return string
     */
    abstract public function __toString();
}

/**
 * Abstract definition of a for a ruleset start token.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssRulesetStartToken extends aCssToken
{
}

/**
 * Abstract definition of a for ruleset end token.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssRulesetEndToken extends aCssToken
{
    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '}';
    }
}

/**
 * Abstract definition of a parser plugin.
 *
 * Every parser plugin have to extend this class. A parser plugin contains the logic to parse one or aspects of a
 * stylesheet.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssParserPlugin
{
    /**
     * Plugin configuration.
     *
     * @var array
     */
    protected $configuration = [];
    /**
     * The CssParser of the plugin.
     *
     * @var CssParser
     */
    protected $parser = null;
    /**
     * Plugin buffer.
     *
     * @var string
     */
    protected $buffer = '';

    /**
     * Constructor.
     *
     * @param CssParser $parser        The CssParser object of this plugin.
     * @param array     $configuration Plugin configuration [optional]
     *
     * @return void
     */
    public function __construct(CssParser $parser, array $configuration = null)
    {
        $this->configuration = $configuration;
        $this->parser = $parser;
    }

    /**
     * Returns the array of chars triggering the parser plugin.
     *
     * @return array
     */
    abstract public function getTriggerChars();

    /**
     * Returns the array of states triggering the parser plugin or FALSE if every state will trigger the parser plugin.
     *
     * @return array
     */
    abstract public function getTriggerStates();

    /**
     * Parser routine of the plugin.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    abstract public function parse($index, $char, $previousChar, $state);
}

/**
 * Abstract definition of a minifier plugin class.
 *
 * Minifier plugin process the parsed tokens one by one to apply changes to the token. Every minifier plugin has to
 * extend this class.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssMinifierPlugin
{
    /**
     * Plugin configuration.
     *
     * @var array
     */
    protected $configuration = [];
    /**
     * The CssMinifier of the plugin.
     *
     * @var CssMinifier
     */
    protected $minifier = null;

    /**
     * Constructor.
     *
     * @param CssMinifier $minifier      The CssMinifier object of this plugin.
     * @param array       $configuration Plugin configuration [optional]
     *
     * @return void
     */
    public function __construct(CssMinifier $minifier, array $configuration = [])
    {
        $this->configuration = $configuration;
        $this->minifier = $minifier;
    }

    /**
     * Apply the plugin to the token.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    abstract public function apply(aCssToken &$token);

    /**
     * --.
     *
     * @return array
     */
    abstract public function getTriggerTokens();
}

/**
 * Abstract definition of a minifier filter class.
 *
 * Minifier filters allows a pre-processing of the parsed token to add, edit or delete tokens. Every minifier filter
 * has to extend this class.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssMinifierFilter
{
    /**
     * Filter configuration.
     *
     * @var array
     */
    protected $configuration = [];
    /**
     * The CssMinifier of the filter.
     *
     * @var CssMinifier
     */
    protected $minifier = null;

    /**
     * Constructor.
     *
     * @param CssMinifier $minifier      The CssMinifier object of this plugin.
     * @param array       $configuration Filter configuration [optional]
     *
     * @return void
     */
    public function __construct(CssMinifier $minifier, array $configuration = [])
    {
        $this->configuration = $configuration;
        $this->minifier = $minifier;
    }

    /**
     * Filter the tokens.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
     */
    abstract public function apply(array &$tokens);
}

/**
 * Abstract formatter definition.
 *
 * Every formatter have to extend this class.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssFormatter
{
    /**
     * Indent string.
     *
     * @var string
     */
    protected $indent = '    ';
    /**
     * Declaration padding.
     *
     * @var int
     */
    protected $padding = 0;
    /**
     * Tokens.
     *
     * @var array
     */
    protected $tokens = [];

    /**
     * Constructor.
     *
     * @param array  $tokens  Array of CssToken
     * @param string $indent  Indent string [optional]
     * @param int    $padding Declaration value padding [optional]
     */
    public function __construct(array $tokens, $indent = null, $padding = null)
    {
        $this->tokens = $tokens;
        $this->indent = !is_null($indent) ? $indent : $this->indent;
        $this->padding = !is_null($padding) ? $padding : $this->padding;
    }

    /**
     * Returns the array of aCssToken as formatted string.
     *
     * @return string
     */
    abstract public function __toString();
}

/**
 * Abstract definition of a ruleset declaration token.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssDeclarationToken extends aCssToken
{
    /**
     * Is the declaration flagged as important?
     *
     * @var bool
     */
    public $IsImportant = false;
    /**
     * Is the declaration flagged as last one of the ruleset?
     *
     * @var bool
     */
    public $IsLast = false;
    /**
     * Property name of the declaration.
     *
     * @var string
     */
    public $Property = '';
    /**
     * Value of the declaration.
     *
     * @var string
     */
    public $Value = '';

    /**
     * Set the properties of the @font-face declaration.
     *
     * @param string $property    Property of the declaration
     * @param string $value       Value of the declaration
     * @param bool   $isImportant Is the !important flag is set?
     * @param bool   $IsLast      Is the declaration the last one of the block?
     *
     * @return void
     */
    public function __construct($property, $value, $isImportant = false, $isLast = false)
    {
        $this->Property = $property;
        $this->Value = $value;
        $this->IsImportant = $isImportant;
        $this->IsLast = $isLast;
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->Property.':'.$this->Value.($this->IsImportant ? ' !important' : '').($this->IsLast ? '' : ';');
    }
}

/**
 * Abstract definition of a for at-rule block start token.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssAtBlockStartToken extends aCssToken
{
}

/**
 * Abstract definition of a for at-rule block end token.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
abstract class aCssAtBlockEndToken extends aCssToken
{
    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '}';
    }
}

/**
 * {@link aCssFromatter Formatter} returning the CSS source in {@link http://goo.gl/etzLs Whitesmiths indent style}.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssWhitesmithsFormatter extends aCssFormatter
{
    /**
     * Implements {@link aCssFormatter::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        $r = [];
        $level = 0;
        for ($i = 0, $l = count($this->tokens); $i < $l; $i++) {
            $token = $this->tokens[$i];
            $class = get_class($token);
            $indent = str_repeat($this->indent, $level);
            if ($class === 'CssCommentToken') {
                $lines = array_map('trim', explode("\n", $token->Comment));
                for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++) {
                    $r[] = $indent.(substr($lines[$ii], 0, 1) == '*' ? ' ' : '').$lines[$ii];
                }
            } elseif ($class === 'CssAtCharsetToken') {
                $r[] = $indent.'@charset '.$token->Charset.';';
            } elseif ($class === 'CssAtFontFaceStartToken') {
                $r[] = $indent.'@font-face';
                $r[] = $this->indent.$indent.'{';
                $level++;
            } elseif ($class === 'CssAtImportToken') {
                $r[] = $indent.'@import '.$token->Import.' '.implode(', ', $token->MediaTypes).';';
            } elseif ($class === 'CssAtKeyframesStartToken') {
                $r[] = $indent.'@keyframes "'.$token->Name.'"';
                $r[] = $this->indent.$indent.'{';
                $level++;
            } elseif ($class === 'CssAtMediaStartToken') {
                $r[] = $indent.'@media '.implode(', ', $token->MediaTypes);
                $r[] = $this->indent.$indent.'{';
                $level++;
            } elseif ($class === 'CssAtPageStartToken') {
                $r[] = $indent.'@page';
                $r[] = $this->indent.$indent.'{';
                $level++;
            } elseif ($class === 'CssAtVariablesStartToken') {
                $r[] = $indent.'@variables '.implode(', ', $token->MediaTypes);
                $r[] = $this->indent.$indent.'{';
                $level++;
            } elseif ($class === 'CssRulesetStartToken' || $class === 'CssAtKeyframesRulesetStartToken') {
                $r[] = $indent.implode(', ', $token->Selectors);
                $r[] = $this->indent.$indent.'{';
                $level++;
            } elseif ($class == 'CssAtFontFaceDeclarationToken'
                || $class === 'CssAtKeyframesRulesetDeclarationToken'
                || $class === 'CssAtPageDeclarationToken'
                || $class == 'CssAtVariablesDeclarationToken'
                || $class === 'CssRulesetDeclarationToken'
            ) {
                $declaration = $indent.$token->Property.': ';
                if ($this->padding) {
                    $declaration = str_pad($declaration, $this->padding, ' ', STR_PAD_RIGHT);
                }
                $r[] = $declaration.$token->Value.($token->IsImportant ? ' !important' : '').';';
            } elseif ($class === 'CssAtFontFaceEndToken'
                || $class === 'CssAtMediaEndToken'
                || $class === 'CssAtKeyframesEndToken'
                || $class === 'CssAtKeyframesRulesetEndToken'
                || $class === 'CssAtPageEndToken'
                || $class === 'CssAtVariablesEndToken'
                || $class === 'CssRulesetEndToken'
            ) {
                $r[] = $indent.'}';
                $level--;
            }
        }

        return implode("\n", $r);
    }
}

/**
 * This {@link aCssMinifierPlugin} will process var-statement and sets the declaration value to the variable value.
 *
 * This plugin only apply the variable values. The variable values itself will get parsed by the
 * {@link CssVariablesMinifierFilter}.
 *
 * Example:
 * <code>
 *
 * @variables
 *        {
 *        defaultColor: black;
 *        }
 * color: var(defaultColor);
 * </code>
 *
 * Will get converted to:
 * <code>
 * color:black;
 * </code>
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssVariablesMinifierPlugin extends aCssMinifierPlugin
{
    /**
     * Regular expression matching a value.
     *
     * @var string
     */
    private $reMatch = "/var\((.+)\)/iSU";
    /**
     * Parsed variables.
     *
     * @var array
     */
    private $variables = null;

    /**
     * Returns the variables.
     *
     * @return array
     */
    public function getVariables()
    {
        return $this->variables;
    }

    /**
     * Implements {@link aCssMinifierPlugin::minify()}.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    public function apply(aCssToken &$token)
    {
        if (stripos($token->Value, 'var') !== false && preg_match_all($this->reMatch, $token->Value, $m)) {
            $mediaTypes = $token->MediaTypes;
            if (!in_array('all', $mediaTypes)) {
                $mediaTypes[] = 'all';
            }
            for ($i = 0, $l = count($m[0]); $i < $l; $i++) {
                $variable = trim($m[1][$i]);
                foreach ($mediaTypes as $mediaType) {
                    if (isset($this->variables[$mediaType], $this->variables[$mediaType][$variable])) {
                        // Variable value found => set the declaration value to the variable value and return
                        $token->Value = str_replace($m[0][$i], $this->variables[$mediaType][$variable], $token->Value);
                        continue 2;
                    }
                }
                // If no value was found trigger an error and replace the token with a CssNullToken
                CssMin::triggerError(new CssError(__FILE__, __LINE__,
                    __METHOD__.': No value found for variable <code>'.$variable.'</code> in media types <code>'.implode(', ',
                        $mediaTypes).'</code>', (string) $token));
                $token = new CssNullToken();

                return true;
            }
        }

        return false;
    }

    /**
     * Implements {@link aMinifierPlugin::getTriggerTokens()}.
     *
     * @return array
     */
    public function getTriggerTokens()
    {
        return [
            'CssAtFontFaceDeclarationToken',
            'CssAtPageDeclarationToken',
            'CssRulesetDeclarationToken',
        ];
    }

    /**
     * Sets the variables.
     *
     * @param array $variables Variables to set
     *
     * @return void
     */
    public function setVariables(array $variables)
    {
        $this->variables = $variables;
    }
}

/**
 * This {@link aCssMinifierFilter minifier filter} will parse the variable declarations out of @variables at-rule
 * blocks. The variables will get store in the {@link CssVariablesMinifierPlugin} that will apply the variables to
 * declaration.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssVariablesMinifierFilter extends aCssMinifierFilter
{
    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        $variables = [];
        $defaultMediaTypes = ['all'];
        $mediaTypes = [];
        $remove = [];
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            // @variables at-rule block found
            if (get_class($tokens[$i]) === 'CssAtVariablesStartToken') {
                $remove[] = $i;
                $mediaTypes = (count($tokens[$i]->MediaTypes) == 0 ? $defaultMediaTypes : $tokens[$i]->MediaTypes);
                foreach ($mediaTypes as $mediaType) {
                    if (!isset($variables[$mediaType])) {
                        $variables[$mediaType] = [];
                    }
                }
                // Read the variable declaration tokens
                for ($i = $i; $i < $l; $i++) {
                    // Found a variable declaration => read the variable values
                    if (get_class($tokens[$i]) === 'CssAtVariablesDeclarationToken') {
                        foreach ($mediaTypes as $mediaType) {
                            $variables[$mediaType][$tokens[$i]->Property] = $tokens[$i]->Value;
                        }
                        $remove[] = $i;
                    } // Found the variables end token => break;
                    elseif (get_class($tokens[$i]) === 'CssAtVariablesEndToken') {
                        $remove[] = $i;
                        break;
                    }
                }
            }
        }
        // Variables in @variables at-rule blocks
        foreach ($variables as $mediaType => $null) {
            foreach ($variables[$mediaType] as $variable => $value) {
                // If a var() statement in a variable value found...
                if (stripos($value, 'var') !== false && preg_match_all("/var\((.+)\)/iSU", $value, $m)) {
                    // ... then replace the var() statement with the variable values.
                    for ($i = 0, $l = count($m[0]); $i < $l; $i++) {
                        $variables[$mediaType][$variable] = str_replace($m[0][$i],
                            (isset($variables[$mediaType][$m[1][$i]]) ? $variables[$mediaType][$m[1][$i]] : ''),
                            $variables[$mediaType][$variable]);
                    }
                }
            }
        }
        // Remove the complete @variables at-rule block
        foreach ($remove as $i) {
            $tokens[$i] = null;
        }
        if (!($plugin = $this->minifier->getPlugin('CssVariablesMinifierPlugin'))) {
            CssMin::triggerError(new CssError(__FILE__, __LINE__,
                __METHOD__.': The plugin <code>CssVariablesMinifierPlugin</code> was not found but is required for <code>'.__CLASS__.'</code>'));
        } else {
            $plugin->setVariables($variables);
        }

        return count($remove);
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for preserve parsing url() values.
 *
 * This plugin return no {@link aCssToken CssToken} but ensures that url() values will get parsed properly.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssUrlParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['(', ')'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return false;
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        // Start of string
        if ($char === '(' && strtolower(substr($this->parser->getSource(), $index - 3,
                4)) === 'url(' && $state !== 'T_URL'
        ) {
            $this->parser->pushState('T_URL');
            $this->parser->setExclusive(__CLASS__);
        } // Escaped LF in url => remove escape backslash and LF
        elseif ($char === "\n" && $previousChar === '\\' && $state === 'T_URL') {
            $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2));
        } // Parse error: Unescaped LF in string literal
        elseif ($char === "\n" && $previousChar !== '\\' && $state === 'T_URL') {
            $line = $this->parser->getBuffer();
            $this->parser->setBuffer(substr($this->parser->getBuffer(), 0,
                    -1).')'); // Replace the LF with the url string delimiter
            $this->parser->popState();
            $this->parser->unsetExclusive();
            CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__.': Unterminated string literal',
                $line.'_'));
        } // End of string
        elseif ($char === ')' && $state === 'T_URL') {
            $this->parser->popState();
            $this->parser->unsetExclusive();
        } else {
            return false;
        }

        return true;
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for preserve parsing string values.
 *
 * This plugin return no {@link aCssToken CssToken} but ensures that string values will get parsed properly.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssStringParserPlugin extends aCssParserPlugin
{
    /**
     * Current string delimiter char.
     *
     * @var string
     */
    private $delimiterChar = null;

    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['"', "'", "\n"];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return false;
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        // Start of string
        if (($char === '"' || $char === "'") && $state !== 'T_STRING') {
            $this->delimiterChar = $char;
            $this->parser->pushState('T_STRING');
            $this->parser->setExclusive(__CLASS__);
        } // Escaped LF in string => remove escape backslash and LF
        elseif ($char === "\n" && $previousChar === '\\' && $state === 'T_STRING') {
            $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2));
        } // Parse error: Unescaped LF in string literal
        elseif ($char === "\n" && $previousChar !== '\\' && $state === 'T_STRING') {
            $line = $this->parser->getBuffer();
            $this->parser->popState();
            $this->parser->unsetExclusive();
            $this->parser->setBuffer(substr($this->parser->getBuffer(), 0,
                    -1).$this->delimiterChar); // Replace the LF with the current string char
            CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__.': Unterminated string literal',
                $line.'_'));
            $this->delimiterChar = null;
        } // End of string
        elseif ($char === $this->delimiterChar && $state === 'T_STRING') {
            // If the Previous char is a escape char count the amount of the previous escape chars. If the amount of
            // escape chars is uneven do not end the string
            if ($previousChar == '\\') {
                $source = $this->parser->getSource();
                $c = 1;
                $i = $index - 2;
                while (substr($source, $i, 1) === '\\') {
                    $c++;
                    $i--;
                }
                if ($c % 2) {
                    return false;
                }
            }
            $this->parser->popState();
            $this->parser->unsetExclusive();
            $this->delimiterChar = null;
        } else {
            return false;
        }

        return true;
    }
}

/**
 * This {@link aCssMinifierFilter minifier filter} sorts the ruleset declarations of a ruleset by name.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Rowan Beentje <http://assanka.net>
 * @copyright      Rowan Beentje <http://assanka.net>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssSortRulesetPropertiesMinifierFilter extends aCssMinifierFilter
{
    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value larger than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        $r = 0;
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            // Only look for ruleset start rules
            if (get_class($tokens[$i]) !== 'CssRulesetStartToken') {
                continue;
            }
            // Look for the corresponding ruleset end
            $endIndex = false;
            for ($ii = $i + 1; $ii < $l; $ii++) {
                if (get_class($tokens[$ii]) !== 'CssRulesetEndToken') {
                    continue;
                }
                $endIndex = $ii;
                break;
            }
            if (!$endIndex) {
                break;
            }
            $startIndex = $i;
            $i = $endIndex;
            // Skip if there's only one token in this ruleset
            if ($endIndex - $startIndex <= 2) {
                continue;
            }
            // Ensure that everything between the start and end is a declaration token, for safety
            for ($ii = $startIndex + 1; $ii < $endIndex; $ii++) {
                if (get_class($tokens[$ii]) !== 'CssRulesetDeclarationToken') {
                    continue 2;
                }
            }
            $declarations = array_slice($tokens, $startIndex + 1, $endIndex - $startIndex - 1);
            // Check whether a sort is required
            $sortRequired = $lastPropertyName = false;
            foreach ($declarations as $declaration) {
                if ($lastPropertyName) {
                    if (strcmp($lastPropertyName, $declaration->Property) > 0) {
                        $sortRequired = true;
                        break;
                    }
                }
                $lastPropertyName = $declaration->Property;
            }
            if (!$sortRequired) {
                continue;
            }
            // Arrange the declarations alphabetically by name
            usort($declarations, [__CLASS__, 'userDefinedSort1']);
            // Update "IsLast" property
            for ($ii = 0, $ll = count($declarations) - 1; $ii <= $ll; $ii++) {
                if ($ii == $ll) {
                    $declarations[$ii]->IsLast = true;
                } else {
                    $declarations[$ii]->IsLast = false;
                }
            }
            // Splice back into the array.
            array_splice($tokens, $startIndex + 1, $endIndex - $startIndex - 1, $declarations);
            $r += $endIndex - $startIndex - 1;
        }

        return $r;
    }

    /**
     * User defined sort function.
     *
     * @return int
     */
    public static function userDefinedSort1($a, $b)
    {
        return strcmp($a->Property, $b->Property);
    }
}

/**
 * This {@link aCssToken CSS token} represents the start of a ruleset.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssRulesetStartToken extends aCssRulesetStartToken
{
    /**
     * Array of selectors.
     *
     * @var array
     */
    public $Selectors = [];

    /**
     * Set the properties of a ruleset token.
     *
     * @param array $selectors Selectors of the ruleset
     *
     * @return void
     */
    public function __construct(array $selectors = [])
    {
        $this->Selectors = $selectors;
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return implode(',', $this->Selectors).'{';
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing ruleset block with including declarations.
 *
 * Found rulesets will add a {@link CssRulesetStartToken} and {@link CssRulesetEndToken} to the
 * parser; including declarations as {@link CssRulesetDeclarationToken}.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssRulesetParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return [',', '{', '}', ':', ';'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return ['T_DOCUMENT', 'T_AT_MEDIA', 'T_RULESET::SELECTORS', 'T_RULESET', 'T_RULESET_DECLARATION'];
    }

    /**
     * Selectors.
     *
     * @var array
     */
    private $selectors = [];

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        // Start of Ruleset and selectors
        if ($char === ',' && ($state === 'T_DOCUMENT' || $state === 'T_AT_MEDIA' || $state === 'T_RULESET::SELECTORS')) {
            if ($state !== 'T_RULESET::SELECTORS') {
                $this->parser->pushState('T_RULESET::SELECTORS');
            }
            $this->selectors[] = $this->parser->getAndClearBuffer(',{');
        } // End of selectors and start of declarations
        elseif ($char === '{' && ($state === 'T_DOCUMENT' || $state === 'T_AT_MEDIA' || $state === 'T_RULESET::SELECTORS')) {
            if ($this->parser->getBuffer() !== '') {
                $this->selectors[] = $this->parser->getAndClearBuffer(',{');
                if ($state == 'T_RULESET::SELECTORS') {
                    $this->parser->popState();
                }
                $this->parser->pushState('T_RULESET');
                $this->parser->appendToken(new CssRulesetStartToken($this->selectors));
                $this->selectors = [];
            }
        } // Start of declaration
        elseif ($char === ':' && $state === 'T_RULESET') {
            $this->parser->pushState('T_RULESET_DECLARATION');
            $this->buffer = $this->parser->getAndClearBuffer(':;', true);
        } // Unterminated ruleset declaration
        elseif ($char === ':' && $state === 'T_RULESET_DECLARATION') {
            // Ignore Internet Explorer filter declarations
            if ($this->buffer === 'filter') {
                return false;
            }
            CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__.': Unterminated declaration',
                $this->buffer.':'.$this->parser->getBuffer().'_'));
        } // End of declaration
        elseif (($char === ';' || $char === '}') && $state === 'T_RULESET_DECLARATION') {
            $value = $this->parser->getAndClearBuffer(';}');
            if (strtolower(substr($value, -10, 10)) === '!important') {
                $value = trim(substr($value, 0, -10));
                $isImportant = true;
            } else {
                $isImportant = false;
            }
            $this->parser->popState();
            $this->parser->appendToken(new CssRulesetDeclarationToken($this->buffer, $value,
                $this->parser->getMediaTypes(), $isImportant));
            // Declaration ends with a right curly brace; so we have to end the ruleset
            if ($char === '}') {
                $this->parser->appendToken(new CssRulesetEndToken());
                $this->parser->popState();
            }
            $this->buffer = '';
        } // End of ruleset
        elseif ($char === '}' && $state === 'T_RULESET') {
            $this->parser->popState();
            $this->parser->clearBuffer();
            $this->parser->appendToken(new CssRulesetEndToken());
            $this->buffer = '';
            $this->selectors = [];
        } else {
            return false;
        }

        return true;
    }
}

/**
 *  This {@link aCssToken CSS token} represents the end of a ruleset.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssRulesetEndToken extends aCssRulesetEndToken
{
}

/**
 * This {@link aCssToken CSS token} represents a ruleset declaration.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssRulesetDeclarationToken extends aCssDeclarationToken
{
    /**
     * Media types of the declaration.
     *
     * @var array
     */
    public $MediaTypes = ['all'];

    /**
     * Set the properties of a ddocument- or at-rule @media level declaration.
     *
     * @param string $property    Property of the declaration
     * @param string $value       Value of the declaration
     * @param mixed  $mediaTypes  Media types of the declaration
     * @param bool   $isImportant Is the !important flag is set
     * @param bool   $isLast      Is the declaration the last one of the ruleset
     *
     * @return void
     */
    public function __construct($property, $value, $mediaTypes = null, $isImportant = false, $isLast = false)
    {
        parent::__construct($property, $value, $isImportant, $isLast);
        $this->MediaTypes = $mediaTypes ? $mediaTypes : ['all'];
    }
}

/**
 * This {@link aCssMinifierFilter minifier filter} sets the IsLast property of any last declaration in a ruleset,.
 *
 * @font-face      at-rule or @page at-rule block. If the property IsLast is TRUE the decrations will get stringified
 * without tailing semicolon.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssRemoveLastDelarationSemiColonMinifierFilter extends aCssMinifierFilter
{
    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            $current = get_class($tokens[$i]);
            $next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false;
            if (($current === 'CssRulesetDeclarationToken' && $next === 'CssRulesetEndToken') ||
                ($current === 'CssAtFontFaceDeclarationToken' && $next === 'CssAtFontFaceEndToken') ||
                ($current === 'CssAtPageDeclarationToken' && $next === 'CssAtPageEndToken')
            ) {
                $tokens[$i]->IsLast = true;
            }
        }

        return 0;
    }
}

/**
 * This {@link aCssMinifierFilter minifier filter} will remove any empty rulesets (including @keyframes at-rule block
 * rulesets).
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssRemoveEmptyRulesetsMinifierFilter extends aCssMinifierFilter
{
    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        $r = 0;
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            $current = get_class($tokens[$i]);
            $next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false;
            if (($current === 'CssRulesetStartToken' && $next === 'CssRulesetEndToken') ||
                ($current === 'CssAtKeyframesRulesetStartToken' && $next === 'CssAtKeyframesRulesetEndToken' && !array_intersect([
                        'from',
                        '0%',
                        'to',
                        '100%',
                    ], array_map('strtolower', $tokens[$i]->Selectors)))
            ) {
                $tokens[$i] = null;
                $tokens[$i + 1] = null;
                $i++;
                $r = $r + 2;
            }
        }

        return $r;
    }
}

/**
 * This {@link aCssMinifierFilter minifier filter} will remove any empty @font-face, @keyframes, @media and @page
 * at-rule blocks.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssRemoveEmptyAtBlocksMinifierFilter extends aCssMinifierFilter
{
    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        $r = 0;
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            $current = get_class($tokens[$i]);
            $next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false;
            if (($current === 'CssAtFontFaceStartToken' && $next === 'CssAtFontFaceEndToken') ||
                ($current === 'CssAtKeyframesStartToken' && $next === 'CssAtKeyframesEndToken') ||
                ($current === 'CssAtPageStartToken' && $next === 'CssAtPageEndToken') ||
                ($current === 'CssAtMediaStartToken' && $next === 'CssAtMediaEndToken')
            ) {
                $tokens[$i] = null;
                $tokens[$i + 1] = null;
                $i++;
                $r = $r + 2;
            }
        }

        return $r;
    }
}

/**
 * This {@link aCssMinifierFilter minifier filter} will remove any comments from the array of parsed tokens.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssRemoveCommentsMinifierFilter extends aCssMinifierFilter
{
    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        $r = 0;
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            if (get_class($tokens[$i]) === 'CssCommentToken') {
                $tokens[$i] = null;
                $r++;
            }
        }

        return $r;
    }
}

/**
 * CSS Parser.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssParser
{
    /**
     * Parse buffer.
     *
     * @var string
     */
    public $buffer = '';
    /**
     * {@link aCssParserPlugin Plugins}.
     *
     * @var array
     */
    private $plugins = [];
    /**
     * Source to parse.
     *
     * @var string
     */
    private $source = '';
    /**
     * Current state.
     *
     * @var int
     */
    private $state = 'T_DOCUMENT';
    /**
     * Exclusive state.
     *
     * @var string
     */
    private $stateExclusive = false;
    /**
     * Media types state.
     *
     * @var mixed
     */
    private $stateMediaTypes = false;
    /**
     * State stack.
     *
     * @var array
     */
    private $states = ['T_DOCUMENT'];
    /**
     * Parsed tokens.
     *
     * @var array
     */
    private $tokens = [];

    /**
     * Constructer.
     *
     *  Create instances of the used {@link aCssParserPlugin plugins}.
     *
     * @param string $source  CSS source [optional]
     * @param array  $plugins Plugin configuration [optional]
     *
     * @return void
     */
    public function __construct($source = null, array $plugins = null)
    {
        $plugins = array_merge([
            'Comment'     => true,
            'String'      => true,
            'Url'         => true,
            'Expression'  => true,
            'Ruleset'     => true,
            'AtCharset'   => true,
            'AtFontFace'  => true,
            'AtImport'    => true,
            'AtKeyframes' => true,
            'AtMedia'     => true,
            'AtPage'      => true,
            'AtVariables' => true,
        ], is_array($plugins) ? $plugins : []);
        // Create plugin instances
        foreach ($plugins as $name => $config) {
            if ($config !== false) {
                $class = 'Css'.$name.'ParserPlugin';
                $config = is_array($config) ? $config : [];
                if (class_exists($class)) {
                    $this->plugins[] = new $class($this, $config);
                } else {
                    CssMin::triggerError(new CssError(__FILE__, __LINE__,
                        __METHOD__.': The plugin <code>'.$name.'</code> with the class name <code>'.$class.'</code> was not found'));
                }
            }
        }
        if (!is_null($source)) {
            $this->parse($source);
        }
    }

    /**
     * Append a token to the array of tokens.
     *
     * @param aCssToken $token Token to append
     *
     * @return void
     */
    public function appendToken(aCssToken $token)
    {
        $this->tokens[] = $token;
    }

    /**
     * Clears the current buffer.
     *
     * @return void
     */
    public function clearBuffer()
    {
        $this->buffer = '';
    }

    /**
     * Returns and clear the current buffer.
     *
     * @param string $trim    Chars to use to trim the returned buffer
     * @param bool   $tolower if TRUE the returned buffer will get converted to lower case
     *
     * @return string
     */
    public function getAndClearBuffer($trim = '', $tolower = false)
    {
        $r = $this->getBuffer($trim, $tolower);
        $this->buffer = '';

        return $r;
    }

    /**
     * Returns the current buffer.
     *
     * @param string $trim    Chars to use to trim the returned buffer
     * @param bool   $tolower if TRUE the returned buffer will get converted to lower case
     *
     * @return string
     */
    public function getBuffer($trim = '', $tolower = false)
    {
        $r = $this->buffer;
        if ($trim) {
            $r = trim($r, " \t\n\r\0\x0B".$trim);
        }
        if ($tolower) {
            $r = strtolower($r);
        }

        return $r;
    }

    /**
     * Returns the current media types state.
     *
     * @return array
     */
    public function getMediaTypes()
    {
        return $this->stateMediaTypes;
    }

    /**
     * Returns the CSS source.
     *
     * @return string
     */
    public function getSource()
    {
        return $this->source;
    }

    /**
     * Returns the current state.
     *
     * @return int The current state
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * Returns a plugin by class name.
     *
     * @param string $name Class name of the plugin
     *
     * @return aCssParserPlugin
     */
    public function getPlugin($class)
    {
        static $index = null;
        if (is_null($index)) {
            $index = [];
            for ($i = 0, $l = count($this->plugins); $i < $l; $i++) {
                $index[get_class($this->plugins[$i])] = $i;
            }
        }

        return isset($index[$class]) ? $this->plugins[$index[$class]] : false;
    }

    /**
     * Returns the parsed tokens.
     *
     * @return array
     */
    public function getTokens()
    {
        return $this->tokens;
    }

    /**
     * Returns if the current state equals the passed state.
     *
     * @param int $state State to compare with the current state
     *
     * @return bool TRUE is the state equals to the passed state; FALSE if not
     */
    public function isState($state)
    {
        return $this->state == $state;
    }

    /**
     * Parse the CSS source and return a array with parsed tokens.
     *
     * @param string $source CSS source
     *
     * @return array Array with tokens
     */
    public function parse($source)
    {
        // Reset
        $this->source = '';
        $this->tokens = [];
        // Create a global and plugin lookup table for trigger chars; set array of plugins as local variable and create
        // several helper variables for plugin handling
        $globalTriggerChars = '';
        $plugins = $this->plugins;
        $pluginCount = count($plugins);
        $pluginIndex = [];
        $pluginTriggerStates = [];
        $pluginTriggerChars = [];
        for ($i = 0, $l = count($plugins); $i < $l; $i++) {
            $tPluginClassName = get_class($plugins[$i]);
            $pluginTriggerChars[$i] = implode('', $plugins[$i]->getTriggerChars());
            $tPluginTriggerStates = $plugins[$i]->getTriggerStates();
            $pluginTriggerStates[$i] = $tPluginTriggerStates === false ? false : '|'.implode('|',
                    $tPluginTriggerStates).'|';
            $pluginIndex[$tPluginClassName] = $i;
            for ($ii = 0, $ll = strlen($pluginTriggerChars[$i]); $ii < $ll; $ii++) {
                $c = substr($pluginTriggerChars[$i], $ii, 1);
                if (strpos($globalTriggerChars, $c) === false) {
                    $globalTriggerChars .= $c;
                }
            }
        }
        // Normalise line endings
        $source = str_replace("\r\n", "\n", $source);    // Windows to Unix line endings
        $source = str_replace("\r", "\n", $source);        // Mac to Unix line endings
        $this->source = $source;
        // Variables
        $buffer = &$this->buffer;
        $exclusive = &$this->stateExclusive;
        $state = &$this->state;
        $c = $p = null;
        // --
        for ($i = 0, $l = strlen($source); $i < $l; $i++) {
            // Set the current Char
            $c = $source[$i]; // Is faster than: $c = substr($source, $i, 1);
            // Normalize and filter double whitespace characters
            if ($exclusive === false) {
                if ($c === "\n" || $c === "\t") {
                    $c = ' ';
                }
                if ($c === ' ' && $p === ' ') {
                    continue;
                }
            }
            $buffer .= $c;
            // Extended processing only if the current char is a global trigger char
            if (strpos($globalTriggerChars, $c) !== false) {
                // Exclusive state is set; process with the exclusive plugin
                if ($exclusive) {
                    $tPluginIndex = $pluginIndex[$exclusive];
                    if (strpos($pluginTriggerChars[$tPluginIndex],
                            $c) !== false && ($pluginTriggerStates[$tPluginIndex] === false || strpos($pluginTriggerStates[$tPluginIndex],
                                $state) !== false)
                    ) {
                        $r = $plugins[$tPluginIndex]->parse($i, $c, $p, $state);
                        // Return value is TRUE => continue with next char
                        if ($r === true) {
                            continue;
                        } // Return value is numeric => set new index and continue with next char
                        elseif ($r !== false && $r != $i) {
                            $i = $r;
                            continue;
                        }
                    }
                } // Else iterate through the plugins
                else {
                    $triggerState = '|'.$state.'|';
                    for ($ii = 0, $ll = $pluginCount; $ii < $ll; $ii++) {
                        // Only process if the current char is one of the plugin trigger chars
                        if (strpos($pluginTriggerChars[$ii],
                                $c) !== false && ($pluginTriggerStates[$ii] === false || strpos($pluginTriggerStates[$ii],
                                    $triggerState) !== false)
                        ) {
                            // Process with the plugin
                            $r = $plugins[$ii]->parse($i, $c, $p, $state);
                            // Return value is TRUE => break the plugin loop and and continue with next char
                            if ($r === true) {
                                break;
                            } // Return value is numeric => set new index, break the plugin loop and and continue with next char
                            elseif ($r !== false && $r != $i) {
                                $i = $r;
                                break;
                            }
                        }
                    }
                }
            }
            $p = $c; // Set the parent char
        }

        return $this->tokens;
    }

    /**
     * Remove the last state of the state stack and return the removed stack value.
     *
     * @return int Removed state value
     */
    public function popState()
    {
        $r = array_pop($this->states);
        $this->state = $this->states[count($this->states) - 1];

        return $r;
    }

    /**
     * Adds a new state onto the state stack.
     *
     * @param int $state State to add onto the state stack.
     *
     * @return int The index of the added state in the state stacks
     */
    public function pushState($state)
    {
        $r = array_push($this->states, $state);
        $this->state = $this->states[count($this->states) - 1];

        return $r;
    }

    /**
     * Sets/restores the buffer.
     *
     * @param string $buffer Buffer to set
     *
     * @return void
     */
    public function setBuffer($buffer)
    {
        $this->buffer = $buffer;
    }

    /**
     * Set the exclusive state.
     *
     * @param string $exclusive Exclusive state
     *
     * @return void
     */
    public function setExclusive($exclusive)
    {
        $this->stateExclusive = $exclusive;
    }

    /**
     * Set the media types state.
     *
     * @param array $mediaTypes Media types state
     *
     * @return void
     */
    public function setMediaTypes(array $mediaTypes)
    {
        $this->stateMediaTypes = $mediaTypes;
    }

    /**
     * Sets the current state in the state stack; equals to {@link CssParser::popState()} + {@link * CssParser::pushState()}.
     *
     * @param int $state State to set
     *
     * @return int
     */
    public function setState($state)
    {
        $r = array_pop($this->states);
        array_push($this->states, $state);
        $this->state = $this->states[count($this->states) - 1];

        return $r;
    }

    /**
     * Removes the exclusive state.
     *
     * @return void
     */
    public function unsetExclusive()
    {
        $this->stateExclusive = false;
    }

    /**
     * Removes the media types state.
     *
     * @return void
     */
    public function unsetMediaTypes()
    {
        $this->stateMediaTypes = false;
    }
}

/**
 * {@link aCssFromatter Formatter} returning the CSS source in {@link http://goo.gl/j4XdU OTBS indent style} (The One
 * True Brace Style).
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssOtbsFormatter extends aCssFormatter
{
    /**
     * Implements {@link aCssFormatter::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        $r = [];
        $level = 0;
        for ($i = 0, $l = count($this->tokens); $i < $l; $i++) {
            $token = $this->tokens[$i];
            $class = get_class($token);
            $indent = str_repeat($this->indent, $level);
            if ($class === 'CssCommentToken') {
                $lines = array_map('trim', explode("\n", $token->Comment));
                for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++) {
                    $r[] = $indent.(substr($lines[$ii], 0, 1) == '*' ? ' ' : '').$lines[$ii];
                }
            } elseif ($class === 'CssAtCharsetToken') {
                $r[] = $indent.'@charset '.$token->Charset.';';
            } elseif ($class === 'CssAtFontFaceStartToken') {
                $r[] = $indent.'@font-face {';
                $level++;
            } elseif ($class === 'CssAtImportToken') {
                $r[] = $indent.'@import '.$token->Import.' '.implode(', ', $token->MediaTypes).';';
            } elseif ($class === 'CssAtKeyframesStartToken') {
                $r[] = $indent.'@keyframes "'.$token->Name.'" {';
                $level++;
            } elseif ($class === 'CssAtMediaStartToken') {
                $r[] = $indent.'@media '.implode(', ', $token->MediaTypes).' {';
                $level++;
            } elseif ($class === 'CssAtPageStartToken') {
                $r[] = $indent.'@page {';
                $level++;
            } elseif ($class === 'CssAtVariablesStartToken') {
                $r[] = $indent.'@variables '.implode(', ', $token->MediaTypes).' {';
                $level++;
            } elseif ($class === 'CssRulesetStartToken' || $class === 'CssAtKeyframesRulesetStartToken') {
                $r[] = $indent.implode(', ', $token->Selectors).' {';
                $level++;
            } elseif ($class == 'CssAtFontFaceDeclarationToken'
                || $class === 'CssAtKeyframesRulesetDeclarationToken'
                || $class === 'CssAtPageDeclarationToken'
                || $class == 'CssAtVariablesDeclarationToken'
                || $class === 'CssRulesetDeclarationToken'
            ) {
                $declaration = $indent.$token->Property.': ';
                if ($this->padding) {
                    $declaration = str_pad($declaration, $this->padding, ' ', STR_PAD_RIGHT);
                }
                $r[] = $declaration.$token->Value.($token->IsImportant ? ' !important' : '').';';
            } elseif ($class === 'CssAtFontFaceEndToken'
                || $class === 'CssAtMediaEndToken'
                || $class === 'CssAtKeyframesEndToken'
                || $class === 'CssAtKeyframesRulesetEndToken'
                || $class === 'CssAtPageEndToken'
                || $class === 'CssAtVariablesEndToken'
                || $class === 'CssRulesetEndToken'
            ) {
                $level--;
                $r[] = str_repeat($indent, $level).'}';
            }
        }

        return implode("\n", $r);
    }
}

/**
 * This {@link aCssToken CSS token} is a utility token that extends {@link aNullToken} and returns only a empty string.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssNullToken extends aCssToken
{
    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '';
    }
}

/**
 * CSS Minifier.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssMinifier
{
    /**
     * {@link aCssMinifierFilter Filters}.
     *
     * @var array
     */
    private $filters = [];
    /**
     * {@link aCssMinifierPlugin Plugins}.
     *
     * @var array
     */
    private $plugins = [];
    /**
     * Minified source.
     *
     * @var string
     */
    private $minified = '';

    /**
     * Constructer.
     *
     * Creates instances of {@link aCssMinifierFilter filters} and {@link aCssMinifierPlugin plugins}.
     *
     * @param string $source  CSS source [optional]
     * @param array  $filters Filter configuration [optional]
     * @param array  $plugins Plugin configuration [optional]
     *
     * @return void
     */
    public function __construct($source = null, array $filters = null, array $plugins = null)
    {
        $filters = array_merge([
            'ImportImports'                 => false,
            'RemoveComments'                => true,
            'RemoveEmptyRulesets'           => true,
            'RemoveEmptyAtBlocks'           => true,
            'ConvertLevel3Properties'       => false,
            'ConvertLevel3AtKeyframes'      => false,
            'Variables'                     => true,
            'RemoveLastDelarationSemiColon' => true,
        ], is_array($filters) ? $filters : []);
        $plugins = array_merge([
            'Variables'                => true,
            'ConvertFontWeight'        => false,
            'ConvertHslColors'         => false,
            'ConvertRgbColors'         => false,
            'ConvertNamedColors'       => false,
            'CompressColorValues'      => false,
            'CompressUnitValues'       => false,
            'CompressExpressionValues' => false,
        ], is_array($plugins) ? $plugins : []);
        // Filters
        foreach ($filters as $name => $config) {
            if ($config !== false) {
                $class = 'Css'.$name.'MinifierFilter';
                $config = is_array($config) ? $config : [];
                if (class_exists($class)) {
                    $this->filters[] = new $class($this, $config);
                } else {
                    CssMin::triggerError(new CssError(__FILE__, __LINE__,
                        __METHOD__.': The filter <code>'.$name.'</code> with the class name <code>'.$class.'</code> was not found'));
                }
            }
        }
        // Plugins
        foreach ($plugins as $name => $config) {
            if ($config !== false) {
                $class = 'Css'.$name.'MinifierPlugin';
                $config = is_array($config) ? $config : [];
                if (class_exists($class)) {
                    $this->plugins[] = new $class($this, $config);
                } else {
                    CssMin::triggerError(new CssError(__FILE__, __LINE__,
                        __METHOD__.': The plugin <code>'.$name.'</code> with the class name <code>'.$class.'</code> was not found'));
                }
            }
        }
        // --
        if (!is_null($source)) {
            $this->minify($source);
        }
    }

    /**
     * Returns the minified Source.
     *
     * @return string
     */
    public function getMinified()
    {
        return $this->minified;
    }

    /**
     * Returns a plugin by class name.
     *
     * @param string $name Class name of the plugin
     *
     * @return aCssMinifierPlugin
     */
    public function getPlugin($class)
    {
        static $index = null;
        if (is_null($index)) {
            $index = [];
            for ($i = 0, $l = count($this->plugins); $i < $l; $i++) {
                $index[get_class($this->plugins[$i])] = $i;
            }
        }

        return isset($index[$class]) ? $this->plugins[$index[$class]] : false;
    }

    /**
     * Minifies the CSS source.
     *
     * @param string $source CSS source
     *
     * @return string
     */
    public function minify($source)
    {
        // Variables
        $r = '';
        $parser = new CssParser($source);
        $tokens = $parser->getTokens();
        $filters = $this->filters;
        $filterCount = count($this->filters);
        $plugins = $this->plugins;
        $pluginCount = count($plugins);
        $pluginIndex = [];
        $pluginTriggerTokens = [];
        $globalTriggerTokens = [];
        for ($i = 0, $l = count($plugins); $i < $l; $i++) {
            $tPluginClassName = get_class($plugins[$i]);
            $pluginTriggerTokens[$i] = $plugins[$i]->getTriggerTokens();
            foreach ($pluginTriggerTokens[$i] as $v) {
                if (!in_array($v, $globalTriggerTokens)) {
                    $globalTriggerTokens[] = $v;
                }
            }
            $pluginTriggerTokens[$i] = '|'.implode('|', $pluginTriggerTokens[$i]).'|';
            $pluginIndex[$tPluginClassName] = $i;
        }
        $globalTriggerTokens = '|'.implode('|', $globalTriggerTokens).'|';
        /*
         * Apply filters
         */
        for ($i = 0; $i < $filterCount; $i++) {
            // Apply the filter; if the return value is larger than 0...
            if ($filters[$i]->apply($tokens) > 0) {
                // ...then filter null values and rebuild the token array
                $tokens = array_values(array_filter($tokens));
            }
        }
        $tokenCount = count($tokens);
        /*
         * Apply plugins
         */
        for ($i = 0; $i < $tokenCount; $i++) {
            $triggerToken = '|'.get_class($tokens[$i]).'|';
            if (strpos($globalTriggerTokens, $triggerToken) !== false) {
                for ($ii = 0; $ii < $pluginCount; $ii++) {
                    if (strpos($pluginTriggerTokens[$ii],
                            $triggerToken) !== false || $pluginTriggerTokens[$ii] === false
                    ) {
                        // Apply the plugin; if the return value is TRUE continue to the next token
                        if ($plugins[$ii]->apply($tokens[$i]) === true) {
                            continue 2;
                        }
                    }
                }
            }
        }
        // Stringify the tokens
        for ($i = 0; $i < $tokenCount; $i++) {
            $r .= (string) $tokens[$i];
        }
        $this->minified = $r;

        return $r;
    }
}

/**
 * CssMin - A (simple) css minifier with benefits.
 *
 * --
 * Copyright (c) 2011 Joe Scylla <joe.scylla@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * --
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssMin
{
    /**
     * Index of classes.
     *
     * @var array
     */
    private static $classIndex = [];
    /**
     * Parse/minify errors.
     *
     * @var array
     */
    private static $errors = [];
    /**
     * Verbose output.
     *
     * @var bool
     */
    private static $isVerbose = false;

    /**
     * {@link http://goo.gl/JrW54 Autoload} function of CssMin.
     *
     * @param string $class Name of the class
     *
     * @return void
     */
    public static function autoload($class)
    {
        if (isset(self::$classIndex[$class])) {
            require self::$classIndex[$class];
        }
    }

    /**
     * Return errors.
     *
     * @return array of {CssError}.
     */
    public static function getErrors()
    {
        return self::$errors;
    }

    /**
     * Returns if there were errors.
     *
     * @return bool
     */
    public static function hasErrors()
    {
        return count(self::$errors) > 0;
    }

    /**
     * Initialises CssMin.
     *
     * @return void
     */
    public static function initialise()
    {
        // Create the class index for autoloading or including
        $paths = [dirname(__FILE__)];
        while (list($i, $path) = each($paths)) {
            $subDirectorys = glob($path.'*', GLOB_MARK | GLOB_ONLYDIR | GLOB_NOSORT);
            if (is_array($subDirectorys)) {
                foreach ($subDirectorys as $subDirectory) {
                    $paths[] = $subDirectory;
                }
            }
            $files = glob($path.'*.php', 0);
            if (is_array($files)) {
                foreach ($files as $file) {
                    $class = substr(basename($file), 0, -4);
                    self::$classIndex[$class] = $file;
                }
            }
        }
        krsort(self::$classIndex);
        // Only use autoloading if spl_autoload_register() is available and no __autoload() is defined (because
        // __autoload() breaks if spl_autoload_register() is used.
        if (function_exists('spl_autoload_register') && !is_callable('__autoload')) {
            spl_autoload_register([__CLASS__, 'autoload']);
        } // Otherwise include all class files
        else {
            foreach (self::$classIndex as $class => $file) {
                if (!class_exists($class)) {
                    require_once $file;
                }
            }
        }
    }

    /**
     * Minifies CSS source.
     *
     * @param string $source  CSS source
     * @param array  $filters Filter configuration [optional]
     * @param array  $plugins Plugin configuration [optional]
     *
     * @return string Minified CSS
     */
    public static function minify($source, array $filters = null, array $plugins = null)
    {
        self::$errors = [];
        $minifier = new CssMinifier($source, $filters, $plugins);

        return $minifier->getMinified();
    }

    /**
     * Parse the CSS source.
     *
     * @param string $source  CSS source
     * @param array  $plugins Plugin configuration [optional]
     *
     * @return array Array of aCssToken
     */
    public static function parse($source, array $plugins = null)
    {
        self::$errors = [];
        $parser = new CssParser($source, $plugins);

        return $parser->getTokens();
    }

    /**
     * --.
     *
     * @param bool $to
     *
     * @return bool
     */
    public static function setVerbose($to)
    {
        self::$isVerbose = (bool) $to;

        return self::$isVerbose;
    }

    /**
     * --.
     *
     * @param CssError $error
     *
     * @return void
     */
    public static function triggerError(CssError $error)
    {
        self::$errors[] = $error;
        if (self::$isVerbose) {
            trigger_error((string) $error, E_USER_WARNING);
        }
    }
}

// Initialises CssMin
CssMin::initialise();

/**
 * This {@link aCssMinifierFilter minifier filter} import external css files defined with the @import at-rule into the
 * current stylesheet.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssImportImportsMinifierFilter extends aCssMinifierFilter
{
    /**
     * Array with already imported external stylesheets.
     *
     * @var array
     */
    private $imported = [];

    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        if (!isset($this->configuration['BasePath']) || !is_dir($this->configuration['BasePath'])) {
            CssMin::triggerError(new CssError(__FILE__, __LINE__,
                __METHOD__.': Base path <code>'.($this->configuration['BasePath'] ? $this->configuration['BasePath'] : 'null').'</code> is not a directory'));

            return 0;
        }
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            if (get_class($tokens[$i]) === 'CssAtImportToken') {
                $import = $this->configuration['BasePath'].'/'.$tokens[$i]->Import;
                // Import file was not found/is not a file
                if (!is_file($import)) {
                    CssMin::triggerError(new CssError(__FILE__, __LINE__,
                        __METHOD__.': Import file <code>'.$import.'</code> was not found.', (string) $tokens[$i]));
                } // Import file already imported; remove this @import at-rule to prevent recursions
                elseif (in_array($import, $this->imported)) {
                    CssMin::triggerError(new CssError(__FILE__, __LINE__,
                        __METHOD__.': Import file <code>'.$import.'</code> was already imported.',
                        (string) $tokens[$i]));
                    $tokens[$i] = null;
                } else {
                    $this->imported[] = $import;
                    $parser = new CssParser(file_get_contents($import));
                    $import = $parser->getTokens();
                    // The @import at-rule has media types defined requiring special handling
                    if (count($tokens[$i]->MediaTypes) > 0 && !(count($tokens[$i]->MediaTypes) == 1 && $tokens[$i]->MediaTypes[0] == 'all')) {
                        $blocks = [];
                        /*
                         * Filter or set media types of @import at-rule or remove the @import at-rule if no media type is matching the parent @import at-rule
                         */
                        for ($ii = 0, $ll = count($import); $ii < $ll; $ii++) {
                            if (get_class($import[$ii]) === 'CssAtImportToken') {
                                // @import at-rule defines no media type or only the "all" media type; set the media types to the one defined in the parent @import at-rule
                                if (count($import[$ii]->MediaTypes) == 0 || (count($import[$ii]->MediaTypes) == 1 && $import[$ii]->MediaTypes[0] == 'all')) {
                                    $import[$ii]->MediaTypes = $tokens[$i]->MediaTypes;
                                } // @import at-rule defineds one or more media types; filter out media types not matching with the  parent @import at-rule
                                elseif (count($import[$ii]->MediaTypes > 0)) {
                                    foreach ($import[$ii]->MediaTypes as $index => $mediaType) {
                                        if (!in_array($mediaType, $tokens[$i]->MediaTypes)) {
                                            unset($import[$ii]->MediaTypes[$index]);
                                        }
                                    }
                                    $import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes);
                                    // If there are no media types left in the @import at-rule remove the @import at-rule
                                    if (count($import[$ii]->MediaTypes) == 0) {
                                        $import[$ii] = null;
                                    }
                                }
                            }
                        }
                        /*
                         * Remove media types of @media at-rule block not defined in the @import at-rule
                         */
                        for ($ii = 0, $ll = count($import); $ii < $ll; $ii++) {
                            if (get_class($import[$ii]) === 'CssAtMediaStartToken') {
                                foreach ($import[$ii]->MediaTypes as $index => $mediaType) {
                                    if (!in_array($mediaType, $tokens[$i]->MediaTypes)) {
                                        unset($import[$ii]->MediaTypes[$index]);
                                    }
                                    $import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes);
                                }
                            }
                        }
                        /*
                         * If no media types left of the @media at-rule block remove the complete block
                         */
                        for ($ii = 0, $ll = count($import); $ii < $ll; $ii++) {
                            if (get_class($import[$ii]) === 'CssAtMediaStartToken') {
                                if (count($import[$ii]->MediaTypes) === 0) {
                                    for ($iii = $ii; $iii < $ll; $iii++) {
                                        if (get_class($import[$iii]) === 'CssAtMediaEndToken') {
                                            break;
                                        }
                                    }
                                    if (get_class($import[$iii]) === 'CssAtMediaEndToken') {
                                        array_splice($import, $ii, $iii - $ii + 1, []);
                                        $ll = count($import);
                                    }
                                }
                            }
                        }
                        /*
                         * If the media types of the @media at-rule equals the media types defined in the @import
                         * at-rule remove the CssAtMediaStartToken and CssAtMediaEndToken token
                         */
                        for ($ii = 0, $ll = count($import); $ii < $ll; $ii++) {
                            if (get_class($import[$ii]) === 'CssAtMediaStartToken' && count(array_diff($tokens[$i]->MediaTypes,
                                    $import[$ii]->MediaTypes)) === 0
                            ) {
                                for ($iii = $ii; $iii < $ll; $iii++) {
                                    if (get_class($import[$iii]) == 'CssAtMediaEndToken') {
                                        break;
                                    }
                                }
                                if (get_class($import[$iii]) == 'CssAtMediaEndToken') {
                                    unset($import[$ii]);
                                    unset($import[$iii]);
                                    $import = array_values($import);
                                    $ll = count($import);
                                }
                            }
                        }
                        /*
                         * Extract CssAtImportToken and CssAtCharsetToken tokens
                         */
                        for ($ii = 0, $ll = count($import); $ii < $ll; $ii++) {
                            $class = get_class($import[$ii]);
                            if ($class === 'CssAtImportToken' || $class === 'CssAtCharsetToken') {
                                $blocks = array_merge($blocks, array_splice($import, $ii, 1, []));
                                $ll = count($import);
                            }
                        }
                        /*
                         * Extract the @font-face, @media and @page at-rule block
                         */
                        for ($ii = 0, $ll = count($import); $ii < $ll; $ii++) {
                            $class = get_class($import[$ii]);
                            if ($class === 'CssAtFontFaceStartToken' || $class === 'CssAtMediaStartToken' || $class === 'CssAtPageStartToken' || $class === 'CssAtVariablesStartToken') {
                                for ($iii = $ii; $iii < $ll; $iii++) {
                                    $class = get_class($import[$iii]);
                                    if ($class === 'CssAtFontFaceEndToken' || $class === 'CssAtMediaEndToken' || $class === 'CssAtPageEndToken' || $class === 'CssAtVariablesEndToken') {
                                        break;
                                    }
                                }
                                $class = get_class($import[$iii]);
                                if (isset($import[$iii]) && ($class === 'CssAtFontFaceEndToken' || $class === 'CssAtMediaEndToken' || $class === 'CssAtPageEndToken' || $class === 'CssAtVariablesEndToken')) {
                                    $blocks = array_merge($blocks, array_splice($import, $ii, $iii - $ii + 1, []));
                                    $ll = count($import);
                                }
                            }
                        }
                        // Create the import array with extracted tokens and the rulesets wrapped into a @media at-rule block
                        $import = array_merge($blocks, [new CssAtMediaStartToken($tokens[$i]->MediaTypes)], $import,
                            [new CssAtMediaEndToken()]);
                    }
                    // Insert the imported tokens
                    array_splice($tokens, $i, 1, $import);
                    // Modify parameters of the for-loop
                    $i--;
                    $l = count($tokens);
                }
            }
        }
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for preserve parsing expression() declaration values.
 *
 * This plugin return no {@link aCssToken CssToken} but ensures that expression() declaration values will get parsed
 * properly.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssExpressionParserPlugin extends aCssParserPlugin
{
    /**
     * Count of left braces.
     *
     * @var int
     */
    private $leftBraces = 0;
    /**
     * Count of right braces.
     *
     * @var int
     */
    private $rightBraces = 0;

    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['(', ')', ';', '}'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return false;
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        // Start of expression
        if ($char === '(' && strtolower(substr($this->parser->getSource(), $index - 10,
                11)) === 'expression(' && $state !== 'T_EXPRESSION'
        ) {
            $this->parser->pushState('T_EXPRESSION');
            $this->leftBraces++;
        } // Count left braces
        elseif ($char === '(' && $state === 'T_EXPRESSION') {
            $this->leftBraces++;
        } // Count right braces
        elseif ($char === ')' && $state === 'T_EXPRESSION') {
            $this->rightBraces++;
        } // Possible end of expression; if left and right braces are equal the expressen ends
        elseif (($char === ';' || $char === '}') && $state === 'T_EXPRESSION' && $this->leftBraces === $this->rightBraces) {
            $this->leftBraces = $this->rightBraces = 0;
            $this->parser->popState();

            return $index - 1;
        } else {
            return false;
        }

        return true;
    }
}

/**
 * CSS Error.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssError
{
    /**
     * File.
     *
     * @var string
     */
    public $File = '';
    /**
     * Line.
     *
     * @var int
     */
    public $Line = 0;
    /**
     * Error message.
     *
     * @var string
     */
    public $Message = '';
    /**
     * Source.
     *
     * @var string
     */
    public $Source = '';

    /**
     * Constructor triggering the error.
     *
     * @param string $message Error message
     * @param string $source  Corresponding line [optional]
     *
     * @return void
     */
    public function __construct($file, $line, $message, $source = '')
    {
        $this->File = $file;
        $this->Line = $line;
        $this->Message = $message;
        $this->Source = $source;
    }

    /**
     * Returns the error as formatted string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->Message.($this->Source ? ': <br /><code>'.$this->Source.'</code>' : '').'<br />in file '.$this->File.' at line '.$this->Line;
    }
}

/**
 * This {@link aCssMinifierPlugin} will convert a color value in rgb notation to hexadecimal notation.
 *
 * Example:
 * <code>
 * color: rgb(200,60%,5);
 * </code>
 *
 * Will get converted to:
 * <code>
 * color:#c89905;
 * </code>
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssConvertRgbColorsMinifierPlugin extends aCssMinifierPlugin
{
    /**
     * Regular expression matching the value.
     *
     * @var string
     */
    private $reMatch = "/rgb\s*\(\s*([0-9%]+)\s*,\s*([0-9%]+)\s*,\s*([0-9%]+)\s*\)/iS";

    /**
     * Implements {@link aCssMinifierPlugin::minify()}.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    public function apply(aCssToken &$token)
    {
        if (stripos($token->Value, 'rgb') !== false && preg_match($this->reMatch, $token->Value, $m)) {
            for ($i = 1, $l = count($m); $i < $l; $i++) {
                if (strpos('%', $m[$i]) !== false) {
                    $m[$i] = substr($m[$i], 0, -1);
                    $m[$i] = (int) (256 * ($m[$i] / 100));
                }
                $m[$i] = str_pad(dechex($m[$i]), 2, '0', STR_PAD_LEFT);
            }
            $token->Value = str_replace($m[0], '#'.$m[1].$m[2].$m[3], $token->Value);
        }

        return false;
    }

    /**
     * Implements {@link aMinifierPlugin::getTriggerTokens()}.
     *
     * @return array
     */
    public function getTriggerTokens()
    {
        return [
            'CssAtFontFaceDeclarationToken',
            'CssAtPageDeclarationToken',
            'CssRulesetDeclarationToken',
        ];
    }
}

/**
 * This {@link aCssMinifierPlugin} will convert named color values to hexadecimal notation.
 *
 * Example:
 * <code>
 * color: black;
 * border: 1px solid indigo;
 * </code>
 *
 * Will get converted to:
 * <code>
 * color:#000;
 * border:1px solid #4b0082;
 * </code>
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssConvertNamedColorsMinifierPlugin extends aCssMinifierPlugin
{
    /**
     * Regular expression matching the value.
     *
     * @var string
     */
    private $reMatch = null;
    /**
     * Regular expression replacing the value.
     *
     * @var string
     */
    private $reReplace = '"${1}" . $this->transformation[strtolower("${2}")] . "${3}"';
    /**
     * Transformation table used by the {@link CssConvertNamedColorsMinifierPlugin::$reReplace replace regular
     * expression}.
     *
     * @var array
     */
    private $transformation = [
        'aliceblue'            => '#f0f8ff',
        'antiquewhite'         => '#faebd7',
        'aqua'                 => '#0ff',
        'aquamarine'           => '#7fffd4',
        'azure'                => '#f0ffff',
        'beige'                => '#f5f5dc',
        'black'                => '#000',
        'blue'                 => '#00f',
        'blueviolet'           => '#8a2be2',
        'brown'                => '#a52a2a',
        'burlywood'            => '#deb887',
        'cadetblue'            => '#5f9ea0',
        'chartreuse'           => '#7fff00',
        'chocolate'            => '#d2691e',
        'coral'                => '#ff7f50',
        'cornflowerblue'       => '#6495ed',
        'cornsilk'             => '#fff8dc',
        'crimson'              => '#dc143c',
        'darkblue'             => '#00008b',
        'darkcyan'             => '#008b8b',
        'darkgoldenrod'        => '#b8860b',
        'darkgray'             => '#a9a9a9',
        'darkgreen'            => '#006400',
        'darkkhaki'            => '#bdb76b',
        'darkmagenta'          => '#8b008b',
        'darkolivegreen'       => '#556b2f',
        'darkorange'           => '#ff8c00',
        'darkorchid'           => '#9932cc',
        'darkred'              => '#8b0000',
        'darksalmon'           => '#e9967a',
        'darkseagreen'         => '#8fbc8f',
        'darkslateblue'        => '#483d8b',
        'darkslategray'        => '#2f4f4f',
        'darkturquoise'        => '#00ced1',
        'darkviolet'           => '#9400d3',
        'deeppink'             => '#ff1493',
        'deepskyblue'          => '#00bfff',
        'dimgray'              => '#696969',
        'dodgerblue'           => '#1e90ff',
        'firebrick'            => '#b22222',
        'floralwhite'          => '#fffaf0',
        'forestgreen'          => '#228b22',
        'fuchsia'              => '#f0f',
        'gainsboro'            => '#dcdcdc',
        'ghostwhite'           => '#f8f8ff',
        'gold'                 => '#ffd700',
        'goldenrod'            => '#daa520',
        'gray'                 => '#808080',
        'green'                => '#008000',
        'greenyellow'          => '#adff2f',
        'honeydew'             => '#f0fff0',
        'hotpink'              => '#ff69b4',
        'indianred'            => '#cd5c5c',
        'indigo'               => '#4b0082',
        'ivory'                => '#fffff0',
        'khaki'                => '#f0e68c',
        'lavender'             => '#e6e6fa',
        'lavenderblush'        => '#fff0f5',
        'lawngreen'            => '#7cfc00',
        'lemonchiffon'         => '#fffacd',
        'lightblue'            => '#add8e6',
        'lightcoral'           => '#f08080',
        'lightcyan'            => '#e0ffff',
        'lightgoldenrodyellow' => '#fafad2',
        'lightgreen'           => '#90ee90',
        'lightgrey'            => '#d3d3d3',
        'lightpink'            => '#ffb6c1',
        'lightsalmon'          => '#ffa07a',
        'lightseagreen'        => '#20b2aa',
        'lightskyblue'         => '#87cefa',
        'lightslategray'       => '#789',
        'lightsteelblue'       => '#b0c4de',
        'lightyellow'          => '#ffffe0',
        'lime'                 => '#0f0',
        'limegreen'            => '#32cd32',
        'linen'                => '#faf0e6',
        'maroon'               => '#800000',
        'mediumaquamarine'     => '#66cdaa',
        'mediumblue'           => '#0000cd',
        'mediumorchid'         => '#ba55d3',
        'mediumpurple'         => '#9370db',
        'mediumseagreen'       => '#3cb371',
        'mediumslateblue'      => '#7b68ee',
        'mediumspringgreen'    => '#00fa9a',
        'mediumturquoise'      => '#48d1cc',
        'mediumvioletred'      => '#c71585',
        'midnightblue'         => '#191970',
        'mintcream'            => '#f5fffa',
        'mistyrose'            => '#ffe4e1',
        'moccasin'             => '#ffe4b5',
        'navajowhite'          => '#ffdead',
        'navy'                 => '#000080',
        'oldlace'              => '#fdf5e6',
        'olive'                => '#808000',
        'olivedrab'            => '#6b8e23',
        'orange'               => '#ffa500',
        'orangered'            => '#ff4500',
        'orchid'               => '#da70d6',
        'palegoldenrod'        => '#eee8aa',
        'palegreen'            => '#98fb98',
        'paleturquoise'        => '#afeeee',
        'palevioletred'        => '#db7093',
        'papayawhip'           => '#ffefd5',
        'peachpuff'            => '#ffdab9',
        'peru'                 => '#cd853f',
        'pink'                 => '#ffc0cb',
        'plum'                 => '#dda0dd',
        'powderblue'           => '#b0e0e6',
        'purple'               => '#800080',
        'red'                  => '#f00',
        'rosybrown'            => '#bc8f8f',
        'royalblue'            => '#4169e1',
        'saddlebrown'          => '#8b4513',
        'salmon'               => '#fa8072',
        'sandybrown'           => '#f4a460',
        'seagreen'             => '#2e8b57',
        'seashell'             => '#fff5ee',
        'sienna'               => '#a0522d',
        'silver'               => '#c0c0c0',
        'skyblue'              => '#87ceeb',
        'slateblue'            => '#6a5acd',
        'slategray'            => '#708090',
        'snow'                 => '#fffafa',
        'springgreen'          => '#00ff7f',
        'steelblue'            => '#4682b4',
        'tan'                  => '#d2b48c',
        'teal'                 => '#008080',
        'thistle'              => '#d8bfd8',
        'tomato'               => '#ff6347',
        'turquoise'            => '#40e0d0',
        'violet'               => '#ee82ee',
        'wheat'                => '#f5deb3',
        'white'                => '#fff',
        'whitesmoke'           => '#f5f5f5',
        'yellow'               => '#ff0',
        'yellowgreen'          => '#9acd32',
    ];

    /**
     * Overwrites {@link aCssMinifierPlugin::__construct()}.
     *
     * The constructor will create the {@link CssConvertNamedColorsMinifierPlugin::$reReplace replace regular
     * expression} based on the {@link CssConvertNamedColorsMinifierPlugin::$transformation transformation table}.
     *
     * @param CssMinifier $minifier      The CssMinifier object of this plugin.
     * @param array       $configuration Plugin configuration [optional]
     *
     * @return void
     */
    public function __construct(CssMinifier $minifier, array $configuration = [])
    {
        $this->reMatch = "/(^|\s)+(".implode('|', array_keys($this->transformation)).")(\s|$)+/eiS";
        parent::__construct($minifier, $configuration);
    }

    /**
     * Implements {@link aCssMinifierPlugin::minify()}.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    public function apply(aCssToken &$token)
    {
        $lcValue = strtolower($token->Value);
        // Declaration value equals a value in the transformation table => simple replace
        if (isset($this->transformation[$lcValue])) {
            $token->Value = $this->transformation[$lcValue];
        } // Declaration value contains a value in the transformation table => regular expression replace
        elseif (preg_match($this->reMatch, $token->Value)) {
            $token->Value = preg_replace($this->reMatch, $this->reReplace, $token->Value);
        }

        return false;
    }

    /**
     * Implements {@link aMinifierPlugin::getTriggerTokens()}.
     *
     * @return array
     */
    public function getTriggerTokens()
    {
        return [
            'CssAtFontFaceDeclarationToken',
            'CssAtPageDeclarationToken',
            'CssRulesetDeclarationToken',
        ];
    }
}

/**
 * This {@link aCssMinifierFilter minifier filter} triggers on CSS Level 3 properties and will add declaration tokens
 * with browser-specific properties.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssConvertLevel3PropertiesMinifierFilter extends aCssMinifierFilter
{
    /**
     * Css property transformations table. Used to convert CSS3 and proprietary properties to the browser-specific
     * counterparts.
     *
     * @var array
     */
    private $transformations = [
        // Property                        Array(Mozilla, Webkit, Opera, Internet Explorer); NULL values are placeholders and will get ignored
        'animation'                           => [null, '-webkit-animation', null, null],
        'animation-delay'                     => [null, '-webkit-animation-delay', null, null],
        'animation-direction'                 => [null, '-webkit-animation-direction', null, null],
        'animation-duration'                  => [null, '-webkit-animation-duration', null, null],
        'animation-fill-mode'                 => [null, '-webkit-animation-fill-mode', null, null],
        'animation-iteration-count'           => [null, '-webkit-animation-iteration-count', null, null],
        'animation-name'                      => [null, '-webkit-animation-name', null, null],
        'animation-play-state'                => [null, '-webkit-animation-play-state', null, null],
        'animation-timing-function'           => [null, '-webkit-animation-timing-function', null, null],
        'appearance'                          => ['-moz-appearance', '-webkit-appearance', null, null],
        'backface-visibility'                 => [null, '-webkit-backface-visibility', null, null],
        'background-clip'                     => [null, '-webkit-background-clip', null, null],
        'background-composite'                => [null, '-webkit-background-composite', null, null],
        'background-inline-policy'            => ['-moz-background-inline-policy', null, null, null],
        'background-origin'                   => [null, '-webkit-background-origin', null, null],
        'background-position-x'               => [null, null, null, '-ms-background-position-x'],
        'background-position-y'               => [null, null, null, '-ms-background-position-y'],
        'background-size'                     => [null, '-webkit-background-size', null, null],
        'behavior'                            => [null, null, null, '-ms-behavior'],
        'binding'                             => ['-moz-binding', null, null, null],
        'border-after'                        => [null, '-webkit-border-after', null, null],
        'border-after-color'                  => [null, '-webkit-border-after-color', null, null],
        'border-after-style'                  => [null, '-webkit-border-after-style', null, null],
        'border-after-width'                  => [null, '-webkit-border-after-width', null, null],
        'border-before'                       => [null, '-webkit-border-before', null, null],
        'border-before-color'                 => [null, '-webkit-border-before-color', null, null],
        'border-before-style'                 => [null, '-webkit-border-before-style', null, null],
        'border-before-width'                 => [null, '-webkit-border-before-width', null, null],
        'border-border-bottom-colors'         => ['-moz-border-bottom-colors', null, null, null],
        'border-bottom-left-radius'           => [
            '-moz-border-radius-bottomleft',
            '-webkit-border-bottom-left-radius',
            null,
            null,
        ],
        'border-bottom-right-radius'          => [
            '-moz-border-radius-bottomright',
            '-webkit-border-bottom-right-radius',
            null,
            null,
        ],
        'border-end'                          => ['-moz-border-end', '-webkit-border-end', null, null],
        'border-end-color'                    => ['-moz-border-end-color', '-webkit-border-end-color', null, null],
        'border-end-style'                    => ['-moz-border-end-style', '-webkit-border-end-style', null, null],
        'border-end-width'                    => ['-moz-border-end-width', '-webkit-border-end-width', null, null],
        'border-fit'                          => [null, '-webkit-border-fit', null, null],
        'border-horizontal-spacing'           => [null, '-webkit-border-horizontal-spacing', null, null],
        'border-image'                        => ['-moz-border-image', '-webkit-border-image', null, null],
        'border-left-colors'                  => ['-moz-border-left-colors', null, null, null],
        'border-radius'                       => ['-moz-border-radius', '-webkit-border-radius', null, null],
        'border-border-right-colors'          => ['-moz-border-right-colors', null, null, null],
        'border-start'                        => ['-moz-border-start', '-webkit-border-start', null, null],
        'border-start-color'                  => ['-moz-border-start-color', '-webkit-border-start-color', null, null],
        'border-start-style'                  => ['-moz-border-start-style', '-webkit-border-start-style', null, null],
        'border-start-width'                  => ['-moz-border-start-width', '-webkit-border-start-width', null, null],
        'border-top-colors'                   => ['-moz-border-top-colors', null, null, null],
        'border-top-left-radius'              => [
            '-moz-border-radius-topleft',
            '-webkit-border-top-left-radius',
            null,
            null,
        ],
        'border-top-right-radius'             => [
            '-moz-border-radius-topright',
            '-webkit-border-top-right-radius',
            null,
            null,
        ],
        'border-vertical-spacing'             => [null, '-webkit-border-vertical-spacing', null, null],
        'box-align'                           => ['-moz-box-align', '-webkit-box-align', null, null],
        'box-direction'                       => ['-moz-box-direction', '-webkit-box-direction', null, null],
        'box-flex'                            => ['-moz-box-flex', '-webkit-box-flex', null, null],
        'box-flex-group'                      => [null, '-webkit-box-flex-group', null, null],
        'box-flex-lines'                      => [null, '-webkit-box-flex-lines', null, null],
        'box-ordinal-group'                   => ['-moz-box-ordinal-group', '-webkit-box-ordinal-group', null, null],
        'box-orient'                          => ['-moz-box-orient', '-webkit-box-orient', null, null],
        'box-pack'                            => ['-moz-box-pack', '-webkit-box-pack', null, null],
        'box-reflect'                         => [null, '-webkit-box-reflect', null, null],
        'box-shadow'                          => ['-moz-box-shadow', '-webkit-box-shadow', null, null],
        'box-sizing'                          => ['-moz-box-sizing', null, null, null],
        'color-correction'                    => [null, '-webkit-color-correction', null, null],
        'column-break-after'                  => [null, '-webkit-column-break-after', null, null],
        'column-break-before'                 => [null, '-webkit-column-break-before', null, null],
        'column-break-inside'                 => [null, '-webkit-column-break-inside', null, null],
        'column-count'                        => ['-moz-column-count', '-webkit-column-count', null, null],
        'column-gap'                          => ['-moz-column-gap', '-webkit-column-gap', null, null],
        'column-rule'                         => ['-moz-column-rule', '-webkit-column-rule', null, null],
        'column-rule-color'                   => ['-moz-column-rule-color', '-webkit-column-rule-color', null, null],
        'column-rule-style'                   => ['-moz-column-rule-style', '-webkit-column-rule-style', null, null],
        'column-rule-width'                   => ['-moz-column-rule-width', '-webkit-column-rule-width', null, null],
        'column-span'                         => [null, '-webkit-column-span', null, null],
        'column-width'                        => ['-moz-column-width', '-webkit-column-width', null, null],
        'columns'                             => [null, '-webkit-columns', null, null],
        'filter'                              => [__CLASS__, 'filter'],
        'float-edge'                          => ['-moz-float-edge', null, null, null],
        'font-feature-settings'               => ['-moz-font-feature-settings', null, null, null],
        'font-language-override'              => ['-moz-font-language-override', null, null, null],
        'font-size-delta'                     => [null, '-webkit-font-size-delta', null, null],
        'font-smoothing'                      => [null, '-webkit-font-smoothing', null, null],
        'force-broken-image-icon'             => ['-moz-force-broken-image-icon', null, null, null],
        'highlight'                           => [null, '-webkit-highlight', null, null],
        'hyphenate-character'                 => [null, '-webkit-hyphenate-character', null, null],
        'hyphenate-locale'                    => [null, '-webkit-hyphenate-locale', null, null],
        'hyphens'                             => [null, '-webkit-hyphens', null, null],
        'force-broken-image-icon'             => ['-moz-image-region', null, null, null],
        'ime-mode'                            => [null, null, null, '-ms-ime-mode'],
        'interpolation-mode'                  => [null, null, null, '-ms-interpolation-mode'],
        'layout-flow'                         => [null, null, null, '-ms-layout-flow'],
        'layout-grid'                         => [null, null, null, '-ms-layout-grid'],
        'layout-grid-char'                    => [null, null, null, '-ms-layout-grid-char'],
        'layout-grid-line'                    => [null, null, null, '-ms-layout-grid-line'],
        'layout-grid-mode'                    => [null, null, null, '-ms-layout-grid-mode'],
        'layout-grid-type'                    => [null, null, null, '-ms-layout-grid-type'],
        'line-break'                          => [null, '-webkit-line-break', null, '-ms-line-break'],
        'line-clamp'                          => [null, '-webkit-line-clamp', null, null],
        'line-grid-mode'                      => [null, null, null, '-ms-line-grid-mode'],
        'logical-height'                      => [null, '-webkit-logical-height', null, null],
        'logical-width'                       => [null, '-webkit-logical-width', null, null],
        'margin-after'                        => [null, '-webkit-margin-after', null, null],
        'margin-after-collapse'               => [null, '-webkit-margin-after-collapse', null, null],
        'margin-before'                       => [null, '-webkit-margin-before', null, null],
        'margin-before-collapse'              => [null, '-webkit-margin-before-collapse', null, null],
        'margin-bottom-collapse'              => [null, '-webkit-margin-bottom-collapse', null, null],
        'margin-collapse'                     => [null, '-webkit-margin-collapse', null, null],
        'margin-end'                          => ['-moz-margin-end', '-webkit-margin-end', null, null],
        'margin-start'                        => ['-moz-margin-start', '-webkit-margin-start', null, null],
        'margin-top-collapse'                 => [null, '-webkit-margin-top-collapse', null, null],
        'marquee '                            => [null, '-webkit-marquee', null, null],
        'marquee-direction'                   => [null, '-webkit-marquee-direction', null, null],
        'marquee-increment'                   => [null, '-webkit-marquee-increment', null, null],
        'marquee-repetition'                  => [null, '-webkit-marquee-repetition', null, null],
        'marquee-speed'                       => [null, '-webkit-marquee-speed', null, null],
        'marquee-style'                       => [null, '-webkit-marquee-style', null, null],
        'mask'                                => [null, '-webkit-mask', null, null],
        'mask-attachment'                     => [null, '-webkit-mask-attachment', null, null],
        'mask-box-image'                      => [null, '-webkit-mask-box-image', null, null],
        'mask-clip'                           => [null, '-webkit-mask-clip', null, null],
        'mask-composite'                      => [null, '-webkit-mask-composite', null, null],
        'mask-image'                          => [null, '-webkit-mask-image', null, null],
        'mask-origin'                         => [null, '-webkit-mask-origin', null, null],
        'mask-position'                       => [null, '-webkit-mask-position', null, null],
        'mask-position-x'                     => [null, '-webkit-mask-position-x', null, null],
        'mask-position-y'                     => [null, '-webkit-mask-position-y', null, null],
        'mask-repeat'                         => [null, '-webkit-mask-repeat', null, null],
        'mask-repeat-x'                       => [null, '-webkit-mask-repeat-x', null, null],
        'mask-repeat-y'                       => [null, '-webkit-mask-repeat-y', null, null],
        'mask-size'                           => [null, '-webkit-mask-size', null, null],
        'match-nearest-mail-blockquote-color' => [null, '-webkit-match-nearest-mail-blockquote-color', null, null],
        'max-logical-height'                  => [null, '-webkit-max-logical-height', null, null],
        'max-logical-width'                   => [null, '-webkit-max-logical-width', null, null],
        'min-logical-height'                  => [null, '-webkit-min-logical-height', null, null],
        'min-logical-width'                   => [null, '-webkit-min-logical-width', null, null],
        'object-fit'                          => [null, null, '-o-object-fit', null],
        'object-position'                     => [null, null, '-o-object-position', null],
        'opacity'                             => [__CLASS__, 'opacity'],
        'outline-radius'                      => ['-moz-outline-radius', null, null, null],
        'outline-bottom-left-radius'          => ['-moz-outline-radius-bottomleft', null, null, null],
        'outline-bottom-right-radius'         => ['-moz-outline-radius-bottomright', null, null, null],
        'outline-top-left-radius'             => ['-moz-outline-radius-topleft', null, null, null],
        'outline-top-right-radius'            => ['-moz-outline-radius-topright', null, null, null],
        'padding-after'                       => [null, '-webkit-padding-after', null, null],
        'padding-before'                      => [null, '-webkit-padding-before', null, null],
        'padding-end'                         => ['-moz-padding-end', '-webkit-padding-end', null, null],
        'padding-start'                       => ['-moz-padding-start', '-webkit-padding-start', null, null],
        'perspective'                         => [null, '-webkit-perspective', null, null],
        'perspective-origin'                  => [null, '-webkit-perspective-origin', null, null],
        'perspective-origin-x'                => [null, '-webkit-perspective-origin-x', null, null],
        'perspective-origin-y'                => [null, '-webkit-perspective-origin-y', null, null],
        'rtl-ordering'                        => [null, '-webkit-rtl-ordering', null, null],
        'scrollbar-3dlight-color'             => [null, null, null, '-ms-scrollbar-3dlight-color'],
        'scrollbar-arrow-color'               => [null, null, null, '-ms-scrollbar-arrow-color'],
        'scrollbar-base-color'                => [null, null, null, '-ms-scrollbar-base-color'],
        'scrollbar-darkshadow-color'          => [null, null, null, '-ms-scrollbar-darkshadow-color'],
        'scrollbar-face-color'                => [null, null, null, '-ms-scrollbar-face-color'],
        'scrollbar-highlight-color'           => [null, null, null, '-ms-scrollbar-highlight-color'],
        'scrollbar-shadow-color'              => [null, null, null, '-ms-scrollbar-shadow-color'],
        'scrollbar-track-color'               => [null, null, null, '-ms-scrollbar-track-color'],
        'stack-sizing'                        => ['-moz-stack-sizing', null, null, null],
        'svg-shadow'                          => [null, '-webkit-svg-shadow', null, null],
        'tab-size'                            => ['-moz-tab-size', null, '-o-tab-size', null],
        'table-baseline'                      => [null, null, '-o-table-baseline', null],
        'text-align-last'                     => [null, null, null, '-ms-text-align-last'],
        'text-autospace'                      => [null, null, null, '-ms-text-autospace'],
        'text-combine'                        => [null, '-webkit-text-combine', null, null],
        'text-decorations-in-effect'          => [null, '-webkit-text-decorations-in-effect', null, null],
        'text-emphasis'                       => [null, '-webkit-text-emphasis', null, null],
        'text-emphasis-color'                 => [null, '-webkit-text-emphasis-color', null, null],
        'text-emphasis-position'              => [null, '-webkit-text-emphasis-position', null, null],
        'text-emphasis-style'                 => [null, '-webkit-text-emphasis-style', null, null],
        'text-fill-color'                     => [null, '-webkit-text-fill-color', null, null],
        'text-justify'                        => [null, null, null, '-ms-text-justify'],
        'text-kashida-space'                  => [null, null, null, '-ms-text-kashida-space'],
        'text-overflow'                       => [null, null, '-o-text-overflow', '-ms-text-overflow'],
        'text-security'                       => [null, '-webkit-text-security', null, null],
        'text-size-adjust'                    => [null, '-webkit-text-size-adjust', null, '-ms-text-size-adjust'],
        'text-stroke'                         => [null, '-webkit-text-stroke', null, null],
        'text-stroke-color'                   => [null, '-webkit-text-stroke-color', null, null],
        'text-stroke-width'                   => [null, '-webkit-text-stroke-width', null, null],
        'text-underline-position'             => [null, null, null, '-ms-text-underline-position'],
        'transform'                           => ['-moz-transform', '-webkit-transform', '-o-transform', null],
        'transform-origin'                    => [
            '-moz-transform-origin',
            '-webkit-transform-origin',
            '-o-transform-origin',
            null,
        ],
        'transform-origin-x'                  => [null, '-webkit-transform-origin-x', null, null],
        'transform-origin-y'                  => [null, '-webkit-transform-origin-y', null, null],
        'transform-origin-z'                  => [null, '-webkit-transform-origin-z', null, null],
        'transform-style'                     => [null, '-webkit-transform-style', null, null],
        'transition'                          => ['-moz-transition', '-webkit-transition', '-o-transition', null],
        'transition-delay'                    => [
            '-moz-transition-delay',
            '-webkit-transition-delay',
            '-o-transition-delay',
            null,
        ],
        'transition-duration'                 => [
            '-moz-transition-duration',
            '-webkit-transition-duration',
            '-o-transition-duration',
            null,
        ],
        'transition-property'                 => [
            '-moz-transition-property',
            '-webkit-transition-property',
            '-o-transition-property',
            null,
        ],
        'transition-timing-function'          => [
            '-moz-transition-timing-function',
            '-webkit-transition-timing-function',
            '-o-transition-timing-function',
            null,
        ],
        'user-drag'                           => [null, '-webkit-user-drag', null, null],
        'user-focus'                          => ['-moz-user-focus', null, null, null],
        'user-input'                          => ['-moz-user-input', null, null, null],
        'user-modify'                         => ['-moz-user-modify', '-webkit-user-modify', null, null],
        'user-select'                         => ['-moz-user-select', '-webkit-user-select', null, null],
        'white-space'                         => [__CLASS__, 'whiteSpace'],
        'window-shadow'                       => ['-moz-window-shadow', null, null, null],
        'word-break'                          => [null, null, null, '-ms-word-break'],
        'word-wrap'                           => [null, null, null, '-ms-word-wrap'],
        'writing-mode'                        => [null, '-webkit-writing-mode', null, '-ms-writing-mode'],
        'zoom'                                => [null, null, null, '-ms-zoom'],
    ];

    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        $r = 0;
        $transformations = &$this->transformations;
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            if (get_class($tokens[$i]) === 'CssRulesetDeclarationToken') {
                $tProperty = $tokens[$i]->Property;
                if (isset($transformations[$tProperty])) {
                    $result = [];
                    if (is_callable($transformations[$tProperty])) {
                        $result = call_user_func_array($transformations[$tProperty], [$tokens[$i]]);
                        if (!is_array($result) && is_object($result)) {
                            $result = [$result];
                        }
                    } else {
                        $tValue = $tokens[$i]->Value;
                        $tMediaTypes = $tokens[$i]->MediaTypes;
                        foreach ($transformations[$tProperty] as $property) {
                            if ($property !== null) {
                                $result[] = new CssRulesetDeclarationToken($property, $tValue, $tMediaTypes);
                            }
                        }
                    }
                    if (count($result) > 0) {
                        array_splice($tokens, $i + 1, 0, $result);
                        $i += count($result);
                        $l += count($result);
                    }
                }
            }
        }

        return $r;
    }

    /**
     * Transforms the Internet Explorer specific declaration property "filter" to Internet Explorer 8+ compatible
     * declaratiopn property "-ms-filter".
     *
     * @param aCssToken $token
     *
     * @return array
     */
    private static function filter($token)
    {
        $r = [
            new CssRulesetDeclarationToken('-ms-filter', '"'.$token->Value.'"', $token->MediaTypes),
        ];

        return $r;
    }

    /**
     * Transforms "opacity: {value}" into browser specific counterparts.
     *
     * @param aCssToken $token
     *
     * @return array
     */
    private static function opacity($token)
    {
        // Calculate the value for Internet Explorer filter statement
        $ieValue = (int) ((float) $token->Value * 100);
        $r = [
            // Internet Explorer >= 8
            new CssRulesetDeclarationToken('-ms-filter', '"alpha(opacity='.$ieValue.')"', $token->MediaTypes),
            // Internet Explorer >= 4 <= 7
            new CssRulesetDeclarationToken('filter', 'alpha(opacity='.$ieValue.')', $token->MediaTypes),
            new CssRulesetDeclarationToken('zoom', '1', $token->MediaTypes),
        ];

        return $r;
    }

    /**
     * Transforms "white-space: pre-wrap" into browser specific counterparts.
     *
     * @param aCssToken $token
     *
     * @return array
     */
    private static function whiteSpace($token)
    {
        if (strtolower($token->Value) === 'pre-wrap') {
            $r = [
                // Firefox < 3
                new CssRulesetDeclarationToken('white-space', '-moz-pre-wrap', $token->MediaTypes),
                // Webkit
                new CssRulesetDeclarationToken('white-space', '-webkit-pre-wrap', $token->MediaTypes),
                // Opera >= 4 <= 6
                new CssRulesetDeclarationToken('white-space', '-pre-wrap', $token->MediaTypes),
                // Opera >= 7
                new CssRulesetDeclarationToken('white-space', '-o-pre-wrap', $token->MediaTypes),
                // Internet Explorer >= 5.5
                new CssRulesetDeclarationToken('word-wrap', 'break-word', $token->MediaTypes),
            ];

            return $r;
        } else {
            return [];
        }
    }
}

/**
 * This {@link aCssMinifierFilter minifier filter} will convert @keyframes at-rule block to browser specific
 * counterparts.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssConvertLevel3AtKeyframesMinifierFilter extends aCssMinifierFilter
{
    /**
     * Implements {@link aCssMinifierFilter::filter()}.
     *
     * @param array $tokens Array of objects of type aCssToken
     *
     * @return int Count of added, changed or removed tokens; a return value larger than 0 will rebuild the array
     */
    public function apply(array &$tokens)
    {
        $r = 0;
        $transformations = ['-moz-keyframes', '-webkit-keyframes'];
        for ($i = 0, $l = count($tokens); $i < $l; $i++) {
            if (get_class($tokens[$i]) === 'CssAtKeyframesStartToken') {
                for ($ii = $i; $ii < $l; $ii++) {
                    if (get_class($tokens[$ii]) === 'CssAtKeyframesEndToken') {
                        break;
                    }
                }
                if (get_class($tokens[$ii]) === 'CssAtKeyframesEndToken') {
                    $add = [];
                    $source = [];
                    for ($iii = $i; $iii <= $ii; $iii++) {
                        $source[] = clone $tokens[$iii];
                    }
                    foreach ($transformations as $transformation) {
                        $t = [];
                        foreach ($source as $token) {
                            $t[] = clone $token;
                        }
                        $t[0]->AtRuleName = $transformation;
                        $add = array_merge($add, $t);
                    }
                    if (isset($this->configuration['RemoveSource']) && $this->configuration['RemoveSource'] === true) {
                        array_splice($tokens, $i, $ii - $i + 1, $add);
                    } else {
                        array_splice($tokens, $ii + 1, 0, $add);
                    }
                    $l = count($tokens);
                    $i = $ii + count($add);
                    $r += count($add);
                }
            }
        }

        return $r;
    }
}

/**
 * This {@link aCssMinifierPlugin} will convert a color value in hsl notation to hexadecimal notation.
 *
 * Example:
 * <code>
 * color: hsl(232,36%,48%);
 * </code>
 *
 * Will get converted to:
 * <code>
 * color:#4e5aa7;
 * </code>
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssConvertHslColorsMinifierPlugin extends aCssMinifierPlugin
{
    /**
     * Regular expression matching the value.
     *
     * @var string
     */
    private $reMatch = "/^hsl\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*%\s*,\s*([0-9]+)\s*%\s*\)/iS";

    /**
     * Implements {@link aCssMinifierPlugin::minify()}.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    public function apply(aCssToken &$token)
    {
        if (stripos($token->Value, 'hsl') !== false && preg_match($this->reMatch, $token->Value, $m)) {
            $token->Value = str_replace($m[0], $this->hsl2hex($m[1], $m[2], $m[3]), $token->Value);
        }

        return false;
    }

    /**
     * Implements {@link aMinifierPlugin::getTriggerTokens()}.
     *
     * @return array
     */
    public function getTriggerTokens()
    {
        return [
            'CssAtFontFaceDeclarationToken',
            'CssAtPageDeclarationToken',
            'CssRulesetDeclarationToken',
        ];
    }

    /**
     * Convert a HSL value to hexadecimal notation.
     *
     * Based on: {@link http://www.easyrgb.com/index.php?X=MATH&H=19#text19}.
     *
     * @param int $hue        Hue
     * @param int $saturation Saturation
     * @param int $lightness  Lightnesss
     *
     * @return string
     */
    private function hsl2hex($hue, $saturation, $lightness)
    {
        $hue = $hue / 360;
        $saturation = $saturation / 100;
        $lightness = $lightness / 100;
        if ($saturation == 0) {
            $red = $lightness * 255;
            $green = $lightness * 255;
            $blue = $lightness * 255;
        } else {
            if ($lightness < 0.5) {
                $v2 = $lightness * (1 + $saturation);
            } else {
                $v2 = ($lightness + $saturation) - ($saturation * $lightness);
            }
            $v1 = 2 * $lightness - $v2;
            $red = 255 * self::hue2rgb($v1, $v2, $hue + (1 / 3));
            $green = 255 * self::hue2rgb($v1, $v2, $hue);
            $blue = 255 * self::hue2rgb($v1, $v2, $hue - (1 / 3));
        }

        return '#'.str_pad(dechex(round($red)), 2, '0', STR_PAD_LEFT).str_pad(dechex(round($green)), 2, '0',
            STR_PAD_LEFT).str_pad(dechex(round($blue)), 2, '0', STR_PAD_LEFT);
    }

    /**
     * Apply hue to a rgb color value.
     *
     * @param int $v1  Value 1
     * @param int $v2  Value 2
     * @param int $hue Hue
     *
     * @return int
     */
    private function hue2rgb($v1, $v2, $hue)
    {
        if ($hue < 0) {
            $hue += 1;
        }
        if ($hue > 1) {
            $hue -= 1;
        }
        if ((6 * $hue) < 1) {
            return $v1 + ($v2 - $v1) * 6 * $hue;
        }
        if ((2 * $hue) < 1) {
            return $v2;
        }
        if ((3 * $hue) < 2) {
            return $v1 + ($v2 - $v1) * ((2 / 3) - $hue) * 6;
        }

        return $v1;
    }
}

/**
 * This {@link aCssMinifierPlugin} will convert the font-weight values normal and bold to their numeric notation.
 *
 * Example:
 * <code>
 * font-weight: normal;
 * font: bold 11px monospace;
 * </code>
 *
 * Will get converted to:
 * <code>
 * font-weight:400;
 * font:700 11px monospace;
 * </code>
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssConvertFontWeightMinifierPlugin extends aCssMinifierPlugin
{
    /**
     * Array of included declaration properties this plugin will process; others declaration properties will get
     * ignored.
     *
     * @var array
     */
    private $include = [
        'font',
        'font-weight',
    ];
    /**
     * Regular expression matching the value.
     *
     * @var string
     */
    private $reMatch = null;
    /**
     * Regular expression replace the value.
     *
     * @var string
     */
    private $reReplace = '"${1}" . $this->transformation["${2}"] . "${3}"';
    /**
     * Transformation table used by the {@link CssConvertFontWeightMinifierPlugin::$reReplace replace regular
     * expression}.
     *
     * @var array
     */
    private $transformation = [
        'normal' => '400',
        'bold'   => '700',
    ];

    /**
     * Overwrites {@link aCssMinifierPlugin::__construct()}.
     *
     * The constructor will create the {@link CssConvertFontWeightMinifierPlugin::$reReplace replace regular expression}
     * based on the {@link CssConvertFontWeightMinifierPlugin::$transformation transformation table}.
     *
     * @param CssMinifier $minifier The CssMinifier object of this plugin.
     *
     * @return void
     */
    public function __construct(CssMinifier $minifier)
    {
        $this->reMatch = "/(^|\s)+(".implode('|', array_keys($this->transformation)).")(\s|$)+/eiS";
        parent::__construct($minifier);
    }

    /**
     * Implements {@link aCssMinifierPlugin::minify()}.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    public function apply(aCssToken &$token)
    {
        if (in_array($token->Property, $this->include) && preg_match($this->reMatch, $token->Value, $m)) {
            $token->Value = preg_replace($this->reMatch, $this->reReplace, $token->Value);
        }

        return false;
    }

    /**
     * Implements {@link aMinifierPlugin::getTriggerTokens()}.
     *
     * @return array
     */
    public function getTriggerTokens()
    {
        return [
            'CssAtFontFaceDeclarationToken',
            'CssAtPageDeclarationToken',
            'CssRulesetDeclarationToken',
        ];
    }
}

/**
 * This {@link aCssMinifierPlugin} will compress several unit values to their short notations. Examples:.
 *
 * <code>
 * padding: 0.5em;
 * border: 0px;
 * margin: 0 0 0 0;
 * </code>
 *
 * Will get compressed to:
 *
 * <code>
 * padding:.5px;
 * border:0;
 * margin:0;
 * </code>
 *
 * --
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssCompressUnitValuesMinifierPlugin extends aCssMinifierPlugin
{
    /**
     * Regular expression used for matching and replacing unit values.
     *
     * @var array
     */
    private $re = [
        "/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)/iS" => '${1}.${2}${4}',
        "/(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/iS"            => '${1}0',
        "/(^0\s0\s0\s0)|(^0\s0\s0$)|(^0\s0$)/iS"                  => '0',
    ];
    /**
     * Regular expression matching the value.
     *
     * @var string
     */
    private $reMatch = "/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)|(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)|(^0\s0\s0\s0$)|(^0\s0\s0$)|(^0\s0$)/iS";

    /**
     * Implements {@link aCssMinifierPlugin::minify()}.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    public function apply(aCssToken &$token)
    {
        if (preg_match($this->reMatch, $token->Value)) {
            foreach ($this->re as $reMatch => $reReplace) {
                $token->Value = preg_replace($reMatch, $reReplace, $token->Value);
            }
        }

        return false;
    }

    /**
     * Implements {@link aMinifierPlugin::getTriggerTokens()}.
     *
     * @return array
     */
    public function getTriggerTokens()
    {
        return [
            'CssAtFontFaceDeclarationToken',
            'CssAtPageDeclarationToken',
            'CssRulesetDeclarationToken',
        ];
    }
}

/**
 * This {@link aCssMinifierPlugin} compress the content of expresssion() declaration values.
 *
 * For compression of expressions {@link https://github.com/rgrove/jsmin-php/ JSMin} will get used. JSMin have to be
 * already included or loadable via {@link http://goo.gl/JrW54 PHP autoloading}.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssCompressExpressionValuesMinifierPlugin extends aCssMinifierPlugin
{
    /**
     * Implements {@link aCssMinifierPlugin::minify()}.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    public function apply(aCssToken &$token)
    {
        if (class_exists('JSMin') && stripos($token->Value, 'expression(') !== false) {
            $value = $token->Value;
            $value = substr($token->Value, stripos($token->Value, 'expression(') + 10);
            $value = trim(JSMin::minify($value));
            $token->Value = 'expression('.$value.')';
        }

        return false;
    }

    /**
     * Implements {@link aMinifierPlugin::getTriggerTokens()}.
     *
     * @return array
     */
    public function getTriggerTokens()
    {
        return [
            'CssAtFontFaceDeclarationToken',
            'CssAtPageDeclarationToken',
            'CssRulesetDeclarationToken',
        ];
    }
}

/**
 * This {@link aCssMinifierPlugin} will convert hexadecimal color value with 6 chars to their 3 char hexadecimal
 * notation (if possible).
 *
 * Example:
 * <code>
 * color: #aabbcc;
 * </code>
 *
 * Will get converted to:
 * <code>
 * color:#abc;
 * </code>
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssCompressColorValuesMinifierPlugin extends aCssMinifierPlugin
{
    /**
     * Regular expression matching 6 char hexadecimal color values.
     *
     * @var string
     */
    private $reMatch = "/\#([0-9a-f]{6})/iS";

    /**
     * Implements {@link aCssMinifierPlugin::minify()}.
     *
     * @param aCssToken $token Token to process
     *
     * @return bool Return TRUE to break the processing of this token; FALSE to continue
     */
    public function apply(aCssToken &$token)
    {
        if (strpos($token->Value, '#') !== false && preg_match($this->reMatch, $token->Value, $m)) {
            $value = strtolower($m[1]);
            if ($value[0] == $value[1] && $value[2] == $value[3] && $value[4] == $value[5]) {
                $token->Value = str_replace($m[0], '#'.$value[0].$value[2].$value[4], $token->Value);
            }
        }

        return false;
    }

    /**
     * Implements {@link aMinifierPlugin::getTriggerTokens()}.
     *
     * @return array
     */
    public function getTriggerTokens()
    {
        return [
            'CssAtFontFaceDeclarationToken',
            'CssAtPageDeclarationToken',
            'CssRulesetDeclarationToken',
        ];
    }
}

/**
 * This {@link aCssToken CSS token} represents a CSS comment.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssCommentToken extends aCssToken
{
    /**
     * Comment as Text.
     *
     * @var string
     */
    public $Comment = '';

    /**
     * Set the properties of a comment token.
     *
     * @param string $comment Comment including comment delimiters
     *
     * @return void
     */
    public function __construct($comment)
    {
        $this->Comment = $comment;
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->Comment;
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing comments.
 *
 * Adds a {@link CssCommentToken} to the parser if a comment was found.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssCommentParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['*', '/'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return false;
    }

    /**
     * Stored buffer for restore.
     *
     * @var string
     */
    private $restoreBuffer = '';

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        if ($char === '*' && $previousChar === '/' && $state !== 'T_COMMENT') {
            $this->parser->pushState('T_COMMENT');
            $this->parser->setExclusive(__CLASS__);
            $this->restoreBuffer = substr($this->parser->getAndClearBuffer(), 0, -2);
        } elseif ($char === '/' && $previousChar === '*' && $state === 'T_COMMENT') {
            $this->parser->popState();
            $this->parser->unsetExclusive();
            $this->parser->appendToken(new CssCommentToken('/*'.$this->parser->getAndClearBuffer()));
            $this->parser->setBuffer($this->restoreBuffer);
        } else {
            return false;
        }

        return true;
    }
}

/**
 * This {@link aCssToken CSS token} represents the start of a @variables at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtVariablesStartToken extends aCssAtBlockStartToken
{
    /**
     * Media types of the @variables at-rule block.
     *
     * @var array
     */
    public $MediaTypes = [];

    /**
     * Set the properties of a @variables at-rule token.
     *
     * @param array $mediaTypes Media types
     *
     * @return void
     */
    public function __construct($mediaTypes = null)
    {
        $this->MediaTypes = $mediaTypes ? $mediaTypes : ['all'];
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '';
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing @variables at-rule block with including declarations.
 *
 * Found @variables at-rule blocks will add a {@link CssAtVariablesStartToken} and {@link CssAtVariablesEndToken} to the
 * parser; including declarations as {@link CssAtVariablesDeclarationToken}.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtVariablesParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['@', '{', '}', ':', ';'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return ['T_DOCUMENT', 'T_AT_VARIABLES::PREPARE', 'T_AT_VARIABLES', 'T_AT_VARIABLES_DECLARATION'];
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        // Start of @variables at-rule block
        if ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                10)) === '@variables'
        ) {
            $this->parser->pushState('T_AT_VARIABLES::PREPARE');
            $this->parser->clearBuffer();

            return $index + 10;
        } // Start of @variables declarations
        elseif ($char === '{' && $state === 'T_AT_VARIABLES::PREPARE') {
            $this->parser->setState('T_AT_VARIABLES');
            $mediaTypes = array_filter(array_map('trim', explode(',', $this->parser->getAndClearBuffer('{'))));
            $this->parser->appendToken(new CssAtVariablesStartToken($mediaTypes));
        }
        // Start of @variables declaration
        if ($char === ':' && $state === 'T_AT_VARIABLES') {
            $this->buffer = $this->parser->getAndClearBuffer(':');
            $this->parser->pushState('T_AT_VARIABLES_DECLARATION');
        } // Unterminated @variables declaration
        elseif ($char === ':' && $state === 'T_AT_VARIABLES_DECLARATION') {
            // Ignore Internet Explorer filter declarations
            if ($this->buffer === 'filter') {
                return false;
            }
            CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__.': Unterminated @variables declaration',
                $this->buffer.':'.$this->parser->getBuffer().'_'));
        } // End of @variables declaration
        elseif (($char === ';' || $char === '}') && $state === 'T_AT_VARIABLES_DECLARATION') {
            $value = $this->parser->getAndClearBuffer(';}');
            if (strtolower(substr($value, -10, 10)) === '!important') {
                $value = trim(substr($value, 0, -10));
                $isImportant = true;
            } else {
                $isImportant = false;
            }
            $this->parser->popState();
            $this->parser->appendToken(new CssAtVariablesDeclarationToken($this->buffer, $value, $isImportant));
            $this->buffer = '';
        } // End of @variables at-rule block
        elseif ($char === '}' && $state === 'T_AT_VARIABLES') {
            $this->parser->popState();
            $this->parser->clearBuffer();
            $this->parser->appendToken(new CssAtVariablesEndToken());
        } else {
            return false;
        }

        return true;
    }
}

/**
 * This {@link aCssToken CSS token} represents the end of a @variables at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtVariablesEndToken extends aCssAtBlockEndToken
{
    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '';
    }
}

/**
 * This {@link aCssToken CSS token} represents a declaration of a @variables at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtVariablesDeclarationToken extends aCssDeclarationToken
{
    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '';
    }
}

/**
 * This {@link aCssToken CSS token} represents the start of a @page at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtPageStartToken extends aCssAtBlockStartToken
{
    /**
     * Selector.
     *
     * @var string
     */
    public $Selector = '';

    /**
     * Sets the properties of the @page at-rule.
     *
     * @param string $selector Selector
     *
     * @return void
     */
    public function __construct($selector = '')
    {
        $this->Selector = $selector;
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '@page'.($this->Selector ? ' '.$this->Selector : '').'{';
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing @page at-rule block with including declarations.
 *
 * Found @page at-rule blocks will add a {@link CssAtPageStartToken} and {@link CssAtPageEndToken} to the
 * parser; including declarations as {@link CssAtPageDeclarationToken}.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtPageParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['@', '{', '}', ':', ';'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return ['T_DOCUMENT', 'T_AT_PAGE::SELECTOR', 'T_AT_PAGE', 'T_AT_PAGE_DECLARATION'];
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        // Start of @page at-rule block
        if ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                5)) === '@page'
        ) {
            $this->parser->pushState('T_AT_PAGE::SELECTOR');
            $this->parser->clearBuffer();

            return $index + 5;
        } // Start of @page declarations
        elseif ($char === '{' && $state === 'T_AT_PAGE::SELECTOR') {
            $selector = $this->parser->getAndClearBuffer('{');
            $this->parser->setState('T_AT_PAGE');
            $this->parser->clearBuffer();
            $this->parser->appendToken(new CssAtPageStartToken($selector));
        } // Start of @page declaration
        elseif ($char === ':' && $state === 'T_AT_PAGE') {
            $this->parser->pushState('T_AT_PAGE_DECLARATION');
            $this->buffer = $this->parser->getAndClearBuffer(':', true);
        } // Unterminated @font-face declaration
        elseif ($char === ':' && $state === 'T_AT_PAGE_DECLARATION') {
            // Ignore Internet Explorer filter declarations
            if ($this->buffer === 'filter') {
                return false;
            }
            CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__.': Unterminated @page declaration',
                $this->buffer.':'.$this->parser->getBuffer().'_'));
        } // End of @page declaration
        elseif (($char === ';' || $char === '}') && $state == 'T_AT_PAGE_DECLARATION') {
            $value = $this->parser->getAndClearBuffer(';}');
            if (strtolower(substr($value, -10, 10)) == '!important') {
                $value = trim(substr($value, 0, -10));
                $isImportant = true;
            } else {
                $isImportant = false;
            }
            $this->parser->popState();
            $this->parser->appendToken(new CssAtPageDeclarationToken($this->buffer, $value, $isImportant));
            // --
            if ($char === '}') {
                $this->parser->popState();
                $this->parser->appendToken(new CssAtPageEndToken());
            }
            $this->buffer = '';
        } // End of @page at-rule block
        elseif ($char === '}' && $state === 'T_AT_PAGE') {
            $this->parser->popState();
            $this->parser->clearBuffer();
            $this->parser->appendToken(new CssAtPageEndToken());
        } else {
            return false;
        }

        return true;
    }
}

/**
 * This {@link aCssToken CSS token} represents the end of a @page at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtPageEndToken extends aCssAtBlockEndToken
{
}

/**
 * This {@link aCssToken CSS token} represents a declaration of a @page at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtPageDeclarationToken extends aCssDeclarationToken
{
}

/**
 * This {@link aCssToken CSS token} represents the start of a @media at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtMediaStartToken extends aCssAtBlockStartToken
{
    /**
     * Sets the properties of the @media at-rule.
     *
     * @param array $mediaTypes Media types
     *
     * @return void
     */
    public function __construct(array $mediaTypes = [])
    {
        $this->MediaTypes = $mediaTypes;
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '@media '.implode(',', $this->MediaTypes).'{';
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing @media at-rule block.
 *
 * Found @media at-rule blocks will add a {@link CssAtMediaStartToken} and {@link CssAtMediaEndToken} to the parser.
 * This plugin will also set the the current media types using {@link CssParser::setMediaTypes()} and
 * {@link CssParser::unsetMediaTypes()}.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtMediaParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['@', '{', '}'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return ['T_DOCUMENT', 'T_AT_MEDIA::PREPARE', 'T_AT_MEDIA'];
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        if ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                6)) === '@media'
        ) {
            $this->parser->pushState('T_AT_MEDIA::PREPARE');
            $this->parser->clearBuffer();

            return $index + 6;
        } elseif ($char === '{' && $state === 'T_AT_MEDIA::PREPARE') {
            $mediaTypes = array_filter(array_map('trim', explode(',', $this->parser->getAndClearBuffer('{'))));
            $this->parser->setMediaTypes($mediaTypes);
            $this->parser->setState('T_AT_MEDIA');
            $this->parser->appendToken(new CssAtMediaStartToken($mediaTypes));
        } elseif ($char === '}' && $state === 'T_AT_MEDIA') {
            $this->parser->appendToken(new CssAtMediaEndToken());
            $this->parser->clearBuffer();
            $this->parser->unsetMediaTypes();
            $this->parser->popState();
        } else {
            return false;
        }

        return true;
    }
}

/**
 * This {@link aCssToken CSS token} represents the end of a @media at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtMediaEndToken extends aCssAtBlockEndToken
{
}

/**
 * This {@link aCssToken CSS token} represents the start of a @keyframes at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtKeyframesStartToken extends aCssAtBlockStartToken
{
    /**
     * Name of the at-rule.
     *
     * @var string
     */
    public $AtRuleName = 'keyframes';
    /**
     * Name.
     *
     * @var string
     */
    public $Name = '';

    /**
     * Sets the properties of the @page at-rule.
     *
     * @param string $selector Selector
     *
     * @return void
     */
    public function __construct($name, $atRuleName = null)
    {
        $this->Name = $name;
        if (!is_null($atRuleName)) {
            $this->AtRuleName = $atRuleName;
        }
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '@'.$this->AtRuleName.' "'.$this->Name.'"{';
    }
}

/**
 * This {@link aCssToken CSS token} represents the start of a ruleset of a @keyframes at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtKeyframesRulesetStartToken extends aCssRulesetStartToken
{
    /**
     * Array of selectors.
     *
     * @var array
     */
    public $Selectors = [];

    /**
     * Set the properties of a ruleset token.
     *
     * @param array $selectors Selectors of the ruleset
     *
     * @return void
     */
    public function __construct(array $selectors = [])
    {
        $this->Selectors = $selectors;
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return implode(',', $this->Selectors).'{';
    }
}

/**
 * This {@link aCssToken CSS token} represents the end of a ruleset of a @keyframes at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtKeyframesRulesetEndToken extends aCssRulesetEndToken
{
}

/**
 * This {@link aCssToken CSS token} represents a ruleset declaration of a @keyframes at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtKeyframesRulesetDeclarationToken extends aCssDeclarationToken
{
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing @keyframes at-rule blocks, rulesets and declarations.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtKeyframesParserPlugin extends aCssParserPlugin
{
    /**
     * @var string Keyword
     */
    private $atRuleName = '';
    /**
     * Selectors.
     *
     * @var array
     */
    private $selectors = [];

    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['@', '{', '}', ':', ',', ';'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return [
            'T_DOCUMENT',
            'T_AT_KEYFRAMES::NAME',
            'T_AT_KEYFRAMES',
            'T_AT_KEYFRAMES_RULESETS',
            'T_AT_KEYFRAMES_RULESET',
            'T_AT_KEYFRAMES_RULESET_DECLARATION',
        ];
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        // Start of @keyframes at-rule block
        if ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                10)) === '@keyframes'
        ) {
            $this->atRuleName = 'keyframes';
            $this->parser->pushState('T_AT_KEYFRAMES::NAME');
            $this->parser->clearBuffer();

            return $index + 10;
        } // Start of @keyframes at-rule block (@-moz-keyframes)
        elseif ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                15)) === '@-moz-keyframes'
        ) {
            $this->atRuleName = '-moz-keyframes';
            $this->parser->pushState('T_AT_KEYFRAMES::NAME');
            $this->parser->clearBuffer();

            return $index + 15;
        } // Start of @keyframes at-rule block (@-webkit-keyframes)
        elseif ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                18)) === '@-webkit-keyframes'
        ) {
            $this->atRuleName = '-webkit-keyframes';
            $this->parser->pushState('T_AT_KEYFRAMES::NAME');
            $this->parser->clearBuffer();

            return $index + 18;
        } // Start of @keyframes rulesets
        elseif ($char === '{' && $state === 'T_AT_KEYFRAMES::NAME') {
            $name = $this->parser->getAndClearBuffer("{\"'");
            $this->parser->setState('T_AT_KEYFRAMES_RULESETS');
            $this->parser->clearBuffer();
            $this->parser->appendToken(new CssAtKeyframesStartToken($name, $this->atRuleName));
        }
        // Start of @keyframe ruleset and selectors
        if ($char === ',' && $state === 'T_AT_KEYFRAMES_RULESETS') {
            $this->selectors[] = $this->parser->getAndClearBuffer(',{');
        } // Start of a @keyframes ruleset
        elseif ($char === '{' && $state === 'T_AT_KEYFRAMES_RULESETS') {
            if ($this->parser->getBuffer() !== '') {
                $this->selectors[] = $this->parser->getAndClearBuffer(',{');
                $this->parser->pushState('T_AT_KEYFRAMES_RULESET');
                $this->parser->appendToken(new CssAtKeyframesRulesetStartToken($this->selectors));
                $this->selectors = [];
            }
        } // Start of @keyframes ruleset declaration
        elseif ($char === ':' && $state === 'T_AT_KEYFRAMES_RULESET') {
            $this->parser->pushState('T_AT_KEYFRAMES_RULESET_DECLARATION');
            $this->buffer = $this->parser->getAndClearBuffer(':;', true);
        } // Unterminated @keyframes ruleset declaration
        elseif ($char === ':' && $state === 'T_AT_KEYFRAMES_RULESET_DECLARATION') {
            // Ignore Internet Explorer filter declarations
            if ($this->buffer === 'filter') {
                return false;
            }
            CssMin::triggerError(new CssError(__FILE__, __LINE__,
                __METHOD__.': Unterminated @keyframes ruleset declaration',
                $this->buffer.':'.$this->parser->getBuffer().'_'));
        } // End of declaration
        elseif (($char === ';' || $char === '}') && $state === 'T_AT_KEYFRAMES_RULESET_DECLARATION') {
            $value = $this->parser->getAndClearBuffer(';}');
            if (strtolower(substr($value, -10, 10)) === '!important') {
                $value = trim(substr($value, 0, -10));
                $isImportant = true;
            } else {
                $isImportant = false;
            }
            $this->parser->popState();
            $this->parser->appendToken(new CssAtKeyframesRulesetDeclarationToken($this->buffer, $value, $isImportant));
            // Declaration ends with a right curly brace; so we have to end the ruleset
            if ($char === '}') {
                $this->parser->appendToken(new CssAtKeyframesRulesetEndToken());
                $this->parser->popState();
            }
            $this->buffer = '';
        } // End of @keyframes ruleset
        elseif ($char === '}' && $state === 'T_AT_KEYFRAMES_RULESET') {
            $this->parser->clearBuffer();

            $this->parser->popState();
            $this->parser->appendToken(new CssAtKeyframesRulesetEndToken());
        } // End of @keyframes rulesets
        elseif ($char === '}' && $state === 'T_AT_KEYFRAMES_RULESETS') {
            $this->parser->clearBuffer();
            $this->parser->popState();
            $this->parser->appendToken(new CssAtKeyframesEndToken());
        } else {
            return false;
        }

        return true;
    }
}

/**
 * This {@link aCssToken CSS token} represents the end of a @keyframes at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtKeyframesEndToken extends aCssAtBlockEndToken
{
}

/**
 * This {@link aCssToken CSS token} represents a @import at-rule.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1.b1 (2001-02-22)
 */
class CssAtImportToken extends aCssToken
{
    /**
     * Import path of the @import at-rule.
     *
     * @var string
     */
    public $Import = '';
    /**
     * Media types of the @import at-rule.
     *
     * @var array
     */
    public $MediaTypes = [];

    /**
     * Set the properties of a @import at-rule token.
     *
     * @param string $import     Import path
     * @param array  $mediaTypes Media types
     *
     * @return void
     */
    public function __construct($import, $mediaTypes)
    {
        $this->Import = $import;
        $this->MediaTypes = $mediaTypes ? $mediaTypes : [];
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '@import "'.$this->Import.'"'.(count($this->MediaTypes) > 0 ? ' '.implode(',',
                $this->MediaTypes) : '').';';
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing @import at-rule.
 *
 * If a @import at-rule was found this plugin will add a {@link CssAtImportToken} to the parser.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtImportParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['@', ';', ',', "\n"];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return ['T_DOCUMENT', 'T_AT_IMPORT'];
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        if ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                7)) === '@import'
        ) {
            $this->parser->pushState('T_AT_IMPORT');
            $this->parser->clearBuffer();

            return $index + 7;
        } elseif (($char === ';' || $char === "\n") && $state === 'T_AT_IMPORT') {
            $this->buffer = $this->parser->getAndClearBuffer(';');
            $pos = false;
            foreach ([')', '"', "'"] as $needle) {
                if (($pos = strrpos($this->buffer, $needle)) !== false) {
                    break;
                }
            }
            $import = substr($this->buffer, 0, $pos + 1);
            if (stripos($import, 'url(') === 0) {
                $import = substr($import, 4, -1);
            }
            $import = trim($import, " \t\n\r\0\x0B'\"");
            $mediaTypes = array_filter(array_map('trim',
                explode(',', trim(substr($this->buffer, $pos + 1), " \t\n\r\0\x0B{"))));
            if ($pos) {
                $this->parser->appendToken(new CssAtImportToken($import, $mediaTypes));
            } else {
                CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__.': Invalid @import at-rule syntax',
                    $this->parser->buffer));
            }
            $this->parser->popState();
        } else {
            return false;
        }

        return true;
    }
}

/**
 * This {@link aCssToken CSS token} represents the start of a @font-face at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtFontFaceStartToken extends aCssAtBlockStartToken
{
    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '@font-face{';
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing @font-face at-rule block with including declarations.
 *
 * Found @font-face at-rule blocks will add a {@link CssAtFontFaceStartToken} and {@link CssAtFontFaceEndToken} to the
 * parser; including declarations as {@link CssAtFontFaceDeclarationToken}.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtFontFaceParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['@', '{', '}', ':', ';'];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return ['T_DOCUMENT', 'T_AT_FONT_FACE::PREPARE', 'T_AT_FONT_FACE', 'T_AT_FONT_FACE_DECLARATION'];
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        // Start of @font-face at-rule block
        if ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                10)) === '@font-face'
        ) {
            $this->parser->pushState('T_AT_FONT_FACE::PREPARE');
            $this->parser->clearBuffer();

            return $index + 10;
        } // Start of @font-face declarations
        elseif ($char === '{' && $state === 'T_AT_FONT_FACE::PREPARE') {
            $this->parser->setState('T_AT_FONT_FACE');
            $this->parser->clearBuffer();
            $this->parser->appendToken(new CssAtFontFaceStartToken());
        } // Start of @font-face declaration
        elseif ($char === ':' && $state === 'T_AT_FONT_FACE') {
            $this->parser->pushState('T_AT_FONT_FACE_DECLARATION');
            $this->buffer = $this->parser->getAndClearBuffer(':', true);
        } // Unterminated @font-face declaration
        elseif ($char === ':' && $state === 'T_AT_FONT_FACE_DECLARATION') {
            // Ignore Internet Explorer filter declarations
            if ($this->buffer === 'filter') {
                return false;
            }
            CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__.': Unterminated @font-face declaration',
                $this->buffer.':'.$this->parser->getBuffer().'_'));
        } // End of @font-face declaration
        elseif (($char === ';' || $char === '}') && $state === 'T_AT_FONT_FACE_DECLARATION') {
            $value = $this->parser->getAndClearBuffer(';}');
            if (strtolower(substr($value, -10, 10)) === '!important') {
                $value = trim(substr($value, 0, -10));
                $isImportant = true;
            } else {
                $isImportant = false;
            }
            $this->parser->popState();
            $this->parser->appendToken(new CssAtFontFaceDeclarationToken($this->buffer, $value, $isImportant));
            $this->buffer = '';
            // --
            if ($char === '}') {
                $this->parser->appendToken(new CssAtFontFaceEndToken());
                $this->parser->popState();
            }
        } // End of @font-face at-rule block
        elseif ($char === '}' && $state === 'T_AT_FONT_FACE') {
            $this->parser->appendToken(new CssAtFontFaceEndToken());
            $this->parser->clearBuffer();
            $this->parser->popState();
        } else {
            return false;
        }

        return true;
    }
}

/**
 * This {@link aCssToken CSS token} represents the end of a @font-face at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtFontFaceEndToken extends aCssAtBlockEndToken
{
}

/**
 * This {@link aCssToken CSS token} represents a declaration of a @font-face at-rule block.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtFontFaceDeclarationToken extends aCssDeclarationToken
{
}

/**
 * This {@link aCssToken CSS token} represents a @charset at-rule.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtCharsetToken extends aCssToken
{
    /**
     * Charset of the @charset at-rule.
     *
     * @var string
     */
    public $Charset = '';

    /**
     * Set the properties of @charset at-rule token.
     *
     * @param string $charset Charset of the @charset at-rule token
     *
     * @return void
     */
    public function __construct($charset)
    {
        $this->Charset = $charset;
    }

    /**
     * Implements {@link aCssToken::__toString()}.
     *
     * @return string
     */
    public function __toString()
    {
        return '@charset '.$this->Charset.';';
    }
}

/**
 * {@link aCssParserPlugin Parser plugin} for parsing @charset at-rule.
 *
 * If a @charset at-rule was found this plugin will add a {@link CssAtCharsetToken} to the parser.
 *
 * @link           http://code.google.com/p/cssmin/
 *
 * @author         Joe Scylla <joe.scylla@gmail.com>
 * @copyright      2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
 * @license        http://opensource.org/licenses/mit-license.php MIT License
 *
 * @version        3.0.1
 */
class CssAtCharsetParserPlugin extends aCssParserPlugin
{
    /**
     * Implements {@link aCssParserPlugin::getTriggerChars()}.
     *
     * @return array
     */
    public function getTriggerChars()
    {
        return ['@', ';', "\n"];
    }

    /**
     * Implements {@link aCssParserPlugin::getTriggerStates()}.
     *
     * @return array
     */
    public function getTriggerStates()
    {
        return ['T_DOCUMENT', 'T_AT_CHARSET'];
    }

    /**
     * Implements {@link aCssParserPlugin::parse()}.
     *
     * @param int    $index        Current index
     * @param string $char         Current char
     * @param string $previousChar Previous char
     *
     * @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and
     *               break the processing
     */
    public function parse($index, $char, $previousChar, $state)
    {
        if ($char === '@' && $state === 'T_DOCUMENT' && strtolower(substr($this->parser->getSource(), $index,
                8)) === '@charset'
        ) {
            $this->parser->pushState('T_AT_CHARSET');
            $this->parser->clearBuffer();

            return $index + 8;
        } elseif (($char === ';' || $char === "\n") && $state === 'T_AT_CHARSET') {
            $charset = $this->parser->getAndClearBuffer(';');
            $this->parser->popState();
            $this->parser->appendToken(new CssAtCharsetToken($charset));
        } else {
            return false;
        }

        return true;
    }
}