PageRenderTime 26ms CodeModel.GetById 12ms app.highlight 9ms RepoModel.GetById 2ms app.codeStats 0ms

/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlObject.cs

http://github.com/icsharpcode/ILSpy
C# | 266 lines | 174 code | 37 blank | 55 comment | 51 complexity | 5c718dd52b3e0280b0b3e28f15f4c233 MD5 | raw file
  1// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
  2// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
  3
  4using System;
  5using System.Collections.Generic;
  6using System.Collections.ObjectModel;
  7using System.Collections.Specialized;
  8using System.Diagnostics;
  9using System.Globalization;
 10using System.Linq;
 11
 12using ICSharpCode.AvalonEdit.Document;
 13
 14namespace ICSharpCode.AvalonEdit.Xml
 15{
 16	/// <summary>
 17	/// Abstact base class for all types
 18	/// </summary>
 19	public abstract class AXmlObject: TextSegment
 20	{
 21		/// <summary> Empty string.  The namespace used if there is no "xmlns" specified </summary>
 22		public static readonly string NoNamespace = string.Empty;
 23		
 24		/// <summary> Namespace for "xml:" prefix: "http://www.w3.org/XML/1998/namespace" </summary>
 25		public static readonly string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
 26		
 27		/// <summary> Namesapce for "xmlns:" prefix: "http://www.w3.org/2000/xmlns/" </summary>
 28		public static readonly string XmlnsNamespace = "http://www.w3.org/2000/xmlns/";
 29		
 30		/// <summary> Parent node. </summary>
 31		/// <remarks>
 32		/// New cached items start with null parent.
 33		/// Cache constraint:
 34		///   If cached item has parent set, then the whole subtree must be consistent
 35		/// </remarks>
 36		public AXmlObject Parent { get; set; }
 37		
 38		/// <summary>
 39		/// Gets the document that has owns this object.
 40		/// Once set, it is not changed.  Not even set to null.
 41		/// </summary>
 42		internal AXmlDocument Document { get; set; }
 43		
 44		/// <summary> Creates new object </summary>
 45		protected AXmlObject()
 46		{
 47			this.LastUpdatedFrom = this;
 48		}
 49		
 50		/// <summary> Occurs before the value of any local properties changes.  Nested changes do not cause the event to occur </summary>
 51		public event EventHandler<AXmlObjectEventArgs> Changing;
 52		
 53		/// <summary> Occurs after the value of any local properties changed.  Nested changes do not cause the event to occur </summary>
 54		public event EventHandler<AXmlObjectEventArgs> Changed;
 55		
 56		/// <summary> Raises Changing event </summary>
 57		protected void OnChanging()
 58		{
 59			AXmlParser.Log("Changing {0}", this);
 60			if (Changing != null) {
 61				Changing(this, new AXmlObjectEventArgs() { Object = this } );
 62			}
 63			AXmlDocument doc = this.Document;
 64			if (doc != null) {
 65				doc.OnObjectChanging(this);
 66			}
 67			// As a convenience, also rasie an event for the parent element
 68			AXmlTag me = this as AXmlTag;
 69			if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) {
 70				me.Parent.OnChanging();
 71			}
 72		}
 73		
 74		/// <summary> Raises Changed event </summary>
 75		protected void OnChanged()
 76		{
 77			AXmlParser.Log("Changed {0}", this);
 78			if (Changed != null) {
 79				Changed(this, new AXmlObjectEventArgs() { Object = this } );
 80			}
 81			AXmlDocument doc = this.Document;
 82			if (doc != null) {
 83				doc.OnObjectChanged(this);
 84			}
 85			// As a convenience, also rasie an event for the parent element
 86			AXmlTag me = this as AXmlTag;
 87			if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) {
 88				me.Parent.OnChanged();
 89			}
 90		}
 91		
 92		List<SyntaxError> syntaxErrors;
 93		
 94		/// <summary>
 95		/// The error that occured in the context of this node (excluding nested nodes)
 96		/// </summary>
 97		public IEnumerable<SyntaxError> MySyntaxErrors {
 98			get {
 99				if (syntaxErrors == null) {
100					return new SyntaxError[] {};
101				} else {
102					return syntaxErrors;
103				}
104			}
105		}
106		
107		/// <summary>
108		/// The error that occured in the context of this node and all nested nodes.
109		/// It has O(n) cost.
110		/// </summary>
111		public IEnumerable<SyntaxError> SyntaxErrors {
112			get {
113				return GetSelfAndAllChildren().SelectMany(obj => obj.MySyntaxErrors);
114			}
115		}
116		
117		internal void AddSyntaxError(SyntaxError error)
118		{
119			DebugAssert(error.Object == this, "Must own the error");
120			if (this.syntaxErrors == null) this.syntaxErrors = new List<SyntaxError>();
121			syntaxErrors.Add(error);
122		}
123		
124		/// <summary> Throws exception if condition is false </summary>
125		/// <remarks> Present in release mode - use only for very cheap aserts </remarks>
126		protected static void Assert(bool condition, string message)
127		{
128			if (!condition) {
129				throw new InternalException("Assertion failed: " + message);
130			}
131		}
132		
133		/// <summary> Throws exception if condition is false </summary>
134		[Conditional("DEBUG")]
135		protected static void DebugAssert(bool condition, string message)
136		{
137			if (!condition) {
138				throw new InternalException("Assertion failed: " + message);
139			}
140		}
141		
142		/// <summary> Recursively gets self and all nested nodes. </summary>
143		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
144		                                                 Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")]
145		public virtual IEnumerable<AXmlObject> GetSelfAndAllChildren()
146		{
147			return new AXmlObject[] { this };
148		}
149		
150		/// <summary> Get all ancestors of this node </summary>
151		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
152		                                                 Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")]
153		public IEnumerable<AXmlObject> GetAncestors()
154		{
155			AXmlObject curr = this.Parent;
156			while(curr != null) {
157				yield return curr;
158				curr = curr.Parent;
159			}
160		}
161		
162		/// <summary> Call appropriate visit method on the given visitor </summary>
163		public abstract void AcceptVisitor(IAXmlVisitor visitor);
164		
165		/// <summary> The parser tree object this object was updated from </summary>
166		/// <remarks> Initialized to 'this' </remarks>
167		internal AXmlObject LastUpdatedFrom { get; private set; }
168		
169		internal bool IsCached { get; set; }
170		
171		/// <summary> Is call to UpdateDataFrom is allowed? </summary>
172		internal bool CanUpdateDataFrom(AXmlObject source)
173		{
174			return
175				this.GetType() == source.GetType() &&
176				this.StartOffset == source.StartOffset &&
177				(this.LastUpdatedFrom == source || !this.IsCached);
178		}
179		
180		/// <summary> Copy all data from the 'source' to this object </summary>
181		/// <remarks> Returns true if any updates were done </remarks>
182		internal virtual bool UpdateDataFrom(AXmlObject source)
183		{
184			Assert(this.GetType() == source.GetType(), "Source has different type");
185			DebugAssert(this.StartOffset == source.StartOffset, "Source has different StartOffset");
186			
187			if (this.LastUpdatedFrom == source) {
188				DebugAssert(this.EndOffset == source.EndOffset, "Source has different EndOffset");
189				return false;
190			}
191			
192			Assert(!this.IsCached, "Can not update cached item");
193			Assert(source.IsCached, "Must update from cache");
194			
195			this.LastUpdatedFrom = source;
196			this.StartOffset = source.StartOffset;
197			// In some cases we are just updating objects of that same
198			// type and position and hoping to be luckily right
199			this.EndOffset = source.EndOffset;
200			
201			// Do not bother comparing - assume changed if non-null
202			if (this.syntaxErrors != null || source.syntaxErrors != null) {
203				// May be called again in derived class - oh, well, does not matter
204				OnChanging();
205				this.Document.Parser.TrackedSegments.RemoveSyntaxErrorsOf(this);
206				if (source.syntaxErrors == null) {
207					this.syntaxErrors = null;
208				} else {
209					this.syntaxErrors = new List<SyntaxError>();
210					foreach(var error in source.MySyntaxErrors) {
211						// The object differs, so create our own copy
212						// The source still might need it in the future and we do not want to break it
213						this.AddSyntaxError(error.Clone(this));
214					}
215				}
216				this.Document.Parser.TrackedSegments.AddSyntaxErrorsOf(this);
217				OnChanged();
218			}
219			
220			return true;
221		}
222		
223		/// <summary> Verify that the item is consistent.  Only in debug build. </summary>
224		[Conditional("DEBUG")]
225		internal virtual void DebugCheckConsistency(bool allowNullParent)
226		{
227			
228		}
229		
230		/// <inheritdoc/>
231		public override string ToString()
232		{
233			return string.Format(CultureInfo.InvariantCulture, "{0}({1}-{2})", this.GetType().Name.Remove(0, 4), this.StartOffset, this.EndOffset);
234		}
235		
236		#region Helpper methods
237		
238		/// <summary> The part of name before ":" </summary>
239		/// <returns> Empty string if not found </returns>
240		protected static string GetNamespacePrefix(string name)
241		{
242			if (string.IsNullOrEmpty(name)) return string.Empty;
243			int colonIndex = name.IndexOf(':');
244			if (colonIndex != -1) {
245				return name.Substring(0, colonIndex);
246			} else {
247				return string.Empty;
248			}
249		}
250		
251		/// <summary> The part of name after ":" </summary>
252		/// <returns> Whole name if ":" not found </returns>
253		protected static string GetLocalName(string name)
254		{
255			if (string.IsNullOrEmpty(name)) return string.Empty;
256			int colonIndex = name.IndexOf(':');
257			if (colonIndex != -1) {
258				return name.Remove(0, colonIndex + 1);
259			} else {
260				return name ?? string.Empty;
261			}
262		}
263		
264		#endregion
265	}
266}