Location: PHPKode > scripts > CSS Crush > peteboere-css-crush-0b51f2e/lib/Importer.php
<?php
/**
 *
 * Recursive file importing
 *
 */
class csscrush_importer {

    static public function hostfile () {

        $config = csscrush::$config;
        $process = csscrush::$process;
        $options = $process->options;
        $regex = csscrush_regex::$patt;
        $input = $process->input;

        // Keep track of all import file info for cache data.
        $mtimes = array();
        $filenames = array();

        $str = '';
        $prepend_file_contents = '';

        // The prepend file.
        if ( $prepend_file = csscrush_util::find( 'Prepend-local.css', 'Prepend.css' ) ) {
            $prepend_file_contents = file_get_contents( $prepend_file );
            $process->currentFile = 'file://' . $prepend_file;

            // If there's a parsing error inside the prepend file, wipe $prepend_file_contents.
            if ( ! self::prepareForStream( $prepend_file_contents ) ) {
                $prepend_file_contents = '';
            }
        }

        // Resolve main input; a string of css or a file.
        if ( isset( $input->string ) ) {
            $str .= $input->string;
            $process->currentFile = 'inline css';
        }
        else {
            $str .= file_get_contents( $input->path );
            $process->currentFile = 'file://' . $input->path;
        }

        // If there's a parsing error go no further.
        if ( ! self::prepareForStream( $str ) ) {
            return $str;
        }

        // Prepend any prepend file contents here.
        $str = $prepend_file_contents . $str;

        // This may be set non-zero during the script if an absolute @import URL is encountered.
        $search_offset = 0;

        // Recurses until the nesting heirarchy is flattened and all import files are inlined.
        while ( preg_match( $regex->import, $str, $match, PREG_OFFSET_CAPTURE, $search_offset ) ) {

            $match_len = strlen( $match[0][0] );
            $match_start = $match[0][1];
            $match_end = $match_start + $match_len;

            // If just stripping the import statements
            if ( isset( $input->importIgnore ) ) {
                $str = substr_replace( $str, '', $match_start, $match_len );
                continue;
            }

            // Fetch the URL object.
            $url = csscrush_url::get( $match[1][0] );

            // Pass over protocoled import urls.
            if ( $url->protocol ) {
                $search_offset = $match_end;
                continue;
            }

            // The media context (if specified).
            $media_context = trim( $match[2][0] );

            // Create import object.
            $import = (object) array();
            $import->url = $url;
            $import->mediaContext = $media_context;

            // Resolve import realpath.
            if ( $url->isRooted ) {
                $import->path = realpath( $process->docRoot . $import->url->value );
            }
            else {
                $import->path = realpath( "$input->dir/{$import->url->value}" );
            }

            // Get the import contents, if unsuccessful just continue with the import line removed.
            if ( ! ( $import->content = @file_get_contents( $import->path ) ) ) {
                csscrush::log( "Import file '{$import->url->value}' not found" );
                $str = substr_replace( $str, '', $match_start, $match_len );
                continue;
            }

            // Import file opened successfully so we process it:
            //   - We need to resolve import statement urls in all imported files since
            //     they will be brought inline with the hostfile
            $process->currentFile = 'file://' . $import->path;

            // If there are unmatched brackets inside the import, strip it.
            if ( ! self::prepareForStream( $import->content ) ) {
                $str = substr_replace( $str, '', $match_start, $match_len );
                continue;
            }

            $import->dir = dirname( $import->url->value );

            // Store import file info for cache validation.
            $mtimes[] = filemtime( $import->path );
            $filenames[] = $import->url->value;

            // Alter all the @import urls to be paths relative to the hostfile.
            foreach ( csscrush_regex::matchAll( $regex->import, $import->content ) as $m ) {

                // Fetch the matched URL.
                $url2 = csscrush_url::get( $m[1][0] );

                // Try to resolve absolute paths.
                // On failure strip the @import statement.
                if ( $url2->isRooted ) {
                    $url2->resolveRootedPath();
                }
                else {
                    $url2->prepend( "$import->dir/" );
                }
            }

            // Optionally rewrite relative url and custom function data-uri references.
            if ( $options->rewrite_import_urls ) {
                self::rewriteImportedUrls( $import );
            }

            // Add media context if it exists
            if ( $import->mediaContext ) {
                $import->content = "@media $import->mediaContext {{$import->content}}";
            }

            $str = substr_replace( $str, $import->content, $match_start, $match_len );
        }

        // Save only if caching is on and the hostfile object is associated with a real file.
        if ( $input->path && $options->cache ) {

            $process->cacheData[ $process->output->filename ] = array(
                'imports'   => $filenames,
                'datem_sum' => array_sum( $mtimes ) + $input->mtime,
                'options'   => clone $options,
            );

            // Save config changes.
            $process->ioCall( 'saveCacheData' );
        }

        return $str;
    }

