/// <inheritdoc /> protected override bool BindControlValue(string propertyName) { ProjectProperty projProp; VisibleItems items; #if !STANDALONEGUI if (this.ProjectMgr == null) { return(false); } #else if (this.CurrentProject == null) { return(false); } #endif if (propertyName == "ApiFilter") { // Pass it the Sandcastle project instance as we use the designer dialog to edit the collection // and it obtains it from the collection to do the required partial build. #if !STANDALONEGUI var filter = new ApiFilterCollection { Project = ((SandcastleBuilderProjectNode)this.ProjectMgr).SandcastleProject }; projProp = this.ProjectMgr.BuildProject.GetProperty("ApiFilter"); #else var filter = new ApiFilterCollection { Project = base.CurrentProject }; projProp = this.CurrentProject.MSBuildProject.GetProperty("ApiFilter"); #endif if (projProp != null && !String.IsNullOrEmpty(projProp.UnevaluatedValue)) { filter.FromXml(projProp.UnevaluatedValue); } ucVisibilityPropertiesPageContent.ApiFilter = filter; ucVisibilityPropertiesPageContent.ApiFilterHasChanges = false; ucVisibilityPropertiesPageContent.UpdateApiFilterInfo(); return(true); } #if !STANDALONEGUI projProp = this.ProjectMgr.BuildProject.GetProperty("VisibleItems"); #else projProp = this.CurrentProject.MSBuildProject.GetProperty("VisibleItems"); #endif // If not found or not valid, we'll ignore it and use the defaults if (projProp == null || !Enum.TryParse <VisibleItems>(projProp.UnevaluatedValue, out items)) { items = VisibleItems.InheritedFrameworkMembers | VisibleItems.InheritedMembers | VisibleItems.Protected | VisibleItems.ProtectedInternalAsProtected | VisibleItems.NonBrowsable; } ucVisibilityPropertiesPageContent.VisibleItems = items; return(true); }
//===================================================================== /// <summary> /// This is used to manually apply the specified API filter to the /// specified reflection information file. /// </summary> /// <param name="apiFilter">The API filter to apply</param> /// <param name="reflectionFilename">The reflection information file</param> /// <remarks>This can be used by any plug-in that does not produce a /// reflection information file using <b>MRefBuilder.exe</b>. In such /// cases, the API filter is not applied unless the plug-in uses this /// method. If the reflection information file is produced by /// <b>MRefBuilder.exe</b>, there is no need to use this method as it /// will apply the API filter automatically to the file that it /// produces.</remarks> public void ApplyManualApiFilter(ApiFilterCollection apiFilter, string reflectionFilename) { XmlDocument refInfo; XmlNode apis; string id; bool keep; refInfo = new XmlDocument(); refInfo.Load(reflectionFilename); apis = refInfo.SelectSingleNode("reflection/apis"); foreach(ApiFilter nsFilter in apiFilter) if(nsFilter.Children.Count == 0) this.RemoveNamespace(apis, nsFilter.FullName); else if(!nsFilter.IsExposed) { // Remove all but the indicated types foreach(XmlNode typeNode in apis.SelectNodes( "api[starts-with(@id, 'T:') and containers/" + "namespace/@api='N:" + nsFilter.FullName + "']")) { id = typeNode.Attributes["id"].Value.Substring(2); keep = false; foreach(ApiFilter typeFilter in nsFilter.Children) if(typeFilter.FullName == id) { // Just keep or remove members this.ApplyMemberFilter(apis, typeFilter); keep = true; break; } if(!keep) this.RemoveType(apis, id); } } else { // Remove just the indicated types or their members foreach(ApiFilter typeFilter in nsFilter.Children) { if(!typeFilter.IsExposed && typeFilter.Children.Count == 0) { this.RemoveType(apis, typeFilter.FullName); continue; } // Just keep or remove members this.ApplyMemberFilter(apis, typeFilter); } } refInfo.Save(reflectionFilename); }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <param name="filter">The item collection to edit</param> internal ApiFilterEditorDlg(ApiFilterCollection filter) { InitializeComponent(); // An italic font is used for nodes that cannot have their // check state changed. A tooltip will give further details. tvApiList.Nodes[0].NodeFont = tvApiList.Nodes[1].NodeFont = italicFont = new Font(this.Font, FontStyle.Italic); apiFilter = filter; }
/// <summary> /// Edit the project's API filter /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void btnEditAPIFilter_Click(object sender, EventArgs e) { #if !STANDALONEGUI if (this.ProjectMgr == null) { return; } // Apply any pending visibility changes first if (this.IsDirty && ((IPropertyPage)this).Apply() != VSConstants.S_OK) { return; } // Create an API filter collection that we can edit ApiFilterCollection filter = new ApiFilterCollection { Project = ((SandcastleBuilderProjectNode)this.ProjectMgr).SandcastleProject }; #else if (base.CurrentProject == null) { return; } // Apply any pending visibility changes first if (this.IsDirty && !this.Apply()) { return; } // Create an API filter collection that we can edit ApiFilterCollection filter = new ApiFilterCollection { Project = base.CurrentProject }; #endif filter.FromXml(apiFilter); using (ApiFilterEditorDlg dlg = new ApiFilterEditorDlg(filter)) { dlg.ShowDialog(); string newFilter = filter.ToXml(); // If it changes, mark the page as dirty and update the local copy of the filter if (apiFilter != newFilter) { apiFilter = newFilter; this.IsDirty = filterChanged = true; this.UpdateApiFilterInfo(); } } }
/// <inheritdoc /> public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { ApiFilterCollection items = value as ApiFilterCollection; if (items == null || destinationType != typeof(string)) { return(base.ConvertTo(context, culture, value, destinationType)); } // Since we can't give a meaningful count, just indicate // whether or not the topics will be filtered. if (items.Count == 0) { return("(None)"); } return("(Filter defined)"); }
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value) { // Get the API filter collection ApiFilterCollection items = value as ApiFilterCollection; if (context == null || provider == null || context.Instance == null || items == null) { return(base.EditValue(context, provider, value)); } using (ApiFilterEditorDlg dlg = new ApiFilterEditorDlg(items)) { dlg.ShowDialog(); } return(value); }
//===================================================================== /// <summary> /// This is used to convert the API filter from the build into a /// dictionary so that it is easier to look up the entries. /// </summary> /// <param name="filter">The filter collection to search for project /// level exclusions.</param> private void ConvertApiFilter(ApiFilterCollection filter) { foreach(ApiFilter entry in filter) { #if DEBUG // This shouldn't happen. If it does, there's a problem // in generating the API filter in the build process. if(buildFilterEntries.ContainsKey(entry.FullName)) System.Diagnostics.Debugger.Break(); #endif // Sometimes, it does happen for unknown reasons. Ignore it // so that it doesn't prevent the filter from opening. if(!buildFilterEntries.ContainsKey(entry.FullName)) buildFilterEntries.Add(entry.FullName, entry); if(entry.Children.Count != 0) this.ConvertApiFilter(entry.Children); } }
/// <summary> /// This refreshes the project instance property values by reloading them from the underlying MSBuild /// project. /// </summary> public void RefreshProjectProperties() { projectPropertyCache = null; docSources = null; apiFilter = null; namespaceSummaries = null; componentConfigs = null; plugInConfigs = null; msBuildProject.ReevaluateIfNecessary(); this.LoadProperties(); }
//===================================================================== /// <summary> /// This is used to manually apply the specified API filter to the specified reflection information file /// </summary> /// <param name="filterToApply">The API filter to apply</param> /// <param name="reflectionFilename">The reflection information file</param> private void ApplyManualApiFilter(ApiFilterCollection filterToApply, string reflectionFilename) { XmlDocument refInfo; XmlNode apis; string id; bool keep; refInfo = new XmlDocument(); refInfo.Load(reflectionFilename); apis = refInfo.SelectSingleNode("reflection/apis"); foreach (ApiFilter nsFilter in filterToApply) { if (nsFilter.Children.Count == 0) { this.RemoveNamespace(apis, nsFilter.FullName); } else if (!nsFilter.IsExposed) { // Remove all but the indicated types foreach (XmlNode typeNode in apis.SelectNodes("api[starts-with(@id, 'T:') and containers/" + "namespace/@api='N:" + nsFilter.FullName + "']")) { id = typeNode.Attributes["id"].Value.Substring(2); keep = false; foreach (ApiFilter typeFilter in nsFilter.Children) { if (typeFilter.FullName == id) { // Just keep or remove members this.ApplyMemberFilter(apis, typeFilter); keep = true; break; } } if (!keep) { this.RemoveType(apis, id); } } } else { // Remove just the indicated types or their members foreach (ApiFilter typeFilter in nsFilter.Children) { if (!typeFilter.IsExposed && typeFilter.Children.Count == 0) { this.RemoveType(apis, typeFilter.FullName); continue; } // Just keep or remove members this.ApplyMemberFilter(apis, typeFilter); } } } refInfo.Save(reflectionFilename); }
//===================================================================== /// <summary> /// This is used to generate the API filter collection used by /// MRefBuilder to exclude items from the reflection information file. /// </summary> /// <remarks>Namespaces and members with an <code><exclude /></code> /// tag in their comments are removed using the ripping feature as it /// is more efficient than searching for and removing them from the /// reflection file after it has been generated especially on large /// projects.</remarks> private void GenerateApiFilter() { XmlNodeList excludes; XmlNode docMember; List<string> ripList; string nameSpace, memberName, typeName, fullName; int pos; this.ReportProgress(BuildStep.GenerateApiFilter, "Generating API filter for MRefBuilder..."); if(this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) return; this.ExecutePlugIns(ExecutionBehaviors.Before); ripList = new List<string>(); // Add excluded namespaces foreach(NamespaceSummaryItem ns in project.NamespaceSummaries) if(!ns.IsDocumented) { memberName = ns.Name; if(memberName[0] == '(') memberName = "N:"; // Global namespace else memberName = "N:" + memberName; ripList.Add(memberName); } // If the namespace summaries don't contain an explicit entry // for the global namespace, exclude it by default. if(project.NamespaceSummaries[null] == null) ripList.Add("N:"); // Add members excluded via comments foreach(XmlCommentsFile comments in commentsFiles) { excludes = comments.Members.SelectNodes("//exclude/.."); foreach(XmlNode member in excludes) { // It should appear at the same level as <summary> so that // we can find the member name in the parent node. if(member.Attributes["name"] == null) { this.ReportProgress(" Incorrect placement of " + "<exclude/> tag. Unable to locate member name."); continue; } memberName = member.Attributes["name"].Value; if(!ripList.Contains(memberName)) ripList.Add(memberName); } } // Sort by entry type and name so that we create the collection // from the namespace down to the members. ripList.Sort( delegate(string x, string y) { ApiEntryType xType = ApiFilter.ApiEntryTypeFromLetter(x[0]), yType = ApiFilter.ApiEntryTypeFromLetter(y[0]); if(xType == yType) return String.Compare(x, y, false, CultureInfo.CurrentCulture); return (int)xType - (int)yType; }); // Clone the project ApiFilter and merge the members from the // rip list. apiFilter = (ApiFilterCollection)project.ApiFilter.Clone(); // For the API filter to work, we have to nest the entries by // namespace, type, and member. As such, we have to break apart // what we've got in the list and merge it with the stuff the user // may have specified using the project's API filter property. foreach(string member in ripList) { // Namespaces are easy if(member[0] == 'N') { if(!apiFilter.MergeEntry(ApiEntryType.Namespace, member.Substring(2), false, true)) this.ReportWarning("BE0008", "Namespace '{0}' " + "excluded via namespace comments conflicted with " + "API filter setting. Exclusion ignored.", member); continue; } // Types and members are a bit tricky. Since we don't have any // real context, we have to assume that we can remove the last // part and look it up. If a type entry isn't found, we can // assume it's the namespace. Where this can fail is on a // nested class where the parent class is lacking XML comments. // Not much we can do about it in that case. if(member[0] == 'T') { fullName = nameSpace = member; typeName = member.Substring(2); memberName = null; } else { // Strip parameters. The ripping feature only goes to // the name level. If one overload is ripped, they are // all ripped. pos = member.IndexOf('('); if(pos != -1) fullName = memberName = member.Substring(0, pos); else fullName = memberName = member; // Generic method pos = memberName.IndexOf("``", StringComparison.Ordinal); if(pos != -1) memberName = memberName.Substring(0, pos); pos = memberName.LastIndexOf('.'); memberName = memberName.Substring(pos + 1); typeName = fullName.Substring(2, pos - 2); nameSpace = "T:" + typeName; } for(int idx = 0; idx < commentsFiles.Count; idx++) { docMember = commentsFiles[idx].Members.SelectSingleNode( "member[@name='" + nameSpace + "']"); if(docMember != null) { pos = nameSpace.LastIndexOf('.'); if(pos == -1) { nameSpace = "N:"; break; } else nameSpace = nameSpace.Substring(0, pos); idx = -1; } } nameSpace = nameSpace.Substring(2); // If the names still match, we probably didn't find comments // for the type so assume the namespace is the part up to // the last period. if(nameSpace == typeName) { pos = nameSpace.LastIndexOf('.'); if(pos != -1) nameSpace = nameSpace.Substring(0, pos); else nameSpace = "N:"; // Global namespace } if(apiFilter.AddNamespaceChild(fullName, nameSpace, typeName, memberName)) { if(fullName.Length > 2) { // If it's a nested class, adjust the filter name fullName = typeName; typeName = typeName.Substring(nameSpace.Length + 1); if(typeName.IndexOf('.') != -1) foreach(ApiFilter ns in apiFilter) if(ns.FullName == nameSpace) { foreach(ApiFilter t in ns.Children) if(t.FullName == fullName) { t.FilterName = typeName; break; } break; } } } else this.ReportWarning("BE0009", "'{0}' is marked with " + "<exclude /> but conflicted with the API filter " + "setting. Exclusion ignored.", member); } this.ExecutePlugIns(ExecutionBehaviors.After); }
/// <summary> /// This is used to convert the API filter from the build into a /// dictionary so that it is easier to look up the entries. /// </summary> /// <param name="filter">The filter collection to search for project /// level exclusions.</param> private void ConvertApiFilter(ApiFilterCollection filter) { foreach(ApiFilter entry in filter) { #if DEBUG // This shouldn't happen. If it does, there's a problem // in generating the API filter in the build process. if(buildFilterEntries.ContainsKey(entry.FullName)) System.Diagnostics.Debugger.Break(); #endif buildFilterEntries.Add(entry.FullName, entry); if(entry.Children.Count != 0) this.ConvertApiFilter(entry.Children); } }
//===================================================================== /// <summary> /// This is used to generate the API filter collection used by /// MRefBuilder to exclude items from the reflection information file. /// </summary> /// <remarks>Namespaces and members with an <code><exclude /></code> /// tag in their comments are removed using the ripping feature as it /// is more efficient than searching for and removing them from the /// reflection file after it has been generated especially on large /// projects.</remarks> private void GenerateApiFilter() { XmlNodeList excludes; XmlNode docMember; List <string> ripList; string nameSpace, memberName, typeName, fullName; int pos; this.ReportProgress(BuildStep.GenerateApiFilter, "Generating API filter for MRefBuilder..."); if (this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { return; } this.ExecutePlugIns(ExecutionBehaviors.Before); ripList = new List <string>(); // Add excluded namespaces foreach (NamespaceSummaryItem ns in project.NamespaceSummaries) { if (!ns.IsDocumented) { memberName = ns.Name; if (memberName[0] == '(') { memberName = "N:"; // Global namespace } else { memberName = "N:" + memberName; } ripList.Add(memberName); } } // If the namespace summaries don't contain an explicit entry // for the global namespace, exclude it by default. if (project.NamespaceSummaries[null] == null) { ripList.Add("N:"); } // Add members excluded via comments foreach (XmlCommentsFile comments in commentsFiles) { excludes = comments.Members.SelectNodes("//exclude/.."); foreach (XmlNode member in excludes) { // It should appear at the same level as <summary> so that // we can find the member name in the parent node. if (member.Attributes["name"] == null) { this.ReportProgress(" Incorrect placement of " + "<exclude/> tag. Unable to locate member name."); continue; } memberName = member.Attributes["name"].Value; if (!ripList.Contains(memberName)) { ripList.Add(memberName); } } } // Sort by entry type and name so that we create the collection // from the namespace down to the members. ripList.Sort( delegate(string x, string y) { ApiEntryType xType = ApiFilter.ApiEntryTypeFromLetter(x[0]), yType = ApiFilter.ApiEntryTypeFromLetter(y[0]); if (xType == yType) { return(String.Compare(x, y, false, CultureInfo.CurrentCulture)); } return((int)xType - (int)yType); }); // Clone the project ApiFilter and merge the members from the // rip list. apiFilter = (ApiFilterCollection)project.ApiFilter.Clone(); // For the API filter to work, we have to nest the entries by // namespace, type, and member. As such, we have to break apart // what we've got in the list and merge it with the stuff the user // may have specified using the project's API filter property. foreach (string member in ripList) { // Namespaces are easy if (member[0] == 'N') { if (!apiFilter.MergeEntry(ApiEntryType.Namespace, member.Substring(2), false, true)) { this.ReportWarning("BE0008", "Namespace '{0}' " + "excluded via namespace comments conflicted with " + "API filter setting. Exclusion ignored.", member); } continue; } // Types and members are a bit tricky. Since we don't have any // real context, we have to assume that we can remove the last // part and look it up. If a type entry isn't found, we can // assume it's the namespace. Where this can fail is on a // nested class where the parent class is lacking XML comments. // Not much we can do about it in that case. if (member[0] == 'T') { fullName = nameSpace = member; typeName = member.Substring(2); memberName = null; } else { // Strip parameters. The ripping feature only goes to // the name level. If one overload is ripped, they are // all ripped. pos = member.IndexOf('('); if (pos != -1) { fullName = memberName = member.Substring(0, pos); } else { fullName = memberName = member; } // Generic method pos = memberName.IndexOf("``", StringComparison.Ordinal); if (pos != -1) { memberName = memberName.Substring(0, pos); } pos = memberName.LastIndexOf('.'); memberName = memberName.Substring(pos + 1); typeName = fullName.Substring(2, pos - 2); nameSpace = "T:" + typeName; } for (int idx = 0; idx < commentsFiles.Count; idx++) { docMember = commentsFiles[idx].Members.SelectSingleNode( "member[@name='" + nameSpace + "']"); if (docMember != null) { pos = nameSpace.LastIndexOf('.'); if (pos == -1) { nameSpace = "N:"; break; } else { nameSpace = nameSpace.Substring(0, pos); } idx = -1; } } nameSpace = nameSpace.Substring(2); // If the names still match, we probably didn't find comments // for the type so assume the namespace is the part up to // the last period. if (nameSpace == typeName) { pos = nameSpace.LastIndexOf('.'); if (pos != -1) { nameSpace = nameSpace.Substring(0, pos); } else { nameSpace = "N:"; // Global namespace } } if (apiFilter.AddNamespaceChild(fullName, nameSpace, typeName, memberName)) { if (fullName.Length > 2) { // If it's a nested class, adjust the filter name fullName = typeName; typeName = typeName.Substring(nameSpace.Length + 1); if (typeName.IndexOf('.') != -1) { foreach (ApiFilter ns in apiFilter) { if (ns.FullName == nameSpace) { foreach (ApiFilter t in ns.Children) { if (t.FullName == fullName) { t.FilterName = typeName; break; } } break; } } } } } else { this.ReportWarning("BE0009", "'{0}' is marked with " + "<exclude /> but conflicted with the API filter " + "setting. Exclusion ignored.", member); } } this.ExecutePlugIns(ExecutionBehaviors.After); }
/// <summary> /// Edit the project's API filter /// </summary> /// <param name="sender">The sender of the event</param> /// <param name="e">The event arguments</param> private void btnEditAPIFilter_Click(object sender, EventArgs e) { #if !STANDALONEGUI if(this.ProjectMgr == null) return; // Apply any pending visibility changes first if(this.IsDirty && ((IPropertyPage)this).Apply() != VSConstants.S_OK) return; // Create an API filter collection that we can edit ApiFilterCollection filter = new ApiFilterCollection { Project = ((SandcastleBuilderProjectNode)this.ProjectMgr).SandcastleProject }; #else if(base.CurrentProject == null) return; // Apply any pending visibility changes first if(this.IsDirty && !this.Apply()) return; // Create an API filter collection that we can edit ApiFilterCollection filter = new ApiFilterCollection { Project = base.CurrentProject }; #endif filter.FromXml(apiFilter); using(ApiFilterEditorDlg dlg = new ApiFilterEditorDlg(filter)) { dlg.ShowDialog(); string newFilter = filter.ToXml(); // If it changes, mark the page as dirty and update the local copy of the filter if(apiFilter != newFilter) { apiFilter = newFilter; this.IsDirty = filterChanged = true; this.UpdateApiFilterInfo(); } } }
//===================================================================== /// <summary> /// Constructor /// </summary> /// <overloads>There are five overloads for the constructor</overloads> protected SandcastleProject() { characterMatchEval = new MatchEvaluator(this.OnCharacterMatch); buildVarMatchEval = new MatchEvaluator(this.OnBuildVarMatch); docSources = new DocumentationSourceCollection(this); docSources.ListChanged += docSources_ListChanged; namespaceSummaries = new NamespaceSummaryItemCollection(this); namespaceSummaries.ListChanged += ItemList_ListChanged; references = new ReferenceItemCollection(this); references.ListChanged += ItemList_ListChanged; componentConfigs = new ComponentConfigurationDictionary(this); plugInConfigs = new PlugInConfigurationDictionary(this); apiFilter = new ApiFilterCollection(this); apiFilter.ListChanged += ItemList_ListChanged; helpAttributes = new MSHelpAttrCollection(this); helpAttributes.ListChanged += ItemList_ListChanged; try { loadingProperties = removeProjectWhenDisposed = true; contentPlacement = ContentPlacement.AboveNamespaces; cleanIntermediates = keepLogFile = binaryTOC = includeStopWordList = true; this.BuildLogFile = null; missingTags = MissingTags.Summary | MissingTags.Parameter | MissingTags.TypeParameter | MissingTags.Returns | MissingTags.AutoDocumentCtors | MissingTags.Namespace | MissingTags.AutoDocumentDispose; visibleItems = VisibleItems.InheritedFrameworkMembers | VisibleItems.InheritedMembers | VisibleItems.Protected | VisibleItems.ProtectedInternalAsProtected; buildAssemblerVerbosity = BuildAssemblerVerbosity.OnlyWarningsAndErrors; helpFileFormat = HelpFileFormats.HtmlHelp1; htmlSdkLinkType = websiteSdkLinkType = HtmlSdkLinkType.Msdn; help2SdkLinkType = MSHelp2SdkLinkType.Msdn; helpViewerSdkLinkType = MSHelpViewerSdkLinkType.Msdn; sdkLinkTarget = SdkLinkTarget.Blank; presentationStyle = Constants.DefaultPresentationStyle; namingMethod = NamingMethod.Guid; syntaxFilters = ComponentUtilities.DefaultSyntaxFilter; collectionTocStyle = CollectionTocStyle.Hierarchical; helpFileVersion = "1.0.0.0"; tocOrder = -1; maximumGroupParts = 2; this.OutputPath = null; this.HtmlHelp1xCompilerPath = this.HtmlHelp2xCompilerPath = this.WorkingPath = this.ComponentPath = null; this.HelpTitle = this.HtmlHelpName = this.CopyrightHref = this.CopyrightText = this.FeedbackEMailAddress = this.FeedbackEMailLinkText = this.HeaderText = this.FooterText = this.ProjectSummary = this.RootNamespaceTitle = this.PlugInNamespaces = this.TopicVersion = this.TocParentId = this.TocParentVersion = this.CatalogProductId = this.CatalogVersion = this.CatalogName = null; this.FrameworkVersion = null; language = new CultureInfo("en-US"); } finally { loadingProperties = false; } }