예제 #1
0
        /// <summary>
        /// Crunched JS string passed to it, returning crunched string.
        /// The ErrorList property will be set with any errors found during the minification process.
        /// </summary>
        /// <param name="source">source Javascript</param>
        /// <param name="codeSettings">code minification settings</param>
        /// <returns>minified Javascript</returns>
        public string MinifyJavaScript(string source, CodeSettings codeSettings)
        {
            // default is an empty string
            var crunched = string.Empty;

            // reset the errors builder
            m_errorList = new List<ContextError>();

            // create the parser from the source string.
            // pass null for the assumed globals array
            var parser = new JSParser(source);

            // file context is a property on the parser
            parser.FileContext = FileName;

            // hook the engine error event
            parser.CompilerError += OnJavaScriptError;

            try
            {
                if (codeSettings != null && codeSettings.PreprocessOnly)
                {
                    // just run through the preprocessor only
                    crunched = parser.PreprocessOnly(codeSettings);
                }
                else
                {
                    // parse the input
                    var scriptBlock = parser.Parse(codeSettings);
                    if (scriptBlock != null)
                    {
                        // we'll return the crunched code
                        if (codeSettings != null && codeSettings.Format == JavaScriptFormat.JSON)
                        {
                            // we're going to use a different output visitor -- one
                            // that specifically returns valid JSON.
                            var sb = new StringBuilder();
                            using (var stringWriter = new StringWriter(sb, CultureInfo.InvariantCulture))
                            {
                                if (!JSONOutputVisitor.Apply(stringWriter, scriptBlock))
                                {
                                    m_errorList.Add(new ContextError(
                                        true,
                                        0,
                                        null,
                                        null,
                                        null,
                                        this.FileName,
                                        0,
                                        0,
                                        0,
                                        0,
                                        JScript.InvalidJSONOutput));
                                }
                            }

                            crunched = sb.ToString();
                        }
                        else
                        {
                            // just use the normal output visitor
                            crunched = scriptBlock.ToCode();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                m_errorList.Add(new ContextError(
                    true,
                    0,
                    null,
                    null,
                    null,
                    this.FileName,
                    0,
                    0,
                    0,
                    0,
                    e.Message));
                throw;
            }

            return crunched;
        }
예제 #2
0
        private Parsed ParseFunction()
        {
            Parsed parsed = Parsed.False;
            if (CurrentTokenType == TokenType.Function)
            {
                var crunchedRGB = false;
                var functionText = GetRoot(CurrentTokenText);

                if (string.Compare(functionText, "rgb(", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    // rgb function parsing
                    bool useRGB = false;
                    // converting to #rrggbb or #rgb IF we don't find any significant comments!
                    // skip any space or comments
                    int[] rgb = new int[3];

                    // we're going to be building up the rgb function just in case we need it
                    StringBuilder sbRGB = new StringBuilder();
                    sbRGB.Append(CurrentTokenText.ToLowerInvariant());

                    string comments = NextSignificantToken();
                    if (comments.Length > 0)
                    {
                        // add the comments
                        sbRGB.Append(comments);
                        // and signal that we need to use the RGB function because of them
                        useRGB = true;
                    }
                    for (int ndx = 0; ndx < 3; ++ndx)
                    {
                        // if this isn't the first number, we better find a comma separator
                        if (ndx > 0)
                        {
                            if (CurrentTokenType == TokenType.Character && CurrentTokenText == ",")
                            {
                                // add it to the rgb string builder
                                sbRGB.Append(',');
                            }
                            else if (CurrentTokenType == TokenType.Character && CurrentTokenText == ")")
                            {
                                ReportError(0, CssErrorCode.ExpectedComma, CurrentTokenText);

                                // closing paren is the end of the function! exit the loop
                                useRGB = true;
                                break;
                            }
                            else
                            {
                                ReportError(0, CssErrorCode.ExpectedComma, CurrentTokenText);
                                sbRGB.Append(CurrentTokenText);
                                useRGB = true;
                            }

                            // skip to the next significant
                            comments = NextSignificantToken();
                            if (comments.Length > 0)
                            {
                                // add the comments
                                sbRGB.Append(comments);
                                // and signal that we need to use the RGB function because of them
                                useRGB = true;
                            }
                        }

                        // although we ALLOW negative numbers here, we'll trim them
                        // later. But in the mean time, save a negation flag.
                        bool negateNumber = false;
                        if (CurrentTokenType == TokenType.Character && CurrentTokenText == "-")
                        {
                            negateNumber = true;
                            comments = NextSignificantToken();
                            if (comments.Length > 0)
                            {
                                // add the comments
                                sbRGB.Append(comments);
                                // and signal that we need to use the RGB function because of them
                                useRGB = true;
                            }
                        }

                        // we might adjust the value, so save the token text
                        string tokenText = CurrentTokenText;

                        if (CurrentTokenType != TokenType.Number && CurrentTokenType != TokenType.Percentage)
                        {
                            ReportError(0, CssErrorCode.ExpectedRgbNumberOrPercentage, CurrentTokenText);
                            useRGB = true;
                        }
                        else
                        {
                            if (CurrentTokenType == TokenType.Number)
                            {
                                // get the number value
                                float numberValue;
                                if (tokenText.TryParseSingleInvariant(out numberValue))
                                {
                                    numberValue *= (negateNumber ? -1 : 1);
                                    // make sure it's between 0 and 255
                                    if (numberValue < 0)
                                    {
                                        tokenText = "0";
                                        rgb[ndx] = 0;
                                    }
                                    else if (numberValue > 255)
                                    {
                                        tokenText = "255";
                                        rgb[ndx] = 255;
                                    }
                                    else
                                    {
                                        rgb[ndx] = System.Convert.ToInt32(numberValue);
                                    }
                                }
                                else
                                {
                                    // error -- not even a number. Keep the rgb function
                                    // (and don't change the token)
                                    useRGB = true;
                                }
                            }
                            else
                            {
                                // percentage
                                float percentageValue;
                                if (tokenText.Substring(0, tokenText.Length - 1).TryParseSingleInvariant(out percentageValue))
                                {
                                    percentageValue *= (negateNumber ? -1 : 1);
                                    if (percentageValue < 0)
                                    {
                                        tokenText = "0%";
                                        rgb[ndx] = 0;
                                    }
                                    else if (percentageValue > 100)
                                    {
                                        tokenText = "100%";
                                        rgb[ndx] = 255;
                                    }
                                    else
                                    {
                                        rgb[ndx] = System.Convert.ToInt32(percentageValue * 255 / 100);
                                    }
                                }
                                else
                                {
                                    // error -- not even a number. Keep the rgb function
                                    // (and don't change the token)
                                    useRGB = true;
                                }
                            }
                        }

                        // add the number to the rgb string builder
                        sbRGB.Append(tokenText);

                        // skip to the next significant
                        comments = NextSignificantToken();
                        if (comments.Length > 0)
                        {
                            // add the comments
                            sbRGB.Append(comments);
                            // and signal that we need to use the RGB function because of them
                            useRGB = true;
                        }
                    }

                    if (useRGB)
                    {
                        // something prevented us from collapsing the rgb function
                        // just output the rgb function we've been building up
                        Append(sbRGB.ToString());
                    }
                    else
                    {
                        // we can collapse it to either #rrggbb or #rgb
                        // calculate the full hex string and crunch it
                        string fullCode = "#{0:x2}{1:x2}{2:x2}".FormatInvariant(rgb[0], rgb[1], rgb[2]);
                        string hexString = CrunchHexColor(fullCode, Settings.ColorNames, m_noColorAbbreviation);
                        Append(hexString);

                        // set the flag so we know we don't want to add the closing paren
                        crunchedRGB = true;
                    }
                }
                else if (string.Compare(functionText, "expression(", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    Append(CurrentTokenText.ToLowerInvariant());
                    NextToken();

                    // for now, just echo out everything up to the matching closing paren, 
                    // taking into account that there will probably be other nested paren pairs. 
                    // The content of the expression is JavaScript, so we'd really
                    // need a full-blown JS-parser to crunch it properly. Kinda scary.
                    // Start the parenLevel at 0 because the "expression(" token contains the first paren.
                    var jsBuilder = new StringBuilder();
                    int parenLevel = 0;

                    while (!m_scanner.EndOfFile
                      && (CurrentTokenType != TokenType.Character
                        || CurrentTokenText != ")"
                        || parenLevel > 0))
                    {
                        if (CurrentTokenType == TokenType.Function)
                        {
                            // the function token INCLUDES the opening parenthesis,
                            // so up the paren level whenever we find a function.
                            // AND this includes the actual expression( token -- so we'll
                            // hit this branch at the beginning. Make sure the parenLevel
                            // is initialized to take that into account
                            ++parenLevel;
                        }
                        else if (CurrentTokenType == TokenType.Character)
                        {
                            switch (CurrentTokenText)
                            {
                                case "(":
                                    // start a nested paren
                                    ++parenLevel;
                                    break;

                                case ")":
                                    // end a nested paren 
                                    // (we know it's nested because if it wasn't, we wouldn't
                                    // have entered the loop)
                                    --parenLevel;
                                    break;
                            }
                        }
                        jsBuilder.Append(CurrentTokenText);
                        NextToken();
                    }

                    // create a JSParser object with the source we found, crunch it, and send 
                    // the minified script to the output
                    var expressionCode = jsBuilder.ToString();
                    if (Settings.MinifyExpressions)
                    {
                        // we want to minify the javascript expressions.
                        // create a JSParser object from the code we parsed.
                        JSParser jsParser = new JSParser(expressionCode);

                        // copy the file context
                        jsParser.FileContext = this.FileContext;

                        // hook the error handler and set the "contains errors" flag to false.
                        // the handler will set the value to true if it encounters any errors
                        var containsErrors = false;
                        jsParser.CompilerError += (sender, ea) =>
                            {
                                ReportError(0, CssErrorCode.ExpressionError, ea.Error.Message);
                                containsErrors = true;
                            };

                        // parse the source as an expression using our common JS settings
                        Block block = jsParser.Parse(m_jsSettings);

                        // if we got back a parsed block and there were no errors, output the minified code.
                        // if we didn't get back the block, or if there were any errors at all, just output
                        // the raw expression source.
                        if (block != null && !containsErrors)
                        {
                            Append(block.ToCode());
                        }
                        else
                        {
                            Append(expressionCode);
                        }
                    }
                    else
                    {
                        // we don't want to minify expression code for some reason.
                        // just output the code exactly as we parsed it
                        Append(expressionCode);
                    }
                }
                else if (string.Compare(functionText, "calc(", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    Append(CurrentTokenText.ToLowerInvariant());
                    SkipSpace();

                    // one sum
                    parsed = ParseSum();
                }
                else if (string.Compare(functionText, "min(", StringComparison.OrdinalIgnoreCase) == 0
                    || string.Compare(functionText, "max(", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    Append(CurrentTokenText.ToLowerInvariant());
                    SkipSpace();

                    // need to be one or more sums, separated by commas
                    // (ParseSum will only return true or false -- never empty)
                    parsed = ParseSum();
                    while (parsed == Parsed.True
                        && CurrentTokenType == TokenType.Character
                        && CurrentTokenText == ",")
                    {
                        AppendCurrent();
                        SkipSpace();
                        parsed = ParseSum();
                    }
                }
                else
                {
                    // generic function parsing
                    AppendCurrent();
                    SkipSpace();

                    if (ParseFunctionParameters() == Parsed.False)
                    {
                        ReportError(0, CssErrorCode.ExpectedExpression, CurrentTokenText);
                    }
                }

                if (CurrentTokenType == TokenType.Character
                  && CurrentTokenText == ")")
                {
                    if (!crunchedRGB)
                    {
                        Append(')');
                    }
                    SkipSpace();
                    parsed = Parsed.True;
                }
                else
                {
                    ReportError(0, CssErrorCode.UnexpectedToken, CurrentTokenText);
                }
            }
            return parsed;
        }