CssSelectorConstructor
Class for construction for CSS selectors as specified in
http://www.w3.org/html/wg/drafts/html/master/selectors.html.
using AngleSharp.Css;
using AngleSharp.Dom;
using AngleSharp.Dom.Css;
using AngleSharp.Dom.Html;
using AngleSharp.Extensions;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace AngleSharp.Parser.Css
{
[DebuggerStepThrough]
internal sealed class CssSelectorConstructor
{
private enum State
{
Data,
Attribute,
AttributeOperator,
AttributeValue,
AttributeEnd,
Class,
PseudoClass,
PseudoElement
}
private abstract class FunctionState
{
public bool Finished(CssToken token)
{
return OnToken(token);
}
public abstract ISelector Produce();
protected abstract bool OnToken(CssToken token);
}
private sealed class NotFunctionState : FunctionState
{
private readonly CssSelectorConstructor _nested;
public NotFunctionState()
{
_nested = Pool.NewSelectorConstructor();
_nested.IsNested = true;
}
protected override bool OnToken(CssToken token)
{
if (token.Type != CssTokenType.RoundBracketClose || _nested.state != 0) {
_nested.Apply(token);
return false;
}
return true;
}
public override ISelector Produce()
{
bool isValid = _nested.IsValid;
ISelector sel = _nested.ToPool();
if (!isValid)
return null;
return SimpleSelector.PseudoClass((IElement el) => !sel.Match(el), PseudoClassNames.Not + "(" + sel.Text + ")");
}
}
private sealed class HasFunctionState : FunctionState
{
private readonly CssSelectorConstructor _nested;
public HasFunctionState()
{
_nested = Pool.NewSelectorConstructor();
}
protected override bool OnToken(CssToken token)
{
if (token.Type != CssTokenType.RoundBracketClose || _nested.state != 0) {
_nested.Apply(token);
return false;
}
return true;
}
public override ISelector Produce()
{
bool isValid = _nested.IsValid;
ISelector sel = _nested.ToPool();
if (!isValid)
return null;
return SimpleSelector.PseudoClass((IElement el) => el.ChildNodes.QuerySelector(sel) != null, PseudoClassNames.Has + "(" + sel.Text + ")");
}
}
private sealed class MatchesFunctionState : FunctionState
{
private readonly CssSelectorConstructor _nested;
public MatchesFunctionState()
{
_nested = Pool.NewSelectorConstructor();
}
protected override bool OnToken(CssToken token)
{
if (token.Type != CssTokenType.RoundBracketClose || _nested.state != 0) {
_nested.Apply(token);
return false;
}
return true;
}
public override ISelector Produce()
{
bool isValid = _nested.IsValid;
ISelector sel = _nested.ToPool();
if (!isValid)
return null;
return SimpleSelector.PseudoClass((IElement el) => sel.Match(el), PseudoClassNames.Matches + "(" + sel.Text + ")");
}
}
private sealed class DirFunctionState : FunctionState
{
private bool valid;
private string value;
public DirFunctionState()
{
valid = true;
value = null;
}
protected override bool OnToken(CssToken token)
{
if (token.Type == CssTokenType.Ident)
value = token.Data;
else {
if (token.Type == CssTokenType.RoundBracketClose)
return true;
if (token.Type != CssTokenType.Whitespace)
valid = false;
}
return false;
}
public override ISelector Produce()
{
if (!valid || value == null)
return null;
string pseudoClass = PseudoClassNames.Dir + "(" + value + ")";
return SimpleSelector.PseudoClass(delegate(IElement el) {
if (el is IHtmlElement)
return ((IHtmlElement)el).Direction.Equals(value, StringComparison.OrdinalIgnoreCase);
return false;
}, pseudoClass);
}
}
private sealed class LangFunctionState : FunctionState
{
private bool valid;
private string value;
public LangFunctionState()
{
valid = true;
value = null;
}
protected override bool OnToken(CssToken token)
{
if (token.Type == CssTokenType.Ident)
value = token.Data;
else {
if (token.Type == CssTokenType.RoundBracketClose)
return true;
if (token.Type != CssTokenType.Whitespace)
valid = false;
}
return false;
}
public override ISelector Produce()
{
if (!valid || value == null)
return null;
string pseudoClass = PseudoClassNames.Lang + "(" + value + ")";
return SimpleSelector.PseudoClass(delegate(IElement el) {
if (el is IHtmlElement)
return ((IHtmlElement)el).Language.StartsWith(value, StringComparison.OrdinalIgnoreCase);
return false;
}, pseudoClass);
}
}
private sealed class ContainsFunctionState : FunctionState
{
private bool valid;
private string value;
public ContainsFunctionState()
{
valid = true;
value = null;
}
protected override bool OnToken(CssToken token)
{
if (token.Type == CssTokenType.Ident || token.Type == CssTokenType.String)
value = token.Data;
else {
if (token.Type == CssTokenType.RoundBracketClose)
return true;
if (token.Type != CssTokenType.Whitespace)
valid = false;
}
return false;
}
public override ISelector Produce()
{
if (!valid || value == null)
return null;
string pseudoClass = PseudoClassNames.Contains + "(" + value + ")";
return SimpleSelector.PseudoClass((IElement el) => el.TextContent.Contains(value), pseudoClass);
}
}
private sealed class HostContextFunctionState : FunctionState
{
private readonly CssSelectorConstructor _nested;
public HostContextFunctionState()
{
_nested = Pool.NewSelectorConstructor();
}
protected override bool OnToken(CssToken token)
{
if (token.Type != CssTokenType.RoundBracketClose || _nested.state != 0) {
_nested.Apply(token);
return false;
}
return true;
}
public override ISelector Produce()
{
bool isValid = _nested.IsValid;
ISelector sel = _nested.ToPool();
if (!isValid)
return null;
return SimpleSelector.PseudoClass(delegate(IElement el) {
IElement element = null;
if (el.Parent is IShadowRoot)
element = ((IShadowRoot)el.Parent).Host;
while (element != null) {
if (sel.Match(element))
return true;
element = element.ParentElement;
}
return false;
}, PseudoClassNames.HostContext + "(" + sel.Text + ")");
}
}
private sealed class ChildFunctionState<T> : FunctionState where T : ChildSelector, ISelector, new
{
private enum ParseState
{
Initial,
AfterInitialSign,
Offset,
BeforeOf,
AfterOf
}
private bool valid;
private int step;
private int offset;
private int sign;
private ParseState state;
private CssSelectorConstructor nested;
private bool allowOf;
public ChildFunctionState(bool withOptionalSelector = true)
{
allowOf = withOptionalSelector;
valid = true;
sign = 1;
state = ParseState.Initial;
}
public override ISelector Produce()
{
bool flag = !valid || (nested != null && !nested.IsValid);
ISelector kind = (nested != null) ? nested.ToPool() : SimpleSelector.All;
if (flag)
return null;
T val = new T();
return val.With(step, offset, kind);
}
protected override bool OnToken(CssToken token)
{
switch (state) {
case ParseState.Initial:
return OnInitial(token);
case ParseState.AfterInitialSign:
return OnAfterInitialSign(token);
case ParseState.Offset:
return OnOffset(token);
case ParseState.BeforeOf:
return OnBeforeOf(token);
default:
return OnAfter(token);
}
}
private bool OnAfterInitialSign(CssToken token)
{
if (token.Type == CssTokenType.Number)
return OnOffset(token);
if (token.Type == CssTokenType.Dimension) {
CssUnitToken cssUnitToken = (CssUnitToken)token;
valid = (valid && cssUnitToken.Unit.Equals("n", StringComparison.OrdinalIgnoreCase) && int.TryParse(token.Data, out step));
step *= sign;
sign = 1;
state = ParseState.Offset;
return false;
}
if (token.Type == CssTokenType.Ident && token.Data.Equals("n", StringComparison.OrdinalIgnoreCase)) {
step = sign;
sign = 1;
state = ParseState.Offset;
return false;
}
if (state == ParseState.Initial && token.Type == CssTokenType.Ident && token.Data.Equals("-n", StringComparison.OrdinalIgnoreCase)) {
step = -1;
state = ParseState.Offset;
return false;
}
valid = false;
return token.Type == CssTokenType.RoundBracketClose;
}
private bool OnAfter(CssToken token)
{
if (token.Type != CssTokenType.RoundBracketClose || nested.state != 0) {
nested.Apply(token);
return false;
}
return true;
}
private bool OnBeforeOf(CssToken token)
{
if (token.Type == CssTokenType.Whitespace)
return false;
if (token.Data.Equals("of", StringComparison.OrdinalIgnoreCase)) {
valid = allowOf;
state = ParseState.AfterOf;
nested = Pool.NewSelectorConstructor();
return false;
}
if (token.Type == CssTokenType.RoundBracketClose)
return true;
valid = false;
return false;
}
private bool OnOffset(CssToken token)
{
if (token.Type == CssTokenType.Whitespace)
return false;
if (token.Type == CssTokenType.Number) {
valid = (valid && ((CssNumberToken)token).IsInteger && int.TryParse(token.Data, out offset));
offset *= sign;
state = ParseState.BeforeOf;
return false;
}
return OnBeforeOf(token);
}
private bool OnInitial(CssToken token)
{
if (token.Type == CssTokenType.Whitespace)
return false;
if (token.Data.Equals("odd", StringComparison.OrdinalIgnoreCase)) {
state = ParseState.BeforeOf;
step = 2;
offset = 1;
return false;
}
if (token.Data.Equals("even", StringComparison.OrdinalIgnoreCase)) {
state = ParseState.BeforeOf;
step = 2;
offset = 0;
return false;
}
if (token.Type == CssTokenType.Delim && (token.Data == "+" || token.Data == "-")) {
sign = ((!(token.Data == "-")) ? 1 : (-1));
state = ParseState.AfterInitialSign;
return false;
}
return OnAfterInitialSign(token);
}
}
private static readonly Dictionary<string, ISelector> pseudoClassSelectors;
private static readonly Dictionary<string, ISelector> pseudoElementSelectors;
private static readonly Dictionary<string, Func<FunctionState>> pseudoClassFunctions;
private readonly Stack<CssCombinator> combinators;
private readonly List<CssToken> tokens;
private State state;
private ISelector temp;
private ListSelector group;
private ComplexSelector complex;
private string attrName;
private string attrValue;
private string attrOp;
private string attrNs;
private bool valid;
private bool ready;
public bool IsValid {
get {
if (valid)
return ready;
return false;
}
}
public bool IsNested { get; set; }
static CssSelectorConstructor()
{
pseudoClassSelectors = new Dictionary<string, ISelector>(StringComparer.OrdinalIgnoreCase);
pseudoElementSelectors = new Dictionary<string, ISelector>(StringComparer.OrdinalIgnoreCase);
pseudoClassFunctions = new Dictionary<string, Func<FunctionState>>(StringComparer.OrdinalIgnoreCase);
pseudoClassSelectors.Add(PseudoClassNames.Root, SimpleSelector.PseudoClass((IElement el) => el.Owner.DocumentElement == el, PseudoClassNames.Root));
pseudoClassSelectors.Add(PseudoClassNames.OnlyType, SimpleSelector.PseudoClass((IElement el) => el.IsOnlyOfType(), PseudoClassNames.OnlyType));
pseudoClassSelectors.Add(PseudoClassNames.FirstOfType, SimpleSelector.PseudoClass((IElement el) => el.IsFirstOfType(), PseudoClassNames.FirstOfType));
pseudoClassSelectors.Add(PseudoClassNames.LastOfType, SimpleSelector.PseudoClass((IElement el) => el.IsLastOfType(), PseudoClassNames.LastOfType));
pseudoClassSelectors.Add(PseudoClassNames.OnlyChild, SimpleSelector.PseudoClass((IElement el) => el.IsOnlyChild(), PseudoClassNames.OnlyChild));
pseudoClassSelectors.Add(PseudoClassNames.FirstChild, SimpleSelector.PseudoClass((IElement el) => el.IsFirstChild(), PseudoClassNames.FirstChild));
pseudoClassSelectors.Add(PseudoClassNames.LastChild, SimpleSelector.PseudoClass((IElement el) => el.IsLastChild(), PseudoClassNames.LastChild));
pseudoClassSelectors.Add(PseudoClassNames.Empty, SimpleSelector.PseudoClass(delegate(IElement el) {
if (el.ChildElementCount == 0)
return el.TextContent.Equals(string.Empty);
return false;
}, PseudoClassNames.Empty));
pseudoClassSelectors.Add(PseudoClassNames.AnyLink, SimpleSelector.PseudoClass(delegate(IElement el) {
if (!el.IsLink())
return el.IsVisited();
return true;
}, PseudoClassNames.AnyLink));
pseudoClassSelectors.Add(PseudoClassNames.Link, SimpleSelector.PseudoClass((IElement el) => el.IsLink(), PseudoClassNames.Link));
pseudoClassSelectors.Add(PseudoClassNames.Visited, SimpleSelector.PseudoClass((IElement el) => el.IsVisited(), PseudoClassNames.Visited));
pseudoClassSelectors.Add(PseudoClassNames.Active, SimpleSelector.PseudoClass((IElement el) => el.IsActive(), PseudoClassNames.Active));
pseudoClassSelectors.Add(PseudoClassNames.Hover, SimpleSelector.PseudoClass((IElement el) => el.IsHovered(), PseudoClassNames.Hover));
pseudoClassSelectors.Add(PseudoClassNames.Focus, SimpleSelector.PseudoClass((IElement el) => el.IsFocused, PseudoClassNames.Focus));
pseudoClassSelectors.Add(PseudoClassNames.Target, SimpleSelector.PseudoClass((IElement el) => el.IsTarget(), PseudoClassNames.Target));
pseudoClassSelectors.Add(PseudoClassNames.Enabled, SimpleSelector.PseudoClass((IElement el) => el.IsEnabled(), PseudoClassNames.Enabled));
pseudoClassSelectors.Add(PseudoClassNames.Disabled, SimpleSelector.PseudoClass((IElement el) => el.IsDisabled(), PseudoClassNames.Disabled));
pseudoClassSelectors.Add(PseudoClassNames.Default, SimpleSelector.PseudoClass((IElement el) => el.IsDefault(), PseudoClassNames.Default));
pseudoClassSelectors.Add(PseudoClassNames.Checked, SimpleSelector.PseudoClass((IElement el) => el.IsChecked(), PseudoClassNames.Checked));
pseudoClassSelectors.Add(PseudoClassNames.Indeterminate, SimpleSelector.PseudoClass((IElement el) => el.IsIndeterminate(), PseudoClassNames.Indeterminate));
pseudoClassSelectors.Add(PseudoClassNames.PlaceholderShown, SimpleSelector.PseudoClass((IElement el) => el.IsPlaceholderShown(), PseudoClassNames.PlaceholderShown));
pseudoClassSelectors.Add(PseudoClassNames.Unchecked, SimpleSelector.PseudoClass((IElement el) => el.IsUnchecked(), PseudoClassNames.Unchecked));
pseudoClassSelectors.Add(PseudoClassNames.Valid, SimpleSelector.PseudoClass((IElement el) => el.IsValid(), PseudoClassNames.Valid));
pseudoClassSelectors.Add(PseudoClassNames.Invalid, SimpleSelector.PseudoClass((IElement el) => el.IsInvalid(), PseudoClassNames.Invalid));
pseudoClassSelectors.Add(PseudoClassNames.Required, SimpleSelector.PseudoClass((IElement el) => el.IsRequired(), PseudoClassNames.Required));
pseudoClassSelectors.Add(PseudoClassNames.ReadOnly, SimpleSelector.PseudoClass((IElement el) => el.IsReadOnly(), PseudoClassNames.ReadOnly));
pseudoClassSelectors.Add(PseudoClassNames.ReadWrite, SimpleSelector.PseudoClass((IElement el) => el.IsEditable(), PseudoClassNames.ReadWrite));
pseudoClassSelectors.Add(PseudoClassNames.InRange, SimpleSelector.PseudoClass((IElement el) => el.IsInRange(), PseudoClassNames.InRange));
pseudoClassSelectors.Add(PseudoClassNames.OutOfRange, SimpleSelector.PseudoClass((IElement el) => el.IsOutOfRange(), PseudoClassNames.OutOfRange));
pseudoClassSelectors.Add(PseudoClassNames.Optional, SimpleSelector.PseudoClass((IElement el) => el.IsOptional(), PseudoClassNames.Optional));
pseudoClassSelectors.Add(PseudoClassNames.Shadow, SimpleSelector.PseudoClass((IElement el) => el.IsShadow(), PseudoClassNames.Shadow));
pseudoElementSelectors.Add(PseudoElementNames.Before, SimpleSelector.PseudoElement((IElement el) => el.IsPseudo("::" + PseudoElementNames.Before), PseudoElementNames.Before));
pseudoElementSelectors.Add(PseudoElementNames.After, SimpleSelector.PseudoElement((IElement el) => el.IsPseudo("::" + PseudoElementNames.After), PseudoElementNames.After));
pseudoElementSelectors.Add(PseudoElementNames.Selection, SimpleSelector.PseudoElement((IElement el) => false, PseudoElementNames.Selection));
pseudoElementSelectors.Add(PseudoElementNames.FirstLine, SimpleSelector.PseudoElement(delegate(IElement el) {
if (el.HasChildNodes)
return el.ChildNodes[0].NodeType == NodeType.Text;
return false;
}, PseudoElementNames.FirstLine));
pseudoElementSelectors.Add(PseudoElementNames.FirstLetter, SimpleSelector.PseudoElement(delegate(IElement el) {
if (el.HasChildNodes && el.ChildNodes[0].NodeType == NodeType.Text)
return el.ChildNodes[0].TextContent.Length > 0;
return false;
}, PseudoElementNames.FirstLetter));
pseudoElementSelectors.Add(PseudoElementNames.Content, SimpleSelector.PseudoElement((IElement el) => false, PseudoElementNames.Content));
pseudoClassSelectors.Add(PseudoElementNames.Before, pseudoElementSelectors[PseudoElementNames.Before]);
pseudoClassSelectors.Add(PseudoElementNames.After, pseudoElementSelectors[PseudoElementNames.After]);
pseudoClassSelectors.Add(PseudoElementNames.FirstLine, pseudoElementSelectors[PseudoElementNames.FirstLine]);
pseudoClassSelectors.Add(PseudoElementNames.FirstLetter, pseudoElementSelectors[PseudoElementNames.FirstLetter]);
pseudoClassFunctions.Add(PseudoClassNames.NthChild, () => new ChildFunctionState<FirstChildSelector>(true));
pseudoClassFunctions.Add(PseudoClassNames.NthLastChild, () => new ChildFunctionState<LastChildSelector>(true));
pseudoClassFunctions.Add(PseudoClassNames.NthOfType, () => new ChildFunctionState<FirstTypeSelector>(false));
pseudoClassFunctions.Add(PseudoClassNames.NthLastOfType, () => new ChildFunctionState<LastTypeSelector>(false));
pseudoClassFunctions.Add(PseudoClassNames.NthColumn, () => new ChildFunctionState<FirstColumnSelector>(false));
pseudoClassFunctions.Add(PseudoClassNames.NthLastColumn, () => new ChildFunctionState<LastColumnSelector>(false));
pseudoClassFunctions.Add(PseudoClassNames.Not, () => new NotFunctionState());
pseudoClassFunctions.Add(PseudoClassNames.Dir, () => new DirFunctionState());
pseudoClassFunctions.Add(PseudoClassNames.Lang, () => new LangFunctionState());
pseudoClassFunctions.Add(PseudoClassNames.Contains, () => new ContainsFunctionState());
pseudoClassFunctions.Add(PseudoClassNames.Has, () => new HasFunctionState());
pseudoClassFunctions.Add(PseudoClassNames.Matches, () => new MatchesFunctionState());
pseudoClassFunctions.Add(PseudoClassNames.HostContext, () => new HostContextFunctionState());
}
public CssSelectorConstructor()
{
combinators = new Stack<CssCombinator>();
tokens = new List<CssToken>();
Reset();
}
public ISelector GetResult()
{
if (!IsValid)
return new UnknownSelector(Serialize());
if (complex != null) {
complex.ConcludeSelector(temp);
temp = complex;
complex = null;
}
if (group == null || group.Length == 0)
return temp ?? SimpleSelector.All;
if (temp == null && group.Length == 1)
return group[0];
if (temp != null) {
group.Add(temp);
temp = null;
}
return group;
}
private string Serialize()
{
StringBuilder stringBuilder = Pool.NewStringBuilder();
foreach (CssToken token in tokens) {
stringBuilder.Append(token.ToValue());
}
return stringBuilder.ToPool();
}
public void Apply(CssToken token)
{
if (token.Type != CssTokenType.Comment) {
tokens.Add(token);
switch (state) {
case State.Data:
OnData(token);
break;
case State.Class:
OnClass(token);
break;
case State.Attribute:
OnAttribute(token);
break;
case State.AttributeOperator:
OnAttributeOperator(token);
break;
case State.AttributeValue:
OnAttributeValue(token);
break;
case State.AttributeEnd:
OnAttributeEnd(token);
break;
case State.PseudoClass:
OnPseudoClass(token);
break;
case State.PseudoElement:
OnPseudoElement(token);
break;
default:
valid = false;
break;
}
}
}
public CssSelectorConstructor Reset()
{
attrName = null;
attrValue = null;
attrNs = null;
attrOp = string.Empty;
state = State.Data;
combinators.Clear();
tokens.Clear();
temp = null;
group = null;
complex = null;
valid = true;
IsNested = false;
ready = true;
return this;
}
private void OnData(CssToken token)
{
switch (token.Type) {
case CssTokenType.SquareBracketOpen:
attrName = null;
attrValue = null;
attrOp = string.Empty;
attrNs = null;
state = State.Attribute;
ready = false;
break;
case CssTokenType.Colon:
state = State.PseudoClass;
ready = false;
break;
case CssTokenType.Hash:
Insert(SimpleSelector.Id(token.Data));
ready = true;
break;
case CssTokenType.Ident:
Insert(SimpleSelector.Type(token.Data));
ready = true;
break;
case CssTokenType.Whitespace:
Insert(CssCombinator.Descendent);
break;
case CssTokenType.Delim:
OnDelim(token);
break;
case CssTokenType.Comma:
InsertOr();
ready = false;
break;
default:
valid = false;
break;
}
}
private void OnAttribute(CssToken token)
{
if (token.Type != CssTokenType.Whitespace) {
if (token.Type == CssTokenType.Ident || token.Type == CssTokenType.String) {
state = State.AttributeOperator;
attrName = token.Data;
} else if (token.Type == CssTokenType.Delim && token.ToValue() == "|") {
state = State.Attribute;
attrNs = string.Empty;
} else if (token.Type == CssTokenType.Delim && token.ToValue() == "*") {
state = State.AttributeOperator;
attrName = token.ToValue();
} else {
state = State.Data;
valid = false;
}
}
}
private void OnAttributeOperator(CssToken token)
{
if (token.Type != CssTokenType.Whitespace) {
if (token.Type == CssTokenType.SquareBracketClose) {
state = State.AttributeValue;
OnAttributeEnd(token);
} else if (token.IsMatchToken() || token.Type == CssTokenType.Delim) {
state = State.AttributeValue;
attrOp = token.ToValue();
if (attrOp == "|") {
attrNs = attrName;
attrName = null;
attrOp = string.Empty;
state = State.Attribute;
}
} else {
state = State.AttributeEnd;
valid = false;
}
}
}
private void OnAttributeValue(CssToken token)
{
if (token.Type != CssTokenType.Whitespace) {
if (token.Type == CssTokenType.Ident || token.Type == CssTokenType.String || token.Type == CssTokenType.Number) {
state = State.AttributeEnd;
attrValue = token.Data;
} else {
state = State.Data;
valid = false;
}
}
}
private void OnAttributeEnd(CssToken token)
{
if (token.Type != CssTokenType.Whitespace) {
state = State.Data;
ready = true;
if (token.Type == CssTokenType.SquareBracketClose) {
SimpleSelector selector = CreateAttrSelector();
Insert(selector);
} else
valid = false;
}
}
private SimpleSelector CreateAttrSelector()
{
switch (attrOp) {
case "=":
return SimpleSelector.AttrMatch(attrName, attrValue, attrNs);
case "~=":
return SimpleSelector.AttrList(attrName, attrValue, attrNs);
case "|=":
return SimpleSelector.AttrHyphen(attrName, attrValue, attrNs);
case "^=":
return SimpleSelector.AttrBegins(attrName, attrValue, attrNs);
case "$=":
return SimpleSelector.AttrEnds(attrName, attrValue, attrNs);
case "*=":
return SimpleSelector.AttrContains(attrName, attrValue, attrNs);
case "!=":
return SimpleSelector.AttrNotMatch(attrName, attrValue, attrNs);
default:
return SimpleSelector.AttrAvailable(attrName, attrNs);
}
}
private void OnPseudoClass(CssToken token)
{
state = State.Data;
ready = true;
if (token.Type == CssTokenType.Colon)
state = State.PseudoElement;
else {
if (token.Type == CssTokenType.Function) {
ISelector pseudoFunction = GetPseudoFunction(token as CssFunctionToken);
if (pseudoFunction != null) {
Insert(pseudoFunction);
return;
}
} else if (token.Type == CssTokenType.Ident) {
ISelector pseudoSelector = GetPseudoSelector(token);
if (pseudoSelector != null) {
Insert(pseudoSelector);
return;
}
}
valid = false;
}
}
private void OnPseudoElement(CssToken token)
{
state = State.Data;
ready = true;
if (token.Type == CssTokenType.Ident) {
if (pseudoElementSelectors.TryGetValue(token.Data, out ISelector value)) {
if (IsNested)
valid = false;
Insert(value);
return;
}
Insert(SimpleSelector.PseudoElement((IElement el) => false, token.Data));
}
valid = false;
}
private void OnClass(CssToken token)
{
state = State.Data;
ready = true;
if (token.Type == CssTokenType.Ident)
Insert(SimpleSelector.Class(token.Data));
else
valid = false;
}
private void InsertOr()
{
if (temp != null) {
if (group == null)
group = new ListSelector();
if (complex != null) {
complex.ConcludeSelector(temp);
group.Add(complex);
complex = null;
} else
group.Add(temp);
temp = null;
}
}
private void Insert(ISelector selector)
{
if (temp != null) {
if (combinators.Count == 0) {
CompoundSelector compoundSelector = temp as CompoundSelector;
if (compoundSelector == null) {
compoundSelector = new CompoundSelector();
compoundSelector.Add(temp);
}
compoundSelector.Add(selector);
temp = compoundSelector;
} else {
if (complex == null)
complex = new ComplexSelector();
CssCombinator combinator = GetCombinator();
complex.AppendSelector(temp, combinator);
temp = selector;
}
} else {
combinators.Clear();
temp = selector;
}
}
private CssCombinator GetCombinator()
{
while (combinators.Count > 1 && combinators.Peek() == CssCombinator.Descendent) {
combinators.Pop();
}
if (combinators.Count > 1) {
CssCombinator cssCombinator = combinators.Pop();
CssCombinator cssCombinator2 = combinators.Pop();
if (cssCombinator == CssCombinator.Child && cssCombinator2 == CssCombinator.Child) {
if (combinators.Count == 0 || combinators.Peek() != CssCombinator.Child)
cssCombinator = CssCombinator.Descendent;
else if (combinators.Pop() == CssCombinator.Child) {
cssCombinator = CssCombinator.Deep;
}
} else if (cssCombinator == CssCombinator.Namespace && cssCombinator2 == CssCombinator.Namespace) {
cssCombinator = CssCombinator.Column;
} else {
combinators.Push(cssCombinator2);
}
while (combinators.Count > 0) {
valid = (combinators.Pop() == CssCombinator.Descendent && valid);
}
return cssCombinator;
}
return combinators.Pop();
}
private void Insert(CssCombinator cssCombinator)
{
combinators.Push(cssCombinator);
}
private void OnDelim(CssToken token)
{
switch (token.Data[0]) {
case ',':
InsertOr();
ready = false;
break;
case '>':
Insert(CssCombinator.Child);
ready = false;
break;
case '+':
Insert(CssCombinator.AdjacentSibling);
ready = false;
break;
case '~':
Insert(CssCombinator.Sibling);
ready = false;
break;
case '*':
Insert(SimpleSelector.All);
ready = true;
break;
case '.':
state = State.Class;
ready = false;
break;
case '|':
if (combinators.Count > 0 && combinators.Peek() == CssCombinator.Descendent)
Insert(SimpleSelector.Type(string.Empty));
Insert(CssCombinator.Namespace);
ready = false;
break;
default:
valid = false;
break;
}
}
private ISelector GetPseudoSelector(CssToken token)
{
if (pseudoClassSelectors.TryGetValue(token.Data, out ISelector value))
return value;
return null;
}
private ISelector GetPseudoFunction(CssFunctionToken arguments)
{
if (pseudoClassFunctions.TryGetValue(arguments.Data, out Func<FunctionState> value)) {
FunctionState functionState = value();
ready = false;
foreach (CssToken argument in arguments) {
if (functionState.Finished(argument)) {
ISelector result = functionState.Produce();
if (IsNested && functionState is NotFunctionState)
result = null;
ready = true;
return result;
}
}
}
return null;
}
}
}