private static bool TryParseSimpleSelector(CssStringStreamReader reader, out CssSimpleSelector selector)
        {
            selector = null;
            reader.SkipWhitespaceAndComments();

            switch (reader.CurrentChar)
            {
            case '#':
                reader.Advance();
                return
                    (reader.TryReadIdentifier(out var id) &&
                     TryParseSimpleSelector(reader, CssSimpleSelectorType.IdSelector, new CssQualifiedName(id), out selector));

            case '.':
                reader.Advance();
                return
                    (reader.TryReadIdentifier(out var className) &&
                     TryParseSimpleSelector(reader, CssSimpleSelectorType.ClassSelector, new CssQualifiedName(className), out selector));

            case '*':
                reader.Advance();
                return(TryParseUniversalSelector(reader, out selector));

            case '[':
            case ':':
                return(TryParseUniversalSelector(reader, out selector));

            default:
                return
                    (reader.TryReadQualifiedName(out var typeName) &&
                     TryParseSimpleSelector(reader, CssSimpleSelectorType.TypeSelector, typeName, out selector));
            }
        }
        private static bool TryParseCompoundSelector(CssStringStreamReader reader, out CssCompoundSelector selector)
        {
            var simpleSelectors = new List <CssSimpleSelector>();

            reader.SkipWhitespaceAndComments();
            while (!reader.IsEndOfStream)
            {
                if (!TryParseSimpleSelector(reader, out var simpleSelector))
                {
                    break;
                }
                simpleSelectors.Add(simpleSelector);
                if (IsCombinatorChar(reader.CurrentChar))
                {
                    break;
                }
            }
            if (simpleSelectors.Count == 0)
            {
                selector = null;
                return(false);
            }
            selector = new CssCompoundSelector(simpleSelectors.ToArray());
            return(true);
        }
        private static bool TryParseSimpleSelector(CssStringStreamReader reader, CssSimpleSelectorType simpleSelectorType, CssQualifiedName name, out CssSimpleSelector selector)
        {
            var attributeSelectors = ParseAttributeSelectors(reader);
            var pseudoClasses      = ParsePseudoClasses(reader);

            if (attributeSelectors == null || pseudoClasses == null)
            {
                selector = null;
                return(false);
            }
            selector = new CssSimpleSelector(simpleSelectorType, name, attributeSelectors, pseudoClasses);
            return(true);
        }
        private static CssPseudoClass[] ParsePseudoClasses(CssStringStreamReader reader)
        {
            var classes = new List <CssPseudoClass>();

            while (reader.CurrentChar == ':')
            {
                reader.Advance();
                if (!reader.TryReadIdentifier(out var className))
                {
                    return(null);
                }
                if (CssFunctionalPseudoClass.TryConvertToFunctionalPseudoClassType(className, out var functionalClassType))
                {
                    if (!reader.Read('(') ||
                        !TryParseSelectorList(reader, out var functionalSelectors) ||
                        !reader.Read(')'))
                    {
                        return(null);
                    }
                    classes.Add(new CssFunctionalPseudoClass(functionalSelectors, functionalClassType));
                    continue;
                }
                if (CssUserActionPseudoClass.TryConvertToUserActionPseudoClassType(className, out var userActionClassType))
                {
                    classes.Add(new CssUserActionPseudoClass(userActionClassType));
                    continue;
                }

                if (CssStructuralPseudoClass.TryConvertToStructuralPseudoClassType(className, out var structuralPseudoClassType))
                {
                    if (!reader.Read('(') ||
                        !TryParseSelectorList(reader, out var structuralSelectors) ||
                        !reader.Read(')'))
                    {
                        structuralSelectors = new CssSelectorList();
                    }

                    classes.Add(new CssStructuralPseudoClass(structuralSelectors, structuralPseudoClassType));
                    continue;
                }
                return(null);    // unknown / unhandled pseudo class
            }
            return(classes.ToArray());
        }
 public static bool TryParseSelectorList(CssStringStreamReader reader, out CssSelectorList selectors)
 {
     selectors = new CssSelectorList();
     while (!reader.IsEndOfStream)
     {
         if (!TryParseComplexSelector(reader, out var complex))
         {
             break;
         }
         selectors.Add(Flatten(complex));
         reader.SkipWhitespaceAndComments();
         if (reader.CurrentChar != ',')
         {
             break;
         }
         reader.Advance();
     }
     return(selectors.Count > 0);
 }
        private static CssAttributeSelector[] ParseAttributeSelectors(CssStringStreamReader reader)
        {
            var selectors = new List <CssAttributeSelector>();

            while (reader.CurrentChar == '[')
            {
                reader.Advance();
                if (!reader.TryReadIdentifier(out var name) || !reader.TryReadCssAttributeSelectorOperation(out var op))
                {
                    return(null);
                }

                var value     = string.Empty;
                var wasString = false;
                if (op != CssAttributeSelectorOperation.Has)
                {
                    if (!reader.TryReadIdentifierOrString(out value, out wasString))
                    {
                        return(null);
                    }
                }

                reader.SkipWhitespaceAndComments();

                var explicitIgnoreCase = reader.CurrentChar == 'i';
                if (explicitIgnoreCase)
                {
                    reader.Advance();
                    reader.SkipWhitespaceAndComments();
                }

                if (reader.CurrentChar != ']')
                {
                    return(null);
                }

                reader.Advance();
                var valueType = wasString ? CssAttributeSelectorValueType.String : CssAttributeSelectorValueType.Identifier;
                selectors.Add(new CssAttributeSelector(name, value, op, valueType, explicitIgnoreCase));
            }
            return(selectors.ToArray());
        }
        /// <summary>
        /// Tries to parse a collection of properties (name value pairs, e.g. 'fill:#000') of a style body
        /// </summary>
        public static bool TryParseStyleRuleBodyProperties(CssStringStreamReader reader, out Dictionary <string, CssStylePropertyValue> properties)
        {
            properties = new Dictionary <string, CssStylePropertyValue>();

            reader.SkipWhitespaceAndComments();

            var builder = new StringBuilder();

            while (!reader.IsEndOfStream && reader.CurrentChar != '}')
            {
                if (!reader.TryReadIdentifier(out var name))
                {
                    return(false);
                }

                reader.SkipWhitespaceAndComments();
                if (!reader.Read(':'))
                {
                    return(false);
                }
                reader.SkipWhitespaceAndComments();

                while (reader.CurrentChar != ';' && reader.CurrentChar != '}' && !reader.IsEndOfStream)
                {
                    builder.Append(reader.CurrentChar);
                    reader.Advance();
                }

                properties[name] = new CssStylePropertyValue(builder.ToString());
                builder.Clear();

                if (reader.CurrentChar == ';')
                {
                    reader.Read(';');
                }

                reader.SkipWhitespaceAndComments();
            }

            return(true);
        }
        public static bool TryParseRules(CssStringStreamReader reader, out CssStyleRulesList rules)
        {
            rules = new CssStyleRulesList();
            reader.SkipWhitespaceAndComments();
            while (!reader.IsEndOfStream)
            {
                if (!TryParseSelectorList(reader, out var selectors) ||
                    !reader.SkipWhitespaceAndComments() ||
                    !reader.Read('{') ||
                    !TryParseStyleRuleBodyProperties(reader, out var properties) ||
                    !reader.SkipWhitespaceAndComments() ||
                    !reader.Read('}'))
                {
                    return(false);
                }

                rules.Add(new CssStyleRule(selectors, properties));
                reader.SkipWhitespaceAndComments();
            }
            return(true);
        }
        private static bool TryParseComplexSelector(CssStringStreamReader reader, out CssComplexSelector selector)
        {
            var items = new List <CssComplexSelectorItem>();

            while (!reader.IsEndOfStream)
            {
                if (!TryParseCompoundSelector(reader, out var compound))
                {
                    if (items.Count > 0)
                    {
                        var last = items[items.Count - 1];
                        if (last.Combinator == CssCombinatorType.Descendant)
                        {
                            // remove the last items combinator
                            items[items.Count - 1] = new CssComplexSelectorItem(last.Selector, null);
                        }
                        else
                        {
                            items.Clear();
                        }
                    }
                    break;
                }

                var hasCombinator = reader.TryReadCombinatorType(out var combinator);

                items.Add(hasCombinator
                    ? new CssComplexSelectorItem(compound, combinator)
                    : new CssComplexSelectorItem(compound, null));

                if (!hasCombinator)
                {
                    break;
                }
            }
            selector = items.Count > 0 ? new CssComplexSelector(items.ToArray()) : null;
            return(selector != null);
        }
 private static bool TryParseUniversalSelector(CssStringStreamReader reader, out CssSimpleSelector selector)
 => TryParseSimpleSelector(reader, CssSimpleSelectorType.UniversalSelector, new CssQualifiedName("*"), out selector);
 internal CssParserException(CssStringStreamReader reader, string message)
     : base($"'{reader.Stream.Substring(Math.Max(0, reader.Position - 10))}', {message}")
 {
 }