Minifier无法识别回车

时间:2012-01-14 03:51:05

标签: css perl whitespace minify

我正在使用这个缩小器:

#!/usr/bin/perl

use Getopt::Std;
use strict;

my %opts;
getopts('f:t:h', \%opts) || usage(1);

if ($opts{'h'}) {
    usage();
    exit;
}

if (!exists($opts{'f'}) || !exists($opts{'t'})) {
    print STDERR "ERROR: You must supply an input file with -f and an output file with -t\n";
    usage(1);
}

# actually perform the minimization
my $issame = 0;
if ($opts{'f'} eq $opts{'t'}) {
    $issame = 1;
    $opts{'t'} = $opts{'f'} . ".tmp$$";
}
open(INFILE, $opts{'f'}) or die "couldn't open the file '$opts{f}'";
open(OUTFILE, ">$opts{'t'}") or die "couldn't open the file '$opts{t}'";
minify(input => *INFILE, outfile => *OUTFILE);
close(INFILE);
close(OUTFILE);

if ($issame) {
    rename($opts{'t'}, $opts{'f'});
}

sub usage {
    print STDERR "USAGE: $0 -f INPUTFILE -t OUTPUTFILE\n";
    exit($_[0] || 0);
}


# -----------------------------------------------------------------------------

sub isSpace {
  my $x = shift;
  return ($x eq ' ' || $x eq "\t");
}

sub isEndspace {
  my $x = shift;
  return ($x eq "\n" || $x eq "\r" || $x eq "\f");
}

sub isWhitespace {
  my $x = shift;
  return (isSpace($x) || isEndspace($x));
}

# whitespace characters before or after these characters can be removed.
sub isInfix {
  my $x = shift;
  return ($x eq '{' || $x eq '}' || $x eq ';' || $x eq ':');  
}

# whitespace characters after these characters can be removed.
sub isPrefix {
  my $x = shift;
  return (isInfix($x));
}

# whitespace characters before these characters can removed.
sub isPostfix {
  my $x = shift;
  return (isInfix($x));
}

# -----------------------------------------------------------------------------

sub _get {
  my $s = shift;
  if ($s->{inputType} eq 'file') {
    return getc($s->{input});
  }
  elsif ($s->{inputType} eq 'string') {
    if ($s->{'inputPos'} < length($s->{input})) {
      return substr($s->{input}, $s->{inputPos}++, 1);
    }
    else { # Simulate getc() when off the end of the input string.
      return undef;
    }
  }
  else {
   die "no input";
  }
}

sub _put {
  my $s = shift;
  my $x = shift;
  my $outfile = ($s->{outfile});
  if (defined($s->{outfile})) {
    print $outfile $x;
  }
  else {
    $s->{output} .= $x;
  }
}

# -----------------------------------------------------------------------------
# print a
# new b
#
# i.e. print a and advance
sub action1 {
  my $s = shift;
  $s->{last} = $s->{a};
  _put($s, $s->{a});
  action2($s);
}

# move b to a
# new b
#
# i.e. delete a and advance
sub action2 {
  my $s = shift;
  $s->{a} = $s->{b};
  $s->{b} = $s->{c};
  $s->{c} = _get($s);
}

# -----------------------------------------------------------------------------

# put string literals
# when this sub is called, $s->{a} is on the opening delimiter character
sub putLiteral {
  my $s = shift;
  my $delimiter = $s->{a}; # ' or "

  action1($s);
  do {
    while (defined($s->{a}) && $s->{a} eq '\\') { # escape character escapes only the next one character
      action1($s);       
      action1($s);       
    }
    action1($s);
  } until ($s->{last} eq $delimiter || !defined($s->{a}));
  if ($s->{last} ne $delimiter) { # ran off end of file before printing the closing delimiter
    die 'unterminated ' . ($delimiter eq '\'' ? 'single quoted string' : 'double quoted string') . ' literal, stopped';
  }
}

# -----------------------------------------------------------------------------

# If $s->{a} is a whitespace then collapse all following whitespace.
# If any of the whitespace is a new line then ensure $s->{a} is a new line
# when this function ends.
sub collapseWhitespace {
  my $s = shift;
  while (defined($s->{a}) && isWhitespace($s->{a}) &&
         defined($s->{b}) && isWhitespace($s->{b})) {
    if (isEndspace($s->{a}) || isEndspace($s->{b})) {
      $s->{a} = "\n";
    }
    action2($s); # delete b
  }
}

# Advance $s->{a} to non-whitespace or end of file.
# Doesn't print any of this whitespace.
sub skipWhitespace {
  my $s = shift;
  while (defined($s->{a}) && isWhitespace($s->{a})) {
    action2($s);
  }
}

