// 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.IO; using System.Xml; using System.Xml.XPath; namespace Microsoft.Ddue.Tools { public class IntellisenseComponent : BuildComponent { public IntellisenseComponent (BuildAssembler assembler, XPathNavigator configuration) : base(assembler, configuration) { XPathNavigator output_node = configuration.SelectSingleNode("output"); if (output_node != null) { string directory_value = output_node.GetAttribute("directory", String.Empty); if (!String.IsNullOrEmpty(directory_value)) { directory = Environment.ExpandEnvironmentVariables(directory_value); if (!Directory.Exists(directory)) WriteMessage(MessageLevel.Error, String.Format("The output directory '{0}' does not exist.", directory)); } } // a way to get additional information into the intellisense file XPathNodeIterator input_nodes = configuration.Select("input"); foreach (XPathNavigator input_node in input_nodes) { string file_value = input_node.GetAttribute("file", String.Empty); if (!String.IsNullOrEmpty(file_value)) { string file = Environment.ExpandEnvironmentVariables(file_value); ReadInputFile(file); } } context.AddNamespace("ddue", "http://ddue.schemas.microsoft.com/authoring/2003/5"); summaryExpression.SetContext(context); memberSummaryExpression.SetContext(context); returnsExpression.SetContext(context); parametersExpression.SetContext(context); parameterNameExpression.SetContext(context); templatesExpression.SetContext(context); templateNameExpression.SetContext(context); exceptionExpression.SetContext(context); exceptionCrefExpression.SetContext(context); } // input content store private void ReadInputFile (string file) { try { XPathDocument document = new XPathDocument(file); XPathNodeIterator member_nodes = document.CreateNavigator().Select("/metadata/topic[@id]"); foreach (XPathNavigator member_node in member_nodes) { string id = member_node.GetAttribute("id", String.Empty); content[id] = member_node.Clone(); } WriteMessage(MessageLevel.Info, String.Format("Read {0} input content nodes.", member_nodes.Count)); } catch (XmlException e) { WriteMessage(MessageLevel.Error, String.Format("The input file '{0}' is not a well-formed XML file. The error message is: {1}", file, e.Message)); } catch (IOException e) { WriteMessage(MessageLevel.Error, String.Format("An error occured while attempting to access the fileThe input file '{0}'. The error message is: {1}", file, e.Message)); } } private Dictionary content = new Dictionary(); // the action of the component public override void Apply (XmlDocument document, string id) { // only generate intellisense if id corresponds to an allowed intellisense ID if (id.Length < 2) return; if (id[1] != ':') return; if (!((id[0] == 'T') || (id[0] == 'M') || (id[0] == 'P') || (id[0] == 'F') || (id[0] == 'E') || (id[0] == 'N'))) return; XPathNavigator root = document.CreateNavigator().SelectSingleNode("/document/comments"); string assembly; if ((string)root.Evaluate(groupExpression) == "namespace") { // get the assembly for the namespace //assembly = (string) root.Evaluate(namespaceAssemblyExpression); // Assign general name for namespace assemblies since they do not belong to any specific assembly assembly = "namespaces"; } else { // get the assembly for the API assembly = (string) root.Evaluate(assemblyExpression); } if (String.IsNullOrEmpty(assembly)) return; // try/catch block for capturing errors try { // get the writer for the assembly XmlWriter writer; if (!writers.TryGetValue(assembly, out writer)) { // create a writer for the assembly string name = Path.Combine(directory, assembly + ".xml"); // Console.WriteLine("creating {0}", name); XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; try { writer = XmlWriter.Create(name, settings); } catch (IOException e) { WriteMessage(MessageLevel.Error, String.Format("An access error occured while attempting to create the intellisense output file '{0}'. The error message is: {1}", name, e.Message)); } writers.Add(assembly, writer); // write out the initial data writer.WriteStartDocument(); writer.WriteStartElement("doc"); //do not generate assembly nodes for namespace topics if ((string)root.Evaluate(groupExpression) != "namespace") { writer.WriteStartElement("assembly"); writer.WriteElementString("name", assembly); writer.WriteEndElement(); } writer.WriteStartElement("members"); } writer.WriteStartElement("member"); writer.WriteAttributeString("name", id); // summary WriteSummary(root, summaryExpression, writer); // return value XPathNavigator returns = root.SelectSingleNode(returnsExpression); if (returns != null) { writer.WriteStartElement("returns"); XmlReader reader = returns.ReadSubtree(); CopyContent(reader, writer); reader.Close(); writer.WriteEndElement(); } // parameters XPathNodeIterator parameters = root.Select(parametersExpression); foreach (XPathNavigator parameter in parameters) { string name = (string)parameter.Evaluate(parameterNameExpression); XmlReader reader = parameter.ReadSubtree(); writer.WriteStartElement("param"); writer.WriteAttributeString("name", name); CopyContent(reader, writer); writer.WriteEndElement(); reader.Close(); } // templates XPathNodeIterator templates = root.Select(templatesExpression); foreach (XPathNavigator template in templates) { string name = (string)template.Evaluate(templateNameExpression); XmlReader reader = template.ReadSubtree(); writer.WriteStartElement("typeparam"); writer.WriteAttributeString("name", name); CopyContent(reader, writer); writer.WriteEndElement(); reader.Close(); } // exceptions XPathNodeIterator exceptions = root.Select(exceptionExpression); foreach (XPathNavigator exception in exceptions) { string exceptionCref = (string)exception.Evaluate(exceptionCrefExpression); XmlReader reader = exception.ReadSubtree(); writer.WriteStartElement("exception"); writer.WriteAttributeString("cref", exceptionCref); CopyContent(reader, writer); writer.WriteEndElement(); reader.Close(); } // stored contents XPathNavigator input; if (content.TryGetValue(id, out input)) { XPathNodeIterator input_nodes = input.SelectChildren(XPathNodeType.Element); foreach (XPathNavigator input_node in input_nodes) { input_node.WriteSubtree(writer); } } writer.WriteFullEndElement(); // enumeration members string subgroup = (string)root.Evaluate(subgroupExpression); if (subgroup == "enumeration") { XPathNodeIterator elements = (XPathNodeIterator)root.Evaluate(elementsExpression); foreach (XPathNavigator element in elements) { string api = (string)element.GetAttribute("api", string.Empty); writer.WriteStartElement("member"); writer.WriteAttributeString("name", api); //summary WriteSummary(element, memberSummaryExpression, writer); writer.WriteFullEndElement(); } } } catch (IOException e) { WriteMessage(MessageLevel.Error, String.Format("An access error occured while attempting to write intellisense data. The error message is: {0}", e.Message)); } catch (XmlException e) { WriteMessage(MessageLevel.Error, String.Format("Intellisense data was not valid XML. The error message is: {0}", e.Message)); } } protected override void Dispose(bool disposing) { if (disposing) { foreach (XmlWriter writer in writers.Values) { writer.WriteEndDocument(); writer.Close(); } } base.Dispose(disposing); } private void WriteSummary(XPathNavigator node, XPathExpression expression, XmlWriter writer) { XPathNavigator summary = node.SelectSingleNode(expression); if (summary != null) { writer.WriteStartElement("summary"); XmlReader reader = summary.ReadSubtree(); CopyContent(reader, writer); reader.Close(); writer.WriteEndElement(); } else { // Console.WriteLine("no summary"); } } private void CopyContent (XmlReader reader, XmlWriter writer) { reader.MoveToContent(); while (true) { //Console.WriteLine("{0} {1}", reader.ReadState, reader.NodeType); if (reader.NodeType == XmlNodeType.Text) { writer.WriteString(reader.ReadString()); } else if (reader.NodeType == XmlNodeType.Element) { //Console.WriteLine(reader.LocalName); if (reader.LocalName == "codeEntityReference") { writer.WriteStartElement("see"); writer.WriteAttributeString("cref", reader.ReadElementString()); writer.WriteEndElement(); } else if (reader.LocalName == "parameterReference") { writer.WriteStartElement("paramref"); writer.WriteAttributeString("name", reader.ReadElementString()); writer.WriteEndElement(); } else if (reader.LocalName == "link") { string displayText = reader.ReadElementString(); if (displayText.StartsWith("GTMT#")) { writer.WriteString(displayText.Substring(displayText.IndexOf("#") + 1)); } else { writer.WriteString(displayText); } } else { reader.Read(); } } else { if (!reader.Read()) break; } } } private string directory = String.Empty; private Dictionary writers = new Dictionary(); private XPathExpression assemblyExpression = XPathExpression.Compile("string(/document/reference/containers/library/@assembly)"); private XPathExpression namespaceAssemblyExpression = XPathExpression.Compile("string(/document/reference/elements/element/containers/library/@assembly)"); private XPathExpression summaryExpression = XPathExpression.Compile("ddue:dduexml/ddue:summary"); private XPathExpression returnsExpression = XPathExpression.Compile("ddue:dduexml/ddue:returnValue"); private XPathExpression parametersExpression = XPathExpression.Compile("ddue:dduexml/ddue:parameters/ddue:parameter/ddue:content"); private XPathExpression parameterNameExpression = XPathExpression.Compile("string(../ddue:parameterReference)"); private XPathExpression templatesExpression = XPathExpression.Compile("ddue:dduexml/ddue:genericParameters/ddue:genericParameter/ddue:content"); private XPathExpression templateNameExpression = XPathExpression.Compile("string(../ddue:parameterReference)"); private XPathExpression exceptionExpression = XPathExpression.Compile("ddue:dduexml/ddue:exceptions/ddue:exception/ddue:content"); private XPathExpression exceptionCrefExpression = XPathExpression.Compile("string(../ddue:codeEntityReference)"); private XPathExpression subgroupExpression = XPathExpression.Compile("string(/document/reference/apidata/@subgroup)"); private XPathExpression groupExpression = XPathExpression.Compile("string(/document/reference/apidata/@group)"); private XPathExpression elementsExpression = XPathExpression.Compile("/document/reference/elements/element"); private XPathExpression memberSummaryExpression = XPathExpression.Compile("ddue:summary"); private XmlNamespaceManager context = new CustomContext(); } }