// Copyright (c) 2009-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using ICSharpCode.NRefactory.Documentation; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.Xml { /// /// Represents an element in the XML documentation. /// Any occurrences of "<inheritdoc/>" are replaced with the inherited documentation. /// public class XmlDocumentationElement { /// /// Gets the XML documentation element for the specified entity. /// Returns null if no documentation is found. /// public static XmlDocumentationElement Get(IEntity entity, bool inheritDocIfMissing = true) { if (entity == null) return null; var documentationComment = entity.Documentation; if (documentationComment != null) { return Create(documentationComment, entity); } IMember member = entity as IMember; if (inheritDocIfMissing && member != null) { if (member.SymbolKind == SymbolKind.Constructor) { // For constructors, the documentation of the base class ctor // isn't really suitable as constructors are not inherited. // We'll use the type's documentation instead: return Get(entity.DeclaringTypeDefinition, inheritDocIfMissing); } foreach (IMember baseMember in InheritanceHelper.GetBaseMembers(member, includeImplementedInterfaces: true)) { documentationComment = baseMember.Documentation; if (documentationComment != null) return Create(documentationComment, baseMember); } } return null; } static XmlDocumentationElement Create(DocumentationComment documentationComment, IEntity declaringEntity) { var doc = new AXmlParser().Parse(documentationComment.Xml); return new XmlDocumentationElement(doc, declaringEntity, documentationComment.ResolveCref); } readonly AXmlObject xmlObject; readonly AXmlElement element; readonly IEntity declaringEntity; readonly Func crefResolver; volatile string textContent; /// /// Inheritance level; used to prevent cyclic doc inheritance. /// int nestingLevel; /// /// Creates a new documentation element. /// public XmlDocumentationElement(AXmlElement element, IEntity declaringEntity, Func crefResolver) { if (element == null) throw new ArgumentNullException("element"); this.element = element; this.xmlObject = element; this.declaringEntity = declaringEntity; this.crefResolver = crefResolver; } /// /// Creates a new documentation element. /// public XmlDocumentationElement(AXmlDocument document, IEntity declaringEntity, Func crefResolver) { if (document == null) throw new ArgumentNullException("document"); this.xmlObject = document; this.declaringEntity = declaringEntity; this.crefResolver = crefResolver; } /// /// Creates a new documentation element. /// public XmlDocumentationElement(string text, IEntity declaringEntity) { if (text == null) throw new ArgumentNullException("text"); this.declaringEntity = declaringEntity; this.textContent = text; } /// /// Gets the entity on which this documentation was originally declared. /// May return null. /// public IEntity DeclaringEntity { get { return declaringEntity; } } IEntity referencedEntity; volatile bool referencedEntityInitialized; /// /// Gets the entity referenced by the 'cref' attribute. /// May return null. /// public IEntity ReferencedEntity { get { if (!referencedEntityInitialized) { string cref = GetAttribute("cref"); if (cref != null && crefResolver != null) referencedEntity = crefResolver(cref); referencedEntityInitialized = true; } return referencedEntity; } } /// /// Gets the element name. /// public string Name { get { return element != null ? element.Name : string.Empty; } } /// /// Gets the attribute value. /// public string GetAttribute(string name) { return element != null ? element.GetAttributeValue(name) : string.Empty; } /// /// Gets whether this is a pure text node. /// public bool IsTextNode { get { return xmlObject == null; } } /// /// Gets the text content. /// public string TextContent { get { if (textContent == null) { StringBuilder b = new StringBuilder(); foreach (var child in this.Children) b.Append(child.TextContent); textContent = b.ToString(); } return textContent; } } IList children; /// /// Gets the child elements. /// public IList Children { get { if (xmlObject == null) return EmptyList.Instance; return LazyInitializer.EnsureInitialized( ref this.children, () => CreateElements(xmlObject.Children, declaringEntity, crefResolver, nestingLevel)); } } static readonly string[] doNotInheritIfAlreadyPresent = { "example", "exclude", "filterpriority", "preliminary", "summary", "remarks", "returns", "threadsafety", "value" }; static List CreateElements(IEnumerable childObjects, IEntity declaringEntity, Func crefResolver, int nestingLevel) { List list = new List(); foreach (var child in childObjects) { var childText = child as AXmlText; var childTag = child as AXmlTag; var childElement = child as AXmlElement; if (childText != null) { list.Add(new XmlDocumentationElement(childText.Value, declaringEntity)); } else if (childTag != null && childTag.IsCData) { foreach (var text in childTag.Children.OfType()) list.Add(new XmlDocumentationElement(text.Value, declaringEntity)); } else if (childElement != null) { if (nestingLevel < 5 && childElement.Name == "inheritdoc") { string cref = childElement.GetAttributeValue("cref"); IEntity inheritedFrom = null; DocumentationComment inheritedDocumentation = null; if (cref != null) { inheritedFrom = crefResolver(cref); if (inheritedFrom != null) inheritedDocumentation = inheritedFrom.Documentation; } else { foreach (IMember baseMember in InheritanceHelper.GetBaseMembers((IMember)declaringEntity, includeImplementedInterfaces: true)) { inheritedDocumentation = baseMember.Documentation; if (inheritedDocumentation != null) { inheritedFrom = baseMember; break; } } } if (inheritedDocumentation != null) { var doc = new AXmlParser().Parse(inheritedDocumentation.Xml); // XPath filter not yet implemented if (childElement.Parent is AXmlDocument && childElement.GetAttributeValue("select") == null) { // Inheriting documentation at the root level List doNotInherit = new List(); doNotInherit.Add("overloads"); doNotInherit.AddRange(childObjects.OfType().Select(e => e.Name).Intersect( doNotInheritIfAlreadyPresent)); var inheritedChildren = doc.Children.Where( inheritedObject => { AXmlElement inheritedElement = inheritedObject as AXmlElement; return !(inheritedElement != null && doNotInherit.Contains(inheritedElement.Name)); }); list.AddRange(CreateElements(inheritedChildren, inheritedFrom, inheritedDocumentation.ResolveCref, nestingLevel + 1)); } } } else { list.Add(new XmlDocumentationElement(childElement, declaringEntity, crefResolver) { nestingLevel = nestingLevel }); } } } if (list.Count > 0 && list[0].IsTextNode) { if (string.IsNullOrWhiteSpace(list[0].textContent)) list.RemoveAt(0); else list[0].textContent = list[0].textContent.TrimStart(); } if (list.Count > 0 && list[list.Count - 1].IsTextNode) { if (string.IsNullOrWhiteSpace(list[list.Count - 1].textContent)) list.RemoveAt(list.Count - 1); else list[list.Count - 1].textContent = list[list.Count - 1].textContent.TrimEnd(); } return list; } /// public override string ToString() { if (element != null) return "<" + element.Name + ">"; else return this.TextContent; } } }