예제 #1
0
        /// <summary>
        /// Checks whether to minify the Angular binding expression in attribute
        /// </summary>
        /// <param name="tag">Tag</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>Result of check (true - can minify expression; false - can not minify expression)</returns>
        private bool CanMinifyAngularBindingExpressionInAttribute(HtmlTag tag, HtmlAttribute attribute)
        {
            string tagNameInLowercase = tag.NameInLowercase;
            string attributeNameInLowercase = attribute.NameInLowercase;
            IList<HtmlAttribute> attributes = tag.Attributes;

            bool canMinify = false;

            if (tag.Flags.HasFlag(HtmlTagFlags.Custom))
            {
                string elementDirectiveName = AngularHelpers.NormalizeDirectiveName(tagNameInLowercase);

                switch (elementDirectiveName)
                {
                    case "ngPluralize":
                        canMinify = attributeNameInLowercase == "count" || attributeNameInLowercase == "when";
                        break;
                    case "ngMessages":
                        canMinify = attributeNameInLowercase == "for";
                        break;
                }
            }

            if (!canMinify)
            {
                string attributeDirectiveName = AngularHelpers.NormalizeDirectiveName(attributeNameInLowercase);
                canMinify = ContainsAngularBindingExpression(attributeDirectiveName);

                if (!canMinify)
                {
                    switch (attributeDirectiveName)
                    {
                        case "ngTrueValue":
                        case "ngFalseValue":
                            canMinify = tagNameInLowercase == "input" && attributes.Any(
                                a => a.NameInLowercase == "type" && a.Value.Trim().IgnoreCaseEquals("checkbox"));
                            break;
                    }
                }
            }

            return canMinify;
        }
 /// <summary>
 /// Minify a Knockout binding expression
 /// </summary>
 /// <param name="context">Markup parsing context</param>
 /// <param name="attribute">Attribute</param>
 /// <returns>Minified binding expression</returns>
 private string MinifyKnockoutBindingExpression(MarkupParsingContext context, HtmlAttribute attribute)
 {
     return MinifyKnockoutBindingExpression(context, attribute.ValueCoordinates, SourceCodeNodeCoordinates.Empty,
         attribute.Value);
 }
예제 #3
0
 /// <summary>
 /// Checks whether attribute is the attribute <code>type</code> of
 /// tag <code>script</code>, that containing JavaScript code
 /// </summary>
 /// <param name="tag">Tag</param>
 /// <param name="attribute">Attribute</param>
 /// <returns>Result of check</returns>
 private static bool IsJavaScriptTypeAttribute(HtmlTag tag, HtmlAttribute attribute)
 {
     return tag.NameInLowercase == "script" && attribute.NameInLowercase == "type"
         && attribute.Value.Trim().IgnoreCaseEquals(JS_CONTENT_TYPE);
 }
예제 #4
0
        /// <summary>
        /// Builds a attribute view model
        /// </summary>
        /// <param name="context">Markup parsing context</param>
        /// <param name="tag">Tag</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>String representation of the attribute</returns>
        private HtmlAttributeViewModel BuildAttributeViewModel(MarkupParsingContext context, HtmlTag tag, HtmlAttribute attribute)
        {
            string tagNameInLowercase = tag.NameInLowercase;
            HtmlAttributeViewModel attributeViewModel;
            string attributeName = attribute.Name;
            string attributeNameInLowercase = attribute.NameInLowercase;
            string attributeValue = attribute.Value;
            bool attributeHasValue = attribute.HasValue;
            bool attributeHasEmptyValue = !attributeHasValue || attributeValue.Length == 0;
            HtmlAttributeType attributeType = attribute.Type;
            bool useHtmlSyntax = !_settings.UseXhtmlSyntax;

            if (useHtmlSyntax && attributeType == HtmlAttributeType.Xml && attributeNameInLowercase != "xmlns")
            {
                string sourceCode = context.SourceCode;
                SourceCodeNodeCoordinates attributeCoordinates = attribute.NameCoordinates;

                WriteWarning(LogCategoryConstants.HtmlMinificationWarning,
                    string.Format(Strings.WarningMessage_XmlBasedAttributeNotAllowed, attributeName), _fileContext,
                    attributeCoordinates.LineNumber, attributeCoordinates.ColumnNumber,
                    SourceCodeNavigator.GetSourceFragment(sourceCode, attributeCoordinates));
            }

            if ((_settings.RemoveRedundantAttributes && IsAttributeRedundant(tag, attribute))
                || (_settings.RemoveJsTypeAttributes && IsJavaScriptTypeAttribute(tag, attribute))
                || (_settings.RemoveCssTypeAttributes && IsCssTypeAttribute(tag, attribute))
                || (useHtmlSyntax && CanRemoveXmlNamespaceAttribute(tag, attribute)))
            {
                if (CanRemoveAttribute(tag, attribute))
                {
                    attributeViewModel = HtmlAttributeViewModel.Empty;
                    return attributeViewModel;
                }
            }

            bool isCustomBooleanAttribute = !attributeHasValue && attributeType == HtmlAttributeType.Text;
            if (isCustomBooleanAttribute)
            {
                if (useHtmlSyntax)
                {
                    attributeViewModel = InnerBuildAttributeViewModel(attribute, true, false);
                    return attributeViewModel;
                }

                attribute.Value = string.Empty;
            }
            else if (attributeType != HtmlAttributeType.Event
                && !attributeHasEmptyValue
                && TemplateTagHelpers.ContainsTag(attributeValue))
            {
                // Processing of template tags
                StringBuilder attributeValueBuilder = StringBuilderPool.GetBuilder();

                TemplateTagHelpers.ParseMarkup(attributeValue,
                    (localContext, expression, startDelimiter, endDelimiter) =>
                    {
                        string processedExpression = expression;
                        if (_settings.MinifyAngularBindingExpressions && startDelimiter == "{{" && endDelimiter == "}}")
                        {
                            processedExpression = MinifyAngularBindingExpression(context, attribute.ValueCoordinates,
                                localContext.NodeCoordinates, expression);
                        }

                        attributeValueBuilder.Append(startDelimiter);
                        attributeValueBuilder.Append(processedExpression);
                        attributeValueBuilder.Append(endDelimiter);
                    },
                    (localContext, textValue) =>
                    {
                        string processedTextValue = textValue;
                        if (attributeType == HtmlAttributeType.ClassName)
                        {
                            processedTextValue = Utils.CollapseWhitespace(textValue);
                        }

                        attributeValueBuilder.Append(processedTextValue);
                    }
                );

                string processedAttributeValue = attributeValueBuilder.ToString();
                StringBuilderPool.ReleaseBuilder(attributeValueBuilder);

                switch (attributeType)
                {
                    case HtmlAttributeType.Uri:
                    case HtmlAttributeType.Numeric:
                    case HtmlAttributeType.ClassName:
                        processedAttributeValue = processedAttributeValue.Trim();
                        break;
                    case HtmlAttributeType.Style:
                        processedAttributeValue = processedAttributeValue.Trim();
                        processedAttributeValue = Utils.RemoveEndingSemicolon(processedAttributeValue);
                        break;
                    default:
                        if (_settings.MinifyAngularBindingExpressions && tag.Flags.HasFlag(HtmlTagFlags.Custom))
                        {
                            string elementDirectiveName = AngularHelpers.NormalizeDirectiveName(tagNameInLowercase);
                            if (elementDirectiveName == "ngPluralize" && attributeNameInLowercase == "when")
                            {
                                processedAttributeValue = MinifyAngularBindingExpression(context, attribute.ValueCoordinates,
                                    processedAttributeValue);
                            }
                        }

                        break;
                }

                attribute.Value = processedAttributeValue;
            }
            else if (attributeType == HtmlAttributeType.Boolean)
            {
                if (_settings.CollapseBooleanAttributes)
                {
                    attributeViewModel = InnerBuildAttributeViewModel(attribute, true, false);
                    return attributeViewModel;
                }

                attribute.Value = attributeName;
            }
            else
            {
                if (!attributeHasEmptyValue)
                {
                    attribute.Value = CleanAttributeValue(context, tag, attribute);
                }

                if (_settings.RemoveEmptyAttributes && CanRemoveEmptyAttribute(tag, attribute))
                {
                    if (CanRemoveAttribute(tag, attribute))
                    {
                        attributeViewModel = HtmlAttributeViewModel.Empty;
                        return attributeViewModel;
                    }
                }
            }

            bool addQuotes = !CanRemoveAttributeQuotes(attribute, _settings.AttributeQuotesRemovalMode);
            attributeViewModel = InnerBuildAttributeViewModel(attribute, false, addQuotes);

            return attributeViewModel;
        }
