diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/AssemblyInfo.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/AssemblyInfo.cs new file mode 100644 index 0000000000..48e0b155e5 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("ICSharpCode.WpfDesign.XamlDom")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: CLSCompliant(true)] diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/WpfDesign.XamlDom.csproj b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/WpfDesign.XamlDom.csproj new file mode 100644 index 0000000000..e184ef871c --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/WpfDesign.XamlDom.csproj @@ -0,0 +1,71 @@ + + + {88DA149F-21B2-48AB-82C4-28FB6BDFD783} + Debug + AnyCPU + Library + ICSharpCode.WpfDesign.XamlDom + ICSharpCode.WpfDesign.XamlDom + bin\Debug\ICSharpCode.WpfDesign.XamlDom.xml + False + False + 4 + false + True + ..\..\..\..\..\Main\ICSharpCode.SharpDevelop.snk + False + File + False + -Microsoft.Globalization#CA1303 + + + bin\Debug\ + true + Full + True + DEBUG;TRACE + False + + + bin\Release\ + False + None + False + TRACE + + + False + Auto + 4194304 + AnyCPU + 4096 + + + + + False + + + False + + + + + False + + + + + GlobalAssemblyInfo.cs + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlConstants.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlConstants.cs new file mode 100644 index 0000000000..a997b94bb9 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlConstants.cs @@ -0,0 +1,36 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.ObjectModel; + +namespace ICSharpCode.WpfDesign.XamlDom +{ + /// + /// Contains constants used by the Xaml parser. + /// + public static class XamlConstants + { + /// + /// The namespace used to identify "xmlns". + /// Value: "http://www.w3.org/2000/xmlns/" + /// + public const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/"; + + /// + /// The namespace used for the XAML schema. + /// Value: "http://schemas.microsoft.com/winfx/2006/xaml" + /// + public const string XamlNamespace = "http://schemas.microsoft.com/winfx/2006/xaml"; + + /// + /// The namespace used for the WPF schema. + /// Value: "http://schemas.microsoft.com/winfx/2006/xaml/presentation" + /// + public const string PresentationNamespace = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlDocument.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlDocument.cs new file mode 100644 index 0000000000..d1f45bd20a --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlDocument.cs @@ -0,0 +1,61 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Xml; + +namespace ICSharpCode.WpfDesign.XamlDom +{ + /// + /// Represents a .xaml document. + /// + public sealed class XamlDocument + { + XmlDocument _xmlDoc; + XamlObject _rootElement; + + /// + /// Gets the root xaml object. + /// + public XamlObject RootElement { + get { return _rootElement; } + } + + /// + /// Gets the object instance created by the root xaml object. + /// + public object RootInstance { + get { return (_rootElement != null) ? _rootElement.Instance : null; } + } + + /// + /// Saves the xaml document into the . + /// + public void Save(XmlWriter writer) + { + if (writer == null) + throw new ArgumentNullException("writer"); + _xmlDoc.Save(writer); + } + + /// + /// Internal constructor, used by XamlParser. + /// + internal XamlDocument(XmlDocument xmlDoc) + { + this._xmlDoc = xmlDoc; + } + + /// + /// Called by XamlParser to finish initializing the document. + /// + internal void ParseComplete(XamlObject rootElement) + { + this._rootElement = rootElement; + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlLoadException.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlLoadException.cs new file mode 100644 index 0000000000..e81bee8c13 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlLoadException.cs @@ -0,0 +1,48 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Runtime.Serialization; + +namespace ICSharpCode.WpfDesign.XamlDom +{ + /// + /// Exception class used for xaml loading failures. + /// + [Serializable] + public class XamlLoadException : Exception + { + /// + /// Create a new XamlLoadException instance. + /// + public XamlLoadException() + { + } + + /// + /// Create a new XamlLoadException instance. + /// + public XamlLoadException(string message) : base(message) + { + } + + /// + /// Create a new XamlLoadException instance. + /// + public XamlLoadException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Create a new XamlLoadException instance. + /// + protected XamlLoadException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlObject.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlObject.cs new file mode 100644 index 0000000000..e68a980f05 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlObject.cs @@ -0,0 +1,78 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Xml; + +namespace ICSharpCode.WpfDesign.XamlDom +{ + /// + /// Represents a xaml object element. + /// + public sealed class XamlObject : XamlPropertyValue + { + XamlDocument document; + XmlElement element; + Type elementType; + object instance; + List properties = new List(); + + /// For use by XamlParser only. + internal XamlObject(XamlDocument document, XmlElement element, Type elementType, object instance) + { + this.document = document; + this.element = element; + this.elementType = elementType; + this.instance = instance; + } + + /// For use by XamlParser only. + internal void AddProperty(XamlProperty property) + { + properties.Add(property); + } + + #region XamlPropertyValue implementation + internal override object GetValueFor(XamlPropertyInfo targetProperty) + { + return instance; + } + #endregion + + /// + /// Gets the XamlDocument where this XamlObject is declared in. + /// + public XamlDocument OwnerDocument { + get { return document; } + } + + /// + /// Gets the instance created by this object element. + /// + public object Instance { + get { return instance; } + } + + /// + /// Gets the type of this object element. + /// + public Type ElementType { + get { return elementType; } + } + + /// + /// Gets a read-only collection of properties set on this XamlObject. + /// This includes both attribute and element properties. + /// + public IList Properties { + get { + return properties.AsReadOnly(); + } + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs new file mode 100644 index 0000000000..85c3a3d3e9 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlParser.cs @@ -0,0 +1,317 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Windows.Markup; +using System.Xml; + +namespace ICSharpCode.WpfDesign.XamlDom +{ + /// + /// Class with static methods to parse XAML files and output a . + /// + public sealed class XamlParser + { + #region Static methods + /// + /// Parses a XAML document using a stream. + /// + public static XamlDocument Parse(Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + XmlDocument doc = new XmlDocument(); + doc.Load(stream); + return Parse(doc); + } + + /// + /// Parses a XAML document using a TextReader. + /// + public static XamlDocument Parse(TextReader reader) + { + if (reader == null) + throw new ArgumentNullException("reader"); + XmlDocument doc = new XmlDocument(); + doc.Load(reader); + return Parse(doc); + } + + /// + /// Parses a XAML document using an XmlReader. + /// + public static XamlDocument Parse(XmlReader reader) + { + if (reader == null) + throw new ArgumentNullException("reader"); + XmlDocument doc = new XmlDocument(); + doc.Load(reader); + return Parse(doc); + } + + /// + /// Creates a XAML document from an existing XmlDocument. + /// + internal static XamlDocument Parse(XmlDocument document) + { + if (document == null) + throw new ArgumentNullException("document"); + XamlParser p = new XamlParser(); + p.document = new XamlDocument(document); + p.document.ParseComplete(p.ParseObject(document.DocumentElement)); + return p.document; + } + #endregion + + XamlDocument document; + XamlTypeFinder typeFinder; + + private XamlParser() + { + typeFinder = XamlTypeFinder.CreateWpfTypeFinder(); + } + + Type FindType(string namespaceUri, string localName) + { + Type elementType = typeFinder.GetType(namespaceUri, localName); + if (elementType == null) + throw new XamlLoadException("Cannot find type " + localName + " in " + namespaceUri); + return elementType; + } + + static string GetAttributeNamespace(XmlAttribute attribute) + { + if (attribute.NamespaceURI.Length > 0) + return attribute.NamespaceURI; + else + return attribute.OwnerElement.NamespaceURI; + } + + XmlSpace currentXmlSpace = XmlSpace.None; + + XamlObject ParseObject(XmlElement element) + { + Type elementType = FindType(element.NamespaceURI, element.LocalName); + object instance = Activator.CreateInstance(elementType); + XamlObject obj = new XamlObject(document, element, elementType, instance); + + ISupportInitialize iSupportInitializeInstance = instance as ISupportInitialize; + if (iSupportInitializeInstance != null) { + iSupportInitializeInstance.BeginInit(); + } + + XmlSpace oldXmlSpace = currentXmlSpace; + foreach (XmlAttribute attribute in element.Attributes) { + if (attribute.NamespaceURI == XamlConstants.XmlnsNamespace) + continue; + if (attribute.Name == "xml:space") { + currentXmlSpace = (XmlSpace)Enum.Parse(typeof(XmlSpace), attribute.Value, true); + continue; + } + if (GetAttributeNamespace(attribute) == XamlConstants.XamlNamespace) + continue; + ParseObjectAttribute(obj, attribute); + } + + XamlPropertyInfo defaultProperty = GetDefaultProperty(elementType); + XamlPropertyValue setDefaultValueTo = null; + object defaultPropertyValue = null; + if (defaultProperty != null && defaultProperty.IsCollection && !element.IsEmpty) { + defaultPropertyValue = defaultProperty.GetValue(instance); + } + + foreach (XmlNode childNode in element.ChildNodes) { + XmlElement childElement = childNode as XmlElement; + if (childElement != null) { + if (ObjectChildElementIsPropertyElement(childElement)) { + // I don't know why the official XamlReader runs the property getter + // here, but let's try to imitate it as good as possible + if (defaultProperty != null && !defaultProperty.IsCollection) { + defaultProperty.GetValue(instance); + } + ParseObjectChildElementAsPropertyElement(obj, childElement, defaultProperty, defaultPropertyValue); + continue; + } + } + XamlPropertyValue childValue = ParseValue(childNode); + if (childValue != null) { + if (defaultProperty != null && defaultProperty.IsCollection) { + defaultProperty.AddValue(defaultPropertyValue, childValue); + } else { + if (setDefaultValueTo != null) + throw new XamlLoadException("default property may have only one value assigned"); + setDefaultValueTo = childValue; + } + } + } + + if (defaultProperty != null && !defaultProperty.IsCollection && !element.IsEmpty) + { + // Runs even when defaultValueSet==false! + // Again, no idea why the official XamlReader does this. + defaultProperty.GetValue(instance); + } + if (setDefaultValueTo != null) { + if (defaultProperty == null) { + throw new XamlLoadException("This element does not have a default value, cannot assign to it"); + } + defaultProperty.SetValue(instance, setDefaultValueTo.GetValueFor(defaultProperty)); + } + + if (iSupportInitializeInstance != null) { + iSupportInitializeInstance.EndInit(); + } + + currentXmlSpace = oldXmlSpace; + + return obj; + } + + XamlPropertyValue ParseValue(XmlNode childNode) + { + XmlText childText = childNode as XmlText; + if (childText != null) { + return new XamlTextValue(childText, currentXmlSpace); + } + XmlElement element = childNode as XmlElement; + if (element != null) { + return ParseObject(element); + } + return null; + } + + static XamlPropertyInfo GetDefaultProperty(Type elementType) + { + foreach (ContentPropertyAttribute cpa in elementType.GetCustomAttributes(typeof(ContentPropertyAttribute), true)) { + return FindProperty(elementType, cpa.Name); + } + return null; + } + + static XamlPropertyInfo FindProperty(Type elementType, string propertyName) + { + PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(elementType); + PropertyDescriptor propertyInfo = properties[propertyName]; + if (propertyInfo == null) { + XamlPropertyInfo pi = TryFindAttachedProperty(elementType, propertyName); + if (pi != null) { + return pi; + } else { + throw new XamlLoadException("property " + propertyName + " not found"); + } + } + return new XamlNormalPropertyInfo(propertyInfo); + } + + static XamlPropertyInfo TryFindAttachedProperty(Type elementType, string propertyName) + { + MethodInfo getMethod = elementType.GetMethod("Get" + propertyName, BindingFlags.Public | BindingFlags.Static); + MethodInfo setMethod = elementType.GetMethod("Set" + propertyName, BindingFlags.Public | BindingFlags.Static); + if (getMethod != null && setMethod != null) { + return new XamlAttachedPropertyInfo(getMethod, setMethod); + } + return null; + } + + static XamlPropertyInfo FindAttachedProperty(Type elementType, string propertyName) + { + XamlPropertyInfo pi = TryFindAttachedProperty(elementType, propertyName); + if (pi != null) { + return pi; + } else { + throw new XamlLoadException("attached property " + elementType.Name + "." + propertyName + " not found"); + } + } + + XamlPropertyInfo GetPropertyInfo(Type elementType, XmlAttribute attribute) + { + if (attribute.LocalName.Contains(".")) { + return GetPropertyInfo(elementType, GetAttributeNamespace(attribute), attribute.LocalName); + } else { + return FindProperty(elementType, attribute.LocalName); + } + } + + XamlPropertyInfo GetPropertyInfo(Type elementType, string xmlNamespace, string localName) + { + string typeName, propertyName; + SplitQualifiedIdentifier(localName, out typeName, out propertyName); + Type propertyType = FindType(xmlNamespace, typeName); + if (elementType == propertyType || propertyType.IsAssignableFrom(elementType)) { + return FindProperty(propertyType, propertyName); + } else { + // This is an attached property + return FindAttachedProperty(propertyType, propertyName); + } + } + + static void SplitQualifiedIdentifier(string qualifiedName, out string typeName, out string propertyName) + { + int pos = qualifiedName.IndexOf('.'); + Debug.Assert(pos > 0); + typeName = qualifiedName.Substring(0, pos); + propertyName = qualifiedName.Substring(pos + 1); + } + + void ParseObjectAttribute(XamlObject obj, XmlAttribute attribute) + { + XamlPropertyInfo propertyInfo = GetPropertyInfo(obj.ElementType, attribute); + XamlTextValue textValue = new XamlTextValue(attribute); + propertyInfo.SetValue(obj.Instance, textValue.GetValueFor(propertyInfo)); + obj.AddProperty(new XamlProperty(obj, propertyInfo, textValue)); + } + + static bool ObjectChildElementIsPropertyElement(XmlElement element) + { + return element.LocalName.Contains("."); + } + + void ParseObjectChildElementAsPropertyElement(XamlObject obj, XmlElement element, XamlPropertyInfo defaultProperty, object defaultPropertyValue) + { + Debug.Assert(element.LocalName.Contains(".")); + // this is a element property syntax + + XamlPropertyInfo propertyInfo = GetPropertyInfo(obj.ElementType, element.NamespaceURI, element.LocalName); + bool valueWasSet = false; + + object collectionInstance = null; + if (propertyInfo.IsCollection) { + if (defaultProperty.FullyQualifiedName == propertyInfo.FullyQualifiedName) { + collectionInstance = defaultPropertyValue; + } else { + collectionInstance = propertyInfo.GetValue(obj.Instance); + } + } + + XmlSpace oldXmlSpace = currentXmlSpace; + if (element.HasAttribute("xml:space")) { + currentXmlSpace = (XmlSpace)Enum.Parse(typeof(XmlSpace), element.GetAttribute("xml:space"), true); + } + + foreach (XmlNode childNode in element.ChildNodes) { + XamlPropertyValue childValue = ParseValue(childNode); + if (childValue != null) { + if (propertyInfo.IsCollection) { + propertyInfo.AddValue(collectionInstance, childValue); + } else { + if (valueWasSet) + throw new XamlLoadException("non-collection property may have only one child element"); + valueWasSet = true; + propertyInfo.SetValue(obj.Instance, childValue.GetValueFor(propertyInfo)); + obj.AddProperty(new XamlProperty(obj, propertyInfo, childValue)); + } + } + } + + currentXmlSpace = oldXmlSpace; + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlProperty.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlProperty.cs new file mode 100644 index 0000000000..01502f3975 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlProperty.cs @@ -0,0 +1,139 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.ComponentModel; +using System.Text; +using System.Xml; + +namespace ICSharpCode.WpfDesign.XamlDom +{ + /// + /// Describes a property on a . + /// + public sealed class XamlProperty + { + XamlObject parentObject; + XamlPropertyInfo propertyInfo; + XamlPropertyValue propertyValue; + + internal XamlProperty(XamlObject parentObject, XamlPropertyInfo propertyInfo, XamlPropertyValue propertyValue) + { + this.parentObject = parentObject; + this.propertyInfo = propertyInfo; + this.propertyValue = propertyValue; + } + + /// + /// Gets the parent object for which this property was declared. + /// + public XamlObject ParentObject { + get { return parentObject; } + } + + /*public bool IsAttributeSyntax { + get { + return attribute != null; + } + } + + public bool IsElementSyntax { + get { + return element != null; + } + } + + public bool IsImplicitDefaultProperty { + get { + return attribute == null && element == null; + } + }*/ + } + + /// + /// Used for the value of a . + /// Can be a or a . + /// + public abstract class XamlPropertyValue + { + internal abstract object GetValueFor(XamlPropertyInfo targetProperty); + } + + /// + /// A textual value in a .xaml file. + /// + public sealed class XamlTextValue : XamlPropertyValue + { + XmlAttribute attribute; + XmlText textNode; + XmlSpace xmlSpace; + + internal XamlTextValue(XmlAttribute attribute) + { + this.attribute = attribute; + } + + internal XamlTextValue(XmlText textNode, XmlSpace xmlSpace) + { + this.xmlSpace = xmlSpace; + this.textNode = textNode; + } + + /// + /// The text represented by the value. + /// + public string Text { + get { + if (attribute != null) + return attribute.Value; + else + return NormalizeWhitespace(textNode.Value); + } + set { + if (attribute != null) + attribute.Value = value; + else + textNode.Value = value; + } + } + + string NormalizeWhitespace(string text) + { + if (xmlSpace == XmlSpace.Preserve) { + return text.Replace("\r", ""); + } + StringBuilder b = new StringBuilder(); + bool wasWhitespace = true; + foreach (char c in text) { + if (char.IsWhiteSpace(c)) { + if (!wasWhitespace) { + b.Append(' '); + } + wasWhitespace = true; + } else { + wasWhitespace = false; + b.Append(c); + } + } + if (b.Length > 0 && wasWhitespace) + b.Length -= 1; + return b.ToString(); + } + + internal override object GetValueFor(XamlPropertyInfo targetProperty) + { + if (targetProperty == null) + return this.Text; + TypeConverter converter = targetProperty.TypeConverter; + if (converter != null) { + return converter.ConvertFromInvariantString(this.Text); + } else { + return this.Text; + } + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlPropertyInfo.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlPropertyInfo.cs new file mode 100644 index 0000000000..b374c27fd0 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlPropertyInfo.cs @@ -0,0 +1,139 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Windows.Markup; + +namespace ICSharpCode.WpfDesign.XamlDom +{ + /// + /// Represents a property assignable in XAML. + /// This can be a normal .NET property or an attached property. + /// + internal abstract class XamlPropertyInfo + { + public abstract object GetValue(object instance); + public abstract void SetValue(object instance, object value); + public abstract TypeConverter TypeConverter { get; } + public abstract string FullyQualifiedName { get; } + public abstract bool IsCollection { get; } + internal abstract void AddValue(object collectionInstance, XamlPropertyValue newElement); + } + + internal sealed class XamlAttachedPropertyInfo : XamlPropertyInfo + { + MethodInfo _getMethod; + MethodInfo _setMethod; + + public XamlAttachedPropertyInfo(MethodInfo getMethod, MethodInfo setMethod) + { + this._getMethod = getMethod; + this._setMethod = setMethod; + } + + public override TypeConverter TypeConverter { + get { + return TypeDescriptor.GetConverter(_getMethod.ReturnType); + } + } + + public override string FullyQualifiedName { + get { + return _getMethod.DeclaringType.FullName + "." + _getMethod.Name; + } + } + + public override bool IsCollection { + get { + return false; + } + } + + public override object GetValue(object instance) + { + return _getMethod.Invoke(null, new object[] { instance }); + } + + public override void SetValue(object instance, object value) + { + _setMethod.Invoke(null, new object[] { instance, value }); + } + + internal override void AddValue(object collectionInstance, XamlPropertyValue newElement) + { + throw new NotSupportedException(); + } + } + + internal sealed class XamlNormalPropertyInfo : XamlPropertyInfo + { + PropertyDescriptor _propertyDescriptor; + + public XamlNormalPropertyInfo(PropertyDescriptor propertyDescriptor) + { + this._propertyDescriptor = propertyDescriptor; + } + + public override object GetValue(object instance) + { + return _propertyDescriptor.GetValue(instance); + } + + public override void SetValue(object instance, object value) + { + _propertyDescriptor.SetValue(instance, value); + } + + public override TypeConverter TypeConverter { + get { + if (_propertyDescriptor.PropertyType == typeof(object)) + return null; + else + return _propertyDescriptor.Converter; + } + } + + public override string FullyQualifiedName { + get { + return _propertyDescriptor.ComponentType.FullName + "." + _propertyDescriptor.Name; + } + } + + public override bool IsCollection { + get { + return CollectionSupport.IsCollectionType(_propertyDescriptor.PropertyType); + } + } + + internal override void AddValue(object collectionInstance, XamlPropertyValue newElement) + { + _propertyDescriptor.PropertyType.InvokeMember( + "Add", BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance, + null, collectionInstance, + new object[] { + newElement.GetValueFor(null) + }, CultureInfo.InvariantCulture); + } + } + + static class CollectionSupport + { + public static bool IsCollectionType(Type type) + { + return typeof(IList).IsAssignableFrom(type) + || typeof(IDictionary).IsAssignableFrom(type) + || type.IsArray + || typeof(IAddChild).IsAssignableFrom(type); + } + + //public static + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlTypeFinder.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlTypeFinder.cs new file mode 100644 index 0000000000..596709fdb5 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlTypeFinder.cs @@ -0,0 +1,183 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Windows.Markup; + +namespace ICSharpCode.WpfDesign.XamlDom +{ + /// + /// Allows finding types in a set of assemblies. + /// + public class XamlTypeFinder : ICloneable + { + sealed class AssemblyNamespaceMapping + { + internal readonly Assembly Assembly; + internal readonly string Namespace; + + internal AssemblyNamespaceMapping(Assembly assembly, string @namespace) + { + this.Assembly = assembly; + this.Namespace = @namespace; + } + } + + sealed class XamlNamespace + { + internal List ClrNamespaces = new List(); + + internal XamlNamespace Clone() + { + XamlNamespace copy = new XamlNamespace(); + // AssemblyNamespaceMapping is immutable + copy.ClrNamespaces.AddRange(this.ClrNamespaces); + return copy; + } + } + + Dictionary namespaces = new Dictionary(); + + /// + /// Gets a type referenced in XAML. + /// + /// The XML namespace to use to look up the type. + /// This can be a registered namespace or a 'clr-namespace' value. + /// The local name of the type to find. + /// + /// The requested type, or null if it could not be found. + /// + public Type GetType(string xmlNamespace, string localName) + { + if (xmlNamespace == null) + throw new ArgumentNullException("xmlNamespace"); + if (localName == null) + throw new ArgumentNullException("localName"); + XamlNamespace ns; + if (!namespaces.TryGetValue(xmlNamespace, out ns)) { + if (xmlNamespace.StartsWith("clr-namespace:")) { + ns = namespaces[xmlNamespace] = ParseNamespace(xmlNamespace); + } else { + return null; + } + } + foreach (AssemblyNamespaceMapping mapping in ns.ClrNamespaces) { + Type type = mapping.Assembly.GetType(mapping.Namespace + "." + localName); + if (type != null) + return type; + } + return null; + } + + XamlNamespace ParseNamespace(string name) + { + Debug.Assert(name.StartsWith("clr-namespace:")); + name = name.Substring("clr-namespace:".Length); + string namespaceName, assembly; + int pos = name.IndexOf(';'); + if (pos < 0) { + namespaceName = name; + assembly = ""; + } else { + namespaceName = name.Substring(0, pos); + name = name.Substring(pos + 1).Trim(); + if (!name.StartsWith("assembly=")) { + throw new XamlLoadException("Expected: 'assembly='"); + } + assembly = name.Substring("assembly=".Length); + } + XamlNamespace ns = new XamlNamespace(); + ns.ClrNamespaces.Add(new AssemblyNamespaceMapping(LoadAssembly(assembly), namespaceName)); + return ns; + } + + /// + /// Registers XAML namespaces defined in the for lookup. + /// + public void RegisterAssembly(Assembly assembly) + { + if (assembly == null) + throw new ArgumentNullException("assembly"); + foreach (XmlnsDefinitionAttribute xmlnsDef in assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute), true)) { + XamlNamespace ns; + if (!namespaces.TryGetValue(xmlnsDef.XmlNamespace, out ns)) { + ns = namespaces[xmlnsDef.XmlNamespace] = new XamlNamespace(); + } + if (string.IsNullOrEmpty(xmlnsDef.AssemblyName)) { + ns.ClrNamespaces.Add(new AssemblyNamespaceMapping(assembly, xmlnsDef.ClrNamespace)); + } else { + Assembly asm = LoadAssembly(xmlnsDef.AssemblyName); + if (asm != null) { + ns.ClrNamespaces.Add(new AssemblyNamespaceMapping(asm, xmlnsDef.ClrNamespace)); + } + } + } + } + + /// + /// Load the assembly with the specified name. + /// You can override this method to implement custom assembly lookup. + /// + public virtual Assembly LoadAssembly(string name) + { + return Assembly.Load(name); + } + + /// + /// Clones this XamlTypeFinder. + /// + public virtual XamlTypeFinder Clone() + { + XamlTypeFinder copy = new XamlTypeFinder(); + copy.ImportFrom(this); + return copy; + } + + /// + /// Import information from another XamlTypeFinder. + /// Use this if you override Clone(). + /// + protected void ImportFrom(XamlTypeFinder source) + { + if (source == null) + throw new ArgumentNullException("source"); + foreach (KeyValuePair pair in source.namespaces) { + this.namespaces.Add(pair.Key, pair.Value.Clone()); + } + } + + object ICloneable.Clone() + { + return this.Clone(); + } + + /// + /// Creates a new XamlTypeFinder where the WPF namespaces are registered. + /// + public static XamlTypeFinder CreateWpfTypeFinder() + { + return WpfTypeFinder.Instance.Clone(); + } + + static class WpfTypeFinder + { + internal static readonly XamlTypeFinder Instance; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] + static WpfTypeFinder() + { + Instance = new XamlTypeFinder(); + Instance.RegisterAssembly(typeof(MarkupExtension).Assembly); // WindowsBase + Instance.RegisterAssembly(typeof(IAddChild).Assembly); // PresentationCore + Instance.RegisterAssembly(typeof(XamlReader).Assembly); // PresentationFramework + } + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/AssemblyInfo.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/AssemblyInfo.cs new file mode 100644 index 0000000000..5c5ff4a347 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Information about this assembly is defined by the following +// attributes. +// +// change them to the information which is associated with the assembly +// you compile. + +[assembly: AssemblyTitle("WpfDesign.XamlDom.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/ExampleClass.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/ExampleClass.cs new file mode 100644 index 0000000000..763cf44ca3 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/ExampleClass.cs @@ -0,0 +1,76 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.ComponentModel; +using System.Windows.Markup; + +namespace ICSharpCode.WpfDesign.XamlDom.Tests +{ + [ContentProperty("StringProp")] + public class ExampleClass : ISupportInitialize + { + internal static int nextUniqueIndex; + + string stringProp, otherProp, otherProp2; + int uniqueIndex = nextUniqueIndex++; + + public ExampleClass() + { + TestHelperLog.Log("ctor" + Identity); + } + + protected string Identity { + get { + return GetType().Name + " (" + uniqueIndex + ")"; + } + } + + void ISupportInitialize.BeginInit() + { + TestHelperLog.Log("BeginInit " + Identity); + } + + void ISupportInitialize.EndInit() + { + TestHelperLog.Log("EndInit " + Identity); + } + + public string StringProp { + get { + TestHelperLog.Log("StringProp.get " + Identity); + return stringProp; + } + set { + TestHelperLog.Log("StringProp.set to " + value + " - " + Identity); + stringProp = value; + } + } + + public string OtherProp { + get { + TestHelperLog.Log("OtherProp.get " + Identity); + return otherProp; + } + set { + TestHelperLog.Log("OtherProp.set to " + value + " - " + Identity); + otherProp = value; + } + } + + public string OtherProp2 { + get { + TestHelperLog.Log("OtherProp2.get " + Identity); + return otherProp2; + } + set { + TestHelperLog.Log("OtherProp2.set to " + value + " - " + Identity); + otherProp2 = value; + } + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/ExampleClassContainer.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/ExampleClassContainer.cs new file mode 100644 index 0000000000..055fdd059d --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/ExampleClassContainer.cs @@ -0,0 +1,27 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Markup; + +namespace ICSharpCode.WpfDesign.XamlDom.Tests +{ + [ContentProperty("List")] + public class ExampleClassContainer : ExampleClass + { + List list = new List(); + + public List List { + get { + TestHelperLog.Log("List.get " + Identity); + return list; + } + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/SamplesTests.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/SamplesTests.cs new file mode 100644 index 0000000000..728e0473c0 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/SamplesTests.cs @@ -0,0 +1,118 @@ +// +// +// +// +// $Revision$ +// + +using System; +using NUnit.Framework; + +namespace ICSharpCode.WpfDesign.XamlDom.Tests +{ + [TestFixture] + public class SamplesTests : TestHelper + { + /// + /// Non-trivial because of: InlineCollection wrapping a string + /// + [Test] + public void Intro1() + { + TestLoading(@" + + + + Hello, World! + + +"); + } + + /// + /// Non-trivial because of: found a bug in Control.Content handling + /// + [Test] + public void Intro2() + { + TestLoading(@" + + + + + + +"); + } + + /// + /// Non-trivial because of: use of attached properties, units for width+height + /// + [Test] + public void Intro3() + { + TestLoading(@" + + + Some Text + Some text at the bottom of the page. + Some More Text + + + + + + Some Text Below the Buttons + + + +"); + } + + /// + /// Using Hyperlinks + /// + [Test] + public void Intro4() + { + TestLoading(@" + + + Start Page + + Go To Page 2 + + +"); + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/SimpleLoadTests.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/SimpleLoadTests.cs new file mode 100644 index 0000000000..b601835774 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/SimpleLoadTests.cs @@ -0,0 +1,184 @@ +// +// +// +// +// $Revision$ +// + +using NUnit.Framework; +using System; + +namespace ICSharpCode.WpfDesign.XamlDom.Tests +{ + [TestFixture] + public class SimpleLoadTests : TestHelper + { + [Test] + public void Window() + { + TestLoading(@" + + + "); + } + + [Test] + public void WindowWithAttributes() + { + TestLoading(@" + + + "); + } + + [Test] + public void WindowWithElementAttribute() + { + TestLoading(@" + + 100 + + "); + } + + [Test] + public void ExampleClassTest() + { + TestLoading(@" + + + "); + } + + [Test] + public void ExampleClassWithStringPropAttribute() + { + TestLoading(@" + + + "); + } + + [Test] + public void ExampleClassUseDefaultProperty() + { + TestLoading(@" + + a test string + + "); + } + + [Test] + public void ExampleClassUseDefaultPropertyExplicitly() + { + TestLoading(@" + + + a test string + + + "); + } + + [Test] + public void ExampleClassUseDefaultPropertyBeforeOtherPropertyElement() + { + TestLoading(@" + + a test string + + otherValue + + + "); + } + + [Test] + public void ExampleClassUseDefaultPropertyAfterOtherPropertyElement() + { + TestLoading(@" + + + otherValue + + a test string + + "); + } + + [Test] + public void ExampleClassUseDefaultPropertyBetweenOtherPropertyElement() + { + TestLoading(@" + + + otherValue + + a test string + + otherValue2 + + + "); + } + + [Test] + public void Container() + { + TestLoading(@" + + + + + + + + "); + } + + [Test] + public void ContainerImplicitList() + { + TestLoading(@" + + + + + "); + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/TestHelper.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/TestHelper.cs new file mode 100644 index 0000000000..a4ff757af5 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/TestHelper.cs @@ -0,0 +1,68 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Text; +using System.Windows.Markup; +using System.Xml; +using System.IO; +using NUnit.Framework; + +namespace ICSharpCode.WpfDesign.XamlDom.Tests +{ + public class TestHelper + { + public static T[] ToArray(IEnumerable e) + { + return new List(e).ToArray(); + } + + public static void TestLoading(string xaml) + { + Debug.WriteLine("Load using own XamlParser:"); + ExampleClass.nextUniqueIndex = 0; + TestHelperLog.logBuilder = new StringBuilder(); + XamlDocument doc = XamlParser.Parse(new XmlTextReader(new StringReader(xaml))); + Assert.IsNotNull(doc, "doc is null"); + object ownResult = doc.RootInstance; + string ownLog = TestHelperLog.logBuilder.ToString(); + Assert.IsNotNull(ownResult, "ownResult is null"); + + Debug.WriteLine("Load using builtin XamlReader:"); + ExampleClass.nextUniqueIndex = 0; + TestHelperLog.logBuilder = new StringBuilder(); + object officialResult = XamlReader.Load(new XmlTextReader(new StringReader(xaml))); + string officialLog = TestHelperLog.logBuilder.ToString(); + Assert.IsNotNull(officialResult, "officialResult is null"); + + TestHelperLog.logBuilder = null; + // compare: + string officialSaved = XamlWriter.Save(officialResult); + string ownSaved = XamlWriter.Save(ownResult); + + Assert.AreEqual(officialSaved, ownSaved); + + // compare logs: + Assert.AreEqual(officialLog, ownLog); + } + } + + internal static class TestHelperLog + { + internal static StringBuilder logBuilder; + + internal static void Log(string text) + { + if (logBuilder != null) { + logBuilder.AppendLine(text); + Debug.WriteLine(text); + } + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/WhitespaceTests.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/WhitespaceTests.cs new file mode 100644 index 0000000000..05cc598549 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/WhitespaceTests.cs @@ -0,0 +1,100 @@ +// +// +// +// +// $Revision$ +// + +using NUnit.Framework; +using System; + +namespace ICSharpCode.WpfDesign.XamlDom.Tests +{ + [TestFixture] + public class WhitespaceTests : TestHelper + { + [Test] + public void TrimSurroundingWhitespace() + { + TestLoading(@" + + + a test string + + + "); + } + + [Test] + public void TrimConsecutiveWhitespace() + { + TestLoading(@" + + a test string + + "); + } + + [Test] + public void ConvertLineFeedToSpace() + { + TestLoading(@" + + a test + string + + "); + } + + [Test] + public void PreserveSurroundingWhitespace() + { + TestLoading(@" + + + a test string + + + "); + } + + [Test] + public void PreserveConsecutiveWhitespace() + { + TestLoading(@" + + a test string + + "); + } + + [Test] + public void PreserveLineFeed() + { + TestLoading(@" + + a test + string + + "); + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/WpfDesign.XamlDom.Tests.csproj b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/WpfDesign.XamlDom.Tests.csproj new file mode 100644 index 0000000000..b80ecba420 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/WpfDesign.XamlDom.Tests.csproj @@ -0,0 +1,63 @@ + + + {BD62F5BC-115F-4339-8B5B-96C7A21DC67E} + Debug + AnyCPU + Library + ICSharpCode.WpfDesign.XamlDom.Tests + ICSharpCode.WpfDesign.XamlDom.Tests + + + bin\Debug\ + True + Full + True + DEBUG;TRACE + + + bin\Release\ + False + None + False + TRACE + + + + + ..\..\..\..\..\Tools\NUnit\nunit.framework.dll + False + True + + + False + + + False + + + + + False + + + + + GlobalAssemblyInfo.cs + + + + + + + + + + + + + + {88DA149F-21B2-48AB-82C4-28FB6BDFD783} + WpfDesign.XamlDom + + + \ No newline at end of file diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/XamlTypeFinderTests.cs b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/XamlTypeFinderTests.cs new file mode 100644 index 0000000000..e7be09c162 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/XamlTypeFinderTests.cs @@ -0,0 +1,64 @@ +// +// +// +// +// $Revision$ +// + +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Windows.Markup; +using System.Windows; +using System.Windows.Controls; + +namespace ICSharpCode.WpfDesign.XamlDom.Tests +{ + [TestFixture] + public class XamlTypeFinderTests : TestHelper + { + XamlTypeFinder typeFinder; + + [SetUp] + public void FixtureSetUp() + { + typeFinder = XamlTypeFinder.CreateWpfTypeFinder(); + } + + [Test] + public void FindWindow() + { + Assert.AreEqual(typeof(Window), + typeFinder.GetType(XamlConstants.PresentationNamespace, "Window")); + } + + [Test] + public void FindButton() + { + Assert.AreEqual(typeof(Button), + typeFinder.GetType(XamlConstants.PresentationNamespace, "Button")); + } + + [Test] + public void FindBindingMarkupExtension() + { + Assert.AreEqual(typeof(StaticResourceExtension), + typeFinder.GetType(XamlConstants.PresentationNamespace, "StaticResourceExtension")); + } + + [Test] + public void FindNullExtension() + { + Assert.AreEqual(typeof(NullExtension), + typeFinder.GetType(XamlConstants.XamlNamespace, "NullExtension")); + } + + [Test] + public void FindExampleClass() + { + Assert.AreEqual(typeof(ExampleClass), + typeFinder.GetType("clr-namespace:ICSharpCode.WpfDesign.XamlDom.Tests;assembly=ICSharpCode.WpfDesign.XamlDom.Tests", + "ExampleClass")); + } + } +} diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/app.config b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/app.config new file mode 100644 index 0000000000..e531f7e030 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Tests/app.config @@ -0,0 +1,15 @@ + + + + +
+ + + + + + + + + diff --git a/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.sln b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.sln new file mode 100644 index 0000000000..3ce5a7c372 --- /dev/null +++ b/src/AddIns/DisplayBindings/WpfDesign/WpfDesign.sln @@ -0,0 +1,24 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +# SharpDevelop 2.1.0.2192 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfDesign.XamlDom", "WpfDesign.XamlDom\Project\WpfDesign.XamlDom.csproj", "{88DA149F-21B2-48AB-82C4-28FB6BDFD783}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfDesign.XamlDom.Tests", "WpfDesign.XamlDom\Tests\WpfDesign.XamlDom.Tests.csproj", "{BD62F5BC-115F-4339-8B5B-96C7A21DC67E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {88DA149F-21B2-48AB-82C4-28FB6BDFD783}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88DA149F-21B2-48AB-82C4-28FB6BDFD783}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88DA149F-21B2-48AB-82C4-28FB6BDFD783}.Release|Any CPU.Build.0 = Release|Any CPU + {88DA149F-21B2-48AB-82C4-28FB6BDFD783}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD62F5BC-115F-4339-8B5B-96C7A21DC67E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD62F5BC-115F-4339-8B5B-96C7A21DC67E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD62F5BC-115F-4339-8B5B-96C7A21DC67E}.Release|Any CPU.Build.0 = Release|Any CPU + {BD62F5BC-115F-4339-8B5B-96C7A21DC67E}.Release|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection +EndGlobal