* XamlG.cs (Execute): Fix earlier commit, include the filename.
[mono.git] / mcs / class / Moonlight.Build.Tasks / Moonlight.Build.Tasks / XamlG.cs
1 //
2 // XamlG.cs
3 //
4 // Author:
5 //      Michael Hutchinson <mhutchinson@novell.com>
6 //      Ankit Jain <jankit@novell.com>
7 //
8 // Copyright (c) 2009 Novell, Inc. (http://www.novell.com)
9 // Copyright (c) 2010 Novell, Inc. (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
30 using System;
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.IO;
34 using System.CodeDom;
35 using System.CodeDom.Compiler;
36 using System.Xml;
37
38 using Microsoft.Build.Framework;
39 using Microsoft.Build.Utilities;
40
41 namespace Moonlight.Build.Tasks {
42         public class XamlG : Task {
43
44                 public override bool Execute ()
45                 {
46                         if (Sources.Length == 0)
47                                 return true;
48
49                         if (OutputFiles == null || Sources.Length != OutputFiles.Length) {
50                                 Log.LogError ("Number of OutputFiles must match the number of Source files");
51                                 return false;
52                         }
53
54                         var codedom_provider = GetCodeDomProviderForLanguage (Language);
55                         if (codedom_provider == null) {
56                                 Log.LogError ("Language {0} not supported for code generation.", Language);
57                                 return false;
58                         }
59
60                         for (int i = 0; i < Sources.Length; i ++) {
61                                 ITaskItem source_item = Sources [i];
62                                 ITaskItem dest_item = OutputFiles [i];
63                                 if (!File.Exists (dest_item.ItemSpec) ||
64                                         File.GetLastWriteTime (dest_item.ItemSpec) < File.GetLastWriteTime (source_item.ItemSpec)) {
65                                         Log.LogMessage (MessageImportance.Low, "Generating codebehind accessors for {0}...", source_item.ItemSpec);
66
67                                         string full_source_path = source_item.GetMetadata ("FullPath");
68                                         try {
69                                                 if (!XamlGCompiler.GenerateFile (codedom_provider, AssemblyName, full_source_path,
70                                                                                 Path.Combine (source_item.GetMetadata ("RelativeDir"),
71                                                                                         Path.GetFileName (source_item.ItemSpec)),
72                                                                                 dest_item.ItemSpec, Log)) {
73                                                         Log.LogError ("Error generating {0} from {1}", full_source_path, dest_item.ItemSpec);
74                                                         return false;
75                                                 }
76                                         } catch (Exception e) {
77                                                 Log.LogError ("Error generating {0} from {1}: {2}", full_source_path, dest_item.ItemSpec, e.Message);
78                                                 Log.LogMessage (MessageImportance.Low, "Error generating {0} from {1}: {2}",
79                                                                 full_source_path, dest_item.ItemSpec, e.ToString ());
80                                                 return false;
81                                         }
82                                 }
83                         }
84
85                         return true;
86                 }
87
88                 CodeDomProvider GetCodeDomProviderForLanguage (string lang)
89                 {
90                         switch (lang.ToLower ()) {
91                         case "c#": return new Microsoft.CSharp.CSharpCodeProvider ();
92                         case "vb": return new Microsoft.VisualBasic.VBCodeProvider ();
93                         }
94
95                         return null;
96                 }
97
98                 [Required]
99                 public ITaskItem [] Sources {
100                         get; set;
101                 }
102
103                 [Required]
104                 public string Language {
105                         get; set;
106                 }
107
108                 [Required]
109                 public string AssemblyName {
110                         get; set;
111                 }
112
113                 [Output]
114                 public ITaskItem [] OutputFiles {
115                         get; set;
116                 }
117
118                 bool HasFileChanged (string source, string dest)
119                 {
120                         if (!File.Exists (dest))
121                                 return true;
122
123                         FileInfo sourceInfo = new FileInfo (source);
124                         FileInfo destinationInfo = new FileInfo (dest);
125
126                         return !(sourceInfo.Length == destinationInfo.Length &&
127                                         File.GetLastWriteTime(source) <= File.GetLastWriteTime (dest));
128                 }
129
130         }
131
132         static class XamlGCompiler
133         {
134                 private static bool sl2 = true;
135
136                 public static bool GenerateFile (CodeDomProvider provider, string app_name,
137                                                        string xaml_file, string xaml_path_in_project, string out_file, TaskLoggingHelper log)
138                 {
139                         XmlDocument xmldoc = new XmlDocument ();
140                         xmldoc.Load (xaml_file);
141
142                         XmlNamespaceManager nsmgr = new XmlNamespaceManager (xmldoc.NameTable);
143                         nsmgr.AddNamespace("x", "http://schemas.microsoft.com/winfx/2006/xaml");
144
145                         XmlNode root = xmldoc.SelectSingleNode ("/*", nsmgr);
146                         if (root == null) {
147                                 log.LogError ("{0}: No root node found.", xaml_file);
148                                 return false;
149                         }
150
151                         XmlAttribute root_class = root.Attributes ["x:Class"];
152                         if (root_class == null) {
153                                 File.WriteAllText (out_file, "");
154                                 return true;
155                         }
156
157                         bool is_application = root.LocalName == "Application";
158                         string root_ns;
159                         string root_type;
160                         string root_asm;
161
162                         ParseXmlns (root_class.Value, out root_type, out root_ns, out root_asm);
163
164                         Hashtable names_and_types = GetNamesAndTypes (root, nsmgr);
165 //                      Hashtable keys_and_types = GetKeysAndTypes (root, nsmgr);
166
167                         CodeCompileUnit ccu = new CodeCompileUnit ();
168                         CodeNamespace decl_ns = new CodeNamespace (root_ns);
169                         ccu.Namespaces.Add (decl_ns);
170
171                         decl_ns.Imports.Add (new CodeNamespaceImport ("System"));
172                         decl_ns.Imports.Add (new CodeNamespaceImport ("System.Windows"));
173                         decl_ns.Imports.Add (new CodeNamespaceImport ("System.Windows.Controls"));
174                         decl_ns.Imports.Add (new CodeNamespaceImport ("System.Windows.Documents"));
175                         decl_ns.Imports.Add (new CodeNamespaceImport ("System.Windows.Input"));
176                         decl_ns.Imports.Add (new CodeNamespaceImport ("System.Windows.Media"));
177                         decl_ns.Imports.Add (new CodeNamespaceImport ("System.Windows.Media.Animation"));
178                         decl_ns.Imports.Add (new CodeNamespaceImport ("System.Windows.Shapes"));
179                         decl_ns.Imports.Add (new CodeNamespaceImport ("System.Windows.Controls.Primitives"));
180
181                         CodeTypeDeclaration decl_type = new CodeTypeDeclaration (root_type);
182                         decl_type.IsPartial = true;
183
184                         decl_ns.Types.Add (decl_type);
185
186                         CodeMemberMethod initcomp = new CodeMemberMethod ();
187                         initcomp.Name = "InitializeComponent";
188                         decl_type.Members.Add (initcomp);
189
190                         if (sl2) {
191                                 CodeMemberField field = new CodeMemberField ();
192                                 field.Name = "_contentLoaded";
193                                 field.Type = new CodeTypeReference (typeof (bool));
194
195                                 decl_type.Members.Add (field);
196
197                                 CodeConditionStatement is_content_loaded = new CodeConditionStatement (new CodeVariableReferenceExpression ("_contentLoaded"),
198                                                 new CodeStatement [] { new CodeMethodReturnStatement () });
199                                 initcomp.Statements.Add (is_content_loaded);
200
201                                 CodeAssignStatement set_content_loaded = new CodeAssignStatement (new CodeVariableReferenceExpression ("_contentLoaded"),
202                                                 new CodePrimitiveExpression (true));
203
204                                 initcomp.Statements.Add (set_content_loaded);
205
206                                 string component_path = String.Format ("/{0};component/{1}", app_name, xaml_path_in_project);
207                                 CodeMethodInvokeExpression load_component = new CodeMethodInvokeExpression (
208                                         new CodeTypeReferenceExpression ("System.Windows.Application"), "LoadComponent",
209                                         new CodeExpression [] { new CodeThisReferenceExpression (),
210                                                                 new CodeObjectCreateExpression (new CodeTypeReference ("System.Uri"), new CodeExpression [] {
211                                                                         new CodePrimitiveExpression (component_path),
212                                                                         new CodeFieldReferenceExpression (new CodeTypeReferenceExpression ("System.UriKind"), "Relative") })
213                                         });
214                                 initcomp.Statements.Add (load_component);
215                         }
216
217                         if (!is_application) {
218                                 foreach (DictionaryEntry entry  in names_and_types) {
219                                         string name = (string) entry.Key;
220                                         CodeTypeReference type = (CodeTypeReference) entry.Value;
221
222                                         CodeMemberField field = new CodeMemberField ();
223
224                                         if (sl2)
225                                                 field.Attributes = MemberAttributes.Assembly;
226
227                                         field.Name = name;
228                                         field.Type = type;
229
230                                         decl_type.Members.Add (field);
231
232                                         CodeMethodInvokeExpression find_invoke = new CodeMethodInvokeExpression (
233                                                 new CodeThisReferenceExpression(), "FindName",
234                                                 new CodeExpression[] { new CodePrimitiveExpression (name) } );
235
236                                         CodeCastExpression cast = new CodeCastExpression (type, find_invoke);
237
238                                         CodeAssignStatement assign = new CodeAssignStatement (
239                                                 new CodeVariableReferenceExpression (name), cast);
240
241                                         initcomp.Statements.Add (assign);
242                                 }
243                         }
244
245
246                         using (StreamWriter writer = new StreamWriter (out_file)) {
247                                 provider.GenerateCodeFromCompileUnit (ccu, writer, new CodeGeneratorOptions ());
248                         }
249
250                         return true;
251                 }
252
253                 private static Hashtable GetNamesAndTypes (XmlNode root, XmlNamespaceManager nsmgr)
254                 {
255                         Hashtable res = new Hashtable ();
256
257                         XmlNodeList names = root.SelectNodes ("//*[@x:Name]", nsmgr);
258                         foreach (XmlNode node in names) {
259
260                                 // Don't take the root canvas
261                                 if (node == root)
262                                         continue;
263
264                                 XmlAttribute attr = node.Attributes ["x:Name"];
265                                 string name = attr.Value;
266                                 string ns = GetNamespace (node);
267                                 string member_type = node.LocalName;
268
269                                 if (ns != null)
270                                         member_type = String.Concat (ns, ".", member_type);
271
272                                 CodeTypeReference type = new CodeTypeReference (member_type);
273                                 if (ns != null)
274                                         type.Options |= CodeTypeReferenceOptions.GlobalReference;
275
276                                 res [name] = type;
277                         }
278
279                         return res;
280                 }
281
282                 /*
283                 private static Hashtable GetKeysAndTypes (XmlNode root, XmlNamespaceManager nsmgr)
284                 {
285                         Hashtable res = new Hashtable ();
286
287                         XmlNodeList keys = root.SelectNodes ("//*[@x:Key]", nsmgr);
288                         foreach (XmlNode node in keys)  {
289
290                                 // Don't take the root canvas
291                                 if (node == root)
292                                         continue;
293
294                                 XmlAttribute attr = node.Attributes ["x:Key"];
295                                 string key = attr.Value;
296                                 string ns = GetNamespace (node);
297                                 string member_type = node.LocalName;
298
299                                 if (ns != null)
300                                         member_type = String.Concat (ns, ".", member_type);
301
302                                 res [key] = member_type;
303                         }
304
305                         return res;
306                 }
307                 */
308
309                 internal static string GetNamespace (XmlNode node)
310                 {
311                         if (!IsCustom (node.NamespaceURI))
312                                 return null;
313
314                         return ParseNamespaceFromXmlns (node.NamespaceURI);
315                 }
316
317                 private static bool IsCustom (string ns)
318                 {
319                         switch (ns) {
320                         case "http://schemas.microsoft.com/winfx/2006/xaml":
321                         case "http://schemas.microsoft.com/winfx/2006/xaml/presentation":
322                         case "http://schemas.microsoft.com/client/2007":
323                                 return false;
324                         }
325
326                         return true;
327                 }
328
329                 private static string ParseNamespaceFromXmlns (string xmlns)
330                 {
331                         string type_name = null;
332                         string ns = null;
333                         string asm = null;
334
335                         ParseXmlns (xmlns, out type_name, out ns, out asm);
336
337                         return ns;
338                 }
339
340 //              private static string ParseTypeFromXmlns (string xmlns)
341 //              {
342 //                      string type_name = null;
343 //                      string ns = null;
344 //                      string asm = null;
345 //
346 //                      ParseXmlns (xmlns, out type_name, out ns, out asm);
347 //
348 //                      return type_name;
349 //              }
350
351                 internal static void ParseXmlns (string xmlns, out string type_name, out string ns, out string asm)
352                 {
353                         type_name = null;
354                         ns = null;
355                         asm = null;
356
357                         string [] decls = xmlns.Split (';');
358                         foreach (string decl in decls) {
359                                 if (decl.StartsWith ("clr-namespace:")) {
360                                         ns = decl.Substring (14, decl.Length - 14);
361                                         continue;
362                                 }
363                                 if (decl.StartsWith ("assembly=")) {
364                                         asm = decl.Substring (9, decl.Length - 9);
365                                         continue;
366                                 }
367                                 int nsind = decl.LastIndexOf (".");
368                                 if (nsind > 0) {
369                                         ns = decl.Substring (0, nsind);
370                                         type_name = decl.Substring (nsind + 1, decl.Length - nsind - 1);
371                                 } else {
372                                         type_name = decl;
373                                 }
374                         }
375                 }
376         }
377
378 }