Exemple #1
0
        protected override string DoCompress(string source)
        {
            int totalLen        = source.Length;
            int startIndex      = 0;
            var comments        = new ArrayList();
            var preservedTokens = new ArrayList();
            int max;

            if (RemoveComments)
            {
                while ((startIndex = source.IndexOf(@"/*", startIndex, StringComparison.OrdinalIgnoreCase)) >= 0)
                {
                    int endIndex = source.IndexOf(@"*/", startIndex + 2, StringComparison.OrdinalIgnoreCase);
                    if (endIndex < 0)
                    {
                        endIndex = totalLen;
                    }

                    // Note: java substring-length param = end index - 2 (which is end index - (startindex + 2))
                    string token = source.Substring(startIndex + 2, endIndex - (startIndex + 2));

                    comments.Add(token);

                    string newResult = Extensions.Replace(source, startIndex + 2, endIndex,
                                                          Tokens.PreserveCandidateComment + (comments.Count - 1) +
                                                          "___");

                    startIndex += 2;
                    source      = newResult;
                }
            }

            // Preserve strings so their content doesn't get accidently minified
            var   stringBuilder = new StringBuilder();
            Match match         = protectStringRegex.Match(source);
            int   index         = 0;

            while (match.Success)
            {
                var text = match.Groups[0].Value;
                if (string.IsNullOrEmpty(text))
                {
                    continue;
                }

                var token = match.Value;
                var quote = token[0];

                // Java code: token.substring(1, token.length() -1) .. but that's ...
                //            token.substring(start index, end index) .. while .NET it's length for the 2nd arg.
                token = token.Substring(1, token.Length - 2);

                // Maybe the string contains a comment-like substring?
                // one, maybe more? put'em back then.
                if (token.IndexOf(Tokens.PreserveCandidateComment, StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    max = comments.Count;
                    for (int i = 0; i < max; i += 1)
                    {
                        token = token.Replace(Tokens.PreserveCandidateComment + i + "___",
                                              comments[i].ToString());
                    }
                }

                // Minify alpha opacity in filter strings.
                token = Extensions.RegexReplace(token, "(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=",
                                                "alpha(opacity=");

                preservedTokens.Add(token);
                string preserver = quote + Tokens.PreservedToken + (preservedTokens.Count - 1) + "___" +
                                   quote;

                index = Extensions.AppendReplacement(match, stringBuilder, source, preserver, index);
                match = match.NextMatch();
            }
            Extensions.AppendTail(stringBuilder, source, index);
            source = stringBuilder.ToString();

            // Strings are safe, now wrestle the comments.
            max = comments.Count;
            for (int i = 0; i < max; i += 1)
            {
                var token       = comments[i].ToString();
                var placeholder = Tokens.PreserveCandidateComment + i + "___";

                // ! in the first position of the comment means preserve
                // so push to the preserved tokens while stripping the !
                if (token.StartsWith("!"))
                {
                    preservedTokens.Add(token);
                    source = source.Replace(placeholder, Tokens.PreservedToken + (preservedTokens.Count - 1) + "___");
                    continue;
                }

                // \ in the last position looks like hack for Mac/IE5
                // shorten that to /*\*/ and the next one to /**/
                if (token.EndsWith("\\"))
                {
                    preservedTokens.Add("\\");
                    source = source.Replace(placeholder, Tokens.PreservedToken + (preservedTokens.Count - 1) + "___");
                    i      = i + 1; // attn: advancing the loop.
                    preservedTokens.Add(string.Empty);
                    source = source.Replace(Tokens.PreserveCandidateComment + i + "___",
                                            Tokens.PreservedToken + (preservedTokens.Count - 1) + "___");
                    continue;
                }

                // keep empty comments after child selectors (IE7 hack)
                // e.g. html >/**/ body
                if (token.Length == 0)
                {
                    startIndex = source.IndexOf(placeholder, StringComparison.OrdinalIgnoreCase);
                    if (startIndex > 2)
                    {
                        if (source[startIndex - 3] == '>')
                        {
                            preservedTokens.Add(string.Empty);
                            source = source.Replace(placeholder,
                                                    Tokens.PreservedToken + (preservedTokens.Count - 1) + "___");
                        }
                    }
                }

                // In all other cases kill the comment.
                // Is this a closed comment?
                if (source.Contains("/*" + placeholder + "*/"))
                {
                    source = source.Replace("/*" + placeholder + "*/", string.Empty);
                }
                else
                {
                    // Nope - is it an unclosed comment?
                    if (source.Contains("/*" + placeholder))
                    {
                        // TODO: Add a Warning to the log (once we have a log!)
                        source = source.Replace("/*" + placeholder, string.Empty);
                    }
                }
            }

            // Preserve any calc(...) css - https://yuicompressor.codeplex.com/workitem/11445
            stringBuilder = new StringBuilder();
            match         = calcRegex.Match(source);
            index         = 0;
            while (match.Success)
            {
                preservedTokens.Add(match.Value);
                string preserver = Tokens.PreservedToken + (preservedTokens.Count - 1) + "___";

                index = Extensions.AppendReplacement(match, stringBuilder, source, preserver, index);
                match = match.NextMatch();
            }
            Extensions.AppendTail(stringBuilder, source, index);
            source = stringBuilder.ToString();

            // Normalize all whitespace strings to single spaces. Easier to work with that way.
            source = Extensions.RegexReplace(source, "\\s+", " ");

            // Remove the spaces before the things that should not have spaces before them.
            // But, be careful not to turn "p :link {...}" into "p:link{...}"
            // Swap out any pseudo-class colons with the token, and then swap back.
            stringBuilder = new StringBuilder();
            match         = spaceRemoverRegex.Match(source);
            index         = 0;
            while (match.Success)
            {
                string text = match.Value;
                text  = text.Replace(":", Tokens.PseudoClassColon);
                text  = text.Replace("\\\\", "\\\\\\\\");
                text  = text.Replace("\\$", "\\\\\\$");
                index = Extensions.AppendReplacement(match, stringBuilder, source, text, index);
                match = match.NextMatch();
            }
            Extensions.AppendTail(stringBuilder, source, index);
            source = stringBuilder.ToString();

            // Remove spaces before the things that should not have spaces before them.
            source = Extensions.RegexReplace(source, "\\s+([!{};:>+\\(\\)\\],])", "$1");

            // Bring back the colon.
            source = Extensions.RegexReplace(source, Tokens.PseudoClassColon, ":");

            // Retain space for special IE6 cases.
            source = Extensions.RegexReplace(source, ":first\\-(line|letter)(\\{|,)", ":first-$1 $2");

            // no space after the end of a preserved comment.
            source = Extensions.RegexReplace(source, "\\*/ ", "*/");

            // If there is a @charset, then only allow one, and push to the top of the file.
            source = Extensions.RegexReplace(source, "^(.*)(@charset \"[^\"]*\";)", "$2$1");
            source = Extensions.RegexReplace(source, "^(\\s*@charset [^;]+;\\s*)+", "$1");

            // Put the space back in some cases, to support stuff like
            // @media screen and (-webkit-min-device-pixel-ratio:0){
            source = Extensions.RegexReplace(source, "\\band\\(", "and (");

            // Remove the spaces after the things that should not have spaces after them.
            source = Extensions.RegexReplace(source, "([!{}:;>+\\(\\[,])\\s+", "$1");

            // remove unnecessary semicolons.
            source = Extensions.RegexReplace(source, ";+}", "}");

            // Replace 0(px,em,%) with 0.
            source = Extensions.RegexReplace(source, "([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");

            // Replace 0 0 0 0; with 0.
            source = Extensions.RegexReplace(source, ":0 0 0 0(;|})", ":0$1");
            source = Extensions.RegexReplace(source, ":0 0 0(;|})", ":0$1");
            source = Extensions.RegexReplace(source, ":0 0(;|})", ":0$1");

            // Replace background-position:0; with background-position:0 0;
            // same for transform-origin
            stringBuilder = new StringBuilder();
            match         = backgroundPositionRegex.Match(source);
            index         = 0;
            while (match.Success)
            {
                index = Extensions.AppendReplacement(match, stringBuilder, source,
                                                     match.Groups[1].Value.ToLowerInvariant() + ":0 0" + match.Groups[2],
                                                     index);
                match = match.NextMatch();
            }
            Extensions.AppendTail(stringBuilder, source, index);
            source = stringBuilder.ToString();

            // Replace 0.6 to .6, but only when preceded by : or a white-space.
            source = Extensions.RegexReplace(source, "(:|\\s)0+\\.(\\d+)", "$1.$2");

            // Shorten colors from rgb(51,102,153) to #336699
            // This makes it more likely that it'll get further compressed in the next step.
            stringBuilder = new StringBuilder();
            match         = colorRegex.Match(source);
            index         = 0;
            while (match.Success)
            {
                var rgbcolors = match.Groups[1].Value.Split(',');
                var hexcolor  = new StringBuilder("#");
                foreach (var rgbColour in rgbcolors)
                {
                    int value;
                    if (!Int32.TryParse(rgbColour, out value))
                    {
                        value = 0;
                    }

                    if (value < 16)
                    {
                        hexcolor.Append("0");
                    }
                    hexcolor.Append(Extensions.ToHexString(value).ToLowerInvariant());
                }

                index = Extensions.AppendReplacement(match, stringBuilder, source, hexcolor.ToString(), index);
                match = match.NextMatch();
            }
            Extensions.AppendTail(stringBuilder, source, index);
            source = stringBuilder.ToString();

            // Shorten colors from #AABBCC to #ABC. Note that we want to make sure
            // the color is not preceded by either ", " or =. Indeed, the property
            //     filter: chroma(color="#FFFFFF");
            // would become
            //     filter: chroma(color="#FFF");
            // which makes the filter break in IE.
            stringBuilder = new StringBuilder();
            match         = colorShortenerRegex.Match(source);
            index         = 0;
            while (match.Success)
            {
                // Test for AABBCC pattern.
                if (Extensions.EqualsIgnoreCase(match.Groups[3].Value, match.Groups[4].Value) &&
                    Extensions.EqualsIgnoreCase(match.Groups[5].Value, match.Groups[6].Value) &&
                    Extensions.EqualsIgnoreCase(match.Groups[7].Value, match.Groups[8].Value))
                {
                    string replacement = String.Concat(match.Groups[1].Value, match.Groups[2].Value, "#",
                                                       match.Groups[3].Value, match.Groups[5].Value,
                                                       match.Groups[7].Value);
                    index = Extensions.AppendReplacement(match, stringBuilder, source, replacement, index);
                }
                else
                {
                    index = Extensions.AppendReplacement(match, stringBuilder, source, match.Value, index);
                }

                match = match.NextMatch();
            }
            Extensions.AppendTail(stringBuilder, source, index);
            source = stringBuilder.ToString();

            // border: none -> border:0
            stringBuilder = new StringBuilder();
            match         = borderRegex.Match(source);
            index         = 0;
            while (match.Success)
            {
                string replacement = match.Groups[1].Value.ToLowerInvariant() + ":0" + match.Groups[2].Value;
                index = Extensions.AppendReplacement(match, stringBuilder, source, replacement, index);
                match = match.NextMatch();
            }
            Extensions.AppendTail(stringBuilder, source, index);
            source = stringBuilder.ToString();

            // Shorter opacity IE filter.
            source = Extensions.RegexReplace(source, "(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");

            // Remove empty rules.
            if (source.Contains("{}"))
            {
                //Test if contains because this regex may be very time consuming, up until 1minute for some 150kb css!)
                source = emptyRuleRegex.Replace(source, String.Empty);
            }

            if (LineBreakPosition >= 0)
            {
                // Some source control tools don't like it when files containing lines longer
                // than, say 8000 characters, are checked in. The linebreak option is used in
                // that case to split long lines after a specific column.
                int i            = 0;
                int linestartpos = 0;
                stringBuilder = new StringBuilder(source);
                while (i < stringBuilder.Length)
                {
                    char c = stringBuilder[i++];
                    if (c == '}' && i - linestartpos > LineBreakPosition)
                    {
                        stringBuilder.Insert(i, '\n');
                        linestartpos = i;
                    }
                }

                source = stringBuilder.ToString();
            }

            // Replace multiple semi-colons in a row by a single one.
            // See SF bug #1980989.
            source = Extensions.RegexReplace(source, ";;+", ";");

            // Restore preserved comments and strings.
            max = preservedTokens.Count;
            for (int i = 0; i < max; i++)
            {
                source = source.Replace(Tokens.PreservedToken + i + "___", preservedTokens[i].ToString());
            }

            // Trim the final string (for any leading or trailing white spaces).
            source = source.Trim();

            // Write the output...
            return(source);
        }