/// <summary>The optimize ruleset node.</summary>
        /// <param name="currentRuleSet">The current rule set.</param>
        /// <param name="ruleSetMediaPageDictionary">The rule set media page dictionary.</param>
        /// <param name="rulesetHashKeysDictionary">The hashkey dictionary</param>
        /// <param name="shouldPreventOrderBasedConflict">should prevent order based conflict</param>
        private static void OptimizeRulesetNode(RulesetNode currentRuleSet,
                                                OrderedDictionary ruleSetMediaPageDictionary,
                                                OrderedDictionary rulesetHashKeysDictionary,
                                                bool shouldPreventOrderBasedConflict)
        {
            // Ruleset node optimization.
            // Selectors concatenation is the hash key here
            // Update: Now this is only primary hash key.
            string primaryHashKey = currentRuleSet.PrintSelector();
            string hashKey        = currentRuleSet.PrintSelector();

            if (rulesetHashKeysDictionary.Contains(primaryHashKey))
            {
                hashKey = ((List <string>)rulesetHashKeysDictionary[primaryHashKey]).Last();
            }
            else
            {
                rulesetHashKeysDictionary.Add(primaryHashKey, new List <string>());
                (rulesetHashKeysDictionary[primaryHashKey] as List <string>).Add(primaryHashKey);
            }
            // If a RuleSet exists already, then remove from
            // dictionary and add a new ruleset with the
            // declarations merged. Don't clobber the item in
            // dictionary to preserve the order and keep the last
            // seen RuleSet
            if (ShouldCollapseTheNewRuleset(hashKey, ruleSetMediaPageDictionary, currentRuleSet, shouldPreventOrderBasedConflict))
            {
                // Merge the declarations
                var newRuleSet = MergeDeclarations((RulesetNode)ruleSetMediaPageDictionary[hashKey], currentRuleSet);

                // Remove the old ruleset from old position
                ruleSetMediaPageDictionary.Remove(hashKey);

                // Add a new ruleset at later position
                ruleSetMediaPageDictionary.Add(hashKey, newRuleSet);
            }
            else
            {
                var newRuleSet = OptimizeRuleset(currentRuleSet);

                // Add the ruleset if there is atleast one unique declaration
                if (newRuleSet != null)
                {
                    // Generates an unique hashkey again, if hashKey already exists.
                    while (ruleSetMediaPageDictionary.Contains(hashKey))
                    {
                        hashKey = GenerateRandomkey();
                    }
                    ruleSetMediaPageDictionary.Add(hashKey, newRuleSet);
                    (rulesetHashKeysDictionary[primaryHashKey] as List <string>).Add(hashKey);
                }
            }
        }
