-
-
Save formix/515d3d11ee7c1c252f92 to your computer and use it in GitHub Desktop.
| 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; | |
| } | |
| } | |
| } |
Thank you for your efforts. A couple of places you where calling memberName.Substring to remove the assembly name and type. This was throwing an IndexOutOfRange exception when the name wasn't present. I modified it to use Replace. For example Replace("T:" + context["assembly"], "");
Thanks for the update @OldNo7!
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
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?
Works in production. Resulting documentaiton can be seen here: https://github.com/formix/MegaCityOne/blob/master/MegaCityOne/doc/api.md