예제 #5
0
        /// <summary>
        /// Parses a start tag
        /// </summary>
        /// <param name="tagName">Tag name</param>
        /// <param name="tagNameInLowercase">Tag name in lowercase</param>
        /// <param name="attributes">List of attributes</param>
        /// <param name="isEmptyTag">Flag that tag is empty</param>
        private void ParseStartTag(string tagName, string tagNameInLowercase, List <HtmlAttribute> attributes,
                                   bool isEmptyTag)
        {
            HtmlTagFlags tagFlags = GetTagFlagsByName(tagNameInLowercase);

            if (tagFlags.IsSet(HtmlTagFlags.Optional))
            {
                HtmlTag lastStackedTag = _tagStack.LastOrDefault();
                if (lastStackedTag != null && lastStackedTag.NameInLowercase == tagNameInLowercase)
                {
                    ParseEndTag(lastStackedTag.Name, lastStackedTag.NameInLowercase);
                }
                else
                {
                    if (tagNameInLowercase == "body" && _tagStack.Any(t => t.NameInLowercase == "head"))
                    {
                        HtmlTag headTag = _tagStack.First(t => t.NameInLowercase == "head");
                        ParseEndTag(headTag.Name, headTag.NameInLowercase);
                    }
                }
            }

            if (tagFlags.IsSet(HtmlTagFlags.Empty))
            {
                isEmptyTag = true;
            }
            else if (isEmptyTag)
            {
                tagFlags |= HtmlTagFlags.Empty;
            }

            int attributeCount = attributes.Count;

            for (int attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++)
            {
                HtmlAttribute attribute = attributes[attributeIndex];
                attribute.Type = _attributeTypeDeterminer.GetAttributeType(tagNameInLowercase, tagFlags,
                                                                           attribute.NameInLowercase, attributes);
            }

            var tag = new HtmlTag(tagName, tagNameInLowercase, attributes, tagFlags);

            if (!isEmptyTag)
            {
                if (_conditionalCommentOpened)
                {
                    HtmlConditionalComment     lastConditionalComment     = _conditionalCommentStack.Peek();
                    HtmlConditionalCommentType lastConditionalCommentType = lastConditionalComment.Type;

                    if (tagFlags.IsSet(HtmlTagFlags.EmbeddedCode) ||
                        lastConditionalCommentType == HtmlConditionalCommentType.RevealedValidating ||
                        lastConditionalCommentType == HtmlConditionalCommentType.RevealedValidatingSimplified)
                    {
                        _tagStack.Add(tag);
                    }
                }
                else
                {
                    _tagStack.Add(tag);
                }
            }

            _handlers.StartTag?.Invoke(_context, tag);

            if (tagFlags.IsSet(HtmlTagFlags.Xml) && !tagFlags.IsSet(HtmlTagFlags.NonIndependent))
            {
                _xmlTagStack.Push(tagNameInLowercase);
            }
        }
