/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlContainer.cs

http://github.com/icsharpcode/ILSpy · C# · 282 lines · 187 code · 33 blank · 62 comment · 57 complexity · 192ed39cc91239f33ae1079ea3e7bcf6 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.Linq;
  9. using ICSharpCode.AvalonEdit.Document;
  10. namespace ICSharpCode.AvalonEdit.Xml
  11. {
  12. /// <summary>
  13. /// Abstact base class for all types that can contain child nodes
  14. /// </summary>
  15. public abstract class AXmlContainer: AXmlObject
  16. {
  17. /// <summary>
  18. /// Children of the node. It is read-only.
  19. /// Note that is has CollectionChanged event.
  20. /// </summary>
  21. public AXmlObjectCollection<AXmlObject> Children { get; private set; }
  22. /// <summary> Create new container </summary>
  23. protected AXmlContainer()
  24. {
  25. this.Children = new AXmlObjectCollection<AXmlObject>();
  26. }
  27. #region Helpper methods
  28. ObservableCollection<AXmlElement> elements;
  29. /// <summary> Gets direcly nested elements (non-recursive) </summary>
  30. public ObservableCollection<AXmlElement> Elements {
  31. get {
  32. if (elements == null) {
  33. elements = new FilteredCollection<AXmlElement, AXmlObjectCollection<AXmlObject>>(this.Children);
  34. }
  35. return elements;
  36. }
  37. }
  38. internal AXmlObject FirstChild {
  39. get {
  40. return this.Children[0];
  41. }
  42. }
  43. internal AXmlObject LastChild {
  44. get {
  45. return this.Children[this.Children.Count - 1];
  46. }
  47. }
  48. #endregion
  49. /// <inheritdoc/>
  50. public override IEnumerable<AXmlObject> GetSelfAndAllChildren()
  51. {
  52. return (new AXmlObject[] { this }).Flatten(
  53. delegate(AXmlObject i) {
  54. AXmlContainer container = i as AXmlContainer;
  55. if (container != null)
  56. return container.Children;
  57. else
  58. return null;
  59. }
  60. );
  61. }
  62. /// <summary>
  63. /// Gets a child fully containg the given offset.
  64. /// Goes recursively down the tree.
  65. /// Specail case if at the end of attribute or text
  66. /// </summary>
  67. public AXmlObject GetChildAtOffset(int offset)
  68. {
  69. foreach(AXmlObject child in this.Children) {
  70. if ((child is AXmlAttribute || child is AXmlText) && offset == child.EndOffset) return child;
  71. if (child.StartOffset < offset && offset < child.EndOffset) {
  72. AXmlContainer container = child as AXmlContainer;
  73. if (container != null) {
  74. return container.GetChildAtOffset(offset);
  75. } else {
  76. return child;
  77. }
  78. }
  79. }
  80. return this; // No childs at offset
  81. }
  82. // Only these four methods should be used to modify the collection
  83. /// <summary> To be used exlucively by the parser </summary>
  84. internal void AddChild(AXmlObject item)
  85. {
  86. // Childs can be only added to newly parsed items
  87. Assert(this.Parent == null, "I have to be new");
  88. Assert(item.IsCached, "Added item must be in cache");
  89. // Do not set parent pointer
  90. this.Children.InsertItemAt(this.Children.Count, item);
  91. }
  92. /// <summary> To be used exlucively by the parser </summary>
  93. internal void AddChildren(IEnumerable<AXmlObject> items)
  94. {
  95. // Childs can be only added to newly parsed items
  96. Assert(this.Parent == null, "I have to be new");
  97. // Do not set parent pointer
  98. this.Children.InsertItemsAt(this.Children.Count, items.ToList());
  99. }
  100. /// <summary>
  101. /// To be used exclusively by the children update algorithm.
  102. /// Insert child and keep links consistent.
  103. /// </summary>
  104. void InsertChild(int index, AXmlObject item)
  105. {
  106. AXmlParser.Log("Inserting {0} at index {1}", item, index);
  107. Assert(this.Document != null, "Can not insert to dangling object");
  108. Assert(item.Parent != this, "Can not own item twice");
  109. SetParentPointersInTree(item);
  110. this.Children.InsertItemAt(index, item);
  111. this.Document.OnObjectInserted(index, item);
  112. }
  113. /// <summary> Recursively fix all parent pointer in a tree </summary>
  114. /// <remarks>
  115. /// Cache constraint:
  116. /// If cached item has parent set, then the whole subtree must be consistent and document set
  117. /// </remarks>
  118. void SetParentPointersInTree(AXmlObject item)
  119. {
  120. // All items come from the parser cache
  121. if (item.Parent == null) {
  122. // Dangling object - either a new parser object or removed tree (still cached)
  123. item.Parent = this;
  124. item.Document = this.Document;
  125. AXmlContainer container = item as AXmlContainer;
  126. if (container != null) {
  127. foreach(AXmlObject child in container.Children) {
  128. container.SetParentPointersInTree(child);
  129. }
  130. }
  131. } else if (item.Parent == this) {
  132. // If node is attached and then deattached, it will have null parent pointer
  133. // but valid subtree - so its children will alredy have correct parent pointer
  134. // like in this case
  135. // item.DebugCheckConsistency(false);
  136. // Rest of the tree is consistent - do not recurse
  137. } else {
  138. // From cache & parent set => consitent subtree
  139. // item.DebugCheckConsistency(false);
  140. // The parent (or any futher parents) can not be part of parsed document
  141. // becuase otherwise this item would be included twice => safe to change parents
  142. // Maintain cache constraint by setting parents to null
  143. foreach(AXmlObject ancest in item.GetAncestors().ToList()) {
  144. ancest.Parent = null;
  145. }
  146. item.Parent = this;
  147. // Rest of the tree is consistent - do not recurse
  148. }
  149. }
  150. /// <summary>
  151. /// To be used exclusively by the children update algorithm.
  152. /// Remove child, set parent to null and notify the document
  153. /// </summary>
  154. void RemoveChild(int index)
  155. {
  156. AXmlObject removed = this.Children[index];
  157. AXmlParser.Log("Removing {0} at index {1}", removed, index);
  158. // Stop tracking if the object can not be used again
  159. if (!removed.IsCached)
  160. this.Document.Parser.TrackedSegments.RemoveParsedObject(removed);
  161. // Null parent pointer
  162. Assert(removed.Parent == this, "Inconsistent child");
  163. removed.Parent = null;
  164. this.Children.RemoveItemAt(index);
  165. this.Document.OnObjectRemoved(index, removed);
  166. }
  167. /// <summary> Verify that the subtree is consistent. Only in debug build. </summary>
  168. /// <remarks> Parent pointers might be null or pointing somewhere else in parse tree </remarks>
  169. internal override void DebugCheckConsistency(bool checkParentPointers)
  170. {
  171. base.DebugCheckConsistency(checkParentPointers);
  172. AXmlObject prevChild = null;
  173. int myStartOffset = this.StartOffset;
  174. int myEndOffset = this.EndOffset;
  175. foreach(AXmlObject child in this.Children) {
  176. Assert(child.Length != 0, "Empty child");
  177. if (checkParentPointers) {
  178. Assert(child.Parent != null, "Null parent reference");
  179. Assert(child.Parent == this, "Inccorect parent reference");
  180. }
  181. if (this.Document != null) {
  182. Assert(child.Document != null, "Child has null document");
  183. Assert(child.Document == this.Document, "Child is in different document");
  184. }
  185. if (this.IsCached)
  186. Assert(child.IsCached, "Child not in cache");
  187. Assert(myStartOffset <= child.StartOffset && child.EndOffset <= myEndOffset, "Child not within parent text range");
  188. if (prevChild != null)
  189. Assert(prevChild.EndOffset <= child.StartOffset, "Overlaping childs");
  190. child.DebugCheckConsistency(checkParentPointers);
  191. prevChild = child;
  192. }
  193. }
  194. /// <remarks>
  195. /// Note the the method is not called recuively.
  196. /// Only the helper methods are recursive.
  197. /// </remarks>
  198. internal void UpdateTreeFrom(AXmlContainer srcContainer)
  199. {
  200. this.StartOffset = srcContainer.StartOffset; // Force the update
  201. this.UpdateDataFrom(srcContainer);
  202. RemoveChildrenNotIn(srcContainer.Children);
  203. InsertAndUpdateChildrenFrom(srcContainer.Children);
  204. }
  205. void RemoveChildrenNotIn(IList<AXmlObject> srcList)
  206. {
  207. Dictionary<int, AXmlObject> srcChildren = srcList.ToDictionary(i => i.StartOffset);
  208. for(int i = 0; i < this.Children.Count;) {
  209. AXmlObject child = this.Children[i];
  210. AXmlObject srcChild;
  211. if (srcChildren.TryGetValue(child.StartOffset, out srcChild) && child.CanUpdateDataFrom(srcChild)) {
  212. // Keep only one item with given offset (we might have several due to deletion)
  213. srcChildren.Remove(child.StartOffset);
  214. // If contaner that needs updating
  215. AXmlContainer childAsContainer = child as AXmlContainer;
  216. if (childAsContainer != null && child.LastUpdatedFrom != srcChild)
  217. childAsContainer.RemoveChildrenNotIn(((AXmlContainer)srcChild).Children);
  218. i++;
  219. } else {
  220. RemoveChild(i);
  221. }
  222. }
  223. }
  224. void InsertAndUpdateChildrenFrom(IList<AXmlObject> srcList)
  225. {
  226. for(int i = 0; i < srcList.Count; i++) {
  227. // End of our list?
  228. if (i == this.Children.Count) {
  229. InsertChild(i, srcList[i]);
  230. continue;
  231. }
  232. AXmlObject child = this.Children[i];
  233. AXmlObject srcChild = srcList[i];
  234. if (child.CanUpdateDataFrom(srcChild)) { // includes offset test
  235. // Does it need updating?
  236. if (child.LastUpdatedFrom != srcChild) {
  237. child.UpdateDataFrom(srcChild);
  238. AXmlContainer childAsContainer = child as AXmlContainer;
  239. if (childAsContainer != null)
  240. childAsContainer.InsertAndUpdateChildrenFrom(((AXmlContainer)srcChild).Children);
  241. }
  242. } else {
  243. InsertChild(i, srcChild);
  244. }
  245. }
  246. Assert(this.Children.Count == srcList.Count, "List lengths differ after update");
  247. }
  248. }
  249. }