// Copyright © Microsoft Corporation. // This source file is subject to the Microsoft Permissive License. // See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx. // All other 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> versionFilters = new Dictionary>(); 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 platformFrameworks; if (!versionFilters.TryGetValue(platformId, out platformFrameworks)) { platformFrameworks = new Dictionary(); 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 supportedPlatforms = new List(); XmlWriter platformsWriter = referenceNode.AppendChild(); foreach (string platformId in versionFilters.Keys) { Dictionary 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 sourceFiles = new List(); 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 namespaceFilters = new Dictionary(); public string VersionId { get { return (versionId); } } public Dictionary NamespaceFilters { get { return (namespaceFilters); } } /// /// 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. /// /// The type's reflection data. /// 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 typeFilters = new Dictionary(); public Dictionary 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 memberFilters = new Dictionary(); public Dictionary 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(paramTypes) && paramTypes == overloadFilter.ParamTypes) 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 overloadFilters = new List(); 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 (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); } } } }