2 using System
.Collections
;
3 using System
.Collections
.Generic
;
4 using System
.Diagnostics
;
7 using System
.Reflection
;
11 using System
.Xml
.XPath
;
13 using Mono
.Documentation
;
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
{
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 () {
51 "Writes the default XSLT to stdout.",
52 v
=> opts
.dumptemplate
= v
!= null },
54 "The file {EXTENSION} to use for created files. "+
55 "This defaults to \"html\".",
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 " +
61 v
=> opts
.forceUpdate
= v
!= null },
63 "The {DIRECTORY} to place the generated files and directories.",
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
},
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 ()),
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
);
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.");
93 if (opts
.dumptemplate
)
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
);
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
));
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
+ "."))
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
;
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
))
180 string typefile
= CombinePath (sourceDir
, nsname
, typefilebase
+ ".xml");
181 if (typefile
== null)
184 string destfile
= opts
.dest
+ "/" + nsname
+ "/" + typefilebase
+ "." + opts
.ext
;
186 if (DestinationIsNewer (typefile
, destfile
))
187 // target already exists, and is newer. why regenerate?
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)
210 ArrayList r
= new ArrayList (extensions
.Count
);
211 foreach (XmlNode n
in extensions
)
216 private static void DumpTemplate() {
217 Stream s
= Assembly
.GetExecutingAssembly().GetManifestResourceStream("defaulttemplate.xsl");
218 Stream o
= Console
.OpenStandardOutput ();
219 byte[] buf
= new byte[1024];
221 while ((r
= s
.Read (buf
, 0, buf
.Length
)) > 0) {
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
= ' ';
234 var intermediate
= new StringBuilder ();
235 transform
.Transform (
236 new XmlNodeReader (source
),
238 XmlWriter
.Create (intermediate
, transform
.OutputSettings
),
239 new ManifestResourceResolver(sourceDirectories
.ToArray ()));
241 XmlReader
.Create (new StringReader (intermediate
.ToString ())),
242 new XsltArgumentList (),
243 new XhtmlWriter (writer
),
245 } catch (Exception e
) {
246 throw new ApplicationException("An error occured while generating " + output
, e
);
251 private XslCompiledTransform
LoadTransform(string name
, List
<string> sourceDirectories
) {
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
);
273 XsltSettings
.TrustedXslt
,
274 new ManifestResourceResolver (sourceDirectories
.ToArray ()));
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 ();
304 d
= (XmlDocument
) docs
[s
];
310 static string CombinePath (params string[] paths
)
314 if (paths
.Length
== 1)
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
]);
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
);
338 foreach (var dir
in directories
) {
339 var indexFile
= Path
.Combine (dir
, "index.xml");
341 var doc
= new XmlDocument ();
342 doc
.Load (indexFile
);
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
);
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
);
376 static void AddChildren (XmlNode dest
, XmlDocument source
, string path
)
378 var n
= source
.SelectSingleNode (path
);
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");
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
));
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
))
411 var c
= dest
.OwnerDocument
.ImportNode (t
, true);
412 AddAttribute (c
, "SourceDirectory", sourceDirectory
);
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)
424 var file
= Path
.Combine (Path
.Combine (sourceDirectory
, ns
), type
.Attributes
["Name"].Value
+ ".xml");
425 if (!File
.Exists (file
))
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")
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
);
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)
457 var remove = new List
<XmlNode
>();
458 foreach (XmlNode m
in doc
.SelectNodes ("/Type/Members/Member")) {
459 if (!MemberInVersions (m
.CreateNavigator ()))
462 XmlNode members
= doc
.SelectSingleNode ("/Type/Members");
463 foreach (var m
in remove)
464 members
.RemoveChild (m
);