/// <summary> /// Beatifies XML comments. /// </summary> /// <param name="outputComment">The output comment.</param> /// <param name="inputComment">The comment to be wrapped and beautified.</param> /// <param name="prefix">The current comment line's prefix.</param> private static void WrapXml(StringBuilder outputComment, string inputComment, string prefix) { // The main idea around formatting XML is to put tags on their own lines when the text can't fit within the margin. To accomplish this, // we'll format the entire tag and then see how long is it. StringBuilder commentBody = new StringBuilder(); FormatCommentCommand.WrapLine(commentBody, inputComment, prefix); // This will pull apart the XML into the tags and the content. Match match = FormatCommentCommand.xmlParts.Match(inputComment); string startTag = match.Groups["startTag"].Value; string body = match.Groups["body"].Value; string endTag = match.Groups["endTag"].Value; // Extract the element name. The name will be used to determine if the tags appear on their own comment lines or if they can be // collapsed onto a single line with their comment. For example, the ' <summary>' tag is generally emitted on its own line whereas the ' // <param>' tag is usually combined with the comments. Regex xmlElementName = new Regex(@"<(?<name>\w+).*>"); string elementName = xmlElementName.Match(startTag).Groups["name"].Value; // If the tags and the content is too long to fit on a line after formatting, we'll put the tags on their own lines. We also force a // certain class of tags to appear on their own line distinct from the content; it's just easier to read this way. if (commentBody.Length >= Properties.Settings.Default.WrapMargin || FormatCommentCommand.breakingElements.Contains(elementName)) { // Write the start tag. outputComment.Append(prefix); outputComment.Append(' '); outputComment.Append(startTag); outputComment.AppendLine(); // This will format the body of the tag to wrap at the right margin. FormatCommentCommand.columnPosition = 0; FormatCommentCommand.WrapLine(outputComment, body, prefix); if (FormatCommentCommand.columnPosition != 0) { outputComment.AppendLine(); } // Write the closing tag. outputComment.Append(prefix); outputComment.Append(' '); outputComment.Append(endTag); outputComment.AppendLine(); } else { // If the XML tags and the line fit within the margin, we just write it to the output comment. outputComment.Append(commentBody.ToString()); outputComment.AppendLine(); } // Reset the wrapping parameters for the next comment line in the block. FormatCommentCommand.columnPosition = 0; }
/// <summary> /// Formats a block of comments and beautifies the XML comments. /// </summary> /// <param name="inputComment">The block of comment lines to format.</param> /// <returns>A right justified and beautified block of comments.</returns> private static string FormatCommentstring(string inputComment) { // These variables control the state of the wrapping. FormatCommentCommand.columnPosition = 0; // This buffer keeps track of the reconstructed comment block. StringBuilder outputComment = new StringBuilder(); // This breaks the input comment into individual lines. string[] commentLines = inputComment.Split(new string[] { "\r\n" }, StringSplitOptions.None | StringSplitOptions.RemoveEmptyEntries); // Parse the line into prefixes (the comment start sequence and whitespace) and the actual comment on the line. for (int index = 0; index < commentLines.Length; index++) { // This parses the comment and the prefix out of the current comment line. The prefix is all characters up to the comment delimiter // character. The comment represents all the characters after the delimiter stripped of the leading and trailing space. Match match = FormatCommentCommand.commentRegex.Match(commentLines[index]); string prefix = match.Groups["prefix"].Value; string comment = match.Groups["comment"].Value; // We assume there was some intent if an author left an entire line blank and so we treat it as a paragraph break. That is, we don't // wrap over it. if (comment.Length == 0) { if (FormatCommentCommand.columnPosition != 0) { outputComment.AppendLine(); } outputComment.AppendLine(prefix); FormatCommentCommand.columnPosition = 0; continue; } // We're going to attempt to format the special comment directives so that they'll wrap nicely. If a single-line directive is too // long, we'll wrap it so the directives end up on separate lines. This test is used to distinguish between the special directives // used for commenting functions, classes and modules from regular block comments. bool isCommentDirective = prefix.EndsWith("///", StringComparison.Ordinal); // This section will provide formatting for XML tags inside comment blocks. The tags are examined to see if the text and tags will // fit within the margins. If not, the tags are placed on their own lines and the comment inside the tags is formatted to wrap // around the margins. The algorithm will also eat up partial lines in order to fill out the XML content to the margin. if (isCommentDirective && xmlStartTag.IsMatch(comment) && !xmlEmptyElement.IsMatch(comment)) { while (!xmlEndTag.IsMatch(comment) && index < commentLines.Length) { match = FormatCommentCommand.commentRegex.Match(commentLines[++index]); comment += ' ' + match.Groups["comment"].Value; } FormatCommentCommand.WrapXml(outputComment, comment, prefix); continue; } // This is used to force a line break on comment lines that meet certain criteria, such as bullet marks and examples. bool isBreakingLine = false; // Lines that begin with an Asterisk are considered bullet marks and do not wrap and are given an extra margin. if (comment.StartsWith("*", StringComparison.Ordinal)) { // If the previous line was waiting for some wrapping to occur, it's going to be disappointed. This line is going to start all // on its own. if (FormatCommentCommand.columnPosition != 0) { outputComment.AppendLine(); } FormatCommentCommand.columnPosition = 0; // The prefix will be indented for all bullet marks. prefix += " "; // This will force a line break after the bullet mark is formatted. isBreakingLine = true; } // Lines that end with colons do not wrap. if (comment.EndsWith(":", StringComparison.Ordinal)) { isBreakingLine = true; } // This is where all the work is done to right justify the block comment to the margin. FormatCommentCommand.WrapLine(outputComment, comment, prefix); // This will force a new line for comment lines that don't wrap such as bullet marks and colons. if (isBreakingLine) { outputComment.AppendLine(); FormatCommentCommand.columnPosition = 0; } } // This will finish off any incomplete lines. if (FormatCommentCommand.columnPosition > 0) { outputComment.AppendLine(); } // At this point we've transformed the input block of comments into a right justified block. return(outputComment.ToString()); }