Location: PHPKode > scripts > CSS Crush > peteboere-css-crush-0b51f2e/lib/Mixin.php
<?php
/**
 *
 *  Mixin objects
 *
 */
class csscrush_mixin {

    public $declarationsTemplate = array();

    public $arguments;

    public $data = array();

    public function __construct ( $block ) {

        // Strip comment markers
        $block = csscrush_util::stripCommentTokens( $block );

        // Prepare the arguments object
        $this->arguments = new csscrush_arglist( $block );

        // Re-assign with the parsed arguments string
        $block = $this->arguments->string;

        // Split the block around semi-colons.
        $declarations = preg_split( '!\s*;\s*!', trim( $block ), null, PREG_SPLIT_NO_EMPTY );

        foreach ( $declarations as $raw_declaration ) {

            $colon = strpos( $raw_declaration, ':' );
            if ( $colon === -1 ) {
                continue;
            }

            // Store template declarations as arrays as they are copied by value not reference
            $declaration = array();

            $declaration['property'] = trim( substr( $raw_declaration, 0, $colon ) );
            $declaration['value'] = trim( substr( $raw_declaration, $colon + 1 ) );

            if ( $declaration['property'] === 'mixin' ) {

                // Mixin can contain other mixins if they are available
                if ( $mixin_declarations = csscrush_mixin::parseValue( $declaration['value'] ) ) {

                    // Add mixin result to the stack
                    $this->declarationsTemplate = array_merge( $this->declarationsTemplate, $mixin_declarations );
                }
            }
            elseif ( ! empty( $declaration['value'] ) ) {
                $this->declarationsTemplate[] = $declaration;
            }
        }

        // Create data table for the mixin.
        // Values that use arg() are excluded
        foreach ( $this->declarationsTemplate as &$declaration ) {
            if ( ! preg_match( csscrush_regex::$patt->aToken, $declaration['value'] ) ) {
                $this->data[ $declaration['property'] ] = $declaration['value'];
            }
        }
        return '';
    }

    public function call ( array $args ) {

        // Copy the template
        $declarations = $this->declarationsTemplate;

        if ( count( $this->arguments ) ) {

            list( $find, $replace ) = $this->arguments->getSubstitutions( $args );

            // Place the arguments
            foreach ( $declarations as &$declaration ) {
                $declaration['value'] = str_replace( $find, $replace, $declaration['value'] );
            }
        }

        // Return mixin declarations
        return $declarations;
    }

    static public function parseSingleValue ( $message ) {

        $message = ltrim( $message );
        $mixin = null;
        $non_mixin = null;

        // e.g.
        //   - mymixin( 50px, rgba(0,0,0,0), left 100% )
        //   - abstract-rule
        //   - #selector

        // Test for leading name
        if ( preg_match( '!^[\w-]+!', $message, $name_match ) ) {

            $name = $name_match[0];

            if ( isset( csscrush::$process->mixins[ $name ] ) ) {

                // Mixin match
                $mixin = csscrush::$process->mixins[ $name ];
            }
            elseif ( isset( csscrush::$process->abstracts[ $name ] ) ) {

                // Abstract rule match
                $non_mixin = csscrush::$process->abstracts[ $name ];
            }
        }

        // If no mixin or abstract rule matched, look for matching selector
        if ( ! $mixin && ! $non_mixin ) {

            $selector_test = csscrush_selector::makeReadableSelector( $message );

            if ( isset( csscrush::$process->selectorRelationships[ $selector_test ] ) ) {
                $non_mixin = csscrush::$process->selectorRelationships[ $selector_test ];
            }
        }

        // If no mixin matched, but matched alternative, use alternative
        if ( ! $mixin ) {

            if ( $non_mixin ) {

                // Return expected format
                $result = array();
                foreach ( $non_mixin as $declaration ) {
                    $result[] = array(
                        'property' => $declaration->property,
                        'value'    => $declaration->value,
                    );
                }
                return $result;
            }
            else {

                // Nothing matches
                return false;
            }
        }

        // We have a valid mixin.
        // Discard the name part and any wrapping parens and whitespace
        $message = substr( $message, strlen( $name ) );
        $message = preg_replace( '!^\s*\(?\s*|\s*\)?\s*$!', '', $message );

        // e.g. "value, rgba(0,0,0,0), left 100%"

        // Determine what raw arguments there are to pass to the mixin
        $args = array();
        if ( $message !== '' ) {
            $args = csscrush_util::splitDelimList( $message );
        }

        return $mixin->call( $args );
    }

