/// <summary> /// Check if the given css block is assignable to the given css box by validating the selector.<br/> /// </summary> /// <param name="box">the box to check assign to</param> /// <param name="block">the block to check assign of</param> /// <returns>true - the block is assignable to the box, false - otherwise</returns> private static bool IsBlockAssignableToBoxWithSelector(CssBox box, CssBlock block) { foreach (var selector in block.Selectors) { bool matched = false; while (!matched) { box = box.ParentBox; while (box != null && box.HtmlTag == null) { box = box.ParentBox; } if (box == null) { return(false); } if (box.HtmlTag.Name.Equals(selector.Class, StringComparison.InvariantCultureIgnoreCase)) { matched = true; } if (!matched && box.HtmlTag.HasAttribute("class")) { var className = box.HtmlTag.TryGetAttribute("class"); if (selector.Class.Equals("." + className, StringComparison.InvariantCultureIgnoreCase) || selector.Class.Equals(box.HtmlTag.Name + "." + className, StringComparison.InvariantCultureIgnoreCase)) { matched = true; } } if (!matched && box.HtmlTag.HasAttribute("id")) { var id = box.HtmlTag.TryGetAttribute("id"); if (selector.Class.Equals("#" + id, StringComparison.InvariantCultureIgnoreCase)) { matched = true; } } if (!matched && selector.DirectParent) { return(false); } } } return(true); }
/* * NOTE: Each ConsumeXXX method consumes the token that is current as of when the * method is called and consumes all subsequent tokens that are read from the tokenizer * during method execution. ConsumeXXX operations must only call other ConsumeYYY * operations and not call any ParseXXX methods. */ /// <summary> /// Reads current token and all subsequent tokens that compose into a <see cref="CssQualifiedRule"/> instance. /// </summary> /// <returns></returns> private CssQualifiedRule ConsumeQualifiedRule(CssToken token) { var preludeFragmentParser = new QualifiedRulePreludeScope(this, _grammar); var prelude = _grammar.ParseQualifiedRulePrelude(preludeFragmentParser); if (token.TokenType == CssTokenType.LeftCurlyBracket && TryRead(out token)) { var blockFragmentParser = new RuleBlockScope(this, _grammar); var block = new CssBlock(CssBlockType.CurlyBrackets, _grammar.ParseQualifiedRuleBlock(blockFragmentParser)); return(_grammar.CreateQualifiedRule(prelude, block)); } // Parse error return(null); }
/// <summary> /// Assigns the given css style block properties to the given css box. /// </summary> /// <param name="box">the css box to assign css to</param> /// <param name="block">the css block to assign</param> private static void AssignCssBlock(CssBox box, CssBlock block) { foreach (var prop in block.Properties) { var value = prop.Value; if (prop.Value == CssConstants.Inherit && box.ParentBox != null) { value = CssUtils.GetPropertyValue(box.ParentBox, prop.Key); } if (IsStyleOnElementAllowed(box, prop.Key, value)) { CssUtils.SetPropertyValue(box, prop.Key, value); } } }
/// <summary> /// Add the given css block to the css data, merging to existing block if required. /// </summary> /// <remarks> /// If there is no css blocks for the same class it will be added to data collection.<br/> /// If there is already css blocks for the same class it will check for each existing block /// if the hierarchical selectors match (or not exists). if do the two css blocks will be merged into /// one where the new block properties overwrite existing if needed. if the new block doesn't mach any /// existing it will be added either to the beginning of the list if it has no hierarchical selectors or at the end.<br/> /// Css block without hierarchical selectors must be added to the beginning of the list so more specific block /// can overwrite it when the style is applied. /// </remarks> /// <param name="media">the media type to add the CSS to</param> /// <param name="cssBlock">the css block to add</param> public void AddCssBlock(string media, CssBlock cssBlock) { Dictionary <string, List <CssBlock> > mid; if (!_mediaBlocks.TryGetValue(media, out mid)) { mid = new Dictionary <string, List <CssBlock> >(StringComparer.InvariantCultureIgnoreCase); _mediaBlocks.Add(media, mid); } if (!mid.ContainsKey(cssBlock.Class)) { var list = new List <CssBlock>(); list.Add(cssBlock); mid[cssBlock.Class] = list; } else { bool merged = false; var list = mid[cssBlock.Class]; foreach (var block in list) { if (block.EqualsSelector(cssBlock)) { merged = true; block.Merge(cssBlock); break; } } if (!merged) { // general block must be first if (cssBlock.Selectors == null) { list.Insert(0, cssBlock); } else { list.Add(cssBlock); } } } }
/// <summary> /// Check if the given css block is assignable to the given css box.<br/> /// the block is assignable if it has no hierarchical selectors or if the hierarchy matches.<br/> /// Special handling for ":hover" pseudo-class.<br/> /// </summary> /// <param name="box">the box to check assign to</param> /// <param name="block">the block to check assign of</param> /// <returns>true - the block is assignable to the box, false - otherwise</returns> private static bool IsBlockAssignableToBox(CssBox box, CssBlock block) { bool assignable = true; if (block.Selectors != null) { assignable = IsBlockAssignableToBoxWithSelector(box, block); } else if (box.HtmlTag.Name.Equals("a", StringComparison.OrdinalIgnoreCase) && block.Class.Equals("a", StringComparison.OrdinalIgnoreCase) && !box.HtmlTag.HasAttribute("href")) { assignable = false; } if (assignable && block.Hover) { box.HtmlContainer.AddHoverBox(box, block); assignable = false; } return(assignable); }
/// <summary> /// Feeds the style with a block about the specific media.<br/> /// When no media is specified, "all" will be used. /// </summary> /// <param name="cssData"> </param> /// <param name="block">the CSS block to handle</param> /// <param name="media">optional: the media (default - all)</param> private static void FeedStyleBlock(CssData cssData, string block, string media = "all") { int startIdx = block.IndexOf("{", StringComparison.Ordinal); int endIdx = startIdx > -1 ? block.IndexOf("}", startIdx) : -1; if (startIdx > -1 && endIdx > -1) { string blockSource = block.Substring(startIdx + 1, endIdx - startIdx - 1); string[] classes = block.Substring(0, startIdx).Split(','); foreach (string cls in classes) { string className = cls.Trim(); if (!String.IsNullOrEmpty(className)) { CssBlock newblock = ParseCssBlockImp(className, blockSource); if (newblock != null) { cssData.AddCssBlock(media, newblock); } } } } }
public void WriteBlock(CssBlock block, int level) { var prevScope = scope; writer.Write("{"); // Block start var condenced = false; var count = 0; // Write the declarations foreach (var node in block.Children) // TODO: Change to an immutable list? { if (node.Kind == NodeKind.Include) { var b2 = new CssBlock(NodeKind.Block); b2.Add(node); scope = ExpandInclude((IncludeNode)node, b2); foreach (var rule in b2.OfType<CssRule>()) { writer.WriteLine(); WriteRule(rule, level + 1); count++; } } else if (node.Kind == NodeKind.Declaration) { var declaration = (CssDeclaration)node; if (block.Children.Count == 1 && !declaration.Info.NeedsExpansion(declaration, browserSupport)) { condenced = true; writer.Write(" "); WriteDeclaration(declaration, 0); } else { if (count == 0) writer.WriteLine(); WritePatchedDeclaration(declaration, level + 1); } } else if (node.Kind == NodeKind.Rule) // Nested rule { if (count == 0) writer.WriteLine(); var childRule = (CssRule)node; WriteRule(childRule, level + 1); } else if (node.Kind == NodeKind.If) { EvaluateIf((IfBlock)node, level + 1); } if (!condenced) { writer.WriteLine(); } count++; } // Limit to declaration if (condenced) { writer.Write(" "); } else { Indent(level); } writer.Write("}"); // Block end prevScope = scope; }
/// <summary> /// Init. /// </summary> public HoverBoxBlock(CssBox cssBox, CssBlock cssBlock) { _cssBox = cssBox; _cssBlock = cssBlock; }
private List<CssBlock> ParseBlocks(ScintillaControl sci) { List<CssBlock> blocks = new List<CssBlock>(); blocks.Clear(); int lines = sci.LineCount; int inString = 0; bool inComment = false; CssBlock block = null; for (int i = 0; i < lines; i++) { string line = sci.GetLine(i); int len = line.Length; int safeLen = len - 1; for (int j = 0; j < len; j++) { char c = line[j]; if (inComment) { if (c == '*' && j < safeLen && line[j + 1] == '/') inComment = false; else continue; } else if (inString > 0) { if (inString == 1 && c == '\'') inString = 0; else if (inString == 2 && c == '"') inString = 0; else continue; } else if (c == '\'') inString = 1; else if (c == '"') inString = 2; else if (c == '/' && j < safeLen && line[j + 1] == '/') break; else if (c == '/' && j < safeLen && line[j + 1] == '*') inComment = true; else if (c == '{') { CssBlock parent = block; block = new CssBlock(); block.LineFrom = i; block.ColFrom = j; if (parent != null) { block.Parent = parent; parent.Children.Add(block); } else blocks.Add(block); } else if (c == '}') { if (block != null) { block.LineTo = i; block.ColTo = j; block = block.Parent; if (block != null) { block.LineTo = i; block.ColTo = j; } } } } } return blocks; }
private bool CursorInBlock(CssBlock block, int line, int col) { if (line < block.LineFrom || line > block.LineTo) return false; if (line == block.LineFrom && col <= block.ColFrom) return false; if (line == block.LineTo && col > block.ColTo) return false; return true; }
private CssBlock LookupBlock(List<CssBlock> blocks, CssBlock parent, int line, int col) { foreach (CssBlock block in blocks) { if (CursorInBlock(block, line, col)) return LookupBlock(block.Children, block, line, col); } return parent; }
/// <summary> /// Init. /// </summary> public HoverBoxBlock(CssBox cssBox, CssBlock cssBlock) { this._cssBox = cssBox; this._cssBlock = cssBlock; }
/// <summary> /// Add the given css block to the css data, merging to existing block if required. /// </summary> /// <remarks> /// If there is no css blocks for the same class it will be added to data collection.<br/> /// If there is already css blocks for the same class it will check for each existing block /// if the hierarchical selectors match (or not exists). if do the two css blocks will be merged into /// one where the new block properties overwrite existing if needed. if the new block doesn't mach any /// existing it will be added either to the beginning of the list if it has no hierarchical selectors or at the end.<br/> /// Css block without hierarchical selectors must be added to the beginning of the list so more specific block /// can overwrite it when the style is applied. /// </remarks> /// <param name="media">the media type to add the CSS to</param> /// <param name="cssBlock">the css block to add</param> public void AddCssBlock(string media, CssBlock cssBlock) { _mediaRules.AddCssBlock(media, cssBlock); }
public virtual CssQualifiedRule CreateQualifiedRule(CssComponent prelude, CssBlock block) { return(new CssQualifiedRule(prelude, block)); }
/// <summary> /// Applies style to all boxes in the tree.<br/> /// If the html tag has style defined for each apply that style to the css box of the tag.<br/> /// If the html tag has "class" attribute and the class name has style defined apply that style on the tag css box.<br/> /// If the html tag has "style" attribute parse it and apply the parsed style on the tag css box.<br/> /// If the html tag is "style" tag parse it content and add to the css data for all future tags parsing.<br/> /// If the html tag is "link" that point to style data parse it content and add to the css data for all future tags parsing.<br/> /// </summary> /// <param name="box"></param> /// <param name="htmlContainer">the html container to use for reference resolve</param> /// <param name="cssData"> </param> /// <param name="cssDataChanged">check if the css data has been modified by the handled html not to change the base css data</param> private static void CascadeStyles(CssBox box, HtmlContainer htmlContainer, ref CssData cssData, ref bool cssDataChanged) { box.InheritStyle(); if (box.HtmlTag != null) { // try assign style using the html element tag AssignCssBlocks(box, cssData, box.HtmlTag.Name); // try assign style using the "class" attribute of the html element if (box.HtmlTag.HasAttribute("class")) { AssignClassCssBlocks(box, cssData); } // try assign style using the "id" attribute of the html element if (box.HtmlTag.HasAttribute("id")) { string id = box.HtmlTag.TryGetAttribute("id"); AssignCssBlocks(box, cssData, "#" + id); } TranslateAttributes(box.HtmlTag, box); // Check for the style="" attribute if (box.HtmlTag.HasAttribute("style")) { CssBlock block = CssParser.ParseCssBlock(box.HtmlTag.Name, box.HtmlTag.TryGetAttribute("style")); AssignCssBlock(box, block); } // Check for the <style> tag if (box.HtmlTag.Name.Equals("style", StringComparison.CurrentCultureIgnoreCase) && box.Boxes.Count == 1) { CloneCssData(ref cssData, ref cssDataChanged); CssParser.ParseStyleSheet(cssData, box.Boxes[0].Text.CutSubstring()); } // Check for the <link rel=stylesheet> tag if (box.HtmlTag.Name.Equals("link", StringComparison.CurrentCultureIgnoreCase) && box.GetAttribute("rel", string.Empty).Equals("stylesheet", StringComparison.CurrentCultureIgnoreCase)) { CloneCssData(ref cssData, ref cssDataChanged); string stylesheet; CssData stylesheetData; StylesheetLoadHandler.LoadStylesheet(htmlContainer, box.GetAttribute("href", string.Empty), box.HtmlTag.Attributes, out stylesheet, out stylesheetData); if (stylesheet != null) { CssParser.ParseStyleSheet(cssData, stylesheet); } else if (stylesheetData != null) { cssData.Combine(stylesheetData); } } } // cascade text decoration only to boxes that actually have text so it will be handled correctly. if (box.TextDecoration != String.Empty && box.Text == null) { foreach (CssBox childBox in box.Boxes) { childBox.TextDecoration = box.TextDecoration; } box.TextDecoration = string.Empty; } foreach (CssBox childBox in box.Boxes) { CascadeStyles(childBox, htmlContainer, ref cssData, ref cssDataChanged); } }
public CssBlock ReadBlock(CssBlock block) { var blockStart = Read(TokenKind.BlockStart, LexicalMode.Block); // Read { ReadTrivia(); while(current.Kind != TokenKind.BlockEnd) { if (isEnd) throw new UnbalancedBlock(LexicalMode.Block, blockStart); // A list of delarations or blocks if (current.Kind == TokenKind.AtSymbol) { Read(); // Read @ var name = tokenizer.Read(); // Name switch (name.Text) { case "include" : block.Add(ReadInclude()); continue; case "if" : block.Add(ReadIfRule()); continue; } } if (current.Kind == TokenKind.Dollar) { block.Add(ReadAssignment()); continue; } var statement = ReadSpan(); switch (current.Kind) { case TokenKind.Colon : block.Add(ReadDeclarationFromName(statement)); break; // DeclarationName case TokenKind.BlockStart : block.Add(ReadRuleBlock(new CssSelector(statement))); break; case TokenKind.BlockEnd : break; // TODO: Figure out where we missed reading the semicolon TEMP case TokenKind.Semicolon : tokenizer.Read(); break; default: throw new UnexpectedTokenException(LexicalMode.Block, current); } } tokenizer.Read(); // read } block.Trailing = ReadTrivia(); return block; }
public virtual CssAtRule CreateAtRule(string name, CssComponent prelude, CssBlock block) { return(new CssAtRule(name, prelude, block)); }
public CssScope ExpandInclude(IncludeNode include, CssBlock rule) { includeCount++; if (includeCount > 1000) throw new Exception("Exceded include limit of 1,000"); MixinNode mixin; if (!context.Mixins.TryGetValue(include.Name, out mixin)) { throw new Exception($"Mixin '{include.Name}' not registered"); } var index = rule.Children.IndexOf(include); var childScope = GetScope(mixin.Parameters, include.Args); var i = 0; foreach (var node in mixin.Children.ToArray()) { // Bind variables if (node is IncludeNode) { ExpandInclude( (IncludeNode)node, rule ); mixin.Children.Remove(node); } rule.Insert(i + 1, node.CloneNode()); i++; } return childScope; }
/// <summary> /// Write the given html tag with all its attributes and styles. /// </summary> /// <param name="sb">the string builder to write html into</param> /// <param name="box">the css box with the html tag to write</param> /// <param name="indent">the indent to use for nice formating</param> /// <param name="styleGen">Controls the way styles are generated when html is generated</param> private static void WriteHtmlTag(StringBuilder sb, CssBox box, int indent, HtmlGenerationStyle styleGen) { sb.Append(new string(' ', indent * 4)); sb.AppendFormat("<{0}", box.HtmlTag.Name); // collect all element style properties incliding from stylesheet var tagStyles = new Dictionary <string, string>(); IEnumerable <CssBlock> tagCssBlock = box.HtmlContainer.CssData.GetCssBlock(box.HtmlTag.Name); if (tagCssBlock != null) { // atodo: handle selectors foreach (CssBlock cssBlock in tagCssBlock) { foreach (var prop in cssBlock.Properties) { tagStyles[prop.Key] = prop.Value; } } } if (box.HtmlTag.HasAttributes()) { sb.Append(" "); foreach (var att in box.HtmlTag.Attributes) { // handle image tags by inserting the image using base64 data if (box.HtmlTag.Name == "img" && att.Key == "src" && (att.Value.StartsWith("property") || att.Value.StartsWith("method"))) { Image img = ((CssBoxImage)box).Image; if (img != null) { using (var buffer = new MemoryStream()) { img.Save(buffer, ImageFormat.Png); string base64 = Convert.ToBase64String(buffer.ToArray()); sb.AppendFormat("{0}=\"data:image/png;base64, {1}\" ", att.Key, base64); } } } else if (styleGen == HtmlGenerationStyle.Inline && att.Key == HtmlConstants.Style) { // if inline style add the styles to the collection CssBlock block = CssParser.ParseCssBlock(box.HtmlTag.Name, box.HtmlTag.TryGetAttribute("style")); foreach (var prop in block.Properties) { tagStyles[prop.Key] = prop.Value; } } else if (styleGen == HtmlGenerationStyle.Inline && att.Key == HtmlConstants.Class) { // if inline style convert the style class to actual properties and add to collection IEnumerable <CssBlock> cssBlocks = box.HtmlContainer.CssData.GetCssBlock("." + att.Value); if (cssBlocks != null) { // atodo: handle selectors foreach (CssBlock cssBlock in cssBlocks) { foreach (var prop in cssBlock.Properties) { tagStyles[prop.Key] = prop.Value; } } } } else { sb.AppendFormat("{0}=\"{1}\" ", att.Key, att.Value); } } sb.Remove(sb.Length - 1, 1); } // if inline style insert the style tag with all collected style properties if (styleGen == HtmlGenerationStyle.Inline && tagStyles.Count > 0) { sb.Append(" style=\""); foreach (var style in tagStyles) { sb.AppendFormat("{0}: {1}; ", style.Key, style.Value); } sb.Remove(sb.Length - 1, 1); sb.Append("\""); } sb.AppendFormat("{0}>", box.HtmlTag.IsSingle ? "/" : ""); sb.AppendLine(); }