use
5.008009;
our
$VERSION
=
'2.09'
;
our
@COMPRESS
= (
'minify'
,
'pretty'
);
our
$DEFAULT_COMPRESS
=
'pretty'
;
our
@BOOLEAN_ACCESSORS
= (
'no_compress_comment'
,
'remove_copyright'
);
our
@COPYRIGHT_ACCESSORS
= (
'copyright'
,
'copyright_comment'
);
our
$COPYRIGHT_COMMENT
=
'\/\*((?>[^*]|\*[^/])*(?:license|copyright)(?>[^*]|\*[^/])*)\*\/'
;
our
$DICTIONARY
= {
'STRING1'
=>
qr~"(?>(?:(?>[^"\\]+)|\\.|\\"|\\\s)*)"~
,
'STRING2'
=>
qr~'(?>(?:(?>[^'\\]+)|\\.|\\'|\\\s)*)'~
};
our
$WHITESPACES
=
'\s+'
;
our
$RULE
=
'([^{};]+)\{([^{}]*)\}'
;
our
$URL
=
'url\(\s*('
.
$DICTIONARY
->{STRING1} .
'|'
.
$DICTIONARY
->{STRING2} .
'|[^\'"\s]+?)\s*\)'
;
our
$IMPORT
=
'\@import\s+('
.
$DICTIONARY
->{STRING1} .
'|'
.
$DICTIONARY
->{STRING2} .
'|'
.
$URL
.
')([^;]*);'
;
our
$AT_RULE
=
'\@\S+([^{}]+)\{((?:'
.
$IMPORT
.
'|'
.
$RULE
.
'|'
.
$WHITESPACES
.
')+)\}'
;
our
$DECLARATION
=
'((?>[^;:]+)):(?<=:)((?>[^;]*))(?:;(?!(charset|base64))|\s*$)'
;
our
$COMMENT
=
'(\/\*[^*]*\*+([^/][^*]*\*+)*\/)'
;
our
$PACKER_COMMENT
=
'\/\*\s*CSS::Packer\s*(\w+)\s*\*\/'
;
our
$CHARSET
=
'^(\@charset)\s+('
.
$DICTIONARY
->{STRING1} .
'|'
.
$DICTIONARY
->{STRING2} .
');'
;
our
@REGGRPS
= (
'whitespaces'
,
'url'
,
'import'
,
'declaration'
,
'rule'
,
'content_value'
,
'mediarules'
,
'global'
);
{
no
strict
'refs'
;
foreach
my
$reggrp
(
@REGGRPS
) {
next
if
defined
*{ __PACKAGE__ .
'::reggrp_'
.
$reggrp
}{CODE};
*{ __PACKAGE__ .
'::reggrp_'
.
$reggrp
} =
sub
{
my
(
$self
) =
shift
;
return
$self
->{
'_reggrp_'
.
$reggrp
};
};
}
foreach
my
$field
(
@BOOLEAN_ACCESSORS
) {
next
if
defined
*{ __PACKAGE__ .
'::'
.
$field
}{CODE};
*{ __PACKAGE__ .
'::'
.
$field
} =
sub
{
my
(
$self
,
$value
) =
@_
;
$self
->{
'_'
.
$field
} =
$value
? 1 :
undef
if
(
defined
(
$value
) );
return
$self
->{
'_'
.
$field
};
};
}
foreach
my
$field
(
@COPYRIGHT_ACCESSORS
) {
$field
=
'_'
.
$field
if
(
$field
eq
'copyright_comment'
);
next
if
defined
*{ __PACKAGE__ .
'::'
.
$field
}{CODE};
*{ __PACKAGE__ .
'::'
.
$field
} =
sub
{
my
(
$self
,
$value
) =
@_
;
if
(
defined
(
$value
) and not
ref
(
$value
) ) {
$value
=~ s/^\s*|\s*$//gs;
$self
->{
'_'
.
$field
} =
$value
;
}
my
$ret
=
''
;
if
(
$self
->{
'_'
.
$field
} ) {
$ret
=
'/* '
.
$self
->{
'_'
.
$field
} .
' */'
.
"\n"
;
}
return
$ret
;
};
}
}
sub
compress {
my
(
$self
,
$value
) =
@_
;
if
(
defined
(
$value
) ) {
if
(
grep
(
$value
eq
$_
,
@COMPRESS
) ) {
$self
->{_compress} =
$value
;
}
elsif
( !
$value
) {
$self
->{_compress} =
undef
;
}
}
$self
->{_compress} ||=
$DEFAULT_COMPRESS
;
return
$self
->{_compress};
}
our
$reggrp_url
;
our
$reggrp_declaration
;
our
$reggrp_mediarules
;
our
$reggrp_content_value
;
our
$global_compress
;
our
$indent
=
''
;
sub
init {
my
$class
=
shift
;
my
$self
= {};
bless
(
$self
,
$class
);
$self
->{content_value}->{reggrp_data} = [
{
regexp
=>
$DICTIONARY
->{STRING1}
},
{
regexp
=>
$DICTIONARY
->{STRING2}
},
{
regexp
=>
qr~([\w-]+)\(\s*([\w-]+)\s*\)~
,
replacement
=>
sub
{
return
$_
[0]->{submatches}->[0] .
'('
.
$_
[0]->{submatches}->[1] .
')'
;
}
},
{
regexp
=>
$WHITESPACES
,
replacement
=>
''
}
];
$self
->{whitespaces}->{reggrp_data} = [
{
regexp
=>
$WHITESPACES
,
replacement
=>
''
}
];
$self
->{url}->{reggrp_data} = [
{
regexp
=>
$URL
,
replacement
=>
sub
{
my
$url
=
$_
[0]->{submatches}->[0];
return
'url('
.
$url
.
')'
;
}
}
];
$self
->{
import
}->{reggrp_data} = [
{
regexp
=>
$IMPORT
,
replacement
=>
sub
{
my
$submatches
=
$_
[0]->{submatches};
my
$url
=
$submatches
->[0];
my
$mediatype
=
$submatches
->[2];
my
$compress
=
$global_compress
;
$reggrp_url
->
exec
( \
$url
);
$mediatype
=~ s/^\s*|\s*$//gs;
$mediatype
=~ s/\s*,\s*/,/gsm;
return
'@import '
.
$url
. (
$mediatype
? (
' '
.
$mediatype
) :
''
) .
';'
. (
$compress
eq
'pretty'
?
"\n"
:
''
);
}
}
];
$self
->{declaration}->{reggrp_data} = [
{
regexp
=>
$DECLARATION
,
replacement
=>
sub
{
my
$submatches
=
$_
[0]->{submatches};
my
$key
=
$submatches
->[0];
my
$value
=
$submatches
->[1];
my
$compress
=
$global_compress
;
$key
=~ s/^\s*|\s*$//gs;
$value
=~ s/^\s*|\s*$//gs;
if
(
$key
eq
'content'
) {
$reggrp_content_value
->
exec
( \
$value
);
}
else
{
$value
=~ s/\s*,\s*/,/gsm;
$value
=~ s/\s+/ /gsm;
}
return
''
if
( not
$key
or
$value
eq
''
);
return
$indent
.
$key
.
':'
.
$value
.
';'
. (
$compress
eq
'pretty'
?
"\n"
:
''
);
}
}
];
$self
->{rule}->{reggrp_data} = [
{
regexp
=>
$RULE
,
replacement
=>
sub
{
my
$submatches
=
$_
[0]->{submatches};
my
$selector
=
$submatches
->[0];
my
$declaration
=
$submatches
->[1];
my
$compress
=
$global_compress
;
$selector
=~ s/^\s*|\s*$//gs;
$selector
=~ s/\s*,\s*/,/gsm;
$selector
=~ s/\s+/ /gsm;
$declaration
=~ s/^\s*|\s*$//gs;
$reggrp_declaration
->
exec
( \
$declaration
);
my
$store
=
$selector
.
'{'
. (
$compress
eq
'pretty'
?
"\n"
:
''
) .
$declaration
.
'}'
.
(
$compress
eq
'pretty'
?
"\n"
:
''
);
$store
=
''
unless
(
$selector
or
$declaration
);
return
$store
;
}
}
];
$self
->{mediarules}->{reggrp_data} = [
@{
$self
->{
import
}->{reggrp_data}},
@{
$self
->{rule}->{reggrp_data}},
@{
$self
->{whitespaces}->{reggrp_data}}
];
$self
->{global}->{reggrp_data} = [
{
regexp
=>
$CHARSET
,
replacement
=>
sub
{
my
$submatches
=
$_
[0]->{submatches};
my
$compress
=
$global_compress
;
return
$submatches
->[0] .
" "
.
$submatches
->[1] . (
$compress
eq
'pretty'
?
"\n"
:
''
);
}
},
{
regexp
=>
$AT_RULE
,
replacement
=>
sub
{
my
$original
=
$_
[0]->{match};
my
(
$selector
) = (
$original
=~ /\@(\S+)/ );
my
$submatches
=
$_
[0]->{submatches};
my
$mediatype
=
$submatches
->[0];
my
$mediarules
=
$submatches
->[1];
my
$compress
=
$global_compress
;
$mediatype
=~ s/^\s*|\s*$//gs;
$mediatype
=~ s/\s*,\s*/,/gsm;
$reggrp_mediarules
->
exec
( \
$mediarules
);
return
"\@$selector "
.
$mediatype
.
'{'
. (
$compress
eq
'pretty'
?
"\n"
:
''
) .
$mediarules
.
'}'
. (
$compress
eq
'pretty'
?
"\n"
:
''
);
}
},
@{
$self
->{mediarules}->{reggrp_data}}
];
map
{
$self
->{
'_reggrp_'
.
$_
} = Regexp::RegGrp->new(
{
reggrp
=>
$self
->{
$_
}->{reggrp_data}
}
);
}
@REGGRPS
;
return
$self
;
}
sub
minify {
my
(
$self
,
$input
,
$opts
);
unless
(
ref
(
$_
[0] ) and
ref
(
$_
[0] ) eq __PACKAGE__
) {
$self
= __PACKAGE__->init();
shift
(
@_
)
unless
(
ref
(
$_
[0] ) );
(
$input
,
$opts
) =
@_
;
}
else
{
(
$self
,
$input
,
$opts
) =
@_
;
}
if
(
ref
(
$input
) ne
'SCALAR'
) {
carp(
'First argument must be a scalarref!'
);
return
undef
;
}
my
$css
= \
''
;
my
$cont
=
'void'
;
if
(
defined
(
wantarray
) ) {
my
$tmp_input
=
ref
(
$input
) ? ${
$input
} :
$input
;
$css
= \
$tmp_input
;
$cont
=
'scalar'
;
}
else
{
$css
=
ref
(
$input
) ?
$input
: \
$input
;
}
if
(
ref
(
$opts
) eq
'HASH'
) {
foreach
my
$field
(
@BOOLEAN_ACCESSORS
) {
$self
->
$field
(
$opts
->{
$field
} )
if
(
defined
(
$opts
->{
$field
} ) );
}
foreach
my
$field
(
'compress'
,
'copyright'
) {
$self
->
$field
(
$opts
->{
$field
} )
if
(
defined
(
$opts
->{
$field
} ) );
}
$indent
=
$opts
->{indent} && ( !
$opts
->{compress} ||
$opts
->{compress} eq
'pretty'
) ?
' '
x
$opts
->{indent} :
''
;
}
$reggrp_url
=
$self
->reggrp_url;
$reggrp_declaration
=
$self
->reggrp_declaration;
$reggrp_mediarules
=
$self
->reggrp_mediarules;
$reggrp_content_value
=
$self
->reggrp_content_value;
$global_compress
=
$self
->compress;
my
$copyright_comment
=
''
;
if
( ${
$css
} =~ /
$COPYRIGHT_COMMENT
/ism ) {
$copyright_comment
= $1;
}
$self
->_copyright_comment(
$copyright_comment
);
if
( not
$self
->no_compress_comment() and ${
$css
} =~ /
$PACKER_COMMENT
/ ) {
my
$compress
= $1;
if
(
$compress
eq
'_no_compress_'
) {
return
(
$cont
eq
'scalar'
) ? ${
$css
} :
undef
;
}
$self
->compress(
$compress
);
}
${
$css
} =~ s/
$COMMENT
/ /gsm;
$self
->reggrp_global()->
exec
(
$css
);
if
( not
$self
->remove_copyright() ) {
${
$css
} = (
$self
->copyright() ||
$self
->_copyright_comment() ) . ${
$css
};
}
return
${
$css
}
if
(
$cont
eq
'scalar'
);
}
1;