// "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
}
}