예제 #6
0
        /// <summary>
        /// Checks whether the attribute is custom
        /// </summary>
        /// <param name="attribute">Attribute</param>
        /// <returns>Result of check</returns>
        private static bool IsCustomAttribute(HtmlAttribute attribute)
        {
            bool isCustomAttribute = false;

            if (attribute.Type == HtmlAttributeType.Text)
            {
                string attributeNameInLowercase = attribute.NameInLowercase;
                int charCount = attributeNameInLowercase.Length;

                for (int charIndex = 0; charIndex < charCount; charIndex++)
                {
                    char charValue = attributeNameInLowercase[charIndex];

                    if (!charValue.IsAlphaLower())
                    {
                        isCustomAttribute = true;
                        break;
                    }
                }

                if (isCustomAttribute)
                {
                    isCustomAttribute = attributeNameInLowercase != "accept-charset"
                        && attributeNameInLowercase != "http-equiv";
                }
            }

            return isCustomAttribute;
        }
예제 #7
0
 /// <summary>
 /// Checks whether remove an the <code>xmlns</code> attribute
 /// </summary>
 /// <param name="tag">Tag</param>
 /// <param name="attribute">Attribute</param>
 /// <returns>Result of check (true - can be removed; false - can not be removed)</returns>
 private static bool CanRemoveXmlNamespaceAttribute(HtmlTag tag, HtmlAttribute attribute)
 {
     return tag.NameInLowercase == "html" && attribute.NameInLowercase == "xmlns";
 }
        /// <summary>
        /// Builds a string representation of the attribute
        /// </summary>
        /// <param name="context">Markup parsing context</param>
        /// <param name="attribute">HTML attribute</param>
        /// <param name="tag">HTML tag</param>
        /// <returns>String representation of the attribute</returns>
        private string BuildAttributeString(MarkupParsingContext context, HtmlTag tag, HtmlAttribute attribute)
        {
            string tagName = tag.Name;
            HtmlTagFlags tagFlags = tag.Flags;
            IList<HtmlAttribute> attributes = tag.Attributes;

            string attributeString;
            string attributeName = attribute.Name;
            string attributeValue = attribute.Value;
            bool attributeHasValue = attribute.HasValue;
            HtmlAttributeType attributeType = attribute.Type;
            bool useHtmlSyntax = !_settings.UseXhtmlSyntax;

            if (useHtmlSyntax && IsXmlAttribute(attributeName))
            {
                string sourceCode = context.SourceCode;
                SourceCodeNodeCoordinates attributeCoordinates = attribute.NameCoordinates;

                WriteWarning(LogCategoryConstants.HtmlMinificationWarning,
                    string.Format(Strings.WarningMessage_XmlBasedAttributeNotAllowed, attributeName), _fileContext,
                    attributeCoordinates.LineNumber, attributeCoordinates.ColumnNumber,
                    SourceCodeNavigator.GetSourceFragment(sourceCode, attributeCoordinates));
            }

            if ((_settings.RemoveRedundantAttributes && IsAttributeRedundant(tagName, attributeName, attributeValue, attributes))
                || (_settings.RemoveJsTypeAttributes && IsJavaScriptTypeAttribute(tagName, attributeName, attributeValue))
                || (_settings.RemoveCssTypeAttributes && IsCssTypeAttribute(tagName, attributeName, attributeValue, attributes))
                || (useHtmlSyntax && CanRemoveXmlAttribute(tagName, attributeName)))
            {
                attributeString = string.Empty;
                return attributeString;
            }

            bool isCustomBooleanAttribute = (!attributeHasValue && attributeType == HtmlAttributeType.Text);
            if (isCustomBooleanAttribute && useHtmlSyntax)
            {
                attributeString = " " + attributeName;
                return attributeString;
            }

            if (attributeType == HtmlAttributeType.Boolean)
            {
                if (_settings.CollapseBooleanAttributes)
                {
                    attributeString = " " + attributeName;
                    return attributeString;
                }

                attributeValue = attributeName;
            }
            else if (isCustomBooleanAttribute)
            {
                attributeValue = string.Empty;
            }
            else
            {
                attributeValue = CleanAttributeValue(context, tag, attribute);

                if (_settings.RemoveEmptyAttributes
                    && CanRemoveEmptyAttribute(tagName, attributeName, attributeValue, attributeType))
                {
                    attributeString = string.Empty;
                    return attributeString;
                }
            }

            bool addQuotes = !CanRemoveAttributeQuotes(tagFlags, attributeValue, _settings.AttributeQuotesRemovalMode);
            attributeString = InnerBuildAttributeString(attributeName, attributeValue, addQuotes);

            return attributeString;
        }
예제 #9
0
        /// <summary>
        /// Checks whether it is possible to remove the attribute quotes
        /// </summary>
        /// <param name="attribute">Attribute</param>
        /// <param name="attributeQuotesRemovalMode">Removal mode of HTML attribute quotes</param>
        /// <returns>Result of check (true - can remove; false - cannot remove)</returns>
        private static bool CanRemoveAttributeQuotes(HtmlAttribute attribute,
			HtmlAttributeQuotesRemovalMode attributeQuotesRemovalMode)
        {
            string attributeValue = attribute.Value;
            bool result = false;

            if (attributeQuotesRemovalMode != HtmlAttributeQuotesRemovalMode.KeepQuotes)
            {
                if (!attributeValue.EndsWith("/"))
                {
                    if (attributeQuotesRemovalMode == HtmlAttributeQuotesRemovalMode.Html4)
                    {
                        result = _html4AttributeValueNotRequireQuotesRegex.IsMatch(attributeValue);
                    }
                    else if (attributeQuotesRemovalMode == HtmlAttributeQuotesRemovalMode.Html5)
                    {
                        result = CommonRegExps.Html5AttributeValueNotRequireQuotes.IsMatch(attributeValue);
                    }
                }
            }

            return result;
        }
