// NAnt - A .NET build tool // Copyright (C) 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 // // Gert Driesen (gert.driesen@ardatis.com) // Scott Hernandez ScottHernandez_At_hOtmail.d.o.t.com using System.Collections; using System.Collections.Specialized; using System.Text; using System.Web; using System.Xml; using System.Xml.XPath; using NAnt.Core; using NAnt.Core.Attributes; using NAnt.Core.Filters; using NDoc.Core.Reflection; namespace NDoc.Documenter.NAnt { /// /// Provides an extension object for the XSLT transformations. /// public class NAntXsltUtilities { #region Private Instance Fields private string _sdkDocBaseUrl; private string _sdkDocExt; private StringDictionary _elementNames = new StringDictionary(); private XmlDocument _doc; private NAntDocumenterConfig _config; #endregion Private Instance Fields #region Private Static Fields private const string SdkDoc10BaseUrl = "ms-help://MS.NETFrameworkSDK/cpref/html/frlrf"; private const string SdkDoc11BaseUrl = "ms-help://MS.NETFrameworkSDKv1.1/cpref/html/frlrf"; private const string SdkDocPageExt = ".htm"; private const string MsdnOnlineSdkBaseUrl = "http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrf"; private const string MsdnOnlineSdkPageExt = ".asp"; private const string SystemPrefix = "System."; private const string MicrosoftWin32Prefix = "Microsoft.Win32."; private static ArrayList Instances = new ArrayList(3); #endregion Private Static Fields #region Public Instance Constructors /// /// Initializes a new instance of the /// class. /// private NAntXsltUtilities(XmlDocument doc, NAntDocumenterConfig config) { _doc = doc; _config = config; if (config.SdkLinksOnWeb) { _sdkDocBaseUrl = MsdnOnlineSdkBaseUrl; _sdkDocExt = MsdnOnlineSdkPageExt; } else { switch (config.SdkDocVersion) { case SdkVersion.SDK_v1_0: _sdkDocBaseUrl = SdkDoc10BaseUrl; _sdkDocExt = SdkDocPageExt; break; case SdkVersion.SDK_v1_1: _sdkDocBaseUrl = SdkDoc11BaseUrl; _sdkDocExt = SdkDocPageExt; break; } } // create a list of element names by id XmlNodeList types = Document.SelectNodes("//class"); foreach (XmlElement typeNode in types) { string typeId = typeNode.Attributes["id"].Value; _elementNames[typeId] = GetElementNameForType(typeNode); XmlNodeList members = typeNode.SelectNodes("*[@id]"); foreach (XmlElement memberNode in members) { string id = memberNode.Attributes["id"].Value; switch (memberNode.Name) { case "constructor": _elementNames[id] = _elementNames[typeId]; break; case "field": _elementNames[id] = memberNode.Attributes["name"].Value; break; case "property": _elementNames[id] = GetElementNameForProperty(memberNode); break; case "method": _elementNames[id] = GetElementNameForMethod(memberNode); break; case "operator": _elementNames[id] = memberNode.Attributes["name"].Value; break; case "event": _elementNames[id] = memberNode.Attributes["name"].Value; break; } } } } #endregion Public Instance Constructors #region Public Instance Properties /// /// Gets the base url for links to system types. /// /// /// The base url for links to system types. /// public string SdkDocBaseUrl { get { return _sdkDocBaseUrl; } } /// /// Gets the page file extension for links to system types. /// public string SdkDocExt { get { return _sdkDocExt; } } #endregion Public Instance Properties #region Private Instance Properties private XmlDocument Document { get { return _doc; } } private NAntDocumenterConfig Config { get { return _config; } } #endregion Private Instance Properties #region Public Instance Methods /// /// Gets 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 GetNamespaceFilter() { return Config.NamespaceFilter; } /// /// Searches the document for the <class> node with the /// given id. /// /// Type.FullName of class to return /// /// The <class> node with the given id, or /// if the node does not exist. /// public XPathNodeIterator GetClassNode(string id) { if (!id.StartsWith("T:")) { id = "T:" + id; } XmlNode typeNode = Document.SelectSingleNode("//class[@id='" + id + "']"); if (typeNode == null) { return null; } return typeNode.CreateNavigator().Select("."); } /// /// Returns an href for a cref. /// /// The cref for which the href will be looked up. /// /// The href for the specified cref. /// public string GetHRef(string cref) { if ((cref.Length < 2) || (cref[1] != ':')) { return string.Empty; } // get the underlying type of the array if (cref.EndsWith("[]")){ cref=cref.Replace("[]",""); } // check if the ref is for a system namespaced element or not if (cref.Length < 9 || (!cref.Substring(2).StartsWith(SystemPrefix) && !cref.Substring(2).StartsWith(MicrosoftWin32Prefix))) { // not a system one. // will hold the filename to link to string fileName = null; switch (cref.Substring(0, 2)) { case "T:": fileName = GetFileNameForType(cref); break; case "M:": fileName = GetFileNameForFunction(cref); break; } if (fileName == null) { return string.Empty; } else { if (cref.Substring(2).StartsWith("NAnt.") && !cref.Substring(2).StartsWith("NAnt.Contrib")) { return Config.NAntBaseUri + fileName; } else { return "../" + fileName; } } } else { // a system cref switch (cref.Substring(0, 2)) { case "N:": // Namespace return SdkDocBaseUrl + cref.Substring(2).Replace(".", "") + SdkDocExt; case "T:": // Type: class, interface, struct, enum, delegate return SdkDocBaseUrl + cref.Substring(2).Replace(".", "") + "ClassTopic" + SdkDocExt; case "F:": // Field // do not generate href for fields, as the .NET SDK does // not have separate pages for enum fields, and we have no // way of knowing whether it's a reference to an enum field // or class field. return string.Empty; case "P:": // Property case "M:": // Method case "E:": // Event return this.GetFilenameForSystemMember(cref); default: return string.Empty; } } } /// /// Returns the name for a given cref. /// /// The cref for which the name will be looked up. /// /// The name for the specified cref. /// public string GetName(string cref) { if (cref.Length < 2) { return cref; } if (cref[1] == ':') { if (cref.Length < 9 || (!cref.Substring(2).StartsWith(SystemPrefix) && !cref.Substring(2).StartsWith(MicrosoftWin32Prefix))) { //what name should be found? string name = _elementNames[cref]; if (name != null) { return name; } } int index; if ((index = cref.IndexOf(".#c")) >= 0) { cref = cref.Substring(2, index - 2); } else if ((index = cref.IndexOf("(")) >= 0) { cref = cref.Substring(2, index - 2); } else { cref = cref.Substring(2); } } return cref.Substring(cref.LastIndexOf(".") + 1); } /// /// Returns the NAnt task name for a given cref. /// /// The cref for the task name will be looked up. /// /// The NAnt task name for the specified cref. /// public string GetTaskName(string cref) { return GetTaskNameForType(GetTypeNodeByID(cref)); } /// /// Returns the NAnt element name for a given cref. /// /// The cref for the element name will be looked up. /// /// The NAnt element name for the specified cref. /// public string GetElementName(string cref) { return GetElementNameForType(GetTypeNodeByID(cref)); } public XmlNode GetTypeNodeByID(string cref) { if (cref[1] == ':' && !cref.StartsWith("T:")) { return null; } if (!cref.StartsWith("T:")) { cref = "T:" + cref; } XmlNode typeNode = Document.SelectSingleNode("//class[@id='" + cref + "']"); if (typeNode == null) { typeNode = Document.SelectSingleNode("//enumeration[@id='" + cref + "']"); } return typeNode; } public XmlNode GetMethodNodeByID(string cref) { if (cref[1] == ':' && !cref.StartsWith("M:")) { return null; } if (!cref.StartsWith("M:")) { cref = "M:" + cref; } return Document.SelectSingleNode("//method[@id='" + cref + "']"); } /// /// Determines the of the given type node. /// /// The type node for which to determine the . /// /// The of the given type node. /// public ElementDocType GetElementDocType(XmlNode typeNode) { if (typeNode == null) { return ElementDocType.None; } // check if type is an enum if (typeNode.LocalName == "enumeration") { return ElementDocType.Enum; } // check if type is a task if (GetTaskNameForType(typeNode) != null) { return ElementDocType.Task; } // check if type is a datatype if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(DataTypeBase).FullName + "']") != null) { if (typeNode.SelectSingleNode("attribute[@name='" + typeof(ElementNameAttribute).FullName + "']") != null) { return ElementDocType.DataTypeElement; } else { return ElementDocType.Element; } } // check if type is a filter if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(Filter).FullName + "']") != null) { if (typeNode.SelectSingleNode("attribute[@name='" + typeof(ElementNameAttribute).FullName + "']") != null) { return ElementDocType.Filter; } else { return ElementDocType.Element; } } // check if type is an element if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(Element).FullName + "']") != null) { return ElementDocType.Element; } // check if type is a functionset if (typeNode.SelectSingleNode("attribute[@name='" + typeof(FunctionSetAttribute).FullName + "']/property[@name='Prefix']/@value") != null) { return ElementDocType.FunctionSet; } return ElementDocType.None; } /// /// Determines the of the type to which /// the given cref points. /// /// The cref for which to determine the . /// /// The of the type to which the given /// cref points. /// public ElementDocType GetElementDocTypeByID(string cref) { return GetElementDocType(GetTypeNodeByID(cref)); } /// /// Determines whether the given cref points to a datatype. /// /// The cref to check. /// /// if the given cref points to a datatype; /// otherwise, . /// public bool IsDataType(string cref) { return GetElementDocTypeByID(cref) == ElementDocType.DataTypeElement; } /// /// Determines whether the given cref points to an element. /// /// The cref to check. /// /// if the given cref points to an element; /// otherwise, . /// /// /// When the cref points to a or /// this method returns . /// public bool IsElement(string cref) { return GetElementDocTypeByID(cref) == ElementDocType.Element; } /// /// Determines whether the given cref points to a datatype. /// /// The cref to check. /// /// if the given cref points to a datatype; /// otherwise, . /// public bool IsFilter(string cref) { return GetElementDocTypeByID(cref) == ElementDocType.Filter; } /// /// Determines whether the given cref points to a task. /// /// The cref to check. /// /// if the given cref points to a task; /// otherwise, . /// public bool IsTask(string cref) { return GetElementDocTypeByID(cref) == ElementDocType.Task; } /// /// Determines whether the given cref points to a functionset. /// /// The cref to check. /// /// if the given cref points to a functionset; /// otherwise, . /// public bool IsFunctionSet(string cref) { return GetElementDocTypeByID(cref) == ElementDocType.FunctionSet; } /// /// Encodes a URL string using for reliable /// HTTP transmission from the Web server to a client. /// /// The text to encode. /// /// The encoded string. /// public string UrlEncode(string value) { return HttpUtility.UrlEncode(value, Encoding.UTF8); } #endregion Public Instance Methods #region Internal Instance Methods /// /// Gets the BuildElementAttribute name for the "class/property" XmlNode /// /// The XmlNode to look for a name. /// The BuildElementAttrbute.Name if the attribute exists for the node. internal string GetElementNameForProperty(XmlNode propertyNode) { // check whether property is a task attribute XmlAttribute taskAttributeNode = propertyNode.SelectSingleNode("attribute[@name='" + typeof(TaskAttributeAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (taskAttributeNode != null) { return taskAttributeNode.Value; } // check whether property is a element array XmlAttribute elementArrayNode = propertyNode.SelectSingleNode("attribute[@name='" + typeof(BuildElementArrayAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (elementArrayNode != null) { return elementArrayNode.Value; } // check whether property is a element collection XmlAttribute elementCollectionNode = propertyNode.SelectSingleNode("attribute[@name='" + typeof(BuildElementCollectionAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (elementCollectionNode != null) { return elementCollectionNode.Value; } // check whether property is an xml element XmlAttribute buildElementNode = propertyNode.SelectSingleNode("attribute[@name='" + typeof(BuildElementAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (buildElementNode != null) { return buildElementNode.Value; } // check whether property is a Framework configurable attribute XmlAttribute frameworkConfigAttributeNode = propertyNode.SelectSingleNode("attribute[@name='" + typeof(FrameworkConfigurableAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (frameworkConfigAttributeNode != null) { return frameworkConfigAttributeNode.Value; } return null; } /// /// Returns the filename to use for the given function XmlElement /// /// The "method" element to find the filename for. internal string GetFileNameForFunction(XmlNode functionNode) { string name = ""; XmlNode n = functionNode.SelectSingleNode("../attribute[@name='NAnt.Core.Attributes.FunctionSetAttribute']/property[@name='Prefix']/@value"); if (n != null && n.InnerText != "") { name += n.InnerText + "."; } n = functionNode.SelectSingleNode("attribute[@name='NAnt.Core.Attributes.FunctionAttribute']/property[@name='Name']/@value"); if (n != null && n.InnerText != "") { name += n.InnerText; } else { name += functionNode.Attributes["name"].Value; } return "functions/" + UrlEncode(name) + ".html"; } /// /// Returns the filename to use for the given class XmlNode /// /// The "Class" element to find the filename for. /// /// The relative path and filename where this type is stored in the /// documentation. /// /// /// For a type that is neither a task, enum, global type, filter or /// functionset, the returned filename will point to the SDK docs for /// that type. /// internal string GetFileNameForType(XmlNode typeNode) { if (typeNode == null) { return null; } string partialURL = null; // if type is task use name set using TaskNameAttribute string taskName = GetTaskNameForType(typeNode); if (taskName != null) { return "tasks/" + UrlEncode(taskName) + ".html"; } // check if type is an enum if (typeNode.LocalName == "enumeration") { return "enums/" + UrlEncode(typeNode.Attributes["id"].Value.Substring(2)) + ".html"; } // check if type derives from NAnt.Core.DataTypeBase if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(DataTypeBase).FullName + "']") != null) { // make sure the type has a ElementName assigned to it XmlAttribute elementNameAttribute = typeNode.SelectSingleNode("attribute[@name='" + typeof(ElementNameAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (elementNameAttribute != null) { return "types/" + UrlEncode(elementNameAttribute.Value) + ".html"; } } // check if type derives from NAnt.Core.Filters.Filter if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(Filter).FullName + "']") != null) { // make sure the type has a ElementName assigned to it XmlAttribute elementNameAttribute = typeNode.SelectSingleNode("attribute[@name='" + typeof(ElementNameAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (elementNameAttribute != null) { return "filters/" + UrlEncode(elementNameAttribute.Value) + ".html"; } } // check if type is a functionset partialURL = GetFileNameForFunctionSet(typeNode); if (partialURL != null) { return partialURL; } // check if type derives from NAnt.Core.Element if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(Element).FullName + "']") != null) { return "elements/" + UrlEncode(typeNode.Attributes["id"].Value.Substring(2)) + ".html"; } return "../sdk/" + UrlEncode(typeNode.Attributes["id"].Value.Substring(2)) + ".html"; } #endregion Internal Instance Methods #region Private Instance Methods /* private string GetElementNameForType(string id) { return GetElementNameForType(GetTypeNodeByID(id)); } */ private string GetElementNameForType(XmlNode typeNode) { if (typeNode == null) { return string.Empty; } // if type is task use name set using TaskNameAttribute string taskName = GetTaskNameForType(typeNode); if (taskName != null) { return "<" + taskName + ">"; } // make sure the type has a ElementNameAttribute assigned to it XmlAttribute elementNameAttribute = typeNode.SelectSingleNode("attribute[@name='" + typeof(ElementNameAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (elementNameAttribute != null) { // check if we're dealing with a data type if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(DataTypeBase).FullName + "']")!= null) { return "<" + elementNameAttribute.Value + ">"; } // check if we're dealing with a filter if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(Filter).FullName + "']")!= null) { return "<" + elementNameAttribute.Value + ">"; } } // if we're dealing with a FunctionSet, use category as name instead // of prefix XmlAttribute categoryNameAttribute = typeNode.SelectSingleNode("attribute[@name='" + typeof(FunctionSetAttribute).FullName + "']/property[@name='Category']/@value") as XmlAttribute; if (categoryNameAttribute != null) { return categoryNameAttribute.Value; } return null; } private string GetFileNameForFunction(string type) { XmlNode functionNode = GetMethodNodeByID(type); if (functionNode != null) { return GetFileNameForFunction(functionNode); } return null; } private string GetFileNameForType(string type) { return GetFileNameForType(GetTypeNodeByID(type)); } private string GetFilenameForSystemMember(string cref) { string crefName; int index; if ((index = cref.IndexOf(".#c")) >= 0) { crefName = cref.Substring(2, index - 2) + ".ctor"; } else if ((index = cref.IndexOf("(")) >= 0) { crefName = cref.Substring(2, index - 2); } else { crefName = cref.Substring(2); } index = crefName.LastIndexOf("."); string crefType = crefName.Substring(0, index); string crefMember = crefName.Substring(index + 1); return SdkDocBaseUrl + crefType.Replace(".", "") + "Class" + crefMember + "Topic" + SdkDocExt; } /// /// Gets the TaskNameAttrbute name for the "class" XmlNode /// /// The XmlNode to look for a name. /// The if the attribute exists for the node. /// /// The class is also checked to make sure it is derived from /// private string GetTaskNameForType(XmlNode typeNode) { if (typeNode == null) { return null; } // make sure the type actually derives from NAnt.Core.Task if (typeNode.SelectSingleNode("descendant::base[@id='T:" + typeof(Task).FullName + "']") != null) { // make sure the type has a TaskNameAttribute assigned to it XmlAttribute taskNameAttribute = typeNode.SelectSingleNode("attribute[@name='" + typeof(TaskNameAttribute).FullName + "']/property[@name='Name']/@value") as XmlAttribute; if (taskNameAttribute != null) { return taskNameAttribute.Value; } } return null; } /// /// Gets the function name for methods that represent a NAtn function. /// /// The XmlNode to look for a name. /// /// The function name if represent a /// NAnt function. /// private string GetElementNameForMethod(XmlNode methodNode) { XmlNode functionNameAttribute = methodNode.SelectSingleNode("attribute[@name='" + typeof(FunctionAttribute).FullName + "']/property[@name='Name']/@value"); if (functionNameAttribute == null) { return methodNode.Attributes["name"].Value; } XmlNode prefixAttribute = methodNode.SelectSingleNode("../attribute[@name='" + typeof(FunctionSetAttribute).FullName + "']/property[@name='Prefix']/@value"); if (prefixAttribute == null) { return methodNode.Attributes["name"].Value; } return prefixAttribute.InnerText + "::" + functionNameAttribute.InnerText + "()"; } /// /// Returns a partial URL to link to the functionset in the function index. /// /// The "Class" element to find the filename for. private string GetFileNameForFunctionSet(XmlNode functionNode) { XmlAttribute categoryValueAttribute = functionNode.SelectSingleNode("attribute[@name='NAnt.Core.Attributes.FunctionSetAttribute']/property[@name='Category']/@value") as XmlAttribute; if (categoryValueAttribute != null && categoryValueAttribute.Value != "") { return "functions/index.html#" + UrlEncode(categoryValueAttribute.Value); } return null; } #endregion Private Instance Methods #region Internal Static Methods internal static NAntXsltUtilities CreateInstance(XmlDocument doc, NAntDocumenterConfig config){ // just in case... but we should never see this happen. lock (Instances) { foreach (NAntXsltUtilities util in Instances) { if (util.Document == doc && util.Config.SdkDocVersion.Equals(config.SdkDocVersion) && util.Config.SdkLinksOnWeb == config.SdkLinksOnWeb) { return util; } } NAntXsltUtilities inst = new NAntXsltUtilities(doc, config); Instances.Add(inst); return inst; } } #endregion Internal Static Methods } }