CssBuilder
See http://dev.w3.org/csswg/css-syntax/#parsing for details.
using AngleSharp.Css;
using AngleSharp.Css.Conditions;
using AngleSharp.Css.MediaFeatures;
using AngleSharp.Css.Values;
using AngleSharp.Dom;
using AngleSharp.Dom.Collections;
using AngleSharp.Dom.Css;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace AngleSharp.Parser.Css
{
[DebuggerStepThrough]
internal sealed class CssBuilder
{
private readonly CssTokenizer _tokenizer;
private readonly CssParser _parser;
public CssBuilder(CssTokenizer tokenizer, CssParser parser)
{
_tokenizer = tokenizer;
_parser = parser;
}
public CssRule CreateAtRule(CssToken token)
{
if (token.Data == RuleNames.Charset)
return CreateCharset(token);
if (token.Data == RuleNames.Page)
return CreatePage(token);
if (token.Data == RuleNames.Import)
return CreateImport(token);
if (token.Data == RuleNames.FontFace)
return CreateFontFace(token);
if (token.Data == RuleNames.Media)
return CreateMedia(token);
if (token.Data == RuleNames.Namespace)
return CreateNamespace(token);
if (token.Data == RuleNames.Supports)
return CreateSupports(token);
if (token.Data == RuleNames.Keyframes)
return CreateKeyframes(token);
if (token.Data == RuleNames.Document)
return CreateDocument(token);
return CreateUnknown(token);
}
public CssRule CreateRule(CssToken token)
{
switch (token.Type) {
case CssTokenType.AtKeyword:
return CreateAtRule(token);
case CssTokenType.CurlyBracketOpen:
_tokenizer.RaiseErrorOccurred(CssParseError.InvalidBlockStart, token.Position);
_tokenizer.SkipUnknownRule();
return null;
case CssTokenType.String:
case CssTokenType.Url:
case CssTokenType.RoundBracketClose:
case CssTokenType.CurlyBracketClose:
case CssTokenType.SquareBracketClose:
_tokenizer.RaiseErrorOccurred(CssParseError.InvalidToken, token.Position);
_tokenizer.SkipUnknownRule();
return null;
default:
return CreateStyle(token);
}
}
public CssRule CreateCharset(CssToken current)
{
CssToken cssToken = _tokenizer.Get();
CssCharsetRule cssCharsetRule = new CssCharsetRule(_parser);
if (cssToken.Type == CssTokenType.String)
cssCharsetRule.CharacterSet = cssToken.Data;
_tokenizer.JumpToNextSemicolon();
return cssCharsetRule;
}
public CssRule CreateDocument(CssToken current)
{
CssToken token = _tokenizer.Get();
CssDocumentRule cssDocumentRule = new CssDocumentRule(_parser);
List<IDocumentFunction> collection = CreateFunctions(ref token);
cssDocumentRule.Conditions.AddRange(collection);
if (token.Type != CssTokenType.CurlyBracketOpen)
return SkipDeclarations(token);
FillRules(cssDocumentRule);
return cssDocumentRule;
}
public CssRule CreateFontFace(CssToken current)
{
CssToken cssToken = _tokenizer.Get();
CssFontFaceRule cssFontFaceRule = new CssFontFaceRule(_parser);
if (cssToken.Type != CssTokenType.CurlyBracketOpen)
return SkipDeclarations(cssToken);
FillFontFaceDeclarations(cssFontFaceRule);
return cssFontFaceRule;
}
public CssRule CreateImport(CssToken current)
{
CssToken token = _tokenizer.Get();
CssImportRule cssImportRule = new CssImportRule(_parser);
if (token.Is(CssTokenType.String, CssTokenType.Url)) {
cssImportRule.Href = token.Data;
token = _tokenizer.Get();
if (token.Type != CssTokenType.Semicolon)
FillMediaList(cssImportRule.Media, ref token);
}
_tokenizer.JumpToNextSemicolon();
return cssImportRule;
}
public CssRule CreateKeyframes(CssToken current)
{
CssToken token = _tokenizer.Get();
CssKeyframesRule cssKeyframesRule = new CssKeyframesRule(_parser);
cssKeyframesRule.Name = GetRuleName(ref token);
if (token.Type != CssTokenType.CurlyBracketOpen)
return SkipDeclarations(token);
FillKeyframeRules(cssKeyframesRule);
return cssKeyframesRule;
}
public CssRule CreateMedia(CssToken current)
{
CssToken token = _tokenizer.Get();
CssMediaRule cssMediaRule = new CssMediaRule(_parser);
FillMediaList(cssMediaRule.Media, ref token);
if (token.Type != CssTokenType.CurlyBracketOpen)
return SkipDeclarations(token);
FillRules(cssMediaRule);
return cssMediaRule;
}
public CssRule CreateNamespace(CssToken current)
{
CssToken token = _tokenizer.Get();
CssNamespaceRule cssNamespaceRule = new CssNamespaceRule(_parser);
cssNamespaceRule.Prefix = GetRuleName(ref token);
if (token.Type == CssTokenType.Url)
cssNamespaceRule.NamespaceUri = token.Data;
_tokenizer.JumpToNextSemicolon();
return cssNamespaceRule;
}
public CssRule CreatePage(CssToken current)
{
CssToken token = _tokenizer.Get();
CssPageRule cssPageRule = new CssPageRule(_parser);
cssPageRule.Selector = CreateSelector(ref token);
if (token.Type != CssTokenType.CurlyBracketOpen)
return SkipDeclarations(token);
FillDeclarations(cssPageRule.Style);
return cssPageRule;
}
public CssRule CreateStyle(CssToken current)
{
CssStyleRule cssStyleRule = new CssStyleRule(_parser);
cssStyleRule.Selector = CreateSelector(ref current);
FillDeclarations(cssStyleRule.Style);
if (cssStyleRule.Selector == null)
return null;
return cssStyleRule;
}
public CssRule CreateSupports(CssToken current)
{
CssToken token = _tokenizer.Get();
CssSupportsRule cssSupportsRule = new CssSupportsRule(_parser);
cssSupportsRule.Condition = CreateCondition(ref token);
if (token.Type != CssTokenType.CurlyBracketOpen)
return SkipDeclarations(token);
FillRules(cssSupportsRule);
return cssSupportsRule;
}
public CssRule CreateUnknown(CssToken current)
{
if (_parser.Options.IsIncludingUnknownRules) {
CssUnknownRule cssUnknownRule = new CssUnknownRule(current.Data, _parser);
_tokenizer.State = CssParseMode.Text;
cssUnknownRule.Prelude = _tokenizer.Get().Data;
_tokenizer.State = CssParseMode.Selector;
StringBuilder stringBuilder = Pool.NewStringBuilder();
CssToken cssToken = _tokenizer.Get();
stringBuilder.Append(cssToken.ToValue());
if (cssToken.Type == CssTokenType.CurlyBracketOpen) {
int num = 1;
do {
cssToken = _tokenizer.Get();
stringBuilder.Append(cssToken.ToValue());
switch (cssToken.Type) {
case CssTokenType.CurlyBracketOpen:
num++;
break;
case CssTokenType.CurlyBracketClose:
num--;
break;
case CssTokenType.Eof:
num = 0;
break;
}
} while (num != 0);
}
cssUnknownRule.Content = stringBuilder.ToPool();
_tokenizer.State = CssParseMode.Data;
return cssUnknownRule;
}
RaiseErrorOccurred(CssParseError.UnknownAtRule, current);
_tokenizer.SkipUnknownRule();
return null;
}
public CssValue CreateValue(ref CssToken token)
{
bool important = false;
return CreateValue(CssTokenType.CurlyBracketClose, ref token, out important);
}
public List<CssMedium> CreateMedia(ref CssToken token)
{
List<CssMedium> list = new List<CssMedium>();
while (token.Type != CssTokenType.Eof) {
CssMedium cssMedium = CreateMedium(ref token);
if (cssMedium == null || token.IsNot(CssTokenType.Comma, CssTokenType.Eof))
throw new DomException(DomError.Syntax);
list.Add(cssMedium);
token = _tokenizer.Get();
}
return list;
}
public ICondition CreateCondition(ref CssToken token)
{
ICondition condition = ExtractCondition(ref token);
if (condition != null) {
if (token.Data.Equals(Keywords.And, StringComparison.OrdinalIgnoreCase)) {
token = _tokenizer.Get();
List<ICondition> conditions = MultipleConditions(condition, Keywords.And, ref token);
return new AndCondition(conditions);
}
if (token.Data.Equals(Keywords.Or, StringComparison.OrdinalIgnoreCase)) {
token = _tokenizer.Get();
List<ICondition> conditions2 = MultipleConditions(condition, Keywords.Or, ref token);
return new OrCondition(conditions2);
}
}
return condition;
}
public CssKeyframeRule CreateKeyframeRule(CssToken token)
{
CssKeyframeRule cssKeyframeRule = new CssKeyframeRule(_parser);
cssKeyframeRule.Key = CreateKeyframeSelector(ref token);
if (cssKeyframeRule.Key == null) {
_tokenizer.JumpToEndOfDeclaration();
return null;
}
FillDeclarations(cssKeyframeRule.Style);
return cssKeyframeRule;
}
public KeyframeSelector CreateKeyframeSelector(ref CssToken token)
{
List<Percent> list = new List<Percent>();
while (token.Type != CssTokenType.Eof) {
if (list.Count > 0) {
if (token.Type == CssTokenType.CurlyBracketOpen)
break;
if (token.Type != CssTokenType.Comma)
return null;
token = _tokenizer.Get();
}
if (token.Type == CssTokenType.Percentage)
list.Add(new Percent(((CssUnitToken)token).Value));
else if (token.Type == CssTokenType.Ident && token.Data.Equals(Keywords.From)) {
list.Add(Percent.Zero);
} else {
if (token.Type != CssTokenType.Ident || !token.Data.Equals(Keywords.To))
return null;
list.Add(Percent.Hundred);
}
token = _tokenizer.Get();
}
return new KeyframeSelector(list);
}
public List<IDocumentFunction> CreateFunctions(ref CssToken token)
{
List<IDocumentFunction> list = new List<IDocumentFunction>();
do {
IDocumentFunction documentFunction = token.ToDocumentFunction();
if (documentFunction == null)
break;
list.Add(documentFunction);
token = _tokenizer.Get();
} while (token.Type == CssTokenType.Comma);
return list;
}
public void FillDeclarations(CssStyleDeclaration style)
{
CssToken token = _tokenizer.Get();
while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) {
CssProperty cssProperty = CreateDeclaration(ref token);
if (cssProperty != null && cssProperty.HasValue)
style.SetProperty(cssProperty);
}
}
public CssProperty CreateDeclarationWith(Func<string, CssProperty> createProperty, ref CssToken token)
{
CssProperty cssProperty = null;
if (token.Type == CssTokenType.Ident) {
string data = token.Data;
token = _tokenizer.Get();
if (token.Type != CssTokenType.Colon)
RaiseErrorOccurred(CssParseError.ColonMissing, token);
else {
cssProperty = ((_parser.Options.IsIncludingUnknownDeclarations || _parser.Options.IsToleratingInvalidValues) ? new CssUnknownProperty(data) : createProperty(data));
if (cssProperty == null)
RaiseErrorOccurred(CssParseError.UnknownDeclarationName, token);
bool important = false;
CssValue cssValue = CreateValue(CssTokenType.CurlyBracketClose, ref token, out important);
if (cssValue == null)
RaiseErrorOccurred(CssParseError.ValueMissing, token);
else if (cssProperty != null && cssProperty.TrySetValue(cssValue)) {
cssProperty.IsImportant = important;
}
}
_tokenizer.JumpToEndOfDeclaration();
token = _tokenizer.Get();
} else if (token.Type != CssTokenType.Eof) {
RaiseErrorOccurred(CssParseError.IdentExpected, token);
_tokenizer.JumpToEndOfDeclaration();
token = _tokenizer.Get();
}
if (token.Type == CssTokenType.Semicolon)
token = _tokenizer.Get();
return cssProperty;
}
public CssProperty CreateDeclaration(ref CssToken token)
{
return CreateDeclarationWith(Factory.Properties.Create, ref token);
}
public CssMedium CreateMedium(ref CssToken token)
{
CssMedium cssMedium = new CssMedium();
if (token.Type == CssTokenType.Ident) {
string data = token.Data;
if (data.Equals(Keywords.Not, StringComparison.OrdinalIgnoreCase)) {
cssMedium.IsInverse = true;
token = _tokenizer.Get();
} else if (data.Equals(Keywords.Only, StringComparison.OrdinalIgnoreCase)) {
cssMedium.IsExclusive = true;
token = _tokenizer.Get();
}
}
if (token.Type == CssTokenType.Ident) {
cssMedium.Type = token.Data;
token = _tokenizer.Get();
if (token.Type != CssTokenType.Ident || string.Compare(token.Data, Keywords.And, StringComparison.OrdinalIgnoreCase) != 0)
return cssMedium;
token = _tokenizer.Get();
}
do {
if (token.Type != CssTokenType.RoundBracketOpen)
return null;
token = _tokenizer.Get();
bool flag = TrySetConstraint(cssMedium, ref token);
if (token.Type != CssTokenType.RoundBracketClose)
return null;
token = _tokenizer.Get();
if (!flag)
return null;
if (token.Type != CssTokenType.Ident || string.Compare(token.Data, Keywords.And, StringComparison.OrdinalIgnoreCase) != 0)
break;
token = _tokenizer.Get();
} while (token.Type != CssTokenType.Eof);
return cssMedium;
}
private ICondition ExtractCondition(ref CssToken token)
{
ICondition condition = null;
if (token.Type == CssTokenType.RoundBracketOpen) {
token = _tokenizer.Get();
condition = CreateCondition(ref token);
if (condition != null)
condition = new GroupCondition(condition);
else if (token.Type == CssTokenType.Ident) {
condition = DeclarationCondition(ref token);
}
if (token.Type == CssTokenType.RoundBracketClose)
token = _tokenizer.Get();
} else if (token.Data.Equals(Keywords.Not, StringComparison.OrdinalIgnoreCase)) {
token = _tokenizer.Get();
condition = ExtractCondition(ref token);
if (condition != null)
condition = new NotCondition(condition);
}
return condition;
}
private ICondition DeclarationCondition(ref CssToken token)
{
string data = token.Data;
CssProperty cssProperty = Factory.Properties.Create(data);
if (cssProperty == null)
cssProperty = new CssUnknownProperty(data);
token = _tokenizer.Get();
if (token.Type == CssTokenType.Colon) {
bool important = false;
CssValue cssValue = CreateValue(CssTokenType.RoundBracketClose, ref token, out important);
cssProperty.IsImportant = important;
if (cssValue != null)
return new DeclarationCondition(cssProperty, cssValue);
}
return null;
}
private List<ICondition> MultipleConditions(ICondition condition, string connector, ref CssToken token)
{
List<ICondition> list = new List<ICondition>();
list.Add(condition);
while (token.Type != CssTokenType.Eof) {
condition = ExtractCondition(ref token);
if (condition == null)
break;
list.Add(condition);
if (!token.Data.Equals(connector, StringComparison.OrdinalIgnoreCase))
break;
token = _tokenizer.Get();
}
return list;
}
private void FillKeyframeRules(CssKeyframesRule parentRule)
{
CssToken token = _tokenizer.Get();
while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) {
CssKeyframeRule cssKeyframeRule = CreateKeyframeRule(token);
if (cssKeyframeRule != null)
parentRule.Rules.Add(cssKeyframeRule, parentRule.Owner, parentRule);
token = _tokenizer.Get();
}
}
private void FillFontFaceDeclarations(CssFontFaceRule rule)
{
CssToken token = _tokenizer.Get();
while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) {
CssProperty cssProperty = CreateDeclarationWith(CreateProperty, ref token);
if (cssProperty != null && cssProperty.HasValue)
rule.SetProperty(cssProperty);
}
}
private CssProperty CreateProperty(string propertyName)
{
if (propertyName.Equals(PropertyNames.Src, StringComparison.OrdinalIgnoreCase))
return new CssSrcProperty();
if (propertyName.Equals(PropertyNames.UnicodeRange, StringComparison.OrdinalIgnoreCase))
return new CssUnicodeRangeProperty();
return Factory.Properties.Create(propertyName);
}
private CssRule SkipDeclarations(CssToken token)
{
RaiseErrorOccurred(CssParseError.InvalidToken, token);
_tokenizer.SkipUnknownRule();
return null;
}
private void FillRules(CssGroupingRule group)
{
CssToken token = _tokenizer.Get();
while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketClose)) {
CssRule rule = CreateRule(token);
group.AddRule(rule);
token = _tokenizer.Get();
}
}
private ISelector CreateSelector(ref CssToken token)
{
CssSelectorConstructor cssSelectorConstructor = Pool.NewSelectorConstructor();
_tokenizer.State = CssParseMode.Selector;
CssToken token2 = token;
while (token.IsNot(CssTokenType.Eof, CssTokenType.CurlyBracketOpen, CssTokenType.CurlyBracketClose)) {
cssSelectorConstructor.Apply(token);
token = _tokenizer.Get();
}
if (!cssSelectorConstructor.IsValid)
RaiseErrorOccurred(CssParseError.InvalidSelector, token2);
_tokenizer.State = CssParseMode.Data;
return cssSelectorConstructor.ToPool();
}
private CssValue CreateValue(CssTokenType closing, ref CssToken token, out bool important)
{
CssValueBuilder cssValueBuilder = Pool.NewValueBuilder();
_tokenizer.State = CssParseMode.Value;
token = _tokenizer.Get();
while (token.Type != CssTokenType.Eof && !token.Is(CssTokenType.Semicolon, closing)) {
cssValueBuilder.Apply(token);
token = _tokenizer.Get();
}
important = cssValueBuilder.IsImportant;
_tokenizer.State = CssParseMode.Data;
return cssValueBuilder.ToPool();
}
private string GetRuleName(ref CssToken token)
{
string result = string.Empty;
if (token.Type == CssTokenType.Ident) {
result = token.Data;
token = _tokenizer.Get();
}
return result;
}
private void FillMediaList(MediaList list, ref CssToken token)
{
if (token.Type != CssTokenType.CurlyBracketOpen) {
while (token.Type != CssTokenType.Eof) {
CssMedium cssMedium = CreateMedium(ref token);
if (cssMedium != null)
list.Add(cssMedium);
if (token.Type != CssTokenType.Comma)
break;
token = _tokenizer.Get();
}
if (token.Type != CssTokenType.CurlyBracketOpen) {
while (token.Type != CssTokenType.Eof && token.Type != CssTokenType.Semicolon) {
token = _tokenizer.Get();
if (token.Type == CssTokenType.CurlyBracketOpen)
break;
}
list.Clear();
}
if (list.Length == 0)
list.Add(new CssMedium {
IsInverse = true,
Type = Keywords.All
});
}
}
private void RaiseErrorOccurred(CssParseError code, CssToken token)
{
_tokenizer.RaiseErrorOccurred(code, token.Position);
}
private bool TrySetConstraint(CssMedium medium, ref CssToken token)
{
if (token.Type != CssTokenType.Ident) {
_tokenizer.JumpToClosedArguments();
token = _tokenizer.Get();
return false;
}
CssValueBuilder cssValueBuilder = Pool.NewValueBuilder();
string data = token.Data;
token = _tokenizer.Get();
if (token.Type == CssTokenType.Colon) {
_tokenizer.State = CssParseMode.Value;
token = _tokenizer.Get();
while ((token.Type != CssTokenType.RoundBracketClose || !cssValueBuilder.IsReady) && token.Type != CssTokenType.Eof) {
cssValueBuilder.Apply(token);
token = _tokenizer.Get();
}
_tokenizer.State = CssParseMode.Data;
CssValue value = cssValueBuilder.ToPool();
MediaFeature mediaFeature = _parser.Options.IsToleratingInvalidConstraints ? new UnknownMediaFeature(data) : Factory.MediaFeatures.Create(data);
if (mediaFeature == null || !mediaFeature.TrySetValue(value))
return false;
medium.AddConstraint(mediaFeature);
return true;
}
return token.Type != CssTokenType.Eof;
}
}
}