예제 #10
0
        /// <summary>
        /// Checks whether remove an the attribute, that has empty value
        /// </summary>
        /// <param name="tag">Tag</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>Result of check (true - can be removed; false - can not be removed)</returns>
        private static bool CanRemoveEmptyAttribute(HtmlTag tag, HtmlAttribute attribute)
        {
            string tagNameInLowercase = tag.NameInLowercase;
            string attributeNameInLowercase = attribute.NameInLowercase;
            string attributeValue = attribute.Value;
            HtmlAttributeType attributeType = attribute.Type;

            bool result = false;
            bool isZeroLengthString = attributeValue.Length == 0;

            if (isZeroLengthString || string.IsNullOrWhiteSpace(attributeValue))
            {
                if (tagNameInLowercase == "input" && attributeNameInLowercase == "value")
                {
                    result = isZeroLengthString;
                }
                else if (attributeType == HtmlAttributeType.Event
                    || (tagNameInLowercase == "form" && attributeNameInLowercase == "action")
                    || _emptyAttributesForRemoval.Contains(attributeNameInLowercase))
                {
                    result = true;
                }
            }

            return result;
        }
예제 #11
0
        private HtmlAttributeViewModel InnerBuildAttributeViewModel(HtmlAttribute attribute, bool omitValue,
			bool addQuotes)
        {
            string displayAttributeName = CanPreserveCase() ? attribute.Name : attribute.NameInLowercase;
            string encodedAttributeValue = !omitValue ?
                HtmlAttribute.HtmlAttributeEncode(attribute.Value, HtmlAttributeQuotesType.Double) : null;
            var attributeViewModel = new HtmlAttributeViewModel(displayAttributeName, encodedAttributeValue, addQuotes);

            return attributeViewModel;
        }
예제 #12
0
        /// <summary>
        /// Cleans a attribute value
        /// </summary>
        /// <param name="context">Markup parsing context</param>
        /// <param name="tag">Tag</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>Processed attribute value</returns>
        private string CleanAttributeValue(MarkupParsingContext context, HtmlTag tag, HtmlAttribute attribute)
        {
            string attributeValue = attribute.Value;
            if (attributeValue.Length == 0)
            {
                return attributeValue;
            }

            string processedAttributeValue = attributeValue;
            string tagNameInLowercase = tag.NameInLowercase;
            IList<HtmlAttribute> attributes = tag.Attributes;
            string attributeNameInLowercase = attribute.NameInLowercase;
            HtmlAttributeType attributeType = attribute.Type;

            switch (attributeType)
            {
                case HtmlAttributeType.Uri:
                    processedAttributeValue = processedAttributeValue.Trim();

                    if (processedAttributeValue.StartsWith(HTTP_PROTOCOL, StringComparison.OrdinalIgnoreCase))
                    {
                        if (_settings.RemoveHttpProtocolFromAttributes && !ContainsRelExternalAttribute(attributes))
                        {
                            int httpProtocolLength = HTTP_PROTOCOL.Length;
                            processedAttributeValue = processedAttributeValue.Substring(httpProtocolLength);
                        }
                    }
                    else if (processedAttributeValue.StartsWith(HTTPS_PROTOCOL, StringComparison.OrdinalIgnoreCase))
                    {
                        if (_settings.RemoveHttpsProtocolFromAttributes && !ContainsRelExternalAttribute(attributes))
                        {
                            int httpsProtocolLength = HTTPS_PROTOCOL.Length;
                            processedAttributeValue = processedAttributeValue.Substring(httpsProtocolLength);
                        }
                    }
                    else if (attributeNameInLowercase == "href"
                        && processedAttributeValue.StartsWith(JS_PROTOCOL, StringComparison.OrdinalIgnoreCase))
                    {
                        processedAttributeValue = ProcessInlineScriptContent(context, attribute);
                    }

                    break;
                case HtmlAttributeType.Numeric:
                    processedAttributeValue = processedAttributeValue.Trim();
                    break;
                case HtmlAttributeType.ClassName:
                    if (AngularHelpers.IsClassDirective(processedAttributeValue))
                    {
                        // Processing of Angular class directives
                        string ngOriginalDirectiveName = string.Empty;
                        string ngNormalizedDirectiveName = string.Empty;
                        string ngExpression;
                        var ngDirectives = new Dictionary<string, string>();

                        AngularHelpers.ParseClassDirective(processedAttributeValue,
                            (localContext, originalDirectiveName, normalizedDirectiveName) =>
                            {
                                ngOriginalDirectiveName = originalDirectiveName;
                                ngNormalizedDirectiveName = normalizedDirectiveName;
                                ngExpression = null;

                                ngDirectives.Add(ngOriginalDirectiveName, ngExpression);
                            },
                            (localContext, expression) =>
                            {
                                ngExpression = expression;
                                if (_settings.MinifyAngularBindingExpressions
                                    && ContainsAngularBindingExpression(ngNormalizedDirectiveName))
                                {
                                    ngExpression = MinifyAngularBindingExpression(context,
                                        attribute.ValueCoordinates, localContext.NodeCoordinates,
                                        expression);
                                }

                                ngDirectives[ngOriginalDirectiveName] = ngExpression;
                            },
                            localContext =>
                            {
                                if (ngDirectives[ngOriginalDirectiveName] == null)
                                {
                                    ngDirectives[ngOriginalDirectiveName] = string.Empty;
                                }
                            }
                        );

                        int directiveCount = ngDirectives.Count;
                        if (directiveCount > 0)
                        {
                            StringBuilder directiveBuilder = StringBuilderPool.GetBuilder();
                            int directiveIndex = 0;
                            int lastDirectiveIndex = directiveCount - 1;
                            string previousExpression = null;

                            foreach (var directive in ngDirectives)
                            {
                                string directiveName = directive.Key;
                                string expression = directive.Value;

                                if (directiveIndex > 0 && (expression == null || previousExpression == null))
                                {
                                    directiveBuilder.Append(" ");
                                }

                                directiveBuilder.Append(directiveName);
                                if (!string.IsNullOrWhiteSpace(expression))
                                {
                                    directiveBuilder.AppendFormat(":{0}", expression);
                                }

                                if (directiveIndex < lastDirectiveIndex && expression != null)
                                {
                                    directiveBuilder.Append(";");
                                }

                                previousExpression = expression;
                                directiveIndex++;
                            }

                            processedAttributeValue = directiveBuilder.ToString();
                            StringBuilderPool.ReleaseBuilder(directiveBuilder);
                        }
                        else
                        {
                            processedAttributeValue = string.Empty;
                        }
                    }
                    else
                    {
                        processedAttributeValue = processedAttributeValue.Trim();
                        processedAttributeValue = Utils.CollapseWhitespace(processedAttributeValue);
                    }

                    break;
                case HtmlAttributeType.Style:
                    processedAttributeValue = ProcessInlineStyleContent(context, attribute);
                    break;
                case HtmlAttributeType.Event:
                    processedAttributeValue = ProcessInlineScriptContent(context, attribute);
                    break;
                default:
                    if (attributeNameInLowercase == "data-bind" && _settings.MinifyKnockoutBindingExpressions)
                    {
                        processedAttributeValue = MinifyKnockoutBindingExpression(context, attribute);
                    }
                    else if (tagNameInLowercase == "meta" && attributeNameInLowercase == "content"
                        && attributes.Any(a => a.NameInLowercase == "name" && a.Value.Trim().IgnoreCaseEquals("keywords")))
                    {
                        processedAttributeValue = processedAttributeValue.Trim();
                        processedAttributeValue = Utils.CollapseWhitespace(processedAttributeValue);
                        processedAttributeValue = _separatingCommaWithSpacesRegex.Replace(processedAttributeValue, ",");
                        processedAttributeValue = _endingCommaWithSpacesRegex.Replace(processedAttributeValue, string.Empty);
                    }
                    else
                    {
                        if (_settings.MinifyAngularBindingExpressions
                            && CanMinifyAngularBindingExpressionInAttribute(tag, attribute))
                        {
                            processedAttributeValue = MinifyAngularBindingExpression(context, attribute.ValueCoordinates,
                                processedAttributeValue);
                        }
                    }

                    break;
            }

            return processedAttributeValue;
        }
