// NAnt - A .NET build tool // Copyright (C) 2001-2003 Gerry Shaw // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // Ian MacLean (ian@maclean.ms) // Gerry Shaw (gerry_shaw@yahoo.com) // Gert Driesen (gert.driesen@ardatis.com) // Scott Hernandez (ScottHernandez_hotmail_com) using System; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Reflection; using System.Text; using System.Xml; using System.Xml.Xsl; using NDoc.Core; using NDoc.Core.Reflection; using NAnt.Core; using NAnt.Core.Attributes; namespace NDoc.Documenter.NAnt { /// /// NDoc Documenter for building custom NAnt User documentation. /// public class NAntDocumenter : BaseReflectionDocumenter { #region Private Instance Fields private XslTransform _xsltTaskIndex; private XslTransform _xsltTypeIndex; private XslTransform _xsltFunctionIndex; private XslTransform _xsltFilterIndex; private XslTransform _xsltTypeDoc; private XslTransform _xsltFunctionDoc; private XmlDocument _xmlDocumentation; private string _resourceDirectory; private StringDictionary _writtenFiles = new StringDictionary(); #endregion Private Instance Fields #region Public Instance Constructors /// /// Initializes a new instance of the class. /// public NAntDocumenter() : base("NAnt") { Clear(); } #endregion Public Instance Constructors #region Public Instance Properties /// /// Gets the documenter's output directory. /// /// /// The documenter's output directory. /// public string OutputDirectory { get { return ((NAntDocumenterConfig) Config).OutputDirectory; } } /// /// Gets or sets the root namespace to document. /// /// /// The root namespace to document, or a empty /// if no restriction should be set on the namespace to document. /// public string NamespaceFilter { get { return ((NAntDocumenterConfig) Config).NamespaceFilter; } } /// /// Gets the name of the product for which documentation should be /// generated. /// /// /// The name of the product for which documentation should be /// generated. /// public string ProductName { get { return ((NAntDocumenterConfig) Config).ProductName; } } /// /// Gets the version of the product for which documentation should be /// generated. /// /// /// The version of the product for which documentation should be /// generated. /// public string ProductVersion { get { return ((NAntDocumenterConfig) Config).ProductVersion; } } /// /// Gets the URL of the website of the product for which documentation /// should be generated. /// /// /// The URL of the website of the product for which documentation should /// be generated. /// public string ProductUrl { get { return ((NAntDocumenterConfig) Config).ProductUrl; } } #endregion Public Instance Properties #region Override implementation of IDocumenter /// /// Gets the documenter's main output file. /// /// /// The documenter's main output file. /// public override string MainOutputFile { get { return ""; } } /// /// Resets the documenter to a clean state. /// public override void Clear() { Config = new NAntDocumenterConfig(); } /// /// Builds the documentation. /// public override void Build(NDoc.Core.Project project) { int buildStepProgress = 0; OnDocBuildingStep(buildStepProgress, "Initializing..."); _resourceDirectory = Path.Combine(Path.Combine(Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData), "NDoc"), "NAnt"); // get assembly in which documenter is defined Assembly assembly = this.GetType().Module.Assembly; // write xslt files to resource directory EmbeddedResources.WriteEmbeddedResources(assembly, "Documenter.xslt", Path.Combine(_resourceDirectory, "xslt")); // create the html output directories try { Directory.CreateDirectory(OutputDirectory); Directory.CreateDirectory(Path.Combine(OutputDirectory, "elements")); Directory.CreateDirectory(Path.Combine(OutputDirectory, "functions")); Directory.CreateDirectory(Path.Combine(OutputDirectory, "types")); Directory.CreateDirectory(Path.Combine(OutputDirectory, "tasks")); Directory.CreateDirectory(Path.Combine(OutputDirectory, "enums")); Directory.CreateDirectory(Path.Combine(OutputDirectory, "filters")); } catch (Exception ex) { throw new DocumenterException("The output directories could not" + " be created.", ex); } buildStepProgress += 10; OnDocBuildingStep(buildStepProgress, "Merging XML documentation..."); // load the stylesheets that will convert the master xml into html pages MakeTransforms(); // will hold the file name containing the NDoc generated XML string tempFile = null; try { // determine temporary file name tempFile = Path.GetTempFileName(); // create the master XML document MakeXmlFile(project, tempFile); // create a xml document that will be transformed using xslt using (FileStream fs = new FileStream(tempFile, FileMode.Open, FileAccess.Read, FileShare.Read)) { _xmlDocumentation = new XmlDocument(); _xmlDocumentation.Load(fs); } } finally { // ensure temporary file is removed if (tempFile != null) { File.Delete(tempFile); } } // build the file mapping buildStepProgress += 15; OnDocBuildingStep(buildStepProgress, "Building mapping..."); // create arguments for nant index page transform XsltArgumentList indexArguments = CreateXsltArgumentList(); // add extension object for NAnt utilities NAntXsltUtilities indexUtilities = NAntXsltUtilities.CreateInstance( _xmlDocumentation, (NAntDocumenterConfig) Config); // add extension object to Xslt arguments indexArguments.AddExtensionObject("urn:NAntUtil", indexUtilities); buildStepProgress += 15; OnDocBuildingStep(buildStepProgress, "Creating Task Index Page..."); // transform nant task index page transform TransformAndWriteResult(_xsltTaskIndex, indexArguments, "tasks/index.html"); buildStepProgress += 10; OnDocBuildingStep(buildStepProgress, "Creating Type Index Page..."); // transform nant type index page transform TransformAndWriteResult(_xsltTypeIndex, indexArguments, "types/index.html"); buildStepProgress += 10; OnDocBuildingStep(buildStepProgress, "Creating Filter Index Page..."); // transform nant type index page transform TransformAndWriteResult(_xsltFilterIndex, indexArguments, "filters/index.html"); OnDocBuildingStep(buildStepProgress, "Creating Function Index Page..."); // transform nant function index page transform TransformAndWriteResult(_xsltFunctionIndex, indexArguments, "functions/index.html"); buildStepProgress += 10; OnDocBuildingStep(buildStepProgress, "Generating Task Documents..."); // generate a page for each marked task XmlNodeList typeNodes = _xmlDocumentation.SelectNodes("//class[starts-with(substring(@id, 3, string-length(@id) - 2), '" + NamespaceFilter + "')]"); foreach (XmlNode typeNode in typeNodes) { ElementDocType elementDocType = indexUtilities.GetElementDocType(typeNode); DocumentType(typeNode, elementDocType, indexUtilities); } OnDocBuildingStep(buildStepProgress, "Generating Function Documents..."); // generate a page for each function - TODO - change the XPath expression to select more functions XmlNodeList functionNodes = _xmlDocumentation.SelectNodes("//method[attribute/@name = 'NAnt.Core.Attributes.FunctionAttribute' and ancestor::class[starts-with(substring(@id, 3, string-length(@id) - 2), '" + NamespaceFilter + "')]]"); foreach (XmlElement function in functionNodes) { DocumentFunction(function, indexUtilities); } OnDocBuildingStep(100, "Complete"); } #endregion Override implementation of IDocumenter #region Private Instance Methods private void DocumentType(XmlNode typeNode, ElementDocType docType, NAntXsltUtilities utilities) { if (typeNode == null) { throw new ArgumentNullException("typeNode"); } if (docType == ElementDocType.None || docType == ElementDocType.FunctionSet) { // we don't need to document this type return; } string classID = typeNode.Attributes["id"].Value; if (!classID.Substring(2).StartsWith(NamespaceFilter)) { // we don't need to types in this namespace return; } string filename = utilities.GetFileNameForType(typeNode); if (filename == null) { // we should never get here, but just in case ... return; } if (_writtenFiles.ContainsValue(classID)) { return; } else { _writtenFiles.Add(filename, classID); } // create arguments for nant task page transform (valid args are class-id, refType, imagePath, relPathAdjust) XsltArgumentList arguments = CreateXsltArgumentList(); arguments.AddParam("class-id", String.Empty, classID); string refTypeString; switch (docType) { case ElementDocType.DataTypeElement: refTypeString = "Type"; break; case ElementDocType.Element: refTypeString = "Element"; break; case ElementDocType.Task: refTypeString = "Task"; break; case ElementDocType.Enum: refTypeString = "Enum"; break; case ElementDocType.Filter: refTypeString = "Filter"; break; default: refTypeString = "Other?"; break; } arguments.AddParam("refType", string.Empty, refTypeString); // add extension object to Xslt arguments arguments.AddExtensionObject("urn:NAntUtil", utilities); // Process all sub-elements and generate docs for them. :) // Just look for properties with attributes to narrow down the foreach loop. // (This is a restriction of NAnt.Core.Attributes.BuildElementAttribute) foreach (XmlNode propertyNode in typeNode.SelectNodes("property[attribute]")) { //get the xml element string elementName = utilities.GetElementNameForProperty(propertyNode); if (elementName != null) { // try to get attribute info if it is an array/collection. // strip the array brakets "[]" to get the type string elementType = "T:" + propertyNode.Attributes["type"].Value.Replace("[]",""); // check whether property is an element array XmlNode nestedElementNode = propertyNode.SelectSingleNode("attribute[@name='" + typeof(BuildElementArrayAttribute).FullName + "']"); if (nestedElementNode == null) { // check whether property is an element collection nestedElementNode = propertyNode.SelectSingleNode("attribute[@name='" + typeof(BuildElementCollectionAttribute).FullName + "']"); } // if property is either array or collection type element if (nestedElementNode != null) { // select the item type in the collection XmlAttribute elementTypeAttribute = _xmlDocumentation.SelectSingleNode("//class[@id='" + elementType + "']/method[@name='Add']/parameter/@type") as XmlAttribute; if (elementTypeAttribute != null) { // get type of collection elements elementType = "T:" + elementTypeAttribute.Value; } // if it contains a ElementType attribute then it is an array or collection // if it is a collection, then we care about the child type. XmlNode explicitElementType = propertyNode.SelectSingleNode("attribute/property[@ElementType]"); if (explicitElementType != null) { // ndoc is inconsistent about how classes are named. elementType = explicitElementType.Attributes["value"].Value.Replace("+","."); } } XmlNode elementTypeNode = utilities.GetTypeNodeByID(elementType); if (elementTypeNode != null) { ElementDocType elementDocType = utilities.GetElementDocType(elementTypeNode); if (elementDocType != ElementDocType.None) { DocumentType(elementTypeNode, elementDocType, utilities); } } } } // create the page TransformAndWriteResult(_xsltTypeDoc, arguments, filename); } private void DocumentFunction(XmlElement functionElement, NAntXsltUtilities utilities) { if (functionElement == null) { throw new ArgumentNullException("functionElement"); } string methodID = functionElement.GetAttribute("id"); string filename = utilities.GetFileNameForFunction(functionElement); XsltArgumentList arguments = CreateXsltArgumentList(); arguments.AddParam("method-id", string.Empty, methodID); arguments.AddParam("refType", string.Empty, "Function"); arguments.AddParam("functionName", string.Empty, functionElement.GetAttribute("name")); // add extension object to Xslt arguments arguments.AddExtensionObject("urn:NAntUtil", utilities); // create the page TransformAndWriteResult(_xsltFunctionDoc, arguments, filename); } private void MakeTransforms() { OnDocBuildingProgress(0); _xsltTaskIndex = new XslTransform(); _xsltTypeIndex = new XslTransform(); _xsltFunctionIndex = new XslTransform(); _xsltFilterIndex = new XslTransform(); _xsltTypeDoc = new XslTransform(); _xsltFunctionDoc = new XslTransform(); MakeTransform(_xsltTaskIndex, "task-index.xslt"); OnDocBuildingProgress(20); MakeTransform(_xsltTypeIndex, "type-index.xslt"); OnDocBuildingProgress(40); MakeTransform(_xsltFilterIndex, "filter-index.xslt"); OnDocBuildingProgress(50); MakeTransform(_xsltFunctionIndex, "function-index.xslt"); OnDocBuildingProgress(60); MakeTransform(_xsltTypeDoc, "type-doc.xslt"); OnDocBuildingProgress(80); MakeTransform(_xsltFunctionDoc, "function-doc.xslt"); OnDocBuildingProgress(100); } private void MakeTransform(XslTransform transform, string fileName) { transform.Load(Path.Combine(Path.Combine(_resourceDirectory, "xslt"), fileName)); } private XsltArgumentList CreateXsltArgumentList() { XsltArgumentList arguments = new XsltArgumentList(); arguments.AddParam("productName", string.Empty, ProductName); arguments.AddParam("productVersion", string.Empty, ProductVersion); arguments.AddParam("productUrl", string.Empty, ProductUrl); return arguments; } private void TransformAndWriteResult(XslTransform transform, XsltArgumentList arguments, string filename) { string path = Path.Combine(OutputDirectory, filename); using (StreamWriter writer = new StreamWriter(path, false, Encoding.UTF8)) { transform.Transform(_xmlDocumentation, arguments, writer); } } #endregion Private Instance Methods } public enum ElementDocType { None = 0, Task = 1, DataTypeElement = 2, Element = 3, Enum = 4, Filter = 5, FunctionSet = 6 } }