aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs
blob: 080d538e8123d966612bc07c0e2473ba579e9a78 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// 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.Globalization;
using System.Linq;

using Tango.Scripting.Editors.Document;

namespace Tango.Scripting.Editors.Xml
{
	/// <summary>
	/// Logical grouping of other nodes together.
	/// </summary>
	public class AXmlElement: AXmlContainer
	{
		/// <summary> No tags are missing anywhere within this element (recursive) </summary>
		public bool IsProperlyNested { get; set; }
		/// <returns> True in wellformed XML </returns>
		public bool HasStartOrEmptyTag { get; set; }
		/// <returns> True in wellformed XML </returns>
		public bool HasEndTag { get; set; }
		
		/// <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");
			base.DebugCheckConsistency(checkParentPointers);
		}
		
		#region Helpper methods
		
		/// <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 (this.HasStartOrEmptyTag) {
					return this.StartTag.Attributes;
				} else {
					return AXmlAttributeCollection.Empty;
				}
			}
		}
		
		ObservableCollection<AXmlObject> attributesAndElements;
		
		/// <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;
			}
		}
		
		/// <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;
				}
			}
		}
		
		/// <summary> The part of name before ":" </summary>
		/// <returns> Empty string if not found </returns>
		public string Prefix {
			get {
				return GetNamespacePrefix(this.Name);
			}
		}
		
		/// <summary> The part of name after ":" </summary>
		/// <returns> Empty string if not found </returns>
		public string LocalName {
			get {
				return GetLocalName(this.Name);
			}
		}
		
		/// <summary> Resolved namespace of the name </summary>
		/// <returns> Empty string if prefix is not found </returns>
		public string Namespace {
			get {
				string prefix = this.Prefix;
				if (string.IsNullOrEmpty(prefix)) {
					return FindDefaultNamespace();
				} else {
					return ResolvePrefix(prefix);
				}
			}
		}
		
		/// <summary> Find the defualt namespace for this context </summary>
		public string FindDefaultNamespace()
		{
			AXmlElement current = this;
			while(current != null) {
				string namesapce = current.GetAttributeValue(NoNamespace, "xmlns");
				if (namesapce != null) return namesapce;
				current = current.Parent as AXmlElement;
			}
			return string.Empty; // No namesapce
		}
		
		/// <summary>
		/// Recursively resolve given prefix in this context.  Prefix must have some value.
		/// </summary>
		/// <returns> Empty string if prefix is not found </returns>
		public string ResolvePrefix(string prefix)
		{
			if (string.IsNullOrEmpty(prefix)) throw new ArgumentException("No prefix given", "prefix");
			
			// Implicit namesapces
			if (prefix == "xml") return XmlNamespace;
			if (prefix == "xmlns") return XmlnsNamespace;
			
			AXmlElement current = this;
			while(current != null) {
				string namesapce = current.GetAttributeValue(XmlnsNamespace, prefix);
				if (namesapce != null) return namesapce;
				current = current.Parent as AXmlElement;
			}
			return NoNamespace; // Can not find prefix
		}
		
		/// <summary>
		/// Get unquoted value of attribute.
		/// It looks in the no namespace (empty string).
		/// </summary>
		/// <returns>Null if not found</returns>
		public string GetAttributeValue(string localName)
		{
			return GetAttributeValue(NoNamespace, localName);
		}
		
		/// <summary>
		/// Get unquoted value of attribute
		/// </summary>
		/// <param name="namespace">Namespace.  Can be no namepace (empty string), which is the default for attributes.</param>
		/// <param name="localName">Local name - text after ":"</param>
		/// <returns>Null if not found</returns>
		public string GetAttributeValue(string @namespace, string localName)
		{
			@namespace = @namespace ?? string.Empty;
			foreach(AXmlAttribute attr in this.Attributes.GetByLocalName(localName)) {
				DebugAssert(attr.LocalName == localName, "Bad hashtable");
				if (attr.Namespace == @namespace) {
					return attr.Value;
				}
			}
			return null;
		}
		
		#endregion
		
		/// <inheritdoc/>
		public override void AcceptVisitor(IAXmlVisitor visitor)
		{
			visitor.VisitElement(this);
		}
		
		/// <inheritdoc/>
		public override string ToString()
		{
			return string.Format(CultureInfo.InvariantCulture, "[{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");
		}
	}
}