mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
8.9 KiB
266 lines
8.9 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Collections.ObjectModel; |
|
using System.Collections.Specialized; |
|
using System.Diagnostics; |
|
using System.Globalization; |
|
using System.Linq; |
|
|
|
using ICSharpCode.AvalonEdit.Document; |
|
|
|
namespace ICSharpCode.AvalonEdit.Xml |
|
{ |
|
/// <summary> |
|
/// Abstact base class for all types |
|
/// </summary> |
|
public abstract class AXmlObject: TextSegment |
|
{ |
|
/// <summary> Empty string. The namespace used if there is no "xmlns" specified </summary> |
|
public 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/"; |
|
|
|
/// <summary> Parent node. </summary> |
|
/// <remarks> |
|
/// New cached items start with null parent. |
|
/// Cache constraint: |
|
/// If cached item has parent set, then the whole subtree must be consistent |
|
/// </remarks> |
|
public AXmlObject Parent { get; set; } |
|
|
|
/// <summary> |
|
/// Gets the document that has owns this object. |
|
/// Once set, it is not changed. Not even set to null. |
|
/// </summary> |
|
internal AXmlDocument Document { get; set; } |
|
|
|
/// <summary> Creates new object </summary> |
|
protected AXmlObject() |
|
{ |
|
this.LastUpdatedFrom = this; |
|
} |
|
|
|
/// <summary> Occurs before the value of any local properties changes. Nested changes do not cause the event to occur </summary> |
|
public event EventHandler<AXmlObjectEventArgs> Changing; |
|
|
|
/// <summary> Occurs after the value of any local properties changed. Nested changes do not cause the event to occur </summary> |
|
public event EventHandler<AXmlObjectEventArgs> Changed; |
|
|
|
/// <summary> Raises Changing event </summary> |
|
protected void OnChanging() |
|
{ |
|
AXmlParser.Log("Changing {0}", this); |
|
if (Changing != null) { |
|
Changing(this, new AXmlObjectEventArgs() { Object = this } ); |
|
} |
|
AXmlDocument doc = this.Document; |
|
if (doc != null) { |
|
doc.OnObjectChanging(this); |
|
} |
|
// As a convenience, also rasie an event for the parent element |
|
AXmlTag me = this as AXmlTag; |
|
if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) { |
|
me.Parent.OnChanging(); |
|
} |
|
} |
|
|
|
/// <summary> Raises Changed event </summary> |
|
protected void OnChanged() |
|
{ |
|
AXmlParser.Log("Changed {0}", this); |
|
if (Changed != null) { |
|
Changed(this, new AXmlObjectEventArgs() { Object = this } ); |
|
} |
|
AXmlDocument doc = this.Document; |
|
if (doc != null) { |
|
doc.OnObjectChanged(this); |
|
} |
|
// As a convenience, also rasie an event for the parent element |
|
AXmlTag me = this as AXmlTag; |
|
if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) { |
|
me.Parent.OnChanged(); |
|
} |
|
} |
|
|
|
List<SyntaxError> syntaxErrors; |
|
|
|
/// <summary> |
|
/// The error that occured in the context of this node (excluding nested nodes) |
|
/// </summary> |
|
public IEnumerable<SyntaxError> MySyntaxErrors { |
|
get { |
|
if (syntaxErrors == null) { |
|
return new SyntaxError[] {}; |
|
} else { |
|
return syntaxErrors; |
|
} |
|
} |
|
} |
|
|
|
/// <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 GetSelfAndAllChildren().SelectMany(obj => obj.MySyntaxErrors); |
|
} |
|
} |
|
|
|
internal void AddSyntaxError(SyntaxError error) |
|
{ |
|
DebugAssert(error.Object == this, "Must own the error"); |
|
if (this.syntaxErrors == null) this.syntaxErrors = new List<SyntaxError>(); |
|
syntaxErrors.Add(error); |
|
} |
|
|
|
/// <summary> Throws exception if condition is false </summary> |
|
/// <remarks> Present in release mode - use only for very cheap aserts </remarks> |
|
protected 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")] |
|
protected static void DebugAssert(bool condition, string message) |
|
{ |
|
if (!condition) { |
|
throw new InternalException("Assertion failed: " + message); |
|
} |
|
} |
|
|
|
/// <summary> Recursively gets self and all nested nodes. </summary> |
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", |
|
Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")] |
|
public virtual IEnumerable<AXmlObject> GetSelfAndAllChildren() |
|
{ |
|
return new AXmlObject[] { this }; |
|
} |
|
|
|
/// <summary> Get all ancestors of this node </summary> |
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", |
|
Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")] |
|
public IEnumerable<AXmlObject> GetAncestors() |
|
{ |
|
AXmlObject curr = this.Parent; |
|
while(curr != null) { |
|
yield return curr; |
|
curr = curr.Parent; |
|
} |
|
} |
|
|
|
/// <summary> Call appropriate visit method on the given visitor </summary> |
|
public abstract void AcceptVisitor(IAXmlVisitor visitor); |
|
|
|
/// <summary> The parser tree object this object was updated from </summary> |
|
/// <remarks> Initialized to 'this' </remarks> |
|
internal AXmlObject LastUpdatedFrom { get; private set; } |
|
|
|
internal bool IsCached { get; set; } |
|
|
|
/// <summary> Is call to UpdateDataFrom is allowed? </summary> |
|
internal bool CanUpdateDataFrom(AXmlObject source) |
|
{ |
|
return |
|
this.GetType() == source.GetType() && |
|
this.StartOffset == source.StartOffset && |
|
(this.LastUpdatedFrom == source || !this.IsCached); |
|
} |
|
|
|
/// <summary> Copy all data from the 'source' to this object </summary> |
|
/// <remarks> Returns true if any updates were done </remarks> |
|
internal virtual bool UpdateDataFrom(AXmlObject source) |
|
{ |
|
Assert(this.GetType() == source.GetType(), "Source has different type"); |
|
DebugAssert(this.StartOffset == source.StartOffset, "Source has different StartOffset"); |
|
|
|
if (this.LastUpdatedFrom == source) { |
|
DebugAssert(this.EndOffset == source.EndOffset, "Source has different EndOffset"); |
|
return false; |
|
} |
|
|
|
Assert(!this.IsCached, "Can not update cached item"); |
|
Assert(source.IsCached, "Must update from cache"); |
|
|
|
this.LastUpdatedFrom = source; |
|
this.StartOffset = source.StartOffset; |
|
// In some cases we are just updating objects of that same |
|
// type and position and hoping to be luckily right |
|
this.EndOffset = source.EndOffset; |
|
|
|
// Do not bother comparing - assume changed if non-null |
|
if (this.syntaxErrors != null || source.syntaxErrors != null) { |
|
// May be called again in derived class - oh, well, does not matter |
|
OnChanging(); |
|
this.Document.Parser.TrackedSegments.RemoveSyntaxErrorsOf(this); |
|
if (source.syntaxErrors == null) { |
|
this.syntaxErrors = null; |
|
} else { |
|
this.syntaxErrors = new List<SyntaxError>(); |
|
foreach(var error in source.MySyntaxErrors) { |
|
// The object differs, so create our own copy |
|
// The source still might need it in the future and we do not want to break it |
|
this.AddSyntaxError(error.Clone(this)); |
|
} |
|
} |
|
this.Document.Parser.TrackedSegments.AddSyntaxErrorsOf(this); |
|
OnChanged(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/// <summary> Verify that the item is consistent. Only in debug build. </summary> |
|
[Conditional("DEBUG")] |
|
internal virtual void DebugCheckConsistency(bool allowNullParent) |
|
{ |
|
|
|
} |
|
|
|
/// <inheritdoc/> |
|
public override string ToString() |
|
{ |
|
return string.Format(CultureInfo.InvariantCulture, "{0}({1}-{2})", this.GetType().Name.Remove(0, 4), this.StartOffset, this.EndOffset); |
|
} |
|
|
|
#region Helpper methods |
|
|
|
/// <summary> The part of name before ":" </summary> |
|
/// <returns> Empty string if not found </returns> |
|
protected 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> |
|
protected 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 |
|
} |
|
}
|
|
|