CSS-Compressor

 view release on metacpan or  search on metacpan

Compressor.pm  view on Meta::CPAN

package CSS::Compressor;

use strict;
use warnings;

use Exporter qw( import );

our @EXPORT_OK = qw( css_compress );

our $VERSION = '0.05';

our $MARKER;

# take package name, replace double colons with underscore and use that as
# marker for search and replace operations
BEGIN {
    $MARKER = uc __PACKAGE__;
    $MARKER =~ tr!:!_!s;
}

# build optimized regular expression variables ( foo -> [Ff][Oo][Oo] )
my (
    $RE_BACKGROUND_POSITION,
    $RE_TRANSFORM_ORIGIN_MOZ,
    $RE_TRANSFORM_ORIGIN_MS,
    $RE_TRANSFORM_ORIGIN_O,
    $RE_TRANSFORM_ORIGIN_WEBKIT,
    $RE_TRANSFORM_ORIGIN,
    $RE_BORDER,
    $RE_BORDER_TOP,
    $RE_BORDER_RIGHT,
    $RE_BORDER_BOTTOM,
    $RE_BORDER_LEFT,
    $RE_OUTLINE,
    $RE_BACKGROUND,
    $RE_ALPHA_FILTER,
) = map +(
    join '' => map m![a-zA-Z]!
       ? '['.ucfirst($_).lc($_).']'
       : '\\'.$_,
       split m//
) => qw[
    background-position
       moz-transform-origin
        ms-transform-origin
         o-transform-origin
    webkit-transform-origin
           transform-origin
    border
    border-top
    border-right
    border-bottom
    border-right
    outline
    background
    progid:DXImageTransform.Microsoft.Alpha(Opacity=
];

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#  compress
#
#  IN: 1 uncompressed CSS
# OUT: 1 compressed CSS

sub css_compress {
    my ( $css ) = @_;
    my @comments,
    my @tokens;

    # collect all comment blocks...
    $css =~ s! /\* (.*?) \*/
             ! '/*___'.$MARKER.'_PRESERVE_CANDIDATE_COMMENT_'.
               ( -1 + push @comments => $1 ).'___*/'
             !sogex;

    # preserve urls to prevent breaking inline SVG for example
    $css =~ s! ( url \( ( (?: [^()]++ | \( (?2) \) )*+ ) \) ) !
        '___'.$MARKER.'_PRESERVED_TOKEN_'.(-1+push @tokens => $1).'___'
        !gxe;

    # preserve strings so their content doesn't get accidentally minified
    $css =~ s! " ( [^"\\]*(?:\\.[^"\\]*)* ) " !
        $_ = $1,

        # maybe the string contains a comment-like substring?
        # one, maybe more? put'em back then
        s/___${MARKER}_PRESERVE_CANDIDATE_COMMENT_([0-9]+)___/$comments[$1]/go,

        # minify alpha opacity in filter strings
        s/$RE_ALPHA_FILTER/alpha(opacity=/go,

        '"___'.$MARKER.'_PRESERVED_TOKEN_'.(-1+push @tokens => $_).'___"'
       !sgxe;
    $css =~ s! ' ( [^'\\]*(?:\\.[^'\\]*)* ) ' !
        $_ = $1,

        s/___${MARKER}_PRESERVE_CANDIDATE_COMMENT_([0-9]+)___/$comments[$1]/go,

        s/$RE_ALPHA_FILTER/alpha(opacity=/go,

        '\'___'.$MARKER.'_PRESERVED_TOKEN_'.(-1+push @tokens => $_).'___\''
       !sgxe;

    # strings are safe, now wrestle the comments

    # ! in the first position of the comment means preserve
    # so push to the preserved tokens while stripping the !
    0 == index $_->[1] => '!'
      and -1 == index $_->[1] => '! @noflip'
      and
        $css =~ s!___${MARKER}_PRESERVE_CANDIDATE_COMMENT_$_->[0]___!
                 '___'.$MARKER.'_PRESERVED_TOKEN_'.(-1+push @tokens => $_->[1]).'___'!e

    # keep empty comments after child selectors (IE7 hack)
    # e.g. html >/**/ body
    or 0 == length $_->[1]



( run in 1.565 second using v1.01-cache-2.11-cpan-5735350b133 )