using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Xml; using NDoc3.Core; using NDoc3.Xml; namespace NDoc3.Documenter.Msdn { /// /// public class NameResolver { public const string EXT = ".html"; public MsdnXsltUtilities utilities; private readonly bool mergeAssemblies; private readonly StringDictionary fileNames = new StringDictionary(); private readonly StringDictionary elemNames = new StringDictionary(); private readonly ReferenceTypeDictionary assemblyReferences = new ReferenceTypeDictionary(); /// /// /// /// public NameResolver(XmlDocument documentation, bool mergeAssemblies) { this.mergeAssemblies = mergeAssemblies; BuildNameTables(documentation, mergeAssemblies); } #region Used for Html file generation /// /// /// /// /// public string GetFilenameForFieldList(string assemblyName, string typeID) { return GetFilenameForIdSpecial(assemblyName, typeID, "~Fields"); } /// /// /// /// /// public string GetFilenameForOperatorList(string assemblyName, string typeID) { return GetFilenameForIdSpecial(assemblyName, typeID, "~Operators"); } /// /// /// /// /// public string GetFilenameForMethodList(string assemblyName, string typeID) { return GetFilenameForIdSpecial(assemblyName, typeID, "~Methods"); } /// /// /// /// /// public string GetFilenameForPropertyList(string assemblyName, string typeID) { return GetFilenameForIdSpecial(assemblyName, typeID, "~Properties"); } /// /// /// /// /// public string GetFilenameForEventList(string assemblyName, string typeID) { return GetFilenameForIdSpecial(assemblyName, typeID, "~Events"); } #endregion // exposed to XSLT /// /// /// /// /// public string GetDisplayNameForId(string currentAssemblyName, string memberId) { string name = elemNames[currentAssemblyName + memberId]; if (name == null) { // search for identifier in referenced assemblies string[] assemblyReference = assemblyReferences[currentAssemblyName]; if (assemblyReference != null) { foreach (string assemblyName in assemblyReference) { name = elemNames[assemblyName + memberId]; if (name != null) break; } } } if (name == null) { name = elemNames[memberId]; } return name; } // exposed to XSLT /// /// /// /// /// public string GetFilenameForId(string currentAssemblyName, string memberId) { // lookup current assembly string filename = GetFilenameForIdInternal(currentAssemblyName, memberId); return filename; } /// /// /// /// public string GetFilenameForAssembly(string assemblyName) { return GetFilenameForId(assemblyName, null); } // exposed to XSLT /// /// /// /// /// public string GetFilenameForNamespaceHierarchy(string assemblyName, string namespaceName) { return GetFilenameForIdSpecial(assemblyName, "N:" + namespaceName, "~Hierarchy"); } // exposed to XSLT /// /// /// /// /// public string GetFilenameForNamespace(string assemblyName, string namespaceName) { if (mergeAssemblies) assemblyName = string.Empty; if (string.IsNullOrEmpty(namespaceName)) namespaceName = "(global)"; return GetFilenameForId(assemblyName, "N:" + namespaceName); } // exposed to XSLT /// /// /// /// /// public string GetFilenameForTypeHierarchy(string assemblyName, string typeID) { return GetFilenameForIdSpecial(assemblyName, typeID, "~Hierarchy"); } // exposed to XSLT /// /// /// /// /// public string GetFilenameForTypeMemberList(string assemblyName, string typeID) { return GetFilenameForIdSpecial(assemblyName, typeID, "~Members"); } // exposed to XSLT /// /// /// /// /// public string GetFilenameForConstructorList(string assemblyName, string typeID) { return GetFilenameForIdSpecial(assemblyName, typeID, "~Constructors"); } // exposed to XSLT /// /// /// /// /// /// public string GetFilenameForOperatorOverloads(string assemblyName, string typeID, string operatorName) { return GetFilenameForIdSpecial(assemblyName, typeID, "." + operatorName.Replace('`', '$') + "~Overloads"); } // exposed to XSLT /// /// /// /// /// /// public string GetFilenameForPropertyOverloads(string assemblyName, string typeID, string propertyName) { string fileName = GetFilenameForIdSpecial(assemblyName, typeID, "." + propertyName.Replace('`', '$') + "~Overloads"); return fileName; } // exposed to XSLT /// /// /// /// /// /// public string GetFilenameForMethodOverloads(string assemblyName, string typeID, string methodName) { string fileName = GetFilenameForIdSpecial(assemblyName, typeID, "." + methodName.Replace('`', '$').Replace("<", "(").Replace(">", ")") + "~Overloads"); return fileName; } // exposed to XSLT /// /// /// /// /// public string GetFilenameForTypename(string currentAssemblyName, string typeName) { // strip leading type identifier if (typeName.Length > 1 && typeName[1] != ':') typeName = "T:" + typeName; string filename = GetFilenameForId(currentAssemblyName, typeName); return filename; } // exposed /// /// /// /// /// /// public string GetFilenameForCRefOverload(string currentAssemblyName, string cref, string overload) { // lookup current assembly string filename = GetFilenameForId(currentAssemblyName, cref); return filename; #region Original XSLT Logic /* */ #endregion } #region BuildNameTables private void BuildNameTables(XmlDocument xmlDocumentation, bool mergeNamespaces) { XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDocumentation.NameTable); nsmgr.AddNamespace("ns", "urn:ndoc-schema"); XmlNodeList assemblies = xmlDocumentation.SelectNodes("/ns:ndoc/ns:assembly", nsmgr); if(assemblies == null) throw new Exception("No assemblies found"); foreach (XmlElement assemblyNode in assemblies) { string assemblyName = GetNodeName(assemblyNode); RegisterAssembly(assemblyName); // build list of assemblyReferences XmlNodeList assemblyReferenceNodes = assemblyNode.SelectNodes("ns:assemblyReference", nsmgr); if(assemblyReferenceNodes == null) throw new Exception("No assembly reference nodes found"); List assemblyReferenceNames = new List(); foreach (XmlNode assemblyReferenceNode in assemblyReferenceNodes) { assemblyReferenceNames.Add(GetNodeName(assemblyReferenceNode)); } assemblyReferences.Add(assemblyName, assemblyReferenceNames.ToArray()); // foreach namespace XmlNodeList namespaces = assemblyNode.SelectNodes("ns:module/ns:namespace", nsmgr); if(namespaces == null) throw new Exception("No namespaces found"); foreach (XmlElement namespaceNode in namespaces) { string namespaceName = GetNodeName(namespaceNode); // register namespace RegisterNamespace((mergeNamespaces ? string.Empty : assemblyName), namespaceName); XmlNodeList types = namespaceNode.SelectNodes("*[@id]", nsmgr); if(types == null) throw new Exception("No types found"); foreach (XmlElement typeNode in types) { string typeId = GetNodeId(typeNode); //TODO The rest should also use displayName ((EE): clarify what above line means - shall we remove 'name' attribute then?) string typeDisplayName = GetNodeDisplayName(typeNode); RegisterType(assemblyName, typeId, typeDisplayName); // foreach member XmlNodeList members = typeNode.SelectNodes("*[@id]"); if(members == null) throw new Exception("No type members found"); foreach (XmlElement memberNode in members) { string memberId = GetNodeId(memberNode); switch (memberNode.Name) { case "constructor": { string overload = XmlUtils.GetAttributeString(memberNode, "overload", false); RegisterConstructor(assemblyName, typeId, memberId, overload); } break; case "field": { bool isEnum = (typeNode.Name == "enumeration"); string memberName = GetNodeName(memberNode); RegisterField(assemblyName, typeId, memberId, isEnum, memberName); } break; case "property": { string overload = GetNodeOverload(memberNode); string memberName = GetNodeName(memberNode); RegisterProperty(assemblyName, memberId, memberName, overload); } break; case "method": { string overload = GetNodeOverload(memberNode); string memberDisplayName = GetNodeDisplayName(memberNode); RegisterMethod(assemblyName, memberId, memberDisplayName, overload); } break; case "operator": { string overload = GetNodeOverload(memberNode); string memberName = GetNodeName(memberNode); RegisterOperator(assemblyName, memberId, memberName, overload); } break; case "event": { string memberName = GetNodeName(memberNode); RegisterEvent(assemblyName, memberId, memberName); } break; } } } } } } private void RegisterAssembly(string assemblyName) { Register(assemblyName, null, assemblyName, CalculateFilenameForId(assemblyName, null, null)); } private void RegisterNamespace(string assemblyName, string namespaceName) { if (string.IsNullOrEmpty(namespaceName)) { namespaceName = "(global)"; } string namespaceId = "N:" + namespaceName; Register(assemblyName, namespaceId, namespaceName, CalculateFilenameForId(assemblyName, namespaceId, null)); } private void RegisterType(string assemblyName, string typeId, string displayName) { Register(assemblyName, typeId, displayName, CalculateFilenameForId(assemblyName, typeId, null)); } private void RegisterConstructor(string assemblyName, string typeId, string id, string overload) { Register(assemblyName, id, GetDisplayNameForId(assemblyName, typeId), CalculateFilenameForId(assemblyName, id, overload)); } private void RegisterOperator(string assemblyName, string memberId, string memberName, string overload) { Register(assemblyName, memberId, memberName, CalculateFilenameForId(assemblyName, memberId, overload)); } private void RegisterMethod(string assemblyName, string memberId, string memberDisplayName, string overload) { Register(assemblyName, memberId, memberDisplayName, CalculateFilenameForId(assemblyName, memberId, overload)); } private void RegisterProperty(string assemblyName, string memberId, string memberName, string overload) { Register(assemblyName, memberId, memberName, CalculateFilenameForId(assemblyName, memberId, overload)); } private void RegisterField(string assemblyName, string typeId, string memberId, bool isEnum, string memberName) { if (isEnum) { Register(assemblyName, memberId, memberName, GetFilenameForId(assemblyName, typeId)); } else { Register(assemblyName, memberId, memberName, CalculateFilenameForId(assemblyName, memberId, null)); } } private void RegisterEvent(string assemblyName, string memberId, string memberName) { Register(assemblyName, memberId, memberName, CalculateFilenameForId(assemblyName, memberId, null)); } #endregion #region Registration & Lookup Logic private string GetFilenameForIdInternal(string currentAssemblyName, string memberId) { string filename = fileNames[currentAssemblyName + memberId]; if (filename == null) { // search for identifier in referenced assemblies string[] assemblyReference = assemblyReferences[currentAssemblyName]; if (assemblyReference != null) { foreach (string assemblyName in assemblyReference) { filename = fileNames[assemblyName + memberId]; if (filename != null) break; } } } if (filename == null && memberId != null) { filename = fileNames[memberId]; } // Debug.Assert(filename != null, string.Format("Filename for assembly:memberId [{0}:{1}] not found", currentAssemblyName, memberId)); // Debug.WriteLine(string.Format("GetFilenameForIdInternal('{0}','{1}') => {2}", currentAssemblyName, memberId, filename)); if (filename != null) return filename; if (utilities != null) { if (memberId.Length > 2 && memberId[1] == ':') memberId = memberId.Substring(2); filename = utilities.FormatOnlineSDKLink(memberId); if (filename != null) return filename; } return String.Empty; } private string GetFilenameForIdSpecial(string assemblyName, string memberId, string postfix) { string fn = GetFilenameForIdInternal(assemblyName, memberId); if (fn != null && fn.Length > EXT.Length) { fn = fn.Insert(fn.Length - EXT.Length, postfix); } // Debug.WriteLine(string.Format("GetFilenameForIdSpecial('{0}','{1}') => {2}", assemblyName, memberId, fn)); return fn; } private void Register(string assemblyName, string id, string displayName, string fileName) { // Debug.WriteLine(string.Format("Registering [{0},{1}]=[{2},{3}]", assemblyName, id, displayName, fileName)); fileNames[assemblyName + id] = fileName; elemNames[assemblyName + id] = displayName; } /// /// of the form "T:XXX", "F:XXX" etc /// private static string CalculateFilenameForId(string assemblyName, string id, string overload) { if (id == null) { return assemblyName + EXT; } // char idType = '\0'; int ix = id.IndexOf(':'); // if (ix > -1) { // idType = id[0]; // } id = id.Substring(ix + 1); // constructors could be #ctor or #cctor // int ixDotHash = id.IndexOf(".#c"); // if (ixDotHash > -1) // id = id.Substring(0, ixDotHash); // methods could have "(" int ixLBrace = id.IndexOf("("); if (ixLBrace > -1) id = id.Substring(0, ixLBrace); if (overload != null) { id += overload; } id = id.Replace('#', '~'); // generic methods have the form "methodname" id = id.Replace('<', '{'); id = id.Replace('>', '}'); id = id.Replace('`', '$'); return assemblyName + "~" + id + EXT; } #endregion #region Xml Utility Methods private static string GetNodeOverload(XmlNode memberNode) { return XmlUtils.GetAttributeString(memberNode, "overload", false); } private static string GetNodeId(XmlNode node) { return XmlUtils.GetNodeId(node); } private static string GetNodeName(XmlNode node) { return XmlUtils.GetNodeName(node); } private static string GetNodeDisplayName(XmlNode node) { return XmlUtils.GetNodeDisplayName(node); } #endregion } }