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;
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()
{
ISelector sel = _nested.ToPool();
if (sel == null)
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()
{
ISelector sel = _nested.ToPool();
if (sel == null)
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()
{
ISelector sel = _nested.ToPool();
if (sel == null)
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 ChildFunctionState<T> : FunctionState where T : NthChildSelector, 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()
{
ISelector kind = (nested != null) ? nested.ToPool() : SimpleSelector.All;
if (!valid || (nested != null && !nested.valid))
return null;
T val = new T();
val.step = step;
val.offset = offset;
val.kind = kind;
return val;
}
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 abstract class NthChildSelector
{
public int step;
public int offset;
public ISelector kind;
public Priority Specifity => Priority.OneClass;
protected string Stringify(string name)
{
string text = step.ToString();
string text2 = string.Empty;
if (offset > 0)
text2 = "+" + offset.ToString();
else if (offset < 0) {
text2 = offset.ToString();
}
return string.Format(":{0}({1}n{2})", new object[3] {
name,
text,
text2
});
}
}
private sealed class NthFirstChildSelector : NthChildSelector, ISelector
{
public string Text => Stringify(PseudoClassNames.NthChild);
public bool Match(IElement element)
{
IElement parentElement = element.ParentElement;
if (parentElement == null)
return false;
int num = Math.Sign(step);
int num2 = 0;
for (int i = 0; i < parentElement.ChildNodes.Length; i++) {
IElement element2 = parentElement.ChildNodes[i] as IElement;
if (element2 != null && kind.Match(element2)) {
num2++;
if (element2 == element) {
int num3 = num2 - offset;
if (num3 != 0) {
if (Math.Sign(num3) == num)
return num3 % step == 0;
return false;
}
return true;
}
}
}
return false;
}
}
private sealed class NthFirstTypeSelector : NthChildSelector, ISelector
{
public string Text => Stringify(PseudoClassNames.NthOfType);
public bool Match(IElement element)
{
IElement parentElement = element.ParentElement;
if (parentElement == null)
return false;
int num = Math.Sign(step);
int num2 = 0;
for (int i = 0; i < parentElement.ChildNodes.Length; i++) {
IElement element2 = parentElement.ChildNodes[i] as IElement;
if (element2 != null && !(element2.NodeName != element.NodeName)) {
num2++;
if (element2 == element) {
int num3 = num2 - offset;
if (num3 != 0) {
if (Math.Sign(num3) == num)
return num3 % step == 0;
return false;
}
return true;
}
}
}
return false;
}
}
private sealed class NthFirstColumnSelector : NthChildSelector, ISelector
{
public string Text => Stringify(PseudoClassNames.NthColumn);
public bool Match(IElement element)
{
IElement parentElement = element.ParentElement;
if (parentElement == null)
return false;
int num = Math.Sign(step);
int num2 = 0;
for (int i = 0; i < parentElement.ChildNodes.Length; i++) {
IHtmlTableCellElement htmlTableCellElement = parentElement.ChildNodes[i] as IHtmlTableCellElement;
if (htmlTableCellElement != null) {
int columnSpan = htmlTableCellElement.ColumnSpan;
num2 += columnSpan;
if (htmlTableCellElement == element) {
int num3 = num2 - offset;
int num4 = 0;
while (num4 < columnSpan) {
if (num3 == 0 || (Math.Sign(num3) == num && num3 % step == 0))
return true;
num4++;
num3--;
}
return false;
}
}
}
return false;
}
}
private sealed class NthLastChildSelector : NthChildSelector, ISelector
{
public string Text => Stringify(PseudoClassNames.NthLastChild);
public bool Match(IElement element)
{
IElement parentElement = element.ParentElement;
if (parentElement == null)
return false;
int num = Math.Sign(step);
int num2 = 0;
for (int num3 = parentElement.ChildNodes.Length - 1; num3 >= 0; num3--) {
IElement element2 = parentElement.ChildNodes[num3] as IElement;
if (element2 != null && kind.Match(element2)) {
num2++;
if (element2 == element) {
int num4 = num2 - offset;
if (num4 != 0) {
if (Math.Sign(num4) == num)
return num4 % step == 0;
return false;
}
return true;
}
}
}
return false;
}
}
private sealed class NthLastTypeSelector : NthChildSelector, ISelector
{
public string Text => Stringify(PseudoClassNames.NthLastOfType);
public bool Match(IElement element)
{
IElement parentElement = element.ParentElement;
if (parentElement == null)
return false;
int num = Math.Sign(step);
int num2 = 0;
for (int num3 = parentElement.ChildNodes.Length - 1; num3 >= 0; num3--) {
IElement element2 = parentElement.ChildNodes[num3] as IElement;
if (element2 != null && !(element2.NodeName != element.NodeName)) {
num2++;
if (element2 == element) {
int num4 = num2 - offset;
if (num4 != 0) {
if (Math.Sign(num4) == num)
return num4 % step == 0;
return false;
}
return true;
}
}
}
return false;
}
}
private sealed class NthLastColumnSelector : NthChildSelector, ISelector
{
public string Text => Stringify(PseudoClassNames.NthLastColumn);
public bool Match(IElement element)
{
IElement parentElement = element.ParentElement;
if (parentElement == null)
return false;
int num = Math.Sign(step);
int num2 = 0;
for (int num3 = parentElement.ChildNodes.Length - 1; num3 >= 0; num3--) {
IHtmlTableCellElement htmlTableCellElement = parentElement.ChildNodes[num3] as IHtmlTableCellElement;
if (htmlTableCellElement != null) {
int columnSpan = htmlTableCellElement.ColumnSpan;
num2 += columnSpan;
if (htmlTableCellElement == element) {
int num4 = num2 - offset;
int num5 = 0;
while (num5 < columnSpan) {
if (num4 == 0 || (Math.Sign(num4) == num && num4 % step == 0))
return true;
num5++;
num4--;
}
return false;
}
}
}
return false;
}
}
private static readonly Dictionary<string, ISelector> pseudoClassSelectors;
private static readonly Dictionary<string, ISelector> pseudoElementSelectors;
private static readonly Dictionary<string, Func<FunctionState>> pseudoClassFunctions;
private State state;
private ISelector temp;
private ListSelector group;
private ComplexSelector complex;
private Stack<CssCombinator> combinators;
private string attrName;
private string attrValue;
private string attrOp;
private string attrNs;
private bool valid;
private bool ready;
public bool IsValid => valid;
public bool IsNested { get; set; }
public ISelector Result {
get {
if (!valid || !ready)
return null;
if (complex != null) {
complex.ConcludeSelector(temp);
temp = complex;
}
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;
}
}
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));
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));
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<NthFirstChildSelector>(true));
pseudoClassFunctions.Add(PseudoClassNames.NthLastChild, () => new ChildFunctionState<NthLastChildSelector>(true));
pseudoClassFunctions.Add(PseudoClassNames.NthOfType, () => new ChildFunctionState<NthFirstTypeSelector>(false));
pseudoClassFunctions.Add(PseudoClassNames.NthLastOfType, () => new ChildFunctionState<NthLastTypeSelector>(false));
pseudoClassFunctions.Add(PseudoClassNames.NthColumn, () => new ChildFunctionState<NthFirstColumnSelector>(false));
pseudoClassFunctions.Add(PseudoClassNames.NthLastColumn, () => new ChildFunctionState<NthLastColumnSelector>(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());
}
public CssSelectorConstructor()
{
combinators = new Stack<CssCombinator>();
Reset();
}
public void Apply(CssToken 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();
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();
if (cssCombinator == CssCombinator.Child && combinators.Peek() == CssCombinator.Child) {
combinators.Pop();
cssCombinator = CssCombinator.Descendent;
} else if (cssCombinator == CssCombinator.Namespace && combinators.Peek() == CssCombinator.Namespace) {
combinators.Pop();
cssCombinator = CssCombinator.Column;
}
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;
}
}
}