SharpDevelop Community

Get your problems solved!
Welcome to SharpDevelop Community Sign in | Join | Help
in Search

Including content from XML files in intellisense tooltips

Last post 03-25-2017 6:36 PM by sbridewell. 1 replies.
Page 1 of 1 (2 items)
Sort Posts: Previous Next
  • 03-19-2017 6:01 PM

    Including content from XML files in intellisense tooltips

    I'm not sure whether this counts as a bug or a feature request, but here goes...

    Steps to reproduce

    1) Create a project containing a class with the following content

    using System;

    namespace IntellisenseTest
    {
        /// <summary>
        /// Description of Widget.
        /// </summary>
        public class Widget
        {
            /// <summary>
            /// Gets or sets a property which is documented inline.
            /// </summary>
            public string Foo { get; set; }
           
            /// <include file="SharedDocumentation.xml"
            ///          path="doc/members/member[@name='P:IntellisenseTest.Widget.Bar']/*" />
            public string Bar { get; set; }
        }
    }

    2) Add an XML file to the project called SharedDocumentation.xml with the following content:

    <?xml version="1.0"?>
    <doc>
        <members>
            <member name="P:IntellisenseTest.Widget.Bar">
                <summary>Gets or sets a property which is documented in a separate XML file.</summary>
            </member>
        </members>
    </doc>

    3) Hover the mouse pointer over the name of the Bar property in the Widget class until a tooltip appears

    Expected result: Tooltip includes the summary text "Gets or sets a property which is documented in a separate XML file"
    Actual result: Tooltip only shows the property's signature

    If I enable the XML documentation property in the project properties and build a Sandcastle help file, it includes the content from my XML file just as if it were included inline in the code, but it seems that SharpDevelop gets the tooltip content by parsing the source code files rather than from the generated XML documentation file.

    I thought I'd have a go at implementing this functionality myself and my investigations led me to the DocumentationUIBuilder class in the ICSharpCode.SharpDevelop project. In the AddDocumentationElement(XmlDocumentationElement) method, there's a switch (element.Name) { ... } block, to which I've added the following:

                   case "include":
                        string filename = element.GetAttribute("file");
                        if (string.IsNullOrWhiteSpace(filename))
                        {
                            // file attribute not supplied or is empty, no point trying to read the file
                            break;
                        }
                       
                        // FIXME: this is resolved relative to the SD bin folder, need the project folder
                        string fullyQualifiedFilename = System.IO.Path.GetFullPath(filename);
                        if (System.IO.File.Exists(fullyQualifiedFilename) == false)
                        {
                            AddText("External documentation file '" + fullyQualifiedFilename + "' does not exist");
                            break;
                        }
                       
                        System.Xml.XPath.XPathDocument xdoc = new System.Xml.XPath.XPathDocument(fullyQualifiedFilename);
                        XPathNavigator documentXPathNavigator = xdoc.CreateNavigator();
                        XPathNavigator pathXPathNavigator = documentXPathNavigator.SelectSingleNode(element.GetAttribute("path"));
                        IEntity entity = null; // TODO: what do I set this to?
                        XmlDocumentationElement e = new XmlDocumentationElement(pathXPathNavigator.InnerXml, entity);
                        AddDocumentationElement(e);
                        break;

    I know this can be made more robust, e.g. not all the exceptions that could be thrown are handled and it'd probably be tidier to move all of this into a separate method, but for now I have two questions.

    1) The tooltip now tells me that the XML file I'm trying to reference can't be found because it's looking in the SharpDevelop bin folder rather than my project folder - how can I get the path to the project folder?
    2) In order to pass content from the XML file to the XmlDocumentationElement method, I also need to supply something which implements IEntity - what is this and how do I create it?

    Alternatively, am I going about this in completely the wrong way?

    SharpDevelop Version : 5.1.0.5134-RC-d5052dc5
    .NET Version         : 4.6.01586
    OS Version           : Microsoft Windows NT 6.3.9600.0
    Current culture      : English (United Kingdom) (en-GB)
    Running under WOW6432, processor architecture: x86-64
    Working Set Memory   : 161904kb
    GC Heap Memory       : 298135kb

    Thanks, Simon

  • 03-25-2017 6:36 PM In reply to

    Re: Including content from XML files in intellisense tooltips

    Looks like I've answered my first question. The XmlDocumentationElement class has a DeclaringEntity property of type IEntity, which has a ParentAssembly property of type IAssembly, which has a GetProject method which returns a IProject, which has a Directory property, and once I've got that it's easy to construct the full path to the XML file I'm looking for. So now my addition to the switch block consists of

                   case "include":
                        AddIncludeElement(element);
                        break;

    And I've added the following two methods

            /// <summary>
            /// Parses a documentation comment which references a separate XML file.
            /// Reads in the file specified in the file attribute (relative to the project folder),
            /// and adds the node(s) which meet the xPath expression in the path attribute
            /// to the FlowDocument as if they were inline documentation comments.
            /// The comment should take the form
            /// <code>
            /// &lt;include file="doc.xml" path="doc/members/member[@name='p:Namespace.Class.Member']/*" /&gt;
            /// </code>.
            /// </summary>
            /// <param name="element"><see cref="XmlDocumentationElement"/> representing the &lt;include&gt; element.</param>
            private void AddIncludeElement(XmlDocumentationElement element)
            {
                string fullyQualifiedFilename = GetFullFilename(element);
                if (string.IsNullOrWhiteSpace(fullyQualifiedFilename))
                {
                    return;
                }
               
                try
                {
                    XPathDocument xdoc = new XPathDocument(fullyQualifiedFilename);
                    XPathNavigator documentXPathNavigator = xdoc.CreateNavigator();
                    string xpath = element.GetAttribute("path");
                    XPathNodeIterator iterator = documentXPathNavigator.Select(xpath);
                    if (iterator.Count == 0)
                    {
                        AddText("XPath expression '" + xpath + "' returned no results from documentation file '" + fullyQualifiedFilename + "'");
                    }
                   
                    while (iterator.MoveNext())
                    {
                        ITextSource textSource = new StringTextSource(iterator.Current.OuterXml);
                        AXmlParser parser = new AXmlParser();
                        AXmlDocument doc = parser.Parse(textSource);
                        XmlDocumentationElement e = new XmlDocumentationElement(doc, null, null);
                        AddDocumentationElement(e);
                    }
                }
                catch (XmlException ex)
                {
                    AddText("Exception while parsing documentation file '" + fullyQualifiedFilename + "'! " + ex.Message);
                }
            }
           
            /// <summary>
            /// Gets the full path and filename for the file referenced in the &lt;include&gt;
            /// element, with respect to the project folder containing the file being parsed.
            /// </summary>
            /// <param name="element"><see cref="XmlDocumentationElement" /> to parse.</param>
            /// <returns>Full path to the file, or null if the full path could not be resolved.</returns>
            /// <remarks>If null is returned then a message is added to the FlowDocument indicating the reason.</remarks>
            private string GetFullFilename(XmlDocumentationElement element)
            {
                string filename = element.GetAttribute("file");
                if (string.IsNullOrWhiteSpace(filename))
                {
                    AddText("No 'file' attribute present on the 'include' documentation element.'");
                    return null;
                }
               
                IEntity declaringEntity = element.DeclaringEntity;
                if (declaringEntity == null)
                {
                    AddText("The 'include' documentation element's DeclaringEntity property returned null.");
                    return null;
                }
               
                IProject project = declaringEntity.ParentAssembly.GetProject();
                if (project == null)
                {
                    AddText("The assembly " + declaringEntity.ParentAssembly.FullAssemblyName + " was not created from a project.");
                    return null;
                }
               
                string projectFolder = project.Directory;
                string fullyQualifiedFilename = Path.Combine(projectFolder, filename);
                if (File.Exists(fullyQualifiedFilename) == false)
                {
                    AddText("External documentation file '" + fullyQualifiedFilename + "' does not exist");
                    return null;
                }
               
                return fullyQualifiedFilename;
            }

    I've sort of answered my second question too. I'm now using a different XmlDocumentationElement constructor, which this time expects a IEntity and also a Func<string, IEntity>, but it seems to work if I set both of these parameters to null. Does it matter whether I set these or not?

    Would the project team be interested in adding this to the application if I were to do a pull request? I'm kind of tempted to add XML comments to all the other members in this class too.

    Simon.

Page 1 of 1 (2 items)
Powered by Community Server (Commercial Edition), by Telligent Systems
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.