Browse Source

Port rich text tooltips from SharpDevelop to ILSpy.

pull/1654/head
Siegfried Pammer 6 years ago
parent
commit
7539a429c6
  1. 285
      ICSharpCode.Decompiler/Documentation/DocumentationElement.cs
  2. 27
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  3. 116
      ICSharpCode.Decompiler/Xml/AXmlAttribute.cs
  4. 55
      ICSharpCode.Decompiler/Xml/AXmlDocument.cs
  5. 243
      ICSharpCode.Decompiler/Xml/AXmlElement.cs
  6. 245
      ICSharpCode.Decompiler/Xml/AXmlObject.cs
  7. 142
      ICSharpCode.Decompiler/Xml/AXmlParser.cs
  8. 477
      ICSharpCode.Decompiler/Xml/AXmlReader.cs
  9. 96
      ICSharpCode.Decompiler/Xml/AXmlTag.cs
  10. 63
      ICSharpCode.Decompiler/Xml/AXmlText.cs
  11. 62
      ICSharpCode.Decompiler/Xml/AXmlVisitor.cs
  12. 43
      ICSharpCode.Decompiler/Xml/AnchorMovementType.cs
  13. 254
      ICSharpCode.Decompiler/Xml/DocumentationElement.cs
  14. 69
      ICSharpCode.Decompiler/Xml/ISegment.cs
  15. 218
      ICSharpCode.Decompiler/Xml/ITextSource.cs
  16. 114
      ICSharpCode.Decompiler/Xml/IncrementalParserState.cs
  17. 175
      ICSharpCode.Decompiler/Xml/InternalDocument.cs
  18. 88
      ICSharpCode.Decompiler/Xml/Log.cs
  19. 109
      ICSharpCode.Decompiler/Xml/ObjectIterator.cs
  20. 52
      ICSharpCode.Decompiler/Xml/ReuseEqualityComparer.cs
  21. 160
      ICSharpCode.Decompiler/Xml/StringTextSource.cs
  22. 72
      ICSharpCode.Decompiler/Xml/SyntaxError.cs
  23. 351
      ICSharpCode.Decompiler/Xml/TagMatchingHeuristics.cs
  24. 834
      ICSharpCode.Decompiler/Xml/TagReader.cs
  25. 100
      ICSharpCode.Decompiler/Xml/TextChangeEventArgs.cs
  26. 223
      ICSharpCode.Decompiler/Xml/TextLocation.cs
  27. 47
      ICSharpCode.Decompiler/Xml/TextType.cs
  28. 349
      ICSharpCode.Decompiler/Xml/TokenReader.cs
  29. 47
      ICSharpCode.Decompiler/Xml/XmlSegment.cs
  30. 40
      ILSpy/ExtensionMethods.cs
  31. 2
      ILSpy/ILSpy.csproj
  32. 5
      ILSpy/Languages/Language.cs
  33. 238
      ILSpy/TextView/DecompilerTextView.cs
  34. 2
      ILSpy/TextView/DecompilerTextView.xaml
  35. 530
      ILSpy/TextView/DocumentationUIBuilder.cs
  36. 448
      ILSpy/TextView/ReadOnlyDocument.cs
  37. 99
      ILSpy/TextView/XmlDocFormatter.cs
  38. 552
      ILSpy/TextView/XmlDocRenderer.cs

285
ICSharpCode.Decompiler/Documentation/DocumentationElement.cs

@ -0,0 +1,285 @@ @@ -0,0 +1,285 @@
// 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.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.Decompiler.Xml;
namespace ICSharpCode.Decompiler.Documentation
{
/// <summary>
/// Represents an element in the XML documentation.
/// Any occurrences of "&lt;inheritdoc/>" are replaced with the inherited documentation.
/// </summary>
public class XmlDocumentationElement
{
/// <summary>
/// Gets the XML documentation element for the specified entity.
/// Returns null if no documentation is found.
/// </summary>
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<string, IEntity> crefResolver;
volatile string textContent;
/// <summary>
/// Inheritance level; used to prevent cyclic doc inheritance.
/// </summary>
int nestingLevel;
/// <summary>
/// Creates a new documentation element.
/// </summary>
public XmlDocumentationElement(AXmlElement element, IEntity declaringEntity, Func<string, IEntity> crefResolver)
{
if (element == null)
throw new ArgumentNullException("element");
this.element = element;
this.xmlObject = element;
this.declaringEntity = declaringEntity;
this.crefResolver = crefResolver;
}
/// <summary>
/// Creates a new documentation element.
/// </summary>
public XmlDocumentationElement(AXmlDocument document, IEntity declaringEntity, Func<string, IEntity> crefResolver)
{
if (document == null)
throw new ArgumentNullException("document");
this.xmlObject = document;
this.declaringEntity = declaringEntity;
this.crefResolver = crefResolver;
}
/// <summary>
/// Creates a new documentation element.
/// </summary>
public XmlDocumentationElement(string text, IEntity declaringEntity)
{
if (text == null)
throw new ArgumentNullException("text");
this.declaringEntity = declaringEntity;
this.textContent = text;
}
/// <summary>
/// Gets the entity on which this documentation was originally declared.
/// May return null.
/// </summary>
public IEntity DeclaringEntity {
get { return declaringEntity; }
}
IEntity referencedEntity;
volatile bool referencedEntityInitialized;
/// <summary>
/// Gets the entity referenced by the 'cref' attribute.
/// May return null.
/// </summary>
public IEntity ReferencedEntity {
get {
if (!referencedEntityInitialized) {
string cref = GetAttribute("cref");
if (cref != null && crefResolver != null)
referencedEntity = crefResolver(cref);
referencedEntityInitialized = true;
}
return referencedEntity;
}
}
/// <summary>
/// Gets the element name.
/// </summary>
public string Name {
get {
return element != null ? element.Name : string.Empty;
}
}
/// <summary>
/// Gets the attribute value.
/// </summary>
public string GetAttribute(string name)
{
return element != null ? element.GetAttributeValue(name) : string.Empty;
}
/// <summary>
/// Gets whether this is a pure text node.
/// </summary>
public bool IsTextNode {
get { return xmlObject == null; }
}
/// <summary>
/// Gets the text content.
/// </summary>
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<XmlDocumentationElement> children;
/// <summary>
/// Gets the child elements.
/// </summary>
public IList<XmlDocumentationElement> Children {
get {
if (xmlObject == null)
return EmptyList<XmlDocumentationElement>.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<XmlDocumentationElement> CreateElements(IEnumerable<AXmlObject> childObjects, IEntity declaringEntity, Func<string, IEntity> crefResolver, int nestingLevel)
{
List<XmlDocumentationElement> list = new List<XmlDocumentationElement>();
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<AXmlText>())
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<string> doNotInherit = new List<string>();
doNotInherit.Add("overloads");
doNotInherit.AddRange(childObjects.OfType<AXmlElement>().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;
}
/// <inheritdoc/>
public override string ToString()
{
if (element != null)
return "<" + element.Name + ">";
else
return this.TextContent;
}
}
}

27
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -579,6 +579,33 @@ @@ -579,6 +579,33 @@
<Compile Include="Util\ReferenceComparer.cs" />
<Compile Include="Util\TreeTraversal.cs" />
<Compile Include="Util\UnionFind.cs" />
<Compile Include="Xml\AnchorMovementType.cs" />
<Compile Include="Xml\AXmlAttribute.cs" />
<Compile Include="Xml\AXmlDocument.cs" />
<Compile Include="Xml\AXmlElement.cs" />
<Compile Include="Xml\AXmlObject.cs" />
<Compile Include="Xml\AXmlParser.cs" />
<Compile Include="Xml\AXmlReader.cs" />
<Compile Include="Xml\AXmlTag.cs" />
<Compile Include="Xml\AXmlText.cs" />
<Compile Include="Xml\AXmlVisitor.cs" />
<Compile Include="Xml\DocumentationElement.cs" />
<Compile Include="Xml\IncrementalParserState.cs" />
<Compile Include="Xml\InternalDocument.cs" />
<Compile Include="Xml\ISegment.cs" />
<Compile Include="Xml\ITextSource.cs" />
<Compile Include="Xml\Log.cs" />
<Compile Include="Xml\ObjectIterator.cs" />
<Compile Include="Xml\ReuseEqualityComparer.cs" />
<Compile Include="Xml\StringTextSource.cs" />
<Compile Include="Xml\SyntaxError.cs" />
<Compile Include="Xml\TagMatchingHeuristics.cs" />
<Compile Include="Xml\TagReader.cs" />
<Compile Include="Xml\TextChangeEventArgs.cs" />
<Compile Include="Xml\TextLocation.cs" />
<Compile Include="Xml\TextType.cs" />
<Compile Include="Xml\TokenReader.cs" />
<Compile Include="Xml\XmlSegment.cs" />
<None Include="ICSharpCode.Decompiler.nuspec" DependentUpon="ICSharpCode.Decompiler.nuspec.template" />
<None Include="ICSharpCode.Decompiler.nuspec.template" />
<None Include="ICSharpCode.Decompiler.ruleset" />

116
ICSharpCode.Decompiler/Xml/AXmlAttribute.cs

@ -0,0 +1,116 @@ @@ -0,0 +1,116 @@
// 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.Globalization;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Name-value pair in a tag
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
public class AXmlAttribute : AXmlObject
{
internal AXmlAttribute(AXmlObject parent, int startOffset, InternalAttribute internalObject)
: base(parent, startOffset, internalObject)
{
}
internal InternalAttribute InternalAttribute {
get { return (InternalAttribute)internalObject; }
}
/// <summary> Name with namespace prefix - exactly as in source file </summary>
public string Name { get { return InternalAttribute.Name; } }
/// <summary> Unquoted and dereferenced value of the attribute </summary>
public string Value { get { return InternalAttribute.Value; } }
/// <summary>Gets the segment for the attribute name</summary>
public ISegment NameSegment {
get { return new XmlSegment(startOffset, startOffset + Name.Length); }
}
/// <summary>Gets the segment for the attribute value, including the quotes</summary>
public ISegment ValueSegment {
get { return new XmlSegment(startOffset + Name.Length + InternalAttribute.EqualsSignLength, this.EndOffset); }
}
/// <summary> The element containing this attribute </summary>
/// <returns> Null if orphaned </returns>
public AXmlElement ParentElement {
get {
AXmlTag tag = this.Parent as AXmlTag;
if (tag != null) {
return tag.Parent as AXmlElement;
}
return null;
}
}
/// <summary> The part of name before ":"</summary>
/// <returns> Empty string if not found </returns>
public string Prefix {
get {
return GetNamespacePrefix(this.Name);
}
}
/// <summary> The part of name after ":" </summary>
/// <returns> Whole name if ":" not found </returns>
public string LocalName {
get {
return GetLocalName(this.Name);
}
}
/// <summary>
/// Resolved namespace of the name. Empty string if not found
/// From the specification: "The namespace name for an unprefixed attribute name always has no value."
/// </summary>
public string Namespace {
get {
if (string.IsNullOrEmpty(this.Prefix)) return NoNamespace;
AXmlElement elem = this.ParentElement;
if (elem != null) {
return elem.LookupNamespace(this.Prefix) ?? NoNamespace;
}
return NoNamespace; // Orphaned attribute
}
}
/// <summary> Attribute is declaring namespace ("xmlns" or "xmlns:*") </summary>
public bool IsNamespaceDeclaration {
get {
return this.Name == "xmlns" || this.Prefix == "xmlns";
}
}
/// <inheritdoc/>
public override void AcceptVisitor(AXmlVisitor visitor)
{
visitor.VisitAttribute(this);
}
/// <inheritdoc/>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}={2}']", base.ToString(), this.Name, this.Value);
}
}
}

55
ICSharpCode.Decompiler/Xml/AXmlDocument.cs

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
// 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.Globalization;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// The root object of the XML document
/// </summary>
public class AXmlDocument : AXmlObject
{
internal AXmlDocument(AXmlObject parent, int startOffset, InternalDocument internalObject)
: base(parent, startOffset, internalObject)
{
}
internal override ObjectIterator CreateIteratorForReader()
{
return new ObjectIterator(internalObject.NestedObjects, startOffset);
}
/// <inheritdoc/>
public override void AcceptVisitor(AXmlVisitor visitor)
{
visitor.VisitDocument(this);
}
/// <inheritdoc/>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[{0} Chld:{1}]", base.ToString(), this.Children.Count);
}
/// <summary>
/// Represents an empty document.
/// </summary>
public readonly static AXmlDocument Empty = new AXmlDocument(null, 0, new InternalDocument());
}
}

243
ICSharpCode.Decompiler/Xml/AXmlElement.cs

@ -0,0 +1,243 @@ @@ -0,0 +1,243 @@
// 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.Globalization;
using System.Linq;
using System.Xml;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// XML element.
/// </summary>
public class AXmlElement : AXmlObject, IXmlNamespaceResolver
{
internal AXmlElement(AXmlObject parent, int startOffset, InternalElement internalObject)
: base(parent, startOffset, internalObject)
{
Log.Assert(internalObject.NestedObjects[0] is InternalTag, "First child of element must be start tag");
}
/// <summary> No tags are missing anywhere within this element (recursive) </summary>
public bool IsProperlyNested {
get { return ((InternalElement)internalObject).IsPropertyNested; }
}
/// <summary>The start or empty-element tag for this element.</summary>
public AXmlTag StartTag {
get { return (AXmlTag)this.Children[0]; }
}
/// <summary>Name with namespace prefix - exactly as in source</summary>
public string Name {
get { return ((InternalTag)internalObject.NestedObjects[0]).Name; }
}
/// <summary>Gets whether an end tag exists for this node.</summary>
public bool HasEndTag {
get { return ((InternalElement)internalObject).HasEndTag; }
}
/// <summary> The end tag, if there is any. Returns null for empty elements "&lt;Element/>" and missing end tags in malformed XML.</summary>
public AXmlTag EndTag {
get {
if (HasEndTag)
return (AXmlTag)this.Children[this.Children.Count - 1];
else
return null;
}
}
/// <summary>
/// Gets the attributes.
/// </summary>
public IEnumerable<AXmlAttribute> Attributes {
get {
return ((AXmlTag)this.Children[0]).Children.OfType<AXmlAttribute>();
}
}
/// <summary>
/// Gets the content (all children except for the start and end tags)
/// </summary>
public IEnumerable<AXmlObject> Content {
get {
int end = this.Children.Count;
if (HasEndTag)
end--;
for (int i = 1; i < end; i++) {
yield return this.Children[i];
}
}
}
/// <summary> The part of name before ":" </summary>
/// <returns> Empty string if not found </returns>
public string Prefix {
get { return ((InternalElement)internalObject).Prefix; }
}
/// <summary> The part of name after ":" </summary>
/// <returns> Empty string if not found </returns>
public string LocalName {
get { return ((InternalElement)internalObject).LocalName; }
}
/// <summary> Resolved namespace of the name </summary>
/// <returns> Empty string if prefix is not found </returns>
public string Namespace {
get {
string prefix = this.Prefix;
return LookupNamespace(prefix);
}
}
/// <summary> Find the default namespace for this context </summary>
[Obsolete("Use LookupNamespace(string.Empty) instead")]
public string FindDefaultNamespace()
{
return LookupNamespace(string.Empty) ?? NoNamespace;
}
/// <summary>
/// Recursively resolve given prefix in this context. Prefix must have some value.
/// </summary>
/// <returns> Empty string if prefix is not found </returns>
[Obsolete("Use LookupNamespace() instead")]
public string ResolvePrefix(string prefix)
{
return LookupNamespace(prefix) ?? NoNamespace;
}
/// <summary>
/// Recursively resolve given prefix in this context.
/// </summary>
/// <returns><c>null</c> if prefix is not found</returns>
public string LookupNamespace(string prefix)
{
if (prefix == null)
throw new ArgumentNullException("prefix");
// Implicit namespaces
if (prefix == "xml") return XmlNamespace;
if (prefix == "xmlns") return XmlnsNamespace;
string lookFor = (prefix.Length > 0 ? "xmlns:" + prefix : "xmlns");
for (AXmlElement current = this; current != null; current = current.Parent as AXmlElement) {
foreach (var attr in current.Attributes) {
if (attr.Name == lookFor)
return attr.Value;
}
}
return null; // Can not find prefix
}
/// <summary>
/// Gets the prefix that is mapped to the specified namespace URI.
/// </summary>
/// <returns>The prefix that is mapped to the namespace URI; null if the namespace URI is not mapped to a prefix.</returns>
public string LookupPrefix(string namespaceName)
{
if (namespaceName == null)
throw new ArgumentNullException("namespaceName");
if (namespaceName == XmlNamespace)
return "xml";
if (namespaceName == XmlnsNamespace)
return "xmlns";
for (AXmlElement current = this; current != null; current = current.Parent as AXmlElement) {
foreach (var attr in current.Attributes) {
if (attr.Value == namespaceName) {
if (attr.Name.StartsWith("xmlns:", StringComparison.Ordinal))
return attr.LocalName;
else if (attr.Name == "xmlns")
return string.Empty;
}
}
}
return null; // Can not find prefix
}
/// <summary>
/// Gets a collection of defined prefix-namespace mappings that are currently in scope.
/// </summary>
public IDictionary<string, string> GetNamespacesInScope(XmlNamespaceScope scope)
{
var result = new Dictionary<string, string>();
if (scope == XmlNamespaceScope.All) {
result["xml"] = XmlNamespace;
//result["xmlns"] = XmlnsNamespace; xmlns should not be included in GetNamespacesInScope() results
}
for (AXmlElement current = this; current != null; current = current.Parent as AXmlElement) {
foreach (var attr in current.Attributes) {
if (attr.Name.StartsWith("xmlns:", StringComparison.Ordinal)) {
string prefix = attr.LocalName;
if (!result.ContainsKey(prefix)) {
result.Add(prefix, attr.Value);
}
} else if (attr.Name == "xmlns" && !result.ContainsKey(string.Empty)) {
result.Add(string.Empty, attr.Value);
}
}
if (scope == XmlNamespaceScope.Local)
break;
}
return result;
}
/// <summary>
/// Get unquoted value of attribute.
/// It looks in the no namespace (empty string).
/// </summary>
/// <returns>Null if not found</returns>
public string GetAttributeValue(string localName)
{
return GetAttributeValue(string.Empty, localName);
}
/// <summary>
/// Get unquoted value of attribute
/// </summary>
/// <param name="namespace">Namespace. Can be no namepace (empty string), which is the default for attributes.</param>
/// <param name="localName">Local name - text after ":"</param>
/// <returns>Null if not found</returns>
public string GetAttributeValue(string @namespace, string localName)
{
@namespace = @namespace ?? string.Empty;
foreach (AXmlAttribute attr in this.Attributes) {
if (attr.LocalName == localName && attr.Namespace == @namespace)
return attr.Value;
}
return null;
}
/// <inheritdoc/>
public override void AcceptVisitor(AXmlVisitor visitor)
{
visitor.VisitElement(this);
}
/// <inheritdoc/>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}' Attr:{2} Chld:{3} Nest:{4}]", base.ToString(), this.Name, this.StartTag.Children.Count, this.Children.Count, this.IsProperlyNested ? "Ok" : "Bad");
}
}
}

245
ICSharpCode.Decompiler/Xml/AXmlObject.cs

@ -0,0 +1,245 @@ @@ -0,0 +1,245 @@
// 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.Xml;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// XML object. Base class for all nodes in the XML document.
/// </summary>
public abstract class AXmlObject : ISegment
{
/// <summary> Empty string. The namespace used if there is no "xmlns" specified </summary>
internal static readonly string NoNamespace = string.Empty;
/// <summary> Namespace for "xml:" prefix: "http://www.w3.org/XML/1998/namespace" </summary>
public static readonly string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
/// <summary> Namesapce for "xmlns:" prefix: "http://www.w3.org/2000/xmlns/" </summary>
public static readonly string XmlnsNamespace = "http://www.w3.org/2000/xmlns/";
readonly AXmlObject parent;
internal readonly int startOffset;
internal readonly InternalObject internalObject;
IList<AXmlObject> children;
internal AXmlObject(AXmlObject parent, int startOffset, InternalObject internalObject)
{
this.parent = parent;
this.startOffset = startOffset;
this.internalObject = internalObject;
}
/// <summary>
/// Creates an XML reader that reads from this document.
/// </summary>
/// <remarks>
/// The reader will ignore comments and processing instructions; and will not have line information.
/// </remarks>
public XmlReader CreateReader()
{
return new AXmlReader(CreateIteratorForReader());
}
/// <summary>
/// Creates an XML reader that reads from this document.
/// </summary>
/// <param name="settings">Reader settings.
/// Currently, only <c>IgnoreComments</c> is supported.</param>
/// <remarks>
/// The reader will not have line information.
/// </remarks>
public XmlReader CreateReader(XmlReaderSettings settings)
{
return new AXmlReader(CreateIteratorForReader(), settings);
}
/// <summary>
/// Creates an XML reader that reads from this document.
/// </summary>
/// <param name="settings">Reader settings.
/// Currently, only <c>IgnoreComments</c> is supported.</param>
/// <param name="document">
/// The document that was used to parse the XML. It is used to convert offsets to line information.
/// </param>
//public XmlReader CreateReader(XmlReaderSettings settings, IDocument document)
//{
// if (document == null)
// throw new ArgumentNullException("document");
// return new AXmlReader(CreateIteratorForReader(), settings, document.GetLocation);
//}
/// <summary>
/// Creates an XML reader that reads from this document.
/// </summary>
/// <param name="settings">Reader settings.
/// Currently, only <c>IgnoreComments</c> is supported.</param>
/// <param name="offsetToTextLocation">
/// A function for converting offsets to line information.
/// </param>
public XmlReader CreateReader(XmlReaderSettings settings, Func<int, TextLocation> offsetToTextLocation)
{
return new AXmlReader(CreateIteratorForReader(), settings, offsetToTextLocation);
}
internal virtual ObjectIterator CreateIteratorForReader()
{
return new ObjectIterator(new[] { internalObject }, startOffset);
}
/// <summary>
/// Gets the parent node.
/// </summary>
public AXmlObject Parent {
get { return parent; }
}
/// <summary>
/// Gets the list of child objects.
/// </summary>
public IList<AXmlObject> Children {
get {
var result = LazyInit.VolatileRead(ref this.children);
if (result != null) {
return result;
} else {
if (internalObject.NestedObjects != null) {
var array = new AXmlObject[internalObject.NestedObjects.Length];
for (int i = 0; i < array.Length; i++) {
array[i] = internalObject.NestedObjects[i].CreatePublicObject(this, startOffset);
}
result = Array.AsReadOnly(array);
} else {
result = EmptyList<AXmlObject>.Instance;
}
return LazyInit.GetOrSet(ref this.children, result);
}
}
}
/// <summary>
/// Gets a child fully containg the given offset.
/// Goes recursively down the tree.
/// Special case if at the end of attribute or text
/// </summary>
public AXmlObject GetChildAtOffset(int offset)
{
foreach(AXmlObject child in this.Children) {
if (offset == child.EndOffset && (child is AXmlAttribute || child is AXmlText))
return child;
if (child.StartOffset < offset && offset < child.EndOffset) {
return child.GetChildAtOffset(offset);
}
}
return this; // No children at offset
}
/// <summary>
/// The error that occured in the context of this node (excluding nested nodes)
/// </summary>
public IEnumerable<SyntaxError> MySyntaxErrors {
get {
if (internalObject.SyntaxErrors != null) {
return internalObject.SyntaxErrors.Select(e => new SyntaxError(startOffset + e.RelativeStart, startOffset + e.RelativeEnd, e.Description));
} else {
return EmptyList<SyntaxError>.Instance;
}
}
}
/// <summary>
/// The error that occured in the context of this node and all nested nodes.
/// It has O(n) cost.
/// </summary>
public IEnumerable<SyntaxError> SyntaxErrors {
get {
return TreeTraversal.PreOrder(this, n => n.Children).SelectMany(obj => obj.MySyntaxErrors);
}
}
/// <summary> Get all ancestors of this node </summary>
public IEnumerable<AXmlObject> Ancestors {
get {
AXmlObject curr = this.Parent;
while(curr != null) {
yield return curr;
curr = curr.Parent;
}
}
}
#region Helper methods
/// <summary> The part of name before ":" </summary>
/// <returns> Empty string if not found </returns>
internal static string GetNamespacePrefix(string name)
{
if (string.IsNullOrEmpty(name)) return string.Empty;
int colonIndex = name.IndexOf(':');
if (colonIndex != -1) {
return name.Substring(0, colonIndex);
} else {
return string.Empty;
}
}
/// <summary> The part of name after ":" </summary>
/// <returns> Whole name if ":" not found </returns>
internal static string GetLocalName(string name)
{
if (string.IsNullOrEmpty(name)) return string.Empty;
int colonIndex = name.IndexOf(':');
if (colonIndex != -1) {
return name.Remove(0, colonIndex + 1);
} else {
return name ?? string.Empty;
}
}
#endregion
/// <summary> Call appropriate visit method on the given visitor </summary>
public abstract void AcceptVisitor(AXmlVisitor visitor);
/// <summary>
/// Gets the start offset of the segment.
/// </summary>
public int StartOffset {
get { return startOffset; }
}
int ISegment.Offset {
get { return startOffset; }
}
/// <inheritdoc/>
public int Length {
get { return internalObject.Length; }
}
/// <inheritdoc/>
public int EndOffset {
get { return startOffset + internalObject.Length; }
}
}
}

