/// <summary> /// Get the table cells spacing for all the cells in the table.<br/> /// Used to calculate the spacing the table has in addition to regular padding and borders. /// </summary> /// <param name="tableBox">the table box to calculate the spacing for</param> /// <returns>the calculated spacing</returns> public static double GetTableSpacing(CssBox tableBox) { int count = 0; int columns = 0; foreach (var box in tableBox.Boxes) { if (box.Display == CssConstants.TableColumn) { columns += GetSpan(box); } else if (box.Display == CssConstants.TableRowGroup) { foreach (CssBox cr in tableBox.Boxes) { count++; if (cr.Display == CssConstants.TableRow) columns = Math.Max(columns, cr.Boxes.Count); } } else if (box.Display == CssConstants.TableRow) { count++; columns = Math.Max(columns, box.Boxes.Count); } // limit the amount of rows to process for performance if (count > 30) break; } // +1 columns because padding is between the cell and table borders return (columns + 1) * GetHorizontalSpacing(tableBox); }
/// <summary> /// Clip the region the graphics will draw on by the overflow style of the containing block.<br/> /// Recursively travel up the tree to find containing block that has overflow style set to hidden. if not /// block found there will be no clipping and null will be returned. /// </summary> /// <param name="g">the graphics to clip</param> /// <param name="box">the box that is rendered to get containing blocks</param> /// <returns>true - was clipped, false - not clipped</returns> public static bool ClipGraphicsByOverflow(RGraphics g, CssBox box) { var containingBlock = box.ContainingBlock; while (true) { if (containingBlock.Overflow == CssConstants.Hidden) { var prevClip = g.GetClip(); var rect = box.ContainingBlock.ClientRectangle; rect.X -= 2; // TODO:a find better way to fix it rect.Width += 2; rect.Offset(box.HtmlContainer.ScrollOffset); rect.Intersect(prevClip); g.PushClip(rect); return true; } else { var cBlock = containingBlock.ContainingBlock; if (cBlock == containingBlock) return false; containingBlock = cBlock; } } }
/// <summary> /// Init. /// </summary> /// <param name="parent">the parent box of this box</param> /// <param name="tag">the html tag data of this box</param> public CssBoxFrame(CssBox parent, HtmlTag tag) : base(parent, tag) { _imageWord = new CssRectImage(this); Words.Add(_imageWord); Uri uri; if (Uri.TryCreate(GetAttribute("src"), UriKind.Absolute, out uri)) { if (uri.Host.IndexOf("youtube.com", StringComparison.InvariantCultureIgnoreCase) > -1) { _isVideo = true; LoadYoutubeDataAsync(uri); } else if (uri.Host.IndexOf("vimeo.com", StringComparison.InvariantCultureIgnoreCase) > -1) { _isVideo = true; LoadVimeoDataAsync(uri); } } if (!_isVideo) { SetErrorBorder(); } }
/// <summary> /// Creates a new LineBox /// </summary> public CssLineBox(CssBox ownerBox) { _rects = new Dictionary<CssBox, RRect>(); _relatedBoxes = new List<CssBox>(); _words = new List<CssRect>(); _ownerBox = ownerBox; _ownerBox.LineBoxes.Add(this); }
public CssSpacingBox(CssBox tableBox, ref CssBox extendedBox, int startRow) : base(tableBox, new HtmlTag("none", false, new Dictionary<string, string> { { "colspan", "1" } })) { _extendedBox = extendedBox; Display = CssConstants.None; _startRow = startRow; _endRow = startRow + Int32.Parse(extendedBox.GetAttribute("rowspan", "1")) - 1; }
/// <summary> /// Check if the given box contains only inline child boxes. /// </summary> /// <param name="box">the box to check</param> /// <returns>true - only inline child boxes, false - otherwise</returns> public static bool ContainsInlinesOnly(CssBox box) { foreach (CssBox b in box.Boxes) { if (!b.IsInline) { return false; } } return true; }
/// <summary> /// Check if the given location is inside the given box deep.<br/> /// Check inner boxes and all lines that the given box spans to. /// </summary> /// <param name="box">the box to check</param> /// <param name="location">the location to check</param> /// <returns>true - location inside the box, false - otherwise</returns> public static bool IsInBox(CssBox box, RPoint location) { foreach (var line in box.Rectangles) { if (line.Value.Contains(location)) return true; } foreach (var childBox in box.Boxes) { if (IsInBox(childBox, location)) return true; } return false; }
/// <summary> /// Recursively searches for the parent with the specified HTML Tag name /// </summary> /// <param name="root"></param> /// <param name="tagName"></param> /// <param name="box"></param> public static CssBox FindParent(CssBox root, string tagName, CssBox box) { if (box == null) { return root; } else if (box.HtmlTag != null && box.HtmlTag.Name.Equals(tagName, StringComparison.CurrentCultureIgnoreCase)) { return box.ParentBox ?? root; } else { return FindParent(root, tagName, box.ParentBox); } }
/// <summary> /// Draws all the border of the box with respect to style, width, etc. /// </summary> /// <param name="g">the device to draw into</param> /// <param name="box">the box to draw borders for</param> /// <param name="rect">the bounding rectangle to draw in</param> /// <param name="isFirst">is it the first rectangle of the element</param> /// <param name="isLast">is it the last rectangle of the element</param> public static void DrawBoxBorders(RGraphics g, CssBox box, RRect rect, bool isFirst, bool isLast) { if (rect.Width > 0 && rect.Height > 0) { if (!(string.IsNullOrEmpty(box.BorderTopStyle) || box.BorderTopStyle == CssConstants.None || box.BorderTopStyle == CssConstants.Hidden) && box.ActualBorderTopWidth > 0) { DrawBorder(Border.Top, box, g, rect, isFirst, isLast); } if (isFirst && !(string.IsNullOrEmpty(box.BorderLeftStyle) || box.BorderLeftStyle == CssConstants.None || box.BorderLeftStyle == CssConstants.Hidden) && box.ActualBorderLeftWidth > 0) { DrawBorder(Border.Left, box, g, rect, true, isLast); } if (!(string.IsNullOrEmpty(box.BorderBottomStyle) || box.BorderBottomStyle == CssConstants.None || box.BorderBottomStyle == CssConstants.Hidden) && box.ActualBorderBottomWidth > 0) { DrawBorder(Border.Bottom, box, g, rect, isFirst, isLast); } if (isLast && !(string.IsNullOrEmpty(box.BorderRightStyle) || box.BorderRightStyle == CssConstants.None || box.BorderRightStyle == CssConstants.Hidden) && box.ActualBorderRightWidth > 0) { DrawBorder(Border.Right, box, g, rect, isFirst, true); } } }
/// <summary> /// Draw the background image of the given box in the given rectangle.<br/> /// Handle background-repeat and background-position values. /// </summary> /// <param name="g">the device to draw into</param> /// <param name="box">the box to draw its background image</param> /// <param name="imageLoadHandler">the handler that loads image to draw</param> /// <param name="rectangle">the rectangle to draw image in</param> public static void DrawBackgroundImage(RGraphics g, CssBox box, ImageLoadHandler imageLoadHandler, RRect rectangle) { // image size depends if specific rectangle given in image loader var imgSize = new RSize(imageLoadHandler.Rectangle == RRect.Empty ? imageLoadHandler.Image.Width : imageLoadHandler.Rectangle.Width, imageLoadHandler.Rectangle == RRect.Empty ? imageLoadHandler.Image.Height : imageLoadHandler.Rectangle.Height); // get the location by BackgroundPosition value var location = GetLocation(box.BackgroundPosition, rectangle, imgSize); var srcRect = imageLoadHandler.Rectangle == RRect.Empty ? new RRect(0, 0, imgSize.Width, imgSize.Height) : new RRect(imageLoadHandler.Rectangle.Left, imageLoadHandler.Rectangle.Top, imgSize.Width, imgSize.Height); // initial image destination rectangle var destRect = new RRect(location, imgSize); // need to clip so repeated image will be cut on rectangle var lRectangle = rectangle; lRectangle.Intersect(g.GetClip()); g.PushClip(lRectangle); switch (box.BackgroundRepeat) { case "no-repeat": g.DrawImage(imageLoadHandler.Image, destRect, srcRect); break; case "repeat-x": DrawRepeatX(g, imageLoadHandler, rectangle, srcRect, destRect, imgSize); break; case "repeat-y": DrawRepeatY(g, imageLoadHandler, rectangle, srcRect, destRect, imgSize); break; default: DrawRepeat(g, imageLoadHandler, rectangle, srcRect, destRect, imgSize); break; } g.PopClip(); }
/// <summary> /// Cascades to the TD's the border spacified in the TABLE tag. /// </summary> /// <param name="table"></param> /// <param name="border"></param> private static void ApplyTableBorder(CssBox table, string border) { SetForAllCells(table, cell => { cell.BorderLeftStyle = cell.BorderTopStyle = cell.BorderRightStyle = cell.BorderBottomStyle = CssConstants.Solid; cell.BorderLeftWidth = cell.BorderTopWidth = cell.BorderRightWidth = cell.BorderBottomWidth = border; }); }
/// <summary> /// Cascades to the TD's the border spacified in the TABLE tag. /// </summary> /// <param name="table"></param> /// <param name="padding"></param> private static void ApplyTablePadding(CssBox table, string padding) { var length = TranslateLength(padding); SetForAllCells(table, cell => cell.PaddingLeft = cell.PaddingTop = cell.PaddingRight = cell.PaddingBottom = length); }
/// <summary> /// /// </summary> /// <param name="tag"></param> /// <param name="box"></param> private void TranslateAttributes(HtmlTag tag, CssBox box) { if (tag.HasAttributes()) { foreach (string att in tag.Attributes.Keys) { string value = tag.Attributes[att]; switch (att) { case HtmlConstants.Align: if (value == HtmlConstants.Left || value == HtmlConstants.Center || value == HtmlConstants.Right || value == HtmlConstants.Justify) box.TextAlign = value.ToLower(); else box.VerticalAlign = value.ToLower(); break; case HtmlConstants.Background: box.BackgroundImage = value.ToLower(); break; case HtmlConstants.Bgcolor: box.BackgroundColor = value.ToLower(); break; case HtmlConstants.Border: if (!string.IsNullOrEmpty(value) && value != "0") box.BorderLeftStyle = box.BorderTopStyle = box.BorderRightStyle = box.BorderBottomStyle = CssConstants.Solid; box.BorderLeftWidth = box.BorderTopWidth = box.BorderRightWidth = box.BorderBottomWidth = TranslateLength(value); if (tag.Name == HtmlConstants.Table) { if (value != "0") ApplyTableBorder(box, "1px"); } else { box.BorderTopStyle = box.BorderLeftStyle = box.BorderRightStyle = box.BorderBottomStyle = CssConstants.Solid; } break; case HtmlConstants.Bordercolor: box.BorderLeftColor = box.BorderTopColor = box.BorderRightColor = box.BorderBottomColor = value.ToLower(); break; case HtmlConstants.Cellspacing: box.BorderSpacing = TranslateLength(value); break; case HtmlConstants.Cellpadding: ApplyTablePadding(box, value); break; case HtmlConstants.Color: box.Color = value.ToLower(); break; case HtmlConstants.Dir: box.Direction = value.ToLower(); break; case HtmlConstants.Face: box.FontFamily = _cssParser.ParseFontFamily(value); break; case HtmlConstants.Height: box.Height = TranslateLength(value); break; case HtmlConstants.Hspace: box.MarginRight = box.MarginLeft = TranslateLength(value); break; case HtmlConstants.Nowrap: box.WhiteSpace = CssConstants.NoWrap; break; case HtmlConstants.Size: if (tag.Name.Equals(HtmlConstants.Hr, StringComparison.OrdinalIgnoreCase)) box.Height = TranslateLength(value); else if (tag.Name.Equals(HtmlConstants.Font, StringComparison.OrdinalIgnoreCase)) box.FontSize = value; break; case HtmlConstants.Valign: box.VerticalAlign = value.ToLower(); break; case HtmlConstants.Vspace: box.MarginTop = box.MarginBottom = TranslateLength(value); break; case HtmlConstants.Width: box.Width = TranslateLength(value); break; } } } }
/// <summary> /// Check if the given box contains inline and block child boxes. /// </summary> /// <param name="box">the box to check</param> /// <returns>true - has variant child boxes, false - otherwise</returns> private static bool ContainsVariantBoxes(CssBox box) { bool hasBlock = false; bool hasInline = false; for (int i = 0; i < box.Boxes.Count && (!hasBlock || !hasInline); i++) { var isBlock = !box.Boxes[i].IsInline; hasBlock = hasBlock || isBlock; hasInline = hasInline || !isBlock; } return hasBlock && hasInline; }
/// <summary> /// Makes block boxes be among only block boxes and all inline boxes have block parent box.<br/> /// Inline boxes should live in a pool of Inline boxes only so they will define a single block.<br/> /// At the end of this process a block box will have only block siblings and inline box will have /// only inline siblings. /// </summary> /// <param name="box">the current box to correct its sub-tree</param> private static void CorrectInlineBoxesParent(CssBox box) { if (ContainsVariantBoxes(box)) { for (int i = 0; i < box.Boxes.Count; i++) { if (box.Boxes[i].IsInline) { var newbox = CssBox.CreateBlock(box, null, box.Boxes[i++]); while (i < box.Boxes.Count && box.Boxes[i].IsInline) { box.Boxes[i].ParentBox = newbox; } } } } if (!DomUtils.ContainsInlinesOnly(box)) { foreach (var childBox in box.Boxes) { CorrectInlineBoxesParent(childBox); } } }
/// <summary> /// Rearrange the DOM of the box to have block box with boxes before the inner block box and after. /// </summary> /// <param name="box">the box that has the problem</param> private static CssBox CorrectBlockInsideInlineImp(CssBox box) { if (box.Display == CssConstants.Inline) box.Display = CssConstants.Block; if (box.Boxes.Count > 1 || box.Boxes[0].Boxes.Count > 1) { var leftBlock = CssBox.CreateBlock(box); while (ContainsInlinesOnlyDeep(box.Boxes[0])) box.Boxes[0].ParentBox = leftBlock; leftBlock.SetBeforeBox(box.Boxes[0]); var splitBox = box.Boxes[1]; splitBox.ParentBox = null; CorrectBlockSplitBadBox(box, splitBox, leftBlock); // remove block that did not get any inner elements if (leftBlock.Boxes.Count < 1) leftBlock.ParentBox = null; int minBoxes = leftBlock.ParentBox != null ? 2 : 1; if (box.Boxes.Count > minBoxes) { // create temp box to handle the tail elements and then get them back so no deep hierarchy is created var tempRightBox = CssBox.CreateBox(box, null, box.Boxes[minBoxes]); while (box.Boxes.Count > minBoxes + 1) box.Boxes[minBoxes + 1].ParentBox = tempRightBox; return tempRightBox; } } else if (box.Boxes[0].Display == CssConstants.Inline) { box.Boxes[0].Display = CssConstants.Block; } return null; }
/// <summary> /// Correct the DOM tree recursively by replacing "br" html boxes with anonymous blocks that respect br spec.<br/> /// If the "br" tag is after inline box then the anon block will have zero height only acting as newline, /// but if it is after block box then it will have min-height of the font size so it will create empty line. /// </summary> /// <param name="box">the current box to correct its sub-tree</param> /// <param name="followingBlock">used to know if the br is following a box so it should create an empty line or not so it only /// move to a new line</param> private static void CorrectLineBreaksBlocks(CssBox box, ref bool followingBlock) { followingBlock = followingBlock || box.IsBlock; foreach (var childBox in box.Boxes) { CorrectLineBreaksBlocks(childBox, ref followingBlock); followingBlock = childBox.Words.Count == 0 && (followingBlock || childBox.IsBlock); } int lastBr = -1; CssBox brBox; do { brBox = null; for (int i = 0; i < box.Boxes.Count && brBox == null; i++) { if (i > lastBr && box.Boxes[i].IsBrElement) { brBox = box.Boxes[i]; lastBr = i; } else if (box.Boxes[i].Words.Count > 0) { followingBlock = false; } else if (box.Boxes[i].IsBlock) { followingBlock = true; } } if (brBox != null) { brBox.Display = CssConstants.Block; if (followingBlock) brBox.Height = ".95em"; // TODO:a check the height to min-height when it is supported } } while (brBox != null); }
/// <summary> /// Go over all the text boxes (boxes that have some text that will be rendered) and /// remove all boxes that have only white-spaces but are not 'preformatted' so they do not effect /// the rendered html. /// </summary> /// <param name="box">the current box to correct its sub-tree</param> private static void CorrectTextBoxes(CssBox box) { for (int i = box.Boxes.Count - 1; i >= 0; i--) { var childBox = box.Boxes[i]; if (childBox.Text != null) { // is the box has text var keepBox = !childBox.Text.IsEmptyOrWhitespace(); // is the box is pre-formatted keepBox = keepBox || childBox.WhiteSpace == CssConstants.Pre || childBox.WhiteSpace == CssConstants.PreWrap; // is the box is only one in the parent keepBox = keepBox || box.Boxes.Count == 1; // is it a whitespace between two inline boxes keepBox = keepBox || (i > 0 && i < box.Boxes.Count - 1 && box.Boxes[i - 1].IsInline && box.Boxes[i + 1].IsInline); // is first/last box where is in inline box and it's next/previous box is inline keepBox = keepBox || (i == 0 && box.Boxes.Count > 1 && box.Boxes[1].IsInline && box.IsInline) || (i == box.Boxes.Count - 1 && box.Boxes.Count > 1 && box.Boxes[i - 1].IsInline && box.IsInline); if (keepBox) { // valid text box, parse it to words childBox.ParseToWords(); } else { // remove text box that has no childBox.ParentBox.Boxes.RemoveAt(i); } } else { // recursive CorrectTextBoxes(childBox); } } }
/// <summary> /// Execute action on all the "td" cells of the table.<br/> /// Handle if there is "theader" or "tbody" exists. /// </summary> /// <param name="table">the table element</param> /// <param name="action">the action to execute</param> private static void SetForAllCells(CssBox table, ActionInt<CssBox> action) { foreach (var l1 in table.Boxes) { foreach (var l2 in l1.Boxes) { if (l2.HtmlTag != null && l2.HtmlTag.Name == "td") { action(l2); } else { foreach (var l3 in l2.Boxes) { action(l3); } } } } }
/// <summary> /// Assigns the given css classes to the given css box checking if matching.<br/> /// Support multiple classes in single attribute separated by whitespace. /// </summary> /// <param name="box">the css box to assign css to</param> /// <param name="cssData">the css data to use to get the matching css blocks</param> private static void AssignClassCssBlocks(CssBox box, CssData cssData) { var classes = box.HtmlTag.TryGetAttribute("class"); var startIdx = 0; while (startIdx < classes.Length) { while (startIdx < classes.Length && classes[startIdx] == ' ') startIdx++; if (startIdx < classes.Length) { var endIdx = classes.IndexOf(' ', startIdx); if (endIdx < 0) endIdx = classes.Length; var cls = "." + classes.Substring(startIdx, endIdx - startIdx); AssignCssBlocks(box, cssData, cls); AssignCssBlocks(box, cssData, box.HtmlTag.Name + cls); startIdx = endIdx + 1; } } }
/// <summary> /// Go over all image boxes and if its display style is set to block, put it inside another block but set the image to inline. /// </summary> /// <param name="box">the current box to correct its sub-tree</param> private static void CorrectImgBoxes(CssBox box) { for (int i = box.Boxes.Count - 1; i >= 0; i--) { var childBox = box.Boxes[i]; if (childBox is CssBoxImage && childBox.Display == CssConstants.Block) { var block = CssBox.CreateBlock(childBox.ParentBox, null, childBox); childBox.ParentBox = block; childBox.Display = CssConstants.Inline; } else { // recursive CorrectImgBoxes(childBox); } } }
/// <summary> /// Assigns the given css style blocks to the given css box checking if matching. /// </summary> /// <param name="box">the css box to assign css to</param> /// <param name="cssData">the css data to use to get the matching css blocks</param> /// <param name="className">the class selector to search for css blocks</param> private static void AssignCssBlocks(CssBox box, CssData cssData, string className) { var blocks = cssData.GetCssBlock(className); foreach (var block in blocks) { if (IsBlockAssignableToBox(box, block)) { AssignCssBlock(box, block); } } }
/// <summary> /// Correct DOM tree if there is block boxes that are inside inline blocks.<br/> /// Need to rearrange the tree so block box will be only the child of other block box. /// </summary> /// <param name="box">the current box to correct its sub-tree</param> private static void CorrectBlockInsideInline(CssBox box) { try { if (DomUtils.ContainsInlinesOnly(box) && !ContainsInlinesOnlyDeep(box)) { var tempRightBox = CorrectBlockInsideInlineImp(box); while (tempRightBox != null) { // loop on the created temp right box for the fixed box until no more need (optimization remove recursion) CssBox newTempRightBox = null; if (DomUtils.ContainsInlinesOnly(tempRightBox) && !ContainsInlinesOnlyDeep(tempRightBox)) newTempRightBox = CorrectBlockInsideInlineImp(tempRightBox); tempRightBox.ParentBox.SetAllBoxes(tempRightBox); tempRightBox.ParentBox = null; tempRightBox = newTempRightBox; } } if (!DomUtils.ContainsInlinesOnly(box)) { foreach (var childBox in box.Boxes) { CorrectBlockInsideInline(childBox); } } } catch (Exception ex) { box.HtmlContainer.ReportError(HtmlRenderErrorType.HtmlParsing, "Failed in block inside inline box correction", ex); } }
/// <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> /// Split bad box that has inline and block boxes into two parts, the left - before the block box /// and right - after the block box. /// </summary> /// <param name="parentBox">the parent box that has the problem</param> /// <param name="badBox">the box to split into different boxes</param> /// <param name="leftBlock">the left block box that is created for the split</param> private static void CorrectBlockSplitBadBox(CssBox parentBox, CssBox badBox, CssBox leftBlock) { CssBox leftbox = null; while (badBox.Boxes[0].IsInline && ContainsInlinesOnlyDeep(badBox.Boxes[0])) { if (leftbox == null) { // if there is no elements in the left box there is no reason to keep it leftbox = CssBox.CreateBox(leftBlock, badBox.HtmlTag); leftbox.InheritStyle(badBox, true); } badBox.Boxes[0].ParentBox = leftbox; } var splitBox = badBox.Boxes[0]; if (!ContainsInlinesOnlyDeep(splitBox)) { CorrectBlockSplitBadBox(parentBox, splitBox, leftBlock); splitBox.ParentBox = null; } else { splitBox.ParentBox = parentBox; } if (badBox.Boxes.Count > 0) { CssBox rightBox; if (splitBox.ParentBox != null || parentBox.Boxes.Count < 3) { rightBox = CssBox.CreateBox(parentBox, badBox.HtmlTag); rightBox.InheritStyle(badBox, true); if (parentBox.Boxes.Count > 2) rightBox.SetBeforeBox(parentBox.Boxes[1]); if (splitBox.ParentBox != null) splitBox.SetBeforeBox(rightBox); } else { rightBox = parentBox.Boxes[2]; } rightBox.SetAllBoxes(badBox); } else if (splitBox.ParentBox != null && parentBox.Boxes.Count > 1) { splitBox.SetBeforeBox(parentBox.Boxes[1]); if (splitBox.HtmlTag != null && splitBox.HtmlTag.Name == "br" && (leftbox != null || leftBlock.Boxes.Count > 1)) splitBox.Display = CssConstants.Inline; } }
/// <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; }
/// <summary> /// Check if the given box contains only inline child boxes in all subtree. /// </summary> /// <param name="box">the box to check</param> /// <returns>true - only inline child boxes, false - otherwise</returns> private static bool ContainsInlinesOnlyDeep(CssBox box) { foreach (var childBox in box.Boxes) { if (!childBox.IsInline || !ContainsInlinesOnlyDeep(childBox)) { return false; } } return true; }
/// <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> /// Read styles defined inside the dom structure in links and style elements.<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">the box to parse style data in</param> /// <param name="htmlContainer">the html container to use for reference resolve</param> /// <param name="cssData">the style data to fill with found styles</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 void CascadeParseStyles(CssBox box, HtmlContainerInt htmlContainer, ref CssData cssData, ref bool cssDataChanged) { if (box.HtmlTag != null) { // 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); } // Check for the <style> tag if (box.HtmlTag.Name.Equals("style", StringComparison.CurrentCultureIgnoreCase) && box.Boxes.Count > 0) { CloneCssData(ref cssData, ref cssDataChanged); foreach (var child in box.Boxes) _cssParser.ParseStyleSheet(cssData, child.Text.CutSubstring()); } } foreach (var childBox in box.Boxes) { CascadeParseStyles(childBox, htmlContainer, ref cssData, ref cssDataChanged); } }
/// <summary> /// Check if the given style is allowed to be set on the given css box.<br/> /// Used to prevent invalid CssBoxes creation like table with inline display style. /// </summary> /// <param name="box">the css box to assign css to</param> /// <param name="key">the style key to cehck</param> /// <param name="value">the style value to check</param> /// <returns>true - style allowed, false - not allowed</returns> private static bool IsStyleOnElementAllowed(CssBox box, string key, string value) { if (box.HtmlTag != null && key == HtmlConstants.Display) { switch (box.HtmlTag.Name) { case HtmlConstants.Table: return value == CssConstants.Table; case HtmlConstants.Tr: return value == CssConstants.TableRow; case HtmlConstants.Tbody: return value == CssConstants.TableRowGroup; case HtmlConstants.Thead: return value == CssConstants.TableHeaderGroup; case HtmlConstants.Tfoot: return value == CssConstants.TableFooterGroup; case HtmlConstants.Col: return value == CssConstants.TableColumn; case HtmlConstants.Colgroup: return value == CssConstants.TableColumnGroup; case HtmlConstants.Td: case HtmlConstants.Th: return value == CssConstants.TableCell; case HtmlConstants.Caption: return value == CssConstants.TableCaption; } } return true; }