PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/mcs/tools/mdoc/Mono.Documentation/monodocs2html.cs

https://bitbucket.org/steenlund/mono-2.6.7-for-amiga
C# | 485 lines | 410 code | 66 blank | 9 comment | 76 complexity | f7238c28a090e84e12228ce3f3868075 MD5 | raw file
Possible License(s): LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0, LGPL-2.1
  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.Xml;
  9. using System.Xml.Xsl;
  10. using System.Xml.XPath;
  11. using Mono.Documentation;
  12. using Mono.Options;
  13. [assembly: AssemblyTitle("Monodocs-to-HTML")]
  14. [assembly: AssemblyCopyright("Copyright (c) 2004 Joshua Tauberer <tauberer@for.net>, released under the GPL.")]
  15. [assembly: AssemblyDescription("Convert Monodoc XML documentation to static HTML.")]
  16. namespace Mono.Documentation {
  17. class MDocToHtmlConverterOptions {
  18. public string dest;
  19. public string ext = "html";
  20. public string onlytype;
  21. public string template;
  22. public bool dumptemplate;
  23. public bool forceUpdate;
  24. public HashSet<string> versions = new HashSet<string> ();
  25. }
  26. class MDocToHtmlConverter : MDocCommand {
  27. static Dictionary<string, string[]> profiles = new Dictionary<string, string[]>() {
  28. // FxVersions----- VsVersions-----
  29. { "monotouch", new[]{"0.0.0.0", "2.0.5.0" } },
  30. { "net_1_0", new[]{"1.0.3300.0", "7.0.3300.0"} },
  31. { "net_1_1", new[]{"1.0.5000.0", "7.0.5000.0"} },
  32. { "net_2_0", new[]{"2.0.0.0", "8.0.0.0"} },
  33. { "net_3_0", new[]{"2.0.0.0", "3.0.0.0", "8.0.0.0"} },
  34. { "net_3_5", new[]{"2.0.0.0", "3.0.0.0", "3.5.0.0", "8.0.0.0"} },
  35. { "net_4_0", new[]{"4.0.0.0" } },
  36. { "silverlight", new[]{"2.0.5.0", "9.0.0.0"} },
  37. };
  38. public override void Run (IEnumerable<string> args)
  39. {
  40. opts = new MDocToHtmlConverterOptions ();
  41. var p = new OptionSet () {
  42. { "default-template",
  43. "Writes the default XSLT to stdout.",
  44. v => opts.dumptemplate = v != null },
  45. { "ext=",
  46. "The file {EXTENSION} to use for created files. "+
  47. "This defaults to \"html\".",
  48. v => opts.ext = v },
  49. { "force-update",
  50. "Always generate new files. If not specified, will only generate a " +
  51. "new file if the source .xml file is newer than the current output " +
  52. "file.",
  53. v => opts.forceUpdate = v != null },
  54. { "o|out=",
  55. "The {DIRECTORY} to place the generated files and directories.",
  56. v => opts.dest = v },
  57. { "template=",
  58. "An XSLT {FILE} to use to generate the created " +
  59. "files.If not specified, uses the template generated by " +
  60. "--default-template.",
  61. v => opts.template = v },
  62. { "with-profile=",
  63. "The .NET {PROFILE} to generate documentation for. This is " +
  64. "equivalent to using --with-version for all of the " +
  65. "versions that a profile uses. Valid profiles are:\n " +
  66. string.Join ("\n ", profiles.Keys.OrderBy (v => v).ToArray ()),
  67. v => {
  68. if (!profiles.ContainsKey (v))
  69. throw new ArgumentException (string.Format ("Unsupported profile '{0}'.", v));
  70. foreach (var ver in profiles [v.ToLowerInvariant ()])
  71. opts.versions.Add (ver);
  72. } },
  73. { "with-version=",
  74. "The assembly {VERSION} to generate documentation for. This allows " +
  75. "display of a subset of types/members that correspond to the given " +
  76. "assembly version. May be specified multiple times. " +
  77. "If not specified, all versions are displayed.",
  78. v => opts.versions.Add (v) }
  79. };
  80. List<string> extra = Parse (p, args, "export-html",
  81. "[OPTIONS]+ DIRECTORIES",
  82. "Export mdoc documentation within DIRECTORIES to HTML.");
  83. if (extra == null)
  84. return;
  85. if (opts.dumptemplate)
  86. DumpTemplate ();
  87. else
  88. ProcessDirectories (extra);
  89. opts.onlytype = "ignore"; // remove warning about unused member
  90. }
  91. static MDocToHtmlConverterOptions opts;
  92. void ProcessDirectories (List<string> sourceDirectories)
  93. {
  94. if (sourceDirectories.Count == 0 || opts.dest == null || opts.dest == "")
  95. throw new ApplicationException("The source and dest options must be specified.");
  96. Directory.CreateDirectory(opts.dest);
  97. // Load the stylesheets, overview.xml, and resolver
  98. XslTransform overviewxsl = LoadTransform("overview.xsl", sourceDirectories);
  99. XslTransform stylesheet = LoadTransform("stylesheet.xsl", sourceDirectories);
  100. XslTransform template;
  101. if (opts.template == null) {
  102. template = LoadTransform("defaulttemplate.xsl", sourceDirectories);
  103. } else {
  104. try {
  105. XmlDocument templatexsl = new XmlDocument();
  106. templatexsl.Load(opts.template);
  107. template = new XslTransform();
  108. template.Load(templatexsl);
  109. } catch (Exception e) {
  110. throw new ApplicationException("There was an error loading " + opts.template, e);
  111. }
  112. }
  113. XmlDocument overview = GetOverview (sourceDirectories);
  114. ArrayList extensions = GetExtensionMethods (overview);
  115. // Create the master page
  116. XsltArgumentList overviewargs = new XsltArgumentList();
  117. var regenIndex = ShouldRegenIndexes (opts, overview, sourceDirectories);
  118. if (regenIndex) {
  119. overviewargs.AddParam("ext", "", opts.ext);
  120. overviewargs.AddParam("basepath", "", "./");
  121. Generate(overview, overviewxsl, overviewargs, opts.dest + "/index." + opts.ext, template, sourceDirectories);
  122. overviewargs.RemoveParam("basepath", "");
  123. }
  124. overviewargs.AddParam("basepath", "", "../");
  125. overviewargs.AddParam("Index", "", overview.CreateNavigator ());
  126. // Create the namespace & type pages
  127. XsltArgumentList typeargs = new XsltArgumentList();
  128. typeargs.AddParam("ext", "", opts.ext);
  129. typeargs.AddParam("basepath", "", "../");
  130. typeargs.AddParam("Index", "", overview.CreateNavigator ());
  131. foreach (XmlElement ns in overview.SelectNodes("Overview/Types/Namespace")) {
  132. string nsname = ns.GetAttribute("Name");
  133. if (opts.onlytype != null && !opts.onlytype.StartsWith(nsname + "."))
  134. continue;
  135. System.IO.DirectoryInfo d = new System.IO.DirectoryInfo(opts.dest + "/" + nsname);
  136. if (!d.Exists) d.Create();
  137. // Create the NS page
  138. string nsDest = opts.dest + "/" + nsname + "/index." + opts.ext;
  139. if (regenIndex) {
  140. overviewargs.AddParam("namespace", "", nsname);
  141. Generate(overview, overviewxsl, overviewargs, nsDest, template, sourceDirectories);
  142. overviewargs.RemoveParam("namespace", "");
  143. }
  144. foreach (XmlElement ty in ns.SelectNodes("Type")) {
  145. string typename, typefile, destfile;
  146. GetTypePaths (opts, ty, out typename, out typefile, out destfile);
  147. if (DestinationIsNewer (typefile, destfile))
  148. // target already exists, and is newer. why regenerate?
  149. continue;
  150. XmlDocument typexml = new XmlDocument();
  151. typexml.Load(typefile);
  152. PreserveMembersInVersions (typexml);
  153. if (extensions != null) {
  154. DocLoader loader = CreateDocLoader (overview);
  155. XmlDocUtils.AddExtensionMethods (typexml, extensions, loader);
  156. }
  157. Console.WriteLine(nsname + "." + typename);
  158. Generate(typexml, stylesheet, typeargs, destfile, template, sourceDirectories);
  159. }
  160. }
  161. }
  162. private static ArrayList GetExtensionMethods (XmlDocument doc)
  163. {
  164. XmlNodeList extensions = doc.SelectNodes ("/Overview/ExtensionMethods/*");
  165. if (extensions.Count == 0)
  166. return null;
  167. ArrayList r = new ArrayList (extensions.Count);
  168. foreach (XmlNode n in extensions)
  169. r.Add (n);
  170. return r;
  171. }
  172. static bool ShouldRegenIndexes (MDocToHtmlConverterOptions opts, XmlDocument overview, List<string> sourceDirectories)
  173. {
  174. string overviewDest = opts.dest + "/index." + opts.ext;
  175. if (sourceDirectories.Any (
  176. d => !DestinationIsNewer (Path.Combine (d, "index.xml"), overviewDest)))
  177. return true;
  178. foreach (XmlElement type in overview.SelectNodes("Overview/Types/Namespace/Type")) {
  179. string _, srcfile, destfile;
  180. GetTypePaths (opts, type, out _, out srcfile, out destfile);
  181. if (srcfile == null || destfile == null)
  182. continue;
  183. if (DestinationIsNewer (srcfile, destfile))
  184. return true;
  185. }
  186. return false;
  187. }
  188. static void GetTypePaths (MDocToHtmlConverterOptions opts, XmlElement type, out string typename, out string srcfile, out string destfile)
  189. {
  190. srcfile = null;
  191. destfile = null;
  192. string nsname = type.ParentNode.Attributes ["Name"].Value;
  193. string typefilebase = type.GetAttribute("Name");
  194. string sourceDir = type.GetAttribute("SourceDirectory");
  195. typename = type.GetAttribute("DisplayName");
  196. if (typename.Length == 0)
  197. typename = typefilebase;
  198. if (opts.onlytype != null && !(nsname + "." + typename).StartsWith(opts.onlytype))
  199. return;
  200. srcfile = CombinePath (sourceDir, nsname, typefilebase + ".xml");
  201. if (srcfile == null)
  202. return;
  203. destfile = CombinePath (opts.dest, nsname, typefilebase + "." + opts.ext);
  204. }
  205. private static void DumpTemplate() {
  206. Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream("defaulttemplate.xsl");
  207. Stream o = Console.OpenStandardOutput ();
  208. byte[] buf = new byte[1024];
  209. int r;
  210. while ((r = s.Read (buf, 0, buf.Length)) > 0) {
  211. o.Write (buf, 0, r);
  212. }
  213. }
  214. private static void Generate(XmlDocument source, XslTransform transform, XsltArgumentList args, string output, XslTransform template, List<string> sourceDirectories) {
  215. using (TextWriter textwriter = new StreamWriter(new FileStream(output, FileMode.Create))) {
  216. XmlTextWriter writer = new XmlTextWriter(textwriter);
  217. writer.Formatting = Formatting.Indented;
  218. writer.Indentation = 2;
  219. writer.IndentChar = ' ';
  220. try {
  221. XmlDocument intermediate = new XmlDocument();
  222. intermediate.PreserveWhitespace = true;
  223. intermediate.Load(transform.Transform(source, args, new ManifestResourceResolver(sourceDirectories.ToArray ()))); // FIXME?
  224. template.Transform(intermediate, new XsltArgumentList(), new XhtmlWriter (writer), null);
  225. } catch (Exception e) {
  226. throw new ApplicationException("An error occured while generating " + output, e);
  227. }
  228. }
  229. }
  230. private static XslTransform LoadTransform(string name, List<string> sourceDirectories) {
  231. try {
  232. XmlDocument xsl = new XmlDocument();
  233. xsl.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream(name));
  234. if (name == "overview.xsl") {
  235. // bit of a hack. overview needs the templates in stylesheet
  236. // for doc formatting, and rather than write a resolver, I'll
  237. // just do the import for it.
  238. XmlNode importnode = xsl.DocumentElement.SelectSingleNode("*[name()='xsl:include']");
  239. xsl.DocumentElement.RemoveChild(importnode);
  240. XmlDocument xsl2 = new XmlDocument();
  241. xsl2.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("stylesheet.xsl"));
  242. foreach (XmlNode node in xsl2.DocumentElement.ChildNodes)
  243. xsl.DocumentElement.AppendChild(xsl.ImportNode(node, true));
  244. }
  245. XslTransform t = new XslTransform();
  246. t.Load (xsl, new ManifestResourceResolver (sourceDirectories.ToArray ())); // FIXME?
  247. return t;
  248. } catch (Exception e) {
  249. throw new ApplicationException("Error loading " + name + " from internal resource", e);
  250. }
  251. }
  252. private static DocLoader CreateDocLoader (XmlDocument overview)
  253. {
  254. Hashtable docs = new Hashtable ();
  255. DocLoader loader = delegate (string s) {
  256. XmlDocument d = null;
  257. if (!docs.ContainsKey (s)) {
  258. foreach (XmlNode n in overview.SelectNodes ("//Type")) {
  259. string ns = n.ParentNode.Attributes ["Name"].Value;
  260. string t = n.Attributes ["Name"].Value;
  261. string sd = n.Attributes ["SourceDirectory"].Value;
  262. if (s == ns + "." + t.Replace ("+", ".")) {
  263. string f = CombinePath (sd, ns, t + ".xml");
  264. if (File.Exists (f)) {
  265. d = new XmlDocument ();
  266. d.Load (f);
  267. }
  268. docs.Add (s, d);
  269. break;
  270. }
  271. }
  272. }
  273. else
  274. d = (XmlDocument) docs [s];
  275. return d;
  276. };
  277. return loader;
  278. }
  279. static string CombinePath (params string[] paths)
  280. {
  281. if (paths == null)
  282. return null;
  283. if (paths.Length == 1)
  284. return paths [0];
  285. var path = Path.Combine (paths [0], paths [1]);
  286. for (int i = 2; i < paths.Length; ++i)
  287. path = Path.Combine (path, paths [i]);
  288. return path;
  289. }
  290. private XmlDocument GetOverview (IEnumerable<string> directories)
  291. {
  292. var index = new XmlDocument ();
  293. var overview = index.CreateElement ("Overview");
  294. var assemblies= index.CreateElement ("Assemblies");
  295. var types = index.CreateElement ("Types");
  296. var ems = index.CreateElement ("ExtensionMethods");
  297. index.AppendChild (overview);
  298. overview.AppendChild (assemblies);
  299. overview.AppendChild (types);
  300. overview.AppendChild (ems);
  301. bool first = true;
  302. foreach (var dir in directories) {
  303. var indexFile = Path.Combine (dir, "index.xml");
  304. try {
  305. var doc = new XmlDocument ();
  306. doc.Load (indexFile);
  307. if (first) {
  308. var c = doc.SelectSingleNode ("/Overview/Copyright");
  309. var t = doc.SelectSingleNode ("/Overview/Title");
  310. var r = doc.SelectSingleNode ("/Overview/Remarks");
  311. if (c != null && t != null && r != null) {
  312. var e = index.CreateElement ("Copyright");
  313. e.InnerXml = c.InnerXml;
  314. overview.AppendChild (e);
  315. e = index.CreateElement ("Title");
  316. e.InnerXml = t.InnerXml;
  317. overview.AppendChild (e);
  318. e = index.CreateElement ("Remarks");
  319. e.InnerXml = r.InnerXml;
  320. overview.AppendChild (e);
  321. first = false;
  322. }
  323. }
  324. AddAssemblies (assemblies, doc);
  325. AddTypes (types, doc, dir);
  326. AddChildren (ems, doc, "/Overview/ExtensionMethods");
  327. }
  328. catch (Exception e) {
  329. Message (TraceLevel.Warning, "Could not load documentation index '{0}': {1}",
  330. indexFile, e.Message);
  331. }
  332. }
  333. return index;
  334. }
  335. static void AddChildren (XmlNode dest, XmlDocument source, string path)
  336. {
  337. var n = source.SelectSingleNode (path);
  338. if (n != null)
  339. foreach (XmlNode c in n.ChildNodes)
  340. dest.AppendChild (dest.OwnerDocument.ImportNode (c, true));
  341. }
  342. static void AddAssemblies (XmlNode dest, XmlDocument source)
  343. {
  344. foreach (XmlNode asm in source.SelectNodes ("/Overview/Assemblies/Assembly")) {
  345. var n = asm.Attributes ["Name"].Value;
  346. var v = asm.Attributes ["Version"].Value;
  347. if (dest.SelectSingleNode (string.Format ("Assembly[@Name='{0}'][@Value='{1}']", n, v)) == null) {
  348. dest.AppendChild (dest.OwnerDocument.ImportNode (asm, true));
  349. }
  350. }
  351. }
  352. static void AddTypes (XmlNode dest, XmlDocument source, string sourceDirectory)
  353. {
  354. var types = source.SelectSingleNode ("/Overview/Types");
  355. if (types == null)
  356. return;
  357. foreach (XmlNode ns in types.ChildNodes) {
  358. var n = ns.Attributes ["Name"].Value;
  359. var nsd = dest.SelectSingleNode (string.Format ("Namespace[@Name='{0}']", n));
  360. if (nsd == null) {
  361. nsd = dest.OwnerDocument.CreateElement ("Namespace");
  362. AddAttribute (nsd, "Name", n);
  363. dest.AppendChild (nsd);
  364. }
  365. foreach (XmlNode t in ns.ChildNodes) {
  366. if (!TypeInVersions (sourceDirectory, n, t))
  367. continue;
  368. var c = dest.OwnerDocument.ImportNode (t, true);
  369. AddAttribute (c, "SourceDirectory", sourceDirectory);
  370. nsd.AppendChild (c);
  371. }
  372. if (nsd.ChildNodes.Count == 0)
  373. dest.RemoveChild (nsd);
  374. }
  375. }
  376. static bool TypeInVersions (string sourceDirectory, string ns, XmlNode type)
  377. {
  378. if (opts.versions.Count == 0)
  379. return true;
  380. var file = Path.Combine (Path.Combine (sourceDirectory, ns), type.Attributes ["Name"].Value + ".xml");
  381. if (!File.Exists (file))
  382. return false;
  383. XPathDocument doc;
  384. using (var s = File.OpenText (file))
  385. doc = new XPathDocument (s);
  386. return MemberInVersions (doc.CreateNavigator ().SelectSingleNode ("/Type"));
  387. }
  388. static bool MemberInVersions (XPathNavigator nav)
  389. {
  390. return nav.Select ("AssemblyInfo/AssemblyVersion")
  391. .Cast<object> ()
  392. .Any (v => opts.versions.Contains (v.ToString ()));
  393. }
  394. static void AddAttribute (XmlNode self, string name, string value)
  395. {
  396. var a = self.OwnerDocument.CreateAttribute (name);
  397. a.Value = value;
  398. self.Attributes.Append (a);
  399. }
  400. private static bool DestinationIsNewer (string source, string dest)
  401. {
  402. return !opts.forceUpdate && File.Exists (dest) &&
  403. File.GetLastWriteTime (source) < File.GetLastWriteTime (dest);
  404. }
  405. private static void PreserveMembersInVersions (XmlDocument doc)
  406. {
  407. if (opts.versions.Count == 0)
  408. return;
  409. var remove = new List<XmlNode>();
  410. foreach (XmlNode m in doc.SelectNodes ("/Type/Members/Member")) {
  411. if (!MemberInVersions (m.CreateNavigator ()))
  412. remove.Add (m);
  413. }
  414. XmlNode members = doc.SelectSingleNode ("/Type/Members");
  415. foreach (var m in remove)
  416. members.RemoveChild (m);
  417. }
  418. }
  419. }