142
ICSharpCode.Decompiler/Xml/AXmlParser.cs

@ -0,0 +1,142 @@ @@ -0,0 +1,142 @@
// 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.Threading;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// XML parser that is error tolerant.
/// </summary>
public class AXmlParser
{
/// <summary>
/// Generate syntax error when seeing entity reference other then the built-in ones
/// </summary>
public bool UnknownEntityReferenceIsError { get; set; }
IList<AXmlObject> CreatePublic(IList<InternalObject> internalObjects)
{
var publicObjects = new AXmlObject[internalObjects.Count];
int pos = 0;
for (int i = 0; i < internalObjects.Count; i++) {
publicObjects[i] = internalObjects[i].CreatePublicObject(null, pos);
pos += internalObjects[i].Length;
}
return Array.AsReadOnly(publicObjects);
}
/// <summary>
/// Parses a document into a flat list of tags.
/// </summary>
/// <returns>Parsed tag soup.</returns>
public IList<AXmlObject> ParseTagSoup(ITextSource textSource,
CancellationToken cancellationToken = default(CancellationToken))
{
if (textSource == null)
throw new ArgumentNullException("textSource");
var reader = new TagReader(this, textSource, false);
var internalObjects = reader.ReadAllObjects(cancellationToken);
return CreatePublic(internalObjects);
}
/// <summary>
/// Parses a document incrementally into a flat list of tags.
/// </summary>
/// <param name="oldParserState">The parser state from a previous call to ParseIncremental(). Use null for the first call.</param>
/// <param name="newTextSource">The text source for the new document version.</param>
/// <param name="newParserState">Out: the new parser state, pass this to the next ParseIncremental() call.</param>
/// <param name="cancellationToken">Optional: cancellation token.</param>
/// <returns>Parsed tag soup.</returns>
public IList<AXmlObject> ParseTagSoupIncremental(
IncrementalParserState oldParserState, ITextSource newTextSource, out IncrementalParserState newParserState,
CancellationToken cancellationToken = default(CancellationToken))
{
if (newTextSource == null)
throw new ArgumentNullException("newTextSource");
var internalObjects = InternalParseIncremental(oldParserState, newTextSource, out newParserState, false, cancellationToken);
return CreatePublic(internalObjects);
}
List<InternalObject> InternalParseIncremental(
IncrementalParserState oldParserState, ITextSource newTextSource, out IncrementalParserState newParserState,
bool collapseProperlyNestedElements, CancellationToken cancellationToken)
{
var reader = new TagReader(this, newTextSource, collapseProperlyNestedElements);
ITextSourceVersion newVersion = newTextSource.Version;
var reuseMap = oldParserState != null ? oldParserState.GetReuseMapTo(newVersion) : null;
List<InternalObject> internalObjects;
if (reuseMap != null)
internalObjects = reader.ReadAllObjectsIncremental(oldParserState.Objects, reuseMap, cancellationToken);
else
internalObjects = reader.ReadAllObjects(cancellationToken);
if (newVersion != null)
newParserState = new IncrementalParserState(newTextSource.TextLength, newVersion, internalObjects.ToArray());
else
newParserState = null;
return internalObjects;
}
/// <summary>
/// Parses a document.
/// </summary>
public AXmlDocument Parse(ITextSource textSource, CancellationToken cancellationToken = default(CancellationToken))
{
if (textSource == null)
throw new ArgumentNullException("textSource");
var reader = new TagReader(this, textSource, true);
var internalObjects = reader.ReadAllObjects(cancellationToken);
var heuristic = new TagMatchingHeuristics(textSource);
return new AXmlDocument(null, 0, heuristic.CreateDocument(internalObjects, cancellationToken));
}
/// <summary>
/// Parses a document incrementally into a flat list of tags.
/// </summary>
/// <param name="oldParserState">The parser state from a previous call to ParseIncremental(). Use null for the first call.</param>
/// <param name="newTextSource">The text source for the new document version.</param>
/// <param name="newParserState">Out: the new parser state, pass this to the next ParseIncremental() call.</param>
/// <param name="cancellationToken">Optional: cancellation token.</param>
/// <returns>Parsed tag soup.</returns>
public AXmlDocument ParseIncremental(
IncrementalParserState oldParserState, ITextSource newTextSource, out IncrementalParserState newParserState,
CancellationToken cancellationToken = default(CancellationToken))
{
if (newTextSource == null)
throw new ArgumentNullException("newTextSource");
var internalObjects = InternalParseIncremental(oldParserState, newTextSource, out newParserState, true, cancellationToken);
var heuristic = new TagMatchingHeuristics(newTextSource);
return new AXmlDocument(null, 0, heuristic.CreateDocument(internalObjects, cancellationToken));
}
/// <summary>
/// Checks whether the given name is a valid XML name.
/// </summary>
public static bool IsValidXmlName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("The XML name cannot be null, empty or consist solely of white space", "name");
return TagReader.IsValidName(name);
}
}
}

477
ICSharpCode.Decompiler/Xml/AXmlReader.cs

@ -0,0 +1,477 @@ @@ -0,0 +1,477 @@
// 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.Xml;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// XmlReader implementation that reads from an <see cref="AXmlDocument"/>.
/// </summary>
sealed class AXmlReader : XmlReader, IXmlLineInfo
{
readonly ObjectIterator objectIterator;
readonly XmlReaderSettings settings;
Func<int, TextLocation> offsetToTextLocation;
readonly XmlNameTable nameTable;
readonly XmlNamespaceManager nsManager;
ReadState readState = ReadState.Initial;
XmlNodeType elementNodeType = XmlNodeType.None;
IList<InternalAttribute> attributes;
int attributeIndex = -1;
bool inAttributeValue;
internal AXmlReader(ObjectIterator objectIterator, XmlReaderSettings settings = null, Func<int, TextLocation> offsetToTextLocation = null)
{
this.objectIterator = objectIterator;
this.settings = settings ?? new XmlReaderSettings();
this.offsetToTextLocation = offsetToTextLocation;
this.nameTable = this.settings.NameTable ?? new NameTable();
this.nsManager = new XmlNamespaceManager(this.nameTable);
objectIterator.StopAtElementEnd = true;
}
public override void ResolveEntity()
{
throw new NotSupportedException();
}
public override ReadState ReadState {
get { return readState; }
}
public override XmlReaderSettings Settings {
get { return settings; }
}
public override bool ReadAttributeValue()
{
if (attributeIndex >= 0 && !inAttributeValue) {
inAttributeValue = true;
return true;
}
return false;
}
public override bool Read()
{
switch (readState) {
case ReadState.Initial:
readState = ReadState.Interactive;
return ReadCurrentPosition();
case ReadState.Interactive:
LeaveNode();
objectIterator.MoveInto();
return ReadCurrentPosition();
default:
return false;
}
}
bool ReadCurrentPosition()
{
attributes = null;
attributeIndex = -1;
inAttributeValue = false;
while (true) {
var obj = objectIterator.CurrentObject;
if (obj == null) {
readState = ReadState.EndOfFile;
elementNodeType = XmlNodeType.None;
return false;
} else if (objectIterator.IsAtElementEnd) {
if (IsEmptyElement) {
// Don't report EndElement for empty elements
nsManager.PopScope();
} else {
elementNodeType = XmlNodeType.EndElement;
return true;
}
} else if (obj is InternalElement) {
// element start
elementNodeType = XmlNodeType.Element;
InternalTag startTag = ((InternalTag)obj.NestedObjects[0]);
nsManager.PushScope();
if (startTag.NestedObjects != null) {
attributes = startTag.NestedObjects.OfType<InternalAttribute>().ToList();
for (int i = 0; i < attributes.Count; i++) {
var attr = attributes[i];
if (attr.Name.StartsWith("xmlns:", StringComparison.Ordinal))
nsManager.AddNamespace(AXmlObject.GetLocalName(attr.Name), attr.Value);
else if (attr.Name == "xmlns")
nsManager.AddNamespace(string.Empty, attr.Value);
}
}
return true;
} else if (obj is InternalText) {
InternalText text = (InternalText)obj;
if (text.ContainsOnlyWhitespace) {
elementNodeType = XmlNodeType.Whitespace;
} else {
elementNodeType = XmlNodeType.Text;
}
return true;
} else if (obj is InternalTag) {
InternalTag tag = (InternalTag)obj;
if (tag.IsStartOrEmptyTag || tag.IsEndTag) {
// start/end tags can be skipped as the parent InternalElement already handles them
} else if (tag.IsComment && !settings.IgnoreComments) {
elementNodeType = XmlNodeType.Comment;
return true;
} else if (tag.IsProcessingInstruction && !settings.IgnoreProcessingInstructions) {
if (tag.Name == "xml") {
elementNodeType = XmlNodeType.XmlDeclaration;
attributes = tag.NestedObjects.OfType<InternalAttribute>().ToList();
} else {
elementNodeType = XmlNodeType.ProcessingInstruction;
}
return true;
} else if (tag.IsCData) {
elementNodeType = XmlNodeType.CDATA;
return true;
} else {
// TODO all other tags
}
} else {
throw new NotSupportedException();
}
objectIterator.MoveInto();
}
}
void LeaveNode()
{
if (elementNodeType == XmlNodeType.EndElement) {
nsManager.PopScope();
}
}
public override void Skip()
{
if (readState == ReadState.Interactive) {
MoveToElement();
LeaveNode();
objectIterator.MoveNext();
ReadCurrentPosition();
}
}
public override string Prefix {
get {
if (readState != ReadState.Interactive)
return string.Empty;
if (attributeIndex >= 0) {
if (inAttributeValue)
return string.Empty;
return nameTable.Add(AXmlObject.GetNamespacePrefix(attributes[attributeIndex].Name));
}
InternalElement element = objectIterator.CurrentObject as InternalElement;
return element != null ? nameTable.Add(element.Prefix) : string.Empty;
}
}
public override string NamespaceURI {
get {
if (readState != ReadState.Interactive)
return string.Empty;
if (attributeIndex >= 0 && !inAttributeValue && attributes[attributeIndex].Name == "xmlns")
return AXmlObject.XmlnsNamespace;
return LookupNamespace(this.Prefix) ?? string.Empty;
}
}
public override string LocalName {
get {
if (readState != ReadState.Interactive)
return string.Empty;
if (attributeIndex >= 0) {
if (inAttributeValue)
return string.Empty;
return nameTable.Add(AXmlObject.GetLocalName(attributes[attributeIndex].Name));
}
string result;
switch (elementNodeType) {
case XmlNodeType.Element:
case XmlNodeType.EndElement:
result = ((InternalElement)objectIterator.CurrentObject).LocalName;
break;
case XmlNodeType.XmlDeclaration:
result = "xml";
break;
default:
return string.Empty;
}
return nameTable.Add(result);
}
}
public override string Name {
get {
if (readState != ReadState.Interactive)
return string.Empty;
if (attributeIndex >= 0) {
if (inAttributeValue)
return string.Empty;
return nameTable.Add(attributes[attributeIndex].Name);
}
string result;
switch (elementNodeType) {
case XmlNodeType.Element:
case XmlNodeType.EndElement:
result = ((InternalElement)objectIterator.CurrentObject).Name;
break;
case XmlNodeType.XmlDeclaration:
result = "xml";
break;
default:
return string.Empty;
}
return nameTable.Add(result);
}
}
public override bool IsEmptyElement {
get {
if (readState != ReadState.Interactive || attributeIndex >= 0)
return false;
InternalElement element = objectIterator.CurrentObject as InternalElement;
return element != null && element.NestedObjects.Length == 1;
}
}
public override string Value {
get {
if (readState != ReadState.Interactive)
return string.Empty;
if (attributeIndex >= 0)
return attributes[attributeIndex].Value;
switch (elementNodeType) {
case XmlNodeType.Text:
case XmlNodeType.Whitespace:
return ((InternalText)objectIterator.CurrentObject).Value;
case XmlNodeType.Comment:
case XmlNodeType.CDATA:
var nestedObjects = objectIterator.CurrentObject.NestedObjects;
if (nestedObjects.Length == 1)
return ((InternalText)nestedObjects[0]).Value;
else
return string.Empty;
case XmlNodeType.XmlDeclaration:
StringBuilder b = new StringBuilder();
foreach (var attr in objectIterator.CurrentObject.NestedObjects.OfType<InternalAttribute>()) {
if (b.Length > 0)
b.Append(' ');
b.Append(attr.Name);
b.Append('=');
b.Append('"');
b.Append(attr.Value);
b.Append('"');
}
return b.ToString();
default:
return string.Empty;
}
}
}
public override bool HasValue {
get {
if (readState != ReadState.Interactive)
return false;
if (attributeIndex >= 0)
return true;
switch (elementNodeType) {
case XmlNodeType.Text:
case XmlNodeType.Whitespace:
case XmlNodeType.Comment:
case XmlNodeType.XmlDeclaration:
case XmlNodeType.CDATA:
return true;
default:
return false;
}
}
}
public override XmlNodeType NodeType {
get {
if (attributeIndex >= 0)
return inAttributeValue ? XmlNodeType.Text : XmlNodeType.Attribute;
else
return elementNodeType;
}
}
public override XmlNameTable NameTable {
get { return nameTable; }
}
public override bool MoveToFirstAttribute()
{
return DoMoveToAttribute(0);
}
public override bool MoveToNextAttribute()
{
return DoMoveToAttribute(attributeIndex + 1);
}
public override void MoveToAttribute(int i)
{
if (!DoMoveToAttribute(i))
throw new ArgumentOutOfRangeException("i");
}
bool DoMoveToAttribute(int i)
{
if (i >= 0 && i < this.AttributeCount) {
attributeIndex = i;
inAttributeValue = false;
return true;
}
return false;
}
public override bool MoveToElement()
{
if (attributeIndex >= 0) {
attributeIndex = -1;
inAttributeValue = false;
return true;
}
return false;
}
int GetAttributeIndex(string name)
{
if (attributes == null)
return -1;
for (int i = 0; i < attributes.Count; i++) {
if (attributes[i].Name == name)
return i;
}
return -1;
}
int GetAttributeIndex(string name, string ns)
{
if (attributes == null)
return -1;
for (int i = 0; i < attributes.Count; i++) {
if (AXmlObject.GetLocalName(attributes[i].Name) == name && (LookupNamespace(AXmlObject.GetNamespacePrefix(attributes[i].Name)) ?? string.Empty) == ns)
return i;
}
return -1;
}
public override bool MoveToAttribute(string name, string ns)
{
return DoMoveToAttribute(GetAttributeIndex(name, ns));
}
public override bool MoveToAttribute(string name)
{
return DoMoveToAttribute(GetAttributeIndex(name));
}
public override string LookupNamespace(string prefix)
{
return nsManager.LookupNamespace(prefix);
}
public override string GetAttribute(int i)
{
if (attributes == null || i < 0 || i >= attributes.Count)
return null;
return attributes[i].Value;
}
public override string GetAttribute(string name, string namespaceURI)
{
return GetAttribute(GetAttributeIndex(name, namespaceURI));
}
public override string GetAttribute(string name)
{
return GetAttribute(GetAttributeIndex(name));
}
public override bool EOF {
get { return readState == ReadState.EndOfFile; }
}
public override int Depth {
get {
if (attributeIndex < 0)
return objectIterator.Depth;
else
return objectIterator.Depth + (inAttributeValue ? 2 : 1);
}
}
public override void Close()
{
readState = ReadState.Closed;
offsetToTextLocation = null;
}
public override string BaseURI {
get { return string.Empty; }
}
public override int AttributeCount {
get { return attributes != null ? attributes.Count : 0; }
}
int CurrentPosition {
get {
if (attributeIndex < 0)
return objectIterator.CurrentPosition;
else
return objectIterator.CurrentPosition + attributes[attributeIndex].StartRelativeToParent;
}
}
public int LineNumber {
get {
if (offsetToTextLocation != null)
return offsetToTextLocation(CurrentPosition).Line;
else
return 0;
}
}
public int LinePosition {
get {
if (offsetToTextLocation != null)
return offsetToTextLocation(CurrentPosition).Column - 1;
else
return 0;
}
}
bool IXmlLineInfo.HasLineInfo()
{
return offsetToTextLocation != null;
}
}
}

96
ICSharpCode.Decompiler/Xml/AXmlTag.cs

@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
// 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.Collections.ObjectModel;
using System.Globalization;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Represents any markup starting with "&lt;" and (hopefully) ending with ">"
/// </summary>
public class AXmlTag : AXmlObject
{
/// <summary> These identify the start of DTD elements </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification="ReadOnlyCollection is immutable")]
public static readonly ReadOnlyCollection<string> DtdNames = new ReadOnlyCollection<string>(
new string[] {"<!DOCTYPE", "<!NOTATION", "<!ELEMENT", "<!ATTLIST", "<!ENTITY" } );
new readonly InternalTag internalObject;
internal AXmlTag(AXmlObject parent, int startOffset, InternalTag internalObject)
: base(parent, startOffset, internalObject)
{
this.internalObject = internalObject;
}
/// <summary> Opening bracket - usually "&lt;" </summary>
public string OpeningBracket {
get { return internalObject.OpeningBracket; }
}
/// <summary> Name following the opening bracket </summary>
public string Name {
get { return internalObject.Name; }
}
/// <summary> Gets the segment containing the tag name </summary>
public ISegment NameSegment {
get {
int start = startOffset + internalObject.RelativeNameStart;
return new XmlSegment(start, start + internalObject.Name.Length);
}
}
/// <summary> Closing bracket - usually "&gt;" </summary>
public string ClosingBracket {
get { return internalObject.ClosingBracket; }
}
/// <summary> True if tag starts with "&lt;" </summary>
public bool IsStartOrEmptyTag { get { return internalObject.IsStartOrEmptyTag; } }
/// <summary> True if tag starts with "&lt;" and ends with "&gt;" </summary>
public bool IsStartTag { get { return internalObject.IsStartTag; } }
/// <summary> True if tag starts with "&lt;" and does not end with "&gt;" </summary>
public bool IsEmptyTag { get { return internalObject.IsEmptyTag; } }
/// <summary> True if tag starts with "&lt;/" </summary>
public bool IsEndTag { get { return internalObject.IsEndTag; } }
/// <summary> True if tag starts with "&lt;?" </summary>
public bool IsProcessingInstruction { get { return internalObject.IsProcessingInstruction; } }
/// <summary> True if tag starts with "&lt;!--" </summary>
public bool IsComment { get { return internalObject.IsComment; } }
/// <summary> True if tag starts with "&lt;![CDATA[" </summary>
public bool IsCData { get { return internalObject.IsCData; } }
/// <summary> True if tag starts with one of the DTD starts </summary>
public bool IsDocumentType { get { return internalObject.IsDocumentType; } }
/// <summary> True if tag starts with "&lt;!" </summary>
public bool IsUnknownBang { get { return internalObject.IsUnknownBang; } }
/// <inheritdoc/>
public override void AcceptVisitor(AXmlVisitor visitor)
{
visitor.VisitTag(this);
}
/// <inheritdoc/>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}{2}{3}' Attr:{4}]", base.ToString(), this.OpeningBracket, this.Name, this.ClosingBracket, this.Children.Count);
}
}
}

63
ICSharpCode.Decompiler/Xml/AXmlText.cs

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
// 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.Globalization;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Whitespace or character data
/// </summary>
public class AXmlText : AXmlObject
{
internal AXmlText(AXmlObject parent, int startOffset, InternalText internalObject)
: base(parent, startOffset, internalObject)
{
}
// /// <summary> The type of the text node </summary>
// public TextType Type {
// get { return ((InternalText)internalObject).Type; }
// }
/// <summary> The text with all entity references resloved </summary>
public string Value {
get { return ((InternalText)internalObject).Value; }
}
/// <summary> True if the text contains only whitespace characters </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "System.Xml also uses 'Whitespace'")]
public bool ContainsOnlyWhitespace {
get { return ((InternalText)internalObject).ContainsOnlyWhitespace; }
}
/// <inheritdoc/>
public override void AcceptVisitor(AXmlVisitor visitor)
{
visitor.VisitText(this);
}
/// <inheritdoc/>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "[{0} Text.Length={1}]", base.ToString(), this.Value.Length);
}
}
}

