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)."); }