diff options
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs')
| -rw-r--r-- | Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs new file mode 100644 index 000000000..4951a7c0b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs @@ -0,0 +1,266 @@ +// 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 Tango.Scripting.Editors.Document; + +namespace Tango.Scripting.Editors.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 + } +} |