예제 #13
0
        /// <summary>
        /// Checks whether remove an the attribute
        /// </summary>
        /// <param name="tag">Tag</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>Result of check (true - can be removed; false - can not be removed)</returns>
        private bool CanRemoveAttribute(HtmlTag tag, HtmlAttribute attribute)
        {
            if (_settings.PreservableAttributeCollection.Count == 0)
            {
                return true;
            }

            string tagNameInLowercase = tag.NameInLowercase;
            string attributeNameInLowercase = attribute.NameInLowercase;
            string attributeValue = attribute.Value;

            bool result = true;

            foreach (HtmlAttributeExpression attributeExpression in _settings.PreservableAttributeCollection)
            {
                bool cannotRemove = attributeExpression.IsMatch(tagNameInLowercase, attributeNameInLowercase,
                    attributeValue);
                if (cannotRemove)
                {
                    result = false;
                    break;
                }
            }

            return result;
        }
        /// <summary>
        /// Processes a inline script content
        /// </summary>
        /// <param name="context">Markup parsing context</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>Processed inline script content</returns>
        private string ProcessInlineScriptContent(MarkupParsingContext context, HtmlAttribute attribute)
        {
            string scriptContent = attribute.Value;
            bool forHrefAttribute = (attribute.Name == "href");

            string result = scriptContent;

            if (_settings.MinifyInlineJsCode && _jsMinifier.IsInlineCodeMinificationSupported)
            {
                bool isJavascriptProtocolRemoved = false;
                if (scriptContent.StartsWith(JS_PROTOCOL, StringComparison.OrdinalIgnoreCase))
                {
                    result = _jsProtocolRegex.Replace(result, string.Empty);
                    isJavascriptProtocolRemoved = true;
                }

                CodeMinificationResult minificationResult = _jsMinifier.Minify(result, true);
                if (minificationResult.Errors.Count == 0)
                {
                    result = minificationResult.MinifiedContent ?? string.Empty;
                }

                if (minificationResult.Errors.Count > 0 || minificationResult.Warnings.Count > 0)
                {
                    string sourceCode = context.SourceCode;
                    SourceCodeNodeCoordinates tagCoordinates = context.NodeCoordinates;
                    SourceCodeNodeCoordinates attributeCoordinates = attribute.ValueCoordinates;

                    foreach (MinificationErrorInfo error in minificationResult.Errors)
                    {
                        var relativeErrorCoordinates = new SourceCodeNodeCoordinates(error.LineNumber, error.ColumnNumber);
                        SourceCodeNodeCoordinates absoluteErrorCoordinates = CalculateAbsoluteInlineCodeErrorCoordinates(
                            tagCoordinates, attributeCoordinates, relativeErrorCoordinates);
                        string sourceFragment = SourceCodeNavigator.GetSourceFragment(
                            sourceCode, absoluteErrorCoordinates);
                        string message = error.Message.Trim();

                        WriteError(LogCategoryConstants.JsMinificationError, message, _fileContext,
                            absoluteErrorCoordinates.LineNumber, absoluteErrorCoordinates.ColumnNumber, sourceFragment);
                    }

                    foreach (MinificationErrorInfo warning in minificationResult.Warnings)
                    {
                        var relativeErrorCoordinates = new SourceCodeNodeCoordinates(warning.LineNumber, warning.ColumnNumber);
                        SourceCodeNodeCoordinates absoluteErrorCoordinates = CalculateAbsoluteInlineCodeErrorCoordinates(
                            tagCoordinates, attributeCoordinates, relativeErrorCoordinates);
                        string sourceFragment = SourceCodeNavigator.GetSourceFragment(
                            sourceCode, absoluteErrorCoordinates);
                        string message = warning.Message.Trim();

                        WriteWarning(LogCategoryConstants.JsMinificationWarning, message, _fileContext,
                            absoluteErrorCoordinates.LineNumber, absoluteErrorCoordinates.ColumnNumber, sourceFragment);
                    }
                }

                if (isJavascriptProtocolRemoved
                    && (forHrefAttribute || !_settings.RemoveJsProtocolFromAttributes))
                {
                    result = JS_PROTOCOL + result;
                }
            }
            else
            {
                result = result.Trim();

                if (!forHrefAttribute && _settings.RemoveJsProtocolFromAttributes)
                {
                    result = _jsProtocolRegex.Replace(result, string.Empty);
                }
            }

            result = Utils.RemoveEndingSemicolon(result);

            return result;
        }
