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
);
129 ArrayList extensions
= GetExtensionMethods (overview
);
131 // Create the master page
132 XsltArgumentList overviewargs
= new XsltArgumentList();
133 overviewargs
.AddParam("Index", "", overview
.CreateNavigator ());
135 var regenIndex
= ShouldRegenIndexes (opts
, overview
, sourceDirectories
);
137 overviewargs
.AddParam("ext", "", opts
.ext
);
138 overviewargs
.AddParam("basepath", "", "./");
139 Generate(overview
, overviewxsl
, overviewargs
, opts
.dest
+ "/index." + opts
.ext
, template
, sourceDirectories
);
140 overviewargs
.RemoveParam("basepath", "");
142 overviewargs
.AddParam("basepath", "", "../");
144 // Create the namespace & type pages
146 XsltArgumentList typeargs
= new XsltArgumentList();
147 typeargs
.AddParam("ext", "", opts
.ext
);
148 typeargs
.AddParam("basepath", "", "../");
149 typeargs
.AddParam("Index", "", overview
.CreateNavigator ());
151 foreach (XmlElement ns
in overview
.SelectNodes("Overview/Types/Namespace")) {
152 string nsname
= ns
.GetAttribute("Name");
154 if (opts
.onlytype
!= null && !opts
.onlytype
.StartsWith(nsname
+ "."))
157 System
.IO
.DirectoryInfo d
= new System
.IO
.DirectoryInfo(opts
.dest
+ "/" + nsname
);
158 if (!d
.Exists
) d
.Create();
160 // Create the NS page
161 string nsDest
= opts
.dest
+ "/" + nsname
+ "/index." + opts
.ext
;
163 overviewargs
.AddParam("namespace", "", nsname
);
164 Generate(overview
, overviewxsl
, overviewargs
, nsDest
, template
, sourceDirectories
);
165 overviewargs
.RemoveParam("namespace", "");
168 foreach (XmlElement ty
in ns
.SelectNodes("Type")) {
169 string typename
, typefile
, destfile
;
170 GetTypePaths (opts
, ty
, out typename
, out typefile
, out destfile
);
172 if (DestinationIsNewer (typefile
, destfile
))
173 // target already exists, and is newer. why regenerate?
176 XmlDocument typexml
= new XmlDocument();
177 typexml
.Load(typefile
);
178 PreserveMembersInVersions (typexml
);
179 if (extensions
!= null) {
180 DocLoader loader
= CreateDocLoader (overview
);
181 XmlDocUtils
.AddExtensionMethods (typexml
, extensions
, loader
);
184 Console
.WriteLine(nsname
+ "." + typename
);
186 Generate(typexml
, stylesheet
, typeargs
, destfile
, template
, sourceDirectories
);
191 private static ArrayList
GetExtensionMethods (XmlDocument doc
)
193 XmlNodeList extensions
= doc
.SelectNodes ("/Overview/ExtensionMethods/*");
194 if (extensions
.Count
== 0)
196 ArrayList r
= new ArrayList (extensions
.Count
);
197 foreach (XmlNode n
in extensions
)
202 static bool ShouldRegenIndexes (MDocToHtmlConverterOptions opts
, XmlDocument overview
, List
<string> sourceDirectories
)
204 string overviewDest
= opts
.dest
+ "/index." + opts
.ext
;
205 if (sourceDirectories
.Any (
206 d
=> !DestinationIsNewer (Path
.Combine (d
, "index.xml"), overviewDest
)))
209 foreach (XmlElement type
in overview
.SelectNodes("Overview/Types/Namespace/Type")) {
210 string _
, srcfile
, destfile
;
211 GetTypePaths (opts
, type
, out _
, out srcfile
, out destfile
);
213 if (srcfile
== null || destfile
== null)
215 if (DestinationIsNewer (srcfile
, destfile
))
222 static void GetTypePaths (MDocToHtmlConverterOptions opts
, XmlElement type
, out string typename
, out string srcfile
, out string destfile
)
227 string nsname
= type
.ParentNode
.Attributes
["Name"].Value
;
228 string typefilebase
= type
.GetAttribute("Name");
229 string sourceDir
= type
.GetAttribute("SourceDirectory");
230 typename
= type
.GetAttribute("DisplayName");
231 if (typename
.Length
== 0)
232 typename
= typefilebase
;
234 if (opts
.onlytype
!= null && !(nsname
+ "." + typename
).StartsWith(opts
.onlytype
))
237 srcfile
= CombinePath (sourceDir
, nsname
, typefilebase
+ ".xml");
241 destfile
= CombinePath (opts
.dest
, nsname
, typefilebase
+ "." + opts
.ext
);
244 private static void DumpTemplate() {
245 Stream s
= Assembly
.GetExecutingAssembly().GetManifestResourceStream("defaulttemplate.xsl");
246 Stream o
= Console
.OpenStandardOutput ();
247 byte[] buf
= new byte[1024];
249 while ((r
= s
.Read (buf
, 0, buf
.Length
)) > 0) {
254 private static void Generate(XmlDocument source
, XslCompiledTransform transform
, XsltArgumentList args
, string output
, XslCompiledTransform template
, List
<string> sourceDirectories
) {
255 using (TextWriter textwriter
= new StreamWriter(new FileStream(output
, FileMode
.Create
))) {
256 XmlTextWriter writer
= new XmlTextWriter(textwriter
);
257 writer
.Formatting
= Formatting
.Indented
;
258 writer
.Indentation
= 2;
259 writer
.IndentChar
= ' ';
262 var intermediate
= new StringBuilder ();
263 transform
.Transform (
264 new XmlNodeReader (source
),
266 XmlWriter
.Create (intermediate
, transform
.OutputSettings
),
267 new ManifestResourceResolver(sourceDirectories
.ToArray ()));
269 XmlReader
.Create (new StringReader (intermediate
.ToString ())),
270 new XsltArgumentList (),
271 new XhtmlWriter (writer
),
273 } catch (Exception e
) {
274 throw new ApplicationException("An error occured while generating " + output
, e
);
279 private XslCompiledTransform
LoadTransform(string name
, List
<string> sourceDirectories
) {
281 XmlDocument xsl
= new XmlDocument();
282 xsl
.Load(Assembly
.GetExecutingAssembly().GetManifestResourceStream(name
));
284 if (name
== "overview.xsl") {
285 // bit of a hack. overview needs the templates in stylesheet
286 // for doc formatting, and rather than write a resolver, I'll
287 // just do the import for it.
289 XmlNode importnode
= xsl
.DocumentElement
.SelectSingleNode("*[name()='xsl:include']");
290 xsl
.DocumentElement
.RemoveChild(importnode
);
292 XmlDocument xsl2
= new XmlDocument();
293 xsl2
.Load(Assembly
.GetExecutingAssembly().GetManifestResourceStream("stylesheet.xsl"));
294 foreach (XmlNode node
in xsl2
.DocumentElement
.ChildNodes
)
295 xsl
.DocumentElement
.AppendChild(xsl
.ImportNode(node
, true));
298 XslCompiledTransform t
= new XslCompiledTransform (DebugOutput
);
301 XsltSettings
.TrustedXslt
,
302 new ManifestResourceResolver (sourceDirectories
.ToArray ()));
305 } catch (Exception e
) {
306 throw new ApplicationException("Error loading " + name
+ " from internal resource", e
);
310 private static DocLoader
CreateDocLoader (XmlDocument overview
)
312 Hashtable docs
= new Hashtable ();
313 DocLoader loader
= delegate (string s
) {
314 XmlDocument d
= null;
315 if (!docs
.ContainsKey (s
)) {
316 foreach (XmlNode n
in overview
.SelectNodes ("//Type")) {
317 string ns
= n
.ParentNode
.Attributes
["Name"].Value
;
318 string t
= n
.Attributes
["Name"].Value
;
319 string sd
= n
.Attributes
["SourceDirectory"].Value
;
320 if (s
== ns
+ "." + t
.Replace ("+", ".")) {
321 string f
= CombinePath (sd
, ns
, t
+ ".xml");
322 if (File
.Exists (f
)) {
323 d
= new XmlDocument ();
332 d
= (XmlDocument
) docs
[s
];
338 static string CombinePath (params string[] paths
)
342 if (paths
.Length
== 1)
344 var path
= Path
.Combine (paths
[0], paths
[1]);
345 for (int i
= 2; i
< paths
.Length
; ++i
)
346 path
= Path
.Combine (path
, paths
[i
]);
350 private XmlDocument
GetOverview (IEnumerable
<string> directories
)
352 var index
= new XmlDocument ();
354 var overview
= index
.CreateElement ("Overview");
355 var assemblies
= index
.CreateElement ("Assemblies");
356 var types
= index
.CreateElement ("Types");
357 var ems
= index
.CreateElement ("ExtensionMethods");
359 index
.AppendChild (overview
);
360 overview
.AppendChild (assemblies
);
361 overview
.AppendChild (types
);
362 overview
.AppendChild (ems
);
366 foreach (var dir
in directories
) {
367 var indexFile
= Path
.Combine (dir
, "index.xml");
369 var doc
= new XmlDocument ();
370 doc
.Load (indexFile
);
372 var c
= doc
.SelectSingleNode ("/Overview/Copyright");
373 var t
= doc
.SelectSingleNode ("/Overview/Title");
374 var r
= doc
.SelectSingleNode ("/Overview/Remarks");
375 if (c
!= null && t
!= null && r
!= null) {
376 var e
= index
.CreateElement ("Copyright");
377 e
.InnerXml
= c
.InnerXml
;
378 overview
.AppendChild (e
);
380 e
= index
.CreateElement ("Title");
381 e
.InnerXml
= t
.InnerXml
;
382 overview
.AppendChild (e
);
384 e
= index
.CreateElement ("Remarks");
385 e
.InnerXml
= r
.InnerXml
;
386 overview
.AppendChild (e
);
391 AddAssemblies (assemblies
, doc
);
392 AddTypes (types
, doc
, dir
);
393 AddChildren (ems
, doc
, "/Overview/ExtensionMethods");
395 catch (Exception e
) {
396 Message (TraceLevel
.Warning
, "Could not load documentation index '{0}': {1}",
397 indexFile
, e
.Message
);
404 static void AddChildren (XmlNode dest
, XmlDocument source
, string path
)
406 var n
= source
.SelectSingleNode (path
);
408 foreach (XmlNode c
in n
.ChildNodes
)
409 dest
.AppendChild (dest
.OwnerDocument
.ImportNode (c
, true));
412 static void AddAssemblies (XmlNode dest
, XmlDocument source
)
414 foreach (XmlNode asm
in source
.SelectNodes ("/Overview/Assemblies/Assembly")) {
415 var n
= asm
.Attributes
["Name"].Value
;
416 var v
= asm
.Attributes
["Version"].Value
;
417 if (dest
.SelectSingleNode (string.Format ("Assembly[@Name='{0}'][@Value='{1}']", n
, v
)) == null) {
418 dest
.AppendChild (dest
.OwnerDocument
.ImportNode (asm
, true));
423 static void AddTypes (XmlNode dest
, XmlDocument source
, string sourceDirectory
)
425 var types
= source
.SelectSingleNode ("/Overview/Types");
428 foreach (XmlNode ns
in types
.ChildNodes
) {
429 var n
= ns
.Attributes
["Name"].Value
;
430 var nsd
= dest
.SelectSingleNode (string.Format ("Namespace[@Name='{0}']", n
));
432 nsd
= dest
.OwnerDocument
.CreateElement ("Namespace");
433 AddAttribute (nsd
, "Name", n
);
434 dest
.AppendChild (nsd
);
436 foreach (XmlNode t
in ns
.ChildNodes
) {
437 if (!TypeInVersions (sourceDirectory
, n
, t
))
439 var c
= dest
.OwnerDocument
.ImportNode (t
, true);
440 AddAttribute (c
, "SourceDirectory", sourceDirectory
);
443 if (nsd
.ChildNodes
.Count
== 0)
444 dest
.RemoveChild (nsd
);
448 static bool TypeInVersions (string sourceDirectory
, string ns
, XmlNode type
)
450 if (opts
.versions
.Count
== 0)
452 var file
= Path
.Combine (Path
.Combine (sourceDirectory
, ns
), type
.Attributes
["Name"].Value
+ ".xml");
453 if (!File
.Exists (file
))
456 using (var s
= File
.OpenText (file
))
457 doc
= new XPathDocument (s
);
458 return MemberInVersions (doc
.CreateNavigator ().SelectSingleNode ("/Type"));
461 static bool MemberInVersions (XPathNavigator nav
)
463 return nav
.Select ("AssemblyInfo/AssemblyVersion")
465 .Any (v
=> opts
.versions
.Contains (v
.ToString ()));
468 static void AddAttribute (XmlNode self
, string name
, string value)
470 var a
= self
.OwnerDocument
.CreateAttribute (name
);
472 self
.Attributes
.Append (a
);
475 private static bool DestinationIsNewer (string source
, string dest
)
477 return !opts
.forceUpdate
&& File
.Exists (dest
) &&
478 File
.GetLastWriteTime (source
) < File
.GetLastWriteTime (dest
);
481 private static void PreserveMembersInVersions (XmlDocument doc
)
483 if (opts
.versions
.Count
== 0)
485 var remove = new List
<XmlNode
>();
486 foreach (XmlNode m
in doc
.SelectNodes ("/Type/Members/Member")) {
487 if (!MemberInVersions (m
.CreateNavigator ()))
490 XmlNode members
= doc
.SelectSingleNode ("/Type/Members");
491 foreach (var m
in remove)
492 members
.RemoveChild (m
);