mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
9.1 KiB
282 lines
9.1 KiB
// 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"); |
|
} |
|
} |
|
}
|
|
|