Browse Source

WPF designer: added markup extension support to XamlDom.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3070 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
c2cf70303e
  1. 13
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Tests/XamlDom/ExampleClass.cs
  2. 65
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Tests/XamlDom/SimpleLoadTests.cs
  3. 14
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Tests/XamlDom/TestHelper.cs
  4. 320
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/MarkupExtensionParser.cs
  5. 7
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/WpfDesign.XamlDom.csproj
  6. 7
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlDocument.cs
  7. 75
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlTextValue.cs
  8. 6
      src/AddIns/DisplayBindings/WpfDesign/WpfDesign.sln

13
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Tests/XamlDom/ExampleClass.cs

@ -72,5 +72,18 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom @@ -72,5 +72,18 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom
otherProp2 = value;
}
}
object objectProp;
public object ObjectProp {
get {
TestHelperLog.Log("ObjectProp.get " + Identity);
return objectProp;
}
set {
TestHelperLog.Log("ObjectProp.set to " + value + " - " + Identity);
objectProp = value;
}
}
}
}

65
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Tests/XamlDom/SimpleLoadTests.cs

@ -73,6 +73,19 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom @@ -73,6 +73,19 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom
");
}
[Test]
public void ExampleClassWithEscapedBraceStringPropAttribute()
{
TestLoading(@"
<t:ExampleClass
xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
xmlns:t=""" + XamlTypeFinderTests.XamlDomTestsNamespace + @"""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
StringProp=""{}{a test string}"">
</t:ExampleClass>
");
}
[Test]
public void ExampleClassUseDefaultProperty()
{
@ -193,5 +206,57 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom @@ -193,5 +206,57 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom
</t:ExampleDependencyObject>
");
}
[Test]
public void ExampleClassObjectPropWithStringValue()
{
TestLoading(@"
<t:ExampleClass
xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
xmlns:t=""" + XamlTypeFinderTests.XamlDomTestsNamespace + @"""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
ObjectProp=""a test string"">
</t:ExampleClass>
");
}
[Test]
public void ExampleClassObjectPropWithTypeValue()
{
TestLoading(@"
<t:ExampleClass
xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
xmlns:t=""" + XamlTypeFinderTests.XamlDomTestsNamespace + @"""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
ObjectProp=""{x:Type t:ExampleClass}"">
</t:ExampleClass>
");
}
[Test]
public void ExampleClassObjectPropWithTypeValue2()
{
TestLoading(@"
<t:ExampleClass
xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
xmlns:t=""" + XamlTypeFinderTests.XamlDomTestsNamespace + @"""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
ObjectProp=""{x:Type TypeName=t:ExampleClass}"">
</t:ExampleClass>
");
}
[Test]
public void ExampleClassObjectPropWithNullValue()
{
TestLoading(@"
<t:ExampleClass
xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
xmlns:t=""" + XamlTypeFinderTests.XamlDomTestsNamespace + @"""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
ObjectProp=""{x:Null}"">
</t:ExampleClass>
");
}
}
}

14
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.Designer/Tests/XamlDom/TestHelper.cs

@ -27,6 +27,13 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom @@ -27,6 +27,13 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom
public static void TestLoading(string xaml)
{
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");
Debug.WriteLine("Load using own XamlParser:");
ExampleClass.nextUniqueIndex = 0;
TestHelperLog.logBuilder = new StringBuilder();
@ -36,13 +43,6 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom @@ -36,13 +43,6 @@ namespace ICSharpCode.WpfDesign.Tests.XamlDom
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);

320
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/MarkupExtensionParser.cs

@ -0,0 +1,320 @@ @@ -0,0 +1,320 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Windows.Markup;
using System.Xml;
namespace ICSharpCode.WpfDesign.XamlDom
{
/// <summary>
/// Tokenizer for markup extension attributes.
/// </summary>
sealed class MarkupExtensionTokenizer
{
private MarkupExtensionTokenizer() {}
string text;
int pos;
List<MarkupExtensionToken> tokens = new List<MarkupExtensionToken>();
public static List<MarkupExtensionToken> Tokenize(string text)
{
MarkupExtensionTokenizer t = new MarkupExtensionTokenizer();
t.text = text;
t.Parse();
return t.tokens;
}
void AddToken(MarkupExtensionTokenKind kind, string val)
{
tokens.Add(new MarkupExtensionToken(kind, val));
}
void Parse()
{
AddToken(MarkupExtensionTokenKind.OpenBrace, "{");
Expect('{');
ConsumeWhitespace();
CheckNotEOF();
StringBuilder b = new StringBuilder();
while (pos < text.Length && !char.IsWhiteSpace(text, pos) && text[pos] != '}')
b.Append(text[pos++]);
AddToken(MarkupExtensionTokenKind.TypeName, b.ToString());
ConsumeWhitespace();
while (pos < text.Length) {
switch (text[pos]) {
case '}':
AddToken(MarkupExtensionTokenKind.CloseBrace, "{");
pos++;
break;
case '=':
AddToken(MarkupExtensionTokenKind.Equals, "=");
pos++;
break;
case ',':
AddToken(MarkupExtensionTokenKind.Comma, ",");
pos++;
break;
case '{':
throw new XamlMarkupExtensionParseException("'{' is invalid at this location");
default:
MembernameOrString();
break;
}
ConsumeWhitespace();
}
}
void MembernameOrString()
{
StringBuilder b = new StringBuilder();
if (text[pos] == '"' || text[pos] == '\'') {
char quote = text[pos++];
CheckNotEOF();
while (!(text[pos] == quote && text[pos-1] != '\\')) {
char c = text[pos++];
if (c != '\\')
b.Append(c);
CheckNotEOF();
}
pos++; // consume closing quote
ConsumeWhitespace();
} else {
int braceTotal = 0;
while (braceTotal >= 0) {
CheckNotEOF();
switch (text[pos]) {
case '\\':
pos++;
CheckNotEOF();
b.Append(text[pos++]);
break;
case '{':
b.Append(text[pos++]);
braceTotal++;
break;
case '}':
if (braceTotal != 0) {
b.Append(text[pos++]);
}
braceTotal--;
break;
case ',':
case '=':
braceTotal = -1;
break;
default:
b.Append(text[pos++]);
break;
}
}
}
CheckNotEOF();
string valueText = b.ToString();
if (text[pos] == '=') {
AddToken(MarkupExtensionTokenKind.Membername, valueText.Trim());
} else {
AddToken(MarkupExtensionTokenKind.String, valueText);
}
}
void Expect(char c)
{
CheckNotEOF();
if (text[pos] != c)
throw new XamlMarkupExtensionParseException("Expected '" + c + "'");
pos++;
}
void ConsumeWhitespace()
{
while (pos < text.Length && char.IsWhiteSpace(text, pos))
pos++;
}
void CheckNotEOF()
{
if (pos >= text.Length)
throw new XamlMarkupExtensionParseException("Unexpected end of markup extension");
}
}
/// <summary>
/// Exception thrown when XAML loading fails because there is a syntax error in a markup extension.
/// </summary>
[Serializable]
public class XamlMarkupExtensionParseException : XamlLoadException
{
/// <summary>
/// Create a new XamlMarkupExtensionParseException instance.
/// </summary>
public XamlMarkupExtensionParseException()
{
}
/// <summary>
/// Create a new XamlMarkupExtensionParseException instance.
/// </summary>
public XamlMarkupExtensionParseException(string message) : base(message)
{
}
/// <summary>
/// Create a new XamlMarkupExtensionParseException instance.
/// </summary>
public XamlMarkupExtensionParseException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Create a new XamlMarkupExtensionParseException instance.
/// </summary>
protected XamlMarkupExtensionParseException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
enum MarkupExtensionTokenKind
{
OpenBrace,
CloseBrace,
Equals,
Comma,
TypeName,
Membername,
String
}
sealed class MarkupExtensionToken
{
public readonly MarkupExtensionTokenKind Kind;
public readonly string Value;
public MarkupExtensionToken(MarkupExtensionTokenKind kind, string value)
{
this.Kind = kind;
this.Value = value;
}
public override string ToString()
{
return "[" + Kind + " " + Value + "]";
}
}
static class MarkupExtensionParser
{
public static MarkupExtension ConstructMarkupExtension(string attributeText, XmlElement containingElement, XamlDocument document)
{
if (containingElement == null)
throw new ArgumentNullException("containingElement");
List<MarkupExtensionToken> markupExtensionTokens = MarkupExtensionTokenizer.Tokenize(attributeText);
if (markupExtensionTokens.Count < 3
|| markupExtensionTokens[0].Kind != MarkupExtensionTokenKind.OpenBrace
|| markupExtensionTokens[1].Kind != MarkupExtensionTokenKind.TypeName
|| markupExtensionTokens[markupExtensionTokens.Count-1].Kind != MarkupExtensionTokenKind.CloseBrace)
{
throw new XamlMarkupExtensionParseException("Invalid markup extension");
}
string typeName = markupExtensionTokens[1].Value;
string typeNamespaceUri;
string typeLocalName;
if (typeName.Contains(":")) {
typeNamespaceUri = containingElement.GetNamespaceOfPrefix(typeName.Substring(0, typeName.IndexOf(':')));
typeLocalName = typeName.Substring(typeName.IndexOf(':') + 1);
} else {
typeNamespaceUri = containingElement.NamespaceURI;
typeLocalName = typeName;
}
if (string.IsNullOrEmpty(typeNamespaceUri))
throw new XamlMarkupExtensionParseException("Unrecognized namespace prefix in type " + typeName);
Type extensionType = null;
if (typeNamespaceUri == "http://schemas.microsoft.com/winfx/2006/xaml" && typeLocalName == "Type") {
extensionType = typeof(TypeExtension);
} else {
extensionType = document.TypeFinder.GetType(typeNamespaceUri, typeLocalName + "Extension");
}
if (extensionType == null || !typeof(MarkupExtension).IsAssignableFrom(extensionType)) {
throw new XamlMarkupExtensionParseException("Unknown markup extension " + typeLocalName + "Extension in " + typeNamespaceUri);
}
List<string> positionalArgs = new List<string>();
List<KeyValuePair<string, string>> namedArgs = new List<KeyValuePair<string, string>>();
for (int i = 2; i < markupExtensionTokens.Count - 1; i++) {
if (markupExtensionTokens[i].Kind == MarkupExtensionTokenKind.String) {
positionalArgs.Add(markupExtensionTokens[i].Value);
} else if (markupExtensionTokens[i].Kind == MarkupExtensionTokenKind.Membername) {
if (markupExtensionTokens[i+1].Kind != MarkupExtensionTokenKind.Equals
|| markupExtensionTokens[i+2].Kind != MarkupExtensionTokenKind.String)
{
throw new XamlMarkupExtensionParseException("Invalid markup extension");
}
namedArgs.Add(new KeyValuePair<string, string>(markupExtensionTokens[i].Value,
markupExtensionTokens[i+2].Value));
i += 2;
}
}
// Find the constructor with positionalArgs.Count arguments
var ctors = extensionType.GetConstructors().Where(c => c.GetParameters().Length == positionalArgs.Count).ToList();
if (ctors.Count < 1)
throw new XamlMarkupExtensionParseException("No constructor for " + extensionType.FullName + " found that takes " + positionalArgs.Count + " arguments");
if (ctors.Count > 1)
throw new XamlMarkupExtensionParseException("Multiple constructors for " + extensionType.FullName + " found that take " + positionalArgs.Count + " arguments");
var ctorParameters = ctors[0].GetParameters();
object[] ctorArguments = new object[positionalArgs.Count];
for (int i = 0; i < ctorArguments.Length; i++) {
TypeConverter c = TypeDescriptor.GetConverter(ctorParameters[i].ParameterType);
ctorArguments[i] = XamlTextValue.AttributeTextToObject(positionalArgs[i],
containingElement,
document,
c);
}
MarkupExtension result = (MarkupExtension)ctors[0].Invoke(ctorArguments);
foreach (var pair in namedArgs) {
string memberName = pair.Key;
if (memberName.Contains(".")) {
throw new NotImplementedException();
} else {
if (memberName.Contains(":"))
memberName = memberName.Substring(memberName.IndexOf(':') + 1);
var property = extensionType.GetProperty(memberName);
if (property == null)
throw new XamlMarkupExtensionParseException("Property not found: " + extensionType.FullName + "." + memberName);
TypeConverter c = TypeDescriptor.GetConverter(property.PropertyType);
object propValue = XamlTextValue.AttributeTextToObject(pair.Value,
containingElement,
document,
c);
property.SetValue(result, propValue, null);
}
}
return result;
}
sealed class TypeExtension : System.Windows.Markup.TypeExtension
{
public TypeExtension() {}
public TypeExtension(string typeName) : base(typeName)
{
}
}
}
}

7
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/WpfDesign.XamlDom.csproj

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<PropertyGroup>
<ProjectGuid>{88DA149F-21B2-48AB-82C4-28FB6BDFD783}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
<CodeAnalysisRules>-Microsoft.Globalization#CA1303</CodeAnalysisRules>
<OutputPath>..\..\..\..\..\..\AddIns\AddIns\DisplayBindings\WpfDesign\</OutputPath>
<DocumentationFile>..\..\..\..\..\..\AddIns\AddIns\DisplayBindings\WpfDesign\ICSharpCode.WpfDesign.XamlDom.xml</DocumentationFile>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
@ -48,6 +49,9 @@ @@ -48,6 +49,9 @@
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="WindowsBase">
<Private>False</Private>
@ -60,6 +64,7 @@ @@ -60,6 +64,7 @@
<Compile Include="AssemblyInfo.cs" />
<Compile Include="CollectionElementsCollection.cs" />
<Compile Include="CollectionSupport.cs" />
<Compile Include="MarkupExtensionParser.cs" />
<Compile Include="XamlConstants.cs" />
<Compile Include="XamlDocument.cs" />
<Compile Include="XamlLoadException.cs" />

7
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlDocument.cs

@ -26,6 +26,13 @@ namespace ICSharpCode.WpfDesign.XamlDom @@ -26,6 +26,13 @@ namespace ICSharpCode.WpfDesign.XamlDom
get { return _xmlDoc; }
}
/// <summary>
/// Gets the type finder used for this XAML document.
/// </summary>
public XamlTypeFinder TypeFinder {
get { return _typeFinder; }
}
/// <summary>
/// Gets the service provider used for markup extensions in this document.
/// </summary>

75
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.XamlDom/Project/XamlTextValue.cs

@ -11,6 +11,7 @@ using System.ComponentModel; @@ -11,6 +11,7 @@ using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Windows.Markup;
using System.Xml;
namespace ICSharpCode.WpfDesign.XamlDom
@ -58,9 +59,12 @@ namespace ICSharpCode.WpfDesign.XamlDom @@ -58,9 +59,12 @@ namespace ICSharpCode.WpfDesign.XamlDom
/// </summary>
public string Text {
get {
if (attribute != null)
return attribute.Value;
else if (textValue != null)
if (attribute != null) {
if (attribute.Value.StartsWith("{}"))
return attribute.Value.Substring(2);
else
return attribute.Value;
} else if (textValue != null)
return textValue;
else if (cDataSection != null)
return cDataSection.Value;
@ -105,8 +109,14 @@ namespace ICSharpCode.WpfDesign.XamlDom @@ -105,8 +109,14 @@ namespace ICSharpCode.WpfDesign.XamlDom
return b.ToString();
}
static readonly TypeConverter stringTypeConverter = TypeDescriptor.GetConverter(typeof(string));
internal override object GetValueFor(XamlPropertyInfo targetProperty)
{
if (attribute != null) {
return AttributeTextToObject(attribute.Value, attribute.OwnerElement, document,
targetProperty != null ? targetProperty.TypeConverter : stringTypeConverter);
}
if (targetProperty == null)
return this.Text;
TypeConverter converter = targetProperty.TypeConverter;
@ -117,6 +127,65 @@ namespace ICSharpCode.WpfDesign.XamlDom @@ -117,6 +127,65 @@ namespace ICSharpCode.WpfDesign.XamlDom
}
}
internal static object AttributeTextToObject(string attributeText, XmlElement containingElement,
XamlDocument document, TypeConverter typeConverter)
{
if (typeConverter == null)
typeConverter = stringTypeConverter;
if (attributeText.StartsWith("{}")) {
return typeConverter.ConvertFromString(
document.GetTypeDescriptorContext(), CultureInfo.InvariantCulture,
attributeText.Substring(2));
} else if (attributeText.StartsWith("{")) {
XamlTypeResolverProvider xtrp = new XamlTypeResolverProvider(document, containingElement, document.ServiceProvider);
MarkupExtension extension = MarkupExtensionParser.ConstructMarkupExtension(
attributeText, containingElement, document);
return extension.ProvideValue(xtrp);
} else {
return typeConverter.ConvertFromString(
document.GetTypeDescriptorContext(), CultureInfo.InvariantCulture,
attributeText);
}
}
sealed class XamlTypeResolverProvider : IXamlTypeResolver, IServiceProvider
{
XamlDocument document;
XmlElement containingElement;
IServiceProvider baseProvider;
public XamlTypeResolverProvider(XamlDocument document, XmlElement containingElement, IServiceProvider baseProvider)
{
this.document = document;
this.containingElement = containingElement;
this.baseProvider = baseProvider;
}
public Type Resolve(string typeName)
{
string typeNamespaceUri;
string typeLocalName;
if (typeName.Contains(":")) {
typeNamespaceUri = containingElement.GetNamespaceOfPrefix(typeName.Substring(0, typeName.IndexOf(':')));
typeLocalName = typeName.Substring(typeName.IndexOf(':') + 1);
} else {
typeNamespaceUri = containingElement.NamespaceURI;
typeLocalName = typeName;
}
if (string.IsNullOrEmpty(typeNamespaceUri))
throw new XamlMarkupExtensionParseException("Unrecognized namespace prefix in type " + typeName);
return document.TypeFinder.GetType(typeNamespaceUri, typeLocalName);
}
public object GetService(Type serviceType)
{
if (serviceType == typeof(IXamlTypeResolver))
return this;
else
return baseProvider.GetService(serviceType);
}
}
internal override void RemoveNodeFromParent()
{
if (attribute != null)

6
src/AddIns/DisplayBindings/WpfDesign/WpfDesign.sln

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 9.00
# Visual Studio 2005
# SharpDevelop 2.1.0.2382
Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
# SharpDevelop 3.0.0.3067
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.Designer", "WpfDesign.Designer\Project\WpfDesign.Designer.csproj", "{78CC29AC-CC79-4355-B1F2-97936DF198AC}"

Loading…
Cancel
Save