62
ICSharpCode.Decompiler/Xml/AXmlVisitor.cs

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
// 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.Text;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Derive from this class to create visitor for the XML tree
/// </summary>
public abstract class AXmlVisitor
{
/// <summary> Visit AXmlDocument </summary>
public virtual void VisitDocument(AXmlDocument document)
{
foreach (AXmlObject child in document.Children)
child.AcceptVisitor(this);
}
/// <summary> Visit AXmlElement </summary>
public virtual void VisitElement(AXmlElement element)
{
foreach (AXmlObject child in element.Children)
child.AcceptVisitor(this);
}
/// <summary> Visit AXmlTag </summary>
public virtual void VisitTag(AXmlTag tag)
{
foreach (AXmlObject child in tag.Children)
child.AcceptVisitor(this);
}
/// <summary> Visit AXmlAttribute </summary>
public virtual void VisitAttribute(AXmlAttribute attribute)
{
}
/// <summary> Visit AXmlText </summary>
public virtual void VisitText(AXmlText text)
{
}
}
}

43
ICSharpCode.Decompiler/Xml/AnchorMovementType.cs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
// 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.
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Defines how a text anchor moves.
/// </summary>
public enum AnchorMovementType
{
/// <summary>
/// When text is inserted at the anchor position, the type of the insertion
/// determines where the caret moves to. For normal insertions, the anchor will move
/// after the inserted text.
/// </summary>
Default,
/// <summary>
/// Behaves like a start marker - when text is inserted at the anchor position, the anchor will stay
/// before the inserted text.
/// </summary>
BeforeInsertion,
/// <summary>
/// Behave like an end marker - when text is insered at the anchor position, the anchor will move
/// after the inserted text.
/// </summary>
AfterInsertion
}
}

254
ICSharpCode.Decompiler/Xml/DocumentationElement.cs

@ -0,0 +1,254 @@ @@ -0,0 +1,254 @@
// 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.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Represents an element in the XML documentation.
/// Any occurrences of "&lt;inheritdoc/>" are replaced with the inherited documentation.
/// </summary>
public class XmlDocumentationElement
{
static XmlDocumentationElement Create(string documentationComment, IEntity declaringEntity)
{
var doc = new AXmlParser().Parse(new StringTextSource(documentationComment));
return new XmlDocumentationElement(doc, declaringEntity, null);
}
readonly AXmlObject xmlObject;
readonly AXmlElement element;
readonly IEntity declaringEntity;
readonly Func<string, IEntity> crefResolver;
volatile string textContent;
/// <summary>
/// Inheritance level; used to prevent cyclic doc inheritance.
/// </summary>
int nestingLevel;
/// <summary>
/// Creates a new documentation element.
/// </summary>
public XmlDocumentationElement(AXmlElement element, IEntity declaringEntity, Func<string, IEntity> crefResolver)
{
if (element == null)
throw new ArgumentNullException("element");
this.element = element;
this.xmlObject = element;
this.declaringEntity = declaringEntity;
this.crefResolver = crefResolver;
}
/// <summary>
/// Creates a new documentation element.
/// </summary>
public XmlDocumentationElement(AXmlDocument document, IEntity declaringEntity, Func<string, IEntity> crefResolver)
{
if (document == null)
throw new ArgumentNullException("document");
this.xmlObject = document;
this.declaringEntity = declaringEntity;
this.crefResolver = crefResolver;
}
/// <summary>
/// Creates a new documentation element.
/// </summary>
public XmlDocumentationElement(string text, IEntity declaringEntity)
{
if (text == null)
throw new ArgumentNullException("text");
this.declaringEntity = declaringEntity;
this.textContent = text;
}
/// <summary>
/// Gets the entity on which this documentation was originally declared.
/// May return null.
/// </summary>
public IEntity DeclaringEntity {
get { return declaringEntity; }
}
IEntity referencedEntity;
volatile bool referencedEntityInitialized;
/// <summary>
/// Gets the entity referenced by the 'cref' attribute.
/// May return null.
/// </summary>
public IEntity ReferencedEntity {
get {
if (!referencedEntityInitialized) {
string cref = GetAttribute("cref");
if (cref != null && crefResolver != null)
referencedEntity = crefResolver(cref);
referencedEntityInitialized = true;
}
return referencedEntity;
}
}
/// <summary>
/// Gets the element name.
/// </summary>
public string Name {
get {
return element != null ? element.Name : string.Empty;
}
}
/// <summary>
/// Gets the attribute value.
/// </summary>
public string GetAttribute(string name)
{
return element != null ? element.GetAttributeValue(name) : string.Empty;
}
/// <summary>
/// Gets whether this is a pure text node.
/// </summary>
public bool IsTextNode {
get { return xmlObject == null; }
}
/// <summary>
/// Gets the text content.
/// </summary>
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<XmlDocumentationElement> children;
/// <summary>
/// Gets the child elements.
/// </summary>
public IList<XmlDocumentationElement> Children {
get {
if (xmlObject == null)
return EmptyList<XmlDocumentationElement>.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<XmlDocumentationElement> CreateElements(IEnumerable<AXmlObject> childObjects, IEntity declaringEntity, Func<string, IEntity> crefResolver, int nestingLevel)
{
List<XmlDocumentationElement> list = new List<XmlDocumentationElement>();
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<AXmlText>())
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<string> doNotInherit = new List<string>();
doNotInherit.Add("overloads");
doNotInherit.AddRange(childObjects.OfType<AXmlElement>().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;
}
/// <inheritdoc/>
public override string ToString()
{
if (element != null)
return "<" + element.Name + ">";
else
return this.TextContent;
}
}
}

69
ICSharpCode.Decompiler/Xml/ISegment.cs

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
// 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.
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// An (Offset,Length)-pair.
/// </summary>
public interface ISegment
{
/// <summary>
/// Gets the start offset of the segment.
/// </summary>
int Offset { get; }
/// <summary>
/// Gets the length of the segment.
/// </summary>
/// <remarks>For line segments (IDocumentLine), the length does not include the line delimeter.</remarks>
int Length { get; }
/// <summary>
/// Gets the end offset of the segment.
/// </summary>
/// <remarks>EndOffset = Offset + Length;</remarks>
int EndOffset { get; }
}
/// <summary>
/// Extension methods for <see cref="ISegment"/>.
/// </summary>
public static class ISegmentExtensions
{
/// <summary>
/// Gets whether <paramref name="segment"/> fully contains the specified segment.
/// </summary>
/// <remarks>
/// Use <c>segment.Contains(offset, 0)</c> to detect whether a segment (end inclusive) contains offset;
/// use <c>segment.Contains(offset, 1)</c> to detect whether a segment (end exclusive) contains offset.
/// </remarks>
public static bool Contains(this ISegment segment, int offset, int length)
{
return segment.Offset <= offset && offset + length <= segment.EndOffset;
}
/// <summary>
/// Gets whether <paramref name="thisSegment"/> fully contains the specified segment.
/// </summary>
public static bool Contains(this ISegment thisSegment, ISegment segment)
{
return segment != null && thisSegment.Offset <= segment.Offset && segment.EndOffset <= thisSegment.EndOffset;
}
}
}

218
ICSharpCode.Decompiler/Xml/ITextSource.cs

@ -0,0 +1,218 @@ @@ -0,0 +1,218 @@
// 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.IO;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// A read-only view on a (potentially mutable) text source.
/// The IDocument interface derives from this interface.
/// </summary>
public interface ITextSource
{
/// <summary>
/// Gets a version identifier for this text source.
/// Returns null for unversioned text sources.
/// </summary>
ITextSourceVersion Version { get; }
/// <summary>
/// Creates an immutable snapshot of this text source.
/// Unlike all other methods in this interface, this method is thread-safe.
/// </summary>
ITextSource CreateSnapshot();
/// <summary>
/// Creates an immutable snapshot of a part of this text source.
/// Unlike all other methods in this interface, this method is thread-safe.
/// </summary>
ITextSource CreateSnapshot(int offset, int length);
/// <summary>
/// Creates a new TextReader to read from this text source.
/// </summary>
TextReader CreateReader();
/// <summary>
/// Creates a new TextReader to read from this text source.
/// </summary>
TextReader CreateReader(int offset, int length);
/// <summary>
/// Gets the total text length.
/// </summary>
/// <returns>The length of the text, in characters.</returns>
/// <remarks>This is the same as Text.Length, but is more efficient because
/// it doesn't require creating a String object.</remarks>
int TextLength { get; }
/// <summary>
/// Gets the whole text as string.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
string Text { get; }
/// <summary>
/// Gets a character at the specified position in the document.
/// </summary>
/// <paramref name="offset">The index of the character to get.</paramref>
/// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to TextLength-1).</exception>
/// <returns>The character at the specified position.</returns>
/// <remarks>This is the same as Text[offset], but is more efficient because
/// it doesn't require creating a String object.</remarks>
char GetCharAt(int offset);
/// <summary>
/// Retrieves the text for a portion of the document.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
/// <remarks>This is the same as Text.Substring, but is more efficient because
/// it doesn't require creating a String object for the whole document.</remarks>
string GetText(int offset, int length);
/// <summary>
/// Retrieves the text for a portion of the document.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception>
string GetText(ISegment segment);
/// <summary>
/// Writes the text from this document into the TextWriter.
/// </summary>
void WriteTextTo(TextWriter writer);
/// <summary>
/// Writes the text from this document into the TextWriter.
/// </summary>
void WriteTextTo(TextWriter writer, int offset, int length);
/// <summary>
/// Gets the index of the first occurrence of the character in the specified array.
/// </summary>
/// <param name="c">Character to search for</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <returns>The first index where the character was found; or -1 if no occurrence was found.</returns>
int IndexOf(char c, int startIndex, int count);
/// <summary>
/// Gets the index of the first occurrence of any character in the specified array.
/// </summary>
/// <param name="anyOf">Characters to search for</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <returns>The first index where any character was found; or -1 if no occurrence was found.</returns>
int IndexOfAny(char[] anyOf, int startIndex, int count);
/// <summary>
/// Gets the index of the first occurrence of the specified search text in this text source.
/// </summary>
/// <param name="searchText">The search text</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <param name="comparisonType">String comparison to use.</param>
/// <returns>The first index where the search term was found; or -1 if no occurrence was found.</returns>
int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType);
/// <summary>
/// Gets the index of the last occurrence of the specified character in this text source.
/// </summary>
/// <param name="c">The search character</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <returns>The last index where the search term was found; or -1 if no occurrence was found.</returns>
/// <remarks>The search proceeds backwards from (startIndex+count) to startIndex.
/// This is different than the meaning of the parameters on string.LastIndexOf!</remarks>
int LastIndexOf(char c, int startIndex, int count);
/// <summary>
/// Gets the index of the last occurrence of the specified search text in this text source.
/// </summary>
/// <param name="searchText">The search text</param>
/// <param name="startIndex">Start index of the area to search.</param>
/// <param name="count">Length of the area to search.</param>
/// <param name="comparisonType">String comparison to use.</param>
/// <returns>The last index where the search term was found; or -1 if no occurrence was found.</returns>
/// <remarks>The search proceeds backwards from (startIndex+count) to startIndex.
/// This is different than the meaning of the parameters on string.LastIndexOf!</remarks>
int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType);
/* What about:
void Insert (int offset, string value);
void Remove (int offset, int count);
void Remove (ISegment segment);
void Replace (int offset, int count, string value);
Or more search operations:
IEnumerable<int> SearchForward (string pattern, int startIndex);
IEnumerable<int> SearchForwardIgnoreCase (string pattern, int startIndex);
IEnumerable<int> SearchBackward (string pattern, int startIndex);
IEnumerable<int> SearchBackwardIgnoreCase (string pattern, int startIndex);
*/
}
/// <summary>
/// Represents a version identifier for a text source.
/// </summary>
/// <remarks>
/// Verions can be used to efficiently detect whether a document has changed and needs reparsing;
/// or even to implement incremental parsers.
/// It is a separate class from ITextSource to allow the GC to collect the text source while
/// the version checkpoint is still in use.
/// </remarks>
public interface ITextSourceVersion
{
/// <summary>
/// Gets whether this checkpoint belongs to the same document as the other checkpoint.
/// </summary>
/// <remarks>
/// Returns false when given <c>null</c>.
/// </remarks>
bool BelongsToSameDocumentAs(ITextSourceVersion other);
/// <summary>
/// Compares the age of this checkpoint to the other checkpoint.
/// </summary>
/// <remarks>This method is thread-safe.</remarks>
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this version.</exception>
/// <returns>-1 if this version is older than <paramref name="other"/>.
/// 0 if <c>this</c> version instance represents the same version as <paramref name="other"/>.
/// 1 if this version is newer than <paramref name="other"/>.</returns>
int CompareAge(ITextSourceVersion other);
/// <summary>
/// Gets the changes from this checkpoint to the other checkpoint.
/// If 'other' is older than this checkpoint, reverse changes are calculated.
/// </summary>
/// <remarks>This method is thread-safe.</remarks>
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
IEnumerable<TextChangeEventArgs> GetChangesTo(ITextSourceVersion other);
/// <summary>
/// Calculates where the offset has moved in the other buffer version.
/// </summary>
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
int MoveOffsetTo(ITextSourceVersion other, int oldOffset, AnchorMovementType movement = AnchorMovementType.Default);
}
}

114
ICSharpCode.Decompiler/Xml/IncrementalParserState.cs

@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
// 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.Diagnostics;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Encapsulates the state of the incremental tag soup parser.
/// </summary>
public class IncrementalParserState
{
internal readonly int TextLength;
internal readonly ITextSourceVersion Version;
internal readonly InternalObject[] Objects;
internal IncrementalParserState(int textLength, ITextSourceVersion version, InternalObject[] objects)
{
this.TextLength = textLength;
this.Version = version;
this.Objects = objects;
}
internal List<UnchangedSegment> GetReuseMapTo(ITextSourceVersion newVersion)
{
ITextSourceVersion oldVersion = this.Version;
if (oldVersion == null || newVersion == null)
return null;
if (!oldVersion.BelongsToSameDocumentAs(newVersion))
return null;
List<UnchangedSegment> reuseMap = new List<UnchangedSegment>();
reuseMap.Add(new UnchangedSegment(0, 0, this.TextLength));
foreach (var change in oldVersion.GetChangesTo(newVersion)) {
bool needsSegmentRemoval = false;
for (int i = 0; i < reuseMap.Count; i++) {
UnchangedSegment segment = reuseMap[i];
if (segment.NewOffset + segment.Length <= change.Offset) {
// change is completely after this segment
continue;
}
if (change.Offset + change.RemovalLength <= segment.NewOffset) {
// change is completely before this segment
segment.NewOffset += change.InsertionLength - change.RemovalLength;
reuseMap[i] = segment;
continue;
}
// Change is overlapping segment.
// Split segment into two parts: the part before change, and the part after change.
var segmentBefore = new UnchangedSegment(segment.OldOffset, segment.NewOffset, change.Offset - segment.NewOffset);
Debug.Assert(segmentBefore.Length < segment.Length);
int lengthAtEnd = segment.NewOffset + segment.Length - (change.Offset + change.RemovalLength);
var segmentAfter = new UnchangedSegment(
segment.OldOffset + segment.Length - lengthAtEnd,
change.Offset + change.InsertionLength,
lengthAtEnd);
Debug.Assert(segmentAfter.Length < segment.Length);
Debug.Assert(segmentBefore.Length + segmentAfter.Length <= segment.Length);
Debug.Assert(segmentBefore.NewOffset + segmentBefore.Length <= segmentAfter.NewOffset);
Debug.Assert(segmentBefore.OldOffset + segmentBefore.Length <= segmentAfter.OldOffset);
if (segmentBefore.Length > 0 && segmentAfter.Length > 0) {
reuseMap[i] = segmentBefore;
reuseMap.Insert(++i, segmentAfter);
} else if (segmentBefore.Length > 0) {
reuseMap[i] = segmentBefore;
} else {
reuseMap[i] = segmentAfter;
if (segmentAfter.Length <= 0)
needsSegmentRemoval = true;
}
}
if (needsSegmentRemoval)
reuseMap.RemoveAll(s => s.Length <= 0);
}
return reuseMap;
}
}
struct UnchangedSegment
{
public int OldOffset;
public int NewOffset;
public int Length;
public UnchangedSegment(int oldOffset, int newOffset, int length)
{
this.OldOffset = oldOffset;
this.NewOffset = newOffset;
this.Length = length;
}
public override string ToString()
{
return string.Format("[UnchangedSegment OldOffset={0}, NewOffset={1}, Length={2}]", OldOffset, NewOffset, Length);
}
}
}

175
ICSharpCode.Decompiler/Xml/InternalDocument.cs

@ -0,0 +1,175 @@ @@ -0,0 +1,175 @@
// 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;
namespace ICSharpCode.Decompiler.Xml
{
internal abstract class InternalObject
{
public int StartRelativeToParent;
public int Length;
/// <summary>Length that was touched to parsed this object.</summary>
public int LengthTouched;
public InternalSyntaxError[] SyntaxErrors;
public InternalObject[] NestedObjects;
public InternalObject SetStartRelativeToParent(int newStartRelativeToParent)
{
if (newStartRelativeToParent == StartRelativeToParent)
return this;
InternalObject obj = (InternalObject)MemberwiseClone();
obj.StartRelativeToParent = newStartRelativeToParent;
return obj;
}
public abstract AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset);
}
sealed class InternalDocument : InternalObject
{
public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
{
return new AXmlDocument(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
}
}
sealed class InternalText : InternalObject
{
public TextType Type;
public bool ContainsOnlyWhitespace;
public string Value;
public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
{
return new AXmlText(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
}
public override string ToString()
{
return "Text: " + this.Value.Replace("\n", "\\n").Replace("\r", "\\r");
}
}
sealed class InternalTag : InternalObject
{
public string OpeningBracket;
public int RelativeNameStart;
public string Name;
public string ClosingBracket;
/// <summary> True if tag starts with "&lt;" </summary>
public bool IsStartOrEmptyTag { get { return OpeningBracket == "<"; } }
/// <summary> True if tag starts with "&lt;" and ends with "&gt;" </summary>
public bool IsStartTag { get { return OpeningBracket == "<" && ClosingBracket == ">"; } }
/// <summary> True if tag starts with "&lt;" and does not end with "&gt;" </summary>
public bool IsEmptyTag { get { return OpeningBracket == "<" && ClosingBracket != ">" ; } }
/// <summary> True if tag starts with "&lt;/" </summary>
public bool IsEndTag { get { return OpeningBracket == "</"; } }
/// <summary> True if tag starts with "&lt;?" </summary>
public bool IsProcessingInstruction { get { return OpeningBracket == "<?"; } }
/// <summary> True if tag starts with "&lt;!--" </summary>
public bool IsComment { get { return OpeningBracket == "<!--"; } }
/// <summary> True if tag starts with "&lt;![CDATA[" </summary>
public bool IsCData { get { return OpeningBracket == "<![CDATA["; } }
/// <summary> True if tag starts with one of the DTD starts </summary>
public bool IsDocumentType { get { return AXmlTag.DtdNames.Contains(OpeningBracket); } }
/// <summary> True if tag starts with "&lt;!" </summary>
public bool IsUnknownBang { get { return OpeningBracket == "<!"; } }
public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
{
return new AXmlTag(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
}
public override string ToString()
{
return "Tag: " + OpeningBracket + Name + ClosingBracket;
}
public InternalTag AddSyntaxError(string description)
{
if (this.SyntaxErrors != null && this.SyntaxErrors.Length > 0)
return this; // don't add error if there already is one
InternalTag tag = (InternalTag)MemberwiseClone();
tag.SyntaxErrors = new InternalSyntaxError[] { new InternalSyntaxError(0, Length, description) };
return tag;
}
}
struct InternalSyntaxError
{
public readonly int RelativeStart;
public readonly int RelativeEnd;
public readonly string Description;
public InternalSyntaxError(int relativeStart, int relativeEnd, string description)
{
this.RelativeStart = relativeStart;
this.RelativeEnd = relativeEnd;
this.Description = description;
}
}
sealed class InternalAttribute : InternalObject
{
public string Name;
public int EqualsSignLength; // length of equals sign including the surrounding whitespace
public string Value;
public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
{
return new AXmlAttribute(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
}
public override string ToString()
{
return "Attribute: " + Name + "='" + Value + "'";
}
}
sealed class InternalElement : InternalObject
{
public bool HasEndTag;
public bool IsPropertyNested;
public readonly string Name;
public InternalElement(InternalTag tag)
{
this.Name = tag.Name;
}
public string Prefix {
get { return AXmlObject.GetNamespacePrefix(Name); }
}
public string LocalName {
get { return AXmlObject.GetLocalName(Name); }
}
public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
{
return new AXmlElement(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
}
public override string ToString()
{
return "Element: " + NestedObjects[0].ToString();
}
}
}

88
ICSharpCode.Decompiler/Xml/Log.cs

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
// 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.Diagnostics;
using System.Runtime.Serialization;
namespace ICSharpCode.Decompiler.Xml
{
static class Log
{
/// <summary> Throws exception if condition is false </summary>
internal static void Assert(bool condition, string message)
{
if (!condition) {
throw new InternalException("Assertion failed: " + message);
}
}
/// <summary> Throws exception if condition is false </summary>
[Conditional("DEBUG")]
internal static void DebugAssert(bool condition, string message)
{
if (!condition) {
throw new InternalException("Assertion failed: " + message);
}
}
[Conditional("DEBUG")]
internal static void WriteLine(string text, params object[] pars)
{
//System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "XML: " + text, pars));
}
}
/// <summary>
/// Exception used for internal errors in XML parser.
/// This exception indicates a bug in NRefactory.
/// </summary>
[Serializable]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "This exception is not public because it is not supposed to be caught by user code - it indicates a bug in AvalonEdit.")]
class InternalException : Exception
{
/// <summary>
/// Creates a new InternalException instance.
/// </summary>
public InternalException() : base()
{
}
/// <summary>
/// Creates a new InternalException instance.
/// </summary>
public InternalException(string message) : base(message)
{
}
/// <summary>
/// Creates a new InternalException instance.
/// </summary>
public InternalException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Creates a new InternalException instance.
/// </summary>
protected InternalException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

109
ICSharpCode.Decompiler/Xml/ObjectIterator.cs

@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
// 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;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Iterates through an internal object tree.
/// </summary>
sealed class ObjectIterator
{
Stack<InternalObject[]> listStack = new Stack<InternalObject[]>();
Stack<int> indexStack = new Stack<int>();
InternalObject[] objects;
int currentIndex;
InternalObject currentObject;
int currentPosition;
internal bool StopAtElementEnd;
bool isAtElementEnd;
public ObjectIterator(InternalObject[] objects, int startPosition = 0)
{
this.currentPosition = startPosition;
this.objects = objects;
if (objects.Length > 0)
this.currentObject = objects[0];
}
public InternalObject CurrentObject {
get { return currentObject; }
}
public int CurrentPosition {
get { return currentPosition; }
}
public bool IsAtElementEnd {
get { return isAtElementEnd; }
}
public int Depth {
get { return listStack.Count; }
}
public void MoveNext()
{
if (currentObject == null)
return;
currentIndex++;
currentPosition += currentObject.Length;
isAtElementEnd = false;
while (currentIndex >= objects.Length && listStack.Count > 0) {
objects = listStack.Pop();
currentIndex = indexStack.Pop();
if (this.StopAtElementEnd) {
isAtElementEnd = true;
break;
} else {
currentIndex++;
}
}
currentObject = (currentIndex < objects.Length ? objects[currentIndex] : null);
}
public void MoveInto()
{
if (isAtElementEnd || !(currentObject is InternalElement)) {
MoveNext();
} else {
listStack.Push(objects);
indexStack.Push(currentIndex);
objects = currentObject.NestedObjects;
currentIndex = 0;
currentObject = objects[0];
}
}
/// <summary>
/// Skips all nodes in front of 'position'
/// </summary>
public void SkipTo(int position)
{
while (currentObject != null && currentPosition < position) {
if (currentPosition + currentObject.Length <= position)
MoveNext();
else
MoveInto();
}
}
}
}

52
ICSharpCode.Decompiler/Xml/ReuseEqualityComparer.cs

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
// 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;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Determines whether two objects are identical (one is a reused version of the other).
/// </summary>
public class ReuseEqualityComparer : IEqualityComparer<AXmlObject>
{
/// <summary>
/// Determines whether two objects are identical (one is a reused version of the other).
/// </summary>
public bool Equals(AXmlObject x, AXmlObject y)
{
if (x == y)
return true;
if (x == null || y == null)
return false;
return x.internalObject == y.internalObject;
}
/// <summary>
/// Gets the object's hash code so that reused versions of an object have the same hash code.
/// </summary>
public int GetHashCode(AXmlObject obj)
{
if (obj == null)
return 0;
else
return obj.internalObject.GetHashCode();
}
}
}

160
ICSharpCode.Decompiler/Xml/StringTextSource.cs

@ -0,0 +1,160 @@ @@ -0,0 +1,160 @@
// 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.IO;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Implements the ITextSource interface using a string.
/// </summary>
[Serializable]
public class StringTextSource : ITextSource
{
/// <summary>
/// Gets a text source containing the empty string.
/// </summary>
public static readonly StringTextSource Empty = new StringTextSource(string.Empty);
readonly string text;
readonly ITextSourceVersion version;
/// <summary>
/// Creates a new StringTextSource with the given text.
/// </summary>
public StringTextSource(string text)
{
if (text == null)
throw new ArgumentNullException("text");
this.text = text;
}
/// <summary>
/// Creates a new StringTextSource with the given text.
/// </summary>
public StringTextSource(string text, ITextSourceVersion version)
{
if (text == null)
throw new ArgumentNullException("text");
this.text = text;
this.version = version;
}
/// <inheritdoc/>
public ITextSourceVersion Version {
get { return version; }
}
/// <inheritdoc/>
public int TextLength {
get { return text.Length; }
}
/// <inheritdoc/>
public string Text {
get { return text; }
}
/// <inheritdoc/>
public ITextSource CreateSnapshot()
{
return this; // StringTextSource is immutable
}
/// <inheritdoc/>
public ITextSource CreateSnapshot(int offset, int length)
{
return new StringTextSource(text.Substring(offset, length));
}
/// <inheritdoc/>
public TextReader CreateReader()
{
return new StringReader(text);
}
/// <inheritdoc/>
public TextReader CreateReader(int offset, int length)
{
return new StringReader(text.Substring(offset, length));
}
/// <inheritdoc/>
public void WriteTextTo(TextWriter writer)
{
writer.Write(text);
}
/// <inheritdoc/>
public void WriteTextTo(TextWriter writer, int offset, int length)
{
writer.Write(text.Substring(offset, length));
}
/// <inheritdoc/>
public char GetCharAt(int offset)
{
return text[offset];
}
/// <inheritdoc/>
public string GetText(int offset, int length)
{
return text.Substring(offset, length);
}
/// <inheritdoc/>
public string GetText(ISegment segment)
{
if (segment == null)
throw new ArgumentNullException("segment");
return text.Substring(segment.Offset, segment.Length);
}
/// <inheritdoc/>
public int IndexOf(char c, int startIndex, int count)
{
return text.IndexOf(c, startIndex, count);
}
/// <inheritdoc/>
public int IndexOfAny(char[] anyOf, int startIndex, int count)
{
return text.IndexOfAny(anyOf, startIndex, count);
}
/// <inheritdoc/>
public int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
return text.IndexOf(searchText, startIndex, count, comparisonType);
}
/// <inheritdoc/>
public int LastIndexOf(char c, int startIndex, int count)
{
return text.LastIndexOf(c, startIndex + count - 1, count);
}
/// <inheritdoc/>
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
return text.LastIndexOf(searchText, startIndex + count - 1, count, comparisonType);
}
}
}