예제 #15
0
        /// <summary>
        /// Checks whether the attribute is redundant
        /// </summary>
        /// <param name="tag">Tag</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>Result of check (true - is redundant; false - is not redundant)</returns>
        private static bool IsAttributeRedundant(HtmlTag tag, HtmlAttribute attribute)
        {
            string tagNameInLowercase = tag.NameInLowercase;
            IList<HtmlAttribute> attributes = tag.Attributes;
            string attributeNameInLowercase = attribute.NameInLowercase;
            string attributeValue = attribute.Value;
            string processedAttributeValue = attributeValue.Trim();

            return (
                (tagNameInLowercase == "script"
                    && ((attributeNameInLowercase == "language" && processedAttributeValue.IgnoreCaseEquals("javascript"))
                    || (attributeNameInLowercase == "charset" && attributes.All(a => a.NameInLowercase != "src"))))
                || (tagNameInLowercase == "link" && attributeNameInLowercase == "charset" && attributes.Any(
                    a => a.NameInLowercase == "rel" && a.Value.Trim().IgnoreCaseEquals("stylesheet")))
                || (tagNameInLowercase == "form" && attributeNameInLowercase == "method"
                    && processedAttributeValue.IgnoreCaseEquals("get"))
                || (tagNameInLowercase == "input" && attributeNameInLowercase == "type"
                    && processedAttributeValue.IgnoreCaseEquals("text"))
                || (tagNameInLowercase == "a" && attributeNameInLowercase == "name" && attributes.Any(
                    a => a.NameInLowercase == "id" && a.Value == attributeValue))
                || (tagNameInLowercase == "area" && attributeNameInLowercase == "shape"
                    && processedAttributeValue.IgnoreCaseEquals("rect"))
            );
        }
        /// <summary>
        /// Processes a inline style content
        /// </summary>
        /// <param name="context">Markup parsing context</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>Processed inline style content</returns>
        private string ProcessInlineStyleContent(MarkupParsingContext context, HtmlAttribute attribute)
        {
            string styleContent = attribute.Value;

            string result = styleContent;
            if (_settings.MinifyInlineCssCode && _cssMinifier.IsInlineCodeMinificationSupported)
            {
                CodeMinificationResult minificationResult = _cssMinifier.Minify(result, true);
                if (minificationResult.Errors.Count == 0)
                {
                    result = minificationResult.MinifiedContent ?? string.Empty;
                }

                if (minificationResult.Errors.Count > 0 || minificationResult.Warnings.Count > 0)
                {
                    string sourceCode = context.SourceCode;
                    SourceCodeNodeCoordinates tagCoordinates = context.NodeCoordinates;
                    SourceCodeNodeCoordinates attributeCoordinates = attribute.ValueCoordinates;

                    foreach (MinificationErrorInfo error in minificationResult.Errors)
                    {
                        var relativeErrorCoordinates = new SourceCodeNodeCoordinates(error.LineNumber, error.ColumnNumber);
                        SourceCodeNodeCoordinates absoluteErrorCoordinates = CalculateAbsoluteInlineCodeErrorCoordinates(
                            tagCoordinates, attributeCoordinates, relativeErrorCoordinates);
                        string sourceFragment = SourceCodeNavigator.GetSourceFragment(
                            sourceCode, absoluteErrorCoordinates);
                        string message = error.Message.Trim();

                        WriteError(LogCategoryConstants.CssMinificationError, message, _fileContext,
                            absoluteErrorCoordinates.LineNumber, absoluteErrorCoordinates.ColumnNumber, sourceFragment);
                    }

                    foreach (MinificationErrorInfo warning in minificationResult.Warnings)
                    {
                        var relativeErrorCoordinates = new SourceCodeNodeCoordinates(warning.LineNumber, warning.ColumnNumber);
                        SourceCodeNodeCoordinates absoluteErrorCoordinates = CalculateAbsoluteInlineCodeErrorCoordinates(
                            tagCoordinates, attributeCoordinates, relativeErrorCoordinates);
                        string sourceFragment = SourceCodeNavigator.GetSourceFragment(
                            sourceCode, absoluteErrorCoordinates);
                        string message = warning.Message.Trim();

                        WriteWarning(LogCategoryConstants.CssMinificationWarning, message, _fileContext,
                            absoluteErrorCoordinates.LineNumber, absoluteErrorCoordinates.ColumnNumber, sourceFragment);
                    }
                }
            }
            else
            {
                result = result.Trim();
            }

            result = Utils.RemoveEndingSemicolon(result);

            return result;
        }
