summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs
blob: 92a53539d8679de8fb39505d5615e3be3450d74b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;

using Xamarin.Forms.Build.Tasks;
using Xamarin.Forms.Xaml;

using static System.String;

namespace Xamarin.Forms.Core.XamlC
{
	class BindablePropertyConverter : ICompiledTypeConverter
	{
		public IEnumerable<Instruction> ConvertFromString(string value, ModuleDefinition module, BaseNode node)
		{
			if (IsNullOrEmpty(value)) {
				yield return Instruction.Create(OpCodes.Ldnull);
				yield break;
			}

			FieldReference bpRef = null;
			string typeName = null, propertyName = null;

			var parts = value.Split('.');
			if (parts.Length == 1) {
				var parent = node.Parent?.Parent as IElementNode;
				if ((node.Parent as ElementNode)?.XmlType.NamespaceUri == "http://xamarin.com/schemas/2014/forms" && (node.Parent as ElementNode)?.XmlType.Name == "Setter") {
					if (parent.XmlType.NamespaceUri == "http://xamarin.com/schemas/2014/forms" &&
						(parent.XmlType.Name == "Trigger" || parent.XmlType.Name == "DataTrigger" || parent.XmlType.Name == "MultiTrigger" || parent.XmlType.Name == "Style")) {
						var ttnode = (parent as ElementNode).Properties [new XmlName("", "TargetType")];
						if (ttnode is ValueNode)
							typeName = (ttnode as ValueNode).Value as string;
						else if (ttnode is IElementNode)
							typeName = ((ttnode as IElementNode).CollectionItems.FirstOrDefault() as ValueNode)?.Value as string ?? ((ttnode as IElementNode).Properties [new XmlName("", "TypeName")] as ValueNode)?.Value as string;
					}
				} else if ((node.Parent as ElementNode)?.XmlType.NamespaceUri == "http://xamarin.com/schemas/2014/forms" && (node.Parent as ElementNode)?.XmlType.Name == "Trigger")
					typeName = ((node.Parent as ElementNode).Properties [new XmlName("", "TargetType")] as ValueNode).Value as string;
				propertyName = parts [0];
			} else if (parts.Length == 2) {
				typeName = parts [0];
				propertyName = parts [1];
			} else
				throw new XamlParseException($"Cannot convert \"{value}\" into {typeof(BindableProperty)}", node);

			var typeRef = GetTypeReference(typeName, module, node);
			if (typeRef == null)
				throw new XamlParseException($"Can't resolve {typeName}", node);
			bpRef = GetBindablePropertyFieldReference(typeRef, propertyName, module);
			if (bpRef == null)
				throw new XamlParseException($"Can't resolve {propertyName} on {typeRef.Name}", node);
			yield return Instruction.Create(OpCodes.Ldsfld, bpRef);
		}

		public static TypeReference GetTypeReference(string xmlType, ModuleDefinition module, BaseNode iNode)
		{
			var split = xmlType.Split(':');
			if (split.Length > 2)
				throw new XamlParseException($"Type \"{xmlType}\" is invalid", iNode);

			string prefix, name;
			if (split.Length == 2) {
				prefix = split [0];
				name = split [1];
			} else {
				prefix = "";
				name = split [0];
			}
			var namespaceuri = iNode.NamespaceResolver.LookupNamespace(prefix) ?? "";
			return XmlTypeExtensions.GetTypeReference(namespaceuri, name, module, iNode);
		}

		public static FieldReference GetBindablePropertyFieldReference(TypeReference typeRef, string propertyName, ModuleDefinition module)
		{
			TypeReference declaringTypeReference;
			FieldReference bpRef = typeRef.GetField(fd => fd.Name == $"{propertyName}Property" && fd.IsStatic && fd.IsPublic, out declaringTypeReference);
			if (bpRef != null) {
				bpRef = module.Import(bpRef.ResolveGenericParameters(declaringTypeReference));
				bpRef.FieldType = module.Import(bpRef.FieldType);
			}
			return bpRef;
		}
	}
}