72
ICSharpCode.Decompiler/Xml/SyntaxError.cs

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
// 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;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// A syntax error.
/// </summary>
public class SyntaxError : ISegment
{
readonly int startOffset;
readonly int endOffset;
readonly string description;
/// <summary>
/// Creates a new syntax error.
/// </summary>
public SyntaxError(int startOffset, int endOffset, string description)
{
if (description == null)
throw new ArgumentNullException("description");
this.startOffset = startOffset;
this.endOffset = endOffset;
this.description = description;
}
/// <summary>
/// Gets a description of the syntax error.
/// </summary>
public string Description {
get { return description; }
}
/// <summary>
/// Gets the start offset of the segment.
/// </summary>
public int StartOffset {
get { return startOffset; }
}
int ISegment.Offset {
get { return startOffset; }
}
/// <inheritdoc/>
public int Length {
get { return endOffset - startOffset; }
}
/// <inheritdoc/>
public int EndOffset {
get { return endOffset; }
}
}
}

351
ICSharpCode.Decompiler/Xml/TagMatchingHeuristics.cs

@ -0,0 +1,351 @@ @@ -0,0 +1,351 @@
// 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.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Collections.Immutable;
namespace ICSharpCode.Decompiler.Xml
{
class TagMatchingHeuristics
{
readonly ITextSource textSource;
const int MaxConfigurationCount = 30;
public TagMatchingHeuristics(ITextSource textSource)
{
this.textSource = textSource;
}
public InternalDocument CreateDocument(List<InternalObject> tagSoup, CancellationToken cancellationToken)
{
var stack = InsertPlaceholderTags(tagSoup, cancellationToken);
InternalDocument doc = new InternalDocument();
var docElements = CreateElements(ref stack);
docElements.Reverse(); // reverse due to stack
doc.NestedObjects = new InternalObject[docElements.Count];
int pos = 0;
for (int i = 0; i < docElements.Count; i++) {
doc.NestedObjects[i] = docElements[i].SetStartRelativeToParent(pos);
pos += doc.NestedObjects[i].Length;
}
doc.Length = pos;
return doc;
}
#region Heuristic implementation - Inserting place holders into object stream
// Tags used to guide the element creation
static readonly InternalTag StartTagPlaceholder = new InternalTag { OpeningBracket = "<", ClosingBracket = ">" };
static readonly InternalTag EndTagPlaceholder = new InternalTag { OpeningBracket = "</", ClosingBracket = ">" };
class OpenTagStack
{
readonly OpenTagStack prev;
public readonly string Name;
public readonly int IndentationLevel;
readonly int hashCode;
public OpenTagStack()
{
}
private OpenTagStack(OpenTagStack prev, string name, int indentationLevel)
{
this.prev = prev;
this.Name = name;
this.IndentationLevel = indentationLevel;
unchecked {
this.hashCode = prev.hashCode * 27 + (name.GetHashCode() ^ indentationLevel);
}
}
public bool IsEmpty {
get { return prev == null; }
}
public OpenTagStack Push(string name, int indentationLevel)
{
return new OpenTagStack(this, name, indentationLevel);
}
public OpenTagStack Pop()
{
return prev;
}
public override int GetHashCode()
{
return hashCode;
}
public override bool Equals(object obj)
{
OpenTagStack o = obj as OpenTagStack;
if (o != null && hashCode == o.hashCode && IndentationLevel == o.IndentationLevel && Name == o.Name) {
if (prev == o.prev)
return true;
if (prev == null || o.prev == null)
return false;
return prev.Equals(o.prev);
}
return false;
}
}
struct Configuration
{
public readonly OpenTagStack OpenTags;
public readonly ImmutableStack<InternalObject> Document;
public readonly uint Cost;
public Configuration(OpenTagStack openTags, ImmutableStack<InternalObject> document, uint cost)
{
this.OpenTags = openTags;
this.Document = document;
this.Cost = cost;
}
}
struct ConfigurationList
{
internal Configuration[] configurations;
internal int count;
public static ConfigurationList Create()
{
return new ConfigurationList {
configurations = new Configuration[MaxConfigurationCount],
count = 0
};
}
public void Clear()
{
this.count = 0;
}
public void Add(OpenTagStack openTags, ImmutableStack<InternalObject> document, uint cost)
{
Add(new Configuration(openTags, document, cost));
}
public void Add(Configuration configuration)
{
for (int i = 0; i < count; i++) {
if (configuration.OpenTags.Equals(configurations[i].OpenTags)) {
// We found an existing configuration with the same state.
// Either replace it, or drop this configurations --
// we don't want to add multiple configurations with the same state.
if (configuration.Cost < configurations[i].Cost)
configurations[i] = configuration;
return;
}
}
if (count < configurations.Length) {
configurations[count++] = configuration;
} else {
int index = 0;
uint maxCost = configurations[0].Cost;
for (int i = 1; i < configurations.Length; i++) {
if (configurations[i].Cost < maxCost) {
maxCost = configurations[i].Cost;
index = i;
}
}
configurations[index] = configuration;
}
}
}
const uint InfiniteCost = uint.MaxValue;
const uint MissingEndTagCost = 10;
const uint IgnoreEndTagCost = 10;
const uint MismatchedNameCost = 6;
int GetIndentationBefore(int position)
{
int indentation = 0;
while (--position >= 0) {
char c = textSource.GetCharAt(position);
switch (c) {
case ' ':
indentation++;
break;
case '\t':
indentation += 4;
break;
case '\n':
return indentation;
default:
return -1;
}
}
return indentation;
}
ImmutableStack<InternalObject> InsertPlaceholderTags(List<InternalObject> objects, CancellationToken cancellationToken)
{
// Calculate indentation levels in front of the tags:
int[] indentationBeforeTags = new int[objects.Count];
int pos = 0;
for (int i = 0; i < objects.Count; i++) {
if (objects[i] is InternalTag)
indentationBeforeTags[i] = GetIndentationBefore(pos);
pos += objects[i].Length;
}
// Create initial configuration:
ConfigurationList listA = ConfigurationList.Create();
ConfigurationList listB = ConfigurationList.Create();
listA.Add(new Configuration(new OpenTagStack(), ImmutableStack<InternalObject>.Empty, 0));
for (int i = 0; i < indentationBeforeTags.Length; i++) {
cancellationToken.ThrowIfCancellationRequested();
ProcessObject(objects[i], indentationBeforeTags[i], listA, ref listB);
Swap(ref listA, ref listB);
}
Configuration cheapestConfiguration = new Configuration(null, null, InfiniteCost);
for (int i = 0; i < listA.count; i++) {
Configuration c = listA.configurations[i];
if (c.Cost < cheapestConfiguration.Cost) {
while (!c.OpenTags.IsEmpty) {
c = new Configuration(c.OpenTags.Pop(), c.Document.Push(EndTagPlaceholder), c.Cost + MissingEndTagCost);
}
if (c.Cost < cheapestConfiguration.Cost)
cheapestConfiguration = c;
}
}
Log.WriteLine("Best configuration has cost {0}", cheapestConfiguration.Cost);
return cheapestConfiguration.Document;
}
static void Swap(ref ConfigurationList a, ref ConfigurationList b)
{
ConfigurationList tmp = a;
a = b;
b = tmp;
}
void ProcessObject(InternalObject obj, int indentationLevel, ConfigurationList oldConfigurations, ref ConfigurationList newConfigurations)
{
newConfigurations.Clear();
InternalTag tag = obj as InternalTag;
for (int i = 0; i < oldConfigurations.count; i++) {
Configuration c = oldConfigurations.configurations[i];
if (c.Cost == InfiniteCost)
continue;
if (tag != null && tag.IsStartTag) {
// Push start tag
newConfigurations.Add(
c.OpenTags.Push(tag.Name, indentationLevel),
c.Document.Push(obj),
c.Cost
);
} else if (tag != null && tag.IsEndTag) {
// We can ignore this end tag
newConfigurations.Add(
c.OpenTags,
c.Document.Push(StartTagPlaceholder).Push(obj),
c.Cost + IgnoreEndTagCost
);
// We can match this end tag with one of the currently open tags
var openTags = c.OpenTags;
var documentWithInsertedEndTags = c.Document;
uint newCost = c.Cost;
while (!openTags.IsEmpty) {
uint matchCost = 0;
if (openTags.IndentationLevel >= 0 && indentationLevel >= 0)
matchCost += (uint)Math.Abs(openTags.IndentationLevel - indentationLevel);
if (openTags.Name != tag.Name)
matchCost += MismatchedNameCost;
newConfigurations.Add(
openTags.Pop(),
documentWithInsertedEndTags.Push(obj),
newCost + matchCost
);
newCost += MissingEndTagCost;
openTags = openTags.Pop();
documentWithInsertedEndTags = documentWithInsertedEndTags.Push(EndTagPlaceholder);
}
} else {
newConfigurations.Add(
c.OpenTags,
c.Document.Push(obj),
c.Cost
);
}
}
}
#endregion
#region Create Elements from stack with place holders
List<InternalObject> CreateElements(ref ImmutableStack<InternalObject> inputObjects)
{
List<InternalObject> objects = new List<InternalObject>();
while (!inputObjects.IsEmpty) {
var obj = inputObjects.Peek();
var tag = obj as InternalTag;
if (tag != null && tag.IsStartTag)
break;
inputObjects = inputObjects.Pop();
if (tag != null && tag.IsEndTag) {
if (inputObjects.Peek() == StartTagPlaceholder) {
objects.Add(tag.AddSyntaxError("Matching opening tag was not found"));
inputObjects = inputObjects.Pop();
} else {
var childElements = CreateElements(ref inputObjects);
var startTag = (InternalTag)inputObjects.Peek();
inputObjects = inputObjects.Pop();
childElements.Add(startTag);
childElements.Reverse();
if (tag != EndTagPlaceholder) {
// add end tag
if (startTag.Name != tag.Name) {
childElements.Add(tag.AddSyntaxError("Expected '</" + startTag.Name + ">'. End tag must have same name as start tag."));
} else {
childElements.Add(tag);
}
}
InternalElement e = new InternalElement(startTag);
e.HasEndTag = (tag != EndTagPlaceholder);
e.NestedObjects = new InternalObject[childElements.Count];
int pos = 0;
for (int i = 0; i < childElements.Count; i++) {
e.NestedObjects[i] = childElements[i].SetStartRelativeToParent(pos);
pos += e.NestedObjects[i].Length;
}
e.Length = pos;
if (tag == EndTagPlaceholder) {
e.SyntaxErrors = new [] { new InternalSyntaxError(pos, pos, "Missing '</" + startTag.Name + ">'") };
}
objects.Add(e);
}
} else {
objects.Add(obj);
}
}
return objects;
}
#endregion
}
}

834
ICSharpCode.Decompiler/Xml/TagReader.cs

