Last active
August 7, 2024 08:02
-
-
Save formix/515d3d11ee7c1c252f92 to your computer and use it in GitHub Desktop.
Generates Markdown From VisualStudio XML documentation files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Text.RegularExpressions; | |
using System.Xml; | |
using System.Xml.Linq; | |
namespace Formix.Utils | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Program app = new Program(); | |
string md = app.ToMarkdown(args[0]); | |
Console.WriteLine(md); | |
} | |
private Dictionary<string, string> context; | |
public Program() | |
{ | |
context = new Dictionary<string, string>(); | |
context["lastNode"] = null; | |
} | |
public string ToMarkdown(string filePath) | |
{ | |
var xdoc = XDocument.Load(filePath); | |
var sw = new StringWriter(); | |
this.ToMarkdown(sw, xdoc.Root); | |
return sw.ToString(); | |
} | |
private void ToMarkdown(StringWriter sw, XElement root) | |
{ | |
if (root.Name != "param" && context["lastNode"] == "param") | |
{ | |
sw.WriteLine(); | |
} | |
if (root.Name == "doc") | |
{ | |
foreach (var node in root.Nodes()) | |
{ | |
var elem = (XElement)node; | |
if (elem.Name == "assembly") | |
{ | |
context["assembly"] = elem.Element("name").Value; | |
sw.WriteLine("\n# {0}\n", context["assembly"]); | |
} | |
else if (elem.Name == "members") | |
{ | |
ToMarkdown(sw, elem); | |
} | |
} | |
} | |
else if (root.Name == "members") | |
{ | |
// Sorts by member name to regroup them all properly. | |
var members = new List<XElement>(root.Elements("member")); | |
members.Sort((a, b) => | |
a.Attribute(XName.Get("name")).Value.Substring(2).CompareTo( | |
b.Attribute(XName.Get("name")).Value.Substring(2))); | |
foreach (var member in members) | |
{ | |
ToMarkdown(sw, member); | |
} | |
} | |
else if (root.Name == "member") | |
{ | |
var memberName = root.Attribute(XName.Get("name")).Value; | |
char memberType = memberName[0]; | |
if (memberType == 'M') | |
{ | |
memberName = RearrangeParametersInContext(root); | |
} | |
if (memberType == 'T') | |
{ | |
string remove = String.Format("T:{0}.",context["assembly"]); | |
string shortMemberName = memberName.Replace(remove,""); | |
sw.WriteLine("\n## {0}\n", shortMemberName); | |
context["typeName"] = shortMemberName; | |
} | |
else | |
{ | |
string shortMemberName = memberName.Replace("P:" + context["assembly"],"").Replace(context["typeName"] + ".",""); | |
if (shortMemberName.StartsWith("#ctor")) | |
{ | |
shortMemberName = shortMemberName.Replace("#ctor", "Constructor"); | |
} | |
sw.WriteLine("\n### {0}\n", shortMemberName); | |
} | |
foreach (var node in root.Nodes()) | |
{ | |
if (node.NodeType == XmlNodeType.Element) | |
{ | |
ToMarkdown(sw, (XElement)node); | |
} | |
} | |
} | |
else if (root.Name == "summary") | |
{ | |
string summary = Regex.Replace(root.Value, "\\s+", " ", RegexOptions.Multiline); | |
sw.WriteLine("{0}\n", summary.Trim()); | |
} | |
else if (root.Name == "param") | |
{ | |
if (context["lastNode"] != "param") | |
{ | |
sw.WriteLine("| Name | Description |"); | |
sw.WriteLine("| ---- | ----------- |"); | |
} | |
string paramName = root.Attribute(XName.Get("name")).Value; | |
if (context.ContainsKey(paramName)) | |
{ | |
sw.WriteLine("| {0} | *{1}*<br>{2} |", | |
paramName, | |
context[paramName], | |
Regex.Replace(root.Value, "\\s+", " ", RegexOptions.Multiline)); | |
} | |
else | |
{ | |
sw.WriteLine("| {0} | *Unknown type*<br>{1} |", | |
paramName, | |
Regex.Replace(root.Value, "\\s+", " ", RegexOptions.Multiline)); | |
} | |
} | |
else if (root.Name == "returns") | |
{ | |
sw.WriteLine("\n#### Returns\n"); | |
sw.WriteLine("{0}\n", Regex.Replace(root.Value, "\\s+", " ", RegexOptions.Multiline)); | |
} | |
else if (root.Name == "remarks") | |
{ | |
sw.WriteLine("\n#### Remarks\n"); | |
sw.WriteLine("{0}\n", Regex.Replace(root.Value, "\\s+", " ", RegexOptions.Multiline)); | |
} | |
else if (root.Name == "exception") | |
{ | |
string exName = root.Attribute("cref").Value.Substring(2); | |
exName = exName.Replace(context["assembly"] + ".", ""); | |
exName = exName.Replace(context["typeName"] + ".", ""); | |
sw.WriteLine("*{0}:* {1}\n", | |
exName, | |
Regex.Replace(root.Value, "\\s+", " ", RegexOptions.Multiline)); | |
} | |
context["lastNode"] = root.Name.ToString(); | |
} | |
private string RearrangeParametersInContext(XElement methodMember) | |
{ | |
string methodPrototype = methodMember.Attribute(XName.Get("name")).Value; | |
Match match = Regex.Match(methodPrototype, "\\((.*)\\)"); | |
string parameterString = match.Groups[1].Value.Replace(" ", ""); | |
string[] parameterTypes = parameterString.Split(','); | |
if (parameterTypes.Length == 0) | |
{ | |
// nothing to do... | |
return methodPrototype; | |
} | |
List<XElement> paramElems = new List<XElement>(methodMember.Elements("param")); | |
if (parameterTypes.Length != paramElems.Count) | |
{ | |
// the parameter count do not match, we can't do the rearrangement. | |
return methodPrototype; | |
} | |
string newParamString = ""; | |
for (int i = 0; i < paramElems.Count; i++) | |
{ | |
XElement paramElem = paramElems[i]; | |
string paramName = paramElem.Attribute(XName.Get("name")).Value; | |
string paramType = parameterTypes[i]; | |
if (newParamString != "") | |
{ | |
newParamString += ", "; | |
} | |
newParamString += paramName; | |
context[paramName] = paramType; | |
} | |
string newMethodPrototype = Regex.Replace( methodPrototype, | |
"\\(.*\\)", | |
"(" + newParamString + ")"); | |
return newMethodPrototype; | |
} | |
} | |
} |
Sadly seems to have a few problems. and style tags within remarks and summary and description sections just disappear, leaving sentences that make no sense. Property names in titles seem to have a leading . left on them. Method names have the leading M: and seem to have the namespace prefixed but the class name missing. For example; "M:ScribeSharp.AutoLoggers.AutoLoggerBase.#ctor(logger)" becomes "M:ScribeSharp.#ctor(logger)" in the output. Odd, because the sample output doesn't have most of these issues. I wonder if it's been updated and not retested?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Inspired from this fantasist gist, I wrote a nuget package containing a MSBuild task to generate Markdown documentation file. Check https://github.com/lijunle/Vsxmd