/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
- // 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.Linq;
- using ICSharpCode.AvalonEdit.Document;
- namespace ICSharpCode.AvalonEdit.Xml
- {
- /// <summary>
- /// Abstact base class for all types that can contain child nodes
- /// </summary>
- public abstract class AXmlContainer: AXmlObject
- {
- /// <summary>
- /// Children of the node. It is read-only.
- /// Note that is has CollectionChanged event.
- /// </summary>
- public AXmlObjectCollection<AXmlObject> Children { get; private set; }
-
- /// <summary> Create new container </summary>
- protected AXmlContainer()
- {
- this.Children = new AXmlObjectCollection<AXmlObject>();
- }
-
- #region Helpper methods
-
- ObservableCollection<AXmlElement> elements;
-
- /// <summary> Gets direcly nested elements (non-recursive) </summary>
- public ObservableCollection<AXmlElement> Elements {
- get {
- if (elements == null) {
- elements = new FilteredCollection<AXmlElement, AXmlObjectCollection<AXmlObject>>(this.Children);
- }
- return elements;
- }
- }
-
- internal AXmlObject FirstChild {
- get {
- return this.Children[0];
- }
- }
-
- internal AXmlObject LastChild {
- get {
- return this.Children[this.Children.Count - 1];
- }
- }
-
- #endregion
-
- /// <inheritdoc/>
- public override IEnumerable<AXmlObject> GetSelfAndAllChildren()
- {
- return (new AXmlObject[] { this }).Flatten(
- delegate(AXmlObject i) {
- AXmlContainer container = i as AXmlContainer;
- if (container != null)
- return container.Children;
- else
- return null;
- }
- );
- }
-
- /// <summary>
- /// Gets a child fully containg the given offset.
- /// Goes recursively down the tree.
- /// Specail case if at the end of attribute or text
- /// </summary>
- public AXmlObject GetChildAtOffset(int offset)
- {
- foreach(AXmlObject child in this.Children) {
- if ((child is AXmlAttribute || child is AXmlText) && offset == child.EndOffset) return child;
- if (child.StartOffset < offset && offset < child.EndOffset) {
- AXmlContainer container = child as AXmlContainer;
- if (container != null) {
- return container.GetChildAtOffset(offset);
- } else {
- return child;
- }
- }
- }
- return this; // No childs at offset
- }
-
- // Only these four methods should be used to modify the collection
-
- /// <summary> To be used exlucively by the parser </summary>
- internal void AddChild(AXmlObject item)
- {
- // Childs can be only added to newly parsed items
- Assert(this.Parent == null, "I have to be new");
- Assert(item.IsCached, "Added item must be in cache");
- // Do not set parent pointer
- this.Children.InsertItemAt(this.Children.Count, item);
- }
-
- /// <summary> To be used exlucively by the parser </summary>
- internal void AddChildren(IEnumerable<AXmlObject> items)
- {
- // Childs can be only added to newly parsed items
- Assert(this.Parent == null, "I have to be new");
- // Do not set parent pointer
- this.Children.InsertItemsAt(this.Children.Count, items.ToList());
- }
-
- /// <summary>
- /// To be used exclusively by the children update algorithm.
- /// Insert child and keep links consistent.
- /// </summary>
- void InsertChild(int index, AXmlObject item)
- {
- AXmlParser.Log("Inserting {0} at index {1}", item, index);
-
- Assert(this.Document != null, "Can not insert to dangling object");
- Assert(item.Parent != this, "Can not own item twice");
-
- SetParentPointersInTree(item);
-
- this.Children.InsertItemAt(index, item);
-
- this.Document.OnObjectInserted(index, item);
- }
-
- /// <summary> Recursively fix all parent pointer in a tree </summary>
- /// <remarks>
- /// Cache constraint:
- /// If cached item has parent set, then the whole subtree must be consistent and document set
- /// </remarks>
- void SetParentPointersInTree(AXmlObject item)
- {
- // All items come from the parser cache
-
- if (item.Parent == null) {
- // Dangling object - either a new parser object or removed tree (still cached)
- item.Parent = this;
- item.Document = this.Document;
- AXmlContainer container = item as AXmlContainer;
- if (container != null) {
- foreach(AXmlObject child in container.Children) {
- container.SetParentPointersInTree(child);
- }
- }
- } else if (item.Parent == this) {
- // If node is attached and then deattached, it will have null parent pointer
- // but valid subtree - so its children will alredy have correct parent pointer
- // like in this case
- // item.DebugCheckConsistency(false);
- // Rest of the tree is consistent - do not recurse
- } else {
- // From cache & parent set => consitent subtree
- // item.DebugCheckConsistency(false);
- // The parent (or any futher parents) can not be part of parsed document
- // becuase otherwise this item would be included twice => safe to change parents
- // Maintain cache constraint by setting parents to null
- foreach(AXmlObject ancest in item.GetAncestors().ToList()) {
- ancest.Parent = null;
- }
- item.Parent = this;
- // Rest of the tree is consistent - do not recurse
- }
- }
-
- /// <summary>
- /// To be used exclusively by the children update algorithm.
- /// Remove child, set parent to null and notify the document
- /// </summary>
- void RemoveChild(int index)
- {
- AXmlObject removed = this.Children[index];
- AXmlParser.Log("Removing {0} at index {1}", removed, index);
-
- // Stop tracking if the object can not be used again
- if (!removed.IsCached)
- this.Document.Parser.TrackedSegments.RemoveParsedObject(removed);
-
- // Null parent pointer
- Assert(removed.Parent == this, "Inconsistent child");
- removed.Parent = null;
-
- this.Children.RemoveItemAt(index);
-
- this.Document.OnObjectRemoved(index, removed);
- }
-
- /// <summary> Verify that the subtree is consistent. Only in debug build. </summary>
- /// <remarks> Parent pointers might be null or pointing somewhere else in parse tree </remarks>
- internal override void DebugCheckConsistency(bool checkParentPointers)
- {
- base.DebugCheckConsistency(checkParentPointers);
- AXmlObject prevChild = null;
- int myStartOffset = this.StartOffset;
- int myEndOffset = this.EndOffset;
- foreach(AXmlObject child in this.Children) {
- Assert(child.Length != 0, "Empty child");
- if (checkParentPointers) {
- Assert(child.Parent != null, "Null parent reference");
- Assert(child.Parent == this, "Inccorect parent reference");
- }
- if (this.Document != null) {
- Assert(child.Document != null, "Child has null document");
- Assert(child.Document == this.Document, "Child is in different document");
- }
- if (this.IsCached)
- Assert(child.IsCached, "Child not in cache");
- Assert(myStartOffset <= child.StartOffset && child.EndOffset <= myEndOffset, "Child not within parent text range");
- if (prevChild != null)
- Assert(prevChild.EndOffset <= child.StartOffset, "Overlaping childs");
- child.DebugCheckConsistency(checkParentPointers);
- prevChild = child;
- }
- }
-
- /// <remarks>
- /// Note the the method is not called recuively.
- /// Only the helper methods are recursive.
- /// </remarks>
- internal void UpdateTreeFrom(AXmlContainer srcContainer)
- {
- this.StartOffset = srcContainer.StartOffset; // Force the update
- this.UpdateDataFrom(srcContainer);
- RemoveChildrenNotIn(srcContainer.Children);
- InsertAndUpdateChildrenFrom(srcContainer.Children);
- }
-
- void RemoveChildrenNotIn(IList<AXmlObject> srcList)
- {
- Dictionary<int, AXmlObject> srcChildren = srcList.ToDictionary(i => i.StartOffset);
- for(int i = 0; i < this.Children.Count;) {
- AXmlObject child = this.Children[i];
- AXmlObject srcChild;
-
- if (srcChildren.TryGetValue(child.StartOffset, out srcChild) && child.CanUpdateDataFrom(srcChild)) {
- // Keep only one item with given offset (we might have several due to deletion)
- srcChildren.Remove(child.StartOffset);
- // If contaner that needs updating
- AXmlContainer childAsContainer = child as AXmlContainer;
- if (childAsContainer != null && child.LastUpdatedFrom != srcChild)
- childAsContainer.RemoveChildrenNotIn(((AXmlContainer)srcChild).Children);
- i++;
- } else {
- RemoveChild(i);
- }
- }
- }
-
- void InsertAndUpdateChildrenFrom(IList<AXmlObject> srcList)
- {
- for(int i = 0; i < srcList.Count; i++) {
- // End of our list?
- if (i == this.Children.Count) {
- InsertChild(i, srcList[i]);
- continue;
- }
- AXmlObject child = this.Children[i];
- AXmlObject srcChild = srcList[i];
-
- if (child.CanUpdateDataFrom(srcChild)) { // includes offset test
- // Does it need updating?
- if (child.LastUpdatedFrom != srcChild) {
- child.UpdateDataFrom(srcChild);
- AXmlContainer childAsContainer = child as AXmlContainer;
- if (childAsContainer != null)
- childAsContainer.InsertAndUpdateChildrenFrom(((AXmlContainer)srcChild).Children);
- }
- } else {
- InsertChild(i, srcChild);
- }
- }
- Assert(this.Children.Count == srcList.Count, "List lengths differ after update");
- }
- }
- }