/// <summary>
        /// Gets the HTML class name for the specified syntax trivia.
        /// </summary>
        /// <param name="trivia">The <see cref="SyntaxTrivia"/> object for which a <c>HtmlClassName</c> enumeration
        /// value is returned.</param>
        /// <returns>A value of the <see cref="HtmlClassName"/> enumeration.</returns>
        protected virtual HtmlClassName GetHtmlClass(SyntaxTrivia trivia)
        {
            HtmlClassName className = HtmlClassName.Unknown;

            switch (trivia.Kind())
            {
            case SyntaxKind.SingleLineCommentTrivia:
            case SyntaxKind.MultiLineCommentTrivia:
            case SyntaxKind.DocumentationCommentExteriorTrivia:
                className = HtmlClassName.Comment;
                break;

            case SyntaxKind.DisabledTextTrivia:
                className = HtmlClassName.DisabledText;
                break;

            case SyntaxKind.PreprocessingMessageTrivia:
                className = HtmlClassName.None;
                break;

            default:
                className = HtmlClassName.None;
                break;
            }

            return(className);
        }
        /// <summary>
        /// Gets the HTML class name for the specified token or trivia wrapped by the <see cref="SyntaxTreeElement"/> instance.
        /// </summary>
        /// <remarks>
        /// The HTML class name is used to create a class attribute for a "span" tag that wraps the trivia or token if the
        /// value is not <c>HtmlClassName.None</c> or <c>HtmlClassName.Unknown</c>.
        /// </remarks>
        /// <param name="treeElement">The tree element.</param>
        /// <returns>A value of the <see cref="HtmlClassName"/> enumeration.</returns>
        /// <exception cref="ArgumentNullException">if <paramref name="treeElement"/> is <c>null</c>.</exception>
        protected virtual HtmlClassName GetHtmlClass(SyntaxTreeElement treeElement)
        {
            if (treeElement == null)
            {
                throw new ArgumentNullException(nameof(treeElement), "SyntaxTree element cannot be null.");
            }

            HtmlClassName className = HtmlClassName.Unknown;

            switch (treeElement.SyntaxElementCategory)
            {
            case SyntaxElementCategory.Token:
                className = GetHtmlClass(treeElement.Token);
                break;

            case SyntaxElementCategory.Trivia:
                className = GetHtmlClass(treeElement.Trivia);
                break;

            default:
                break;
            }

            return(className);
        }
        /// <summary>
        /// Gets the HTML class name for the specified <see cref="SyntaxToken"/> whose <c>Kind</c> is the
        /// <c>SyntaxKind.Identifier</c> enumeration value.
        /// </summary>
        /// <remarks>
        /// This method is called by <see cref="GetHtmlClass(SyntaxToken)"/> when the class name for a token cannot be determined
        /// by the the syntax (<c>SyntaxKind</c> from the syntax tree) alone. This method uses the semantic model to recover
        /// an <see cref="ISymbol"/> instance and uses that to determine if the <see cref="HtmlClassName.Identifier"/>
        /// should be used for this token.
        /// </remarks>
        /// <param name="token">The identifier token whose class name is returned.</param>
        /// <returns>Returns the class name for the token. This is a value of the <see cref="HtmlClassName"/> enumeration and is
        /// used if the token is wrapped in a "span" tag. If <c>HtmlClassName.None</c> is returned, the identifier is not
        /// wrapped in a "span" tag. The only other values returned are <c>HtmlClassName.Identifier</c> or
        /// <c>HtmlClassName.UnknownIdentifier.</c></returns>
        /// <seealso cref="IsIdentifier"/>
        protected virtual HtmlClassName GetIdentifierTokenHtmlClass(SyntaxToken token)
        {
            HtmlClassName className = HtmlClassName.Unknown;

            ISymbol symbol = token.GetSymbol(SemanticModel);

            TraceWrite("For identifier token [{0}], symbol is [{1}] ",
                       token.Text,
                       (symbol == null) ? "Null" : symbol.Kind.ToString());

            if (symbol != null)
            {
                switch (symbol.Kind)
                {
                // Named types are highlighted
                case SymbolKind.NamedType:
                    className = HtmlClassName.Identifier;
                    break;

                // Methods are only highlighted when specified in attribute statement, for example
                // <c>[assembly: AssemblyTitle("This Title")]</c>
                case SymbolKind.Method:
                    className = token.IsInNode(SyntaxKind.Attribute) ? HtmlClassName.Identifier : HtmlClassName.None;
                    break;

                // The following known SymbolKind values for identifiers are never highlighted
                case SymbolKind.Namespace:
                case SymbolKind.Parameter:
                case SymbolKind.Local:
                case SymbolKind.Property:
                case SymbolKind.Field:
                    className = HtmlClassName.None;
                    break;

                default:
                    break;
                }
            }

            if (className == HtmlClassName.Unknown)
            {
                if (InferIdentifier)
                {
                    className = IsIdentifier(token) ? HtmlClassName.InferredIdentifier : HtmlClassName.None;
                }
                else
                {
                    className = HtmlClassName.UnknownIdentifier;
                }
            }

            return(className);
        }
        /// <summary>
        /// Gets the HTML class name for the specified syntax token.
        /// </summary>
        /// <param name="token">The <see cref="SyntaxToken"/> object for which a <c>HtmlClassName</c> enumeration
        /// value is returned.</param>
        /// <returns>A value of the <see cref="HtmlClassName"/> enumeration.</returns>
        protected virtual HtmlClassName GetHtmlClass(SyntaxToken token)
        {
            HtmlClassName className = HtmlClassName.Unknown;

            if (token.IsKeyword() || SyntaxFacts.IsPreprocessorKeyword(token.Kind()))
            {
                className = token.IsInDocumentationCommentTrivia() ? HtmlClassName.DocumentationComment : HtmlClassName.Keyword;
            }
            else
            {
                switch (token.Kind())
                {
                case SyntaxKind.HashToken:
                    if (token.IsInNode(n => SyntaxFacts.IsPreprocessorDirective(n.Kind())))
                    {
                        className = HtmlClassName.Keyword;
                    }

                    break;

                case SyntaxKind.EqualsToken:
                case SyntaxKind.DoubleQuoteToken:
                case SyntaxKind.SingleQuoteToken:
                case SyntaxKind.OpenParenToken:
                case SyntaxKind.CloseParenToken:
                case SyntaxKind.CommaToken:
                case SyntaxKind.DotToken:
                case SyntaxKind.LessThanToken:
                case SyntaxKind.GreaterThanToken:
                case SyntaxKind.LessThanSlashToken:
                case SyntaxKind.SlashGreaterThanToken:
                    if (token.IsInDocumentationCommentTrivia())
                    {
                        className = HtmlClassName.DocumentationComment;
                    }

                    break;

                case SyntaxKind.XmlCDataStartToken:
                case SyntaxKind.XmlCDataEndToken:
                case SyntaxKind.XmlTextLiteralToken:
                case SyntaxKind.XmlEntityLiteralToken:      // &lt; &gt; &quot; &amp; &apos; or &name; or &#nnnn; or &#xhhhh;
                    if (token.IsInNode(SyntaxKind.XmlCDataSection))
                    {
                        className = HtmlClassName.DocumentationComment;
                    }
                    else if (token.IsInDocumentationCommentTrivia())
                    {
                        className = HtmlClassName.Comment;
                    }

                    break;

                case SyntaxKind.StringLiteralToken:
                    className = HtmlClassName.StringLiteral;
                    break;

                case SyntaxKind.CharacterLiteralToken:
                    className = HtmlClassName.CharacterLiteral;
                    break;

                case SyntaxKind.NumericLiteralToken:
                    className = HtmlClassName.NumericLiteral;
                    break;

                case SyntaxKind.IdentifierToken:

                    // If the identifier is part of XML documentation, it is just takes the DocumentComment class name.
                    if (token.IsInDocumentationCommentTrivia())
                    {
                        className = HtmlClassName.DocumentationComment;
                    }
                    else if (token.IsInNode(n => SyntaxFacts.IsPreprocessorDirective(n.Kind())))
                    {
                        // If the identifier is part of the preprocessor directive (define, if, etc.), no class is used.
                        className = HtmlClassName.None;
                    }
                    else
                    {
                        className = GetIdentifierTokenHtmlClass(token);
                    }

                    break;

                default:
                    break;
                }
            }

            return(className);
        }
        /// <summary>
        /// Processes the token or trivia by returning the text of the token possibly wrapped in a "span" tag with any
        /// necessary class attribute.
        /// </summary>
        /// <remarks>
        /// This method is called from the <c>CoreRenderer.Callback</c> delegate which is set in the constructor. This method
        /// and its helper methods, primarily, <see cref="GetText"/> and <see cref="GetHtmlClass(SyntaxTreeElement)"/>
        /// (and their helper methods) do the heavy lifting to generate the correct HTML code for the C# code text.
        /// </remarks>
        /// <param name="syntaxTreeElement">The syntax tree element containing the token or trivia.</param>
        /// <exception cref="ArgumentNullException">if <paramref name="syntaxTreeElement"/> is <c>null</c>.</exception>
        protected virtual void ProcessTokenOrTrivia(SyntaxTreeElement syntaxTreeElement)
        {
            if (syntaxTreeElement == null)
            {
                throw new ArgumentNullException(nameof(syntaxTreeElement), "SyntaxTree element may not be null.");
            }

            HtmlClassName className = GetHtmlClass(syntaxTreeElement);
            string        text      = GetText(syntaxTreeElement);

            TraceWriteLine("Class Name: [{0}]; Text: [{1}]", className, text.Replace("\r\n", "\\r\\n"));

            StringBuilder outputText = new StringBuilder();

            if (syntaxTreeElement.SyntaxKind == SyntaxKind.MultiLineCommentTrivia)
            {
                string[] lines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                for (int i = 0; i < lines.Length; i++)
                {
                    string line = lines[i];
                    outputText.AppendFormat("<span class=\"{0}\"", HtmlClassName.Comment);
                    if (IncludeDebugInfo)
                    {
                        outputText.AppendFormat(" data-syntaxKind=\"{0}\"", syntaxTreeElement.SyntaxKind);
                    }

                    outputText.AppendFormat(">{0}</span>", line);

                    // Because GetLineEndText is called, IsLineEmpty is reset to true (actually GetLineStartText sets it to true
                    // which is called by GetLineEndText. Consequently, IsLineEmpty needs to be set to false for the lines of
                    // comment so that a trailing &nbsp; is not added to the end of the text. Usually this is done in GetText,
                    // but that method is not called here, because we are creating the lines individually. All part of the
                    // complexity of the "old-fashioned" multi-line comments (those marked with "/*" and "*/").
                    if (line.Length != 0)
                    {
                        IsLineEmpty = false;
                    }

                    if (i < lines.Length - 1)
                    {
                        outputText.Append(GetLineEndText());
                    }
                }
            }
            else if (className > HtmlClassName.None)
            {
                outputText.AppendFormat("<span class=\"{0}\"", className);
                if (IncludeDebugInfo)
                {
                    outputText.AppendFormat(" data-syntaxKind=\"{0}\"", syntaxTreeElement.SyntaxKind);
                }

                outputText.AppendFormat(">{0}</span>", text);
            }

            if (outputText.Length != 0)
            {
                text = outputText.ToString();
            }

            Writer.Write(text);
        }