Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static string Humanize(string text, string xmlCommentEndOfLine)
.HumanizeRefTags()
.HumanizeHrefTags()
.HumanizeCodeTags()
.HumanizeMultilineCodeTags()
.HumanizeMultilineCodeTags(xmlCommentEndOfLine)
.HumanizeParaTags()
.HumanizeBrTags(xmlCommentEndOfLine) // must be called after HumanizeParaTags() so that it replaces any additional <br> tags
.DecodeXml();
Expand Down Expand Up @@ -105,7 +105,7 @@ private static string HumanizeCodeTags(this string text)
return CodeTag().Replace(text, (match) => "`" + match.Groups["display"].Value + "`");
}

private static string HumanizeMultilineCodeTags(this string text)
private static string HumanizeMultilineCodeTags(this string text, string xmlCommentEndOfLine)
{
return MultilineCodeTag().Replace(text, match =>
{
Expand All @@ -115,12 +115,17 @@ private static string HumanizeMultilineCodeTags(this string text)
var builder = new StringBuilder().Append("```");
if (!codeText.StartsWith("\r") && !codeText.StartsWith("\n"))
{
builder.AppendLine();
builder.Append(EndOfLine(xmlCommentEndOfLine));
}

return builder.AppendLine(codeText.TrimEnd())
.Append("```")
.ToString();
builder.Append(RemoveCommonLeadingWhitespace(codeText, xmlCommentEndOfLine));
if (!codeText.EndsWith("\n"))
{
builder.Append(EndOfLine(xmlCommentEndOfLine));
}

builder.Append("```");
return DoubleUpLineBreaks().Replace(builder.ToString(), EndOfLine(xmlCommentEndOfLine));
}

return $"```{codeText}```";
Expand All @@ -129,30 +134,68 @@ private static string HumanizeMultilineCodeTags(this string text)

private static string HumanizeParaTags(this string text)
{
return ParaTag().Replace(text, match =>
{
var paraText = "<br>" + match.Groups["display"].Value.Trim();
return LineBreaks().Replace(paraText, _ => string.Empty);
Comment thread
martincostello marked this conversation as resolved.
});
return ParaTag().Replace(text, match => "<br>" + match.Groups["display"].Value.Trim());
}

private static string HumanizeBrTags(this string text, string xmlCommentEndOfLine)
{
return BrTag().Replace(text, _ => xmlCommentEndOfLine ?? Environment.NewLine);
return BrTag().Replace(text, _ => EndOfLine(xmlCommentEndOfLine));
}

private static string DecodeXml(this string text)
{
return WebUtility.HtmlDecode(text);
}

private static string RemoveCommonLeadingWhitespace(string input, string xmlCommentEndOfLine)
{
var lines = input.Split(["\r\n", "\n"], StringSplitOptions.None);
var minLeadingSpaces = int.MaxValue;
foreach (var line in lines)
{
if (string.IsNullOrEmpty(line))
{
continue;
}

var leadingSpaces = line.Length - line.TrimStart(' ').Length;
minLeadingSpaces = Math.Min(minLeadingSpaces, leadingSpaces);
Comment thread
martincostello marked this conversation as resolved.
Outdated
if (minLeadingSpaces == 0)
{
return input;
Comment thread
martincostello marked this conversation as resolved.
Outdated
}
}

if (minLeadingSpaces is int.MaxValue)
{
return input;
}

var builder = new StringBuilder();
foreach (var line in lines)
{
builder.Append(string.IsNullOrWhiteSpace(line)
? line
: line.Substring(minLeadingSpaces));
builder.Append(EndOfLine(xmlCommentEndOfLine));
}

return builder.ToString();
}

private static string EndOfLine(string xmlCommentEndOfLine)
{
return xmlCommentEndOfLine ?? Environment.NewLine;
}

private const string RefTagPattern = @"<(see|paramref) (name|cref|langword)=""([TPF]{1}:)?(?<display>.+?)"" ?/>";
private const string CodeTagPattern = @"<c>(?<display>.+?)</c>";
private const string MultilineCodeTagPattern = @"<code>(?<display>.+?)</code>";
private const string ParaTagPattern = @"<para>(?<display>.+?)</para>";
private const string HrefPattern = @"<see href=\""(.*)\"">(.*)<\/see>";
private const string BrPattern = @"(<br ?\/?>)"; // handles <br>, <br/>, <br />
private const string LineBreaksPattern = @"\r?\n";
private const string DoubleUpLineBreaksPattern = @"(\r?\n){2,}";

#if NET7_0_OR_GREATER
[GeneratedRegex(RefTagPattern)]
Expand All @@ -175,6 +218,9 @@ private static string DecodeXml(this string text)

[GeneratedRegex(LineBreaksPattern)]
private static partial Regex LineBreaks();

[GeneratedRegex(DoubleUpLineBreaksPattern)]
private static partial Regex DoubleUpLineBreaks();
#else
private static readonly Regex _refTag = new(RefTagPattern);
private static readonly Regex _codeTag = new(CodeTagPattern);
Expand All @@ -183,6 +229,7 @@ private static string DecodeXml(this string text)
private static readonly Regex _hrefTag = new(HrefPattern);
private static readonly Regex _brTag = new(BrPattern);
private static readonly Regex _lineBreaks = new(LineBreaksPattern);
private static readonly Regex _doubleUpLineBreaks = new(DoubleUpLineBreaksPattern);

private static Regex RefTag() => _refTag;
private static Regex CodeTag() => _codeTag;
Expand All @@ -191,6 +238,7 @@ private static string DecodeXml(this string text)
private static Regex HrefTag() => _hrefTag;
private static Regex BrTag() => _brTag;
private static Regex LineBreaks() => _lineBreaks;
private static Regex DoubleUpLineBreaks() => _doubleUpLineBreaks;
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"CrudActions"
],
"summary": "Get all products",
"description": "```\r\n {\r\n \"Id\":1,\r\n \"Description\":\"\",\r\n \"Status\": 0,\r\n \"Status2\": 1\r\n }\r\n```",
"description": "```\r\n{\r\n \"Id\":1,\r\n \"Description\":\"\",\r\n \"Status\": 0,\r\n \"Status2\": 1\r\n}\r\n \r\n```",
"responses": {
"200": {
"description": "OK",
Expand Down Expand Up @@ -199,7 +199,7 @@
"CrudActions"
],
"summary": "Updates some properties of a specific product",
"description": "\r\nOnly provided properties will be updated, other remain unchanged.\r\n\r\nIdentifier must be non-default value\r\n\r\nBody must be specified",
"description": "\r\nOnly provided properties will be updated,\r\n other remain unchanged.\r\n\r\nIdentifier must be non-default value\r\n\r\nBody must be specified",
"operationId": "PatchProduct",
"parameters": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
"CrudActions"
],
"summary": "Get all products",
"description": "```\r\n {\r\n \"Id\":1,\r\n \"Description\":\"\",\r\n \"Status\": 0,\r\n \"Status2\": 1\r\n }\r\n```",
"description": "```\r\n{\r\n \"Id\":1,\r\n \"Description\":\"\",\r\n \"Status\": 0,\r\n \"Status2\": 1\r\n}\r\n \r\n```",
"responses": {
"200": {
"description": "OK",
Expand Down Expand Up @@ -199,7 +199,7 @@
"CrudActions"
],
"summary": "Updates some properties of a specific product",
"description": "\r\nOnly provided properties will be updated, other remain unchanged.\r\n\r\nIdentifier must be non-default value\r\n\r\nBody must be specified",
"description": "\r\nOnly provided properties will be updated,\r\n other remain unchanged.\r\n\r\nIdentifier must be non-default value\r\n\r\nBody must be specified",
"operationId": "PatchProduct",
"parameters": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ This is a paragraph.

var output = XmlCommentsTextHelper.Humanize(input);

Assert.Equal("\r\nThis is a paragraph. MultiLined.\r\n\r\nThis is a paragraph.", output, false, true);
Assert.Equal("\r\nThis is a paragraph.\r\n MultiLined.\r\n\r\nThis is a paragraph.", output, false, true);
}

[Fact]
Expand All @@ -218,10 +218,10 @@ public void Humanize_CodeMultiLineTag()
var expected = string.Join("\r\n",
[
"```",
" {",
" \"Prop1\":1,",
" \"Prop2\":[]",
" }",
"{",
" \"Prop1\":1,",
" \"Prop2\":[]",
"}",
"```"
]);
Assert.Equal(expected, output, false, true);
Expand Down Expand Up @@ -250,5 +250,50 @@ public void Humanize_CodeMultiLineTag_OnSameLine()
]);
Assert.Equal(expected, output, false, true);
}

[Fact]
public void Humanize_CodeInsideParaTag()
{
var input = string.Join("\r\n",
[
"<para>Creates a new Answer</para>",
"<para><code><![CDATA[",
"POST /api/answers",
"{",
""" "name": "OnlyYes",""",
""" "label": "Yes",""",
""" "answers": [""",
" {",
""" "answer": "yes""",
" }",
" ]",
"}",
"]]></code></para>",
]);

var output = XmlCommentsTextHelper.Humanize(input);

var expected = string.Join("\r\n",
[
"",
"Creates a new Answer",
"",
"```",
"<![CDATA[",
"POST /api/answers",
"{",
""" "name": "OnlyYes",""",
""" "label": "Yes",""",
""" "answers": [""",
" {",
""" "answer": "yes""",
" }",
" ]",
"}",
"]]>",
"```"
]);
Assert.Equal(expected, output, false, true);
}
}
}