Browse Source

XML Parser: Handling of Elements without start tag

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4691 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
David Srbecký 16 years ago
parent
commit
57385217a3
  1. 2
      samples/XmlDOM/Window1.xaml
  2. 13
      samples/XmlDOM/Window1.xaml.cs
  3. 6
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlAttributeCollection.cs
  4. 67
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlElement.cs
  5. 10
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlObject.cs
  6. 16
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlTag.cs
  7. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/FilteredCollection.cs
  8. 8
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagMatchingHeuristics.cs
  9. 10
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagReader.cs

2
samples/XmlDOM/Window1.xaml

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
<TextBlock Text="XML Document" Margin="2"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type clr:AXmlElement}" ItemsSource="{Binding AttributesAndElements}">
<TextBlock Text="{Binding StartTag.Name}" Margin="2" Initialized="BindElement"/>
<TextBlock Text="{Binding Name}" Margin="2" Initialized="BindObject"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type clr:AXmlAttribute}">
<StackPanel Orientation="Horizontal" Margin="2">

13
samples/XmlDOM/Window1.xaml.cs

@ -103,18 +103,5 @@ namespace XmlDOM @@ -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();
};
}
}
}

6
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlAttributeCollection.cs

@ -15,6 +15,12 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -15,6 +15,12 @@ namespace ICSharpCode.AvalonEdit.Xml
/// </summary>
public class AXmlAttributeCollection: FilteredCollection<AXmlAttribute, AXmlObjectCollection<AXmlObject>>
{
/// <summary> Empty unbound collection </summary>
public static AXmlAttributeCollection Empty = new AXmlAttributeCollection();
/// <summary> Create unbound collection </summary>
protected AXmlAttributeCollection() {}
/// <summary> Wrap the given collection. Non-attributes are filtered </summary>
public AXmlAttributeCollection(AXmlObjectCollection<AXmlObject> source): base(source) {}

67
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlElement.cs

@ -28,13 +28,44 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -28,13 +28,44 @@ namespace ICSharpCode.AvalonEdit.Xml
/// <returns> True in wellformed XML </returns>
public bool HasEndTag { get; set; }
/// <summary> StartTag of an element. </summary>
public AXmlTag StartTag {
/// <inheritdoc/>
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;
}
}
/// <summary> The start or empty-element tag if there is any </summary>
internal AXmlTag StartTag {
get {
Assert(HasStartOrEmptyTag, "Does not have a start tag");
return (AXmlTag)this.Children[0];
}
}
/// <summary> The end tag if there is any </summary>
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 @@ -43,30 +74,38 @@ namespace ICSharpCode.AvalonEdit.Xml
#region Helpper methods
AXmlAttributeCollection attributes;
/// <summary> Gets attributes of the element </summary>
/// <remarks>
/// 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.
/// </remarks>
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<AXmlObject> attributesAndElements;
// TODO: Identity
/// <summary> Gets both attributes and elements </summary>
/// <summary> Gets both attributes and elements. Expensive, avoid use. </summary>
/// <remarks> Warning: the collection will regenerate after each update </remarks>
public ObservableCollection<AXmlObject> AttributesAndElements {
get {
if (attributesAndElements == null) {
if (this.HasStartOrEmptyTag) {
attributesAndElements = new MergedCollection<AXmlObject, ObservableCollection<AXmlObject>> (
// New wrapper with RawObject types
new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.StartTag.Children, x => x is AXmlAttribute),
new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.Children, x => x is AXmlElement)
);
} else {
attributesAndElements = new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.Children, x => x is AXmlElement);
}
}
return attributesAndElements;
}
@ -75,7 +114,11 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -75,7 +114,11 @@ namespace ICSharpCode.AvalonEdit.Xml
/// <summary> Name with namespace prefix - exactly as in source </summary>
public string Name {
get {
if (this.HasStartOrEmptyTag) {
return this.StartTag.Name;
} else {
return this.EndTag.Name;
}
}
}
@ -83,7 +126,7 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -83,7 +126,7 @@ namespace ICSharpCode.AvalonEdit.Xml
/// <returns> Empty string if not found </returns>
public string Prefix {
get {
return GetNamespacePrefix(this.StartTag.Name);
return GetNamespacePrefix(this.Name);
}
}
@ -91,7 +134,7 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -91,7 +134,7 @@ namespace ICSharpCode.AvalonEdit.Xml
/// <returns> Empty string if not found </returns>
public string LocalName {
get {
return GetLocalName(this.StartTag.Name);
return GetLocalName(this.Name);
}
}
@ -180,7 +223,7 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -180,7 +223,7 @@ namespace ICSharpCode.AvalonEdit.Xml
/// <inheritdoc/>
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");
}
}
}

10
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlObject.cs

@ -74,6 +74,11 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -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();
}
}
/// <summary> Raises Changed event </summary>
@ -87,6 +92,11 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -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<SyntaxError> syntaxErrors;

16
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/AXmlTag.cs

@ -50,6 +50,22 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -50,6 +50,22 @@ namespace ICSharpCode.AvalonEdit.Xml
/// <summary> True if tag starts with "&lt;!" </summary>
public bool IsUnknownBang { get { return OpeningBracket == "<!"; } }
#region Helpper methods
AXmlAttributeCollection attributes;
/// <summary> Gets attributes of the tag (if applicable) </summary>
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");

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/FilteredCollection.cs

@ -23,6 +23,9 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -23,6 +23,9 @@ namespace ICSharpCode.AvalonEdit.Xml
Predicate<object> condition;
List<int> srcPtrs = new List<int>(); // Index to the original collection
/// <summary> Create unbound collection </summary>
protected FilteredCollection() {}
/// <summary> Wrap the given collection. Items of type other then T are filtered </summary>
public FilteredCollection(C source) : this (source, x => true) { }

8
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagMatchingHeuristics.cs

@ -104,7 +104,9 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -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 @@ -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 @@ -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;

10
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Xml/TagReader.cs

@ -115,7 +115,12 @@ namespace ICSharpCode.AvalonEdit.Xml @@ -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 @@ -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;
}

Loading…
Cancel
Save