예제 #17
0
        /// <summary>
        /// Checks whether attribute is the attribute <code>type</code> of tag <code>link</code>
        /// or <code>style</code>, that containing CSS code
        /// </summary>
        /// <param name="tag">Tag</param>
        /// <param name="attribute">Attribute</param>
        /// <returns>Result of check</returns>
        private static bool IsCssTypeAttribute(HtmlTag tag, HtmlAttribute attribute)
        {
            string tagNameInLowercase = tag.NameInLowercase;
            string attributeNameInLowercase = attribute.NameInLowercase;
            string attributeValue = attribute.Value;
            IList<HtmlAttribute> attributes = tag.Attributes;
            bool isCssTypeAttribute = false;

            if (tagNameInLowercase == "link" || tagNameInLowercase == "style")
            {
                string processedAttributeValue = attributeValue.Trim();

                if (attributeNameInLowercase == "type" && processedAttributeValue.IgnoreCaseEquals(CSS_CONTENT_TYPE))
                {
                    if (tagNameInLowercase == "link")
                    {
                        isCssTypeAttribute = attributes.Any(a => a.NameInLowercase == "rel"
                            && a.Value.Trim().IgnoreCaseEquals("stylesheet"));
                    }
                    else if (tagNameInLowercase == "style")
                    {
                        isCssTypeAttribute = true;
                    }
                }
            }

            return isCssTypeAttribute;
        }
        /// <summary>
        /// Cleans a attribute value
        /// </summary>
        /// <param name="context">Markup parsing context</param>
        /// <param name="tag">HTML tag</param>
        /// <param name="attribute">HTML attribute</param>
        /// <returns>Processed attribute value</returns>
        private string CleanAttributeValue(MarkupParsingContext context, HtmlTag tag, HtmlAttribute attribute)
        {
            string attributeValue = attribute.Value;
            if (attributeValue.Length == 0)
            {
                return attributeValue;
            }

            string result = attributeValue;
            string tagName = tag.Name;
            IList<HtmlAttribute> attributes = tag.Attributes;
            string attributeName = attribute.Name;
            HtmlAttributeType attributeType = attribute.Type;

            if (attributeType != HtmlAttributeType.Event && MustacheStyleTagHelpers.ContainsMustacheStyleTag(result))
            {
                // Processing of Angular Mustache-style tags
                var attributeValueBuilder = new StringBuilder();

                MustacheStyleTagHelpers.ParseMarkup(result,
                    (localContext, expression, startDelimiter, endDelimiter) =>
                    {
                        string processedExpression = expression;
                        if (_settings.MinifyAngularBindingExpressions && startDelimiter == "{{" && endDelimiter == "}}")
                        {
                            processedExpression = MinifyAngularBindingExpression(context, attribute.ValueCoordinates,
                                localContext.NodeCoordinates, expression);
                        }

                        attributeValueBuilder.Append(startDelimiter);
                        attributeValueBuilder.Append(processedExpression);
                        attributeValueBuilder.Append(endDelimiter);
                    },
                    (localContext, textValue) =>
                    {
                        string processedTextValue = textValue;
                        if (attributeType == HtmlAttributeType.ClassName)
                        {
                            processedTextValue = Utils.CollapseWhitespace(textValue);
                        }

                        attributeValueBuilder.Append(processedTextValue);
                    }
                );

                result = attributeValueBuilder.ToString();
                attributeValueBuilder.Clear();

                switch (attributeType)
                {
                    case HtmlAttributeType.Uri:
                    case HtmlAttributeType.Numeric:
                    case HtmlAttributeType.ClassName:
                        result = result.Trim();
                        break;
                    case HtmlAttributeType.Style:
                        result = result.Trim();
                        result = Utils.RemoveEndingSemicolon(result);
                        break;
                    default:
                        if (_settings.MinifyAngularBindingExpressions)
                        {
                            string elementDirectiveName = AngularHelpers.NormalizeDirectiveName(tagName);
                            if (elementDirectiveName == "ngPluralize" && attributeName == "when")
                            {
                                result = MinifyAngularBindingExpression(context, attribute.ValueCoordinates, result);
                            }
                        }

                        break;
                }
            }
            else
            {
                switch (attributeType)
                {
                    case HtmlAttributeType.Uri:
                        result = result.Trim();

                        if (result.StartsWith(HTTP_PROTOCOL, StringComparison.OrdinalIgnoreCase))
                        {
                            if (_settings.RemoveHttpProtocolFromAttributes && !ContainsRelExternalAttribute(attributes))
                            {
                                int httpProtocolLength = HTTP_PROTOCOL.Length;
                                result = result.Substring(httpProtocolLength);
                            }
                        }
                        else if (result.StartsWith(HTTPS_PROTOCOL, StringComparison.OrdinalIgnoreCase))
                        {
                            if (_settings.RemoveHttpsProtocolFromAttributes && !ContainsRelExternalAttribute(attributes))
                            {
                                int httpsProtocolLength = HTTPS_PROTOCOL.Length;
                                result = result.Substring(httpsProtocolLength);
                            }
                        }
                        else if (result == "href" && result.StartsWith(JS_PROTOCOL, StringComparison.OrdinalIgnoreCase))
                        {
                            result = ProcessInlineScriptContent(context, attribute);
                        }

                        break;
                    case HtmlAttributeType.Numeric:
                        result = result.Trim();
                        break;
                    case HtmlAttributeType.ClassName:
                        if (AngularHelpers.IsClassDirective(result))
                        {
                            // Processing of Angular class directives
                            string ngOriginalDirectiveName = string.Empty;
                            string ngNormalizedDirectiveName = string.Empty;
                            string ngExpression;
                            var ngDirectives = new Dictionary<string, string>();

                            AngularHelpers.ParseClassDirective(result,
                                (localContext, originalDirectiveName, normalizedDirectiveName) =>
                                {
                                    ngOriginalDirectiveName = originalDirectiveName;
                                    ngNormalizedDirectiveName = normalizedDirectiveName;
                                    ngExpression = null;

                                    ngDirectives.Add(ngOriginalDirectiveName, ngExpression);
                                },
                                (localContext, expression) =>
                                {
                                    ngExpression = expression;
                                    if (_settings.MinifyAngularBindingExpressions
                                        && ContainsAngularBindingExpression(ngNormalizedDirectiveName))
                                    {
                                        ngExpression = MinifyAngularBindingExpression(context,
                                            attribute.ValueCoordinates, localContext.NodeCoordinates,
                                            expression);
                                    }

                                    ngDirectives[ngOriginalDirectiveName] = ngExpression;
                                },
                                localContext =>
                                {
                                    if (ngDirectives[ngOriginalDirectiveName] == null)
                                    {
                                        ngDirectives[ngOriginalDirectiveName] = string.Empty;
                                    }
                                }
                            );

                            int directiveCount = ngDirectives.Count;
                            if (directiveCount > 0)
                            {
                                var directiveBuilder = new StringBuilder();
                                int directiveIndex = 0;
                                int lastDirectiveIndex = directiveCount - 1;
                                string previousExpression = null;

                                foreach (var directive in ngDirectives)
                                {
                                    string directiveName = directive.Key;
                                    string expression = directive.Value;

                                    if (directiveIndex > 0 && (expression == null || previousExpression == null))
                                    {
                                        directiveBuilder.Append(" ");
                                    }

                                    directiveBuilder.Append(directiveName);
                                    if (!string.IsNullOrWhiteSpace(expression))
                                    {
                                        directiveBuilder.AppendFormat(":{0}", expression);
                                    }

                                    if (directiveIndex < lastDirectiveIndex && expression != null)
                                    {
                                        directiveBuilder.Append(";");
                                    }

                                    previousExpression = expression;
                                    directiveIndex++;
                                }

                                result = directiveBuilder.ToString();
                                directiveBuilder.Clear();
                            }
                            else
                            {
                                result = string.Empty;
                            }
                        }
                        else
                        {
                            result = result.Trim();
                            result = Utils.CollapseWhitespace(result);
                        }

                        break;
                    case HtmlAttributeType.Style:
                        result = ProcessInlineStyleContent(context, attribute);
                        break;
                    case HtmlAttributeType.Event:
                        result = ProcessInlineScriptContent(context, attribute);
                        break;
                    default:
                        if (attributeName == "data-bind" && _settings.MinifyKnockoutBindingExpressions)
                        {
                            result = MinifyKnockoutBindingExpression(context, attribute);
                        }
                        else if (tagName == "meta" && attributeName == "content"
                            && attributes.Any(a => a.Name == "name" && a.Value.Trim().IgnoreCaseEquals("keywords")))
                        {
                            result = result.Trim();
                            result = Utils.CollapseWhitespace(result);
                            result = _separatingCommaWithSpacesRegex.Replace(result, ",");
                            result = _endingCommaWithSpacesRegex.Replace(result, string.Empty);
                        }
                        else
                        {
                            if (_settings.MinifyAngularBindingExpressions
                                && CanMinifyAngularBindingExpressionInAttribute(tagName, attributeName, attributes))
                            {
                                result = MinifyAngularBindingExpression(context, attribute.ValueCoordinates, result);
                            }
                        }

                        break;
                }
            }

            return result;
        }
