update MEF to preview 9
[mcs.git] / tools / mdoc / Mono.Documentation / monodocs2html.cs
blob92e0cd73eafc46964a879223dcbe9fa6b56adff4
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.IO;
6 using System.Linq;
7 using System.Reflection;
8 using System.Text;
9 using System.Xml;
10 using System.Xml.Xsl;
11 using System.Xml.XPath;
13 using Mono.Documentation;
14 using Mono.Options;
16 [assembly: AssemblyTitle("Monodocs-to-HTML")]
17 [assembly: AssemblyCopyright("Copyright (c) 2004 Joshua Tauberer <tauberer@for.net>, released under the GPL.")]
18 [assembly: AssemblyDescription("Convert Monodoc XML documentation to static HTML.")]
20 namespace Mono.Documentation {
22 class MDocToHtmlConverterOptions {
23 public string dest;
24 public string ext = "html";
25 public string onlytype;
26 public string template;
27 public bool dumptemplate;
28 public bool forceUpdate;
29 public HashSet<string> versions = new HashSet<string> ();
32 class MDocToHtmlConverter : MDocCommand {
34 static Dictionary<string, string[]> profiles = new Dictionary<string, string[]>() {
35 // FxVersions----- VsVersions-----
36 { "monotouch", new[]{"0.0.0.0", "2.0.5.0" } },
37 { "net_1_0", new[]{"1.0.3300.0", "7.0.3300.0"} },
38 { "net_1_1", new[]{"1.0.5000.0", "7.0.5000.0"} },
39 { "net_2_0", new[]{"2.0.0.0", "8.0.0.0"} },
40 { "net_3_0", new[]{"2.0.0.0", "3.0.0.0", "8.0.0.0"} },
41 { "net_3_5", new[]{"2.0.0.0", "3.0.0.0", "3.5.0.0", "8.0.0.0"} },
42 { "net_4_0", new[]{"4.0.0.0" } },
43 { "silverlight", new[]{"2.0.5.0", "9.0.0.0"} },
46 public override void Run (IEnumerable<string> args)
48 opts = new MDocToHtmlConverterOptions ();
49 var p = new OptionSet () {
50 { "default-template",
51 "Writes the default XSLT to stdout.",
52 v => opts.dumptemplate = v != null },
53 { "ext=",
54 "The file {EXTENSION} to use for created files. "+
55 "This defaults to \"html\".",
56 v => opts.ext = v },
57 { "force-update",
58 "Always generate new files. If not specified, will only generate a " +
59 "new file if the source .xml file is newer than the current output " +
60 "file.",
61 v => opts.forceUpdate = v != null },
62 { "o|out=",
63 "The {DIRECTORY} to place the generated files and directories.",
64 v => opts.dest = v },
65 { "template=",
66 "An XSLT {FILE} to use to generate the created " +
67 "files.If not specified, uses the template generated by " +
68 "--default-template.",
69 v => opts.template = v },
70 { "with-profile=",
71 "The .NET {PROFILE} to generate documentation for. This is " +
72 "equivalent to using --with-version for all of the " +
73 "versions that a profile uses. Valid profiles are:\n " +
74 string.Join ("\n ", profiles.Keys.OrderBy (v => v).ToArray ()),
75 v => {
76 if (!profiles.ContainsKey (v))
77 throw new ArgumentException (string.Format ("Unsupported profile '{0}'.", v));
78 foreach (var ver in profiles [v.ToLowerInvariant ()])
79 opts.versions.Add (ver);
80 } },
81 { "with-version=",
82 "The assembly {VERSION} to generate documentation for. This allows " +
83 "display of a subset of types/members that correspond to the given " +
84 "assembly version. May be specified multiple times. " +
85 "If not specified, all versions are displayed.",
86 v => opts.versions.Add (v) }
88 List<string> extra = Parse (p, args, "export-html",
89 "[OPTIONS]+ DIRECTORIES",
90 "Export mdoc documentation within DIRECTORIES to HTML.");
91 if (extra == null)
92 return;
93 if (opts.dumptemplate)
94 DumpTemplate ();
95 else
96 ProcessDirectories (extra);
97 opts.onlytype = "ignore"; // remove warning about unused member
100 static MDocToHtmlConverterOptions opts;
102 void ProcessDirectories (List<string> sourceDirectories)
104 if (sourceDirectories.Count == 0 || opts.dest == null || opts.dest == "")
105 throw new ApplicationException("The source and dest options must be specified.");
107 Directory.CreateDirectory(opts.dest);
109 // Load the stylesheets, overview.xml, and resolver
111 XslCompiledTransform overviewxsl = LoadTransform("overview.xsl", sourceDirectories);
112 XslCompiledTransform stylesheet = LoadTransform("stylesheet.xsl", sourceDirectories);
113 XslCompiledTransform template;
114 if (opts.template == null) {
115 template = LoadTransform("defaulttemplate.xsl", sourceDirectories);
116 } else {
117 try {
118 XmlDocument templatexsl = new XmlDocument();
119 templatexsl.Load(opts.template);
120 template = new XslCompiledTransform (DebugOutput);
121 template.Load(templatexsl);
122 } catch (Exception e) {
123 throw new ApplicationException("There was an error loading " + opts.template, e);
127 XmlDocument overview = GetOverview (sourceDirectories);
128 string overviewDest = opts.dest + "/index." + opts.ext;
130 ArrayList extensions = GetExtensionMethods (overview);
132 // Create the master page
133 XsltArgumentList overviewargs = new XsltArgumentList();
134 overviewargs.AddParam("Index", "", overview.CreateNavigator ());
136 var regenIndex = sourceDirectories.Any (
137 d => !DestinationIsNewer (Path.Combine (d, "index.xml"), overviewDest));
138 if (regenIndex) {
139 overviewargs.AddParam("ext", "", opts.ext);
140 overviewargs.AddParam("basepath", "", "./");
141 Generate(overview, overviewxsl, overviewargs, opts.dest + "/index." + opts.ext, template, sourceDirectories);
142 overviewargs.RemoveParam("basepath", "");
144 overviewargs.AddParam("basepath", "", "../");
146 // Create the namespace & type pages
148 XsltArgumentList typeargs = new XsltArgumentList();
149 typeargs.AddParam("ext", "", opts.ext);
150 typeargs.AddParam("basepath", "", "../");
151 typeargs.AddParam("Index", "", overview.CreateNavigator ());
153 foreach (XmlElement ns in overview.SelectNodes("Overview/Types/Namespace")) {
154 string nsname = ns.GetAttribute("Name");
156 if (opts.onlytype != null && !opts.onlytype.StartsWith(nsname + "."))
157 continue;
159 System.IO.DirectoryInfo d = new System.IO.DirectoryInfo(opts.dest + "/" + nsname);
160 if (!d.Exists) d.Create();
162 // Create the NS page
163 string nsDest = opts.dest + "/" + nsname + "/index." + opts.ext;
164 if (regenIndex) {
165 overviewargs.AddParam("namespace", "", nsname);
166 Generate(overview, overviewxsl, overviewargs, nsDest, template, sourceDirectories);
167 overviewargs.RemoveParam("namespace", "");
170 foreach (XmlElement ty in ns.SelectNodes("Type")) {
171 string typefilebase = ty.GetAttribute("Name");
172 string sourceDir = ty.GetAttribute("SourceDirectory");
173 string typename = ty.GetAttribute("DisplayName");
174 if (typename.Length == 0)
175 typename = typefilebase;
177 if (opts.onlytype != null && !(nsname + "." + typename).StartsWith(opts.onlytype))
178 continue;
180 string typefile = CombinePath (sourceDir, nsname, typefilebase + ".xml");
181 if (typefile == null)
182 continue;
184 string destfile = opts.dest + "/" + nsname + "/" + typefilebase + "." + opts.ext;
186 if (DestinationIsNewer (typefile, destfile))
187 // target already exists, and is newer. why regenerate?
188 continue;
190 XmlDocument typexml = new XmlDocument();
191 typexml.Load(typefile);
192 PreserveMembersInVersions (typexml);
193 if (extensions != null) {
194 DocLoader loader = CreateDocLoader (overview);
195 XmlDocUtils.AddExtensionMethods (typexml, extensions, loader);
198 Console.WriteLine(nsname + "." + typename);
200 Generate(typexml, stylesheet, typeargs, destfile, template, sourceDirectories);
205 private static ArrayList GetExtensionMethods (XmlDocument doc)
207 XmlNodeList extensions = doc.SelectNodes ("/Overview/ExtensionMethods/*");
208 if (extensions.Count == 0)
209 return null;
210 ArrayList r = new ArrayList (extensions.Count);
211 foreach (XmlNode n in extensions)
212 r.Add (n);
213 return r;
216 private static void DumpTemplate() {
217 Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream("defaulttemplate.xsl");
218 Stream o = Console.OpenStandardOutput ();
219 byte[] buf = new byte[1024];
220 int r;
221 while ((r = s.Read (buf, 0, buf.Length)) > 0) {
222 o.Write (buf, 0, r);
226 private static void Generate(XmlDocument source, XslCompiledTransform transform, XsltArgumentList args, string output, XslCompiledTransform template, List<string> sourceDirectories) {
227 using (TextWriter textwriter = new StreamWriter(new FileStream(output, FileMode.Create))) {
228 XmlTextWriter writer = new XmlTextWriter(textwriter);
229 writer.Formatting = Formatting.Indented;
230 writer.Indentation = 2;
231 writer.IndentChar = ' ';
233 try {
234 var intermediate = new StringBuilder ();
235 transform.Transform (
236 new XmlNodeReader (source),
237 args,
238 XmlWriter.Create (intermediate, transform.OutputSettings),
239 new ManifestResourceResolver(sourceDirectories.ToArray ()));
240 template.Transform (
241 XmlReader.Create (new StringReader (intermediate.ToString ())),
242 new XsltArgumentList (),
243 new XhtmlWriter (writer),
244 null);
245 } catch (Exception e) {
246 throw new ApplicationException("An error occured while generating " + output, e);
251 private XslCompiledTransform LoadTransform(string name, List<string> sourceDirectories) {
252 try {
253 XmlDocument xsl = new XmlDocument();
254 xsl.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream(name));
256 if (name == "overview.xsl") {
257 // bit of a hack. overview needs the templates in stylesheet
258 // for doc formatting, and rather than write a resolver, I'll
259 // just do the import for it.
261 XmlNode importnode = xsl.DocumentElement.SelectSingleNode("*[name()='xsl:include']");
262 xsl.DocumentElement.RemoveChild(importnode);
264 XmlDocument xsl2 = new XmlDocument();
265 xsl2.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("stylesheet.xsl"));
266 foreach (XmlNode node in xsl2.DocumentElement.ChildNodes)
267 xsl.DocumentElement.AppendChild(xsl.ImportNode(node, true));
270 XslCompiledTransform t = new XslCompiledTransform (DebugOutput);
271 t.Load (
272 xsl,
273 XsltSettings.TrustedXslt,
274 new ManifestResourceResolver (sourceDirectories.ToArray ()));
276 return t;
277 } catch (Exception e) {
278 throw new ApplicationException("Error loading " + name + " from internal resource", e);
282 private static DocLoader CreateDocLoader (XmlDocument overview)
284 Hashtable docs = new Hashtable ();
285 DocLoader loader = delegate (string s) {
286 XmlDocument d = null;
287 if (!docs.ContainsKey (s)) {
288 foreach (XmlNode n in overview.SelectNodes ("//Type")) {
289 string ns = n.ParentNode.Attributes ["Name"].Value;
290 string t = n.Attributes ["Name"].Value;
291 string sd = n.Attributes ["SourceDirectory"].Value;
292 if (s == ns + "." + t.Replace ("+", ".")) {
293 string f = CombinePath (sd, ns, t + ".xml");
294 if (File.Exists (f)) {
295 d = new XmlDocument ();
296 d.Load (f);
298 docs.Add (s, d);
299 break;
303 else
304 d = (XmlDocument) docs [s];
305 return d;
307 return loader;
310 static string CombinePath (params string[] paths)
312 if (paths == null)
313 return null;
314 if (paths.Length == 1)
315 return paths [0];
316 var path = Path.Combine (paths [0], paths [1]);
317 for (int i = 2; i < paths.Length; ++i)
318 path = Path.Combine (path, paths [i]);
319 return path;
322 private XmlDocument GetOverview (IEnumerable<string> directories)
324 var index = new XmlDocument ();
326 var overview = index.CreateElement ("Overview");
327 var assemblies= index.CreateElement ("Assemblies");
328 var types = index.CreateElement ("Types");
329 var ems = index.CreateElement ("ExtensionMethods");
331 index.AppendChild (overview);
332 overview.AppendChild (assemblies);
333 overview.AppendChild (types);
334 overview.AppendChild (ems);
336 bool first = true;
338 foreach (var dir in directories) {
339 var indexFile = Path.Combine (dir, "index.xml");
340 try {
341 var doc = new XmlDocument ();
342 doc.Load (indexFile);
343 if (first) {
344 var c = doc.SelectSingleNode ("/Overview/Copyright");
345 var t = doc.SelectSingleNode ("/Overview/Title");
346 var r = doc.SelectSingleNode ("/Overview/Remarks");
347 if (c != null && t != null && r != null) {
348 var e = index.CreateElement ("Copyright");
349 e.InnerXml = c.InnerXml;
350 overview.AppendChild (e);
352 e = index.CreateElement ("Title");
353 e.InnerXml = t.InnerXml;
354 overview.AppendChild (e);
356 e = index.CreateElement ("Remarks");
357 e.InnerXml = r.InnerXml;
358 overview.AppendChild (e);
360 first = false;
363 AddAssemblies (assemblies, doc);
364 AddTypes (types, doc, dir);
365 AddChildren (ems, doc, "/Overview/ExtensionMethods");
367 catch (Exception e) {
368 Message (TraceLevel.Warning, "Could not load documentation index '{0}': {1}",
369 indexFile, e.Message);
373 return index;
376 static void AddChildren (XmlNode dest, XmlDocument source, string path)
378 var n = source.SelectSingleNode (path);
379 if (n != null)
380 foreach (XmlNode c in n.ChildNodes)
381 dest.AppendChild (dest.OwnerDocument.ImportNode (c, true));
384 static void AddAssemblies (XmlNode dest, XmlDocument source)
386 foreach (XmlNode asm in source.SelectNodes ("/Overview/Assemblies/Assembly")) {
387 var n = asm.Attributes ["Name"].Value;
388 var v = asm.Attributes ["Version"].Value;
389 if (dest.SelectSingleNode (string.Format ("Assembly[@Name='{0}'][@Value='{1}']", n, v)) == null) {
390 dest.AppendChild (dest.OwnerDocument.ImportNode (asm, true));
395 static void AddTypes (XmlNode dest, XmlDocument source, string sourceDirectory)
397 var types = source.SelectSingleNode ("/Overview/Types");
398 if (types == null)
399 return;
400 foreach (XmlNode ns in types.ChildNodes) {
401 var n = ns.Attributes ["Name"].Value;
402 var nsd = dest.SelectSingleNode (string.Format ("Namespace[@Name='{0}']", n));
403 if (nsd == null) {
404 nsd = dest.OwnerDocument.CreateElement ("Namespace");
405 AddAttribute (nsd, "Name", n);
406 dest.AppendChild (nsd);
408 foreach (XmlNode t in ns.ChildNodes) {
409 if (!TypeInVersions (sourceDirectory, n, t))
410 continue;
411 var c = dest.OwnerDocument.ImportNode (t, true);
412 AddAttribute (c, "SourceDirectory", sourceDirectory);
413 nsd.AppendChild (c);
415 if (nsd.ChildNodes.Count == 0)
416 dest.RemoveChild (nsd);
420 static bool TypeInVersions (string sourceDirectory, string ns, XmlNode type)
422 if (opts.versions.Count == 0)
423 return true;
424 var file = Path.Combine (Path.Combine (sourceDirectory, ns), type.Attributes ["Name"].Value + ".xml");
425 if (!File.Exists (file))
426 return false;
427 XPathDocument doc;
428 using (var s = File.OpenText (file))
429 doc = new XPathDocument (s);
430 return MemberInVersions (doc.CreateNavigator ().SelectSingleNode ("/Type"));
433 static bool MemberInVersions (XPathNavigator nav)
435 return nav.Select ("AssemblyInfo/AssemblyVersion")
436 .Cast<object> ()
437 .Any (v => opts.versions.Contains (v.ToString ()));
440 static void AddAttribute (XmlNode self, string name, string value)
442 var a = self.OwnerDocument.CreateAttribute (name);
443 a.Value = value;
444 self.Attributes.Append (a);
447 private static bool DestinationIsNewer (string source, string dest)
449 return !opts.forceUpdate && File.Exists (dest) &&
450 File.GetLastWriteTime (source) < File.GetLastWriteTime (dest);
453 private static void PreserveMembersInVersions (XmlDocument doc)
455 if (opts.versions.Count == 0)
456 return;
457 var remove = new List<XmlNode>();
458 foreach (XmlNode m in doc.SelectNodes ("/Type/Members/Member")) {
459 if (!MemberInVersions (m.CreateNavigator ()))
460 remove.Add (m);
462 XmlNode members = doc.SelectSingleNode ("/Type/Members");
463 foreach (var m in remove)
464 members.RemoveChild (m);