From 8afd82f176599966502795afce888f025fb22df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Mon, 3 Aug 2009 16:42:46 +0000 Subject: [PATCH] XML Parser: Joint multiple collection updates together git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4586 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../ICSharpCode.AvalonEdit.csproj | 2 +- .../XmlParser/Collections.cs | 211 ++++++++++++++++++ .../XmlParser/ObservableCollections.cs | 112 ---------- .../XmlParser/RawObjects.cs | 117 +++++++--- 4 files changed, 292 insertions(+), 150 deletions(-) create mode 100644 src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Collections.cs delete mode 100644 src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/ObservableCollections.cs diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj index 2a5d656ac7..2aa59e0627 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj @@ -304,7 +304,7 @@ - + diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Collections.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Collections.cs new file mode 100644 index 0000000000..4ec287876e --- /dev/null +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Collections.cs @@ -0,0 +1,211 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +// Missing XML comment +#pragma warning disable 1591 + +namespace ICSharpCode.AvalonEdit.XmlParser +{ + /// + /// Collection that is publicly read-only and has support + /// for adding/removing multiple items at a time. + /// + public class ChildrenCollection: Collection, INotifyCollectionChanged + { + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (CollectionChanged != null) { + CollectionChanged(this, e); + } + } + + protected override void ClearItems() + { + throw new NotSupportedException(); + } + + protected override void InsertItem(int index, T item) + { + throw new NotSupportedException(); + } + + protected override void RemoveItem(int index) + { + throw new NotSupportedException(); + } + + protected override void SetItem(int index, T item) + { + throw new NotSupportedException(); + } + + internal void InsertItems(int index, IList items) + { + for(int i = 0; i < items.Count; i++) { + base.InsertItem(index + i, items[i]); + } + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)items, index)); + } + + internal void RemoveItems(int index, int count) + { + List removed = new List(); + for(int i = 0; i < count; i++) { + removed.Add(this[index]); + base.RemoveItem(index); + } + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)removed, index)); + } + } + + /// + /// Collection that presents only some items from the wrapped collection + /// + public class FilteredCollection: ObservableCollection where C: INotifyCollectionChanged, IList + { + C source; + Predicate condition; + List srcPtrs = new List(); // Index to the original collection + + public FilteredCollection(C source, Predicate condition) + { + this.source = source; + this.condition = condition; + + this.source.CollectionChanged += SourceCollectionChanged; + + Reset(); + } + + void Reset() + { + this.Clear(); + srcPtrs.Clear(); + for(int i = 0; i < source.Count; i++) { + if (condition(source[i])) { + this.Add(source[i]); + srcPtrs.Add(i); + } + } + } + + void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch(e.Action) { + case NotifyCollectionChangedAction.Add: + // Update pointers + for(int i = 0; i < srcPtrs.Count; i++) { + if (srcPtrs[i] >= e.NewStartingIndex) { + srcPtrs[i] += e.NewItems.Count; + } + } + // Find where to add items + int addIndex = srcPtrs.FindIndex(srcPtr => srcPtr >= e.NewStartingIndex); + if (addIndex == -1) addIndex = this.Count; + // Add items to collection + for(int i = 0; i < e.NewItems.Count; i++) { + if (condition((T)e.NewItems[i])) { + this.InsertItem(addIndex, (T)e.NewItems[i]); + srcPtrs.Insert(addIndex, e.NewStartingIndex + i); + addIndex++; + } + } + break; + case NotifyCollectionChangedAction.Remove: + // Remove the item from our collection + for(int i = 0; i < e.OldItems.Count; i++) { + // Anyone points to the removed item? + int removeIndex = srcPtrs.IndexOf(e.OldStartingIndex + i); + // Remove + if (removeIndex != -1) { + this.RemoveAt(removeIndex); + srcPtrs.RemoveAt(removeIndex); + } + } + // Update pointers + for(int i = 0; i < srcPtrs.Count; i++) { + if (srcPtrs[i] >= e.OldStartingIndex) { + srcPtrs[i] -= e.OldItems.Count; + } + } + break; + case NotifyCollectionChangedAction.Reset: + Reset(); + break; + default: + throw new NotSupportedException(e.Action.ToString()); + } + } + } + + /// + /// Two collections in sequence + /// + public class MergedCollection: ObservableCollection where C: INotifyCollectionChanged, IList + { + C a; + C b; + + public MergedCollection(C a, C b) + { + this.a = a; + this.b = b; + + this.a.CollectionChanged += SourceCollectionAChanged; + this.b.CollectionChanged += SourceCollectionBChanged; + + Reset(); + } + + void Reset() + { + this.Clear(); + foreach(T item in a) this.Add(item); + foreach(T item in b) this.Add(item); + } + + void SourceCollectionAChanged(object sender, NotifyCollectionChangedEventArgs e) + { + SourceCollectionChanged(0, e); + } + + void SourceCollectionBChanged(object sender, NotifyCollectionChangedEventArgs e) + { + SourceCollectionChanged(a.Count, e); + } + + void SourceCollectionChanged(int collectionStart, NotifyCollectionChangedEventArgs e) + { + switch(e.Action) { + case NotifyCollectionChangedAction.Add: + for (int i = 0; i < e.NewItems.Count; i++) { + this.InsertItem(collectionStart + e.NewStartingIndex + i, (T)e.NewItems[i]); + } + break; + case NotifyCollectionChangedAction.Remove: + for (int i = 0; i < e.OldItems.Count; i++) { + this.RemoveAt(collectionStart + e.OldStartingIndex); + } + break; + case NotifyCollectionChangedAction.Reset: + Reset(); + break; + default: + throw new NotSupportedException(e.Action.ToString()); + } + } + } +} diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/ObservableCollections.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/ObservableCollections.cs deleted file mode 100644 index 541e1e9157..0000000000 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/ObservableCollections.cs +++ /dev/null @@ -1,112 +0,0 @@ -// -// -// -// -// $Revision$ -// - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; - -// Missing XML comment -#pragma warning disable 1591 - -namespace ICSharpCode.AvalonEdit.XmlParser -{ - public class FilteredObservableCollection: ObservableCollection - { - ObservableCollection source; - Predicate condition; - List srcPtrs = new List(); - - public FilteredObservableCollection(ObservableCollection source, Predicate condition) - { - this.source = source; - this.condition = condition; - - for(int i = 0; i < source.Count; i++) { - if (condition(source[i])) { - int index = srcPtrs.Count; - this.InsertItem(index, source[i]); - srcPtrs.Insert(index, i); - } - } - - this.source.CollectionChanged += new NotifyCollectionChangedEventHandler(FilteredObservableCollection_CollectionChanged); - } - - void FilteredObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Remove) { - // Remove the item from our collection - if (condition((T)e.OldItems[0])) { - int index = srcPtrs.IndexOf(e.OldStartingIndex); - this.RemoveAt(index); - srcPtrs.RemoveAt(index); - } - // Update pointers - for(int i = 0; i < srcPtrs.Count; i++) { - if (srcPtrs[i] > e.OldStartingIndex) { - srcPtrs[i]--; - } - } - } - if (e.Action == NotifyCollectionChangedAction.Add) { - // Update pointers - for(int i = 0; i < srcPtrs.Count; i++) { - if (srcPtrs[i] >= e.NewStartingIndex) { - srcPtrs[i]++; - } - } - // Add item to collection - if (condition((T)e.NewItems[0])) { - int index = srcPtrs.FindIndex(srcPtr => srcPtr >= e.NewStartingIndex); - if (index == -1) index = srcPtrs.Count; - this.InsertItem(index, (T)e.NewItems[0]); - srcPtrs.Insert(index, e.NewStartingIndex); - } - } - } - } - - public class MergedObservableCollection: ObservableCollection - { - ObservableCollection a; - ObservableCollection b; - - public MergedObservableCollection(ObservableCollection a, ObservableCollection b) - { - this.a = a; - this.b = b; - - foreach(T item in a) this.Add(item); - foreach(T item in b) this.Add(item); - - this.a.CollectionChanged += new NotifyCollectionChangedEventHandler(MergedObservableCollection_CollectionAChanged); - this.b.CollectionChanged += new NotifyCollectionChangedEventHandler(MergedObservableCollection_CollectionBChanged); - } - - void MergedObservableCollection_CollectionAChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Remove) { - this.RemoveAt(e.OldStartingIndex); - } - if (e.Action == NotifyCollectionChangedAction.Add) { - this.InsertItem(e.NewStartingIndex, (T)e.NewItems[0]); - } - } - - void MergedObservableCollection_CollectionBChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Remove) { - this.RemoveAt(e.OldStartingIndex + a.Count); - } - if (e.Action == NotifyCollectionChangedAction.Add) { - this.InsertItem(e.NewStartingIndex + a.Count, (T)e.NewItems[0]); - } - } - } -} diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs index 28a4fdfee2..c4ef714208 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs @@ -123,16 +123,16 @@ namespace ICSharpCode.AvalonEdit.XmlParser /// Children of the node. Can be Elements, Attributes, etc... /// Please do not modify directly! /// - public ObservableCollection Children { get; private set; } + public ChildrenCollection Children { get; private set; } public RawContainer() { - this.Children = new ObservableCollection(); + this.Children = new ChildrenCollection(); } public ObservableCollection Helper_Elements { get { - return new FilteredObservableCollection(this.Children, x => x is RawElement); + return new FilteredCollection, RawObject>(this.Children, x => x is RawElement); } } @@ -158,30 +158,55 @@ namespace ICSharpCode.AvalonEdit.XmlParser public void AddChild(RawObject item) { item.Parent = this; - this.Children.Add(item); + this.Children.InsertItems(this.Children.Count, new RawObject[] {item}.ToList()); } - protected virtual void Insert(int index, RawObject item) + /// + /// Insert children, set parent for them and notify the document + /// + protected virtual void Insert(int index, IList items) { - LogDom("Inserting {0} at index {1}", item, index); - item.Parent = this; - this.Children.Insert(index, item); - if (this.Document != null) { - foreach(RawObject obj in GetSelfAndAllChildren()) { - this.Document.OnObjectAttached(item); + if (items.Count == 1) { + LogDom("Inserting {0} at index {1}", items[0], index); + } else { + LogDom("Inserting at index {0}:", index); + foreach(RawObject item in items) LogDom(" {0}", item); + } + foreach(RawObject item in items) item.Parent = this; + this.Children.InsertItems(index, items); + RawDocument document = this.Document; + if (document != null) { + foreach(RawObject item in items) { + foreach(RawObject obj in item.GetSelfAndAllChildren()) { + document.OnObjectAttached(obj); + } } } } - protected virtual void RemoveAt(int index) + /// + /// Remove children, set parent to null for them and notify the document + /// + protected virtual void RemoveAt(int index, int count) { - RawObject removedItem = this.Children[index]; - LogDom("Removing {0} at index {1}", removedItem, index); - removedItem.Parent = null; - this.Children.RemoveAt(index); - if (this.Document != null) { - foreach(RawObject obj in GetSelfAndAllChildren()) { - this.Document.OnObjectDettached(removedItem); + List removed = new List(count); + for(int i = 0; i < count; i++) { + removed.Add(this.Children[index + i]); + } + if (count == 1) { + LogDom("Removing {0} at index {1}", removed[0], index); + } else { + LogDom("Removing at index {0}:", index); + foreach(RawObject item in removed) LogDom(" {0}", item); + } + foreach(RawObject item in removed) item.Parent = null; + this.Children.RemoveItems(index, count); + RawDocument document = this.Document; + if (document != null) { + foreach(RawObject item in removed) { + foreach(RawObject obj in item.GetSelfAndAllChildren()) { + document.OnObjectDettached(obj); + } } } } @@ -204,7 +229,12 @@ namespace ICSharpCode.AvalonEdit.XmlParser while(i < srcList.Count) { // Item is missing - 'i' is invalid index if (i >= dstList.Count) { - Insert(i, srcList[i]); + // Add the rest of the items + List itemsToAdd = new List(); + for(int j = i; j < srcList.Count; j++) { + itemsToAdd.Add(srcList[j]); + } + Insert(i, itemsToAdd); i++; continue; } RawObject srcItem = srcList[i]; @@ -225,9 +255,11 @@ namespace ICSharpCode.AvalonEdit.XmlParser for(int srcItemIndex = i; srcItemIndex < srcList.Count; srcItemIndex++) { RawObject src = srcList[srcItemIndex]; if (src.StartOffset == dstItem.StartOffset && src.GetType() == dstItem.GetType()) { + List itemsToAdd = new List(); for(int j = i; j < srcItemIndex; j++) { - Insert(j, srcList[j]); + itemsToAdd.Add(srcList[j]); } + Insert(i, itemsToAdd); i = srcItemIndex; goto continue2; } @@ -236,9 +268,7 @@ namespace ICSharpCode.AvalonEdit.XmlParser for(int dstItemIndex = i; dstItemIndex < dstList.Count; dstItemIndex++) { RawObject dst = dstList[dstItemIndex]; if (srcItem.StartOffset == dst.StartOffset && srcItem.GetType() == dst.GetType()) { - for(int j = 0; j < dstItemIndex - i; j++) { - RemoveAt(i); - } + RemoveAt(i, dstItemIndex - i); goto continue2; } } @@ -247,22 +277,22 @@ namespace ICSharpCode.AvalonEdit.XmlParser dstItem.UpdateDataFrom(srcItem); i++; continue; } - // Remove whitespace in hope that element update will occur next - if (dstItem is RawText) { - RemoveAt(i); + // Remove fluf in hope that element/attribute update will occur next + if (!(dstItem is RawElement) && !(dstItem is RawAttribute)) { + RemoveAt(i, 1); continue; } // Otherwise just add the item { - Insert(i, srcList[i]); + Insert(i, new RawObject[] {srcList[i]}.ToList()); i++; continue; } // Continue for inner loops continue2:; } // Remove extra items - while(dstList.Count > srcList.Count) { - RemoveAt(srcList.Count); + if (dstList.Count > srcList.Count) { + RemoveAt(srcList.Count, dstList.Count - srcList.Count); } } } @@ -357,9 +387,9 @@ namespace ICSharpCode.AvalonEdit.XmlParser public ObservableCollection Helper_AttributesAndElements { get { - return new MergedObservableCollection( - new FilteredObservableCollection(this.StartTag.Children, x => x is RawAttribute), - new FilteredObservableCollection(this.Children, x => x is RawElement) + return new MergedCollection, RawObject>( + new FilteredCollection, RawObject>(this.StartTag.Children, x => x is RawAttribute), + new FilteredCollection, RawObject>(this.Children, x => x is RawElement) ); } } @@ -394,9 +424,8 @@ namespace ICSharpCode.AvalonEdit.XmlParser if (!firstUpdate) LogLinq("Updating XElement Attributes of '{0}'", this.StartTag.Name); xElem.ReplaceAttributes(); // Otherwise we get duplicate item exception - xElem.ReplaceAttributes( - this.StartTag.Children.OfType().Select(x => x.GetXAttribute()).ToArray() - ); + XAttribute[] attrs = this.StartTag.Children.OfType().Select(x => x.GetXAttribute()).Distinct(new AttributeNameComparer()).ToArray(); + xElem.ReplaceAttributes(attrs); } void UpdateXElementChildren(bool firstUpdate) @@ -408,6 +437,19 @@ namespace ICSharpCode.AvalonEdit.XmlParser ); } + class AttributeNameComparer: IEqualityComparer + { + public bool Equals(XAttribute x, XAttribute y) + { + return x.Name == y.Name; + } + + public int GetHashCode(XAttribute obj) + { + return obj.Name.GetHashCode(); + } + } + public override string ToString() { return string.Format("[{0} '{1}{2}{3}' Attr:{4} Chld:{5}]", base.ToString(), this.StartTag.OpeningBracket, this.StartTag.Name, this.StartTag.ClosingBracket, this.StartTag.Children.Count, this.Children.Count); @@ -461,7 +503,8 @@ namespace ICSharpCode.AvalonEdit.XmlParser if (xAttr.Name == EncodeXName(this.Name, this.Namesapce)) { xAttr.Value = this.Value ?? string.Empty; } else { - xAttr.Remove(); + XElement xParent = xAttr.Parent; + if (xAttr.Parent != null) xAttr.Remove(); // Duplicate items are not added xAttr = null; deleted = true; // No longer get events for this instance ((RawElement)this.Parent.Parent).UpdateXElementAttributes(false);