예제 #19
0
        /// <summary>
        /// Process a attributes
        /// </summary>
        /// <returns>List of attributes</returns>
        private List <HtmlAttribute> ProcessAttributes()
        {
            string content         = _innerContext.SourceCode;
            int    currentPosition = _innerContext.Position;
            SourceCodeNodeCoordinates currentCoordinates = _innerContext.NodeCoordinates;

            Match match = _attributeRegex.Match(content, currentPosition, _innerContext.RemainderLength);

            while (match.Success)
            {
                GroupCollection groups                  = match.Groups;
                Group           attributeNameGroup      = groups["attributeName"];
                Group           attributeEqualSignGroup = groups["attributeEqualSign"];
                Group           attributeValueGroup     = groups["attributeValue"];

                string attributeName            = attributeNameGroup.Value;
                string attributeNameInLowercase = attributeName;
                if (Utils.ContainsUppercaseCharacters(attributeName))
                {
                    attributeNameInLowercase = attributeName.ToLowerInvariant();
                }
                string attributeValue = null;

                if (attributeEqualSignGroup.Success)
                {
                    if (attributeValueGroup.Success)
                    {
                        attributeValue = attributeValueGroup.Value;
                        if (!string.IsNullOrWhiteSpace(attributeValue))
                        {
                            attributeValue = HtmlAttributeValueHelpers.Decode(attributeValue);
                        }
                    }
                    else
                    {
                        attributeValue = string.Empty;
                    }
                }

                var attributeNameCoordinates = SourceCodeNodeCoordinates.Empty;
                int attributeNamePosition    = -1;
                if (attributeNameGroup.Success)
                {
                    attributeNamePosition = attributeNameGroup.Index;
                }

                if (attributeNamePosition != -1)
                {
                    int lineBreakCount;
                    int charRemainderCount;

                    SourceCodeNavigator.CalculateLineBreakCount(content, currentPosition,
                                                                attributeNamePosition - currentPosition, out lineBreakCount, out charRemainderCount);
                    attributeNameCoordinates = SourceCodeNavigator.CalculateAbsoluteNodeCoordinates(
                        currentCoordinates, lineBreakCount, charRemainderCount);

                    currentPosition    = attributeNamePosition;
                    currentCoordinates = attributeNameCoordinates;
                }

                var attributeValueCoordinates = SourceCodeNodeCoordinates.Empty;
                int attributeValuePosition    = -1;
                if (attributeValueGroup.Success)
                {
                    attributeValuePosition = attributeValueGroup.Index;
                }

                if (attributeValuePosition != -1)
                {
                    int lineBreakCount;
                    int charRemainderCount;

                    SourceCodeNavigator.CalculateLineBreakCount(content, currentPosition,
                                                                attributeValuePosition - currentPosition, out lineBreakCount, out charRemainderCount);
                    attributeValueCoordinates = SourceCodeNavigator.CalculateAbsoluteNodeCoordinates(
                        currentCoordinates, lineBreakCount, charRemainderCount);

                    currentPosition    = attributeValuePosition;
                    currentCoordinates = attributeValueCoordinates;
                }

                var attribute = new HtmlAttribute(attributeName, attributeNameInLowercase, attributeValue,
                                                  HtmlAttributeType.Unknown, attributeNameCoordinates, attributeValueCoordinates);
                _tempAttributes.Add(attribute);

                _innerContext.IncreasePosition(match.Length);
                match = _attributeRegex.Match(content, _innerContext.Position, _innerContext.RemainderLength);
            }

            int attributeCount = _tempAttributes.Count;
            var attributes     = new List <HtmlAttribute>(attributeCount);

            for (int attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++)
            {
                attributes.Add(_tempAttributes[attributeIndex]);
            }

            _tempAttributes.Clear();

            return(attributes);
        }