@ -0,0 +1,834 @@ @@ -0,0 +1,834 @@
// 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.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;
namespace ICSharpCode.Decompiler.Xml
{
class TagReader : TokenReader
{
readonly AXmlParser tagSoupParser;
readonly Stack<string> elementNameStack;
public TagReader(AXmlParser tagSoupParser, ITextSource input, bool collapseProperlyNestedElements) : base(input)
{
this.tagSoupParser = tagSoupParser;
if (collapseProperlyNestedElements)
elementNameStack = new Stack<string>();
}
public List<InternalObject> ReadAllObjects(CancellationToken cancellationToken)
{
while (HasMoreData()) {
cancellationToken.ThrowIfCancellationRequested();
ReadObject();
}
return objects;
}
public List<InternalObject> ReadAllObjectsIncremental(InternalObject[] oldObjects, List<UnchangedSegment> reuseMap, CancellationToken cancellationToken)
{
ObjectIterator oldObjectIterator = new ObjectIterator(oldObjects);
int reuseMapIndex = 0;
while (reuseMapIndex < reuseMap.Count) {
var reuseEntry = reuseMap[reuseMapIndex];
while (this.CurrentLocation < reuseEntry.NewOffset) {
cancellationToken.ThrowIfCancellationRequested();
ReadObject();
}
if (this.CurrentLocation >= reuseEntry.NewOffset + reuseEntry.Length) {
reuseMapIndex++;
continue;
}
Debug.Assert(reuseEntry.NewOffset <= this.CurrentLocation && this.CurrentLocation < reuseEntry.NewOffset + reuseEntry.Length);
// reuse the nodes within this reuseEntry starting at oldOffset:
int oldOffset = this.CurrentLocation - reuseEntry.NewOffset + reuseEntry.OldOffset;
// seek to oldOffset in the oldObjects array:
oldObjectIterator.SkipTo(oldOffset);
if (oldObjectIterator.CurrentPosition == oldOffset) {
// reuse old objects within this reuse entry:
int reuseEnd = reuseEntry.OldOffset + reuseEntry.Length;
while (oldObjectIterator.CurrentObject != null && oldObjectIterator.CurrentPosition + oldObjectIterator.CurrentObject.LengthTouched < reuseEnd) {
StoreObject(oldObjectIterator.CurrentObject);
Skip(oldObjectIterator.CurrentObject.Length);
oldObjectIterator.MoveNext();
}
reuseMapIndex++; // go to next re-use map
} else {
// We are in a region where old objects are available, but aren't aligned correctly.
// Don't skip this reuse entry, and read a single object so that we can re-align
ReadObject();
}
}
while (HasMoreData()) {
cancellationToken.ThrowIfCancellationRequested();
ReadObject();
}
return objects;
}
void StoreObject(InternalObject obj)
{
objects.Add(obj);
// Now combine properly-nested elements:
if (elementNameStack == null)
return; // parsing tag soup
InternalTag tag = obj as InternalTag;
if (tag == null)
return;
if (tag.IsEmptyTag) {
// the tag is its own element
objects[objects.Count - 1] = new InternalElement(tag) {
Length = tag.Length,
LengthTouched = tag.LengthTouched,
IsPropertyNested = true,
StartRelativeToParent = tag.StartRelativeToParent,
NestedObjects = new [] { tag.SetStartRelativeToParent(0) }
};
} else if (tag.IsStartTag) {
elementNameStack.Push(tag.Name);
} else if (tag.IsEndTag && elementNameStack.Count > 0) {
// Now look for the start element:
int startIndex = objects.Count - 2;
bool ok = false;
string expectedName = elementNameStack.Pop();
if (tag.Name == expectedName) {
while (startIndex > 0) {
var startTag = objects[startIndex] as InternalTag;
if (startTag != null) {
if (startTag.IsStartTag) {
ok = (startTag.Name == expectedName);
break;
} else if (startTag.IsEndTag) {
break;
}
}
startIndex--;
}
}
if (ok) {
// We found a correct nesting, let's create an element:
InternalObject[] nestedObjects = new InternalObject[objects.Count - startIndex];
int oldStartRelativeToParent = objects[startIndex].StartRelativeToParent;
int pos = 0;
int maxLengthTouched = 0;
for (int i = 0; i < nestedObjects.Length; i++) {
nestedObjects[i] = objects[startIndex + i].SetStartRelativeToParent(pos);
maxLengthTouched = Math.Max(maxLengthTouched, pos + nestedObjects[i].LengthTouched);
pos += nestedObjects[i].Length;
}
objects.RemoveRange(startIndex, nestedObjects.Length);
objects.Add(
new InternalElement((InternalTag)nestedObjects[0]) {
HasEndTag = true,
IsPropertyNested = true,
Length = pos,
LengthTouched = maxLengthTouched,
StartRelativeToParent = oldStartRelativeToParent,
NestedObjects = nestedObjects
});
} else {
// Mismatched name - the nesting isn't properly;
// clear the whole stack so that none of the currently open elements are closed as properly-nested.
elementNameStack.Clear();
}
}
}
/// <summary>
/// Reads one or more objects.
/// </summary>
void ReadObject()
{
if (TryPeek('<')) {
ReadTag();
} else {
ReadText(TextType.CharacterData);
}
}
#region BeginInternalObject / EndInternalObject
List<InternalObject> objects = new List<InternalObject>();
int internalObjectStartPosition;
int CurrentRelativeLocation {
get { return CurrentLocation - internalObjectStartPosition; }
}
struct InternalObjectFrame
{
public readonly InternalObject InternalObject;
public readonly int ParentStartPosition;
public InternalObjectFrame(InternalObject internalObject, int parentStartPosition)
{
this.InternalObject = internalObject;
this.ParentStartPosition = parentStartPosition;
}
}
InternalObjectFrame BeginInternalObject(InternalObject internalObject)
{
return BeginInternalObject(internalObject, this.CurrentLocation);
}
InternalObjectFrame BeginInternalObject(InternalObject internalObject, int beginLocation)
{
internalObject.StartRelativeToParent = beginLocation - internalObjectStartPosition;
var frame = new InternalObjectFrame(internalObject, internalObjectStartPosition);
internalObjectStartPosition = CurrentLocation;
return frame;
}
void EndInternalObject(InternalObjectFrame frame, bool storeNewObject = true)
{
frame.InternalObject.Length = this.CurrentLocation - internalObjectStartPosition;
frame.InternalObject.LengthTouched = this.MaxTouchedLocation - internalObjectStartPosition;
frame.InternalObject.SyntaxErrors = GetSyntaxErrors();
if (storeNewObject)
StoreObject(frame.InternalObject);
internalObjectStartPosition = frame.ParentStartPosition;
}
#endregion
#region Read Tag
/// <summary>
/// Context: "&lt;"
/// </summary>
void ReadTag()
{
AssertHasMoreData();
int tagStart = this.CurrentLocation;
InternalTag tag = new InternalTag();
var frame = BeginInternalObject(tag);
// Read the opening bracket
// It identifies the type of tag and parsing behavior for the rest of it
tag.OpeningBracket = ReadOpeningBracket();
if (tag.IsUnknownBang && !TryPeekWhiteSpace())
OnSyntaxError(tagStart, this.CurrentLocation, "Unknown tag");
if (tag.IsStartOrEmptyTag || tag.IsEndTag || tag.IsProcessingInstruction) {
// Read the name
TryMoveToNonWhiteSpace();
tag.RelativeNameStart = this.CurrentRelativeLocation;
string name;
if (TryReadName(out name)) {
if (!IsValidName(name)) {
OnSyntaxError(this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name);
}
} else {
OnSyntaxError("Element name expected");
}
tag.Name = name;
} else {
tag.Name = string.Empty;
}
bool isXmlDeclr = tag.Name == "xml" && tag.IsProcessingInstruction;
int oldObjectCount = objects.Count;
if (tag.IsStartOrEmptyTag || tag.IsEndTag || isXmlDeclr) {
// Read attributes for the tag
while (HasMoreData()) {
// Chech for all forbiden 'name' characters first - see ReadName
TryMoveToNonWhiteSpace();
if (TryPeek('<')) break;
string endBr;
int endBrStart = this.CurrentLocation; // Just peek
if (TryReadClosingBracket(out endBr)) { // End tag
GoBack(endBrStart);
break;
}
// We have "=\'\"" or name - read attribute
int attrStartOffset = this.CurrentLocation;
ReadAttribute();
if (tag.IsEndTag)
OnSyntaxError(attrStartOffset, this.CurrentLocation, "Attribute not allowed in end tag.");
}
} else if (tag.IsDocumentType) {
ReadContentOfDTD();
} else {
int start = this.CurrentLocation;
if (tag.IsComment) {
ReadText(TextType.Comment);
} else if (tag.IsCData) {
ReadText(TextType.CData);
} else if (tag.IsProcessingInstruction) {
ReadText(TextType.ProcessingInstruction);
} else if (tag.IsUnknownBang) {
ReadText(TextType.UnknownBang);
} else {
throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket));
}
// Backtrack at complete start
if (IsEndOfFile() || (tag.IsUnknownBang && TryPeek('<'))) {
GoBack(start);
objects.RemoveRange(oldObjectCount, objects.Count - oldObjectCount);
}
}
// Read closing bracket
string bracket;
TryReadClosingBracket(out bracket);
tag.ClosingBracket = bracket;
// Error check
int brStart = this.CurrentLocation - (tag.ClosingBracket ?? string.Empty).Length;
int brEnd = this.CurrentLocation;
if (tag.Name == null) {
// One error was reported already
} else if (tag.IsStartOrEmptyTag) {
if (tag.ClosingBracket != ">" && tag.ClosingBracket != "/>") OnSyntaxError(brStart, brEnd, "'>' or '/>' expected");
} else if (tag.IsEndTag) {
if (tag.ClosingBracket != ">") OnSyntaxError(brStart, brEnd, "'>' expected");
} else if (tag.IsComment) {
if (tag.ClosingBracket != "-->") OnSyntaxError(brStart, brEnd, "'-->' expected");
} else if (tag.IsCData) {
if (tag.ClosingBracket != "]]>") OnSyntaxError(brStart, brEnd, "']]>' expected");
} else if (tag.IsProcessingInstruction) {
if (tag.ClosingBracket != "?>") OnSyntaxError(brStart, brEnd, "'?>' expected");
} else if (tag.IsUnknownBang) {
if (tag.ClosingBracket != ">") OnSyntaxError(brStart, brEnd, "'>' expected");
} else if (tag.IsDocumentType) {
if (tag.ClosingBracket != ">") OnSyntaxError(brStart, brEnd, "'>' expected");
} else {
throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket));
}
// Attribute name may not apper multiple times
if (objects.Count > oldObjectCount) {
// Move nested objects into tag.NestedObjects:
tag.NestedObjects = new InternalObject[objects.Count - oldObjectCount];
objects.CopyTo(oldObjectCount, tag.NestedObjects, 0, tag.NestedObjects.Length);
objects.RemoveRange(oldObjectCount, objects.Count - oldObjectCount);
// Look for duplicate attributes:
HashSet<string> attributeNames = new HashSet<string>();
foreach (var obj in tag.NestedObjects) {
InternalAttribute attr = obj as InternalAttribute;
if (attr != null && !attributeNames.Add(attr.Name)) {
int attrStart = tagStart + attr.StartRelativeToParent;
OnSyntaxError(attrStart, attrStart + attr.Name.Length, "Attribute with name '{0}' already exists", attr.Name);
}
}
}
EndInternalObject(frame);
}
#endregion
#region Read DTD
void ReadContentOfDTD()
{
int start = this.CurrentLocation;
while (HasMoreData()) {
TryMoveToNonWhiteSpace(); // Skip whitespace
if (TryRead('\'')) TryMoveTo('\''); // Skip single quoted string TODO: Bug
if (TryRead('\"')) TryMoveTo('\"'); // Skip single quoted string
if (TryRead('[')) { // Start of nested infoset
// Reading infoset
while (HasMoreData()) {
TryMoveToAnyOf('<', ']');
if (TryPeek('<')) {
if (start != this.CurrentLocation) { // Two following tags
MakeText(start, this.CurrentLocation);
}
ReadTag();
start = this.CurrentLocation;
}
if (TryPeek(']')) break;
}
}
TryRead(']'); // End of nested infoset
if (TryPeek('>')) break; // Proper closing
if (TryPeek('<')) break; // Malformed XML
TryMoveNext(); // Skip anything else
}
if (start != this.CurrentLocation) {
MakeText(start, this.CurrentLocation);
}
}
void MakeText(int start, int end)
{
Log.DebugAssert(end > start, "Empty text");
Log.DebugAssert(end == this.CurrentLocation, "end == current location");
InternalText text = new InternalText();
var frame = BeginInternalObject(text, start);
text.Type = TextType.Other;
text.Value = GetText(start, end);
EndInternalObject(frame);
}
#endregion
#region Read Brackets
/// <summary>
/// Reads any of the know opening brackets. (only full bracket)
/// Context: "&lt;"
/// </summary>
string ReadOpeningBracket()
{
// We are using a lot of string literals so that the memory instances are shared
//int start = this.CurrentLocation;
if (TryRead('<')) {
if (TryRead('/')) {
return "</";
} else if (TryRead('?')) {
return "<?";
} else if (TryRead('!')) {
if (TryRead("--")) {
return "<!--";
} else if (TryRead("[CDATA[")) {
return "<![CDATA[";
} else {
foreach (string dtdName in AXmlTag.DtdNames) {
// the dtdName includes "<!"
if (TryRead(dtdName.Remove(0, 2))) return dtdName;
}
return "<!";
}
} else {
return "<";
}
} else {
throw new InternalException("'<' expected");
}
}
/// <summary>
/// Reads any of the know closing brackets. (only full bracket)
/// Context: any
/// </summary>
bool TryReadClosingBracket(out string bracket)
{
// We are using a lot of string literals so that the memory instances are shared
if (TryRead('>')) {
bracket = ">";
} else if (TryRead("/>")) {
bracket = "/>";
} else if (TryRead("?>")) {
bracket = "?>";
} else if (TryRead("-->")) {
bracket = "-->";
} else if (TryRead("]]>")) {
bracket = "]]>";
} else {
bracket = string.Empty;
return false;
}
return true;
}
#endregion
#region Attributes
/// <summary>
/// Context: name or "=\'\""
/// </summary>
void ReadAttribute()
{
AssertHasMoreData();
InternalAttribute attr = new InternalAttribute();
var frame = BeginInternalObject(attr);
// Read name
string name;
if (TryReadName(out name)) {
if (!IsValidName(name)) {
OnSyntaxError(this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name);
}
} else {
OnSyntaxError("Attribute name expected");
}
attr.Name = name;
// Read equals sign and surrounding whitespace
int checkpoint = this.CurrentLocation;
TryMoveToNonWhiteSpace();
if (TryRead('=')) {
int chk2 = this.CurrentLocation;
TryMoveToNonWhiteSpace();
if (!TryPeek('"') && !TryPeek('\'')) {
// Do not read whitespace if quote does not follow
GoBack(chk2);
}
attr.EqualsSignLength = this.CurrentLocation - checkpoint;
} else {
GoBack(checkpoint);
OnSyntaxError("'=' expected");
attr.EqualsSignLength = 0;
}
// Read attribute value
int start = this.CurrentLocation;
char quoteChar = TryPeek('"') ? '"' : '\'';
bool startsWithQuote;
if (TryRead(quoteChar)) {
startsWithQuote = true;
int valueStart = this.CurrentLocation;
TryMoveToAnyOf(quoteChar, '<');
if (TryRead(quoteChar)) {
if (!TryPeekAnyOf(' ', '\t', '\n', '\r', '/', '>', '?')) {
if (TryPeekPrevious('=', 2) || (TryPeekPrevious('=', 3) && TryPeekPrevious(' ', 2))) {
// This actually most likely means that we are in the next attribute value
GoBack(valueStart);
ReadAttributeValue(quoteChar);
if (TryRead(quoteChar)) {
OnSyntaxError("White space or end of tag expected");
} else {
OnSyntaxError("Quote {0} expected (or add whitespace after the following one)", quoteChar);
}
} else {
OnSyntaxError("White space or end of tag expected");
}
}
} else {
// '<' or end of file
GoBack(valueStart);
ReadAttributeValue(quoteChar);
OnSyntaxError("Quote {0} expected", quoteChar);
}
} else {
startsWithQuote = false;
int valueStart = this.CurrentLocation;
ReadAttributeValue(null);
TryRead('\"');
TryRead('\'');
if (valueStart == this.CurrentLocation) {
OnSyntaxError("Attribute value expected");
} else {
OnSyntaxError(valueStart, this.CurrentLocation, "Attribute value must be quoted");
}
}
string val = GetText(start, this.CurrentLocation);
val = Unquote(val);
attr.Value = Dereference(val, startsWithQuote ? start + 1 : start);
EndInternalObject(frame);
}
/// <summary>
/// Read everything up to quote (excluding), opening/closing tag or attribute signature
/// </summary>
void ReadAttributeValue(char? quote)
{
while (HasMoreData()) {
// What is next?
int start = this.CurrentLocation;
TryMoveToNonWhiteSpace(); // Read white space (if any)
if (quote.HasValue) {
if (TryPeek(quote.Value)) return;
} else {
if (TryPeek('"') || TryPeek('\'')) return;
}
// Opening/closing tag
string endBr;
if (TryPeek('<') || TryReadClosingBracket(out endBr)) {
GoBack(start);
return;
}
// Try reading attribute signature
if (TryReadName()) {
int nameEnd = this.CurrentLocation;
if (TryMoveToNonWhiteSpace() && TryRead("=") &&
TryMoveToNonWhiteSpace() && TryPeekAnyOf('"', '\''))
{
// Start of attribute. Great
GoBack(start);
return; // Done
} else {
// Just some gargabe - make it part of the value
GoBack(nameEnd);
continue; // Read more
}
}
TryMoveNext(); // Accept everyting else
}
}
/// <summary> Remove quoting from the given string </summary>
static string Unquote(string quoted)
{
if (string.IsNullOrEmpty(quoted)) return string.Empty;
char first = quoted[0];
if (quoted.Length == 1) return (first == '"' || first == '\'') ? string.Empty : quoted;
char last = quoted[quoted.Length - 1];
if (first == '"' || first == '\'') {
if (first == last) {
// Remove both quotes
return quoted.Substring(1, quoted.Length - 2);
} else {
// Remove first quote
return quoted.Remove(0, 1);
}
} else {
if (last == '"' || last == '\'') {
// Remove last quote
return quoted.Substring(0, quoted.Length - 1);
} else {
// Keep whole string
return quoted;
}
}
}
#endregion
#region Text
/// <summary>
/// Reads text.
/// </summary>
void ReadText(TextType type)
{
var text = new InternalText();
var frame = BeginInternalObject(text);
text.Type = type;
int start = this.CurrentLocation;
int fragmentEnd = inputLength;
// Whitespace would be skipped anyway by any operation
TryMoveToNonWhiteSpace(fragmentEnd);
int wsEnd = this.CurrentLocation;
// Try move to the terminator given by the context
if (type == TextType.WhiteSpace) {
TryMoveToNonWhiteSpace(fragmentEnd);
} else if (type == TextType.CharacterData) {
while(true) {
if (!TryMoveToAnyOf(new char[] {'<', ']'}, fragmentEnd)) break; // End of fragment
if (TryPeek('<')) break;
if (TryPeek(']')) {
if (TryPeek("]]>")) {
OnSyntaxError(this.CurrentLocation, this.CurrentLocation + 3, "']]>' is not allowed in text");
}
TryMoveNext();
continue;
}
throw new InternalException("Infinite loop");
}
} else if (type == TextType.Comment) {
// Do not report too many errors
bool errorReported = false;
while(true) {
if (!TryMoveTo('-', fragmentEnd)) break; // End of fragment
if (TryPeek("-->")) break;
if (TryPeek("--") && !errorReported) {
OnSyntaxError(this.CurrentLocation, this.CurrentLocation + 2, "'--' is not allowed in comment");
errorReported = true;
}
TryMoveNext();
}
} else if (type == TextType.CData) {
while(true) {
// We can not use use TryMoveTo("]]>", fragmentEnd) because it may incorectly accept "]" at the end of fragment
if (!TryMoveTo(']', fragmentEnd)) break; // End of fragment
if (TryPeek("]]>")) break;
TryMoveNext();
}
} else if (type == TextType.ProcessingInstruction) {
while(true) {
if (!TryMoveTo('?', fragmentEnd)) break; // End of fragment
if (TryPeek("?>")) break;
TryMoveNext();
}
} else if (type == TextType.UnknownBang) {
TryMoveToAnyOf(new char[] {'<', '>'}, fragmentEnd);
} else {
throw new InternalException("Unknown type " + type);
}
text.ContainsOnlyWhitespace = (wsEnd == this.CurrentLocation);
string escapedValue = GetText(start, this.CurrentLocation);
if (type == TextType.CharacterData) {
text.Value = Dereference(escapedValue, start);
} else {
text.Value = escapedValue;
}
text.Value = GetCachedString(text.Value);
EndInternalObject(frame, storeNewObject: this.CurrentLocation > start);
}
#endregion
#region Dereference
const int maxEntityLength = 16; // The longest built-in one is 10 ("&#1114111;")
string Dereference(string text, int textLocation)
{
StringBuilder sb = null; // The dereferenced text so far (all up to 'curr')
int curr = 0;
while(true) {
// Reached end of input
if (curr == text.Length) {
if (sb != null) {
return sb.ToString();
} else {
return text;
}
}
// Try to find reference
int start = text.IndexOf('&', curr);
// No more references found
if (start == -1) {
if (sb != null) {
sb.Append(text, curr, text.Length - curr); // Add rest
return sb.ToString();
} else {
return text;
}
}
// Append text before the enitiy reference
if (sb == null) sb = new StringBuilder(text.Length);
sb.Append(text, curr, start - curr);
curr = start;
// Process the entity
int errorLoc = textLocation + sb.Length;
// Find entity name
int end = text.IndexOfAny(new char[] {'&', ';'}, start + 1, Math.Min(maxEntityLength, text.Length - (start + 1)));
if (end == -1 || text[end] == '&') {
// Not found
OnSyntaxError(errorLoc, errorLoc + 1, "Entity reference must be terminated with ';'");
// Keep '&'
sb.Append('&');
curr++;
continue; // Restart and next character location
}
string name = text.Substring(start + 1, end - (start + 1));
// Resolve the name
string replacement;
if (name.Length == 0) {
replacement = null;
OnSyntaxError(errorLoc + 1, errorLoc + 1, "Entity name expected");
} else if (name == "amp") {
replacement = "&";
} else if (name == "lt") {
replacement = "<";
} else if (name == "gt") {
replacement = ">";
} else if (name == "apos") {
replacement = "'";
} else if (name == "quot") {
replacement = "\"";
} else if (name.Length > 0 && name[0] == '#') {
int num;
if (name.Length > 1 && name[1] == 'x') {
if (!int.TryParse(name.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat, out num)) {
num = -1;
OnSyntaxError(errorLoc + 3, errorLoc + 1 + name.Length, "Hexadecimal code of unicode character expected");
}
} else {
if (!int.TryParse(name.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out num)) {
num = -1;
OnSyntaxError(errorLoc + 2, errorLoc + 1 + name.Length, "Numeric code of unicode character expected");
}
}
if (num != -1) {
try {
replacement = char.ConvertFromUtf32(num);
} catch (ArgumentOutOfRangeException) {
replacement = null;
OnSyntaxError(errorLoc + 2, errorLoc + 1 + name.Length, "Invalid unicode character U+{0:X} ({0})", num);
}
} else {
replacement = null;
}
} else if (!IsValidName(name)) {
replacement = null;
OnSyntaxError(errorLoc + 1, errorLoc + 1, "Invalid entity name");
} else {
replacement = null;
if (tagSoupParser.UnknownEntityReferenceIsError) {
OnSyntaxError(errorLoc, errorLoc + 1 + name.Length + 1, "Unknown entity reference '{0}'", name);
}
}
// Append the replacement to output
if (replacement != null) {
sb.Append(replacement);
} else {
sb.Append('&');
sb.Append(name);
sb.Append(';');
}
curr = end + 1;
continue;
}
}
#endregion
#region Syntax Errors
List<InternalSyntaxError> syntaxErrors = new List<InternalSyntaxError>();
InternalSyntaxError[] GetSyntaxErrors()
{
if (syntaxErrors.Count > 0) {
var arr = syntaxErrors.ToArray();
syntaxErrors.Clear();
return arr;
} else {
return null;
}
}
void OnSyntaxError(string message, params object[] args)
{
OnSyntaxError(this.CurrentLocation, this.CurrentLocation + 1, message, args);
}
void OnSyntaxError(int start, int end, string message, params object[] args)
{
if (end <= start) end = start + 1;
string formattedMessage = string.Format(CultureInfo.InvariantCulture, message, args);
Log.WriteLine("Syntax error ({0}-{1}): {2}", start, end, formattedMessage);
syntaxErrors.Add(new InternalSyntaxError(start - internalObjectStartPosition, end - internalObjectStartPosition, formattedMessage));
}
#endregion
#region Helper functions
internal static bool IsValidName(string name)
{
try {
System.Xml.XmlConvert.VerifyName(name);
return true;
} catch (System.Xml.XmlException) {
return false;
}
}
#endregion
}
}

100
ICSharpCode.Decompiler/Xml/TextChangeEventArgs.cs

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
using System;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// Describes a change of the document text.
/// This class is thread-safe.
/// </summary>
[Serializable]
public class TextChangeEventArgs : EventArgs
{
readonly int offset;
readonly ITextSource removedText;
readonly ITextSource insertedText;
/// <summary>
/// The offset at which the change occurs.
/// </summary>
public int Offset {
get { return offset; }
}
/// <summary>
/// The text that was removed.
/// </summary>
public ITextSource RemovedText {
get { return removedText; }
}
/// <summary>
/// The number of characters removed.
/// </summary>
public int RemovalLength {
get { return removedText.TextLength; }
}
/// <summary>
/// The text that was inserted.
/// </summary>
public ITextSource InsertedText {
get { return insertedText; }
}
/// <summary>
/// The number of characters inserted.
/// </summary>
public int InsertionLength {
get { return insertedText.TextLength; }
}
/// <summary>
/// Creates a new TextChangeEventArgs object.
/// </summary>
public TextChangeEventArgs(int offset, string removedText, string insertedText)
{
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", offset, "offset must not be negative");
this.offset = offset;
this.removedText = removedText != null ? new StringTextSource(removedText) : StringTextSource.Empty;
this.insertedText = insertedText != null ? new StringTextSource(insertedText) : StringTextSource.Empty;
}
/// <summary>
/// Creates a new TextChangeEventArgs object.
/// </summary>
public TextChangeEventArgs(int offset, ITextSource removedText, ITextSource insertedText)
{
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", offset, "offset must not be negative");
this.offset = offset;
this.removedText = removedText ?? StringTextSource.Empty;
this.insertedText = insertedText ?? StringTextSource.Empty;
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public virtual int GetNewOffset(int offset, AnchorMovementType movementType = AnchorMovementType.Default)
{
if (offset >= this.Offset && offset <= this.Offset + this.RemovalLength) {
if (movementType == AnchorMovementType.BeforeInsertion)
return this.Offset;
else
return this.Offset + this.InsertionLength;
} else if (offset > this.Offset) {
return offset + this.InsertionLength - this.RemovalLength;
} else {
return offset;
}
}
/// <summary>
/// Creates TextChangeEventArgs for the reverse change.
/// </summary>
public virtual TextChangeEventArgs Invert()
{
return new TextChangeEventArgs(offset, insertedText, removedText);
}
}
}

223
ICSharpCode.Decompiler/Xml/TextLocation.cs