    static public function parseValue ( $message ) {

        // Call the mixin and return the list of declarations
        $declarations = array();

        foreach ( csscrush_util::splitDelimList( $message ) as $item ) {

            if ( $result = self::parseSingleValue( $item ) ) {

                $declarations = array_merge( $declarations, $result );
            }
        }
        return $declarations;
    }
}


/**
 *
 *  Fragment objects
 *
 */
class csscrush_fragment {

    public $template = array();

    public $arguments;

    public function __construct ( $block ) {

        // Prepare the arguments object
        $this->arguments = new csscrush_arglist( $block );

        // Re-assign with the parsed arguments string
        $this->template = $this->arguments->string;
    }

    public function call ( array $args ) {

        // Copy the template
        $template = $this->template;

        if ( count( $this->arguments ) ) {

            list( $find, $replace ) = $this->arguments->getSubstitutions( $args );
            $template = str_replace( $find, $replace, $template );
        }

        // Return fragment css
        return $template;
    }
}


/**
 *
 *  Argument list management for mixins and fragments
 *
 */
class csscrush_arglist implements Countable {

    // Positional argument default values
    public $defaults = array();

    // The number of expected arguments
    public $argCount = 0;

    // The string passed in with arg calls replaced by tokens
    public $string;

    public function __construct ( $str ) {

        // Parse all arg function calls in the passed string, callback creates default values
        csscrush_function::executeCustomFunctions( $str, 
                csscrush_regex::$patt->argFunction, array(
                    'arg' => array( $this, 'store' )
                ));
        $this->string = $str;
    }

    public function store ( $raw_argument ) {

        $args = csscrush_function::parseArgsSimple( $raw_argument );

        // Match the argument index integer
        if ( ! ctype_digit( $args[0] ) ) {

            // On failure to match an integer, return an empty string
            return '';
        }

        // Get the match from the array
        $position_match = $args[0];

        // Store the default value
        $default_value = isset( $args[1] ) ? $args[1] : null;

        if ( ! is_null( $default_value ) ) {
            $this->defaults[ $position_match ] = trim( $default_value );
        }

        // Update the mixin argument count
        $argNumber = ( (int) $position_match ) + 1;
        $this->argCount = max( $this->argCount, $argNumber );

        // Return the argument token
        return "?arg$position_match?";
    }

    public function getArgValue ( $index, &$args ) {

        // First lookup a passed value
        if ( isset( $args[ $index ] ) && $args[ $index ] !== 'default' ) {
            return $args[ $index ];
        }

        // Get a default value
        $default = isset( $this->defaults[ $index ] ) ? $this->defaults[ $index ] : '';

        // Recurse for nested arg() calls
        if ( preg_match( csscrush_regex::$patt->aToken, $default, $m ) ) {

            $default = $this->getArgValue( (int) $m[1], $args );
        }
        return $default;
    }

    public function getSubstitutions ( $args ) {

        $argIndexes = range( 0, $this->argCount-1 );

        // Create table of substitutions
        $find = array();
        $replace = array();

        foreach ( $argIndexes as $index ) {

            $find[] = "?arg$index?";
            $replace[] = $this->getArgValue( $index, $args );
        }

        return array( $find, $replace );
    }

    public function count () {
        return $this->argCount;
    }
}

Return current item: CSS Crush