summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Build.Tasks/XamlGTask.cs
blob: a0813a5c4de61253c16cab1600f58828beb8ee9e (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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.CSharp;
using Xamarin.Forms.Xaml;

namespace Xamarin.Forms.Build.Tasks
{
	public class XamlGTask : Task
	{
		internal static CodeDomProvider Provider = new CSharpCodeProvider();

		[Required]
		public string Source { get; set; }

		public string Language { get; set; }

		public string AssemblyName { get; set; }

		[Output]
		public string OutputFile { get; set; }

		public override bool Execute()
		{
			if (Source == null || OutputFile == null)
			{
				Log.LogMessage("Skipping XamlG");
				return true;
			}

			Log.LogMessage("Source: {0}", Source);
			Log.LogMessage("Language: {0}", Language);
			Log.LogMessage("AssemblyName: {0}", AssemblyName);
			Log.LogMessage("OutputFile {0}", OutputFile);

			try
			{
				GenerateFile(Source, OutputFile);
				return true;
			}
			catch (XmlException xe)
			{
				Log.LogError(null, null, null, Source, xe.LineNumber, xe.LinePosition, 0, 0, xe.Message, xe.HelpLink, xe.Source);

				return false;
			}
			catch (Exception e)
			{
				Log.LogError(null, null, null, Source, 0, 0, 0, 0, e.Message, e.HelpLink, e.Source);
				return false;
			}
		}

		internal static void ParseXaml(TextReader xaml, out string rootType, out string rootNs, out CodeTypeReference baseType,
			out IDictionary<string, CodeTypeReference> namesAndTypes)
		{
			var xmlDoc = new XmlDocument();
			xmlDoc.Load(xaml);

			var nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
			nsmgr.AddNamespace("x", "http://schemas.microsoft.com/winfx/2006/xaml");
			nsmgr.AddNamespace("x2009", "http://schemas.microsoft.com/winfx/2009/xaml");
			nsmgr.AddNamespace("f", "http://xamarin.com/schemas/2014/forms");

			var root = xmlDoc.SelectSingleNode("/*", nsmgr);
			if (root == null)
			{
				Console.Error.WriteLine("{0}: No root node found");
				rootType = null;
				rootNs = null;
				baseType = null;
				namesAndTypes = null;
				return;
			}

			var rootClass = root.Attributes["Class", "http://schemas.microsoft.com/winfx/2006/xaml"] ??
			                root.Attributes["Class", "http://schemas.microsoft.com/winfx/2009/xaml"];
			if (rootClass == null)
			{
				rootType = null;
				rootNs = null;
				baseType = null;
				namesAndTypes = null;
				return;
			}

			string rootAsm, targetPlatform;
			XmlnsHelper.ParseXmlns(rootClass.Value, out rootType, out rootNs, out rootAsm, out targetPlatform);
			namesAndTypes = GetNamesAndTypes(root, nsmgr);

			var typeArguments = root.Attributes["TypeArguments", "http://schemas.microsoft.com/winfx/2009/xaml"];

			baseType = GetType(root.NamespaceURI, root.LocalName, typeArguments == null ? null : typeArguments.Value.Split(','),
				root.GetNamespaceOfPrefix);
		}

		internal static void GenerateCode(string rootType, string rootNs, CodeTypeReference baseType,
			IDictionary<string, CodeTypeReference> namesAndTypes, string outFile)
		{
			if (rootType == null)
			{
				File.WriteAllText(outFile, "");
				return;
			}

			var ccu = new CodeCompileUnit();
			var declNs = new CodeNamespace(rootNs);
			ccu.Namespaces.Add(declNs);

			var declType = new CodeTypeDeclaration(rootType);
			declType.IsPartial = true;
			declType.BaseTypes.Add(baseType);

			declNs.Types.Add(declType);

			var initcomp = new CodeMemberMethod
			{
				Name = "InitializeComponent",
				CustomAttributes =
				{
					new CodeAttributeDeclaration(new CodeTypeReference(typeof (GeneratedCodeAttribute)),
						new CodeAttributeArgument(new CodePrimitiveExpression("Xamarin.Forms.Build.Tasks.XamlG")),
						new CodeAttributeArgument(new CodePrimitiveExpression("0.0.0.0")))
				}
			};
			declType.Members.Add(initcomp);

			initcomp.Statements.Add(new CodeMethodInvokeExpression(
				new CodeTypeReferenceExpression(new CodeTypeReference("global::Xamarin.Forms.Xaml.Extensions")),
				"LoadFromXaml", new CodeThisReferenceExpression(), new CodeTypeOfExpression(declType.Name)));

			foreach (var entry in namesAndTypes)
			{
				string name = entry.Key;
				var type = entry.Value;

				var field = new CodeMemberField
				{
					Name = name,
					Type = type,
					CustomAttributes =
					{
						new CodeAttributeDeclaration(new CodeTypeReference(typeof (GeneratedCodeAttribute)),
							new CodeAttributeArgument(new CodePrimitiveExpression("Xamarin.Forms.Build.Tasks.XamlG")),
							new CodeAttributeArgument(new CodePrimitiveExpression("0.0.0.0")))
					}
				};

				declType.Members.Add(field);

				var find_invoke = new CodeMethodInvokeExpression(
					new CodeMethodReferenceExpression(
						new CodeTypeReferenceExpression(new CodeTypeReference("global::Xamarin.Forms.NameScopeExtensions")),
						"FindByName", type),
					new CodeThisReferenceExpression(), new CodePrimitiveExpression(name));

				//CodeCastExpression cast = new CodeCastExpression (type, find_invoke);

				CodeAssignStatement assign = new CodeAssignStatement(
					new CodeVariableReferenceExpression(name), find_invoke);

				initcomp.Statements.Add(assign);
			}

			using (var writer = new StreamWriter(outFile))
				Provider.GenerateCodeFromCompileUnit(ccu, writer, new CodeGeneratorOptions());
		}

		internal static void GenerateFile(string xamlFile, string outFile)
		{
			string rootType, rootNs;
			CodeTypeReference baseType;
			IDictionary<string, CodeTypeReference> namesAndTypes;
			using (StreamReader reader = File.OpenText(xamlFile))
				ParseXaml(reader, out rootType, out rootNs, out baseType, out namesAndTypes);
			GenerateCode(rootType, rootNs, baseType, namesAndTypes, outFile);
		}

		static Dictionary<string, CodeTypeReference> GetNamesAndTypes(XmlNode root, XmlNamespaceManager nsmgr)
		{
			var res = new Dictionary<string, CodeTypeReference>();

			foreach (string attrib in new[] { "x:Name", "x2009:Name" })
			{
				XmlNodeList names =
					root.SelectNodes(
						"//*[@" + attrib +
						"][not(ancestor:: f:DataTemplate) and not(ancestor:: f:ControlTemplate) and not(ancestor:: f:Style)]", nsmgr);
				foreach (XmlNode node in names)
				{
					// Don't take the root canvas
					if (node == root)
						continue;

					XmlAttribute attr = node.Attributes["Name", "http://schemas.microsoft.com/winfx/2006/xaml"] ??
					                    node.Attributes["Name", "http://schemas.microsoft.com/winfx/2009/xaml"];
					XmlAttribute typeArgsAttr = node.Attributes["x:TypeArguments"];
					var typeArgsList = typeArgsAttr == null ? null : typeArgsAttr.Value.Split(',').ToList();
					string name = attr.Value;

					res[name] = GetType(node.NamespaceURI, node.LocalName, typeArgsList, node.GetNamespaceOfPrefix);
				}
			}

			return res;
		}

		static CodeTypeReference GetType(string nsuri, string type, IList<string> typeArguments = null,
			Func<string, string> getNamespaceOfPrefix = null)
		{
			var ns = GetNamespace(nsuri);
			if (ns != null)
				type = String.Concat(ns, ".", type);

			if (typeArguments != null)
				type = String.Concat(type, "`", typeArguments.Count);

			var returnType = new CodeTypeReference(type);
			if (ns != null)
				returnType.Options |= CodeTypeReferenceOptions.GlobalReference;

			if (typeArguments != null)
			{
				foreach (var typeArg in typeArguments)
				{
					var ns_uri = "";
					var _type = typeArg;
					if (typeArg.Contains(":"))
					{
						var prefix = typeArg.Split(':')[0].Trim();
						ns_uri = getNamespaceOfPrefix(prefix);
						_type = typeArg.Split(':')[1].Trim();
					}
					returnType.TypeArguments.Add(GetType(ns_uri, _type, null, getNamespaceOfPrefix));
				}
			}

			return returnType;
		}

		static string GetNamespace(string namespaceuri)
		{
			if (!XmlnsHelper.IsCustom(namespaceuri))
				return "Xamarin.Forms";
			if (namespaceuri == "http://schemas.microsoft.com/winfx/2009/xaml")
				return "System";
			if (namespaceuri != "http://schemas.microsoft.com/winfx/2006/xaml" && !namespaceuri.Contains("clr-namespace"))
				throw new Exception(String.Format("Can't load types from xmlns {0}", namespaceuri));
			return XmlnsHelper.ParseNamespaceFromXmlns(namespaceuri);
		}
	}
}