@ -0,0 +1,223 @@ @@ -0,0 +1,223 @@
// 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.ComponentModel;
using System.Globalization;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary>
/// A line/column position.
/// Text editor lines/columns are counted started from one.
/// </summary>
/// <remarks>
/// The document provides the methods <see cref="Editor.IDocument.GetLocation"/> and
/// <see cref="Editor.IDocument.GetOffset(TextLocation)"/> to convert between offsets and TextLocations.
/// </remarks>
[Serializable]
[TypeConverter(typeof(TextLocationConverter))]
public struct TextLocation : IComparable<TextLocation>, IEquatable<TextLocation>
{
/// <summary>
/// Represents no text location (0, 0).
/// </summary>
public static readonly TextLocation Empty = new TextLocation(0, 0);
/// <summary>
/// Constant of the minimum line.
/// </summary>
public const int MinLine = 1;
/// <summary>
/// Constant of the minimum column.
/// </summary>
public const int MinColumn = 1;
/// <summary>
/// Creates a TextLocation instance.
/// </summary>
public TextLocation(int line, int column)
{
this.line = line;
this.column = column;
}
int column, line;
/// <summary>
/// Gets the line number.
/// </summary>
public int Line {
get { return line; }
}
/// <summary>
/// Gets the column number.
/// </summary>
public int Column {
get { return column; }
}
/// <summary>
/// Gets whether the TextLocation instance is empty.
/// </summary>
public bool IsEmpty {
get {
return column < MinLine && line < MinColumn;
}
}
/// <summary>
/// Gets a string representation for debugging purposes.
/// </summary>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "(Line {1}, Col {0})", this.column, this.line);
}
/// <summary>
/// Gets a hash code.
/// </summary>
public override int GetHashCode()
{
return unchecked(191 * column.GetHashCode() ^ line.GetHashCode());
}
/// <summary>
/// Equality test.
/// </summary>
public override bool Equals(object obj)
{
if (!(obj is TextLocation)) return false;
return (TextLocation)obj == this;
}
/// <summary>
/// Equality test.
/// </summary>
public bool Equals(TextLocation other)
{
return this == other;
}
/// <summary>
/// Equality test.
/// </summary>
public static bool operator ==(TextLocation left, TextLocation right)
{
return left.column == right.column && left.line == right.line;
}
/// <summary>
/// Inequality test.
/// </summary>
public static bool operator !=(TextLocation left, TextLocation right)
{
return left.column != right.column || left.line != right.line;
}
/// <summary>
/// Compares two text locations.
/// </summary>
public static bool operator <(TextLocation left, TextLocation right)
{
if (left.line < right.line)
return true;
else if (left.line == right.line)
return left.column < right.column;
else
return false;
}
/// <summary>
/// Compares two text locations.
/// </summary>
public static bool operator >(TextLocation left, TextLocation right)
{
if (left.line > right.line)
return true;
else if (left.line == right.line)
return left.column > right.column;
else
return false;
}
/// <summary>
/// Compares two text locations.
/// </summary>
public static bool operator <=(TextLocation left, TextLocation right)
{
return !(left > right);
}
/// <summary>
/// Compares two text locations.
/// </summary>
public static bool operator >=(TextLocation left, TextLocation right)
{
return !(left < right);
}
/// <summary>
/// Compares two text locations.
/// </summary>
public int CompareTo(TextLocation other)
{
if (this == other)
return 0;
if (this < other)
return -1;
else
return 1;
}
}
public class TextLocationConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(TextLocation) || base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string) {
string[] parts = ((string)value).Split(';', ',');
if (parts.Length == 2) {
return new TextLocation(int.Parse(parts[0]), int.Parse(parts[1]));
}
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value is TextLocation) {
var loc = (TextLocation)value;
return loc.Line + ";" + loc.Column;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}

47
ICSharpCode.Decompiler/Xml/TextType.cs

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
// 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;
namespace ICSharpCode.Decompiler.Xml
{
/// <summary> Identifies the context in which the text occured </summary>
enum TextType
{
/// <summary> Ends with non-whitespace </summary>
WhiteSpace,
/// <summary> Ends with "&lt;"; "]]&gt;" is error </summary>
CharacterData,
/// <summary> Ends with "-->"; "--" is error </summary>
Comment,
/// <summary> Ends with "]]&gt;" </summary>
CData,
/// <summary> Ends with "?>" </summary>
ProcessingInstruction,
/// <summary> Ends with "&lt;" or ">" </summary>
UnknownBang,
/// <summary> Unknown </summary>
Other
}
}

349
ICSharpCode.Decompiler/Xml/TokenReader.cs

@ -0,0 +1,349 @@ @@ -0,0 +1,349 @@
// 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.Diagnostics;
using System.Linq;
namespace ICSharpCode.Decompiler.Xml
{
class TokenReader
{
protected readonly ITextSource input;
protected readonly int inputLength;
int currentLocation;
// CurrentLocation is assumed to be touched and the fact does not
// have to be recorded in this variable.
// This stores any value bigger than that if applicable.
// Actual value is max(currentLocation, maxTouchedLocation).
int maxTouchedLocation;
public int InputLength {
get { return inputLength; }
}
public int CurrentLocation {
get { return currentLocation; }
}
public int MaxTouchedLocation {
// add 1 to currentLocation because single-char-peek does not increment maxTouchedLocation
get { return Math.Max(currentLocation + 1, maxTouchedLocation); }
}
public TokenReader(ITextSource input)
{
this.input = input;
this.inputLength = input.TextLength;
}
protected bool IsEndOfFile()
{
return currentLocation == inputLength;
}
protected bool HasMoreData()
{
return currentLocation < inputLength;
}
protected void AssertHasMoreData()
{
Log.Assert(HasMoreData(), "Unexpected end of file");
}
protected bool TryMoveNext()
{
if (currentLocation == inputLength) return false;
currentLocation++;
return true;
}
protected void Skip(int count)
{
Log.Assert(currentLocation + count <= inputLength, "Skipping after the end of file");
currentLocation += count;
}
protected void GoBack(int oldLocation)
{
Log.Assert(oldLocation <= currentLocation, "Trying to move forward");
// add 1 because single-char-peek does not increment maxTouchedLocation
maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation + 1);
currentLocation = oldLocation;
}
protected bool TryRead(char c)
{
if (currentLocation == inputLength) return false;
if (input.GetCharAt(currentLocation) == c) {
currentLocation++;
return true;
} else {
return false;
}
}
protected bool TryReadAnyOf(params char[] c)
{
if (currentLocation == inputLength) return false;
if (c.Contains(input.GetCharAt(currentLocation))) {
currentLocation++;
return true;
} else {
return false;
}
}
protected bool TryRead(string text)
{
if (TryPeek(text)) {
currentLocation += text.Length;
return true;
} else {
return false;
}
}
protected bool TryPeekPrevious(char c, int back)
{
if (currentLocation - back == inputLength) return false;
if (currentLocation - back < 0 ) return false;
return input.GetCharAt(currentLocation - back) == c;
}
protected bool TryPeek(char c)
{
if (currentLocation == inputLength) return false;
return input.GetCharAt(currentLocation) == c;
}
protected bool TryPeekAnyOf(params char[] chars)
{
if (currentLocation == inputLength) return false;
return chars.Contains(input.GetCharAt(currentLocation));
}
protected bool TryPeek(string text)
{
if (!TryPeek(text[0])) return false; // Early exit
maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation + text.Length);
// The following comparison 'touches' the end of file - it does depend on the end being there
if (currentLocation + text.Length > inputLength) return false;
return input.GetText(currentLocation, text.Length) == text;
}
protected bool TryPeekWhiteSpace()
{
if (currentLocation == inputLength) return false;
char c = input.GetCharAt(currentLocation);
return ((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r');
}
// The move functions do not have to move if already at target
// The move functions allow 'overriding' of the document length
protected bool TryMoveTo(char c)
{
return TryMoveTo(c, inputLength);
}
protected bool TryMoveTo(char c, int inputLength)
{
if (currentLocation == inputLength) return false;
int index = input.IndexOf(c, currentLocation, inputLength - currentLocation);
if (index != -1) {
currentLocation = index;
return true;
} else {
currentLocation = inputLength;
return false;
}
}
protected bool TryMoveToAnyOf(params char[] c)
{
return TryMoveToAnyOf(c, inputLength);
}
protected bool TryMoveToAnyOf(char[] c, int inputLength)
{
if (currentLocation == inputLength) return false;
int index = input.IndexOfAny(c, currentLocation, inputLength - currentLocation);
if (index != -1) {
currentLocation = index;
return true;
} else {
currentLocation = inputLength;
return false;
}
}
protected bool TryMoveTo(string text)
{
return TryMoveTo(text, inputLength);
}
protected bool TryMoveTo(string text, int inputLength)
{
if (currentLocation == inputLength) return false;
int index = input.IndexOf(text, currentLocation, inputLength - currentLocation, StringComparison.Ordinal);
if (index != -1) {
maxTouchedLocation = index + text.Length;
currentLocation = index;
return true;
} else {
currentLocation = inputLength;
return false;
}
}
protected bool TryMoveToNonWhiteSpace()
{
return TryMoveToNonWhiteSpace(inputLength);
}
protected bool TryMoveToNonWhiteSpace(int inputLength)
{
while(true) {
if (currentLocation == inputLength) return false; // Reject end of file
char c = input.GetCharAt(currentLocation);
if (((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
currentLocation++; // Accept white-space
continue;
} else {
return true; // Found non-white-space
}
}
}
/// <summary>
/// Read a name token.
/// The following characters are not allowed:
/// "" End of file
/// " \n\r\t" Whitesapce
/// "=\'\"" Attribute value
/// "&lt;>/?" Tags
/// </summary>
/// <returns>Returns the length of the name</returns>
protected bool TryReadName()
{
int start = currentLocation;
// Keep reading up to invalid character
while (HasMoreData()) {
char c = input.GetCharAt(currentLocation);
if (0x41 <= (int)c) { // Accpet from 'A' onwards
currentLocation++;
continue;
}
if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || // Reject whitesapce
c == '=' || c == '\'' || c == '"' || // Reject attributes
c == '<' || c == '>' || c == '/' || c == '?') { // Reject tags
break;
} else {
currentLocation++;
continue; // Accept other character
}
}
return currentLocation > start;
}
protected bool TryReadName(out string name)
{
int start = currentLocation;
if (TryReadName()) {
name = GetCachedString(GetText(start, currentLocation));
return true;
} else {
name = string.Empty;
return false;
}
}
protected string GetText(int start, int end)
{
Log.Assert(end <= currentLocation, "Reading ahead of current location");
return input.GetText(start, end - start);
}
Dictionary<string, string> stringCache = new Dictionary<string, string>();
#if DEBUG
int stringCacheRequestedCount;
int stringCacheRequestedSize;
int stringCacheStoredCount;
int stringCacheStoredSize;
#endif
internal void PrintStringCacheStats()
{
#if DEBUG
Log.WriteLine("String cache: Requested {0} ({1} bytes); Actaully stored {2} ({3} bytes); {4}% stored", stringCacheRequestedCount, stringCacheRequestedSize, stringCacheStoredCount, stringCacheStoredSize, stringCacheRequestedSize == 0 ? 0 : stringCacheStoredSize * 100 / stringCacheRequestedSize);
#endif
}
[Conditional("DEBUG")]
void AddToRequestedSize(string text)
{
#if DEBUG
stringCacheRequestedCount += 1;
stringCacheRequestedSize += 8 + 2 * text.Length;
#endif
}
[Conditional("DEBUG")]
void AddToStoredSize(string text)
{
#if DEBUG
stringCacheStoredCount += 1;
stringCacheStoredSize += 8 + 2 * text.Length;
#endif
}
protected string GetCachedString(string cached)
{
AddToRequestedSize(cached);
// Do not bother with long strings
if (cached.Length > 32) {
AddToStoredSize(cached);
return cached;
}
string result;
if (stringCache.TryGetValue(cached, out result)) {
// Get the instance from the cache instead
return result;
} else {
// Add to cache
AddToStoredSize(cached);
stringCache.Add(cached, cached);
return cached;
}
}
}
}

47
ICSharpCode.Decompiler/Xml/XmlSegment.cs

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
// 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;
namespace ICSharpCode.Decompiler.Xml
{
sealed class XmlSegment : ISegment
{
readonly int startOffset, endOffset;
public XmlSegment(int startOffset, int endOffset)
{
if (endOffset < startOffset)
throw new ArgumentOutOfRangeException();
this.startOffset = startOffset;
this.endOffset = endOffset;
}
int ISegment.Offset {
get { return startOffset; }
}
int ISegment.Length {
get { return endOffset - startOffset; }
}
int ISegment.EndOffset {
get { return endOffset; }
}
}
}

40
ILSpy/ExtensionMethods.cs

@ -18,6 +18,8 @@ @@ -18,6 +18,8 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using ICSharpCode.ILSpy.Options;
namespace ICSharpCode.ILSpy
@ -161,5 +163,43 @@ namespace ICSharpCode.ILSpy @@ -161,5 +163,43 @@ namespace ICSharpCode.ILSpy
}
return result;
}
#region DPI independence
public static Rect TransformToDevice(this Rect rect, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
return Rect.Transform(rect, matrix);
}
public static Rect TransformFromDevice(this Rect rect, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
return Rect.Transform(rect, matrix);
}
public static Size TransformToDevice(this Size size, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
return new Size(size.Width * matrix.M11, size.Height * matrix.M22);
}
public static Size TransformFromDevice(this Size size, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
return new Size(size.Width * matrix.M11, size.Height * matrix.M22);
}
public static Point TransformToDevice(this Point point, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice;
return matrix.Transform(point);
}
public static Point TransformFromDevice(this Point point, Visual visual)
{
Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice;
return matrix.Transform(point);
}
#endregion
}
}

2
ILSpy/ILSpy.csproj

@ -39,6 +39,7 @@ @@ -39,6 +39,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System.IO.Compression" />
@ -207,6 +208,7 @@ @@ -207,6 +208,7 @@
<Compile Include="TaskHelper.cs" />
<Compile Include="TextView\EditorCommands.cs" />
<Compile Include="TextView\FoldingCommands.cs" />
<Compile Include="TextView\ReadOnlyDocument.cs" />
<Compile Include="TextView\XmlDocRenderer.cs" />
<Compile Include="Analyzers\AnalyzeCommand.cs" />
<Compile Include="Analyzers\Builtin\FieldAccessAnalyzer.cs" />

5
ILSpy/Languages/Language.cs