    static protected function rewriteImportedUrls ( $import ) {

        $link = csscrush_util::getLinkBetweenDirs(
            csscrush::$process->input->dir, dirname( $import->path ) );

        if ( empty( $link ) ) {
            return;
        }

        // Match all urls that are not imports.
        preg_match_all( '#(?<!@import )\?u\d+\?#iS', $import->content, $matches );

        foreach ( $matches[0] as $token ) {

            // Fetch the matched URL.
            $url = csscrush_url::get( $token );

            if ( $url->isRelative ) {
                // Prepend the relative url prefix.
                $url->prepend( $link );
            }
        }
    }

    static protected function prepareForStream ( &$str ) {

        $regex = csscrush_regex::$patt;
        $process = csscrush::$process;

        // Convert all end-of-lines to unix style.
        $str = preg_replace( '~\r\n?~', "\n", $str );

        $str = preg_replace_callback( $regex->commentAndString,
            array( 'self', 'cb_extractCommentAndString' ), $str );

        // If @charset is set store it.
        if ( preg_match( $regex->charset, $str, $m ) ) {
            $replace = '';
            if ( ! $process->charset ) {
                // Keep track of newlines for line tracing.
                $replace = str_repeat( "\n", substr_count( $m[0], "\n" ) );
                $process->charset = trim( $process->fetchToken( $m[1] ), '"\'' );
            }
            $str = preg_replace( $regex->charset, $replace, $str );
        }

        // Catch obvious typing errors.
        $parse_errors = array();
        $current_file = $process->currentFile;
        $balanced_parens = substr_count( $str, "(" ) === substr_count( $str, ")" );
        $balanced_curlies = substr_count( $str, "{" ) === substr_count( $str, "}" );

        if ( ! $balanced_parens ) {
            $parse_errors[] = "Unmatched '(' in $current_file.";
        }
        if ( ! $balanced_curlies ) {
            $parse_errors[] = "Unmatched '{' in $current_file.";
        }

        if ( $parse_errors ) {
            foreach ( $parse_errors as $error_msg ) {
                csscrush::logError( $error_msg );
                trigger_error( "$error_msg\n", E_USER_WARNING );
            }
            return false;
        }

        // Optionally add tracing stubs.
        if ( in_array( 'stubs', $process->options->trace ) ) {
            self::addTracingStubs( $str );
        }

        // Strip unneeded whitespace.
        $str = csscrush_util::normalizeWhiteSpace( $str );

        self::captureUrls( $str );

        return true;
    }