Пример #2
0
        private static void EnsureDeclarationNode(DeclarationNode declarationNode, RulesetNode ruleSetNode, MediaNode mediaQueryNode, StyleSheetNode actualCss)
        {
            var rules = actualCss.StyleSheetRules.ToArray();
            var expectedMediaQuery = string.Empty;

            if (mediaQueryNode != null)
            {
                expectedMediaQuery = mediaQueryNode.PrintSelector();
                rules = rules.OfType <MediaNode>().Where(r => r.PrintSelector().Equals(expectedMediaQuery)).SelectMany(mq => mq.Rulesets).ToArray();
            }

            var expectedRule = ruleSetNode.PrintSelector();
            var declarations = rules
                               .OfType <RulesetNode>()
                               .Where(rsn => rsn.PrintSelector().Equals(expectedRule))
                               .SelectMany(r => r.Declarations).ToArray();

            var expectedproperty  = declarationNode.Property;
            var declarationValues = declarations.Where(d => d.Property.Equals(expectedproperty)).ToArray();

            var expectedValue = declarationNode.ExprNode.TermNode.MinifyPrint();

            if (!declarationValues.Any(d => d.ExprNode.TermNode.MinifyPrint().Equals(expectedValue)))
            {
                Assert.Fail("Could not find [{0}] --> [{1}] --> {2}: {3}; ".InvariantFormat(expectedMediaQuery, expectedRule, expectedproperty, expectedValue));
            }
        }
        /// <summary>
        /// Determines if we should collapse with the old ruleset.
        /// </summary>
        /// <param name="hashKey"> hash key of last same ruleset occurs</param>
        /// <param name="ruleSetMediaPageDictionary"> Dictionary of hash keys and RulesetNodes</param>
        /// <param name="currentRuleSet"> RulesetNode currently tracking.</param>
        /// <param name="shouldPreventOrderBasedConflict">should prevent conflict.</param>
        /// <returns> Whether we should collapse or not.</returns>
        private static bool ShouldCollapseTheNewRuleset(string hashKey,
                                                        OrderedDictionary ruleSetMediaPageDictionary,
                                                        RulesetNode currentRuleSet,
                                                        bool shouldPreventOrderBasedConflict)
        {
            if (ruleSetMediaPageDictionary.Contains(hashKey))
            {
                if (!shouldPreventOrderBasedConflict)
                {
                    return(true);
                }

                // Dictionary for declarations
                var declarationNodeDictionary = new OrderedDictionary();
                var styleSheetRuleNodes       = ruleSetMediaPageDictionary.Values.Cast <StyleSheetRuleNode>().ToList();
                styleSheetRuleNodes.Reverse();

                // Iterate through all rulesetnodes.
                foreach (var previousStyleSheetRulesetNode in styleSheetRuleNodes)
                {
                    // If the node is actually RulesetNode instance
                    if (currentRuleSet.GetType().IsAssignableFrom(previousStyleSheetRulesetNode.GetType()))
                    {
                        // Previous Ruleset Node
                        var previousRulesetNode = previousStyleSheetRulesetNode as RulesetNode;

                        // If Ruleset node has same selectors set
                        if (previousRulesetNode.PrintSelector().Equals(currentRuleSet.PrintSelector()))
                        {
                            return(true);
                        }

                        // Add each declaration of the previous ruleset in the dictionary
                        foreach (var declaration in previousRulesetNode.Declarations)
                        {
                            string hashKeyForDeclaration = declaration.Property;
                            if (!declarationNodeDictionary.Contains(hashKeyForDeclaration))
                            {
                                declarationNodeDictionary[hashKeyForDeclaration] = declaration;
                            }
                        }

                        // Check if the last same RulesetNode has same declaration property
                        var lastRuleSet = (RulesetNode)ruleSetMediaPageDictionary[hashKey];
                        if (lastRuleSet.HasConflictingDeclaration(declarationNodeDictionary))
                        {
                            return(false);
                        }
                    }
                }

                return(true);
            }

            return(false);
        }
        /// <summary>
        /// Merge Rulesets based on Common Declarations
        /// </summary>
        /// <param name="currentRuleSet"></param>
        /// <param name="ruleSetMediaPageDictionary"></param>
        private static void MergeBasedOnCommonDeclarations(RulesetNode currentRuleSet, OrderedDictionary ruleSetMediaPageDictionary)
        {
            string hashKey = currentRuleSet.PrintSelector();
            // Now, we should decide whether we should merge with another ruleset node.
            // Note: A variable hashkey is the hash key of the RulesetNode which our cursor is on.

            // List of all hashkeys in dictionary
            var hashKeyEnumerator = ruleSetMediaPageDictionary.Keys.GetEnumerator();
            // Current RulesetNode we interested in
            var  currentRuleset = ruleSetMediaPageDictionary[hashKey];
            var  diffRuleSetMediaPageDictionary = new OrderedDictionary();
            bool removed = false;

            while (hashKeyEnumerator.MoveNext())
            {
                var otherHashKey = hashKeyEnumerator.Current;
                var previousStyleSheetRulesetNode = ruleSetMediaPageDictionary[otherHashKey];
                //if  the ruleset is instance RuleseNode class
                if (ruleSetMediaPageDictionary.Contains(hashKey) && currentRuleset.GetType().IsAssignableFrom(previousStyleSheetRulesetNode.GetType()) && !otherHashKey.Equals(hashKey))
                {
                    // Previous Ruleset Node
                    var preRulesetNode     = (RulesetNode)previousStyleSheetRulesetNode;
                    var currentRulesetNode = (RulesetNode)currentRuleset;

                    // if the current ruleset should merge with the preRulesetNode
                    if (preRulesetNode.ShouldMergeWith(currentRulesetNode))
                    {
                        var mergedRulesetNode = currentRulesetNode.GetMergedRulesetNode(preRulesetNode);
                        var newHashKey        = mergedRulesetNode.PrintSelector();
                        // Remove the old ruleset from old position
                        if (currentRulesetNode.Declarations.Count < 1)
                        {
                            removed = true;
                        }
                        if (preRulesetNode.Declarations.Count < 1)
                        {
                            diffRuleSetMediaPageDictionary.Add(otherHashKey, null);
                        }
                        // Generates an unique hashkey again, if hashKey already exists.
                        while (ruleSetMediaPageDictionary.Contains(newHashKey) || diffRuleSetMediaPageDictionary.Contains(newHashKey))
                        {
                            newHashKey = GenerateRandomkey();
                        }

                        diffRuleSetMediaPageDictionary.Add(newHashKey, mergedRulesetNode);
                    }
                }
            }
            if (removed)
            {
                diffRuleSetMediaPageDictionary.Add(hashKey, null);
            }

            //Now, sync the dictionary
            var diffHashKeyEnumerator = diffRuleSetMediaPageDictionary.Keys.GetEnumerator();

            while (diffHashKeyEnumerator.MoveNext())
            {
                var diffHashKey = diffHashKeyEnumerator.Current;
                if (diffRuleSetMediaPageDictionary[diffHashKey] == null)
                {
                    ruleSetMediaPageDictionary.Remove(diffHashKey);
                }
                else
                {
                    ruleSetMediaPageDictionary.Add(diffHashKey, diffRuleSetMediaPageDictionary[diffHashKey]);
                }
            }
        }
        /// <summary>The <see cref="RulesetNode"/> visit implementation</summary>
        /// <param name="rulesetNode">The ruleset AST node</param>
        /// <returns>The modified AST node if modified otherwise the original node</returns>
        public override AstNode VisitRulesetNode(RulesetNode rulesetNode)
        {
            // Here are few possible scenarios as how client code will utilize this visitor:
            // 1. Request to raise the exception for hacks. It would do a match on selectors and if match is found
            // exception will be thrown.
            // 2. Remove the banned selectors It would do a match on the selectors and if a match is found,
            // the ruleset will be returned null so that it can be removed from transformed Ast.
            var rulesetSelector = rulesetNode.PrintSelector();
            var hack            = string.Empty;
            var match           = false;

            if (this.shouldMatchExactly)
            {
                // Find the match in list (lower/upper case not considered since lowercase is already a standard)
                match = this.selectorsToValidateOrRemove.Contains(rulesetSelector);
                hack  = rulesetSelector;
            }
            else
            {
                foreach (var selectorToValidateOrRemove in this.selectorsToValidateOrRemove)
                {
                    if (!rulesetSelector.Contains(selectorToValidateOrRemove))
                    {
                        continue;
                    }

                    // Match is found
                    match = true;
                    hack  = selectorToValidateOrRemove;
                    break;
                }
            }

            // If a match is found, take a decision whether to validate/optimize/both
            if (match)
            {
                // It is just required to validate
                if (this.validate)
                {
                    throw new BuildWorkflowException(string.Format(CultureInfo.CurrentUICulture, CssStrings.CssSelectorHackError, hack));
                }

                // If the ruleset has multiple selectors, we need to check if we need to remove all of them or just some.
                if (rulesetNode.SelectorsGroupNode.SelectorNodes.Count > 1)
                {
                    // Get the selector nodes that do not match the banned selectors
                    var selectorNodes = rulesetNode.SelectorsGroupNode.SelectorNodes
                                        .Where(sn =>
                                               !this.selectorsToValidateOrRemove.Any(sr =>
                                                                                     sn.MinifyPrint().Contains(sr))).ToList();

                    // If we still have selectors remaining, we create a new rulesetnode, with the remaining selectors, and return it as the current node.
                    if (selectorNodes.Any())
                    {
                        return(new RulesetNode(
                                   new SelectorsGroupNode(
                                       new ReadOnlyCollection <SelectorNode>(selectorNodes)),
                                   rulesetNode.Declarations,
                                   rulesetNode.ImportantComments));
                    }
                }

                // Otherwise it is an optimization to remove the ruleset
                return(null);
            }

            return(rulesetNode);
        }