From 57385217a3f83f7a43483d6bd2f8cf880a7be0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Sat, 15 Aug 2009 19:32:20 +0000 Subject: [PATCH] XML Parser: Handling of Elements without start tag git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4691 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- samples/XmlDOM/Window1.xaml | 2 +- samples/XmlDOM/Window1.xaml.cs | 13 --- .../Xml/AXmlAttributeCollection.cs | 6 ++ .../ICSharpCode.AvalonEdit/Xml/AXmlElement.cs | 79 ++++++++++++++----- .../ICSharpCode.AvalonEdit/Xml/AXmlObject.cs | 10 +++ .../ICSharpCode.AvalonEdit/Xml/AXmlTag.cs | 16 ++++ .../Xml/FilteredCollection.cs | 3 + .../Xml/TagMatchingHeuristics.cs | 8 +- .../ICSharpCode.AvalonEdit/Xml/TagReader.cs | 10 ++- 9 files changed, 111 insertions(+), 36 deletions(-) diff --git a/samples/XmlDOM/Window1.xaml b/samples/XmlDOM/Window1.xaml index ff48e55990..ef7a6460de 100644 --- a/samples/XmlDOM/Window1.xaml +++ b/samples/XmlDOM/Window1.xaml @@ -13,7 +13,7 @@ - + diff --git a/samples/XmlDOM/Window1.xaml.cs b/samples/XmlDOM/Window1.xaml.cs index cb883df5c8..7eff91e722 100644 --- a/samples/XmlDOM/Window1.xaml.cs +++ b/samples/XmlDOM/Window1.xaml.cs @@ -103,18 +103,5 @@ namespace XmlDOM sb.Begin(); }; } - - void BindElement(object sender, EventArgs e) - { - TextBlock textBlock = (TextBlock)sender; - AXmlElement node = (AXmlElement)textBlock.DataContext; - node.StartTag.Changed += delegate { - BindingOperations.GetBindingExpression(textBlock, TextBlock.TextProperty).UpdateTarget(); - textBlock.Background = new SolidColorBrush(Colors.LightGreen); - Storyboard sb = ((Storyboard)this.FindResource("anim")); - Storyboard.SetTarget(sb, textBlock); - sb.Begin(); - }; - } } } \ No newline at end of file diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlAttributeCollection.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlAttributeCollection.cs index 77a6985026..bbd68e9db1 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlAttributeCollection.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlAttributeCollection.cs @@ -15,6 +15,12 @@ namespace ICSharpCode.AvalonEdit.Xml /// public class AXmlAttributeCollection: FilteredCollection> { + /// Empty unbound collection + public static AXmlAttributeCollection Empty = new AXmlAttributeCollection(); + + /// Create unbound collection + protected AXmlAttributeCollection() {} + /// Wrap the given collection. Non-attributes are filtered public AXmlAttributeCollection(AXmlObjectCollection source): base(source) {} diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlElement.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlElement.cs index 7670b13a81..d6cca5d474 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlElement.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlElement.cs @@ -28,13 +28,44 @@ namespace ICSharpCode.AvalonEdit.Xml /// True in wellformed XML public bool HasEndTag { get; set; } - /// StartTag of an element. - public AXmlTag StartTag { + /// + internal override bool UpdateDataFrom(AXmlObject source) + { + if (!base.UpdateDataFrom(source)) return false; + AXmlElement src = (AXmlElement)source; + // Clear the cache for this - quite expensive + attributesAndElements = null; + if (this.IsProperlyNested != src.IsProperlyNested || + this.HasStartOrEmptyTag != src.HasStartOrEmptyTag || + this.HasEndTag != src.HasEndTag) + { + OnChanging(); + this.IsProperlyNested = src.IsProperlyNested; + this.HasStartOrEmptyTag = src.HasStartOrEmptyTag; + this.HasEndTag = src.HasEndTag; + OnChanged(); + return true; + } else { + return false; + } + } + + /// The start or empty-element tag if there is any + internal AXmlTag StartTag { get { + Assert(HasStartOrEmptyTag, "Does not have a start tag"); return (AXmlTag)this.Children[0]; } } + /// The end tag if there is any + internal AXmlTag EndTag { + get { + Assert(HasEndTag, "Does not have an end tag"); + return (AXmlTag)this.Children[this.Children.Count - 1]; + } + } + internal override void DebugCheckConsistency(bool checkParentPointers) { DebugAssert(Children.Count > 0, "No children"); @@ -43,30 +74,38 @@ namespace ICSharpCode.AvalonEdit.Xml #region Helpper methods - AXmlAttributeCollection attributes; - /// Gets attributes of the element + /// + /// Warning: this is a cenvenience method to access the attributes of the start tag. + /// However, since the start tag might be moved/replaced, this property might return + /// different values over time. + /// public AXmlAttributeCollection Attributes { get { - if (attributes == null) { - attributes = new AXmlAttributeCollection(this.StartTag.Children); + if (this.HasStartOrEmptyTag) { + return this.StartTag.Attributes; + } else { + return AXmlAttributeCollection.Empty; } - return attributes; } } ObservableCollection attributesAndElements; - // TODO: Identity - /// Gets both attributes and elements + /// Gets both attributes and elements. Expensive, avoid use. + /// Warning: the collection will regenerate after each update public ObservableCollection AttributesAndElements { get { if (attributesAndElements == null) { - attributesAndElements = new MergedCollection> ( - // New wrapper with RawObject types - new FilteredCollection>(this.StartTag.Children, x => x is AXmlAttribute), - new FilteredCollection>(this.Children, x => x is AXmlElement) - ); + if (this.HasStartOrEmptyTag) { + attributesAndElements = new MergedCollection> ( + // New wrapper with RawObject types + new FilteredCollection>(this.StartTag.Children, x => x is AXmlAttribute), + new FilteredCollection>(this.Children, x => x is AXmlElement) + ); + } else { + attributesAndElements = new FilteredCollection>(this.Children, x => x is AXmlElement); + } } return attributesAndElements; } @@ -75,7 +114,11 @@ namespace ICSharpCode.AvalonEdit.Xml /// Name with namespace prefix - exactly as in source public string Name { get { - return this.StartTag.Name; + if (this.HasStartOrEmptyTag) { + return this.StartTag.Name; + } else { + return this.EndTag.Name; + } } } @@ -83,7 +126,7 @@ namespace ICSharpCode.AvalonEdit.Xml /// Empty string if not found public string Prefix { get { - return GetNamespacePrefix(this.StartTag.Name); + return GetNamespacePrefix(this.Name); } } @@ -91,7 +134,7 @@ namespace ICSharpCode.AvalonEdit.Xml /// Empty string if not found public string LocalName { get { - return GetLocalName(this.StartTag.Name); + return GetLocalName(this.Name); } } @@ -180,7 +223,7 @@ namespace ICSharpCode.AvalonEdit.Xml /// public override string ToString() { - return string.Format("[{0} '{1}{2}{3}' Attr:{4} Chld:{5} Nest:{6}]", base.ToString(), this.StartTag.OpeningBracket, this.StartTag.Name, this.StartTag.ClosingBracket, this.StartTag.Children.Count, this.Children.Count, this.IsProperlyNested ? "Ok" : "Bad"); + return string.Format("[{0} '{1}' Attr:{2} Chld:{3} Nest:{4}]", base.ToString(), this.Name, this.HasStartOrEmptyTag ? this.StartTag.Children.Count : 0, this.Children.Count, this.IsProperlyNested ? "Ok" : "Bad"); } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlObject.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlObject.cs index 93fdfc1dca..3ae3a0944c 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlObject.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlObject.cs @@ -74,6 +74,11 @@ namespace ICSharpCode.AvalonEdit.Xml if (doc != null) { doc.OnObjectChanging(this); } + // As a convenience, also rasie an event for the parent element + AXmlTag me = this as AXmlTag; + if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) { + me.Parent.OnChanging(); + } } /// Raises Changed event @@ -87,6 +92,11 @@ namespace ICSharpCode.AvalonEdit.Xml if (doc != null) { doc.OnObjectChanged(this); } + // As a convenience, also rasie an event for the parent element + AXmlTag me = this as AXmlTag; + if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) { + me.Parent.OnChanged(); + } } List syntaxErrors; diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlTag.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlTag.cs index 1cbd57a1a8..236610152f 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlTag.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlTag.cs @@ -50,6 +50,22 @@ namespace ICSharpCode.AvalonEdit.Xml /// True if tag starts with "<!" public bool IsUnknownBang { get { return OpeningBracket == " Gets attributes of the tag (if applicable) + public AXmlAttributeCollection Attributes { + get { + if (attributes == null) { + attributes = new AXmlAttributeCollection(this.Children); + } + return attributes; + } + } + + #endregion + internal override void DebugCheckConsistency(bool checkParentPointers) { Assert(OpeningBracket != null, "Null OpeningBracket"); diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/FilteredCollection.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/FilteredCollection.cs index 626f657542..54f8b1e161 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/FilteredCollection.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/FilteredCollection.cs @@ -23,6 +23,9 @@ namespace ICSharpCode.AvalonEdit.Xml Predicate condition; List srcPtrs = new List(); // Index to the original collection + /// Create unbound collection + protected FilteredCollection() {} + /// Wrap the given collection. Items of type other then T are filtered public FilteredCollection(C source) : this (source, x => true) { } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagMatchingHeuristics.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagMatchingHeuristics.cs index f177a8da3b..216c528eb3 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagMatchingHeuristics.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagMatchingHeuristics.cs @@ -104,7 +104,9 @@ namespace ICSharpCode.AvalonEdit.Xml } // Read content and end tag - if (element.StartTag.IsStartTag || startTag == StartTagPlaceholder) { + if (startTag == StartTagPlaceholder || // Check first in case the start tag is null + element.StartTag.IsStartTag) + { while(true) { AXmlTag currTag = objStream.Current as AXmlTag; // Peek if (currTag == EndTagPlaceholder) { @@ -115,7 +117,7 @@ namespace ICSharpCode.AvalonEdit.Xml element.IsProperlyNested = false; break; } else if (currTag != null && currTag.IsEndTag) { - if (currTag.Name != element.StartTag.Name) { + if (element.HasStartOrEmptyTag && currTag.Name != element.StartTag.Name) { TagReader.OnSyntaxError(element, currTag.StartOffset + 2, currTag.StartOffset + 2 + currTag.Name.Length, "Expected '{0}'. End tag must have same name as start tag.", element.StartTag.Name); } @@ -139,6 +141,8 @@ namespace ICSharpCode.AvalonEdit.Xml element.StartOffset = element.FirstChild.StartOffset; element.EndOffset = element.LastChild.EndOffset; + AXmlParser.Assert(element.HasStartOrEmptyTag || element.HasEndTag, "Must have at least start or end tag"); + AXmlParser.Log("Constructed {0}", element); trackedSegments.AddParsedObject(element, null); // Need all elements in cache for offset tracking return element; diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagReader.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagReader.cs index 6a1b68a53a..400baf09bc 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagReader.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagReader.cs @@ -115,7 +115,12 @@ namespace ICSharpCode.AvalonEdit.Xml continue; // End of file might be next } if (TryPeek('<')) break; - if (TryPeek('>') || TryPeek('/') || TryPeek('?')) break; // End tag + string endBr; + int endBrStart = this.CurrentLocation; // Just peek + if (TryReadClosingBracket(out endBr)) { // End tag + GoBack(endBrStart); + break; + } // We have "=\'\"" or name - read attribute tag.AddChild(ReadAttribulte()); @@ -383,7 +388,8 @@ namespace ICSharpCode.AvalonEdit.Xml if (TryPeek('"') || TryPeek('\'')) return; } // Opening/closing tag - if (TryPeekAnyOf('<', '/', '>')) { + string endBr; + if (TryPeek('<') || TryReadClosingBracket(out endBr)) { GoBack(start); return; }