diff --git a/samples/XmlDOM/Window1.xaml b/samples/XmlDOM/Window1.xaml
index 6fe699cc89..d9e44b3bd3 100644
--- a/samples/XmlDOM/Window1.xaml
+++ b/samples/XmlDOM/Window1.xaml
@@ -9,10 +9,10 @@
-
+
-
+
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Collections.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Collections.cs
index 4ec287876e..ab75ca8ade 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Collections.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Collections.cs
@@ -72,15 +72,18 @@ namespace ICSharpCode.AvalonEdit.XmlParser
}
///
- /// Collection that presents only some items from the wrapped collection
+ /// Collection that presents only some items from the wrapped collection.
+ /// It implicitely filters object that are not of type T (or derived).
///
- public class FilteredCollection: ObservableCollection where C: INotifyCollectionChanged, IList
+ public class FilteredCollection: ObservableCollection where C: INotifyCollectionChanged, IList
{
C source;
- Predicate condition;
+ Predicate condition;
List srcPtrs = new List(); // Index to the original collection
- public FilteredCollection(C source, Predicate condition)
+ public FilteredCollection(C source) : this (source, x => true) { }
+
+ public FilteredCollection(C source, Predicate condition)
{
this.source = source;
this.condition = condition;
@@ -95,8 +98,8 @@ namespace ICSharpCode.AvalonEdit.XmlParser
this.Clear();
srcPtrs.Clear();
for(int i = 0; i < source.Count; i++) {
- if (condition(source[i])) {
- this.Add(source[i]);
+ if (source[i] is T && condition(source[i])) {
+ this.Add((T)source[i]);
srcPtrs.Add(i);
}
}
@@ -117,7 +120,7 @@ namespace ICSharpCode.AvalonEdit.XmlParser
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])) {
+ if (e.NewItems[i] is T && condition(e.NewItems[i])) {
this.InsertItem(addIndex, (T)e.NewItems[i]);
srcPtrs.Insert(addIndex, e.NewStartingIndex + i);
addIndex++;
@@ -154,7 +157,7 @@ namespace ICSharpCode.AvalonEdit.XmlParser
///
/// Two collections in sequence
///
- public class MergedCollection: ObservableCollection where C: INotifyCollectionChanged, IList
+ public class MergedCollection: ObservableCollection where C: INotifyCollectionChanged, IList
{
C a;
C b;
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs
index 684f001414..db3070e6b6 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/RawObjects.cs
@@ -165,6 +165,58 @@ namespace ICSharpCode.AvalonEdit.XmlParser
namesapce = XmlConvert.EncodeLocalName(namesapce);
return XName.Get(name, namesapce);
}
+
+ #region Helpper methods
+
+ /// The part of name before ":". Empty string if not found
+ protected static string GetNamespacePrefix(string name)
+ {
+ int colonIndex = name.IndexOf(':');
+ if (colonIndex != -1) {
+ return name.Substring(0, colonIndex);
+ } else {
+ return string.Empty;
+ }
+ }
+
+ /// The part of name after ":". Whole name if not found
+ protected static string GetLocalName(string name)
+ {
+ int colonIndex = name.IndexOf(':');
+ if (colonIndex != -1) {
+ return name.Remove(0, colonIndex + 1);
+ } else {
+ return name ?? string.Empty;
+ }
+ }
+
+ /// Remove quoting from the given string
+ protected static string Unquote(string quoted)
+ {
+ if (string.IsNullOrEmpty(quoted)) return string.Empty;
+ char first = quoted[0];
+ if (quoted.Length == 1) return (first == '"' || first == '\'') ? string.Empty : quoted;
+ char last = quoted[quoted.Length - 1];
+ if (first == '"' || first == '\'') {
+ if (first == last) {
+ // Remove both quotes
+ return quoted.Substring(1, quoted.Length - 2);
+ } else {
+ // Remove first quote
+ return quoted.Remove(0, 1);
+ }
+ } else {
+ if (last == '"' || last == '\'') {
+ // Remove last quote
+ return quoted.Substring(0, quoted.Length - 1);
+ } else {
+ // Keep whole string
+ return quoted;
+ }
+ }
+ }
+
+ #endregion
}
///
@@ -183,12 +235,22 @@ namespace ICSharpCode.AvalonEdit.XmlParser
this.Children = new ChildrenCollection();
}
- public ObservableCollection Helper_Elements {
+ #region Helpper methods
+
+ ObservableCollection elements;
+
+ /// Gets direcly nested elements (non-recursive)
+ public ObservableCollection Elements {
get {
- return new FilteredCollection, RawObject>(this.Children, x => x is RawElement);
+ if (elements == null) {
+ elements = new FilteredCollection>(this.Children);
+ }
+ return elements;
}
}
+ #endregion
+
public override void UpdateDataFrom(RawObject source)
{
if (this.ReadCallID == source.ReadCallID) return;
@@ -576,15 +638,101 @@ namespace ICSharpCode.AvalonEdit.XmlParser
}
}
- public ObservableCollection Helper_AttributesAndElements {
+ #region Helpper methods
+
+ ObservableCollection attributes;
+
+ /// Gets attributes of the element
+ public ObservableCollection Attributes {
get {
- return new MergedCollection, RawObject>(
- new FilteredCollection, RawObject>(this.StartTag.Children, x => x is RawAttribute),
- new FilteredCollection, RawObject>(this.Children, x => x is RawElement)
- );
+ if (attributes == null) {
+ attributes = new FilteredCollection>(this.StartTag.Children);
+ }
+ return attributes;
}
}
+ ObservableCollection attributesAndElements;
+
+ /// Gets both attributes and elements
+ public ObservableCollection AttributesAndElements {
+ get {
+ if (attributesAndElements == null) {
+ attributesAndElements = new MergedCollection> (
+ // New wrapper with RawObject types
+ new FilteredCollection>(this.StartTag.Children, x => x is RawAttribute),
+ new FilteredCollection>(this.Children, x => x is RawElement)
+ );
+ }
+ return attributesAndElements;
+ }
+ }
+
+ /// The part of name before ":". Empty string if not found
+ public string NamespacePrefix {
+ get {
+ return GetNamespacePrefix(this.StartTag.Name);
+ }
+ }
+
+ /// The part of name after ":". Whole name if not found
+ public string LocalName {
+ get {
+ return GetLocalName(this.StartTag.Name);
+ }
+ }
+
+ /// Resolved namespace of the name. String empty if not found
+ public string Namespace {
+ get {
+ return ResloveNamespacePrefix(this.NamespacePrefix);
+ }
+ }
+
+ ///
+ /// Recursively resolve given prefix in this context.
+ /// If prefix is empty find "xmlns" namespace.
+ ///
+ public string ResloveNamespacePrefix(string prefix)
+ {
+ string definition = string.IsNullOrEmpty(prefix) ? "xmlns" : "xmlns:" + prefix;
+ RawElement current = this;
+ while(current != null) {
+ string namesapce = current.GetAttributeValue(definition);
+ if (namesapce != null) return namesapce;
+ current = current.Parent as RawElement;
+ }
+ return string.Empty; // Default or undefined
+ }
+
+ ///
+ /// Get unqoted value of attribute or null if not found.
+ /// It will match the local name in any namesapce.
+ ///
+ public string GetAttributeValue(string localName)
+ {
+ return GetAttributeValue(null, localName);
+ }
+
+ ///
+ /// Get unqoted value of attribute or null if not found
+ ///
+ /// Namespace. Empty stirng to match default. Null to match any.
+ /// Local name - text after ":"
+ ///
+ public string GetAttributeValue(string @namespace, string localName)
+ {
+ // TODO: More efficient
+ foreach(RawAttribute attr in this.Attributes) {
+ if (attr.LocalName == localName && (@namespace == null || attr.Namespace == @namespace)) {
+ return attr.Value;
+ }
+ }
+ return null;
+ }
+
+ #endregion
+
public override void AcceptVisitor(IXmlVisitor visitor)
{
visitor.VisitElement(this);
@@ -660,11 +808,58 @@ namespace ICSharpCode.AvalonEdit.XmlParser
///
public class RawAttribute: RawObject
{
+ /// The raw name - exactly as in source file
public string Name { get; set; }
+ /// Equals sign and surrounding whitespace
public string EqualsSign { get; set; }
- public string Value { get; set; }
+ /// The raw value - exactly as in source file (*probably* quoted)
+ public string QuotedValue { get; set; }
+
+ #region Helpper methods
- // TODO: Provide method to dereference Value - &
+ /// The part of name before ":". Empty string if not found
+ public string NamespacePrefix {
+ get {
+ return GetNamespacePrefix(this.Name);
+ }
+ }
+
+ /// The part of name after ":". Whole name if not found
+ public string LocalName {
+ get {
+ return GetLocalName(this.Name);
+ }
+ }
+
+ /// Resolved namespace of the name. String empty if not found
+ public string Namespace {
+ get {
+ RawTag tag = this.Parent as RawTag;
+ if (tag != null) {
+ RawElement elem = tag.Parent as RawElement;
+ if (elem != null) {
+ return elem.ResloveNamespacePrefix(this.NamespacePrefix);
+ }
+ }
+ return string.Empty; // Orphaned
+ }
+ }
+
+ /// Attribute is declaring namespace ("xmlns" or "xmlns:*")
+ public bool IsNamespaceDeclaration {
+ get {
+ return this.Name == "xmlns" || this.NamespacePrefix == "xmlns";
+ }
+ }
+
+ /// Unquoted value of the attribute
+ public string Value {
+ get {
+ return Unquote(this.QuotedValue);
+ }
+ }
+
+ #endregion
public override void AcceptVisitor(IXmlVisitor visitor)
{
@@ -678,11 +873,11 @@ namespace ICSharpCode.AvalonEdit.XmlParser
RawAttribute src = (RawAttribute)source;
if (this.Name != src.Name ||
this.EqualsSign != src.EqualsSign ||
- this.Value != src.Value)
+ this.QuotedValue != src.QuotedValue)
{
this.Name = src.Name;
this.EqualsSign = src.EqualsSign;
- this.Value = src.Value;
+ this.QuotedValue = src.QuotedValue;
OnChanged();
}
}
@@ -692,7 +887,7 @@ namespace ICSharpCode.AvalonEdit.XmlParser
public XAttribute GetXAttribute()
{
if (xAttr == null) {
- LogLinq("Creating XAttribute '{0}={1}'", this.Name, this.Value);
+ LogLinq("Creating XAttribute '{0}={1}'", this.Name, this.QuotedValue);
xAttr = new XAttribute(EncodeXName(this.Name), string.Empty);
xAttr.AddAnnotation(this);
bool deleted = false;
@@ -704,10 +899,10 @@ namespace ICSharpCode.AvalonEdit.XmlParser
void UpdateXAttribute(bool firstUpdate, ref bool deleted)
{
- if (!firstUpdate) LogLinq("Updating XAttribute '{0}={1}'", this.Name, this.Value);
+ if (!firstUpdate) LogLinq("Updating XAttribute '{0}={1}'", this.Name, this.QuotedValue);
if (xAttr.Name == EncodeXName(this.Name)) {
- xAttr.Value = this.Value ?? string.Empty;
+ xAttr.Value = this.QuotedValue ?? string.Empty;
} else {
XElement xParent = xAttr.Parent;
if (xAttr.Parent != null) xAttr.Remove(); // Duplicate items are not added
@@ -719,7 +914,7 @@ namespace ICSharpCode.AvalonEdit.XmlParser
public override string ToString()
{
- return string.Format("[{0} '{1}{2}{3}']", base.ToString(), this.Name, this.EqualsSign, this.Value);
+ return string.Format("[{0} '{1}{2}{3}']", base.ToString(), this.Name, this.EqualsSign, this.QuotedValue);
}
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Visitors.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Visitors.cs
index e4a604c9f1..df78a65952 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Visitors.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/Visitors.cs
@@ -109,7 +109,7 @@ namespace ICSharpCode.AvalonEdit.XmlParser
{
sb.Append(attribute.Name);
sb.Append(attribute.EqualsSign);
- sb.Append(attribute.Value);
+ sb.Append(attribute.QuotedValue);
}
/// Visit RawText
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/XmlParser.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/XmlParser.cs
index 0dd84d44a0..746c332ae0 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/XmlParser.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/XmlParser/XmlParser.cs
@@ -896,7 +896,7 @@ namespace ICSharpCode.AvalonEdit.XmlParser
OnSyntaxError(attr, valueStart, currentLocation, "Attribute value must be quoted");
}
}
- attr.Value = GetText(start, currentLocation);
+ attr.QuotedValue = GetText(start, currentLocation);
attr.EndOffset = currentLocation;