AngleSharp by Florian Rappl

<PackageReference Include="AngleSharp" Version="0.8.9" />

 CssSelectorConstructor

sealed class 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; } } }