@ -341,6 +341,11 @@ namespace ICSharpCode.ILSpy @@ -341,6 +341,11 @@ namespace ICSharpCode.ILSpy
return GetDisplayName(entity, true, true, true);
}
public virtual object GetFancyTooltip(IEntity entity)
{
return GetTooltip(entity);
}
public virtual string FieldToString(IField field, bool includeDeclaringTypeName, bool includeNamespace, bool includeNamespaceOfDeclaringTypeName)
{
if (field == null)

238
ILSpy/TextView/DecompilerTextView.cs

@ -27,6 +27,7 @@ using System.Threading; @@ -27,6 +27,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
@ -44,8 +45,10 @@ using ICSharpCode.AvalonEdit.Rendering; @@ -44,8 +45,10 @@ using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Search;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.AvalonEdit;
using ICSharpCode.ILSpy.Options;
@ -63,7 +66,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -63,7 +66,7 @@ namespace ICSharpCode.ILSpy.TextView
{
readonly ReferenceElementGenerator referenceElementGenerator;
readonly UIElementGenerator uiElementGenerator;
List<VisualLineElementGenerator> activeCustomElementGenerators = new List<VisualLineElementGenerator>();
readonly List<VisualLineElementGenerator> activeCustomElementGenerators = new List<VisualLineElementGenerator>();
RichTextColorizer activeRichTextColorizer;
FoldingManager foldingManager;
ILSpyTreeNode[] decompiledNodes;
@ -88,7 +91,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -88,7 +91,7 @@ namespace ICSharpCode.ILSpy.TextView
}
});
HighlightingManager.Instance.RegisterHighlighting(
/*HighlightingManager.Instance.RegisterHighlighting(
"C#", new string[] { ".cs" },
delegate {
using (Stream s = typeof(DecompilerTextView).Assembly.GetManifestResourceStream(typeof(DecompilerTextView), "CSharp-Mode.xshd")) {
@ -96,7 +99,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -96,7 +99,7 @@ namespace ICSharpCode.ILSpy.TextView
return HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
}
});
});*/
InitializeComponent();
@ -109,6 +112,8 @@ namespace ICSharpCode.ILSpy.TextView @@ -109,6 +112,8 @@ namespace ICSharpCode.ILSpy.TextView
textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped;
textEditor.TextArea.PreviewMouseDown += TextAreaMouseDown;
textEditor.TextArea.PreviewMouseUp += TextAreaMouseUp;
textEditor.MouseMove += TextEditorMouseMove;
textEditor.MouseLeave += TextEditorMouseLeave;
textEditor.SetBinding(Control.FontFamilyProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFont") });
textEditor.SetBinding(Control.FontSizeProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("SelectedFontSize") });
textEditor.SetBinding(TextEditor.WordWrapProperty, new Binding { Source = DisplaySettingsPanel.CurrentDisplaySettings, Path = new PropertyPath("EnableWordWrap") });
@ -163,20 +168,18 @@ namespace ICSharpCode.ILSpy.TextView @@ -163,20 +168,18 @@ namespace ICSharpCode.ILSpy.TextView
}
}
}
#endregion
#region Tooltip support
ToolTip tooltip;
void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
{
if (tooltip != null)
tooltip.IsOpen = false;
}
ToolTip toolTip;
Popup popupToolTip;
void TextViewMouseHover(object sender, MouseEventArgs e)
{
if (!TryCloseExistingPopup(false)) {
return;
}
TextViewPosition? position = GetPositionFromMousePosition();
if (position == null)
return;
@ -187,35 +190,166 @@ namespace ICSharpCode.ILSpy.TextView @@ -187,35 +190,166 @@ namespace ICSharpCode.ILSpy.TextView
if (seg == null)
return;
object content = GenerateTooltip(seg);
if (tooltip != null)
tooltip.IsOpen = false;
if (content != null)
tooltip = new ToolTip() { Content = content, IsOpen = true };
if (content != null) {
popupToolTip = content as Popup;
if (popupToolTip != null) {
var popupPosition = GetPopupPosition(e);
popupToolTip.Closed += ToolTipClosed;
popupToolTip.HorizontalOffset = popupPosition.X;
popupToolTip.VerticalOffset = popupPosition.Y;
popupToolTip.StaysOpen = true; // We will close it ourselves
e.Handled = true;
popupToolTip.IsOpen = true;
distanceToPopupLimit = double.PositiveInfinity; // reset limit; we'll re-calculate it on the next mouse movement
} else {
if (toolTip == null) {
toolTip = new ToolTip();
toolTip.Closed += ToolTipClosed;
}
toolTip.PlacementTarget = this; // required for property inheritance
if (content is string s) {
toolTip.Content = new TextBlock {
Text = s,
TextWrapping = TextWrapping.Wrap
};
} else
toolTip.Content = content;
e.Handled = true;
toolTip.IsOpen = true;
}
}
}
bool TryCloseExistingPopup(bool mouseClick)
{
if (popupToolTip != null) {
if (popupToolTip.IsOpen && !mouseClick && popupToolTip is FlowDocumentTooltip t && !t.CloseWhenMouseMovesAway) {
return false; // Popup does not want to be closed yet
}
popupToolTip.IsOpen = false;
popupToolTip = null;
}
return true;
}
/// <summary> Returns Popup position based on mouse position, in device independent units </summary>
Point GetPopupPosition(MouseEventArgs mouseArgs)
{
Point mousePos = mouseArgs.GetPosition(this);
Point positionInPixels;
// align Popup with line bottom
TextViewPosition? logicalPos = textEditor.GetPositionFromPoint(mousePos);
if (logicalPos.HasValue) {
var textView = textEditor.TextArea.TextView;
positionInPixels =
textView.PointToScreen(
textView.GetVisualPosition(logicalPos.Value, VisualYPosition.LineBottom) - textView.ScrollOffset);
positionInPixels.X -= 4;
} else {
positionInPixels = PointToScreen(mousePos + new Vector(-4, 6));
}
// use device independent units, because Popup Left/Top are in independent units
return positionInPixels.TransformFromDevice(this);
}
void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
{
// Non-popup tooltips get closed as soon as the mouse starts moving again
if (toolTip != null) {
toolTip.IsOpen = false;
e.Handled = true;
}
}
double distanceToPopupLimit;
const double MaxMovementAwayFromPopup = 5;
void TextEditorMouseMove(object sender, MouseEventArgs e)
{
if (popupToolTip != null) {
double distanceToPopup = GetDistanceToPopup(e);
if (distanceToPopup > distanceToPopupLimit) {
// Close popup if mouse moved away, exceeding the limit
TryCloseExistingPopup(false);
} else {
// reduce distanceToPopupLimit
distanceToPopupLimit = Math.Min(distanceToPopupLimit, distanceToPopup + MaxMovementAwayFromPopup);
}
}
}
double GetDistanceToPopup(MouseEventArgs e)
{
Point p = popupToolTip.Child.PointFromScreen(PointToScreen(e.GetPosition(this)));
Size size = popupToolTip.Child.RenderSize;
double x = 0;
if (p.X < 0)
x = -p.X;
else if (p.X > size.Width)
x = p.X - size.Width;
double y = 0;
if (p.Y < 0)
y = -p.Y;
else if (p.Y > size.Height)
y = p.Y - size.Height;
return Math.Sqrt(x * x + y * y);
}
void TextEditorMouseLeave(object sender, MouseEventArgs e)
{
if (popupToolTip != null && !popupToolTip.IsMouseOver) {
// do not close popup if mouse moved from editor to popup
TryCloseExistingPopup(false);
}
}
void OnUnloaded(object sender, EventArgs e)
{
// Close popup when another document gets selected
// TextEditorMouseLeave is not sufficient for this because the mouse might be over the popup when the document switch happens (e.g. Ctrl+Tab)
TryCloseExistingPopup(true);
}
void ToolTipClosed(object sender, EventArgs e)
{
if (toolTip == sender) {
toolTip = null;
}
if (popupToolTip == sender) {
// Because popupToolTip instances are created by the tooltip provider,
// they might be reused; so we should detach the event handler
popupToolTip.Closed -= ToolTipClosed;
popupToolTip = null;
}
}
object GenerateTooltip(ReferenceSegment segment)
{
if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code) {
XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation;
if (docProvider != null){
DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), MainWindow.Instance.CurrentLanguage.SyntaxHighlighting);
renderer.AddSignatureBlock($"{code.Name} (0x{code.Code:x})");
if (docProvider != null) {
string documentation = docProvider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + code.EncodedName);
if (documentation != null) {
XmlDocRenderer renderer = new XmlDocRenderer();
renderer.AppendText($"{code.Name} (0x{code.Code:x}) - ");
renderer.AddXmlDocumentation(documentation);
return renderer.CreateTextBlock();
}
}
return $"{code.Name} (0x{code.Code:x})";
return new FlowDocumentTooltip(renderer.CreateDocument());
} else if (segment.Reference is IEntity entity) {
return CreateTextBlockForEntity(entity);
return new FlowDocumentTooltip(CreateTooltipForEntity(entity));
} else if (segment.Reference is ValueTuple<PEFile, System.Reflection.Metadata.EntityHandle> unresolvedEntity) {
var typeSystem = new DecompilerTypeSystem(unresolvedEntity.Item1, unresolvedEntity.Item1.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached);
try {
IEntity resolved = typeSystem.MainModule.ResolveEntity(unresolvedEntity.Item2);
if (resolved == null)
return null;
return CreateTextBlockForEntity(resolved);
return new FlowDocumentTooltip(CreateTooltipForEntity(resolved));
} catch (BadImageFormatException) {
return null;
}
@ -223,10 +357,11 @@ namespace ICSharpCode.ILSpy.TextView @@ -223,10 +357,11 @@ namespace ICSharpCode.ILSpy.TextView
return null;
}
static TextBlock CreateTextBlockForEntity(IEntity resolved)
static FlowDocument CreateTooltipForEntity(IEntity resolved)
{
XmlDocRenderer renderer = new XmlDocRenderer();
renderer.AppendText(MainWindow.Instance.CurrentLanguage.GetTooltip(resolved));
Language currentLanguage = MainWindow.Instance.CurrentLanguage;
DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), currentLanguage.SyntaxHighlighting);
renderer.AddSignatureBlock(currentLanguage.GetTooltip(resolved));
try {
if (resolved.ParentModule == null || resolved.ParentModule.PEFile == null)
return null;
@ -234,14 +369,57 @@ namespace ICSharpCode.ILSpy.TextView @@ -234,14 +369,57 @@ namespace ICSharpCode.ILSpy.TextView
if (docProvider != null) {
string documentation = docProvider.GetDocumentation(resolved.GetIdString());
if (documentation != null) {
renderer.AppendText(Environment.NewLine);
//renderer.AppendText(Environment.NewLine);
renderer.AddXmlDocumentation(documentation);
}
}
} catch (XmlException) {
// ignore
}
return renderer.CreateTextBlock();
return renderer.CreateDocument();
}
sealed class FlowDocumentTooltip : Popup
{
readonly FlowDocumentScrollViewer viewer;
public FlowDocumentTooltip(FlowDocument document)
{
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
viewer = new FlowDocumentScrollViewer();
viewer.Document = document;
Border border = new Border {
Background = SystemColors.InfoBrush,
BorderBrush = SystemColors.InfoTextBrush,
BorderThickness = new Thickness(1),
MaxHeight = 400,
Child = viewer
};
this.Child = border;
viewer.Foreground = SystemColors.InfoTextBrush;
document.FontSize = DisplaySettingsPanel.CurrentDisplaySettings.SelectedFontSize;
}
public bool CloseWhenMouseMovesAway {
get { return !this.IsKeyboardFocusWithin; }
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
this.IsOpen = false;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
// When the mouse is over the popup, it is possible for ILSpy to be minimized,
// or moved into the background, and yet the popup stays open.
// We don't have a good method here to check whether the mouse moved back into the text area
// or somewhere else, so we'll just close the popup.
if (CloseWhenMouseMovesAway)
this.IsOpen = false;
}
}
#endregion
@ -321,7 +499,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -321,7 +499,7 @@ namespace ICSharpCode.ILSpy.TextView
return tcs.Task;
}
void cancelButton_Click(object sender, RoutedEventArgs e)
void CancelButton_Click(object sender, RoutedEventArgs e)
{
if (currentCancellationTokenSource != null) {
currentCancellationTokenSource.Cancel();

2
ILSpy/TextView/DecompilerTextView.xaml

@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock FontSize="14pt" Text="{x:Static properties:Resources.Decompiling}"/>
<ProgressBar Name="progressBar" Height="16" Margin="0, 4" />
<Button Click="cancelButton_Click" HorizontalAlignment="Center" Content="{x:Static properties:Resources.Cancel}"/>
<Button Click="CancelButton_Click" HorizontalAlignment="Center" Content="{x:Static properties:Resources.Cancel}"/>
</StackPanel>
</Border>
</Grid>

530
ILSpy/TextView/DocumentationUIBuilder.cs

@ -0,0 +1,530 @@ @@ -0,0 +1,530 @@
// Copyright (c) 2014 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.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Xml;
using System.Xml.Linq;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.Options;
namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Builds a FlowDocument for XML documentation.
/// </summary>
public class DocumentationUIBuilder
{
FlowDocument flowDocument;
BlockCollection blockCollection;
InlineCollection inlineCollection;
IAmbience ambience;
public DocumentationUIBuilder(IAmbience ambience)
{
this.ambience = ambience;
this.flowDocument = new FlowDocument();
this.blockCollection = flowDocument.Blocks;
this.ShowSummary = true;
this.ShowAllParameters = true;
this.ShowReturns = true;
this.ShowThreadSafety = true;
this.ShowExceptions = true;
this.ShowTypeParameters = true;
this.ShowExample = true;
this.ShowPreliminary = true;
this.ShowSeeAlso = true;
this.ShowValue = true;
this.ShowPermissions = true;
this.ShowRemarks = true;
}
public FlowDocument CreateFlowDocument()
{
FlushAddedText(true);
flowDocument.FontSize = DisplaySettingsPanel.CurrentDisplaySettings.SelectedFontSize;
return flowDocument;
}
public bool ShowExceptions { get; set; }
public bool ShowPermissions { get; set; }
public bool ShowExample { get; set; }
public bool ShowPreliminary { get; set; }
public bool ShowRemarks { get; set; }
public bool ShowSummary { get; set; }
public bool ShowReturns { get; set; }
public bool ShowSeeAlso { get; set; }
public bool ShowThreadSafety { get; set; }
public bool ShowTypeParameters { get; set; }
public bool ShowValue { get; set; }
public bool ShowAllParameters { get; set; }
/// <summary>
/// Gets/Sets the name of the parameter that should be shown.
/// </summary>
public string ParameterName { get; set; }
public void AddDocumentationElement(XNode node)
{
if (node == null)
throw new ArgumentNullException(nameof(node));
if (node is XText text) {
AddText(text.Value);
return;
}
if (!(node is XElement element))
throw new NotImplementedException();
switch (element.Name.ToString()) {
case "b":
AddSpan(new Bold(), element.Elements());
break;
case "i":
AddSpan(new Italic(), element.ele);
break;
case "c":
AddSpan(new Span { FontFamily = GetCodeFont() }, element.Children);
break;
case "code":
AddCodeBlock(element.Value);
break;
case "example":
if (ShowExample)
AddSection("Example: ", element.Children);
break;
case "exception":
if (ShowExceptions)
AddException(element.ReferencedEntity, element.Children);
break;
case "list":
AddList(element.GetAttribute("type"), element.Children);
break;
//case "note":
// throw new NotImplementedException();
case "para":
AddParagraph(new Paragraph { Margin = new Thickness(0, 5, 0, 5) }, element.Children);
break;
case "param":
if (ShowAllParameters || (ParameterName != null && ParameterName == element.GetAttribute("name")))
AddParam(element.GetAttribute("name"), element.Children);
break;
case "paramref":
AddParamRef(element.GetAttribute("name"));
break;
case "permission":
if (ShowPermissions)
AddPermission(element.ReferencedEntity, element.Children);
break;
case "preliminary":
if (ShowPreliminary)
AddPreliminary(element.Children);
break;
case "remarks":
if (ShowRemarks)
AddSection("Remarks: ", element.Children);
break;
case "returns":
if (ShowReturns)
AddSection("Returns: ", element.Children);
break;
case "see":
AddSee(element);
break;
case "seealso":
if (inlineCollection != null)
AddSee(element);
else if (ShowSeeAlso)
AddSection(new Run("See also: "), () => AddSee(element));
break;
case "summary":
if (ShowSummary)
AddSection("Summary: ", element.Children);
break;
case "threadsafety":
if (ShowThreadSafety)
AddThreadSafety(ParseBool(element.GetAttribute("static")), ParseBool(element.GetAttribute("instance")), element.Children);
break;
case "typeparam":
if (ShowTypeParameters)
AddSection("Type parameter " + element.GetAttribute("name") + ": ", element.Children);
break;
case "typeparamref":
AddText(element.GetAttribute("name"));
break;
case "value":
if (ShowValue)
AddSection("Value: ", element.Children);
break;
case "exclude":
case "filterpriority":
case "overloads":
// ignore children
break;
case "br":
AddLineBreak();
break;
default:
foreach (var child in element.Children)
AddDocumentationElement(child);
break;
}
}
void AddList(string type, IEnumerable<XNode> items)
{
List list = new List();
AddBlock(list);
list.Margin = new Thickness(0, 5, 0, 5);
if (type == "number")
list.MarkerStyle = TextMarkerStyle.Decimal;
else if (type == "bullet")
list.MarkerStyle = TextMarkerStyle.Disc;
var oldBlockCollection = blockCollection;
try {
foreach (var itemElement in items) {
if (itemElement.Name == "listheader" || itemElement.Name == "item") {
ListItem item = new ListItem();
blockCollection = item.Blocks;
inlineCollection = null;
foreach (var prop in itemElement.Children) {
AddDocumentationElement(prop);
}
FlushAddedText(false);
list.ListItems.Add(item);
}
}
} finally {
blockCollection = oldBlockCollection;
}
}
public void AddCodeBlock(string textContent, bool keepLargeMargin = false)
{
var document = new ReadOnlyDocument(textContent);
var highlightingDefinition = HighlightingManager.Instance.GetDefinition("C#");
var block = DocumentPrinter.ConvertTextDocumentToBlock(document, highlightingDefinition);
block.FontFamily = GetCodeFont();
if (!keepLargeMargin)
block.Margin = new Thickness(0, 6, 0, 6);
AddBlock(block);
}
public void AddSignatureBlock(string signature, int currentParameterOffset, int currentParameterLength, string currentParameterName)
{
ParameterName = currentParameterName;
var document = new ReadOnlyDocument(signature);
var highlightingDefinition = HighlightingManager.Instance.GetDefinition("C#");
var richText = DocumentPrinter.ConvertTextDocumentToRichText(document, highlightingDefinition).ToRichTextModel();
richText.SetFontWeight(currentParameterOffset, currentParameterLength, FontWeights.Bold);
var block = new Paragraph();
block.Inlines.AddRange(richText.CreateRuns(document));
block.FontFamily = GetCodeFont();
block.TextAlignment = TextAlignment.Left;
AddBlock(block);
}
bool? ParseBool(string input)
{
bool result;
if (bool.TryParse(input, out result))
return result;
else
return null;
}
void AddThreadSafety(bool? staticThreadSafe, bool? instanceThreadSafe, IEnumerable<XNode> children)
{
AddSection(
new Run("Thread-safety: "),
delegate {
if (staticThreadSafe == true)
AddText("Any public static members of this type are thread safe. ");
else if (staticThreadSafe == false)
AddText("The static members of this type are not thread safe. ");
if (instanceThreadSafe == true)
AddText("Any public instance members of this type are thread safe. ");
else if (instanceThreadSafe == false)
AddText("Any instance members are not guaranteed to be thread safe. ");
foreach (var child in children)
AddDocumentationElement(child);
});
}
FontFamily GetCodeFont()
{
return new FontFamily(SD.EditorControlService.GlobalOptions.FontFamily);
}
void AddException(IEntity referencedEntity, IList<XNode> children)
{
Span span = new Span();
if (referencedEntity != null)
span.Inlines.Add(ConvertReference(referencedEntity));
else
span.Inlines.Add("Exception");
span.Inlines.Add(": ");
AddSection(span, children);
}
void AddPermission(IEntity referencedEntity, IList<XNode> children)
{
Span span = new Span();
span.Inlines.Add("Permission");
if (referencedEntity != null) {
span.Inlines.Add(" ");
span.Inlines.Add(ConvertReference(referencedEntity));
}
span.Inlines.Add(": ");
AddSection(span, children);
}
Inline ConvertReference(IEntity referencedEntity)
{
var h = new Hyperlink(new Run(ambience.ConvertSymbol(referencedEntity)));
h.Click += CreateNavigateOnClickHandler(referencedEntity);
return h;
}
void AddParam(string name, IEnumerable<XNode> children)
{
Span span = new Span();
span.Inlines.Add(new Run(name ?? string.Empty) { FontStyle = FontStyles.Italic });
span.Inlines.Add(": ");
AddSection(span, children);
}
void AddParamRef(string name)
{
if (name != null) {
AddInline(new Run(name) { FontStyle = FontStyles.Italic });
}
}
void AddPreliminary(IEnumerable<XNode> children)
{
if (children.Any()) {
foreach (var child in children)
AddDocumentationElement(child);
} else {
AddText("[This is preliminary documentation and subject to change.]");
}
}
void AddSee(XNode element)
{
IEntity referencedEntity = element.ReferencedEntity;
if (referencedEntity != null) {
if (element.Children.Any()) {
Hyperlink link = new Hyperlink();
link.Click += CreateNavigateOnClickHandler(referencedEntity);
AddSpan(link, element.Children);
} else {
AddInline(ConvertReference(referencedEntity));
}
} else if (element.GetAttribute("langword") != null) {
AddInline(new Run(element.GetAttribute("langword")) { FontFamily = GetCodeFont() });
} else if (element.GetAttribute("href") != null) {
Uri uri;
if (Uri.TryCreate(element.GetAttribute("href"), UriKind.Absolute, out uri)) {
if (element.Children.Any()) {
AddSpan(new Hyperlink { NavigateUri = uri }, element.Children);
} else {
AddInline(new Hyperlink(new Run(element.GetAttribute("href"))) { NavigateUri = uri });
}
}
} else {
// Invalid reference: print the cref value
AddText(element.GetAttribute("cref"));
}
}
RoutedEventHandler CreateNavigateOnClickHandler(IEntity referencedEntity)
{
// Don't let the anonymous method capture the referenced entity
// (we don't want to keep the whole compilation in memory)
// Use the IEntityModel instead.
var model = referencedEntity.GetModel();
return delegate (object sender, RoutedEventArgs e) {
IEntity resolvedEntity = model != null ? model.Resolve() : null;
if (resolvedEntity != null) {
bool shouldDisplayHelp = CodeCompletionOptions.TooltipLinkTarget == TooltipLinkTarget.Documentation
&& resolvedEntity.ParentAssembly.IsPartOfDotnetFramework();
if (!shouldDisplayHelp || !HelpProvider.ShowHelp(resolvedEntity))
NavigationService.NavigateTo(resolvedEntity);
}
e.Handled = true;
};
}
void AddSection(string title, IEnumerable<XNode> children)
{
AddSection(new Run(title), children);
}
void AddSection(Inline title, IEnumerable<XNode> children)
{
AddSection(
title, delegate {
foreach (var child in children)
AddDocumentationElement(child);
});
}
void AddSection(Inline title, Action addChildren)
{
var section = new Section();
AddBlock(section);
var oldBlockCollection = blockCollection;
try {
blockCollection = section.Blocks;
inlineCollection = null;
if (title != null)
AddInline(new Bold(title));
addChildren();
FlushAddedText(false);
} finally {
blockCollection = oldBlockCollection;
inlineCollection = null;
}
}
void AddParagraph(Paragraph para, IEnumerable<XNode> children)
{
AddBlock(para);
try {
inlineCollection = para.Inlines;
foreach (var child in children)
AddDocumentationElement(child);
FlushAddedText(false);
} finally {
inlineCollection = null;
}
}
void AddSpan(Span span, IEnumerable<XNode> children)
{
AddInline(span);
var oldInlineCollection = inlineCollection;
try {
inlineCollection = span.Inlines;
foreach (var child in children)
AddDocumentationElement(child);
FlushAddedText(false);
} finally {
inlineCollection = oldInlineCollection;
}
}
public void AddInline(Inline inline)
{
FlushAddedText(false);
if (inlineCollection == null) {
var para = new Paragraph();
para.Margin = new Thickness(0, 0, 0, 5);
inlineCollection = para.Inlines;
AddBlock(para);
}
inlineCollection.Add(inline);
ignoreWhitespace = false;
}
public void AddBlock(Block block)
{
FlushAddedText(true);
blockCollection.Add(block);
}
StringBuilder addedText = new StringBuilder();
bool ignoreWhitespace;
public void AddLineBreak()
{
TrimEndOfAddedText();
addedText.AppendLine();
ignoreWhitespace = true;
}
public void AddText(string textContent)
{
if (string.IsNullOrEmpty(textContent))
return;
for (int i = 0; i < textContent.Length; i++) {
char c = textContent[i];
if (c == '\n' && IsEmptyLineBefore(textContent, i)) {
AddLineBreak(); // empty line -> line break
} else if (char.IsWhiteSpace(c)) {
// any whitespace sequence gets converted to a single space (like HTML)
if (!ignoreWhitespace) {
addedText.Append(' ');
ignoreWhitespace = true;
}
} else {
addedText.Append(c);
ignoreWhitespace = false;
}
}
}
bool IsEmptyLineBefore(string text, int i)
{
// Skip previous whitespace
do {
i--;
} while (i >= 0 && (text[i] == ' ' || text[i] == '\r'));
// Check if previous non-whitespace char is \n
return i >= 0 && text[i] == '\n';
}
void TrimEndOfAddedText()
{
while (addedText.Length > 0 && addedText[addedText.Length - 1] == ' ') {
addedText.Length--;
}
}
void FlushAddedText(bool trim)
{
if (trim) // trim end of current text element
TrimEndOfAddedText();
if (addedText.Length == 0)
return;
string text = addedText.ToString();
addedText.Length = 0;
AddInline(new Run(text));
ignoreWhitespace = trim; // trim start of next text element
}
}
}

448
ILSpy/TextView/ReadOnlyDocument.cs

@ -0,0 +1,448 @@ @@ -0,0 +1,448 @@
// Copyright (c) 2010-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 ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Read-only implementation of <see cref="IDocument"/>.
/// </summary>
[Serializable]
public sealed class ReadOnlyDocument : IDocument
{
readonly ITextSource textSource;
readonly string fileName;
int[] lines;
static readonly char[] newline = { '\r', '\n' };
/// <summary>
/// Creates a new ReadOnlyDocument from the given text source.
/// </summary>
public ReadOnlyDocument(ITextSource textSource)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
// ensure that underlying buffer is immutable
this.textSource = textSource.CreateSnapshot();
List<int> lines = new List<int>();
lines.Add(0);
int offset = 0;
int textLength = textSource.TextLength;
while ((offset = textSource.IndexOfAny(newline, offset, textLength - offset)) >= 0) {
offset++;
if (textSource.GetCharAt(offset - 1) == '\r' && offset < textLength && textSource.GetCharAt(offset) == '\n') {
offset++;
}
lines.Add(offset);
}
this.lines = lines.ToArray();
}
/// <summary>
/// Creates a new ReadOnlyDocument from the given string.
/// </summary>
public ReadOnlyDocument(string text)
: this(new StringTextSource(text))
{
}
/// <summary>
/// Creates a new ReadOnlyDocument from the given text source;
/// and sets IDocument.FileName to the specified file name.
/// </summary>
public ReadOnlyDocument(ITextSource textSource, string fileName)
: this(textSource)
{
this.fileName = fileName;
}
/// <inheritdoc/>
public IDocumentLine GetLineByNumber(int lineNumber)
{
if (lineNumber < 1 || lineNumber > lines.Length)
throw new ArgumentOutOfRangeException("lineNumber", lineNumber, "Value must be between 1 and " + lines.Length);
return new ReadOnlyDocumentLine(this, lineNumber);
}
sealed class ReadOnlyDocumentLine : IDocumentLine
{
readonly ReadOnlyDocument doc;
readonly int lineNumber;
readonly int offset, endOffset;
public ReadOnlyDocumentLine(ReadOnlyDocument doc, int lineNumber)
{
this.doc = doc;
this.lineNumber = lineNumber;
this.offset = doc.GetStartOffset(lineNumber);
this.endOffset = doc.GetEndOffset(lineNumber);
}
public override int GetHashCode()
{
return doc.GetHashCode() ^ lineNumber;
}
public override bool Equals(object obj)
{
ReadOnlyDocumentLine other = obj as ReadOnlyDocumentLine;
return other != null && doc == other.doc && lineNumber == other.lineNumber;
}
public int Offset {
get { return offset; }
}
public int Length {
get { return endOffset - offset; }
}
public int EndOffset {
get { return endOffset; }
}
public int TotalLength {
get {
return doc.GetTotalEndOffset(lineNumber) - offset;
}
}
public int DelimiterLength {
get {
return doc.GetTotalEndOffset(lineNumber) - endOffset;
}
}
public int LineNumber {
get { return lineNumber; }
}
public IDocumentLine PreviousLine {
get {
if (lineNumber == 1)
return null;
else
return new ReadOnlyDocumentLine(doc, lineNumber - 1);
}
}
public IDocumentLine NextLine {
get {
if (lineNumber == doc.LineCount)
return null;
else
return new ReadOnlyDocumentLine(doc, lineNumber + 1);
}
}
public bool IsDeleted {
get { return false; }
}
}
int GetStartOffset(int lineNumber)
{
return lines[lineNumber - 1];
}
int GetTotalEndOffset(int lineNumber)
{
return lineNumber < lines.Length ? lines[lineNumber] : textSource.TextLength;
}
int GetEndOffset(int lineNumber)
{
if (lineNumber == lines.Length)
return textSource.TextLength;
int off = lines[lineNumber] - 1;
if (off > 0 && textSource.GetCharAt(off - 1) == '\r' && textSource.GetCharAt(off) == '\n')
off--;
return off;
}
/// <inheritdoc/>
public IDocumentLine GetLineByOffset(int offset)
{
return GetLineByNumber(GetLineNumberForOffset(offset));
}
int GetLineNumberForOffset(int offset)
{
int r = Array.BinarySearch(lines, offset);
return r < 0 ? ~r : r + 1;
}
/// <inheritdoc/>
public int GetOffset(int line, int column)
{
if (line < 1 || line > lines.Length)
throw new ArgumentOutOfRangeException("line", line, "Value must be between 1 and " + lines.Length);
int lineStart = GetStartOffset(line);
if (column <= 1)
return lineStart;
int lineEnd = GetEndOffset(line);
if (column - 1 >= lineEnd - lineStart)
return lineEnd;
return lineStart + column - 1;
}
/// <inheritdoc/>
public int GetOffset(TextLocation location)
{
return GetOffset(location.Line, location.Column);
}
/// <inheritdoc/>
public TextLocation GetLocation(int offset)
{
if (offset < 0 || offset > textSource.TextLength)
throw new ArgumentOutOfRangeException("offset", offset, "Value must be between 0 and " + textSource.TextLength);
int line = GetLineNumberForOffset(offset);
return new TextLocation(line, offset - GetStartOffset(line) + 1);
}
/// <inheritdoc/>
public string Text {
get { return textSource.Text; }
set {
throw new NotSupportedException();
}
}
/// <inheritdoc/>
public int LineCount {
get { return lines.Length; }
}
/// <inheritdoc/>
public ITextSourceVersion Version {
get { return textSource.Version; }
}
/// <inheritdoc/>
public int TextLength {
get { return textSource.TextLength; }
}
event EventHandler<TextChangeEventArgs> IDocument.TextChanging { add { } remove { } }
event EventHandler<TextChangeEventArgs> IDocument.TextChanged { add { } remove { } }
event EventHandler IDocument.ChangeCompleted { add { } remove { } }
void IDocument.Insert(int offset, string text)
{
throw new NotSupportedException();
}
void IDocument.Insert(int offset, string text, AnchorMovementType defaultAnchorMovementType)
{
throw new NotSupportedException();
}
void IDocument.Remove(int offset, int length)
{
throw new NotSupportedException();
}
void IDocument.Replace(int offset, int length, string newText)
{
throw new NotSupportedException();
}
void IDocument.Insert(int offset, ITextSource text)
{
throw new NotSupportedException();
}
void IDocument.Insert(int offset, ITextSource text, AnchorMovementType defaultAnchorMovementType)
{
throw new NotSupportedException();
}
void IDocument.Replace(int offset, int length, ITextSource newText)
{
throw new NotSupportedException();
}
void IDocument.StartUndoableAction()
{
}
void IDocument.EndUndoableAction()
{
}
IDisposable IDocument.OpenUndoGroup()
{
return null;
}
/// <inheritdoc/>
public ITextAnchor CreateAnchor(int offset)
{
return new ReadOnlyDocumentTextAnchor(GetLocation(offset), offset);
}
sealed class ReadOnlyDocumentTextAnchor : ITextAnchor
{
readonly TextLocation location;
readonly int offset;
public ReadOnlyDocumentTextAnchor(TextLocation location, int offset)
{
this.location = location;
this.offset = offset;
}
public event EventHandler Deleted { add { } remove { } }
public TextLocation Location {
get { return location; }
}
public int Offset {
get { return offset; }
}
public AnchorMovementType MovementType { get; set; }
public bool SurviveDeletion { get; set; }
public bool IsDeleted {
get { return false; }
}
public int Line {
get { return location.Line; }
}
public int Column {
get { return location.Column; }
}
}
/// <inheritdoc/>
public ITextSource CreateSnapshot()
{
return textSource; // textBuffer is immutable
}
/// <inheritdoc/>
public ITextSource CreateSnapshot(int offset, int length)
{
return textSource.CreateSnapshot(offset, length);
}
/// <inheritdoc/>
public IDocument CreateDocumentSnapshot()
{
return this; // ReadOnlyDocument is immutable
}
/// <inheritdoc/>
public System.IO.TextReader CreateReader()
{
return textSource.CreateReader();
}
/// <inheritdoc/>
public System.IO.TextReader CreateReader(int offset, int length)
{
return textSource.CreateReader(offset, length);
}
/// <inheritdoc/>
public void WriteTextTo(System.IO.TextWriter writer)
{
textSource.WriteTextTo(writer);
}
/// <inheritdoc/>
public void WriteTextTo(System.IO.TextWriter writer, int offset, int length)
{
textSource.WriteTextTo(writer, offset, length);
}
/// <inheritdoc/>
public char GetCharAt(int offset)
{
return textSource.GetCharAt(offset);
}
/// <inheritdoc/>
public string GetText(int offset, int length)
{
return textSource.GetText(offset, length);
}
/// <inheritdoc/>
public string GetText(ISegment segment)
{
return textSource.GetText(segment);
}
/// <inheritdoc/>
public int IndexOf(char c, int startIndex, int count)
{
return textSource.IndexOf(c, startIndex, count);
}
/// <inheritdoc/>
public int IndexOfAny(char[] anyOf, int startIndex, int count)
{
return textSource.IndexOfAny(anyOf, startIndex, count);
}
/// <inheritdoc/>
public int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
return textSource.IndexOf(searchText, startIndex, count, comparisonType);
}
/// <inheritdoc/>
public int LastIndexOf(char c, int startIndex, int count)
{
return textSource.LastIndexOf(c, startIndex, count);
}
/// <inheritdoc/>
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
return textSource.LastIndexOf(searchText, startIndex, count, comparisonType);
}
object IServiceProvider.GetService(Type serviceType)
{
return null;
}
/// <inheritdoc/>
/// <remarks>Will never be raised on <see cref="ReadOnlyDocument" />.</remarks>
public event EventHandler FileNameChanged { add { } remove { } }
/// <inheritdoc/>
public string FileName {
get { return fileName; }
}
}
}

