summaryrefslogtreecommitdiffstats
path: root/tools/Sandcastle/Source/BuildAssembler/BuildComponents/PlatformsComponent.cs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/Sandcastle/Source/BuildAssembler/BuildComponents/PlatformsComponent.cs')
-rw-r--r--tools/Sandcastle/Source/BuildAssembler/BuildComponents/PlatformsComponent.cs679
1 files changed, 679 insertions, 0 deletions
diff --git a/tools/Sandcastle/Source/BuildAssembler/BuildComponents/PlatformsComponent.cs b/tools/Sandcastle/Source/BuildAssembler/BuildComponents/PlatformsComponent.cs
new file mode 100644
index 0000000..7aaa64d
--- /dev/null
+++ b/tools/Sandcastle/Source/BuildAssembler/BuildComponents/PlatformsComponent.cs
@@ -0,0 +1,679 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Xml;
+using System.Xml.XPath;
+using System.IO;
+using System.Text;
+
+// still have problems with spaces
+
+namespace Microsoft.Ddue.Tools
+{
+
+ public class PlatformsComponent : BuildComponent
+ {
+
+ private Dictionary<string, Dictionary<string, VersionFilter>> versionFilters = new Dictionary<string, Dictionary<string, VersionFilter>>();
+
+ private XPathExpression platformQuery = XPathExpression.Compile("/platforms/platform");
+ private XPathExpression referenceExpression = XPathExpression.Compile("/document/reference");
+ private XPathExpression versionNodesExpression = XPathExpression.Compile("versions//version");
+ private XPathExpression apiGroupExpression = XPathExpression.Compile("string(apidata/@group)");
+ private XPathExpression topicdataGroupExpression = XPathExpression.Compile("string(topicdata/@group)");
+ private XPathExpression topicdataSubgroupExpression = XPathExpression.Compile("string(topicdata/@subgroup)");
+ private XPathExpression listTypeNameExpression = XPathExpression.Compile("string(apidata/@name)");
+ private XPathExpression apiNamespaceNameExpression = XPathExpression.Compile("string(containers/namespace/apidata/@name)");
+ private XPathExpression memberTypeNameExpression = XPathExpression.Compile("string(containers/type/apidata/@name)");
+
+ private XPathExpression listTopicElementNodesExpression = XPathExpression.Compile("elements//element");
+ private XPathExpression elementIdExpression = XPathExpression.Compile("string(@api)");
+
+ public PlatformsComponent(BuildAssembler assembler, XPathNavigator configuration)
+ : base(assembler, configuration)
+ {
+ // get the filter files
+ XPathNodeIterator filterNodes = configuration.Select("filter");
+ foreach (XPathNavigator filterNode in filterNodes)
+ {
+ string filterFiles = filterNode.GetAttribute("files", String.Empty);
+ if ((filterFiles == null) || (filterFiles.Length == 0))
+ throw new ConfigurationErrorsException("The filter/@files attribute must specify a path.");
+ ParseDocuments(filterFiles);
+ }
+ //WriteMessage(MessageLevel.Info, String.Format("Indexed {0} elements.", index.Count));
+ }
+
+ public void ParseDocuments(string wildcardPath)
+ {
+ string filterFiles = Environment.ExpandEnvironmentVariables(wildcardPath);
+ if ((filterFiles == null) || (filterFiles.Length == 0))
+ throw new ConfigurationErrorsException("The filter path is an empty string.");
+
+ WriteMessage(MessageLevel.Info, String.Format("Searching for files that match '{0}'.", filterFiles));
+ string directoryPart = Path.GetDirectoryName(filterFiles);
+ if (String.IsNullOrEmpty(directoryPart))
+ directoryPart = Environment.CurrentDirectory;
+ directoryPart = Path.GetFullPath(directoryPart);
+ string filePart = Path.GetFileName(filterFiles);
+ string[] files = Directory.GetFiles(directoryPart, filePart);
+ foreach (string file in files)
+ ParseDocument(file);
+ WriteMessage(MessageLevel.Info, String.Format("Found {0} files in {1}.", files.Length, filterFiles));
+ }
+
+ private void AddPlatformVersionFilter(string platformId, string versionId, XPathNavigator platformNode, string file)
+ {
+ Dictionary<string, VersionFilter> platformFrameworks;
+ if (!versionFilters.TryGetValue(platformId, out platformFrameworks))
+ {
+ platformFrameworks = new Dictionary<string, VersionFilter>();
+ versionFilters.Add(platformId, platformFrameworks);
+ }
+
+ try
+ {
+ VersionFilter filter;
+ XmlReader platformReader = platformNode.ReadSubtree();
+ platformReader.MoveToContent();
+ if (!platformFrameworks.TryGetValue(versionId, out filter))
+ {
+ filter = new VersionFilter(platformReader, versionId, file);
+ }
+ else
+ {
+ // if the platform already has a filter for this version, add the data from the current platform node
+ filter.AddPlatformNode(platformReader, file);
+ }
+ platformReader.Close();
+
+ platformFrameworks.Remove(versionId);
+ platformFrameworks.Add(versionId, filter);
+ }
+ catch (Exception e)
+ {
+ WriteMessage(MessageLevel.Error, e.Message);
+ }
+ }
+
+ private void ParseDocument(string file)
+ {
+ try
+ {
+ XPathDocument document = new XPathDocument(file);
+
+ XPathNodeIterator platformNodes = document.CreateNavigator().Select(platformQuery);
+ foreach (XPathNavigator platformNode in platformNodes)
+ {
+ string platformId = platformNode.GetAttribute("name", String.Empty);
+ string[] platformIds = platformId.Split(',');
+
+ string version = platformNode.GetAttribute("version", String.Empty);
+ string[] versionIds = version.Split(',');
+ for (int i = 0; i < versionIds.Length; i++)
+ {
+ for (int j = 0; j < platformIds.Length; j++)
+ {
+ XPathNavigator platformNodeClone = platformNode.Clone();
+ AddPlatformVersionFilter(platformIds[j], versionIds[i], platformNodeClone, file);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ WriteMessage(MessageLevel.Error, e.Message);
+ }
+ }
+
+ public override void Apply(XmlDocument document, string key)
+ {
+ XPathNavigator targetDoc = document.CreateNavigator();
+ XPathNavigator referenceNode = targetDoc.SelectSingleNode(referenceExpression);
+ string apiGroup = (string)referenceNode.Evaluate(apiGroupExpression);
+ string topicdataGroup = (string)referenceNode.Evaluate(topicdataGroupExpression);
+ string topicdataSubgroup = (string)referenceNode.Evaluate(topicdataSubgroupExpression);
+
+ // get the namespace and typename of the current type to locate the filter information that applies to the current topic
+ // For filtering inherited members, the platform filters use the namespace and typename of the inheriting type, not the declaring type,
+ string topicNamespaceName = (string)referenceNode.Evaluate(apiNamespaceNameExpression);
+ string topicTypeName = (string)referenceNode.Evaluate(memberTypeNameExpression);
+
+ // write platforms info for normal api topics (excluding memberlist and overload list topics
+ if (topicdataGroup != "list" && topicdataSubgroup != "DerivedTypeList" && (apiGroup == "type" || apiGroup == "member") && versionFilters.Count > 0)
+ {
+ WriteApiPlatforms(referenceNode, apiGroup, key, topicTypeName, topicNamespaceName);
+ }
+
+ // write platforms for elements//element nodes (member list and overload topics; not root or namespace)
+ if ((topicdataGroup == "list" && topicdataSubgroup != "DerivedTypeList") && apiGroup != "root" && versionFilters.Count > 0)
+ {
+ if (topicdataSubgroup != "overload")
+ topicTypeName = (string)referenceNode.Evaluate(listTypeNameExpression);
+
+ XPathNodeIterator elementNodes = referenceNode.Select(listTopicElementNodesExpression);
+ foreach (XPathNavigator elementNode in elementNodes)
+ {
+ string elementId = (string)elementNode.Evaluate(elementIdExpression);
+ WriteApiPlatforms(elementNode, "member", elementId, topicTypeName, topicNamespaceName);
+ }
+ }
+ }
+
+ private void WriteApiPlatforms(XPathNavigator referenceNode, string apiGroup, string key, string topicTypeName, string topicNamespaceName)
+ {
+ XPathNodeIterator versionNodes = referenceNode.Select(versionNodesExpression);
+ List<string> supportedPlatforms = new List<string>();
+ XmlWriter platformsWriter = referenceNode.AppendChild();
+
+ foreach (string platformId in versionFilters.Keys)
+ {
+ Dictionary<string, VersionFilter> filters = versionFilters[platformId];
+ bool included = false;
+ foreach (XPathNavigator versionNode in versionNodes)
+ {
+ string versionId = versionNode.GetAttribute("name", string.Empty);
+ VersionFilter filter;
+ if (filters.TryGetValue(versionId, out filter))
+ {
+ switch (apiGroup)
+ {
+ case "type":
+ included = filter.IsIncludedType(referenceNode, key, topicNamespaceName);
+ break;
+ case "member":
+ included = filter.IsIncludedMember(referenceNode, key, topicTypeName, topicNamespaceName);
+ break;
+ }
+ }
+ if (included)
+ break;
+ }
+
+ if (included)
+ supportedPlatforms.Add(platformId);
+ }
+ platformsWriter.WriteStartElement("platforms");
+ foreach (string platformId in supportedPlatforms)
+ {
+ platformsWriter.WriteElementString("platform", platformId);
+ }
+ platformsWriter.WriteEndElement();
+ platformsWriter.Close();
+ }
+
+ }
+
+ public abstract class InclusionFilter
+ {
+ public InclusionFilter(string file)
+ {
+ sourceFiles.Add(file);
+ }
+
+ protected List<string> sourceFiles = new List<string>();
+
+ protected static XPathExpression apiNameExpression = XPathExpression.Compile("string(apidata/@name)");
+ protected static XPathExpression apiParameterNodesExpression = XPathExpression.Compile("parameters/parameter");
+ protected static XPathExpression apiParameterTypeNameExpression = XPathExpression.Compile("string(.//type/@api)");
+ protected static XPathExpression apiParameterTemplateNameExpression = XPathExpression.Compile("string(.//template/@name)");
+ }
+
+ public class VersionFilter : InclusionFilter
+ {
+
+ public VersionFilter(XmlReader platformReader, string id, string file)
+ : base(file)
+ {
+ // platform/@version can only list included framework versions; excluding versions is not supported
+ included = true;
+ versionId = id;
+ AddPlatformNode(platformReader, file);
+ }
+
+ public void AddPlatformNode(XmlReader platformReader, string file)
+ {
+ XmlReader subtree = platformReader.ReadSubtree();
+ while (subtree.Read())
+ {
+ if ((subtree.NodeType == XmlNodeType.Element) && (subtree.Name == "namespace"))
+ {
+ string namespaceName = subtree.GetAttribute("name");
+
+ NamespaceFilter namespaceFilter;
+ if (!namespaceFilters.TryGetValue(namespaceName, out namespaceFilter))
+ {
+ namespaceFilter = new NamespaceFilter(subtree, file);
+ }
+ else
+ {
+ // if the version already has a filter for this namespace, add the data from the current namespace node
+ // unless the namespace node has a different @include value, in which case log a warning
+ string nsIncludeAttr = subtree.GetAttribute("include");
+ bool nsIncluded = Convert.ToBoolean(string.IsNullOrEmpty(nsIncludeAttr) ? "true" : nsIncludeAttr);
+ if (nsIncluded != namespaceFilter.Included)
+ {
+ // write warning message about conflicting filters
+ // ISSUE: how to invoke WriteMessage from here
+ Console.Write("");
+ }
+ else
+ {
+ namespaceFilter.AddNamespaceNode(subtree, file);
+ }
+
+ }
+ namespaceFilters.Remove(namespaceName);
+ namespaceFilters.Add(namespaceName, namespaceFilter);
+ }
+ }
+ subtree.Close();
+ }
+
+ private string versionId;
+
+ // platform/@version can only list included framework versions; excluding versions is not supported
+ private bool included;
+
+ private Dictionary<string, NamespaceFilter> namespaceFilters = new Dictionary<string, NamespaceFilter>();
+
+ public string VersionId
+ {
+ get
+ {
+ return (versionId);
+ }
+ }
+
+ public Dictionary<string, NamespaceFilter> NamespaceFilters
+ {
+ get
+ {
+ return (namespaceFilters);
+ }
+ }
+
+ /// <summary>
+ /// If we get here, we know that the platform supports this version, and the api is included in this version.
+ /// So returns true unless the type or its namespace are explicitly excluded by this version filter.
+ /// </summary>
+ /// <param name="referenceNode">The type's reflection data.</param>
+ /// <returns></returns>
+ public bool IsIncludedType(XPathNavigator referenceNode, string key, string topicNamespaceName)
+ {
+ // if we have a filter for the topic's namespace, check it
+ NamespaceFilter namespaceFilter;
+ if (namespaceFilters.TryGetValue(topicNamespaceName, out namespaceFilter))
+ {
+ return namespaceFilter.IsIncludedType(referenceNode, key);
+ }
+ return included;
+ }
+
+ public bool IsIncludedMember(XPathNavigator referenceNode, string key, string topicTypeName, string topicNamespaceName)
+ {
+ // if we have a filter for the topic's namespace, check it
+ NamespaceFilter namespaceFilter;
+ if (namespaceFilters.TryGetValue(topicNamespaceName, out namespaceFilter))
+ {
+ return namespaceFilter.IsIncludedMember(referenceNode, key, topicTypeName);
+ }
+ return included;
+ }
+ }
+
+
+ public class NamespaceFilter : InclusionFilter
+ {
+
+ public NamespaceFilter(XmlReader namespaceReader, string file)
+ : base(file)
+ {
+ name = namespaceReader.GetAttribute("name");
+ string includeAttr = namespaceReader.GetAttribute("include");
+ included = Convert.ToBoolean(string.IsNullOrEmpty(includeAttr) ? "true" : includeAttr);
+ AddNamespaceNode(namespaceReader, file);
+ }
+
+ public void AddNamespaceNode(XmlReader namespaceReader, string file)
+ {
+ XmlReader subtree = namespaceReader.ReadSubtree();
+ while (subtree.Read())
+ {
+ if ((subtree.NodeType == XmlNodeType.Element) && (subtree.Name == "type"))
+ {
+ string typeName = subtree.GetAttribute("name");
+
+ TypeFilter typeFilter;
+ if (!typeFilters.TryGetValue(typeName, out typeFilter))
+ {
+ typeFilter = new TypeFilter(subtree, file);
+ }
+ else
+ {
+ // if the namespace already has a filter for this type, add the data from the current type node
+ // unless the type node has a different @include value, in which case log a warning
+ string typeIncludeAttr = subtree.GetAttribute("include");
+ bool typeIncluded = Convert.ToBoolean(string.IsNullOrEmpty(typeIncludeAttr) ? "true" : typeIncludeAttr);
+ if (typeIncluded != typeFilter.Included)
+ {
+ // write warning message about conflicting filters
+ // ISSUE: how to invoke WriteMessage from here
+ Console.Write("");
+ }
+ else
+ {
+ typeFilter.AddTypeNode(subtree, file);
+ }
+ }
+ typeFilters.Remove(typeName);
+ typeFilters.Add(typeName, typeFilter);
+ }
+ }
+ subtree.Close();
+ }
+
+ private string name;
+
+ private bool included;
+ public bool Included
+ {
+ get
+ {
+ return (included);
+ }
+ }
+
+ private Dictionary<string, TypeFilter> typeFilters = new Dictionary<string, TypeFilter>();
+ public Dictionary<string, TypeFilter> TypeFilters
+ {
+ get
+ {
+ return (typeFilters);
+ }
+ }
+
+ public bool IsIncludedType(XPathNavigator referenceNode, string key)
+ {
+ // get the type's name
+ string typeName = (string)referenceNode.Evaluate(apiNameExpression);
+
+ // if we have a filter for that type, check it
+ TypeFilter typeFilter;
+ if (typeFilters.TryGetValue(typeName, out typeFilter))
+ {
+ return typeFilter.Included;
+ }
+ return included;
+ }
+
+ public bool IsIncludedMember(XPathNavigator referenceNode, string key, string topicTypeName)
+ {
+ // if we have a filter for the type, check it
+ TypeFilter typeFilter;
+ if (typeFilters.TryGetValue(topicTypeName, out typeFilter))
+ {
+ return typeFilter.IsIncludedMember(referenceNode, key);
+ }
+ return included;
+ }
+ }
+
+ public class TypeFilter : InclusionFilter
+ {
+
+ public TypeFilter(XmlReader typeReader, string file)
+ : base(file)
+ {
+ name = typeReader.GetAttribute("name");
+ string includeAttr = typeReader.GetAttribute("include");
+ included = Convert.ToBoolean(string.IsNullOrEmpty(includeAttr) ? "true" : includeAttr);
+ AddTypeNode(typeReader, file);
+ }
+
+ public void AddTypeNode(XmlReader typeReader, string file)
+ {
+ XmlReader subtree = typeReader.ReadSubtree();
+ while (subtree.Read())
+ {
+ if ((subtree.NodeType == XmlNodeType.Element) && (subtree.Name == "member"))
+ {
+ string memberName = subtree.GetAttribute("name");
+
+ MemberFilter memberFilter;
+ if (!memberFilters.TryGetValue(memberName, out memberFilter))
+ {
+ memberFilter = new MemberFilter(subtree, file);
+ }
+ else
+ {
+ // if the type already has a filter for this member, add the data from the current member node
+ // unless the member node has a different @include value, in which case log a warning
+ string memberIncludeAttr = subtree.GetAttribute("include");
+ bool memberIncluded = Convert.ToBoolean(string.IsNullOrEmpty(memberIncludeAttr) ? "true" : memberIncludeAttr);
+ if (memberIncluded != memberFilter.Included)
+ {
+ // write warning message about conflicting filters
+ // ISSUE: how to invoke WriteMessage from here
+ Console.Write("");
+ }
+ else
+ {
+ memberFilter.AddMemberNode(subtree, file);
+ }
+ }
+ memberFilters.Remove(memberName);
+ memberFilters.Add(memberName, memberFilter);
+ }
+ }
+ subtree.Close();
+ }
+
+ private string name;
+
+ private bool included;
+ public bool Included
+ {
+ get
+ {
+ return (included);
+ }
+ }
+
+ private Dictionary<string, MemberFilter> memberFilters = new Dictionary<string, MemberFilter>();
+ public Dictionary<string, MemberFilter> MemberFilters
+ {
+ get
+ {
+ return (memberFilters);
+ }
+ }
+
+ public bool IsIncludedMember(XPathNavigator referenceNode, string key)
+ {
+ // get the member's name
+ string memberName = (string)referenceNode.Evaluate(apiNameExpression);
+
+ // if we have a filter for that member, check it
+ MemberFilter memberFilter;
+ if (memberFilters.TryGetValue(memberName, out memberFilter))
+ {
+ return memberFilter.IsIncludedMember(referenceNode, key);
+ }
+ return included;
+ }
+
+ }
+
+ public class MemberFilter : InclusionFilter
+ {
+
+ public MemberFilter(XmlReader memberReader, string file)
+ : base(file)
+ {
+ name = memberReader.GetAttribute("name");
+
+ string includeAttr = memberReader.GetAttribute("include");
+ included = Convert.ToBoolean(string.IsNullOrEmpty(includeAttr) ? "true" : includeAttr);
+ AddMemberNode(memberReader, file);
+ }
+
+ public void AddMemberNode(XmlReader memberReader, string file)
+ {
+ XmlReader subtree = memberReader.ReadSubtree();
+ while (subtree.Read())
+ {
+ if ((subtree.NodeType == XmlNodeType.Element) && (subtree.Name == "overload"))
+ {
+ string overloadId = subtree.GetAttribute("api");
+ string paramTypes = subtree.GetAttribute("types");
+ string paramNames = subtree.GetAttribute("names");
+ string overloadIncludeAttr = subtree.GetAttribute("include");
+ bool overloadIncluded = Convert.ToBoolean(string.IsNullOrEmpty(overloadIncludeAttr) ? "true" : overloadIncludeAttr);
+ // check for existing overload filters that identify the same overload
+ bool alreadyFiltered = false;
+ foreach (OverloadFilter overloadFilter in overloadFilters)
+ {
+ if (!string.IsNullOrEmpty(overloadId) && overloadId == overloadFilter.OverloadId)
+ alreadyFiltered = true;
+ if (!string.IsNullOrEmpty(paramTypes) && paramTypes == overloadFilter.ParamTypes)
+ alreadyFiltered = true;
+ if (!string.IsNullOrEmpty(paramNames) && paramNames == overloadFilter.ParamNames)
+ alreadyFiltered = true;
+ if (alreadyFiltered && (overloadIncluded != overloadFilter.Included))
+ {
+ // write warning message about conflicting filters
+ // ISSUE: how to invoke WriteMessage from here
+ Console.Write("");
+ }
+ }
+ if (!alreadyFiltered)
+ {
+ OverloadFilter overloadFilter = new OverloadFilter(subtree, file);
+ overloadFilters.Add(overloadFilter);
+ }
+ }
+ }
+ subtree.Close();
+ }
+
+ private string name;
+
+ private bool included;
+ public bool Included
+ {
+ get
+ {
+ return (included);
+ }
+ }
+
+ private List<OverloadFilter> overloadFilters = new List<OverloadFilter>();
+
+ public bool IsIncludedMember(XPathNavigator referenceNode, string key)
+ {
+ if (overloadFilters.Count == 0)
+ return included;
+
+ // get the member's paramTypes string
+
+
+ // get the member's paramNames string
+ XPathNodeIterator parameterNodes = referenceNode.Select(apiParameterNodesExpression);
+
+ StringBuilder paramNames = new StringBuilder();
+ StringBuilder paramTypes = new StringBuilder();
+ int i = 0;
+ foreach (XPathNavigator parameterNode in parameterNodes)
+ {
+ i++;
+ paramNames.Append(parameterNode.GetAttribute("name", string.Empty));
+ if (i < parameterNodes.Count)
+ paramNames.Append(",");
+
+ // BUGBUG: code here and in the psx conversion transform is a quick hack; make it better
+ string arrayOf = (parameterNode.SelectSingleNode("arrayOf") == null) ? "" : "[]";
+ string typeName = (string)parameterNode.Evaluate(apiParameterTypeNameExpression);
+ if (string.IsNullOrEmpty(typeName))
+ typeName = (string)parameterNode.Evaluate(apiParameterTemplateNameExpression);
+
+ int basenameStart = typeName.LastIndexOf('.') + 1;
+ if (basenameStart > 0 && basenameStart < typeName.Length)
+ typeName = typeName.Substring(basenameStart);
+
+ paramTypes.Append(typeName + arrayOf);
+ if (i < parameterNodes.Count)
+ paramTypes.Append(",");
+ }
+
+ foreach (OverloadFilter overloadFilter in overloadFilters)
+ {
+ if (key == overloadFilter.OverloadId)
+ return overloadFilter.Included;
+ if (paramNames.ToString() == overloadFilter.ParamNames)
+ return overloadFilter.Included;
+ if (paramTypes.ToString() == overloadFilter.ParamTypes)
+ return overloadFilter.Included;
+ }
+
+ return included;
+ }
+
+ }
+
+ public class OverloadFilter : InclusionFilter
+ {
+
+ public OverloadFilter(XmlReader overloadReader, string file)
+ : base(file)
+ {
+ name = overloadReader.GetAttribute("name");
+ string includeAttr = overloadReader.GetAttribute("include");
+ included = Convert.ToBoolean(string.IsNullOrEmpty(includeAttr) ? "true" : includeAttr);
+ overloadId = overloadReader.GetAttribute("api");
+ paramTypes = overloadReader.GetAttribute("types");
+ paramNames = overloadReader.GetAttribute("names");
+ }
+
+ private string name;
+
+ private bool included;
+ public bool Included
+ {
+ get
+ {
+ return (included);
+ }
+ }
+
+ private string paramTypes;
+ public string ParamTypes
+ {
+ get
+ {
+ return (paramTypes);
+ }
+ }
+
+ private string paramNames;
+ public string ParamNames
+ {
+ get
+ {
+ return (paramNames);
+ }
+ }
+ private string overloadId;
+ public string OverloadId
+ {
+ get
+ {
+ return (overloadId);
+ }
+ }
+
+ }
+
+} \ No newline at end of file