Browse Source

Avoid using element syntax for content properties when doing so can lead to problems with the {Binding} markup extension. Closes #14.

pull/144/head
Daniel Grunwald 14 years ago
parent
commit
5e37f38406
  1. 250
      ILSpy/BamlDecompiler.cs

250
ILSpy/BamlDecompiler.cs

@ -14,6 +14,9 @@ using System.Xml.Linq; @@ -14,6 +14,9 @@ using System.Xml.Linq;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
namespace ICSharpCode.ILSpy.Baml
{
@ -24,47 +27,239 @@ namespace ICSharpCode.ILSpy.Baml @@ -24,47 +27,239 @@ namespace ICSharpCode.ILSpy.Baml
{
}
public string DecompileBaml(MemoryStream bamlCode, string containingAssemblyFile)
abstract class XamlNode
{
bamlCode.Position = 0;
TextWriter w = new StringWriter();
public readonly List<XamlNode> Children = new List<XamlNode>();
Assembly assembly = Assembly.LoadFile(containingAssemblyFile);
public abstract void WriteTo(XamlWriter writer);
}
[Conditional("DEBUG")]
static void Log(string format, params object[] args)
{
//Debug.WriteLine(format, args);
}
sealed class XamlObjectNode : XamlNode
{
public readonly XamlType Type;
Baml2006Reader reader = new Baml2006Reader(bamlCode, new XamlReaderSettings() { ValuesMustBeString = true, LocalAssembly = assembly });
XDocument doc = new XDocument();
XamlXmlWriter writer = new XamlXmlWriter(doc.CreateWriter(), reader.SchemaContext);
public XamlObjectNode(XamlType type)
{
this.Type = type;
}
public override void WriteTo(XamlWriter writer)
{
Log("StartObject {0}", this.Type);
writer.WriteStartObject(this.Type);
Debug.Indent();
foreach (XamlNode node in this.Children)
node.WriteTo(writer);
Debug.Unindent();
Log("EndObject");
writer.WriteEndObject();
}
}
sealed class XamlGetObjectNode : XamlNode
{
public override void WriteTo(XamlWriter writer)
{
Log("GetObject");
writer.WriteGetObject();
Debug.Indent();
foreach (XamlNode node in this.Children)
node.WriteTo(writer);
Debug.Unindent();
Log("EndObject");
writer.WriteEndObject();
}
}
sealed class XamlMemberNode : XamlNode
{
public XamlMember Member;
public XamlMemberNode(XamlMember member)
{
this.Member = member;
}
public override void WriteTo(XamlWriter writer)
{
Log("StartMember {0}", this.Member);
writer.WriteStartMember(this.Member);
Debug.Indent();
foreach (XamlNode node in this.Children)
node.WriteTo(writer);
Debug.Unindent();
Log("EndMember");
writer.WriteEndMember();
}
}
sealed class XamlValueNode : XamlNode
{
public readonly object Value;
public XamlValueNode(object value)
{
this.Value = value;
}
public override void WriteTo(XamlWriter writer)
{
Log("Value {0}", this.Value);
Debug.Assert(this.Children.Count == 0);
// requires XamlReaderSettings.ValuesMustBeString = true to work properly
writer.WriteValue(this.Value);
}
}
sealed class XamlNamespaceDeclarationNode : XamlNode
{
public readonly NamespaceDeclaration Namespace;
public XamlNamespaceDeclarationNode(NamespaceDeclaration @namespace)
{
this.Namespace = @namespace;
}
public override void WriteTo(XamlWriter writer)
{
Log("NamespaceDeclaration {0}", this.Namespace);
Debug.Assert(this.Children.Count == 0);
writer.WriteNamespace(this.Namespace);
}
}
static List<XamlNode> Parse(XamlReader reader)
{
List<XamlNode> currentList = new List<XamlNode>();
Stack<List<XamlNode>> stack = new Stack<List<XamlNode>>();
while (reader.Read()) {
switch (reader.NodeType) {
case XamlNodeType.None:
break;
case XamlNodeType.StartObject:
writer.WriteStartObject(reader.Type);
XamlObjectNode obj = new XamlObjectNode(reader.Type);
currentList.Add(obj);
stack.Push(currentList);
currentList = obj.Children;
break;
case XamlNodeType.GetObject:
writer.WriteGetObject();
break;
case XamlNodeType.EndObject:
writer.WriteEndObject();
XamlGetObjectNode getObject = new XamlGetObjectNode();
currentList.Add(getObject);
stack.Push(currentList);
currentList = getObject.Children;
break;
case XamlNodeType.StartMember:
writer.WriteStartMember(reader.Member);
break;
case XamlNodeType.EndMember:
writer.WriteEndMember();
XamlMemberNode member = new XamlMemberNode(reader.Member);
currentList.Add(member);
stack.Push(currentList);
currentList = member.Children;
break;
case XamlNodeType.Value:
// requires XamlReaderSettings.ValuesMustBeString = true to work properly
writer.WriteValue(reader.Value);
currentList.Add(new XamlValueNode(reader.Value));
break;
case XamlNodeType.NamespaceDeclaration:
writer.WriteNamespace(reader.Namespace);
currentList.Add(new XamlNamespaceDeclarationNode(reader.Namespace));
break;
case XamlNodeType.EndObject:
case XamlNodeType.EndMember:
currentList = stack.Pop();
break;
default:
throw new Exception("Invalid value for XamlNodeType");
throw new InvalidOperationException("Invalid value for XamlNodeType");
}
}
if (stack.Count != 0)
throw new InvalidOperationException("Imbalanced stack");
return currentList;
}
void AvoidContentProperties(XamlNode node)
{
foreach (XamlNode child in node.Children)
AvoidContentProperties(child);
XamlObjectNode obj = node as XamlObjectNode;
if (obj != null) {
// Visit all except for the last child:
for (int i = 0; i < obj.Children.Count - 1; i++) {
// Avoids using content property syntax for simple string values, if the content property is not the last member.
// Without this, we cannot decompile &lt;GridViewColumn Header="Culture" DisplayMemberBinding="{Binding Culture}" /&gt;,
// because the Header property is the content property, but there is no way to represent the Binding as an element.
XamlMemberNode memberNode = obj.Children[i] as XamlMemberNode;
if (memberNode != null && memberNode.Member == obj.Type.ContentProperty) {
if (memberNode.Children.Count == 1 && memberNode.Children[0] is XamlValueNode) {
// By creating a clone of the XamlMember, we prevent WPF from knowing that it's the content property.
XamlMember member = memberNode.Member;
memberNode.Member = new XamlMember(member.Name, member.DeclaringType, member.IsAttachable);
}
}
}
// We also need to avoid using content properties that have a markup extension as value, as the XamlXmlWriter would always expand those:
for (int i = 0; i < obj.Children.Count; i++) {
XamlMemberNode memberNode = obj.Children[i] as XamlMemberNode;
if (memberNode != null && memberNode.Member == obj.Type.ContentProperty && memberNode.Children.Count == 1) {
XamlObjectNode me = memberNode.Children[0] as XamlObjectNode;
if (me != null && me.Type.IsMarkupExtension) {
// By creating a clone of the XamlMember, we prevent WPF from knowing that it's the content property.
XamlMember member = memberNode.Member;
memberNode.Member = new XamlMember(member.Name, member.DeclaringType, member.IsAttachable);
}
}
}
}
}
/// <summary>
/// It seems like BamlReader will always output 'x:Key' as last property. However, it must be specified as attribute in valid .xaml, so we move it to the front
/// of the attribute list.
/// </summary>
void MoveXKeyToFront(XamlNode node)
{
foreach (XamlNode child in node.Children)
MoveXKeyToFront(child);
XamlObjectNode obj = node as XamlObjectNode;
if (obj != null && obj.Children.Count > 0) {
XamlMemberNode memberNode = obj.Children[obj.Children.Count - 1] as XamlMemberNode;
if (memberNode != null && memberNode.Member == XamlLanguage.Key) {
// move memberNode in front of the first member node:
for (int i = 0; i < obj.Children.Count; i++) {
if (obj.Children[i] is XamlMemberNode) {
obj.Children.Insert(i, memberNode);
obj.Children.RemoveAt(obj.Children.Count - 1);
break;
}
}
}
}
}
public string DecompileBaml(MemoryStream bamlCode, string containingAssemblyFile)
{
bamlCode.Position = 0;
TextWriter w = new StringWriter();
Assembly assembly = Assembly.LoadFile(containingAssemblyFile);
Baml2006Reader reader = new Baml2006Reader(bamlCode, new XamlReaderSettings() { ValuesMustBeString = true, LocalAssembly = assembly });
var xamlDocument = Parse(reader);
foreach (var xamlNode in xamlDocument) {
AvoidContentProperties(xamlNode);
MoveXKeyToFront(xamlNode);
}
XDocument doc = new XDocument();
XamlXmlWriter writer = new XamlXmlWriter(doc.CreateWriter(), reader.SchemaContext, new XamlXmlWriterSettings { AssumeValidInput = true });
foreach (var xamlNode in xamlDocument)
xamlNode.WriteTo(writer);
writer.Close();
// Fix namespace references
@ -78,18 +273,7 @@ namespace ICSharpCode.ILSpy.Baml @@ -78,18 +273,7 @@ namespace ICSharpCode.ILSpy.Baml
}
}
}
// Convert x:Key into an attribute where possible
XName xKey = XName.Get("Key", "http://schemas.microsoft.com/winfx/2006/xaml");
foreach (XElement e in doc.Descendants(xKey).ToList()) {
if (e.Nodes().Count() != 1)
continue;
XText text = e.Nodes().Single() as XText;
if (text != null) {
e.Parent.SetAttributeValue(xKey, text.Value);
e.Remove();
}
}
return doc.ToString();
}

Loading…
Cancel
Save