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>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)); }
/// <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); }
/// <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); }
/// <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)); }
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)); }