# #s->{a} should be on whitespace when this function is called
sub preserveWhitespace {
  my $s = shift;
  collapseWhitespace($s);
  if (defined($s->{a}) && defined($s->{b}) && !isPostfix($s->{b})) {
    action1($s); # print the whitespace character
  }
  skipWhitespace($s);
}

# -----------------------------------------------------------------------------

sub minify {
  my %h = @_;
  # Immediately turn hash into a hash reference so that notation is the same in this function
  # as others. Easier refactoring.
  my $s = \%h; # hash reference for "state". This module is functional programming and the state is passed between functions.

  # determine if the the input is a string or a file handle.
  my $ref = \$s->{input};
  if (defined($ref) && ref($ref) eq 'SCALAR'){
    $s->{inputPos} = 0;
    $s->{inputType} = 'string';
  }
  else {
    $s->{inputType} = 'file';
  }

  # Determine if the output is to a string or a file.
  if (!defined($s->{outfile})) {
    $s->{output} = '';
  }

  # Print the copyright notice first
  if ($s->{copyright}) {
    _put($s, '/* ' . $s->{copyright} . ' */');
  }

  # Initialize the buffer.
  do {
    $s->{a} = _get($s);
  } while (defined($s->{a}) && isWhitespace($s->{a}));
  $s->{b} = _get($s);
  $s->{c} = _get($s);
  $s->{last} = undef;

  # local variables
  my $macIeCommentHackFlag = 0; # marks if a have recently seen a comment with an escaped  close like this /* foo \*/
                                # and have not yet seen a regular comment to close this like /* bar */

  while (defined($s->{a})) { # on this line $s->{a} should always be a non-whitespace character or undef (i.e. end of file)

    if (isWhitespace($s->{a})) { # check that this program is running correctly
      die 'minifier bug: minify while loop starting with whitespace, stopped';
    }

    # Each branch handles trailing whitespace and ensures $s->{a} is on non-whitespace or undef when branch finishes
    if ($s->{a} eq '/' && defined($s->{b}) && $s->{b} eq '*') { # a comment
      do {
        action2($s); # advance buffer by one
        # if a is \, b is *, c is /, hack flag false
           # Mac/IE hack start
           # set hack flag true
           # print /*\*/
        if ($s->{a} eq '\\' &&
            defined($s->{b}) && $s->{b} eq '*' && 
            defined($s->{c}) && $s->{c} eq '/' && 
            !$macIeCommentHackFlag) {
            $macIeCommentHackFlag = 1;
            _put($s, '/*\\*/');
            $s->{last} = '/';
        }
        # if a is not \, b is *, c is /, hack flag true
           # Mac/IE hack end
           # set hack flag false
           # print /**/
        if ($s->{a} ne '\\' &&
            defined($s->{b}) && $s->{b} eq '*' && 
            defined($s->{c}) && $s->{c} eq '/' && 
            $macIeCommentHackFlag) {
            $macIeCommentHackFlag = 0;
            _put($s, '/**/');
            $s->{last} = '/';
        }

      } until (!defined($s->{b}) || ($s->{a} eq '*' && $s->{b} eq '/'));
      if (defined($s->{b})) { # $s->{a} is asterisk and $s->{b} is forward slash
        action2($s); # the *
        $s->{a} = ' ';  # the /
        skipWhitespace($s);
      }
      else {
        die 'unterminated comment, stopped';
      }
    }
    elsif ($s->{a} eq '\'' || $s->{a} eq '"') {
      putLiteral($s);
      if (defined($s->{a}) && isWhitespace($s->{a})) {
        preserveWhitespace($s); # can this be skipWhitespace?
      }
    }
    elsif (isPrefix($s->{a})) {
      action1($s);
      skipWhitespace($s);
    }
    else { # anything else just prints
      action1($s);
      if (defined($s->{a}) && isWhitespace($s->{a})) {
          preserveWhitespace($s);
      }
    }
  }

  if (!defined($s->{outfile})) {
    return $s->{output};
  }

}

1;

...但它不适用于多个选择器。所以这个:

#id h3,
#id tag {
    property:value;
}

tag1,
tag2,
tag3 {
    property:value;
}

转换为:

#id h3,
#id tag{property:value;}tag1,
tag2,
tag3{property:value;}

如您所见,换行符(在#id h3之后)保持原样,但不应该。

我应该添加什么来完成这项工作。

谢谢!

1 个答案:

答案 0 :(得分:2)

您应该能够简单地修改isInfix函数:

# whitespace characters before or after these characters can be removed.
sub isInfix {
  my $x = shift;
  return ($x eq '{' || $x eq '}' || $x eq ';' || $x eq ':' || $x eq ',');
}