public static DocumentationParsingResults ParseDocumentation(CSharpSyntaxNode member) { DocumentationParsingResults results = new DocumentationParsingResults(); results.MethodDocumentation.ParameterDescriptions = new SortedList <string, string>(); if (member.HasLeadingTrivia) { try { var trivia = member.GetLeadingTrivia().Single(t => t.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia || t.Kind() == SyntaxKind.MultiLineCommentTrivia); // Convert the trivia to a string, and strip out the tabs and /// string rawTrivia = Regex.Replace(trivia.ToString(), @"^\s+\/\/\/", "", RegexOptions.Multiline); // Strip <see> tags from the text, but leave in place the referenced function or type // i.e. <see cref="DownloadWaveform(NIRfsg, Waveform)"/> becomes DownloadWaveform rawTrivia = Regex.Replace(rawTrivia, @"<see cref=""([\w\.]+)(?:\(.*\))?""\/>", "$1", RegexOptions.Multiline); // Strip <paramRef> tags from the text, leaving in place the referenced parameter name rawTrivia = Regex.Replace(rawTrivia, @"<paramref name=""(\w+)""\/>", "$1", RegexOptions.Multiline); // Clear <para> tags rawTrivia = Regex.Replace(rawTrivia, @"<\/*para>", "", RegexOptions.Multiline); // The XML structure of the documentation is "rootless", so adding simple root node avoids parser errors rawTrivia = "<root>" + rawTrivia + "</root>"; // Parse the documentation XML XElement documentation = XElement.Parse(rawTrivia); // Get the summary element which contains the description for the function, parameter, or type string memberDescription = documentation.Element("summary").Value; // Remove newlines from the description memberDescription = Regex.Replace(memberDescription, "[\n\r]", ""); results.MemberDescription = memberDescription; try { string returnValue = documentation.Element("returns").Value; // Remove newlines from the description returnValue = Regex.Replace(returnValue, "[\n\r]", ""); results.MethodDocumentation.ReturnValueDescription = returnValue; } catch (System.NullReferenceException) { } List <XElement> parameters = documentation.Elements("param").ToList(); foreach (XElement param in parameters) { string parameterName = param.Attribute("name").Value; string parameterDescription = param.Value; // Remove newlines from the description parameterDescription = Regex.Replace(parameterDescription, "[\n\r]", ""); results.MethodDocumentation.ParameterDescriptions.Add(parameterName, parameterDescription); } } // If no leading syntax is found, we catch the exception here and will return an empty struct catch (System.InvalidOperationException) { } } return(results); }
static void ParseMembers(SyntaxList <MemberDeclarationSyntax> members, XmlWriter xmlWriter, Options o) { // These sorted lists are utilized so that we can drill into the code in the order it is presented, // while also creating the sorted list that we want to export at the same time. We cannot merely // sort the items beforehand because we need to sort by item name, but do not have access to the // name beforehand until we determine the type of object. // We add specific data to the value attribute of the sorted list, while using the item name // as the key so that they are sorted properly. SortedList <string, MemberData> membersList = new SortedList <string, MemberData>(); SortedList <string, string> paramList; MemberData memData; foreach (MemberDeclarationSyntax member in members) { paramList = new SortedList <string, string>(); DocumentationParsingResults memberDocumentation = ParseDocumentation(member); switch (member.Kind()) { case SyntaxKind.MethodDeclaration: MethodDeclarationSyntax method = (MethodDeclarationSyntax)member; // Get the return data type TypeSyntax returnType = method.ReturnType; if (returnType.ToString().ToLower() != "void") { paramList.Add("ReturnValue;Type=" + returnType.ToString(), memberDocumentation.MethodDocumentation.ReturnValueDescription); } // Get all input and output parameters foreach (ParameterSyntax param in method.ParameterList.Parameters) { string parameterName = param.Identifier.ToString(); memberDocumentation.MethodDocumentation.ParameterDescriptions.TryGetValue(parameterName, out string parameterDescription); paramList.Add(param.Identifier.ToString(), parameterDescription); } memData = new MemberData { MemberType = MemberTypes.Method, Description = memberDocumentation.MemberDescription, paramList = paramList }; membersList.Add(method.Identifier.ToString(), memData); break; case SyntaxKind.StructDeclaration: StructDeclarationSyntax myStruct = (StructDeclarationSyntax)member; // Get all members of the struct foreach (MemberDeclarationSyntax structMem in myStruct.Members) { // Some structs have methods defined for them as well if (structMem.Kind() == SyntaxKind.FieldDeclaration) { FieldDeclarationSyntax param = (FieldDeclarationSyntax)structMem; DocumentationParsingResults paramDoc = ParseDocumentation(param); for (int i = 0; i < param.Declaration.Variables.Count; i++) { var variableDeclaration = param.Declaration.Variables[i]; paramList.Add(variableDeclaration.Identifier.ToString(), paramDoc.MemberDescription); } } } memData = new MemberData { MemberType = MemberTypes.Type, Description = memberDocumentation.MemberDescription, paramList = paramList }; membersList.Add(myStruct.Identifier.ToString(), memData); break; case SyntaxKind.EnumDeclaration: // A few enums are defined in the class modules EnumDeclarationSyntax myEnum = (EnumDeclarationSyntax)member; memData = new MemberData { MemberType = MemberTypes.Type, Description = memberDocumentation.MemberDescription, paramList = paramList }; membersList.Add(myEnum.Identifier.ToString(), memData); break; case SyntaxKind.ClassDeclaration: // Recursively parse any sub-classes that are found ClassDeclarationSyntax subClass = (ClassDeclarationSyntax)member; xmlWriter.WriteStartElement("Class"); xmlWriter.WriteAttributeString("Name", subClass.Identifier.ToString()); DocumentationParsingResults classDoc = ParseDocumentation(subClass); if (o.ListDescriptions) { xmlWriter.WriteElementString("Description", classDoc.MemberDescription); } ParseMembers(subClass.Members, xmlWriter, o); xmlWriter.WriteEndElement(); break; case SyntaxKind.NamespaceDeclaration: // Recurse through the namespace and new members ParseNamespace(member, xmlWriter, o); break; default: break; } } if (membersList.Count > 0) { // Now, write all the data we have gathered for this class that has been sorted to the file for (int i = 0; i < 2; i++) { if (i == 0) { xmlWriter.WriteStartElement("Methods"); } else { xmlWriter.WriteStartElement("Types"); } foreach (KeyValuePair <string, MemberData> pair in membersList) { if ((i == 0 && pair.Value.MemberType == MemberTypes.Method) || (i == 1 && pair.Value.MemberType == MemberTypes.Type)) { xmlWriter.WriteStartElement(pair.Value.MemberType.ToString()); xmlWriter.WriteAttributeString("Name", pair.Key); if (o.ListDescriptions) { xmlWriter.WriteElementString("Description", pair.Value.Description); } if (o.ListParameters) { xmlWriter.WriteStartElement("Parameters"); foreach (KeyValuePair <string, string> paramPair in pair.Value.paramList) { xmlWriter.WriteStartElement("Parameter"); xmlWriter.WriteAttributeString("Name", paramPair.Key); if (o.ListDescriptions) { xmlWriter.WriteElementString("Description", paramPair.Value); } xmlWriter.WriteEndElement(); //xmlWriter.WriteElementString(paramPair.Value, paramPair.Key); } xmlWriter.WriteEndElement(); } xmlWriter.WriteEndElement(); } } xmlWriter.WriteEndElement(); } } }