Пример #1
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));
            }
        }
Пример #2
0
        /// <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)
        {
            // ruleset
            // : selectors_group
            // '{' S* declaration? [ ';' S* declaration? ]* '}' S*
            // ;
            rulesetNode.SelectorsGroupNode.Accept(this);


            // '{' S* declaration? [ ';' S* declaration? ]* '}' S*
            _printerFormatter.WriteIndent();
            _printerFormatter.AppendLine(CssConstants.OpenCurlyBracket);
            _printerFormatter.IncrementIndentLevel();
            rulesetNode.Declarations.ForEach((declaration, last) =>
            {
                // Invoke the Declaration visitor
                var result = declaration.Accept(this);
                if (!last && result != null)
                {
                    _printerFormatter.AppendLine(CssConstants.Semicolon);
                }
            });
            //Visit Important CommentNodes
            rulesetNode.ImportantComments.ForEach(comment => comment.Accept(this));

            _printerFormatter.DecrementIndentLevel();

            // End the declarations with a line
            _printerFormatter.AppendLine();
            _printerFormatter.WriteIndent();
            _printerFormatter.AppendLine(CssConstants.CloseCurlyBracket);

            return(rulesetNode);
        }
        /// <summary>Computes the merged set of RulesetNode based on the declarations</summary>
        /// <param name="sourceRuleset">The original ruleset</param>
        /// <param name="destinationRuleset">The new ruleset which should take the non matched declarations from <paramref name="sourceRuleset"/></param>
        /// <returns>The new ruleset with the merged declarations</returns>
        private static RulesetNode MergeDeclarations(RulesetNode sourceRuleset, RulesetNode destinationRuleset)
        {
            // First take the unique declarations from destinationRuleset and sourceRuleset for following scenario:
            // #foo
            // {
            // k1:v1
            // k1:v2
            // }
            // To
            // #foo
            // {
            // k1:v2;
            // }
            // Combine the declarations preserving the order (later key takes preference with in a scope of ruleset).
            // This could have been a simple dictionary merge but it would break the backward compatibility here with
            // previous release of Csl. The unique declarations in source not found in the destination are gathered first
            // followed by unique declarations in destination.
            // Example:
            //  div
            //  {
            //      k1:v1;
            //      k2:v2;
            //  }
            //  div
            //  {
            //      k1:v3;
            //      k3:v4;
            //  }
            //
            // becomes:
            //
            //  div
            //  {
            //      k2:v2;
            //      k1:v3;
            //      k3:v4;
            //  }
            //
            // Vendor specific values are suopposed to live next to each other.
            // display: -ms-grid;
            // display: -moz-box;
            // display: block;
            // should not be merged.
            // Logic is that non vendor values get merged and vendor values are kept as duplicates, except for vendor properties.
            var uniqueDestinationDeclarations = UniqueDeclarations(destinationRuleset);
            var targetDeclarations            = UniqueDeclarations(sourceRuleset);

            foreach (DeclarationNode newDeclaration in uniqueDestinationDeclarations.Values)
            {
                AddDeclaration(targetDeclarations, newDeclaration);
            }

            // Convert dictionary to list
            var resultDeclarations = targetDeclarations.Values.Cast <DeclarationNode>().ToList();

            return(new RulesetNode(destinationRuleset.SelectorsGroupNode, resultDeclarations.AsReadOnly(), sourceRuleset.ImportantComments));
        }
        /// <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>Converts a ruleset node to dictionary preserving the order of nodes</summary>
        /// <param name="rulesetNode">The ruleset node to scan</param>
        /// <returns>The ordered dictionary</returns>
        private static OrderedDictionary UniqueDeclarations(RulesetNode rulesetNode)
        {
            var dictionary = new OrderedDictionary();

            foreach (var declarationNode in rulesetNode.Declarations)
            {
                AddDeclaration(dictionary, declarationNode);
            }

            return(dictionary);
        }
        /// <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);
                }
            }
        }
        /// <summary>Optimizes a ruleset node to remove the declarations preserving the order of nodes</summary>
        /// <param name="rulesetNode">The ruleset node to scan</param>
        /// <returns>The updated ruleset</returns>
        private static RulesetNode OptimizeRuleset(RulesetNode rulesetNode)
        {
            // Omit the complete ruleset if there are no declarations
            if (rulesetNode.Declarations.Count == 0)
            {
                return(null);
            }

            var dictionary         = UniqueDeclarations(rulesetNode);
            var resultDeclarations = dictionary.Values.Cast <DeclarationNode>().ToList();

            return(new RulesetNode(rulesetNode.SelectorsGroupNode, resultDeclarations.AsReadOnly(), rulesetNode.ImportantComments));
        }
Пример #8
0
        /// <summary>Computes the selectors hash from a ruleset</summary>
        /// <param name="rulesetNode">The ruleset node from which the selector need to be computed</param>
        /// <returns>String format</returns>
        internal static string PrintSelector(this RulesetNode rulesetNode)
        {
            if (rulesetNode == null)
            {
                return(string.Empty);
            }

            var rulesetBuilder = new StringBuilder();

            rulesetNode.SelectorsGroupNode.SelectorNodes.ForEach((selector, last) =>
            {
                rulesetBuilder.Append(selector.MinifyPrint());
                if (!last)
                {
                    rulesetBuilder.Append(CssConstants.Comma);
                }
            });
            return(rulesetBuilder.ToString());
        }
        /// <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)
        {
            if (rulesetNode == null)
            {
                throw new ArgumentNullException("rulesetNode");
            }

            try
            {
                // Validate the selectors for lower case
                rulesetNode.SelectorsGroupNode.SelectorNodes.ForEach(selectorNode => ValidateForLowerCase(selectorNode.MinifyPrint()));

                // Visit declarations
                rulesetNode.Declarations.ForEach(declarationNode => declarationNode.Accept(this));
            }
            catch (BuildWorkflowException exception)
            {
                throw new WorkflowException(string.Format(CultureInfo.CurrentUICulture, CssStrings.CssLowercaseValidationParentNodeError, rulesetNode.PrettyPrint()), exception);
            }

            return(rulesetNode);
        }
Пример #10
0
 /// <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)
 {
     this.VisitBackgroundDeclarationNode(rulesetNode.Declarations, rulesetNode);
     return(rulesetNode);
 }
Пример #11
0
 /// <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)
 {
     return(new RulesetNode(rulesetNode.SelectorsGroupNode, this.UpdateDeclarations(rulesetNode.Declarations, rulesetNode), rulesetNode.ImportantComments));
 }
Пример #12
0
 public virtual AstNode VisitRulesetNode(RulesetNode rulesetNode)
 {
     return(rulesetNode);
 }
        /// <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);
        }
 /// <summary>The <see cref="Ast.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)
 {
     return(new RulesetNode(
                rulesetNode.SelectorsGroupNode.Accept(this) as SelectorsGroupNode,
                rulesetNode.Declarations.Select(declarationNode => (DeclarationNode)declarationNode.Accept(this)).ToSafeReadOnlyCollection(), rulesetNode.ImportantComments));
 }