protected virtual void AsTextContent(CodeWriter writer, RenderFlags flags)
 {
     writer.EscapeUnicode = false;
     if (_content is string)
     {
         DocText.AsTextText(writer, GetContentForDisplay(flags), flags);
     }
     else if (_content is ChildList <DocComment> )
     {
         ChildList <DocComment> content = (ChildList <DocComment>)_content;
         for (int i = 0; i < content.Count; ++i)
         {
             DocComment docComment = content[i];
             if (docComment is DocText)
             {
                 DocText.AsTextText(writer, GetContentForDisplay((DocText)docComment, i == 0, i == content.Count - 1, flags), flags);
             }
             else
             {
                 docComment.AsText(writer, flags);
             }
         }
     }
     else if (_content is CodeObject)
     {
         // Turn on translation of '<', '&', and '>' for content
         writer.InDocCommentContent = true;
         ((CodeObject)_content).AsText(writer, flags);
         writer.InDocCommentContent = false;
     }
     writer.EscapeUnicode = true;
 }
 /// <summary>
 /// Create a <see cref="DocComment"/> with the specified child <see cref="DocComment"/> content.
 /// </summary>
 public DocComment(DocComment docComment)
 {
     _content = new ChildList <DocComment>(this)
     {
         docComment
     };
 }
        /// <summary>
        /// Get the root documentation comment object.
        /// </summary>
        public DocComment GetRootDocComment()
        {
            DocComment parent = this;

            while (parent.Parent is DocComment)
            {
                parent = (DocComment)parent.Parent;
            }
            return(parent);
        }
        // NOTE: No parse-point is installed for general documentation comments - instead, the parser calls
        //       the parsing method below directly based upon the token type.  Documentation comments with
        //       specific tags do have parse-points installed.
        // NOTE: Manual parsing of the XML is done instead of using an XML parser - this is for
        //       performance, and to handle malformed XML properly, and also so embedded code references
        //       and fragments can be parsed properly with the main parser.

        /// <summary>
        /// Parse a <see cref="DocComment"/>.
        /// </summary>
        public static DocComment Parse(Parser parser, CodeObject parent, ParseFlags flags)
        {
            Token token            = parser.Token;
            byte  prefixSpaceCount = (token.LeadingWhitespace.Length < byte.MaxValue ? (byte)token.LeadingWhitespace.Length : byte.MaxValue);

            // Get any newlines preceeding the documentation comment
            int newLines = token.NewLines;

            parser.NextToken(true);  // Move past '///' or '/**'

            // Start a new Unused list in the parser to catch unrecognized tokens in otherwise valid tags, etc.
            // This must be done in order to prevent anything already in the unused list from being emitted
            // within the doc comment.
            parser.PushUnusedList();

            // Remove any leading blank lines from inside the doc comment
            parser.Token.NewLines = 0;

            // Parse a DocComment object
            DocComment docComment = new DocComment(parser, parent)
            {
                NewLines = newLines
            };

            // Restore the previous Unused list in the parser - it's the responsibility of the DocComment parsing
            // logic to flush any unused tokens, such as into the content area of the comment.
            parser.PopUnusedList();

            // Remove the parent DocComment if it only has a single child
            if (docComment.Content is string)
            {
                DocText docText = new DocText((string)docComment.Content)
                {
                    NewLines = newLines
                };
                docText.SetLineCol(docComment);
                docComment = docText;
            }
            else
            {
                ChildList <DocComment> content = (ChildList <DocComment>)docComment.Content;
                if (content.Count == 1)
                {
                    DocComment first = content[0];
                    first.NewLines = newLines;
                    first.SetLineCol(docComment);
                    docComment = first;
                }
            }

            // Store the number of prefixed spaces
            docComment._prefixSpaceCount = prefixSpaceCount;

            return(docComment);
        }
        /// <summary>
        /// Deep-clone the code object.
        /// </summary>
        public override CodeObject Clone()
        {
            DocComment clone = (DocComment)base.Clone();

            if (_content is ChildList <DocComment> )
            {
                clone._content = ChildListHelpers.Clone((ChildList <DocComment>)_content, clone);
            }
            else
            {
                clone.CloneField(ref clone._content, _content);
            }
            return(clone);
        }
 /// <summary>
 /// Add the specified child <see cref="DocComment"/> to the documentation comment.
 /// </summary>
 public virtual void Add(DocComment docComment)
 {
     if (docComment != null)
     {
         if (_content == null)
         {
             _content = new ChildList <DocComment>(this);
         }
         else if (_content is string)
         {
             string existing = (string)_content;
             _content = new ChildList <DocComment>(this);
             if (existing.Length > 0)  // Don't use NotEmpty(), because we want to preserve whitespace
             {
                 ((ChildList <DocComment>)_content).Add(new DocText(existing));
             }
         }
         if (docComment.GetType() == typeof(DocComment))
         {
             // If we're adding a base container, merge the two containers instead
             object content = docComment.Content;
             if (content is string)
             {
                 ((ChildList <DocComment>)_content).Add(new DocText((string)content));
             }
             else if (_content is ChildList <DocComment> )
             {
                 ((ChildList <DocComment>)_content).AddRange((ChildList <DocComment>)content);
                 NormalizeContent();
             }
         }
         else if (_content is ChildList <DocComment> )
         {
             ((ChildList <DocComment>)_content).Add(docComment);
             NormalizeContent();
         }
         else
         {
             throw new Exception("Can't add to a DocComment that contains code objects - add to the contained BlockDecl instead.");
         }
     }
 }
        /// <summary>
        /// Parse the content of a <see cref="DocComment"/> tag.
        /// </summary>
        /// <returns>True if the content was followed by a valid end tag, otherwise false.</returns>
        protected virtual bool ParseContent(Parser parser)
        {
            bool foundEndTag = false;

            // Default to an empty string (leaving it null would combine the end tag with the start tag)
            _content = "";

            if (parser.TokenText == ParseTokenTagClose)
            {
                parser.NextToken(true);  // Move past '>'
            }
            // Special check for a comment terminating a doc comment
            Token lastToken = parser.LastToken;

            if (lastToken != null && lastToken.Text == null && lastToken.HasTrailingComments)
            {
                foreach (CommentBase commentBase in parser.LastToken.TrailingComments)
                {
                    _content += (commentBase.IsFirstOnLine ? "\n" : "") + commentBase.AsString();
                }
                parser.LastToken.TrailingComments = null;
            }

            // Stop if we hit EOF, or if we've exited the doc comment and processed the last doc comment string
            while (parser.Token != null && (parser.InDocComment || parser.TokenType == TokenType.DocCommentString))
            {
                DocComment comment = null;

                // Look for any embedded start/end tag
                if (parser.TokenText == ParseTokenTagOpen && !parser.Token.WasEscaped)
                {
                    // Peek ahead first to determine if the end tag matches a parent's open
                    // tag instead of the current one.
                    if (parser.PeekNextTokenText() == ParseTokenEndTag)
                    {
                        string endTagName = parser.PeekNextTokenText();
                        if (endTagName != TagName)
                        {
                            // If the end tag doesn't match the current open tag, but does
                            // match a parent's open tag, then abort processing this tag.
                            DocComment parent = _parent as DocComment;
                            while (parent != null)
                            {
                                if (endTagName == parent.TagName)
                                {
                                    break;
                                }
                                parent = parent.Parent as DocComment;
                            }
                            if (parent != null)
                            {
                                parser.ResetPeekAhead();
                                break;
                            }
                        }
                    }

                    // Add any leading whitespace on the tag as text
                    if (parser.Token.LeadingWhitespace.Length > 0)
                    {
                        // If the token is on a new line, insert a newline in the text, and change the token to NOT be on a new line.
                        string whitespace = parser.Token.LeadingWhitespace;
                        if (parser.Token.IsFirstOnLine)
                        {
                            whitespace            = '\n' + whitespace;
                            parser.Token.NewLines = 0;
                        }
                        Add(whitespace);
                    }

                    Token openTagToken = parser.Token;
                    parser.NextToken(true);  // Move past '<'

                    if (parser.TokenText == ParseTokenEndTag && !parser.Token.WasEscaped)
                    {
                        int newLines = parser.LastToken.NewLines;

                        // Handle an end tag
                        parser.NextToken(true);  // Move past '/'
                        Token endTag = parser.Token;
                        parser.NextToken(true);  // Move past tag
                        if (parser.TokenText == ParseTokenTagClose)
                        {
                            parser.NextToken(true);  // Move past '>'
                        }
                        // If the end tag matches the current open tag, we're done
                        if (endTag.Text == TagName)
                        {
                            // Add any newlines on the end tag as text content
                            if (newLines > 0)
                            {
                                Add(new string('\n', newLines));
                            }
                            foundEndTag = true;
                            break;
                        }

                        // Handle an unexpected end tag
                        comment = new DocTag(endTag, newLines, parser, this);
                    }
                    else
                    {
                        // Recursively parse a start tag
                        comment = (DocComment)parser.ProcessToken(this);
                    }
                    if (comment != null)
                    {
                        Add(comment);
                    }
                    else
                    {
                        // If we failed to parse a tag, save the open tag and last unused tokens for parsing
                        // into comment text below.
                        Token lastUnusedToken = parser.RemoveLastUnused().AsToken();
                        parser.AddUnused(openTagToken);
                        parser.AddUnused(lastUnusedToken);
                    }
                }

                // If we didn't parse a tag, then handle comment text
                if (comment == null)
                {
                    string text;
                    if (parser.Token.TokenType != TokenType.DocCommentStart)
                    {
                        // Handle comment text
                        text = parser.Token.LeadingWhitespace + parser.TokenText;

                        // Add any newlines to the front of the text
                        if (parser.Token.NewLines > 0)
                        {
                            text = new string('\n', parser.Token.NewLines) + text;
                        }
                    }
                    else
                    {
                        text = "\n";
                    }

                    // Flush any unused tokens to the front of the text
                    while (parser.HasUnused)
                    {
                        Token unusedToken = parser.RemoveLastUnused().AsToken();
                        text = unusedToken.LeadingWhitespace + unusedToken.Text + text;
                    }

                    parser.NextToken(true);  // Move past text

                    // If we're at the end of the doc comment, truncate the trailing newline
                    if (!(parser.InDocComment || parser.TokenType == TokenType.DocCommentString))
                    {
                        text = text.TrimEnd('\n');
                    }
                    Add(text);
                }
            }

            return(foundEndTag);
        }
 /// <summary>
 /// Throws an exception if called.
 /// </summary>
 public override void Add(DocComment docComment)
 {
     throw new Exception("Can't add DocComment objects to DocText objects (add them both to a parent DocComment instead).");
 }