/// <summary> /// This adds documentation model elements to a general node /// </summary> /// <param name="memberNode">The member node to update</param> private void UpdateGeneralApiNode(ApiMember memberNode) { var containers = memberNode.Node.Element("containers"); var type = containers?.Element("type"); var topicData = new XElement("topicdata", new XAttribute("group", ApiMemberGroup.Api.ToString().ToLowerInvariant()), (type != null && apiMembers[type.Attribute("api").Value].Subgroup == ApiMemberGroup.Enumeration) ? new XAttribute("notopic", String.Empty) : null); string implementedApiType = null; // For EII members, add the name of the type to which the implemented member belongs to keep them // distinct if the name appears in multiple explicitly implemented types. if (memberNode.IsExplicitlyImplemented) { implementedApiType = memberNode.ImplementedType; int pos = implementedApiType.LastIndexOf('.'); if (pos != -1) { topicData.Add(new XAttribute("eiiName", implementedApiType.Substring(pos + 1) + "." + memberNode.Name)); } else { topicData.Add(new XAttribute("eiiName", implementedApiType.Substring(2) + "." + memberNode.Name)); } } memberNode.Node.AddFirst(topicData); if (containers != null) { foreach (var l in containers.Elements("library")) { this.AddLibraryAssemblyData(l, true); } } var memberData = memberNode.Node.Element("memberdata"); if (memberData == null || memberNode.Subgroup == ApiMemberGroup.Field) { return; } // Add an overload ID to the memberdata element if needed. var typeNode = apiMembers[type.Attribute("api").Value]; string overloadId = DetermineOverloadId(typeNode, memberNode); if (typeNode.Node.Element("elements").Elements("element").Any( el => el.Attribute("api").Value == overloadId)) { memberData.Add(new XAttribute("overload", overloadId)); } }
/// <summary> /// Update an element node /// </summary> /// <param name="typeNode">The type node</param> /// <param name="member">The member for which to update the element node</param> /// <returns>The node itself if it is an <c>element</c> element or the original <c>element</c> node /// from the containing type if not.</returns> private static XElement UpdateElementNode(ApiMember typeNode, ApiMember member) { // If the node is an element, return it if (member.Node.Name == "element") { return(member.Node); } // Otherwise, look up and return the original element node in the type's element list var el = typeNode.Node.Element("elements").Elements("element").FirstOrDefault( mel => mel.Attribute("api").Value == member.MemberId); return(el); }
/// <summary> /// Add a table of contents entry for a member /// </summary> /// <param name="writer">The XML writer to which the output is written</param> /// <param name="member">The member for which to add TOC entry</param> /// <param name="declaringTypePrefix">The declaring type's prefix used to filter members</param> private void AddMember(XmlWriter writer, ApiMember member, string declaringTypePrefix) { writer.WriteStartElement("topic"); writer.WriteAttributeString("id", member.MemberId); writer.WriteAttributeString("file", member.TopicFilename); // Add child elements if any (members for an overload for example). Only include direct members of // the given type. Ignore inherited members. Sort by parameter count and then by the type name // of the first parameter. var childMembers = member.ChildElements.Select(id => apiMembers.ContainsKey(id) ? apiMembers[id] : null).Where( m => m != null && m.MemberIdWithoutPrefix.StartsWith(declaringTypePrefix, StringComparison.Ordinal)).OrderBy( m => m.ParameterCount).ThenBy(m => m.FirstParameterTypeName); foreach (var m in childMembers) { AddMember(writer, m, declaringTypePrefix); } writer.WriteEndElement(); }
/// <summary> /// Add a member list topic and its children /// </summary> /// <param name="writer">The XML writer to which the output is written</param> /// <param name="list">The member list topic to output</param> /// <param name="declaringTypePrefix">The declaring type's prefix used to filter members</param> private void AddMemberListTree(XmlWriter writer, ApiMember list, string declaringTypePrefix) { writer.WriteStartElement("topic"); writer.WriteAttributeString("id", list.MemberId); writer.WriteAttributeString("file", list.TopicFilename); // Add the child elements of the list. Only include direct members of the given type. Ignore // inherited members. Sort by name (explicit interface implementation name if present or // the member name if not. var childMembers = list.ChildElements.Select(id => apiMembers.ContainsKey(id) ? apiMembers[id] : null).Where( m => m != null && m.MemberIdWithoutPrefix.StartsWith(declaringTypePrefix, StringComparison.Ordinal)).OrderBy( m => m.TopicEiiName ?? m.Name); foreach (var m in childMembers) { AddMember(writer, m, declaringTypePrefix); } writer.WriteEndElement(); }
/// <summary> /// This is used to determine the ID of an overloads member entry /// </summary> /// <param name="typeNode">The type node to use</param> /// <param name="member">The member to use</param> /// <returns>The ID for the overloads member entry</returns> private static string DetermineOverloadId(ApiMember typeNode, ApiMember member) { string overloadId; if (member.IsExplicitlyImplemented) { string memberId = member.MemberIdWithoutParameters.Substring(2); overloadId = typeNode.MemberIdWithoutPrefix + "." + memberId.Substring(memberId.LastIndexOf('.') + 1); int pos = overloadId.LastIndexOf("``", StringComparison.Ordinal); if (pos != -1) { overloadId = overloadId.Substring(0, pos); } } else { overloadId = typeNode.MemberIdWithoutPrefix; if (member.Subgroup == ApiMemberGroup.Constructor) { overloadId += ".#ctor"; } else { if (member.SubSubgroup == ApiMemberGroup.Operator) { overloadId += ".op_" + member.Name; } else { overloadId += "." + member.Name; } } } return("Overload:" + overloadId); }
/// <summary> /// Add member list topics for a type (constructors, properties, methods, events, etc.). The members are /// added as children of each list topic. /// </summary> /// <param name="writer">The XML writer to which the output is written</param> /// <param name="apiType">The type member for which to add member list topics</param> private void AddMemberListTopics(XmlWriter writer, ApiMember apiType) { string declaringTypePrefix = apiType.MemberIdWithoutPrefix + "."; // Only include direct members of the given type. Ignore inherited members. var constructors = apiType.ChildElements.Select(id => apiMembers.ContainsKey(id) ? apiMembers[id] : null).Where( m => m != null && m.MemberIdWithoutPrefix.StartsWith(declaringTypePrefix, StringComparison.Ordinal) && m.Subgroup == ApiMemberGroup.Constructor); // Constructors don't have a containing list topic so we need to get them separately foreach (var m in constructors) { AddMember(writer, m, declaringTypePrefix); } // Add lists for properties, methods, events, etc. Some categories may not appear for a type. foreach (ApiMemberGroup listType in this.ListTopicOrder) { if (apiMembers.TryGetValue(listType.ToString() + "." + apiType.MemberId, out ApiMember list)) { AddMemberListTree(writer, list, declaringTypePrefix); } } }
/// <inheritdoc /> public void ApplyDocumentModel(string reflectionDataFile, string docModelReflectionDataFile) { // Load the information needed to apply the document model using (XmlReader reader = XmlReader.Create(reflectionDataFile, new XmlReaderSettings { IgnoreWhitespace = true })) { reader.Read(); while (!reader.EOF) { switch (reader.NodeType) { case XmlNodeType.XmlDeclaration: case XmlNodeType.EndElement: reader.Read(); break; case XmlNodeType.Element: switch (reader.Name) { case "api": var node = (XElement)XNode.ReadFrom(reader); var apiMember = new ApiMember(node) { Node = node }; apiMembers[apiMember.MemberId] = apiMember; break; case "assembly": var assembly = new ApiAssemblyProperties((XElement)XNode.ReadFrom(reader)); assemblies[assembly.AssemblyName] = assembly; break; default: reader.Read(); break; } break; default: reader.Read(); break; } } } // Clone the file and add the document model elements using (XmlReader reader = XmlReader.Create(reflectionDataFile, new XmlReaderSettings { IgnoreWhitespace = true })) using (XmlWriter writer = XmlWriter.Create(docModelReflectionDataFile, new XmlWriterSettings { Indent = true })) { writer.WriteStartDocument(); reader.Read(); while (!reader.EOF) { switch (reader.NodeType) { case XmlNodeType.XmlDeclaration: case XmlNodeType.EndElement: if (reader.Name == "apis" && !String.IsNullOrWhiteSpace(this.RootNamespaceContainerId)) { this.AddRootNamespaceContainer(writer); } reader.Read(); break; case XmlNodeType.Element: switch (reader.Name) { case "apis": case "reflection": writer.WriteStartElement(reader.Name); reader.Read(); break; case "api": string id = reader.GetAttribute("id"); // Discard the file copy and use the one we loaded above reader.ReadToNextSibling("api"); var apiMember = apiMembers[id]; if (apiMember.Group == ApiMemberGroup.Type) { this.UpdateTypeApiNode(apiMember); } else { this.UpdateGeneralApiNode(apiMember); } apiMember.Node.WriteTo(writer); if (apiMember.Group == ApiMemberGroup.Type && apiMember.Subgroup != ApiMemberGroup.Enumeration && apiMember.Node.Element("elements") != null) { this.AddMemberListApiElement(writer, ApiMemberGroup.Methods, apiMember); this.AddMemberListApiElement(writer, ApiMemberGroup.Operators, apiMember); this.AddMemberListApiElement(writer, ApiMemberGroup.Properties, apiMember); this.AddMemberListApiElement(writer, ApiMemberGroup.Events, apiMember); this.AddMemberListApiElement(writer, ApiMemberGroup.Fields, apiMember); this.AddMemberListApiElement(writer, ApiMemberGroup.AttachedProperties, apiMember); this.AddMemberListApiElement(writer, ApiMemberGroup.AttachedEvents, apiMember); this.AddOverloadListApiElements(writer, apiMember); } break; default: writer.WriteNode(reader.ReadSubtree(), true); break; } break; default: reader.Read(); break; } } writer.WriteEndDocument(); } }
/// <summary> /// Update the member list elements with overload set entries /// </summary> /// <returns>An enumerable list of <c>element</c> elements for the member list</returns> private static IEnumerable <XElement> UpdateMemberListElements(ApiMember typeNode, IEnumerable <ApiMember> memberList) { string declaredPrefix = typeNode.MemberIdWithoutPrefix + "."; // Group EII members separately from non-EII members. Extension methods are also separated. List <ApiMember> eiiMembers = new List <ApiMember>(), nonEiiMembers = new List <ApiMember>(), extensionMethods = new List <ApiMember>(); foreach (var m in memberList) { if (m.IsExplicitlyImplemented) { eiiMembers.Add(m); } else { if (m.SubSubgroup == ApiMemberGroup.Extension) { extensionMethods.Add(m); } else { nonEiiMembers.Add(m); } } } // Extension methods are never grouped even when overloaded. We just mark them with an attribute. var groupedMembers = eiiMembers.GroupBy(m => m.ImplementedType + "/" + m.Name + "/" + m.Subgroup.ToString() + "/" + m.SubSubgroup).Concat( nonEiiMembers.GroupBy(m => m.Name + "/" + m.Subgroup.ToString() + "/" + m.SubSubgroup)).Concat( extensionMethods.GroupBy(m => m.Name)).OrderBy( m => m.First().SubSubgroup == ApiMemberGroup.Extension ? 0 : 1).ThenBy( m => m.First().MemberId).ToList(); foreach (var g in groupedMembers) { var firstMember = g.First(); if (g.Count() == 1) { yield return(UpdateElementNode(typeNode, firstMember)); } else { // Extension methods just get an attribute if (firstMember.SubSubgroup == ApiMemberGroup.Extension) { foreach (var m in g.OrderBy(m => m.ParameterCount).ThenBy(m => m.FirstParameterTypeName)) { var elementNode = UpdateElementNode(typeNode, m); elementNode.Add(new XAttribute("overload", "true")); yield return(elementNode); } } else { var overloadElement = new XElement("element"); overloadElement.Add(new XAttribute("api", DetermineOverloadId(typeNode, firstMember))); foreach (var m in g.OrderBy(m => m.ParameterCount).ThenBy(m => m.FirstParameterTypeName).ThenBy( m => m.MemberIdWithoutPrefix)) { overloadElement.Add(UpdateElementNode(typeNode, m)); } yield return(overloadElement); } } } }
/// <summary> /// Add overload list topics for each overloaded member in the type /// </summary> /// <param name="writer">The XML writer to which the element is added</param> /// <param name="typeNode">The type node to update</param> private void AddOverloadListApiElements(XmlWriter writer, ApiMember typeNode) { XElement containers = null; var memberList = new List <ApiMember>(); // Get members matching the subgroup or sub-subgroup foreach (var element in typeNode.Node.Element("elements").Elements("element")) { // Members with an apidata element are extension methods if (element.Attribute("api").Value.StartsWith("Overload:", StringComparison.Ordinal)) { var ol = new ApiMember(element) { Node = element }; // Only generate an overload topic if there is at least one member in the containing type. // If all overloads are inherited, ignore it. var firstDeclaredMember = ol.Node.Elements("element").FirstOrDefault( el => el.Attribute("api").Value.Substring(2).StartsWith( typeNode.MemberIdWithoutPrefix + ".", StringComparison.Ordinal)); if (firstDeclaredMember != null) { // Use the container from the first declared member for the overloads topic if (containers == null) { var fm = apiMembers[firstDeclaredMember.Attribute("api").Value]; containers = new XElement(fm.Node.Element("containers")); foreach (var l in containers.Elements("library")) { this.AddLibraryAssemblyData(l, false); } } memberList.Add(ol); } } } if (memberList.Count == 0) { return; } // Create the member list node and add it to the file foreach (var m in memberList.OrderBy(m => m.MemberId)) { var firstEl = m.Node.Element("element"); if (!apiMembers.TryGetValue(firstEl.Attribute("api").Value, out ApiMember firstMember)) { firstMember = new ApiMember(firstEl) { Node = firstEl } } ; var apidata = firstMember.Node.Element("apidata"); var apiList = new XElement("api", new XAttribute("id", m.MemberId), new XElement("topicdata", new XAttribute("name", firstMember.Name), new XAttribute("group", ApiMemberGroup.List.ToString().ToLowerInvariant()), new XAttribute("subgroup", ApiMemberGroup.Overload.ToString().ToLowerInvariant()), new XAttribute("memberSubgroup", firstMember.Subgroup.ToString().ToLowerInvariant()), new XAttribute("pseudo", "true")), apidata, new XElement("elements", m.Node.Elements("element").Select(el => new XElement(el))), containers); apiList.WriteTo(writer); } }
/// <summary> /// Add a member list topic of the specified type for the given type node /// </summary> /// <param name="writer">The XML writer to which the element is added</param> /// <param name="listType">The list type to add</param> /// <param name="typeNode">The type node to update</param> private void AddMemberListApiElement(XmlWriter writer, ApiMemberGroup listType, ApiMember typeNode) { ApiMemberGroup subgroup = ApiMemberGroup.None, subsubgroup = ApiMemberGroup.None; switch (listType) { case ApiMemberGroup.Methods: subgroup = ApiMemberGroup.Method; break; case ApiMemberGroup.Operators: subsubgroup = ApiMemberGroup.Operator; break; case ApiMemberGroup.Properties: subgroup = ApiMemberGroup.Property; break; case ApiMemberGroup.Events: subgroup = ApiMemberGroup.Event; break; case ApiMemberGroup.Fields: subgroup = ApiMemberGroup.Field; break; case ApiMemberGroup.AttachedProperties: subsubgroup = ApiMemberGroup.AttachedProperty; break; case ApiMemberGroup.AttachedEvents: subsubgroup = ApiMemberGroup.AttachedEvent; break; default: throw new InvalidOperationException("Unexpected list type encountered"); } var memberList = new List <ApiMember>(); ApiMember listElement, compareElement; // Get members matching the subgroup or sub-subgroup foreach (var element in typeNode.Node.Element("elements").Elements("element")) { // For overloads, use the first element so that we get the proper subgroup for comparison if (element.Attribute("api").Value.StartsWith("Overload:", StringComparison.Ordinal)) { listElement = new ApiMember(element) { Node = element }; var firstEl = element.Element("element"); if (!apiMembers.TryGetValue(firstEl.Attribute("api").Value, out compareElement)) { compareElement = new ApiMember(firstEl) { Node = firstEl }; } } else { // Members with an apidata element are extension methods if (element.Element("apidata") == null) { listElement = compareElement = apiMembers[element.Attribute("api").Value]; } else { listElement = compareElement = new ApiMember(element) { Node = element } }; } if (subgroup == ApiMemberGroup.Method) { // For methods we want everything but operators if (compareElement.Subgroup == subgroup && compareElement.SubSubgroup != ApiMemberGroup.Operator) { memberList.Add(listElement); } } else { if ((subgroup == ApiMemberGroup.None && compareElement.SubSubgroup == subsubgroup) || (subgroup == compareElement.Subgroup && subsubgroup == compareElement.SubSubgroup)) { memberList.Add(listElement); } } } if (memberList.Count == 0) { return; } XElement apidata = typeNode.Node.Element("apidata"), typedata = typeNode.Node.Element("typedata"), templates = typeNode.Node.Element("templates"), containers = new XElement(typeNode.Node.Element("containers")); // Add or update the type element. A type element will already exist for nested types. In such // cases, we just replace it for the list topic. var t = containers.Element("type"); if (t != null) { t.RemoveAll(); t.Add(new XAttribute("api", typeNode.MemberId)); } else { containers.Add(new XElement("type", new XAttribute("api", typeNode.MemberId))); } // Create the member list node and add it to the file var apiList = new XElement("api", new XAttribute("id", listType.ToString() + "." + typeNode.MemberId), new XElement("topicdata", new XAttribute("name", typeNode.Name), new XAttribute("group", ApiMemberGroup.List.ToString().ToLowerInvariant()), new XAttribute("subgroup", listType), (subsubgroup != ApiMemberGroup.None) ? new XAttribute("subsubgroup", listType) : null, new XAttribute("typeTopicId", typeNode.MemberId)), apidata, typedata, templates, new XElement("elements", memberList.Select(m => UpdateElementNode(typeNode, m))), containers); apiList.WriteTo(writer); }
/// <summary> /// This adds documentation model elements to a type node /// </summary> /// <param name="typeNode">The type node to update</param> private void UpdateTypeApiNode(ApiMember typeNode) { typeNode.Node.AddFirst(new XElement("topicdata", new XAttribute("group", ApiMemberGroup.Api.ToString().ToLowerInvariant()))); var containers = typeNode.Node.Element("containers"); if (containers != null) { foreach (var l in containers.Elements("library")) { this.AddLibraryAssemblyData(l, false); } } if (typeNode.Subgroup != ApiMemberGroup.Enumeration && typeNode.Node.Element("elements") != null) { var memberList = new List <ApiMember>(); var elements = typeNode.Node.Element("elements"); foreach (var element in elements.Elements("element")) { // Members with an apidata element are extension methods if (element.Element("apidata") == null) { // Odd case: Sometimes a member makes it to the element list but doesn't appear in the // file as an entry. These will be omitted. if (apiMembers.TryGetValue(element.Attribute("api").Value, out ApiMember m)) { memberList.Add(m); } /* * // TODO: Should probably look into this but. Only happens on a couple of designer * // classes in the .NET Framework so ignoring for now. * else * System.Diagnostics.Debug.WriteLine("No API entry for " + element.Attribute("api").Value); */ } else { memberList.Add(new ApiMember(element) { Node = element }); } } // Update the element list with overloads topics var newElementList = new List <XElement>(); foreach (var el in UpdateMemberListElements(typeNode, memberList)) { newElementList.Add(el); } elements.RemoveAll(); foreach (var el in newElementList) { elements.Add(el); } } }
public static MethodEntryPoint entry(ApiMember src) => new MethodEntryPoint(src.Token, src.BaseAddress);
/// <inheritdoc /> public void GenerateApiTocFile(string reflectionDataFile, string tocFile) { ApiMember root = null, rootGroup = null; var namespaces = new List <ApiMember>(); // Load the API member information to determine how the TOC should be generated using (XmlReader reader = XmlReader.Create(reflectionDataFile, new XmlReaderSettings { IgnoreWhitespace = true })) { reader.Read(); while (!reader.EOF) { switch (reader.NodeType) { case XmlNodeType.XmlDeclaration: case XmlNodeType.EndElement: reader.Read(); break; case XmlNodeType.Element: switch (reader.Name) { case "api": var apiMember = new ApiMember((XElement)XNode.ReadFrom(reader)); #if DEBUG if (apiMember.Group == ApiMemberGroup.Unknown || apiMember.Subgroup == ApiMemberGroup.Unknown || apiMember.TopicGroup == ApiMemberGroup.Unknown || apiMember.TopicSubgroup == ApiMemberGroup.Unknown) { throw new InvalidOperationException("Unknown group value: " + apiMember.MemberId + $" - API Data: {apiMember.Group}/{apiMember.Subgroup} Topic Data: {apiMember.TopicGroup} {apiMember.TopicSubgroup}"); } #endif if (apiMember.TopicGroup == ApiMemberGroup.Root) { if (root != null) { throw new InvalidOperationException("Unexpected duplicate root container entry"); } root = apiMember; } else { if (apiMember.TopicGroup == ApiMemberGroup.RootGroup) { if (rootGroup != null) { throw new InvalidOperationException("Unexpected duplicate root group container entry"); } rootGroup = apiMember; } else { if (apiMember.Group == ApiMemberGroup.Namespace) { namespaces.Add(apiMember); } } } apiMembers[apiMember.MemberId] = apiMember; break; default: reader.Read(); break; } break; default: reader.Read(); break; } } } // Generate the TOC using (XmlWriter writer = XmlWriter.Create(tocFile, new XmlWriterSettings { Indent = true })) { writer.WriteStartDocument(); writer.WriteStartElement("topics"); if (root != null) { // "R:" root namespace container. Create a root entry and an entry for each namespace and // namespace group and their children. writer.WriteStartElement("topic"); writer.WriteAttributeString("id", root.MemberId); writer.WriteAttributeString("file", root.TopicFilename); foreach (string child in root.ChildElements) { AddNamespaceOrTypeTopic(writer, child); } writer.WriteEndElement(); } else if (rootGroup != null) { // No root namespace container but has a "G:" root group. There is no topic generated for // the root group node, just its children. foreach (string child in rootGroup.ChildElements) { AddNamespaceOrTypeTopic(writer, child); } } else { // No root namespace container and no grouped namespaces. List the namespaces at the root // level, each containing their children. foreach (var ns in namespaces.OrderBy(n => n.MemberIdWithoutPrefix)) { AddNamespaceOrTypeTopic(writer, ns.MemberId); } } writer.WriteEndDocument(); } }
public static MemoryAddress jit(ApiMember src) { sys.prepare(src.Method.MethodHandle); return(fptr(src.Method)); }