99
ILSpy/TextView/XmlDocFormatter.cs

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
// Copyright (c) 2014 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.Windows.Documents;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.Xml;
namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Provides helper methods to create nicely formatted FlowDocuments from NRefactory XmlDoc.
/// </summary>
public static class XmlDocFormatter
{
public static FlowDocument CreateTooltip(IType type, bool useFullyQualifiedMemberNames = true)
{
var ambience = AmbienceService.GetCurrentAmbience();
ambience.ConversionFlags = ConversionFlags.StandardConversionFlags | ConversionFlags.ShowDeclaringType;
if (useFullyQualifiedMemberNames)
ambience.ConversionFlags |= ConversionFlags.UseFullyQualifiedEntityNames;
string header;
if (type is ITypeDefinition)
header = ambience.ConvertSymbol((ITypeDefinition)type);
else
header = ambience.ConvertType(type);
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList;
DocumentationUIBuilder b = new DocumentationUIBuilder(ambience);
b.AddCodeBlock(header, keepLargeMargin: true);
ITypeDefinition entity = type.GetDefinition();/*
if (entity != null) {
var documentation = XmlDocumentationElement.Get(entity);
if (documentation != null) {
foreach (var child in documentation.Children) {
b.AddDocumentationElement(child);
}
}
}*/
return b.CreateFlowDocument();
}
public static FlowDocument CreateTooltip(IEntity entity, bool useFullyQualifiedMemberNames = true)
{
var ambience = AmbienceService.GetCurrentAmbience();
ambience.ConversionFlags = ConversionFlags.StandardConversionFlags | ConversionFlags.ShowDeclaringType;
if (useFullyQualifiedMemberNames)
ambience.ConversionFlags |= ConversionFlags.UseFullyQualifiedEntityNames;
string header = ambience.ConvertSymbol(entity);
var documentation = XmlDocumentationElement.Get(entity);
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList;
DocumentationUIBuilder b = new DocumentationUIBuilder(ambience);
b.AddCodeBlock(header, keepLargeMargin: true);
if (documentation != null) {
foreach (var child in documentation.Children) {
b.AddDocumentationElement(child);
}
}
return b.CreateFlowDocument();
}
public static FlowDocument CreateTooltip(ISymbol symbol)
{
var ambience = AmbienceService.GetCurrentAmbience();
ambience.ConversionFlags = ConversionFlags.StandardConversionFlags | ConversionFlags.ShowDeclaringType;
string header = ambience.ConvertSymbol(symbol);
if (symbol is IParameter) {
header = "parameter " + header;
} else if (symbol is IVariable) {
header = "local variable " + header;
}
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList;
DocumentationUIBuilder b = new DocumentationUIBuilder(ambience);
b.AddCodeBlock(header, keepLargeMargin: true);
return b.CreateFlowDocument();
}
}
}

552
ILSpy/TextView/XmlDocRenderer.cs

@ -17,25 +17,103 @@ @@ -17,25 +17,103 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Xml;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Xml;
using ICSharpCode.ILSpy.Options;
namespace ICSharpCode.ILSpy.TextView
{
/// <summary>
/// Renders XML documentation into a WPF <see cref="TextBlock"/>.
/// Renders XML documentation into a WPF <see cref="FlowDocument"/>.
/// </summary>
public class XmlDocRenderer
public class DocumentationUIBuilder
{
readonly StringBuilder ret = new StringBuilder();
public void AppendText(string text)
readonly IAmbience ambience;
readonly IHighlightingDefinition highlightingDefinition;
readonly FlowDocument document;
BlockCollection blockCollection;
InlineCollection inlineCollection;
public DocumentationUIBuilder(IAmbience ambience, IHighlightingDefinition highlightingDefinition)
{
this.ambience = ambience;
this.highlightingDefinition = highlightingDefinition;
this.document = new FlowDocument();
this.blockCollection = document.Blocks;
this.ShowSummary = true;
this.ShowAllParameters = true;
this.ShowReturns = true;
this.ShowThreadSafety = true;
this.ShowExceptions = true;
this.ShowTypeParameters = true;
this.ShowExample = true;
this.ShowPreliminary = true;
this.ShowSeeAlso = true;
this.ShowValue = true;
this.ShowPermissions = true;
this.ShowRemarks = true;
}
public FlowDocument CreateDocument()
{
FlushAddedText(true);
return document;
}
public bool ShowExceptions { get; set; }
public bool ShowPermissions { get; set; }
public bool ShowExample { get; set; }
public bool ShowPreliminary { get; set; }
public bool ShowRemarks { get; set; }
public bool ShowSummary { get; set; }
public bool ShowReturns { get; set; }
public bool ShowSeeAlso { get; set; }
public bool ShowThreadSafety { get; set; }
public bool ShowTypeParameters { get; set; }
public bool ShowValue { get; set; }
public bool ShowAllParameters { get; set; }
public void AddCodeBlock(string textContent, bool keepLargeMargin = false)
{
var document = new TextDocument(textContent);
var highlighter = new DocumentHighlighter(document, highlightingDefinition);
var richText = DocumentPrinter.ConvertTextDocumentToRichText(document, highlighter).ToRichTextModel();
var block = new Paragraph();
block.Inlines.AddRange(richText.CreateRuns(document));
block.FontFamily = GetCodeFont();
if (!keepLargeMargin)
block.Margin = new Thickness(0, 6, 0, 6);
AddBlock(block);
}
public void AddSignatureBlock(string signature)
{
ret.Append(text);
var document = new TextDocument(signature);
var highlighter = new DocumentHighlighter(document, highlightingDefinition);
var richText = DocumentPrinter.ConvertTextDocumentToRichText(document, highlighter).ToRichTextModel();
var block = new Paragraph();
block.Inlines.AddRange(richText.CreateRuns(document));
block.FontFamily = GetCodeFont();
block.TextAlignment = TextAlignment.Left;
AddBlock(block);
}
public void AddXmlDocumentation(string xmlDocumentation)
@ -43,78 +121,263 @@ namespace ICSharpCode.ILSpy.TextView @@ -43,78 +121,263 @@ namespace ICSharpCode.ILSpy.TextView
if (xmlDocumentation == null)
return;
Debug.WriteLine(xmlDocumentation);
AXmlParser parser = new AXmlParser();
var doc = parser.Parse(new Decompiler.Xml.StringTextSource(xmlDocumentation));
AddDocumentationElement(new XmlDocumentationElement(doc, null, null));
}
/// <summary>
/// Gets/Sets the name of the parameter that should be shown.
/// </summary>
public string ParameterName { get; set; }
public void AddDocumentationElement(XmlDocumentationElement element)
{
if (element == null)
throw new ArgumentNullException("element");
if (element.IsTextNode) {
AddText(element.TextContent);
return;
}
switch (element.Name) {
case "b":
AddSpan(new Bold(), element.Children);
break;
case "i":
AddSpan(new Italic(), element.Children);
break;
case "c":
AddSpan(new Span { FontFamily = GetCodeFont() }, element.Children);
break;
case "code":
AddCodeBlock(element.TextContent);
break;
case "example":
if (ShowExample)
AddSection("Example: ", element.Children);
break;
case "exception":
if (ShowExceptions)
AddException(element.ReferencedEntity, element.Children);
break;
case "list":
AddList(element.GetAttribute("type"), element.Children);
break;
//case "note":
// throw new NotImplementedException();
case "para":
AddParagraph(new Paragraph { Margin = new Thickness(0, 5, 0, 5) }, element.Children);
break;
case "param":
if (ShowAllParameters || (ParameterName != null && ParameterName == element.GetAttribute("name")))
AddParam(element.GetAttribute("name"), element.Children);
break;
case "paramref":
AddParamRef(element.GetAttribute("name"));
break;
case "permission":
if (ShowPermissions)
AddPermission(element.ReferencedEntity, element.Children);
break;
case "preliminary":
if (ShowPreliminary)
AddPreliminary(element.Children);
break;
case "remarks":
if (ShowRemarks)
AddSection("Remarks: ", element.Children);
break;
case "returns":
if (ShowReturns)
AddSection("Returns: ", element.Children);
break;
case "see":
AddSee(element);
break;
case "seealso":
if (inlineCollection != null)
AddSee(element);
else if (ShowSeeAlso)
AddSection(new Run("See also: "), () => AddSee(element));
break;
case "summary":
if (ShowSummary)
AddSection("Summary: ", element.Children);
break;
case "threadsafety":
if (ShowThreadSafety)
AddThreadSafety(ParseBool(element.GetAttribute("static")), ParseBool(element.GetAttribute("instance")), element.Children);
break;
case "typeparam":
if (ShowTypeParameters)
AddSection("Type parameter " + element.GetAttribute("name") + ": ", element.Children);
break;
case "typeparamref":
AddText(element.GetAttribute("name"));
break;
case "value":
if (ShowValue)
AddSection("Value: ", element.Children);
break;
case "exclude":
case "filterpriority":
case "overloads":
// ignore children
break;
case "br":
AddLineBreak();
break;
default:
foreach (var child in element.Children)
AddDocumentationElement(child);
break;
}
}
void AddList(string type, IEnumerable<XmlDocumentationElement> items)
{
List list = new List();
AddBlock(list);
list.Margin = new Thickness(0, 5, 0, 5);
if (type == "number")
list.MarkerStyle = TextMarkerStyle.Decimal;
else if (type == "bullet")
list.MarkerStyle = TextMarkerStyle.Disc;
var oldBlockCollection = blockCollection;
try {
XmlTextReader r = new XmlTextReader(new StringReader("<docroot>" + xmlDocumentation + "</docroot>"));
r.XmlResolver = null;
AddXmlDocumentation(r);
} catch (XmlException) {
foreach (var itemElement in items) {
if (itemElement.Name == "listheader" || itemElement.Name == "item") {
ListItem item = new ListItem();
blockCollection = item.Blocks;
inlineCollection = null;
foreach (var prop in itemElement.Children) {
AddDocumentationElement(prop);
}
FlushAddedText(false);
list.ListItems.Add(item);
}
}
} finally {
blockCollection = oldBlockCollection;
}
}
static readonly Regex whitespace = new Regex(@"\s+");
public void AddXmlDocumentation(XmlReader xml)
{
while (xml.Read()) {
if (xml.NodeType == XmlNodeType.Element) {
string elname = xml.Name.ToLowerInvariant();
switch (elname) {
case "filterpriority":
case "remarks":
xml.Skip();
break;
case "example":
ret.Append(Environment.NewLine);
ret.Append("Example:");
ret.Append(Environment.NewLine);
break;
case "exception":
ret.Append(Environment.NewLine);
ret.Append(GetCref(xml["cref"]));
ret.Append(": ");
break;
case "returns":
ret.Append(Environment.NewLine);
ret.Append("Returns: ");
break;
case "see":
ret.Append(GetCref(xml["cref"]));
ret.Append(xml["langword"]);
break;
case "seealso":
ret.Append(Environment.NewLine);
ret.Append("See also: ");
ret.Append(GetCref(xml["cref"]));
break;
case "paramref":
ret.Append(xml["name"]);
break;
case "param":
ret.Append(Environment.NewLine);
ret.Append(whitespace.Replace(xml["name"].Trim()," "));
ret.Append(": ");
break;
case "typeparam":
ret.Append(Environment.NewLine);
ret.Append(whitespace.Replace(xml["name"].Trim()," "));
ret.Append(": ");
break;
case "value":
ret.Append(Environment.NewLine);
ret.Append("Value: ");
ret.Append(Environment.NewLine);
break;
case "br":
case "para":
ret.Append(Environment.NewLine);
break;
bool? ParseBool(string input)
{
bool result;
if (bool.TryParse(input, out result))
return result;
else
return null;
}
void AddThreadSafety(bool? staticThreadSafe, bool? instanceThreadSafe, IEnumerable<XmlDocumentationElement> children)
{
AddSection(
new Run("Thread-safety: "),
delegate {
if (staticThreadSafe == true)
AddText("Any public static members of this type are thread safe. ");
else if (staticThreadSafe == false)
AddText("The static members of this type are not thread safe. ");
if (instanceThreadSafe == true)
AddText("Any public instance members of this type are thread safe. ");
else if (instanceThreadSafe == false)
AddText("Any instance members are not guaranteed to be thread safe. ");
foreach (var child in children)
AddDocumentationElement(child);
});
}
void AddException(IEntity referencedEntity, IList<XmlDocumentationElement> children)
{
Span span = new Span();
if (referencedEntity != null)
span.Inlines.Add(ConvertReference(referencedEntity));
else
span.Inlines.Add("Exception");
span.Inlines.Add(": ");
AddSection(span, children);
}
void AddPermission(IEntity referencedEntity, IList<XmlDocumentationElement> children)
{
Span span = new Span();
span.Inlines.Add("Permission");
if (referencedEntity != null) {
span.Inlines.Add(" ");
span.Inlines.Add(ConvertReference(referencedEntity));
}
span.Inlines.Add(": ");
AddSection(span, children);
}
Inline ConvertReference(IEntity referencedEntity)
{
var h = new Hyperlink(new Run(ambience.ConvertSymbol(referencedEntity)));
//h.Click += CreateNavigateOnClickHandler(referencedEntity);
return h;
}
void AddParam(string name, IEnumerable<XmlDocumentationElement> children)
{
Span span = new Span();
span.Inlines.Add(new Run(name ?? string.Empty) { FontStyle = FontStyles.Italic });
span.Inlines.Add(": ");
AddSection(span, children);
}
void AddParamRef(string name)
{
if (name != null) {
AddInline(new Run(name) { FontStyle = FontStyles.Italic });
}
}
void AddPreliminary(IEnumerable<XmlDocumentationElement> children)
{
if (children.Any()) {
foreach (var child in children)
AddDocumentationElement(child);
} else {
AddText("[This is preliminary documentation and subject to change.]");
}
}
void AddSee(XmlDocumentationElement element)
{
IEntity referencedEntity = element.ReferencedEntity;
if (referencedEntity != null) {
if (element.Children.Any()) {
Hyperlink link = new Hyperlink();
// TODO
//link.Click += CreateNavigateOnClickHandler(referencedEntity);
AddSpan(link, element.Children);
} else {
AddInline(ConvertReference(referencedEntity));
}
} else if (element.GetAttribute("langword") != null) {
AddInline(new Run(element.GetAttribute("langword")) { FontFamily = GetCodeFont() });
} else if (element.GetAttribute("href") != null) {
Uri uri;
if (Uri.TryCreate(element.GetAttribute("href"), UriKind.Absolute, out uri)) {
if (element.Children.Any()) {
AddSpan(new Hyperlink { NavigateUri = uri }, element.Children);
} else {
AddInline(new Hyperlink(new Run(element.GetAttribute("href"))) { NavigateUri = uri });
}
} else if (xml.NodeType == XmlNodeType.Text) {
ret.Append(whitespace.Replace(xml.Value, " "));
}
} else {
// Invalid reference: print the cref value
AddText(element.GetAttribute("cref"));
}
}
static string GetCref(string cref)
{
if (cref == null || cref.Trim().Length==0) {
@ -128,10 +391,151 @@ namespace ICSharpCode.ILSpy.TextView @@ -128,10 +391,151 @@ namespace ICSharpCode.ILSpy.TextView
}
return cref;
}
public TextBlock CreateTextBlock()
FontFamily GetCodeFont()
{
return DisplaySettingsPanel.CurrentDisplaySettings.SelectedFont;
}
public void AddInline(Inline inline)
{
FlushAddedText(false);
if (inlineCollection == null) {
var para = new Paragraph();
para.Margin = new Thickness(0, 0, 0, 5);
inlineCollection = para.Inlines;
AddBlock(para);
}
inlineCollection.Add(inline);
ignoreWhitespace = false;
}
void AddSection(string title, IEnumerable<XmlDocumentationElement> children)
{
AddSection(new Run(title), children);
}
void AddSection(Inline title, IEnumerable<XmlDocumentationElement> children)
{
AddSection(
title, delegate {
foreach (var child in children)
AddDocumentationElement(child);
});
}
void AddSection(Inline title, Action addChildren)
{
var section = new Section();
AddBlock(section);
var oldBlockCollection = blockCollection;
try {
blockCollection = section.Blocks;
inlineCollection = null;
if (title != null)
AddInline(new Bold(title));
addChildren();
FlushAddedText(false);
} finally {
blockCollection = oldBlockCollection;
inlineCollection = null;
}
}
void AddParagraph(Paragraph para, IEnumerable<XmlDocumentationElement> children)
{
AddBlock(para);
try {
inlineCollection = para.Inlines;
foreach (var child in children)
AddDocumentationElement(child);
FlushAddedText(false);
} finally {
inlineCollection = null;
}
}
void AddSpan(Span span, IEnumerable<XmlDocumentationElement> children)
{
AddInline(span);
var oldInlineCollection = inlineCollection;
try {
inlineCollection = span.Inlines;
foreach (var child in children)
AddDocumentationElement(child);
FlushAddedText(false);
} finally {
inlineCollection = oldInlineCollection;
}
}
public void AddBlock(Block block)
{
FlushAddedText(true);
blockCollection.Add(block);
}
StringBuilder addedText = new StringBuilder();
bool ignoreWhitespace;
public void AddLineBreak()
{
TrimEndOfAddedText();
addedText.AppendLine();
ignoreWhitespace = true;
}
public void AddText(string textContent)
{
return new TextBlock { Text = ret.ToString() };
if (string.IsNullOrEmpty(textContent))
return;
for (int i = 0; i < textContent.Length; i++) {
char c = textContent[i];
if (c == '\n' && IsEmptyLineBefore(textContent, i)) {
AddLineBreak(); // empty line -> line break
} else if (char.IsWhiteSpace(c)) {
// any whitespace sequence gets converted to a single space (like HTML)
if (!ignoreWhitespace) {
addedText.Append(' ');
ignoreWhitespace = true;
}
} else {
addedText.Append(c);
ignoreWhitespace = false;
}
}
}
bool IsEmptyLineBefore(string text, int i)
{
// Skip previous whitespace
do {
i--;
} while (i >= 0 && (text[i] == ' ' || text[i] == '\r'));
// Check if previous non-whitespace char is \n
return i >= 0 && text[i] == '\n';
}
void TrimEndOfAddedText()
{
while (addedText.Length > 0 && addedText[addedText.Length - 1] == ' ') {
addedText.Length--;
}
}
void FlushAddedText(bool trim)
{
if (trim) // trim end of current text element
TrimEndOfAddedText();
if (addedText.Length == 0)
return;
string text = addedText.ToString();
addedText.Length = 0;
AddInline(new Run(text));
ignoreWhitespace = trim; // trim start of next text element
}
}
}

Loading…
Cancel
Save