Click here to Skip to main content
15,884,298 members
Articles / Web Development / CSS3

AngleSharp

Rate me:
Please Sign up or sign in to vote.
5.00/5 (87 votes)
3 Jul 2013BSD28 min read 261.1K   4.3K   166  
Bringing the DOM to C# with a HTML5/CSS3 parser written in C#.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;

namespace AngleSharp.Css
{
    /// <summary>
    /// The CSS tokenizer.
    /// See http://dev.w3.org/csswg/css-syntax/#tokenization for more details.
    /// </summary>
    //[DebuggerStepThrough]
    class CssTokenizer
    {
        #region Members

        StringBuilder stringBuffer;
        SourceManager src;

        #endregion

        #region Events

        /// <summary>
        /// The event will be fired once an error has been detected.
        /// </summary>
        public event EventHandler<ParseErrorEventArgs> ErrorOccurred;

        #endregion

        #region ctor

        public CssTokenizer(SourceManager source)
        {
            stringBuffer = new StringBuilder();
            src = source;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the underlying stream.
        /// </summary>
        public SourceManager Stream
        {
            get { return src; }
        }

        /// <summary>
        /// Gets the iterator for the tokens.
        /// </summary>
        public IEnumerator<CssToken> Iterator
        {
            get { return Tokens.GetEnumerator(); }
        }

        /// <summary>
        /// Gets the token enumerable.
        /// </summary>
        public IEnumerable<CssToken> Tokens
        {
            get
            {
                CssToken token;

                while(true)
                {
                    token = Data(src.Current);

                    if (token == null)
                        yield break;

                    src.Advance();
                    yield return token;
                }
            }
        }

        #endregion

        #region States

        /// <summary>
        /// 4.4.1. Data state
        /// </summary>
        CssToken Data(Char current)
        {
            switch (current)
            {
                case Specification.LF:
                case Specification.CR:
                case Specification.TAB:
                case Specification.SPACE:
                    do { current = src.Next; }
                    while (Specification.IsSpaceCharacter(current));
                    src.Back();
                    return CssSpecialCharacter.Whitespace;

                case Specification.DQ:
                    return StringDQ(src.Next);

                case Specification.NUM:
                    return HashStart(src.Next);

                case Specification.DOLLAR:
                    current = src.Next;

                    if (current == Specification.EQ)
                        return CssMatchToken.Suffix;

                    return CssToken.Delim(src.Previous);

                case Specification.SQ:
                    return StringSQ(src.Next);

                case '(':
                    return CssBracketToken.OpenRound;

                case ')':
                    return CssBracketToken.CloseRound;

                case Specification.ASTERISK:
                    current = src.Next;

                    if (current == Specification.EQ)
                        return CssMatchToken.Substring;

                    return CssToken.Delim(src.Previous);

                case Specification.PLUS:
                    {
                        var c1 = src.Next;

                        if (c1 == Specification.EOF)
                        {
                            src.Back();
                        }
                        else
                        {
                            var c2 = src.Next;
                            src.Back(2);

                            if (Specification.IsDigit(c1) || (c1 == Specification.FS && Specification.IsDigit(c2)))
                                return NumberStart(current);
                        }
                        
                        return CssToken.Delim(current);
                    }

                case Specification.COMMA:
                    return CssSpecialCharacter.Comma;

                case Specification.FS:
                    {
                        var c = src.Next;

                        if (Specification.IsDigit(c))
                            return NumberStart(src.Previous);
                        
                        return CssToken.Delim(src.Previous);
                    }

                case Specification.DASH:
                    {
                        var c1 = src.Next;

                        if (c1 == Specification.EOF)
                        {
                            src.Back();
                        }
                        else
                        {
                            var c2 = src.Next;
                            src.Back(2);

                            if (Specification.IsDigit(c1) || (c1 == Specification.FS && Specification.IsDigit(c2)))
                                return NumberStart(current);
                            else if (Specification.IsNameStart(c1))
                                return IdentStart(current);
                            else if (c1 == Specification.RSOLIDUS && !Specification.IsLineBreak(c2) && c2 != Specification.EOF)
                                return IdentStart(current);
                            else if (c1 == Specification.DASH && c2 == Specification.GT)
                            {
                                src.Advance(2);
                                return CssCommentToken.Close;
                            }
                        }
                        
                        return CssToken.Delim(current);
                    }

                case Specification.SOLIDUS:
                    current = src.Next;

                    if (current == Specification.ASTERISK)
                        return Comment(src.Next);
                        
                    return CssToken.Delim(src.Previous);

                case Specification.RSOLIDUS:
                    current = src.Next;

                    if (Specification.IsLineBreak(current) || current == Specification.EOF)
                    {
                        RaiseErrorOccurred(current == Specification.EOF ? ErrorCode.EOF : ErrorCode.LineBreakUnexpected);
                        return CssToken.Delim(src.Previous);
                    }

                    return IdentStart(src.Previous);

                case Specification.COL:
                    return CssSpecialCharacter.Colon;

                case Specification.SC:
                    return CssSpecialCharacter.Semicolon;

                case Specification.LT:
                    current = src.Next;

                    if (current == Specification.EM)
                    {
                        current = src.Next;

                        if (current == Specification.DASH)
                        {
                            current = src.Next;

                            if (current == Specification.DASH)
                                return CssCommentToken.Open;

                            current = src.Previous;
                        }

                        current = src.Previous;
                    }

                    return CssToken.Delim(src.Previous);

                case Specification.AT:
                    return AtKeywordStart(src.Next);

                case '[':
                    return CssBracketToken.OpenSquare;

                case ']':
                    return CssBracketToken.CloseSquare;

                case Specification.CA:
                    current = src.Next;

                    if (current == Specification.EQ)
                        return CssMatchToken.Prefix;

                    return CssToken.Delim(src.Previous);

                case '{':
                    return CssBracketToken.OpenCurly;

                case '}':
                    return CssBracketToken.CloseCurly;

                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    return NumberStart(current);

                case 'U':
                case 'u':
                    current = src.Next;

                    if (current == Specification.PLUS)
                    {
                        current = src.Next;

                        if (Specification.IsHex(current) || current == Specification.QM)
                            return UnicodeRange(current);

                        current = src.Previous;
                    }

                    return IdentStart(src.Previous);

                case Specification.PIPE:
                    current = src.Next;

                    if (current == Specification.EQ)
                        return CssMatchToken.Dash;
                    else if (current == Specification.PIPE)
                        return CssToken.Column;

                    return CssToken.Delim(src.Previous);

                case Specification.TILDE:
                    current = src.Next;

                    if (current == Specification.EQ)
                        return CssMatchToken.Include;

                    return CssToken.Delim(src.Previous);

                case Specification.EOF:
                    return null;

                case Specification.EM:
                    current = src.Next;

                    if (current == Specification.EQ)
                        return CssMatchToken.Not;

                    return CssToken.Delim(src.Previous);

                default:
                    if (Specification.IsNameStart(current))
                        return IdentStart(current);

                    return CssToken.Delim(current);
            }
        }

        /// <summary>
        /// 4.4.2. Double quoted string state
        /// </summary>
        CssToken StringDQ(Char current)
        {
            while (true)
            {
                switch (current)
                {
                    case Specification.DQ:
                    case Specification.EOF:
                        return CssStringToken.Plain(FlushBuffer());

                    case Specification.FF:
                    case Specification.LF:
                        RaiseErrorOccurred(ErrorCode.LineBreakUnexpected);
                        src.Back();
                        return CssStringToken.Plain(FlushBuffer(), true);

                    case Specification.RSOLIDUS:
                        current = src.Next;

                        if (Specification.IsLineBreak(current))
                            stringBuffer.AppendLine();
                        else if (current != Specification.EOF)
                            stringBuffer.Append(ConsumeEscape(current));
                        else
                        {
                            RaiseErrorOccurred(ErrorCode.EOF);
                            src.Back();
                            return CssStringToken.Plain(FlushBuffer(), true);
                        }

                        break;

                    default:
                        stringBuffer.Append(current);
                        break;
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.3. Single quoted string state
        /// </summary>
        CssToken StringSQ(Char current)
        {
            while (true)
            {
                switch (current)
                {
                    case Specification.SQ:
                    case Specification.EOF:
                        return CssStringToken.Plain(FlushBuffer());

                    case Specification.FF:
                    case Specification.LF:
                        RaiseErrorOccurred(ErrorCode.LineBreakUnexpected);
                        src.Back();
                        return (CssStringToken.Plain(FlushBuffer(), true));

                    case Specification.RSOLIDUS:
                        current = src.Next;

                        if (Specification.IsLineBreak(current))
                            stringBuffer.AppendLine();
                        else if (current != Specification.EOF)
                            stringBuffer.Append(ConsumeEscape(current));
                        else
                        {
                            RaiseErrorOccurred(ErrorCode.EOF);
                            src.Back();
                            return(CssStringToken.Plain(FlushBuffer(), true));
                        }

                        break;

                    default:
                        stringBuffer.Append(current);
                        break;
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.4. Hash state
        /// </summary>
        CssToken HashStart(Char current)
        {
            if (Specification.IsNameStart(current))
            {
                stringBuffer.Append(current);
                return HashRest(src.Next);
            }
            else if (IsValidEscape(current))
            {
                current = src.Next;
                stringBuffer.Append(ConsumeEscape(current));
                return HashRest(src.Next);
            }
            else if (current == Specification.RSOLIDUS)
            {
                RaiseErrorOccurred(ErrorCode.InvalidCharacter);
                src.Back();
                return CssToken.Delim(Specification.NUM);
            }
            else
            {
                src.Back();
                return CssToken.Delim(Specification.NUM);
            }
        }

        /// <summary>
        /// 4.4.5. Hash-rest state
        /// </summary>
        CssToken HashRest(Char current)
        {
            while (true)
            {
                if (Specification.IsName(current))
                    stringBuffer.Append(current);
                else if (IsValidEscape(current))
                {
                    current = src.Next;
                    stringBuffer.Append(ConsumeEscape(current));
                }
                else if (current == Specification.RSOLIDUS)
                {
                    RaiseErrorOccurred(ErrorCode.InvalidCharacter);
                    src.Back();
                    return CssKeywordToken.Hash(FlushBuffer());
                }
                else
                {
                    src.Back();
                    return CssKeywordToken.Hash(FlushBuffer());
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.6. Comment state
        /// </summary>
        CssToken Comment(Char current)
        {
            while (true)
            {
                if (current == Specification.ASTERISK)
                {
                    current = src.Next;

                    if (current == Specification.SOLIDUS)
                        return Data(src.Next);
                }
                else if (current == Specification.EOF)
                {
                    RaiseErrorOccurred(ErrorCode.EOF);
                    return Data(current);
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.7. At-keyword state
        /// </summary>
        CssToken AtKeywordStart(Char current)
        {
            if (current == Specification.DASH)
            {
                current = src.Next;

                if (Specification.IsNameStart(current) || IsValidEscape(current))
                {
                    stringBuffer.Append(Specification.DASH);
                    return AtKeywordRest(current);
                }

                src.Back(2);
                return CssToken.Delim(Specification.AT);
            }
            else if (Specification.IsNameStart(current))
            {
                stringBuffer.Append(current);
                return AtKeywordRest(src.Next);
            }
            else if (IsValidEscape(current))
            {
                current = src.Next;
                stringBuffer.Append(ConsumeEscape(current));
                return AtKeywordRest(src.Next);
            }
            else
            {
                src.Back();
                return CssToken.Delim(Specification.AT);
            }
        }

        /// <summary>
        /// 4.4.8. At-keyword-rest state
        /// </summary>
        CssToken AtKeywordRest(Char current)
        {
            while (true)
            {
                if (Specification.IsName(current))
                    stringBuffer.Append(current);
                else if (IsValidEscape(current))
                {
                    current = src.Next;
                    stringBuffer.Append(ConsumeEscape(current));
                }
                else
                {
                    src.Back();
                    return CssKeywordToken.At(FlushBuffer());
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.9. Ident state
        /// </summary>
        CssToken IdentStart(Char current)
        {
            if (current == Specification.DASH)
            {
                current = src.Next;

                if (Specification.IsNameStart(current) || IsValidEscape(current))
                {
                    stringBuffer.Append(Specification.DASH);
                    return IdentRest(current);
                }

                src.Back();
                return CssToken.Delim(Specification.DASH);
            }
            else if (Specification.IsNameStart(current))
            {
                stringBuffer.Append(current);
                return IdentRest(src.Next);
            }
            else if (current == Specification.RSOLIDUS)
            {
                if (IsValidEscape(current))
                {
                    current = src.Next;
                    stringBuffer.Append(ConsumeEscape(current));
                    return IdentRest(src.Next);
                }
            }

            return Data(current);
        }

        /// <summary>
        /// 4.4.10. Ident-rest state
        /// </summary>
        CssToken IdentRest(Char current)
        {
            while (true)
            {
                if (Specification.IsName(current))
                    stringBuffer.Append(current);
                else if (IsValidEscape(current))
                {
                    current = src.Next;
                    stringBuffer.Append(ConsumeEscape(current));
                }
                else if (current == '(')
                {
                    if (stringBuffer.ToString().Equals("url", StringComparison.OrdinalIgnoreCase))
                    {
                        stringBuffer.Clear();
                        return UrlStart(src.Next);
                    }

                    return CssKeywordToken.Function(FlushBuffer());
                }
                //false could be replaced with a transform whitespace flag, which is set to true if in SVG transform mode.
                //else if (false && Specification.IsSpaceCharacter(current))
                //    InstantSwitch(TransformFunctionWhitespace);
                else
                {
                    src.Back();
                    return CssKeywordToken.Ident(FlushBuffer());
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.11. Transform-function-whitespace state
        /// </summary>
        CssToken TransformFunctionWhitespace(Char current)
        {
            while (true)
            {
                current = src.Next;

                if (current == '(')
                {
                    src.Back();
                    return CssKeywordToken.Function(FlushBuffer());
                }
                else if (!Specification.IsSpaceCharacter(current))
                {
                    src.Back(2);
                    return CssKeywordToken.Ident(FlushBuffer());
                }
            }
        }

        /// <summary>
        /// 4.4.12. Number state
        /// </summary>
        CssToken NumberStart(Char current)
        {
            while (true)
            {
                if (current == Specification.PLUS || current == Specification.DASH)
                {
                    stringBuffer.Append(current);
                    current = src.Next;

                    if (current == Specification.FS)
                    {
                        stringBuffer.Append(current);
                        stringBuffer.Append(src.Next);
                        return NumberFraction(src.Next);
                    }

                    stringBuffer.Append(current);
                    return NumberRest(src.Next);
                }
                else if (current == Specification.FS)
                {
                    stringBuffer.Append(current);
                    stringBuffer.Append(src.Next);
                    return NumberFraction(src.Next);
                }
                else if (Specification.IsDigit(current))
                {
                    stringBuffer.Append(current);
                    return NumberRest(src.Next);
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.13. Number-rest state
        /// </summary>
        CssToken NumberRest(Char current)
        {
            while (true)
            {
                if (Specification.IsDigit(current))
                    stringBuffer.Append(current);
                else if (Specification.IsNameStart(current))
                {
                    var number = FlushBuffer();
                    stringBuffer.Append(current);
                    return Dimension(src.Next, number);
                }
                else if (IsValidEscape(current))
                {
                    current = src.Next;
                    var number = FlushBuffer();
                    stringBuffer.Append(ConsumeEscape(current));
                    return Dimension(src.Next, number);
                }
                else
                    break;

                current = src.Next;
            }

            switch (current)
            {
                case Specification.FS:
                    current = src.Next;

                    if (Specification.IsDigit(current))
                    {
                        stringBuffer.Append(Specification.FS).Append(current);
                        return NumberFraction(src.Next);
                    }

                    src.Back();
                    return CssToken.Number(FlushBuffer());

                case '%':
                    return CssUnitToken.Percentage(FlushBuffer());

                case 'e':
                case 'E':
                    return NumberExponential(current);

                case Specification.DASH:
                    return NumberDash(current);

                default:
                    src.Back();
                    return CssToken.Number(FlushBuffer());
            }
        }

        /// <summary>
        /// 4.4.14. Number-fraction state
        /// </summary>
        CssToken NumberFraction(Char current)
        {
            while (true)
            {
                if (Specification.IsDigit(current))
                    stringBuffer.Append(current);
                else if (Specification.IsNameStart(current))
                {
                    var number = FlushBuffer();
                    stringBuffer.Append(current);
                    return Dimension(src.Next, number);
                }
                else if (IsValidEscape(current))
                {
                    current = src.Next;
                    var number = FlushBuffer();
                    stringBuffer.Append(ConsumeEscape(current));
                    return Dimension(src.Next, number);
                }
                else
                    break;

                current = src.Next;
            }

            switch (current)
            {
                case 'e':
                case 'E':
                    return NumberExponential(current);

                case '%':
                    return CssUnitToken.Percentage(FlushBuffer());

                case Specification.DASH:
                    return NumberDash(current);

                default:
                    src.Back();
                    return CssToken.Number(FlushBuffer());
            }
        }

        /// <summary>
        /// 4.4.15. Dimension state
        /// </summary>
        CssToken Dimension(Char current, String number)
        {
            while (true)
            {
                if (Specification.IsName(current))
                    stringBuffer.Append(current);
                else if (IsValidEscape(current))
                {
                    current = src.Next;
                    stringBuffer.Append(ConsumeEscape(current));
                }
                else
                {
                    src.Back();
                    return CssUnitToken.Dimension(number, FlushBuffer());
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.16. SciNotation state
        /// </summary>
        CssToken SciNotation(Char current)
        {
            while (true)
            {
                if (Specification.IsDigit(current))
                    stringBuffer.Append(current);
                else
                {
                    src.Back();
                    return CssToken.Number(FlushBuffer());
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.17. URL state
        /// </summary>
        CssToken UrlStart(Char current)
        {
            while (Specification.IsSpaceCharacter(current))
                current = src.Next;

            switch (current)
            {
                case Specification.EOF:
                    RaiseErrorOccurred(ErrorCode.EOF);
                    return CssStringToken.Url(String.Empty, true);

                case Specification.DQ:
                    return UrlDQ(src.Next);

                case Specification.SQ:
                    return UrlSQ(src.Next);

                case ')':
                    return CssStringToken.Url(String.Empty, false);

                default:
                    return UrlUQ(current);
            }
        }

        /// <summary>
        /// 4.4.18. URL-double-quoted state
        /// </summary>
        CssToken UrlDQ(Char current)
        {
            while (true)
            {
                if (Specification.IsLineBreak(current))
                {
                    RaiseErrorOccurred(ErrorCode.LineBreakUnexpected);
                    return UrlBad(src.Next);
                }
                else if (Specification.EOF == current)
                {
                    return CssStringToken.Url(FlushBuffer());
                }
                else if (current == Specification.DQ)
                {
                    return UrlEnd(src.Next);
                }
                else if (current == Specification.RSOLIDUS)
                {
                    current = src.Next;

                    if (current == Specification.EOF)
                    {
                        src.Back(2);
                        RaiseErrorOccurred(ErrorCode.EOF);
                        return CssStringToken.Url(FlushBuffer(), true);
                    }
                    else if (Specification.IsLineBreak(current))
                        stringBuffer.AppendLine();
                    else
                        stringBuffer.Append(ConsumeEscape(current));
                }
                else
                    stringBuffer.Append(current);

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.19. URL-single-quoted state
        /// </summary>
        CssToken UrlSQ(Char current)
        {
            while (true)
            {
                if (Specification.IsLineBreak(current))
                {
                    RaiseErrorOccurred(ErrorCode.LineBreakUnexpected);
                    return UrlBad(src.Next);
                }
                else if (Specification.EOF == current)
                {
                    return CssStringToken.Url(FlushBuffer());
                }
                else if (current == Specification.SQ)
                {
                    return UrlEnd(src.Next);
                }
                else if (current == Specification.RSOLIDUS)
                {
                    current = src.Next;

                    if (current == Specification.EOF)
                    {
                        src.Back(2);
                        RaiseErrorOccurred(ErrorCode.EOF);
                        return CssStringToken.Url(FlushBuffer(), true);
                    }
                    else if (Specification.IsLineBreak(current))
                        stringBuffer.AppendLine();
                    else
                        stringBuffer.Append(ConsumeEscape(current));
                }
                else
                    stringBuffer.Append(current);

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.21. URL-unquoted state
        /// </summary>
        CssToken UrlUQ(Char current)
        {
            while (true)
            {
                if (Specification.IsSpaceCharacter(current))
                {
                    return UrlEnd(src.Next);
                }
                else if (current == ')' || current == Specification.EOF)
                {
                    return CssStringToken.Url(FlushBuffer());
                }
                else if (current == Specification.DQ || current == Specification.SQ || current == '(' || Specification.IsNonPrintable(current))
                {
                    RaiseErrorOccurred(ErrorCode.InvalidCharacter);
                    return UrlBad(src.Next);
                }
                else if (current == Specification.RSOLIDUS)
                {
                    if (IsValidEscape(current))
                    {
                        current = src.Next;
                        stringBuffer.Append(ConsumeEscape(current));
                    }
                    else
                    {
                        RaiseErrorOccurred(ErrorCode.InvalidCharacter);
                        return UrlBad(src.Next);
                    }
                }
                else
                    stringBuffer.Append(current);

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.20. URL-end state
        /// </summary>
        CssToken UrlEnd(Char current)
        {
            while (true)
            {
                if (current == ')')
                    return CssStringToken.Url(FlushBuffer());
                else if (!Specification.IsSpaceCharacter(current))
                {
                    RaiseErrorOccurred(ErrorCode.InvalidCharacter);
                    return UrlBad(current);
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.22. Bad URL state
        /// </summary>
        CssToken UrlBad(Char current)
        {
            while (true)
            {
                if (current == Specification.EOF)
                {
                    RaiseErrorOccurred(ErrorCode.EOF);
                    return CssStringToken.Url(FlushBuffer(), true);
                }
                else if (current == ')')
                {
                    return CssStringToken.Url(FlushBuffer(), true);
                }
                else if (IsValidEscape(current))
                {
                    current = src.Next;
                    stringBuffer.Append(ConsumeEscape(current));
                }

                current = src.Next;
            }
        }

        /// <summary>
        /// 4.4.23. Unicode-range State
        /// </summary>
        CssToken UnicodeRange(Char current)
        {
            for (int i = 0; i < 6; i++)
            {
                if (!Specification.IsHex(current))
                    break;

                stringBuffer.Append(current);
                current = src.Next;
            }

            if (stringBuffer.Length != 6)
            {
                for (int i = 0; i < 6 - stringBuffer.Length; i++)
                {
                    if (current != Specification.QM)
                    {
                        current = src.Previous;
                        break;
                    }

                    stringBuffer.Append(current);
                    current = src.Next;
                }

                var range = FlushBuffer();
                var start = range.Replace(Specification.QM, '0');
                var end = range.Replace(Specification.QM, 'F');
                return CssToken.Range(start, end);
            }
            else if (current == Specification.DASH)
            {
                current = src.Next;

                if (Specification.IsHex(current))
                {
                    var start = stringBuffer.ToString();
                    stringBuffer.Clear();

                    for (int i = 0; i < 6; i++)
                    {
                        if (!Specification.IsHex(current))
                        {
                            current = src.Previous;
                            break;
                        }

                        stringBuffer.Append(current);
                        current = src.Next;
                    }

                    var end = FlushBuffer();
                    return CssToken.Range(start, end);
                }
                else
                {
                    src.Back(2);
                    return CssToken.Range(FlushBuffer(), null);
                }
            }
            else
            {
                src.Back();
                return CssToken.Range(FlushBuffer(), null);
            }
        }

        #endregion

        #region Helpers

        String FlushBuffer()
        {
            var tmp = stringBuffer.ToString();
            stringBuffer.Clear();
            return tmp;
        }

        /// <summary>
        /// Substate of several Number states.
        /// </summary>
        CssToken NumberExponential(Char current)
        {
            current = src.Next;

            if (Specification.IsDigit(current))
            {
                stringBuffer.Append('e').Append(current);
                return SciNotation(src.Next);
            }
            else if (current == Specification.PLUS || current == Specification.DASH)
            {
                var op = current;
                current = src.Next;

                if (Specification.IsDigit(current))
                {
                    stringBuffer.Append('e').Append(op).Append(current);
                    return SciNotation(src.Next);
                }

                src.Back();
            }

            current = src.Previous;
            var number = FlushBuffer();
            stringBuffer.Append(current);
            return Dimension(src.Next, number);
        }

        /// <summary>
        /// Substate of several Number states.
        /// </summary>
        CssToken NumberDash(Char current)
        {
            current = src.Next;

            if (Specification.IsNameStart(current))
            {
                var number = FlushBuffer();
                stringBuffer.Append(Specification.DASH).Append(current);
                return Dimension(src.Next, number);
            }
            else if (IsValidEscape(current))
            {
                current = src.Next;
                var number = FlushBuffer();
                stringBuffer.Append(Specification.DASH).Append(ConsumeEscape(current));
                return Dimension(src.Next, number);
            }
            else
            {
                src.Back(2);
                return CssToken.Number(FlushBuffer());
            }
        }

        /// <summary>
        /// Consumes an escaped character AFTER the solidus has already been
        /// consumed.
        /// </summary>
        /// <returns>The escaped character.</returns>
        String ConsumeEscape(Char current)
        {
            if (Specification.IsHex(current))
            {
                var escape = new List<Char>();

                for (int i = 0; i < 6; i++)
                {
                    escape.Add(current);
                    current = src.Next;

                    if (!Specification.IsHex(current))
                        break;
                }

                current = src.Previous;
                var code = Int32.Parse(new String(escape.ToArray()), NumberStyles.HexNumber);
                return Char.ConvertFromUtf32(code);
            }

            return current.ToString();
        }

        /// <summary>
        /// Checks if the current position is the beginning of a valid escape sequence.
        /// </summary>
        /// <returns>The result of the check.</returns>
        Boolean IsValidEscape(Char current)
        {
            if (current != Specification.RSOLIDUS)
                return false;

            current = src.Next;
            src.Back();

            if (current == Specification.EOF)
                return false;
            else if (Specification.IsLineBreak(current))
                return false;

            return true;
        }

        #endregion

        #region Event-Helpers

        /// <summary>
        /// Fires an error occurred event.
        /// </summary>
        /// <param name="code">The associated error code.</param>
        void RaiseErrorOccurred(ErrorCode code)
        {
            if (ErrorOccurred != null)
            {
                var pck = new ParseErrorEventArgs((int)code, Errors.GetError(code));
                pck.Line = src.Line;
                pck.Column = src.Column;
                ErrorOccurred(this, pck);
            }
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Chief Technology Officer
Germany Germany
Florian lives in Munich, Germany. He started his programming career with Perl. After programming C/C++ for some years he discovered his favorite programming language C#. He did work at Siemens as a programmer until he decided to study Physics.

During his studies he worked as an IT consultant for various companies. After graduating with a PhD in theoretical particle Physics he is working as a senior technical consultant in the field of home automation and IoT.

Florian has been giving lectures in C#, HTML5 with CSS3 and JavaScript, software design, and other topics. He is regularly giving talks at user groups, conferences, and companies. He is actively contributing to open-source projects. Florian is the maintainer of AngleSharp, a completely managed browser engine.

Comments and Discussions