Merge pull request #1109 from adbre/iss358
[mono.git] / mcs / tools / mdoc / Mono.Documentation / monodocs2slashdoc.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.IO;
5 using System.Text;
6 using System.Text.RegularExpressions;
7 using System.Xml;
8
9 using Monodoc;
10 using Mono.Options;
11
12 namespace Mono.Documentation {
13 public class MDocToMSXDocConverter : MDocCommand {
14
15         public override void Run (IEnumerable<string> args)
16         {
17                 string file = null;
18                 var p = new OptionSet () {
19                         { "o|out=", 
20                                 "The XML {FILE} to generate.\n" + 
21                                 "If not specified, will create a set of files in the curent directory " +
22                                 "based on the //AssemblyInfo/AssemblyName values within the documentation.\n" +
23                                 "Use '-' to write to standard output.",
24                                 v => file = v },
25                 };
26                 List<string> directories = Parse (p, args, "export-slashdoc", 
27                                 "[OPTIONS]+ DIRECTORIES",
28                                 "Export mdoc(5) documentation within DIRECTORIES into \n" +
29                                         "Microsoft XML Documentation format files.");
30                 if (directories == null)
31                         return;
32                 Run (file, directories);
33         }
34         
35         public static void Run (string file, IEnumerable<string> dirs)
36         {
37                 Dictionary<string, XmlElement> outputfiles = new Dictionary<string, XmlElement> ();
38
39                 XmlDocument nsSummaries = new XmlDocument();
40                 nsSummaries.LoadXml("<namespaces/>");
41
42                 foreach (string dir in dirs)
43                         Process (dir, outputfiles, nsSummaries, file == null);
44
45                 if (outputfiles.Count > 0 && file != null) {
46                         List<string> files = new List<string> (outputfiles.Keys);
47                         files.Sort ();
48                         XmlDocument d = new XmlDocument ();
49                         d.AppendChild (d.CreateElement ("doc"));
50                         d.FirstChild.AppendChild (
51                                         d.ImportNode (outputfiles [files [0]].SelectSingleNode ("/doc/assembly"), true));
52                         XmlElement members = d.CreateElement ("members");
53                         d.FirstChild.AppendChild (members);
54                         foreach (string f in files) {
55                                 XmlElement from = (XmlElement) outputfiles [f];
56                                 foreach (XmlNode n in from.SelectNodes ("/doc/members/*"))
57                                         members.AppendChild (d.ImportNode (n, true));
58                         }
59                         using (TextWriter tw = file == "-" ? Console.Out : new StreamWriter (file))
60                                 WriteXml (d.DocumentElement, tw);
61                         return;
62                 }
63
64                 // Write out each of the assembly documents
65                 foreach (string assemblyName in outputfiles.Keys) {
66                         XmlElement members = (XmlElement)outputfiles[assemblyName];
67                         Console.WriteLine(assemblyName + ".xml");
68                         using(StreamWriter sw = new StreamWriter(assemblyName + ".xml")) {
69                                 WriteXml(members.OwnerDocument.DocumentElement, sw);
70                         }
71                 }
72         
73                 // Write out a namespace summaries file.
74                 Console.WriteLine("NamespaceSummaries.xml");
75                 using(StreamWriter writer = new StreamWriter("NamespaceSummaries.xml")) {
76                         WriteXml(nsSummaries.DocumentElement, writer);
77                 }
78         }
79
80         private static void Process (string basepath, Dictionary<string, XmlElement> outputfiles, XmlDocument nsSummaries, bool implicitFiles)
81         {
82                 if (System.Environment.CurrentDirectory == System.IO.Path.GetFullPath(basepath) && implicitFiles) {
83                         Console.WriteLine("Don't run this tool from your documentation directory, since some files could be accidentally overwritten.");
84                         return;
85                 }
86
87                 XmlDocument index_doc = new XmlDocument();
88                 index_doc.Load(Path.Combine(basepath, "index.xml"));
89                 XmlElement index = index_doc.DocumentElement;
90                 
91                 foreach (XmlElement assmbly in index.SelectNodes("Assemblies/Assembly")) {
92                         string assemblyName = assmbly.GetAttribute("Name");
93                         if (outputfiles.ContainsKey (assemblyName))
94                                 continue;
95                         XmlDocument output = new XmlDocument();
96                         XmlElement output_root = output.CreateElement("doc");
97                         output.AppendChild(output_root);
98
99                         XmlElement output_assembly = output.CreateElement("assembly");
100                         output_root.AppendChild(output_assembly);
101                         XmlElement output_assembly_name = output.CreateElement("name");
102                         output_assembly.AppendChild(output_assembly_name);
103                         output_assembly_name.InnerText = assemblyName;
104                 
105                         XmlElement members = output.CreateElement("members");
106                         output_root.AppendChild(members);
107                         
108                         outputfiles.Add (assemblyName, members);
109                 }
110                         
111                 foreach (XmlElement nsnode in index.SelectNodes("Types/Namespace")) {
112                         string ns = nsnode.GetAttribute("Name");
113                         foreach (XmlElement typedoc in nsnode.SelectNodes("Type")) {
114                                 string typename = typedoc.GetAttribute("Name");
115                                 XmlDocument type = new XmlDocument();
116                                 type.Load(Path.Combine(Path.Combine(basepath, ns), typename) + ".xml");
117                                 
118                                 string assemblyname = type.SelectSingleNode("Type/AssemblyInfo/AssemblyName").InnerText;
119                                 XmlElement members = outputfiles [assemblyname];
120                                 if (members == null) continue; // assembly is strangely not listed in the index
121                                 
122                                 CreateMember (GetCref (type.DocumentElement), type.DocumentElement, members);
123                                         
124                                 foreach (XmlElement memberdoc in type.SelectNodes("Type/Members/Member")) {
125                                         string name = GetCref (memberdoc);
126                                         CreateMember(name, memberdoc, members);
127                                 }
128                         }
129                 }
130                 foreach (XmlElement nsnode in index.SelectNodes("Types/Namespace")) {
131                         AddNamespaceSummary(nsSummaries, basepath, nsnode.GetAttribute("Name"));
132                 }
133         }
134
135         static string GetCref (XmlElement member)
136         {
137                 string typeName = XmlDocUtils.ToEscapedTypeName (member.SelectSingleNode("/Type/@FullName").InnerText);
138                 if (member.Name == "Type")
139                         return "T:" + typeName;
140                 string memberType = member.SelectSingleNode("MemberType").InnerText;
141                 switch (memberType) {
142                         case "Constructor":
143                                 return "C:" + typeName + MakeArgs(member);
144                         case "Event":
145                                 return "E:" + typeName + "." + XmlDocUtils.ToEscapedMemberName (member.GetAttribute("MemberName"));
146                         case "Field":
147                                 return "F:" + typeName + "." + XmlDocUtils.ToEscapedMemberName (member.GetAttribute("MemberName"));
148                         case "Method": {
149                                 string name = "M:" + typeName + "." + XmlDocUtils.ToEscapedMemberName (member.GetAttribute("MemberName")) + MakeArgs(member);
150                                 if (member.GetAttribute("MemberName") == "op_Implicit" || member.GetAttribute("MemberName") == "op_Explicit")
151                                         name += "~" + XmlDocUtils.ToTypeName (member.SelectSingleNode("ReturnValue/ReturnType").InnerText, member);
152                                 return name;
153                         }
154                         case "Property":
155                                 return "P:" + typeName + "." + XmlDocUtils.ToEscapedMemberName (member.GetAttribute("MemberName")) + MakeArgs(member);
156                         default:
157                                 throw new NotSupportedException ("MemberType '" + memberType + "' is not supported.");
158                 }
159         }
160
161         static string MakeArgs (XmlElement member)
162         {
163                 XmlNodeList parameters = member.SelectNodes ("Parameters/Parameter");
164                 if (parameters.Count == 0)
165                         return "";
166                 StringBuilder args = new StringBuilder ();
167                 args.Append ("(");
168                 args.Append (XmlDocUtils.ToTypeName (parameters [0].Attributes ["Type"].Value, member));
169                 for (int i = 1; i < parameters.Count; ++i) {
170                         args.Append (",");
171                         args.Append (XmlDocUtils.ToTypeName (parameters [i].Attributes ["Type"].Value, member));
172                 }
173                 args.Append (")");
174                 return args.ToString ();
175         }
176         
177         private static void AddNamespaceSummary(XmlDocument nsSummaries, string basepath, string currentNs) {
178                 foreach (var filename in new [] {
179                                 Path.Combine(basepath, currentNs + ".xml"),
180                                 Path.Combine(basepath, "ns-" + currentNs + ".xml")}) {
181                         if (File.Exists(filename))      {
182                                 XmlDocument nsSummary = new XmlDocument();
183                                 nsSummary.Load(filename);
184                                 XmlElement ns = nsSummaries.CreateElement("namespace");
185                                 nsSummaries.DocumentElement.AppendChild(ns);
186                                 ns.SetAttribute("name", currentNs);
187                                 ns.InnerText = nsSummary.SelectSingleNode("/Namespace/Docs/summary").InnerText;
188                         }
189                 }
190         }
191         
192         private static void CreateMember(string name, XmlElement input, XmlElement output) {
193                 XmlElement member = output.OwnerDocument.CreateElement("member");
194                 output.AppendChild(member);
195                 
196                 member.SetAttribute("name", name);
197                 
198                 foreach (XmlNode docnode in input.SelectSingleNode("Docs"))
199                         member.AppendChild(output.OwnerDocument.ImportNode(docnode, true));
200         }
201
202         private static void WriteXml(XmlElement element, System.IO.TextWriter output) {
203                 XmlTextWriter writer = new XmlTextWriter(output);
204                 writer.Formatting = Formatting.Indented;
205                 writer.Indentation = 4;
206                 writer.IndentChar = ' ';
207                 element.WriteTo(writer);
208                 output.WriteLine();     
209         }
210 }
211
212 }