Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3070 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
8 changed files with 493 additions and 14 deletions
@ -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) |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue