// "Therefore those skilled at the unorthodox // are infinite as heaven and earth, // inexhaustible as the great rivers. // When they come to an end, // they begin again, // like the days and months; // they die and are reborn, // like the four seasons." // // - Sun Tsu, // "The Art of War" using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.Adapters.Entities; using TheArtOfDev.HtmlRenderer.Core.Entities; using TheArtOfDev.HtmlRenderer.Core.Utils; namespace TheArtOfDev.HtmlRenderer.Core.Parse { /// /// Parser to parse CSS stylesheet source string into CSS objects. /// internal sealed class CssParser { #region Fields and Consts /// /// split CSS rule /// private static readonly char[] _cssBlockSplitters = new[] { '}', ';' }; /// /// /// private readonly RAdapter _adapter; /// /// Utility for value parsing. /// private readonly CssValueParser _valueParser; /// /// The chars to trim the css class name by /// private static readonly char[] _cssClassTrimChars = new[] { '\r', '\n', '\t', ' ', '-', '!', '<', '>' }; #endregion /// /// Init. /// public CssParser(RAdapter adapter) { ArgChecker.AssertArgNotNull(adapter, "global"); _valueParser = new CssValueParser(adapter); _adapter = adapter; } /// /// Parse the given stylesheet source to CSS blocks dictionary.
/// The CSS blocks are organized into two level buckets of media type and class name.
/// Root media type are found under 'all' bucket.
/// If is true the parsed css blocks are added to the /// default css data (as defined by W3), merged if class name already exists. If false only the data in the given stylesheet is returned. ///
/// raw css stylesheet to parse /// true - combine the parsed css data with default css data, false - return only the parsed css data /// the CSS data with parsed CSS objects (never null) public CssData ParseStyleSheet(string stylesheet, bool combineWithDefault) { var cssData = combineWithDefault ? _adapter.DefaultCssData.Clone() : new CssData(); if (!string.IsNullOrEmpty(stylesheet)) { ParseStyleSheet(cssData, stylesheet); } return cssData; } /// /// Parse the given stylesheet source to CSS blocks dictionary.
/// The CSS blocks are organized into two level buckets of media type and class name.
/// Root media type are found under 'all' bucket.
/// The parsed css blocks are added to the given css data, merged if class name already exists. ///
/// the CSS data to fill with parsed CSS objects /// raw css stylesheet to parse public void ParseStyleSheet(CssData cssData, string stylesheet) { if (!String.IsNullOrEmpty(stylesheet)) { stylesheet = RemoveStylesheetComments(stylesheet); ParseStyleBlocks(cssData, stylesheet); ParseMediaStyleBlocks(cssData, stylesheet); } } /// /// Parse single CSS block source into CSS block instance. /// /// the name of the css class of the block /// the CSS block to parse /// the created CSS block instance public CssBlock ParseCssBlock(string className, string blockSource) { return ParseCssBlockImp(className, blockSource); } /// /// Parse a complex font family css property to check if it contains multiple fonts and if the font exists.
/// returns the font family name to use or 'inherit' if failed. ///
/// the font-family value to parse /// parsed font-family value public string ParseFontFamily(string value) { return ParseFontFamilyProperty(value); } /// /// Parses a color value in CSS style; e.g. #ff0000, red, rgb(255,0,0), rgb(100%, 0, 0) /// /// color string value to parse /// color value public RColor ParseColor(string colorStr) { return _valueParser.GetActualColor(colorStr); } #region Private methods /// /// Remove comments from the given stylesheet. /// /// the stylesheet to remove comments from /// stylesheet without comments private static string RemoveStylesheetComments(string stylesheet) { StringBuilder sb = null; int prevIdx = 0, startIdx = 0; while (startIdx > -1 && startIdx < stylesheet.Length) { startIdx = stylesheet.IndexOf("/*", startIdx); if (startIdx > -1) { if (sb == null) sb = new StringBuilder(stylesheet.Length); sb.Append(stylesheet.Substring(prevIdx, startIdx - prevIdx)); var endIdx = stylesheet.IndexOf("*/", startIdx + 2); if (endIdx < 0) endIdx = stylesheet.Length; prevIdx = startIdx = endIdx + 2; } else if (sb != null) { sb.Append(stylesheet.Substring(prevIdx)); } } return sb != null ? sb.ToString() : stylesheet; } /// /// Parse given stylesheet for CSS blocks
/// This blocks are added under the "all" keyword. ///
/// the CSS data to fill with parsed CSS objects /// the stylesheet to parse private void ParseStyleBlocks(CssData cssData, string stylesheet) { var startIdx = 0; int endIdx = 0; while (startIdx < stylesheet.Length && endIdx > -1) { endIdx = startIdx; while (endIdx + 1 < stylesheet.Length) { endIdx++; if (stylesheet[endIdx] == '}') startIdx = endIdx + 1; if (stylesheet[endIdx] == '{') break; } int midIdx = endIdx + 1; if (endIdx > -1) { endIdx++; while (endIdx < stylesheet.Length) { if (stylesheet[endIdx] == '{') startIdx = midIdx + 1; if (stylesheet[endIdx] == '}') break; endIdx++; } if (endIdx < stylesheet.Length) { while (Char.IsWhiteSpace(stylesheet[startIdx])) startIdx++; var substring = stylesheet.Substring(startIdx, endIdx - startIdx + 1); FeedStyleBlock(cssData, substring); } startIdx = endIdx + 1; } } } /// /// Parse given stylesheet for media CSS blocks
/// This blocks are added under the specific media block they are found. ///
/// the CSS data to fill with parsed CSS objects /// the stylesheet to parse private void ParseMediaStyleBlocks(CssData cssData, string stylesheet) { int startIdx = 0; string atrule; while ((atrule = RegexParserUtils.GetCssAtRules(stylesheet, ref startIdx)) != null) { //Just process @media rules if (!atrule.StartsWith("@media", StringComparison.InvariantCultureIgnoreCase)) continue; //Extract specified media types MatchCollection types = RegexParserUtils.Match(RegexParserUtils.CssMediaTypes, atrule); if (types.Count == 1) { string line = types[0].Value; if (line.StartsWith("@media", StringComparison.InvariantCultureIgnoreCase) && line.EndsWith("{")) { //Get specified media types in the at-rule string[] media = line.Substring(6, line.Length - 7).Split(' '); //Scan media types foreach (string t in media) { if (!String.IsNullOrEmpty(t.Trim())) { //Get blocks inside the at-rule var insideBlocks = RegexParserUtils.Match(RegexParserUtils.CssBlocks, atrule); //Scan blocks and feed them to the style sheet foreach (Match insideBlock in insideBlocks) { FeedStyleBlock(cssData, insideBlock.Value, t.Trim()); } } } } } } } /// /// Feeds the style with a block about the specific media.
/// When no media is specified, "all" will be used. ///
/// /// the CSS block to handle /// optional: the media (default - all) private 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); var classes = block.Substring(0, startIdx).Split(','); foreach (string cls in classes) { string className = cls.Trim(_cssClassTrimChars); if (!String.IsNullOrEmpty(className)) { var newblock = ParseCssBlockImp(className, blockSource); if (newblock != null) { cssData.AddCssBlock(media, newblock); } } } } } /// /// Parse single CSS block source into CSS block instance. /// /// the name of the css class of the block /// the CSS block to parse /// the created CSS block instance private CssBlock ParseCssBlockImp(string className, string blockSource) { className = className.ToLower(); string psedoClass = null; var colonIdx = className.IndexOf(":", StringComparison.Ordinal); if (colonIdx > -1 && !className.StartsWith("::")) { psedoClass = colonIdx < className.Length - 1 ? className.Substring(colonIdx + 1).Trim() : null; className = className.Substring(0, colonIdx).Trim(); } if (!string.IsNullOrEmpty(className) && (psedoClass == null || psedoClass == "link" || psedoClass == "hover")) { string firstClass; var selectors = ParseCssBlockSelector(className, out firstClass); var properties = ParseCssBlockProperties(blockSource); return new CssBlock(firstClass, properties, selectors, psedoClass == "hover"); } return null; } /// /// Parse css block selector to support hierarchical selector (p class1 > class2). /// /// the class selector to parse /// return the main class the css block is on /// returns the hierarchy of classes or null if single class selector private static List ParseCssBlockSelector(string className, out string firstClass) { List selectors = null; firstClass = null; int endIdx = className.Length - 1; while (endIdx > -1) { bool directParent = false; while (char.IsWhiteSpace(className[endIdx]) || className[endIdx] == '>') { directParent = directParent || className[endIdx] == '>'; endIdx--; } var startIdx = endIdx; while (startIdx > -1 && !char.IsWhiteSpace(className[startIdx]) && className[startIdx] != '>') startIdx--; if (startIdx > -1) { if (selectors == null) selectors = new List(); var subclass = className.Substring(startIdx + 1, endIdx - startIdx); if (firstClass == null) { firstClass = subclass; } else { while (char.IsWhiteSpace(className[startIdx]) || className[startIdx] == '>') startIdx--; selectors.Add(new CssBlockSelectorItem(subclass, directParent)); } } else if (firstClass != null) { selectors.Add(new CssBlockSelectorItem(className.Substring(0, endIdx + 1), directParent)); } endIdx = startIdx; } firstClass = firstClass ?? className; return selectors; } /// /// Parse the properties of the given css block into a key-value dictionary. /// /// the raw css block to parse /// dictionary with parsed css block properties private Dictionary ParseCssBlockProperties(string blockSource) { var properties = new Dictionary(); int startIdx = 0; while (startIdx < blockSource.Length) { int endIdx = blockSource.IndexOfAny(_cssBlockSplitters, startIdx); if (endIdx < 0) endIdx = blockSource.Length - 1; var splitIdx = blockSource.IndexOf(':', startIdx, endIdx - startIdx); if (splitIdx > -1) { //Extract property name and value startIdx = startIdx + (blockSource[startIdx] == ' ' ? 1 : 0); var adjEndIdx = endIdx - (blockSource[endIdx] == ' ' || blockSource[endIdx] == ';' ? 1 : 0); string propName = blockSource.Substring(startIdx, splitIdx - startIdx).Trim().ToLower(); splitIdx = splitIdx + (blockSource[splitIdx + 1] == ' ' ? 2 : 1); if (adjEndIdx >= splitIdx) { string propValue = blockSource.Substring(splitIdx, adjEndIdx - splitIdx + 1).Trim(); if (!propValue.StartsWith("url", StringComparison.InvariantCultureIgnoreCase)) propValue = propValue.ToLower(); AddProperty(propName, propValue, properties); } } startIdx = endIdx + 1; } return properties; } /// /// Add the given property to the given properties collection, if the property is complex containing /// multiple css properties then parse them and add the inner properties. /// /// the name of the css property to add /// the value of the css property to add /// the properties collection to add to private void AddProperty(string propName, string propValue, Dictionary properties) { // remove !important css crap propValue = propValue.Replace("!important", string.Empty).Trim(); if (propName == "width" || propName == "height" || propName == "lineheight") { ParseLengthProperty(propName, propValue, properties); } else if (propName == "color" || propName == "backgroundcolor" || propName == "bordertopcolor" || propName == "borderbottomcolor" || propName == "borderleftcolor" || propName == "borderrightcolor") { ParseColorProperty(propName, propValue, properties); } else if (propName == "font") { ParseFontProperty(propValue, properties); } else if (propName == "border") { ParseBorderProperty(propValue, null, properties); } else if (propName == "border-left") { ParseBorderProperty(propValue, "-left", properties); } else if (propName == "border-top") { ParseBorderProperty(propValue, "-top", properties); } else if (propName == "border-right") { ParseBorderProperty(propValue, "-right", properties); } else if (propName == "border-bottom") { ParseBorderProperty(propValue, "-bottom", properties); } else if (propName == "margin") { ParseMarginProperty(propValue, properties); } else if (propName == "border-style") { ParseBorderStyleProperty(propValue, properties); } else if (propName == "border-width") { ParseBorderWidthProperty(propValue, properties); } else if (propName == "border-color") { ParseBorderColorProperty(propValue, properties); } else if (propName == "padding") { ParsePaddingProperty(propValue, properties); } else if (propName == "background-image") { properties["background-image"] = ParseBackgroundImageProperty(propValue); } else if (propName == "font-family") { properties["font-family"] = ParseFontFamilyProperty(propValue); } else { properties[propName] = propValue; } } /// /// Parse length property to add only valid lengths. /// /// the name of the css property to add /// the value of the css property to add /// the properties collection to add to private static void ParseLengthProperty(string propName, string propValue, Dictionary properties) { if (CssValueParser.IsValidLength(propValue) || propValue.Equals(CssConstants.Auto, StringComparison.OrdinalIgnoreCase)) { properties[propName] = propValue; } } /// /// Parse color property to add only valid color. /// /// the name of the css property to add /// the value of the css property to add /// the properties collection to add to private void ParseColorProperty(string propName, string propValue, Dictionary properties) { if (_valueParser.IsColorValid(propValue)) { properties[propName] = propValue; } } /// /// Parse a complex font property value that contains multiple css properties into specific css properties. /// /// the value of the property to parse to specific values /// the properties collection to add the specific properties to private void ParseFontProperty(string propValue, Dictionary properties) { int mustBePos; string mustBe = RegexParserUtils.Search(RegexParserUtils.CssFontSizeAndLineHeight, propValue, out mustBePos); if (!string.IsNullOrEmpty(mustBe)) { mustBe = mustBe.Trim(); //Check for style||variant||weight on the left string leftSide = propValue.Substring(0, mustBePos); string fontStyle = RegexParserUtils.Search(RegexParserUtils.CssFontStyle, leftSide); string fontVariant = RegexParserUtils.Search(RegexParserUtils.CssFontVariant, leftSide); string fontWeight = RegexParserUtils.Search(RegexParserUtils.CssFontWeight, leftSide); //Check for family on the right string rightSide = propValue.Substring(mustBePos + mustBe.Length); string fontFamily = rightSide.Trim(); //Parser.Search(Parser.CssFontFamily, rightSide); //TODO: Would this be right? //Check for font-size and line-height string fontSize = mustBe; string lineHeight = string.Empty; if (mustBe.Contains("/") && mustBe.Length > mustBe.IndexOf("/", StringComparison.Ordinal) + 1) { int slashPos = mustBe.IndexOf("/", StringComparison.Ordinal); fontSize = mustBe.Substring(0, slashPos); lineHeight = mustBe.Substring(slashPos + 1); } if (!string.IsNullOrEmpty(fontFamily)) properties["font-family"] = ParseFontFamilyProperty(fontFamily); if (!string.IsNullOrEmpty(fontStyle)) properties["font-style"] = fontStyle; if (!string.IsNullOrEmpty(fontVariant)) properties["font-variant"] = fontVariant; if (!string.IsNullOrEmpty(fontWeight)) properties["font-weight"] = fontWeight; if (!string.IsNullOrEmpty(fontSize)) properties["font-size"] = fontSize; if (!string.IsNullOrEmpty(lineHeight)) properties["line-height"] = lineHeight; } else { // Check for: caption | icon | menu | message-box | small-caption | status-bar //TODO: Interpret font values of: caption | icon | menu | message-box | small-caption | status-bar } } /// /// /// /// the value of the property to parse /// parsed value private static string ParseBackgroundImageProperty(string propValue) { int startIdx = propValue.IndexOf("url(", StringComparison.InvariantCultureIgnoreCase); if (startIdx > -1) { startIdx += 4; var endIdx = propValue.IndexOf(')', startIdx); if (endIdx > -1) { endIdx -= 1; while (startIdx < endIdx && (char.IsWhiteSpace(propValue[startIdx]) || propValue[startIdx] == '\'' || propValue[startIdx] == '"')) startIdx++; while (startIdx < endIdx && (char.IsWhiteSpace(propValue[endIdx]) || propValue[endIdx] == '\'' || propValue[endIdx] == '"')) endIdx--; if (startIdx <= endIdx) return propValue.Substring(startIdx, endIdx - startIdx + 1); } } return propValue; } /// /// Parse a complex font family css property to check if it contains multiple fonts and if the font exists.
/// returns the font family name to use or 'inherit' if failed. ///
/// the value of the property to parse /// parsed font-family value private string ParseFontFamilyProperty(string propValue) { int start = 0; while (start > -1 && start < propValue.Length) { while (char.IsWhiteSpace(propValue[start]) || propValue[start] == ',' || propValue[start] == '\'' || propValue[start] == '"') start++; var end = propValue.IndexOf(',', start); if (end < 0) end = propValue.Length; var adjEnd = end - 1; while (char.IsWhiteSpace(propValue[adjEnd]) || propValue[adjEnd] == '\'' || propValue[adjEnd] == '"') adjEnd--; var font = propValue.Substring(start, adjEnd - start + 1); if (_adapter.IsFontExists(font)) { return font; } start = end; } return CssConstants.Inherit; } /// /// Parse a complex border property value that contains multiple css properties into specific css properties. /// /// the value of the property to parse to specific values /// the left, top, right or bottom direction of the border to parse /// the properties collection to add the specific properties to private void ParseBorderProperty(string propValue, string direction, Dictionary properties) { string borderWidth; string borderStyle; string borderColor; ParseBorder(propValue, out borderWidth, out borderStyle, out borderColor); if (direction != null) { if (borderWidth != null) properties["border" + direction + "-width"] = borderWidth; if (borderStyle != null) properties["border" + direction + "-style"] = borderStyle; if (borderColor != null) properties["border" + direction + "-color"] = borderColor; } else { if (borderWidth != null) ParseBorderWidthProperty(borderWidth, properties); if (borderStyle != null) ParseBorderStyleProperty(borderStyle, properties); if (borderColor != null) ParseBorderColorProperty(borderColor, properties); } } /// /// Parse a complex margin property value that contains multiple css properties into specific css properties. /// /// the value of the property to parse to specific values /// the properties collection to add the specific properties to private static void ParseMarginProperty(string propValue, Dictionary properties) { string bottom, top, left, right; SplitMultiDirectionValues(propValue, out left, out top, out right, out bottom); if (left != null) properties["margin-left"] = left; if (top != null) properties["margin-top"] = top; if (right != null) properties["margin-right"] = right; if (bottom != null) properties["margin-bottom"] = bottom; } /// /// Parse a complex border style property value that contains multiple css properties into specific css properties. /// /// the value of the property to parse to specific values /// the properties collection to add the specific properties to private static void ParseBorderStyleProperty(string propValue, Dictionary properties) { string bottom, top, left, right; SplitMultiDirectionValues(propValue, out left, out top, out right, out bottom); if (left != null) properties["border-left-style"] = left; if (top != null) properties["border-top-style"] = top; if (right != null) properties["border-right-style"] = right; if (bottom != null) properties["border-bottom-style"] = bottom; } /// /// Parse a complex border width property value that contains multiple css properties into specific css properties. /// /// the value of the property to parse to specific values /// the properties collection to add the specific properties to private static void ParseBorderWidthProperty(string propValue, Dictionary properties) { string bottom, top, left, right; SplitMultiDirectionValues(propValue, out left, out top, out right, out bottom); if (left != null) properties["border-left-width"] = left; if (top != null) properties["border-top-width"] = top; if (right != null) properties["border-right-width"] = right; if (bottom != null) properties["border-bottom-width"] = bottom; } /// /// Parse a complex border color property value that contains multiple css properties into specific css properties. /// /// the value of the property to parse to specific values /// the properties collection to add the specific properties to private static void ParseBorderColorProperty(string propValue, Dictionary properties) { string bottom, top, left, right; SplitMultiDirectionValues(propValue, out left, out top, out right, out bottom); if (left != null) properties["border-left-color"] = left; if (top != null) properties["border-top-color"] = top; if (right != null) properties["border-right-color"] = right; if (bottom != null) properties["border-bottom-color"] = bottom; } /// /// Parse a complex padding property value that contains multiple css properties into specific css properties. /// /// the value of the property to parse to specific values /// the properties collection to add the specific properties to private static void ParsePaddingProperty(string propValue, Dictionary properties) { string bottom, top, left, right; SplitMultiDirectionValues(propValue, out left, out top, out right, out bottom); if (left != null) properties["padding-left"] = left; if (top != null) properties["padding-top"] = top; if (right != null) properties["padding-right"] = right; if (bottom != null) properties["padding-bottom"] = bottom; } /// /// Split multi direction value into the proper direction values (left, top, right, bottom). /// private static void SplitMultiDirectionValues(string propValue, out string left, out string top, out string right, out string bottom) { top = null; left = null; right = null; bottom = null; string[] values = SplitValues(propValue); switch (values.Length) { case 1: top = left = right = bottom = values[0]; break; case 2: top = bottom = values[0]; left = right = values[1]; break; case 3: top = values[0]; left = right = values[1]; bottom = values[2]; break; case 4: top = values[0]; right = values[1]; bottom = values[2]; left = values[3]; break; } } /// /// Split the value by the specified separator; e.g. Useful in values like 'padding:5 4 3 inherit' /// /// Value to be splitted /// /// Splitted and trimmed values private static string[] SplitValues(string value, char separator = ' ') { //TODO: CRITICAL! Don't split values on parenthesis (like rgb(0, 0, 0)) or quotes ("strings") if (!string.IsNullOrEmpty(value)) { string[] values = value.Split(separator); List result = new List(); foreach (string t in values) { string val = t.Trim(); if (!string.IsNullOrEmpty(val)) { result.Add(val); } } return result.ToArray(); } return new string[0]; } /// /// /// /// /// /// /// public void ParseBorder(string value, out string width, out string style, out string color) { width = style = color = null; if (!string.IsNullOrEmpty(value)) { int idx = 0; int length; while ((idx = CommonUtils.GetNextSubString(value, idx, out length)) > -1) { if (width == null) width = ParseBorderWidth(value, idx, length); if (style == null) style = ParseBorderStyle(value, idx, length); if (color == null) color = ParseBorderColor(value, idx, length); idx = idx + length + 1; } } } /// /// Parse the given substring to extract border width substring. /// Assume given substring is not empty and all indexes are valid!
///
/// found border width value or null private static string ParseBorderWidth(string str, int idx, int length) { if ((length > 2 && char.IsDigit(str[idx])) || (length > 3 && str[idx] == '.')) { string unit = null; if (CommonUtils.SubStringEquals(str, idx + length - 2, 2, CssConstants.Px)) unit = CssConstants.Px; else if (CommonUtils.SubStringEquals(str, idx + length - 2, 2, CssConstants.Pt)) unit = CssConstants.Pt; else if (CommonUtils.SubStringEquals(str, idx + length - 2, 2, CssConstants.Em)) unit = CssConstants.Em; else if (CommonUtils.SubStringEquals(str, idx + length - 2, 2, CssConstants.Ex)) unit = CssConstants.Ex; else if (CommonUtils.SubStringEquals(str, idx + length - 2, 2, CssConstants.In)) unit = CssConstants.In; else if (CommonUtils.SubStringEquals(str, idx + length - 2, 2, CssConstants.Cm)) unit = CssConstants.Cm; else if (CommonUtils.SubStringEquals(str, idx + length - 2, 2, CssConstants.Mm)) unit = CssConstants.Mm; else if (CommonUtils.SubStringEquals(str, idx + length - 2, 2, CssConstants.Pc)) unit = CssConstants.Pc; if (unit != null) { if (CssValueParser.IsFloat(str, idx, length - 2)) return str.Substring(idx, length); } } else { if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Thin)) return CssConstants.Thin; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Medium)) return CssConstants.Medium; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Thick)) return CssConstants.Thick; } return null; } /// /// Parse the given substring to extract border style substring.
/// Assume given substring is not empty and all indexes are valid!
///
/// found border width value or null private static string ParseBorderStyle(string str, int idx, int length) { if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.None)) return CssConstants.None; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Solid)) return CssConstants.Solid; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Hidden)) return CssConstants.Hidden; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Dotted)) return CssConstants.Dotted; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Dashed)) return CssConstants.Dashed; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Double)) return CssConstants.Double; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Groove)) return CssConstants.Groove; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Ridge)) return CssConstants.Ridge; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Inset)) return CssConstants.Inset; if (CommonUtils.SubStringEquals(str, idx, length, CssConstants.Outset)) return CssConstants.Outset; return null; } /// /// Parse the given substring to extract border style substring.
/// Assume given substring is not empty and all indexes are valid!
///
/// found border width value or null private string ParseBorderColor(string str, int idx, int length) { RColor color; return _valueParser.TryGetColor(str, idx, length, out color) ? str.Substring(idx, length) : null; } #endregion } }