зеркало из https://github.com/DeGsoft/maui-linux.git
681 строка
27 KiB
C#
681 строка
27 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml;
|
|
using Mono.Cecil;
|
|
using Mono.Cecil.Cil;
|
|
using Mono.Cecil.Rocks;
|
|
using Xamarin.Forms.Internals;
|
|
using Xamarin.Forms.Xaml;
|
|
|
|
namespace Xamarin.Forms.Build.Tasks
|
|
{
|
|
class SetPropertiesVisitor : IXamlNodeVisitor
|
|
{
|
|
static int dtcount;
|
|
|
|
readonly IList<XmlName> skips = new List<XmlName>
|
|
{
|
|
XmlName.xKey,
|
|
XmlName.xTypeArguments,
|
|
XmlName.xArguments,
|
|
XmlName.xFactoryMethod,
|
|
XmlName.xName
|
|
};
|
|
|
|
public SetPropertiesVisitor(ILContext context, bool stopOnResourceDictionary = false)
|
|
{
|
|
Context = context;
|
|
Module = context.Body.Method.Module;
|
|
StopOnResourceDictionary = stopOnResourceDictionary;
|
|
}
|
|
|
|
public ILContext Context { get; }
|
|
|
|
ModuleDefinition Module { get; }
|
|
|
|
public bool VisitChildrenFirst
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
public bool StopOnDataTemplate
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
public bool StopOnResourceDictionary { get; }
|
|
|
|
public void Visit(ValueNode node, INode parentNode)
|
|
{
|
|
//TODO support Label text as element
|
|
XmlName propertyName;
|
|
if (!TryGetPropertyName(node, parentNode, out propertyName))
|
|
{
|
|
if (!IsCollectionItem(node, parentNode))
|
|
return;
|
|
string contentProperty;
|
|
if (!Context.Variables.ContainsKey((IElementNode)parentNode))
|
|
return;
|
|
var parentVar = Context.Variables[(IElementNode)parentNode];
|
|
if ((contentProperty = GetContentProperty(parentVar.VariableType)) != null)
|
|
propertyName = new XmlName(((IElementNode)parentNode).NamespaceURI, contentProperty);
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (skips.Contains(propertyName))
|
|
return;
|
|
if (node.SkipPrefix((node.NamespaceResolver ?? parentNode.NamespaceResolver)?.LookupPrefix(propertyName.NamespaceURI)))
|
|
return;
|
|
if (propertyName.NamespaceURI == "http://schemas.openxmlformats.org/markup-compatibility/2006" &&
|
|
propertyName.LocalName == "Ignorable")
|
|
{
|
|
(parentNode.IgnorablePrefixes ?? (parentNode.IgnorablePrefixes = new List<string>())).AddRange(
|
|
(node.Value as string).Split(','));
|
|
return;
|
|
}
|
|
SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node);
|
|
}
|
|
|
|
public void Visit(MarkupNode node, INode parentNode)
|
|
{
|
|
}
|
|
|
|
public void Visit(ElementNode node, INode parentNode)
|
|
{
|
|
if (node.SkipPrefix((node.NamespaceResolver ?? parentNode.NamespaceResolver)?.LookupPrefix(node.NamespaceURI)))
|
|
return;
|
|
|
|
//if this node is an IMarkupExtension, invoke ProvideValue() and replace the variable
|
|
var vardef = Context.Variables[node];
|
|
var vardefref = new VariableDefinitionReference(vardef);
|
|
Context.IL.Append(ProvideValue(vardefref, Context, Module, node));
|
|
if (vardef != vardefref.VariableDefinition)
|
|
{
|
|
vardef = vardefref.VariableDefinition;
|
|
Context.Body.Variables.Add(vardef);
|
|
Context.Variables[node] = vardef;
|
|
}
|
|
|
|
XmlName propertyName;
|
|
if (TryGetPropertyName(node, parentNode, out propertyName))
|
|
{
|
|
if (skips.Contains(propertyName))
|
|
return;
|
|
|
|
if (propertyName == XmlName._CreateContent)
|
|
SetDataTemplate((IElementNode)parentNode, node, Context, node);
|
|
else
|
|
SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node);
|
|
}
|
|
else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode)
|
|
{
|
|
// Collection element, implicit content, or implicit collection element.
|
|
string contentProperty;
|
|
var parentVar = Context.Variables[(IElementNode)parentNode];
|
|
if (parentVar.VariableType.ImplementsInterface(Module.Import(typeof (IEnumerable))))
|
|
{
|
|
var elementType = parentVar.VariableType;
|
|
if (elementType.FullName != "Xamarin.Forms.ResourceDictionary")
|
|
{
|
|
var adderTuple = elementType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).First();
|
|
var adderRef = Module.Import(adderTuple.Item1);
|
|
adderRef = Module.Import(adderRef.ResolveGenericParameters(adderTuple.Item2, Module));
|
|
|
|
Context.IL.Emit(OpCodes.Ldloc, parentVar);
|
|
Context.IL.Emit(OpCodes.Ldloc, vardef);
|
|
Context.IL.Emit(OpCodes.Callvirt, adderRef);
|
|
if (adderRef.ReturnType.FullName != "System.Void")
|
|
Context.IL.Emit(OpCodes.Pop);
|
|
}
|
|
}
|
|
else if ((contentProperty = GetContentProperty(parentVar.VariableType)) != null)
|
|
{
|
|
var name = new XmlName(node.NamespaceURI, contentProperty);
|
|
if (skips.Contains(name))
|
|
return;
|
|
SetPropertyValue(Context.Variables[(IElementNode)parentNode], name, node, Context, node);
|
|
}
|
|
}
|
|
else if (IsCollectionItem(node, parentNode) && parentNode is ListNode)
|
|
{
|
|
// IL_000d: ldloc.2
|
|
// IL_000e: callvirt instance class [mscorlib]System.Collections.Generic.IList`1<!0> class [Xamarin.Forms.Core]Xamarin.Forms.Layout`1<class [Xamarin.Forms.Core]Xamarin.Forms.View>::get_Children()
|
|
// IL_0013: ldloc.0
|
|
// IL_0014: callvirt instance void class [mscorlib]System.Collections.Generic.ICollection`1<class [Xamarin.Forms.Core]Xamarin.Forms.View>::Add(!0)
|
|
|
|
var parentList = (ListNode)parentNode;
|
|
var parent = Context.Variables[((IElementNode)parentNode.Parent)];
|
|
|
|
if (skips.Contains(parentList.XmlName))
|
|
return;
|
|
|
|
var elementType = parent.VariableType;
|
|
var localname = parentList.XmlName.LocalName;
|
|
|
|
GetNameAndTypeRef(ref elementType, parentList.XmlName.NamespaceURI, ref localname, Context, node);
|
|
|
|
TypeReference propertyDeclaringType;
|
|
var property = elementType.GetProperty(pd => pd.Name == localname, out propertyDeclaringType);
|
|
MethodDefinition propertyGetter;
|
|
|
|
if (property != null && (propertyGetter = property.GetMethod) != null && propertyGetter.IsPublic)
|
|
{
|
|
var propertyGetterRef = Module.Import(propertyGetter);
|
|
propertyGetterRef = Module.Import(propertyGetterRef.ResolveGenericParameters(propertyDeclaringType, Module));
|
|
var propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(propertyDeclaringType);
|
|
|
|
var adderTuple = propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).First();
|
|
var adderRef = Module.Import(adderTuple.Item1);
|
|
adderRef = Module.Import(adderRef.ResolveGenericParameters(adderTuple.Item2, Module));
|
|
|
|
Context.IL.Emit(OpCodes.Ldloc, parent);
|
|
Context.IL.Emit(OpCodes.Callvirt, propertyGetterRef);
|
|
Context.IL.Emit(OpCodes.Ldloc, vardef);
|
|
Context.IL.Emit(OpCodes.Callvirt, adderRef);
|
|
if (adderRef.ReturnType.FullName != "System.Void")
|
|
Context.IL.Emit(OpCodes.Pop);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Visit(RootNode node, INode parentNode)
|
|
{
|
|
}
|
|
|
|
public void Visit(ListNode node, INode parentNode)
|
|
{
|
|
}
|
|
|
|
public static bool TryGetPropertyName(INode node, INode parentNode, out XmlName name)
|
|
{
|
|
name = default(XmlName);
|
|
var parentElement = parentNode as IElementNode;
|
|
if (parentElement == null)
|
|
return false;
|
|
foreach (var kvp in parentElement.Properties)
|
|
{
|
|
if (kvp.Value != node)
|
|
continue;
|
|
name = kvp.Key;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool IsCollectionItem(INode node, INode parentNode)
|
|
{
|
|
var parentList = parentNode as IListNode;
|
|
if (parentList == null)
|
|
return false;
|
|
return parentList.CollectionItems.Contains(node);
|
|
}
|
|
|
|
static string GetContentProperty(TypeReference typeRef)
|
|
{
|
|
var typeDef = typeRef.Resolve();
|
|
var attributes = typeDef.CustomAttributes;
|
|
var attr =
|
|
attributes.FirstOrDefault(cad => ContentPropertyAttribute.ContentPropertyTypes.Contains(cad.AttributeType.FullName));
|
|
if (attr != null)
|
|
return attr.ConstructorArguments[0].Value as string;
|
|
if (typeDef.BaseType == null)
|
|
return null;
|
|
return GetContentProperty(typeDef.BaseType);
|
|
}
|
|
|
|
public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefref, ILContext context,
|
|
ModuleDefinition module, ElementNode node)
|
|
{
|
|
GenericInstanceType markupExtension;
|
|
IList<TypeReference> genericArguments;
|
|
if (vardefref.VariableDefinition.VariableType.FullName == "Xamarin.Forms.Xaml.ArrayExtension" &&
|
|
vardefref.VariableDefinition.VariableType.ImplementsGenericInterface("Xamarin.Forms.Xaml.IMarkupExtension`1",
|
|
out markupExtension, out genericArguments))
|
|
{
|
|
var markExt = markupExtension.Resolve();
|
|
var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue");
|
|
var provideValue = module.Import(provideValueInfo);
|
|
provideValue =
|
|
module.Import(provideValue.MakeGeneric(markupExtension.GenericArguments.Select(tr => module.Import(tr)).ToArray()));
|
|
|
|
var typeNode = node.Properties[new XmlName("", "Type")];
|
|
TypeReference arrayTypeRef;
|
|
if (context.TypeExtensions.TryGetValue(typeNode, out arrayTypeRef))
|
|
vardefref.VariableDefinition = new VariableDefinition(module.Import(arrayTypeRef.MakeArrayType()));
|
|
else
|
|
vardefref.VariableDefinition = new VariableDefinition(module.Import(genericArguments.First()));
|
|
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
|
|
foreach (var instruction in node.PushServiceProvider(context))
|
|
yield return instruction;
|
|
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
|
|
|
|
if (arrayTypeRef != null)
|
|
yield return Instruction.Create(OpCodes.Castclass, module.Import(arrayTypeRef.MakeArrayType()));
|
|
yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
|
|
}
|
|
else if (vardefref.VariableDefinition.VariableType.ImplementsGenericInterface("Xamarin.Forms.Xaml.IMarkupExtension`1",
|
|
out markupExtension, out genericArguments))
|
|
{
|
|
var markExt = markupExtension.Resolve();
|
|
var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue");
|
|
var provideValue = module.Import(provideValueInfo);
|
|
provideValue =
|
|
module.Import(provideValue.MakeGeneric(markupExtension.GenericArguments.Select(tr => module.Import(tr)).ToArray()));
|
|
|
|
vardefref.VariableDefinition = new VariableDefinition(module.Import(genericArguments.First()));
|
|
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
|
|
foreach (var instruction in node.PushServiceProvider(context))
|
|
yield return instruction;
|
|
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
|
|
yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
|
|
}
|
|
else if (context.Variables[node].VariableType.ImplementsInterface(module.Import(typeof (IMarkupExtension))))
|
|
{
|
|
var markExt = module.Import(typeof (IMarkupExtension)).Resolve();
|
|
var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue");
|
|
var provideValue = module.Import(provideValueInfo);
|
|
|
|
vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
|
|
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
|
|
foreach (var instruction in node.PushServiceProvider(context))
|
|
yield return instruction;
|
|
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
|
|
yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
|
|
}
|
|
else if (context.Variables[node].VariableType.ImplementsInterface(module.Import(typeof (IValueProvider))))
|
|
{
|
|
var markExt = module.Import(typeof (IValueProvider)).Resolve();
|
|
var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue");
|
|
var provideValue = module.Import(provideValueInfo);
|
|
|
|
vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object);
|
|
yield return Instruction.Create(OpCodes.Ldloc, context.Variables[node]);
|
|
foreach (var instruction in node.PushServiceProvider(context))
|
|
yield return instruction;
|
|
yield return Instruction.Create(OpCodes.Callvirt, provideValue);
|
|
yield return Instruction.Create(OpCodes.Stloc, vardefref.VariableDefinition);
|
|
}
|
|
}
|
|
|
|
public static void SetPropertyValue(VariableDefinition parent, XmlName propertyName, INode valueNode,
|
|
ILContext context, IXmlLineInfo iXmlLineInfo)
|
|
{
|
|
var elementType = parent.VariableType;
|
|
var localName = propertyName.LocalName;
|
|
var module = context.Body.Method.Module;
|
|
var br = Instruction.Create(OpCodes.Nop);
|
|
TypeReference declaringTypeReference;
|
|
var handled = false;
|
|
|
|
//If it's an attached BP, update elementType and propertyName
|
|
var attached = GetNameAndTypeRef(ref elementType, propertyName.NamespaceURI, ref localName, context, iXmlLineInfo);
|
|
|
|
//If the target is an event, connect
|
|
// IL_0007: ldloc.0
|
|
// IL_0008: ldarg.0
|
|
// IL_0009: ldftn instance void class Xamarin.Forms.Xaml.XamlcTests.MyPage::OnButtonClicked(object, class [mscorlib]System.EventArgs)
|
|
// IL_000f: newobj instance void class [mscorlib]System.EventHandler::'.ctor'(object, native int)
|
|
// IL_0014: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.Button::add_Clicked(class [mscorlib]System.EventHandler)
|
|
|
|
var eventinfo = elementType.GetEvent(ed => ed.Name == localName);
|
|
if (eventinfo != null)
|
|
{
|
|
var value = ((ValueNode)valueNode).Value;
|
|
|
|
context.IL.Emit(OpCodes.Ldloc, parent);
|
|
if (context.Root is VariableDefinition)
|
|
context.IL.Emit(OpCodes.Ldloc, context.Root as VariableDefinition);
|
|
else if (context.Root is FieldDefinition)
|
|
{
|
|
context.IL.Emit(OpCodes.Ldarg_0);
|
|
context.IL.Emit(OpCodes.Ldfld, context.Root as FieldDefinition);
|
|
}
|
|
else
|
|
throw new InvalidProgramException();
|
|
var declaringType = context.Body.Method.DeclaringType;
|
|
if (declaringType.IsNested)
|
|
declaringType = declaringType.DeclaringType;
|
|
var handler = declaringType.AllMethods().FirstOrDefault(md => md.Name == value as string);
|
|
if (handler == null)
|
|
{
|
|
throw new XamlParseException(
|
|
string.Format("EventHandler \"{0}\" not found in type \"{1}\"", value, context.Body.Method.DeclaringType.FullName),
|
|
iXmlLineInfo);
|
|
}
|
|
context.IL.Emit(OpCodes.Ldftn, handler);
|
|
//FIXME: eventually get the right ctor instead fo the First() one, just in case another one could exists (not even sure it's possible).
|
|
var ctor = module.Import(eventinfo.EventType.Resolve().GetConstructors().First());
|
|
ctor = ctor.ResolveGenericParameters(eventinfo.EventType, module);
|
|
context.IL.Emit(OpCodes.Newobj, module.Import(ctor));
|
|
context.IL.Emit(OpCodes.Callvirt, module.Import(eventinfo.AddMethod));
|
|
return;
|
|
}
|
|
|
|
FieldReference bpRef = elementType.GetField(fd => fd.Name == localName + "Property" && fd.IsStatic && fd.IsPublic,
|
|
out declaringTypeReference);
|
|
if (bpRef != null)
|
|
{
|
|
bpRef = module.Import(bpRef.ResolveGenericParameters(declaringTypeReference));
|
|
bpRef.FieldType = module.Import(bpRef.FieldType);
|
|
}
|
|
|
|
//If Value is DynamicResource, SetDynamicResource
|
|
VariableDefinition varValue;
|
|
if (bpRef != null && valueNode is IElementNode &&
|
|
context.Variables.TryGetValue(valueNode as IElementNode, out varValue) &&
|
|
varValue.VariableType.FullName == typeof (DynamicResource).FullName)
|
|
{
|
|
var setDynamicResource =
|
|
module.Import(typeof (IDynamicResourceHandler)).Resolve().Methods.First(m => m.Name == "SetDynamicResource");
|
|
var getKey = typeof (DynamicResource).GetProperty("Key").GetMethod;
|
|
|
|
context.IL.Emit(OpCodes.Ldloc, parent);
|
|
context.IL.Emit(OpCodes.Ldsfld, bpRef);
|
|
context.IL.Emit(OpCodes.Ldloc, varValue);
|
|
context.IL.Emit(OpCodes.Callvirt, module.Import(getKey));
|
|
context.IL.Emit(OpCodes.Callvirt, module.Import(setDynamicResource));
|
|
return;
|
|
}
|
|
|
|
//If Value is a BindingBase and target is a BP, SetBinding
|
|
if (bpRef != null && valueNode is IElementNode &&
|
|
context.Variables.TryGetValue(valueNode as IElementNode, out varValue) &&
|
|
varValue.VariableType.InheritsFromOrImplements(module.Import(typeof (BindingBase))))
|
|
{
|
|
//TODO: check if parent is a BP
|
|
var setBinding = typeof (BindableObject).GetMethod("SetBinding",
|
|
new[] { typeof (BindableProperty), typeof (BindingBase) });
|
|
|
|
context.IL.Emit(OpCodes.Ldloc, parent);
|
|
context.IL.Emit(OpCodes.Ldsfld, bpRef);
|
|
context.IL.Emit(OpCodes.Ldloc, varValue);
|
|
context.IL.Emit(OpCodes.Callvirt, module.Import(setBinding));
|
|
return;
|
|
}
|
|
|
|
//If it's a BP, SetValue ()
|
|
// IL_0007: ldloc.0
|
|
// IL_0008: ldsfld class [Xamarin.Forms.Core]Xamarin.Forms.BindableProperty [Xamarin.Forms.Core]Xamarin.Forms.Label::TextProperty
|
|
// IL_000d: ldstr "foo"
|
|
// IL_0012: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.BindableObject::SetValue(class [Xamarin.Forms.Core]Xamarin.Forms.BindableProperty, object)
|
|
if (bpRef != null)
|
|
{
|
|
//TODO: check if parent is a BP
|
|
var setValue = typeof (BindableObject).GetMethod("SetValue", new[] { typeof (BindableProperty), typeof (object) });
|
|
|
|
if (valueNode is ValueNode)
|
|
{
|
|
context.IL.Emit(OpCodes.Ldloc, parent);
|
|
context.IL.Emit(OpCodes.Ldsfld, bpRef);
|
|
context.IL.Append(((ValueNode)valueNode).PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context),
|
|
true, false));
|
|
context.IL.Emit(OpCodes.Callvirt, module.Import(setValue));
|
|
return;
|
|
}
|
|
if (valueNode is IElementNode)
|
|
{
|
|
var getPropertyReturnType = module.Import(typeof (BindableProperty).GetProperty("ReturnType").GetGetMethod());
|
|
//FIXME: this should check for inheritance too
|
|
var isInstanceOfType = module.Import(typeof (Type).GetMethod("IsInstanceOfType", new[] { typeof (object) }));
|
|
var brfalse = Instruction.Create(OpCodes.Nop);
|
|
|
|
context.IL.Emit(OpCodes.Ldsfld, bpRef);
|
|
context.IL.Emit(OpCodes.Call, getPropertyReturnType);
|
|
context.IL.Emit(OpCodes.Ldloc, context.Variables[valueNode as IElementNode]);
|
|
if (context.Variables[valueNode as IElementNode].VariableType.IsValueType)
|
|
context.IL.Emit(OpCodes.Box, context.Variables[valueNode as IElementNode].VariableType);
|
|
context.IL.Emit(OpCodes.Callvirt, isInstanceOfType);
|
|
context.IL.Emit(OpCodes.Brfalse, brfalse);
|
|
context.IL.Emit(OpCodes.Ldloc, parent);
|
|
context.IL.Emit(OpCodes.Ldsfld, bpRef);
|
|
context.IL.Emit(OpCodes.Ldloc, context.Variables[(IElementNode)valueNode]);
|
|
if (context.Variables[valueNode as IElementNode].VariableType.IsValueType)
|
|
context.IL.Emit(OpCodes.Box, context.Variables[valueNode as IElementNode].VariableType);
|
|
context.IL.Emit(OpCodes.Callvirt, module.Import(setValue));
|
|
context.IL.Emit(OpCodes.Br, br);
|
|
context.IL.Append(brfalse);
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
//If it's a property, set it
|
|
// IL_0007: ldloc.0
|
|
// IL_0008: ldstr "foo"
|
|
// IL_000d: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.Label::set_Text(string)
|
|
PropertyDefinition property = elementType.GetProperty(pd => pd.Name == localName, out declaringTypeReference);
|
|
MethodDefinition propertySetter;
|
|
if (property != null && (propertySetter = property.SetMethod) != null && propertySetter.IsPublic)
|
|
{
|
|
module.Import(elementType.Resolve());
|
|
var propertySetterRef =
|
|
module.Import(module.Import(propertySetter).ResolveGenericParameters(declaringTypeReference, module));
|
|
propertySetterRef.ImportTypes(module);
|
|
var propertyType = property.ResolveGenericPropertyType(declaringTypeReference);
|
|
ValueNode vnode = null;
|
|
|
|
if ((vnode = valueNode as ValueNode) != null)
|
|
{
|
|
context.IL.Emit(OpCodes.Ldloc, parent);
|
|
context.IL.Append(vnode.PushConvertedValue(context,
|
|
propertyType,
|
|
new ICustomAttributeProvider[] { propertyType.Resolve() },
|
|
valueNode.PushServiceProvider(context), false, true));
|
|
context.IL.Emit(OpCodes.Callvirt, propertySetterRef);
|
|
|
|
context.IL.Append(br);
|
|
return;
|
|
}
|
|
if (valueNode is IElementNode)
|
|
{
|
|
var vardef = context.Variables[(ElementNode)valueNode];
|
|
var implicitOperators =
|
|
vardef.VariableType.GetMethods(md => md.IsPublic && md.IsStatic && md.IsSpecialName && md.Name == "op_Implicit",
|
|
module).ToList();
|
|
MethodReference implicitOperator = null;
|
|
if (implicitOperators.Any())
|
|
{
|
|
foreach (var op in implicitOperators)
|
|
{
|
|
var cast = op.Item1;
|
|
var opDeclTypeRef = op.Item2;
|
|
var castDef = module.Import(cast).ResolveGenericParameters(opDeclTypeRef, module);
|
|
var returnType = castDef.ReturnType;
|
|
if (returnType.IsGenericParameter)
|
|
returnType = ((GenericInstanceType)opDeclTypeRef).GenericArguments[((GenericParameter)returnType).Position];
|
|
if (returnType.FullName == propertyType.FullName &&
|
|
cast.Parameters[0].ParameterType.Name == vardef.VariableType.Name)
|
|
{
|
|
implicitOperator = castDef;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//TODO replace latest check by a runtime type check
|
|
if (implicitOperator != null || vardef.VariableType.InheritsFromOrImplements(propertyType) ||
|
|
propertyType.FullName == "System.Object" || vardef.VariableType.FullName == "System.Object")
|
|
{
|
|
context.IL.Emit(OpCodes.Ldloc, parent);
|
|
context.IL.Emit(OpCodes.Ldloc, vardef);
|
|
if (implicitOperator != null)
|
|
{
|
|
// IL_000f: call !0 class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<bool>::op_Implicit(class [Xamarin.Forms.Core]Xamarin.Forms.OnPlatform`1<!0>)
|
|
context.IL.Emit(OpCodes.Call, module.Import(implicitOperator));
|
|
}
|
|
else if (!vardef.VariableType.IsValueType && propertyType.IsValueType)
|
|
context.IL.Emit(OpCodes.Unbox_Any, module.Import(propertyType));
|
|
else if (vardef.VariableType.IsValueType && propertyType.FullName == "System.Object")
|
|
context.IL.Emit(OpCodes.Box, vardef.VariableType);
|
|
context.IL.Emit(OpCodes.Callvirt, propertySetterRef);
|
|
context.IL.Append(br);
|
|
return;
|
|
}
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
//If it's an already initialized property, add to it
|
|
MethodDefinition propertyGetter;
|
|
//TODO: check if property is assigned
|
|
if (property != null && (propertyGetter = property.GetMethod) != null && propertyGetter.IsPublic)
|
|
{
|
|
var propertyGetterRef = module.Import(propertyGetter);
|
|
propertyGetterRef = module.Import(propertyGetterRef.ResolveGenericParameters(declaringTypeReference, module));
|
|
var propertyType = propertyGetterRef.ReturnType.ResolveGenericParameters(declaringTypeReference);
|
|
|
|
//TODO check md.Parameters[0] type
|
|
var adderTuple =
|
|
propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, module).FirstOrDefault();
|
|
if (adderTuple != null)
|
|
{
|
|
var adderRef = module.Import(adderTuple.Item1);
|
|
adderRef = module.Import(adderRef.ResolveGenericParameters(adderTuple.Item2, module));
|
|
|
|
if (valueNode is IElementNode)
|
|
{
|
|
context.IL.Emit(OpCodes.Ldloc, parent);
|
|
context.IL.Emit(OpCodes.Callvirt, propertyGetterRef);
|
|
context.IL.Emit(OpCodes.Ldloc, context.Variables[(ElementNode)valueNode]);
|
|
context.IL.Emit(OpCodes.Callvirt, adderRef);
|
|
if (adderRef.ReturnType.FullName != "System.Void")
|
|
context.IL.Emit(OpCodes.Pop);
|
|
context.IL.Append(br);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (!handled)
|
|
{
|
|
throw new XamlParseException(string.Format("No property, bindable property, or event found for '{0}'", localName),
|
|
iXmlLineInfo);
|
|
}
|
|
context.IL.Append(br);
|
|
}
|
|
|
|
static bool GetNameAndTypeRef(ref TypeReference elementType, string namespaceURI, ref string localname,
|
|
ILContext context, IXmlLineInfo lineInfo)
|
|
{
|
|
var dotIdx = localname.IndexOf('.');
|
|
if (dotIdx > 0)
|
|
{
|
|
var typename = localname.Substring(0, dotIdx);
|
|
localname = localname.Substring(dotIdx + 1);
|
|
elementType = new XmlType(namespaceURI, typename, null).GetTypeReference(context.Body.Method.Module, lineInfo);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void SetDataTemplate(IElementNode parentNode, ElementNode node, ILContext parentContext,
|
|
IXmlLineInfo xmlLineInfo)
|
|
{
|
|
var parentVar = parentContext.Variables[parentNode];
|
|
//Push the DataTemplate to the stack, for setting the template
|
|
parentContext.IL.Emit(OpCodes.Ldloc, parentVar);
|
|
|
|
//Create nested class
|
|
// .class nested private auto ansi sealed beforefieldinit '<Main>c__AnonStorey0'
|
|
// extends [mscorlib]System.Object
|
|
|
|
var module = parentContext.Body.Method.Module;
|
|
var anonType = new TypeDefinition(
|
|
null,
|
|
"<" + parentContext.Body.Method.Name + ">_anonXamlCDataTemplate_" + dtcount++,
|
|
TypeAttributes.BeforeFieldInit |
|
|
TypeAttributes.Sealed |
|
|
TypeAttributes.NestedPrivate)
|
|
{
|
|
BaseType = module.TypeSystem.Object
|
|
};
|
|
|
|
parentContext.Body.Method.DeclaringType.NestedTypes.Add(anonType);
|
|
var ctor = anonType.AddDefaultConstructor();
|
|
|
|
var loadTemplate = new MethodDefinition("LoadDataTemplate",
|
|
MethodAttributes.Assembly | MethodAttributes.HideBySig,
|
|
module.TypeSystem.Object);
|
|
anonType.Methods.Add(loadTemplate);
|
|
|
|
var parentValues = new FieldDefinition("parentValues", FieldAttributes.Assembly, module.Import(typeof (object[])));
|
|
anonType.Fields.Add(parentValues);
|
|
|
|
TypeReference rootType = null;
|
|
var vdefRoot = parentContext.Root as VariableDefinition;
|
|
if (vdefRoot != null)
|
|
rootType = vdefRoot.VariableType;
|
|
var fdefRoot = parentContext.Root as FieldDefinition;
|
|
if (fdefRoot != null)
|
|
rootType = fdefRoot.FieldType;
|
|
|
|
var root = new FieldDefinition("root", FieldAttributes.Assembly, rootType);
|
|
anonType.Fields.Add(root);
|
|
|
|
//Fill the loadTemplate Body
|
|
var templateIl = loadTemplate.Body.GetILProcessor();
|
|
templateIl.Emit(OpCodes.Nop);
|
|
var templateContext = new ILContext(templateIl, loadTemplate.Body, parentValues)
|
|
{
|
|
Root = root
|
|
};
|
|
node.Accept(new CreateObjectVisitor(templateContext), null);
|
|
node.Accept(new SetNamescopesAndRegisterNamesVisitor(templateContext), null);
|
|
node.Accept(new SetFieldVisitor(templateContext), null);
|
|
node.Accept(new SetResourcesVisitor(templateContext), null);
|
|
node.Accept(new SetPropertiesVisitor(templateContext), null);
|
|
templateIl.Emit(OpCodes.Ldloc, templateContext.Variables[node]);
|
|
templateIl.Emit(OpCodes.Ret);
|
|
|
|
//Instanciate nested class
|
|
var parentIl = parentContext.IL;
|
|
parentIl.Emit(OpCodes.Newobj, ctor);
|
|
|
|
//Copy required local vars
|
|
parentIl.Emit(OpCodes.Dup); //Duplicate the nestedclass instance
|
|
parentIl.Append(node.PushParentObjectsArray(parentContext));
|
|
parentIl.Emit(OpCodes.Stfld, parentValues);
|
|
parentIl.Emit(OpCodes.Dup); //Duplicate the nestedclass instance
|
|
if (parentContext.Root is VariableDefinition)
|
|
parentIl.Emit(OpCodes.Ldloc, parentContext.Root as VariableDefinition);
|
|
else if (parentContext.Root is FieldDefinition)
|
|
{
|
|
parentIl.Emit(OpCodes.Ldarg_0);
|
|
parentIl.Emit(OpCodes.Ldfld, parentContext.Root as FieldDefinition);
|
|
}
|
|
else
|
|
throw new InvalidProgramException();
|
|
parentIl.Emit(OpCodes.Stfld, root);
|
|
|
|
//SetDataTemplate
|
|
parentIl.Emit(OpCodes.Ldftn, loadTemplate);
|
|
var funcCtor =
|
|
module.Import(typeof (Func<>))
|
|
.MakeGenericInstanceType(module.TypeSystem.Object)
|
|
.Resolve()
|
|
.Methods.First(md => md.IsConstructor && md.Parameters.Count == 2)
|
|
.MakeGeneric(module.TypeSystem.Object);
|
|
parentIl.Emit(OpCodes.Newobj, module.Import(funcCtor));
|
|
|
|
var propertySetter =
|
|
module.Import(typeof (IDataTemplate)).Resolve().Properties.First(p => p.Name == "LoadTemplate").SetMethod;
|
|
parentContext.IL.Emit(OpCodes.Callvirt, module.Import(propertySetter));
|
|
}
|
|
}
|
|
|
|
class VariableDefinitionReference
|
|
{
|
|
public VariableDefinitionReference(VariableDefinition vardef)
|
|
{
|
|
VariableDefinition = vardef;
|
|
}
|
|
|
|
public VariableDefinition VariableDefinition { get; set; }
|
|
|
|
public static implicit operator VariableDefinition(VariableDefinitionReference vardefref)
|
|
{
|
|
return vardefref.VariableDefinition;
|
|
}
|
|
}
|
|
} |