// 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;
}
}
}