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