    static protected function captureUrls ( &$str ) {

        $patt = '#
            @import\x{20}(\?s\d+\?)
            |
            (?<!-) \b (url|data-uri)\(
        #ixS';

        $offset = 0;
        while ( preg_match( $patt, $str, $outer_m, PREG_OFFSET_CAPTURE, $offset ) ) {

            $outer_offset = $outer_m[0][1];
            $is_import_url = ! isset( $outer_m[2] );

            if ( $is_import_url ) {
                $url = new csscrush_url( $outer_m[1][0] );
                $str = str_replace( $outer_m[1][0], $url->label, $str );
            }
            // Match parenthesis if not a string token.
            elseif (
                preg_match( csscrush_regex::$patt->balancedParens, $str, $inner_m, PREG_OFFSET_CAPTURE, $outer_offset )
            ) {
                $url = new csscrush_url( $inner_m[1][0] );
                $func_name = strtolower( $outer_m[2][0] );
                $url->convertToData = 'data-uri' === $func_name;
                $str = substr_replace( $str, $url->label, $outer_offset,
                    strlen( $func_name ) + strlen( $inner_m[0][0] ) );
            }
            // If brackets cannot be matched, skip over the original match.
            else {
                $offset += strlen( $outer_m[0][0] );
            }
        }
    }

    static protected function cb_extractCommentAndString ( $match ) {

        $full_match = $match[0];
        $process = csscrush::$process;

        // We return the newlines to maintain line numbering when tracing.
        $newlines = str_repeat( "\n", substr_count( $full_match, "\n" ) );

        if ( strpos( $full_match, '/*' ) === 0 ) {

            // Bail without storing comment if in debug mode or a private comment.
            if (
                $process->options->minify ||
                strpos( $full_match, '/*$' ) === 0
            ) {
                return $newlines;
            }

            // Fix broken comments as they will break any subsquent
            // imported files that are inlined.
            if ( ! preg_match( '!\*/$!', $full_match ) ) {
                $full_match .= '*/';
            }
            $label = $process->addToken( $full_match, 'c' );
        }
        else {

            // Fix broken strings as they will break any subsquent
            // imported files that are inlined.
            if ( $full_match[0] !== $full_match[ strlen( $full_match )-1 ] ) {
                $full_match .= $full_match[0];
            }
            $label = $process->addToken( $full_match, 's' );
        }

        return $newlines . $label;
    }

    static protected function addTracingStubs ( &$str ) {

        $selector_patt = '! (^|;|\})+ ([^;{}]+) (\{) !xmS';
        $token_or_whitespace = '!(\s*\?c\d+\?\s*|\s+)!S';

        $matches = csscrush_regex::matchAll( $selector_patt, $str );

        // Start from last match and move backwards.
        while ( $m = array_pop( $matches ) ) {

            // Shortcuts for readability.
            list( $full_match, $before, $content, $after ) = $m;
            $full_match_text  = $full_match[0];
            $full_match_start = $full_match[1];

            // The correct before string.
            $before = substr( $full_match_text, 0, $content[1] - $full_match_start );

            // Split the matched selector part.
            $content_parts = preg_split( $token_or_whitespace, $content[0], null,
                PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );

            foreach ( $content_parts as $part ) {

                if ( ! preg_match( $token_or_whitespace, $part ) ) {

                    // Match to a valid selector.
                    if ( preg_match( '!^([^@]|@(?:page|abstract))!iS', $part ) ) {

                        // Count line breaks between the start of stream and
                        // the matched selector to get the line number.
                        $selector_index = $full_match_start + strlen( $before );
                        $line_num = 1;
                        $str_before = "";
                        if ( $selector_index ) {
                            $str_before = substr( $str, 0, $selector_index );
                            $line_num = substr_count( $str_before, "\n" ) + 1;
                        }

                        // Get the currently processed file path, and escape it.
                        $current_file = str_replace( ' ', '%20', csscrush::$process->currentFile );
                        $current_file = preg_replace( '![^\w-]!', '\\\\$0', $current_file );

                        // Splice in tracing stub.
                        $label = csscrush::$process->addToken( "@media -sass-debug-info{filename{font-family:$current_file}line{font-family:\\00003$line_num}}", 't' );

                        $str = $str_before . $label . substr( $str, $selector_index );
                    }
                    else {
                        // Not matched as a valid selector, move on.
                        continue 2;
                    }
                    break;
                }

                // Append split segment to $before.
                $before .= $part;
            }
        }
    }

}
Return current item: CSS Crush