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 
Run(IEnumerable<string> args)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 
Run(string file, IEnumerable<string> dirs)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 
Process(string basepath, Dictionary<string, XmlElement> outputfiles, XmlDocument nsSummaries, bool implicitFiles)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 
GetCref(XmlElement member)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 
MakeArgs(XmlElement member)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 
AddNamespaceSummary(XmlDocument nsSummaries, string basepath, string currentNs)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 
CreateMember(string name, XmlElement input, XmlElement output)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 
WriteXml(XmlElement element, System.IO.TextWriter output)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 }
213