601ef7d89af5f375c3ab31b617d2f1f322ab16c5
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Xaml / XamlLoader.cs
1 //
2 // XamlLoader.cs
3 //
4 // Author:
5 //       Stephane Delcroix <stephane@mi8.be>
6 //
7 // Copyright (c) 2018 Mobile Inception
8 // Copyright (c) 2018-2014 Xamarin, Inc
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27
28 using System;
29 using System.Collections.Generic;
30 using System.ComponentModel;
31 using System.Diagnostics;
32 using System.IO;
33 using System.Reflection;
34 using System.Text.RegularExpressions;
35 using System.Xml;
36 using Tizen.NUI.BaseComponents;
37 using Tizen.NUI.Binding;
38 using Tizen.NUI.Binding.Internals;
39
40 namespace Tizen.NUI.Xaml.Internals
41 {
42     [Obsolete("Replaced by ResourceLoader")]
43     internal static class XamlLoader
44     {
45         static Func<Type, string> xamlFileProvider;
46
47         public static Func<Type, string> XamlFileProvider
48         {
49             get { return xamlFileProvider; }
50             internal set
51             {
52                 xamlFileProvider = value;
53                 Tizen.NUI.Xaml.DesignMode.IsDesignModeEnabled = true;
54                 //¯\_(??_/¯ the previewer forgot to set that bool
55                 DoNotThrowOnExceptions = value != null;
56             }
57         }
58
59         internal static bool DoNotThrowOnExceptions { get; set; }
60     }
61 }
62
63 namespace Tizen.NUI.Xaml
64 {
65     static internal class XamlLoader
66     {
67         public static void Load(object view, Type callingType)
68         {
69             try
70             {
71                 var xaml = GetXamlForType(callingType);
72                 if (string.IsNullOrEmpty(xaml))
73                     throw new XamlParseException(string.Format("Can't get xaml from type {0}", callingType), new XmlLineInfo());
74                 Load(view, xaml);
75             }
76             catch (XamlParseException e)
77             {
78                 Tizen.Log.Fatal("NUI", "XamlParseException e.Message: " + e.Message);
79                 Console.WriteLine("\n[FATAL] XamlParseException e.Message: {0}\n", e.Message);
80             }
81         }
82
83         public static T LoadObject<T>(string path)
84         {
85             var xaml = GetAnimationXaml(path);
86             if (string.IsNullOrEmpty(xaml))
87                 throw new XamlParseException(string.Format("No embeddedresource found for {0}", path), new XmlLineInfo());
88             Type type = typeof(T);
89             T ret = (T)type.Assembly.CreateInstance(type.FullName);
90
91             NameScopeExtensions.PushElement(ret);
92
93             using (var textReader = new StringReader(xaml))
94             using (var reader = XmlReader.Create(textReader))
95             {
96                 while (reader.Read())
97                 {
98                     //Skip until element
99                     if (reader.NodeType == XmlNodeType.Whitespace)
100                         continue;
101                     if (reader.NodeType == XmlNodeType.XmlDeclaration)
102                         continue;
103                     if (reader.NodeType != XmlNodeType.Element)
104                     {
105                         Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
106                         continue;
107                     }
108
109                     var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), ret, (IXmlNamespaceResolver)reader);
110                     XamlParser.ParseXaml(rootnode, reader);
111                     Visit(rootnode, new HydrationContext
112                     {
113                         RootElement = ret,
114 #pragma warning disable 0618
115                         ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action<Exception>)null)
116 #pragma warning restore 0618
117                     });
118                     break;
119                 }
120             }
121
122             NameScopeExtensions.PopElement();
123             return ret;
124         }
125
126         public static void Load(object view, string xaml)
127         {
128             using (var textReader = new StringReader(xaml))
129             using (var reader = XmlReader.Create(textReader))
130             {
131                 while (reader.Read())
132                 {
133                     //Skip until element
134                     if (reader.NodeType == XmlNodeType.Whitespace)
135                         continue;
136                     if (reader.NodeType == XmlNodeType.XmlDeclaration)
137                         continue;
138                     if (reader.NodeType != XmlNodeType.Element)
139                     {
140                         Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
141                         continue;
142                     }
143
144                     if (view is Element)
145                     {
146                         (view as Element).IsCreateByXaml = true;
147                     }
148
149                     var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), view, (IXmlNamespaceResolver)reader);
150                     XamlParser.ParseXaml(rootnode, reader);
151                     Visit(rootnode, new HydrationContext
152                     {
153                         RootElement = view,
154 #pragma warning disable 0618
155                         ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action<Exception>)null)
156 #pragma warning restore 0618
157                     });
158                     break;
159                 }
160             }
161         }
162
163         [Obsolete("Use the XamlFileProvider to provide xaml files. We will remove this when Cycle 8 hits Stable.")]
164         public static object Create(string xaml, bool doNotThrow = false)
165         {
166             object inflatedView = null;
167             using (var textreader = new StringReader(xaml))
168             using (var reader = XmlReader.Create(textreader))
169             {
170                 while (reader.Read())
171                 {
172                     //Skip until element
173                     if (reader.NodeType == XmlNodeType.Whitespace)
174                         continue;
175                     if (reader.NodeType == XmlNodeType.XmlDeclaration)
176                         continue;
177                     if (reader.NodeType != XmlNodeType.Element)
178                     {
179                         Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
180                         continue;
181                     }
182
183                     var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), null, (IXmlNamespaceResolver)reader);
184                     XamlParser.ParseXaml(rootnode, reader);
185                     var visitorContext = new HydrationContext
186                     {
187                         ExceptionHandler = doNotThrow ? e => { } : (Action<Exception>)null,
188                     };
189                     var cvv = new CreateValuesVisitor(visitorContext);
190                     cvv.Visit((ElementNode)rootnode, null);
191                     inflatedView = rootnode.Root = visitorContext.Values[rootnode];
192                     visitorContext.RootElement = inflatedView as BindableObject;
193
194                     Visit(rootnode, visitorContext);
195                     break;
196                 }
197             }
198             return inflatedView;
199         }
200
201         static void Visit(RootNode rootnode, HydrationContext visitorContext)
202         {
203             rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null); //set parents for {StaticResource}
204             rootnode.Accept(new ExpandMarkupsVisitor(visitorContext), null);
205             rootnode.Accept(new PruneIgnoredNodesVisitor(), null);
206             rootnode.Accept(new NamescopingVisitor(visitorContext), null); //set namescopes for {x:Reference}
207             rootnode.Accept(new CreateValuesVisitor(visitorContext), null);
208             rootnode.Accept(new RegisterXNamesVisitor(visitorContext), null);
209             rootnode.Accept(new FillResourceDictionariesVisitor(visitorContext), null);
210             rootnode.Accept(new ApplyPropertiesVisitor(visitorContext, true), null);
211         }
212
213         static string GetAnimationXaml(string animationXamlPath)
214         {
215             string xaml;
216             if (File.Exists(animationXamlPath))
217             {
218                 StreamReader reader = new StreamReader(animationXamlPath);
219                 xaml = reader.ReadToEnd();
220                 reader.Close();
221                 reader.Dispose();
222                 Tizen.Log.Fatal("NUI", "File is exist!, try with xaml: " + xaml);
223                 return xaml;
224             }
225
226             return null;
227         }
228         static string GetXamlForType(Type type)
229         {
230             //the Previewer might want to provide it's own xaml for this... let them do that
231             //the check at the end is preferred (using ResourceLoader). keep this until all the previewers are updated
232
233             string xaml;
234             string resourceName = type.Name + ".xaml";
235             string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
236
237             Tizen.Log.Fatal("NUI", "the resource path: " + resource);
238             int windowWidth = Window.Instance.Size.Width;
239             int windowHeight = Window.Instance.Size.Height;
240
241             string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName;
242             Tizen.Log.Fatal("NUI", "the resource path: " + likelyResourcePath);
243
244             if (!File.Exists(likelyResourcePath))
245             {
246                 likelyResourcePath = resource + "layout/" + resourceName;
247             }
248
249             //Find the xaml file in the layout folder
250             if (File.Exists(likelyResourcePath))
251             {
252                 StreamReader reader = new StreamReader(likelyResourcePath);
253                 xaml = reader.ReadToEnd();
254                 reader.Close();
255                 reader.Dispose();
256                 Tizen.Log.Fatal("NUI", "File is exist!, try with xaml: " + xaml);
257                 var pattern = String.Format("x:Class *= *\"{0}\"", type.FullName);
258                 var regex = new Regex(pattern, RegexOptions.ECMAScript);
259                 if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", type.FullName)))
260                 {
261                     return xaml;
262                 }
263                 else
264                 {
265                     throw new XamlParseException(string.Format("Can't find type {0}", type.FullName), new XmlLineInfo());
266                 }
267             }
268
269             return null;
270         }
271
272         //if the assembly was generated using a version of XamlG that doesn't outputs XamlResourceIdAttributes, we still need to find the resource, and load it
273         static readonly Dictionary<Type, string> XamlResources = new Dictionary<Type, string>();
274         static string LegacyGetXamlForType(Type type)
275         {
276             var assembly = type.GetTypeInfo().Assembly;
277
278             string resourceId;
279             if (XamlResources.TryGetValue(type, out resourceId))
280             {
281                 var result = ReadResourceAsXaml(type, assembly, resourceId);
282                 if (result != null)
283                     return result;
284             }
285
286             var likelyResourceName = type.Name + ".xaml";
287             var resourceNames = assembly.GetManifestResourceNames();
288             string resourceName = null;
289
290             // first pass, pray to find it because the user named it correctly
291
292             foreach (var resource in resourceNames)
293             {
294                 if (ResourceMatchesFilename(assembly, resource, likelyResourceName))
295                 {
296                     resourceName = resource;
297                     var xaml = ReadResourceAsXaml(type, assembly, resource);
298                     if (xaml != null)
299                         return xaml;
300                 }
301             }
302
303             // okay maybe they at least named it .xaml
304
305             foreach (var resource in resourceNames)
306             {
307                 if (!resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
308                     continue;
309
310                 resourceName = resource;
311                 var xaml = ReadResourceAsXaml(type, assembly, resource);
312                 if (xaml != null)
313                     return xaml;
314             }
315
316             foreach (var resource in resourceNames)
317             {
318                 if (resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
319                     continue;
320
321                 resourceName = resource;
322                 var xaml = ReadResourceAsXaml(type, assembly, resource, true);
323                 if (xaml != null)
324                     return xaml;
325             }
326
327             return null;
328         }
329
330         //legacy...
331         static bool ResourceMatchesFilename(Assembly assembly, string resource, string filename)
332         {
333             try
334             {
335                 var info = assembly.GetManifestResourceInfo(resource);
336
337                 if (!string.IsNullOrEmpty(info.FileName) &&
338                     string.Compare(info.FileName, filename, StringComparison.OrdinalIgnoreCase) == 0)
339                     return true;
340             }
341             catch (PlatformNotSupportedException)
342             {
343                 // Because Win10 + .NET Native
344             }
345
346             if (resource.EndsWith("." + filename, StringComparison.OrdinalIgnoreCase) ||
347                 string.Compare(resource, filename, StringComparison.OrdinalIgnoreCase) == 0)
348                 return true;
349
350             return false;
351         }
352
353         //part of the legacy as well...
354         static string ReadResourceAsXaml(Type type, Assembly assembly, string likelyTargetName, bool validate = false)
355         {
356             using (var stream = assembly.GetManifestResourceStream(likelyTargetName))
357             using (var reader = new StreamReader(stream))
358             {
359                 if (validate)
360                 {
361                     // terrible validation of XML. Unfortunately it will probably work most of the time since comments
362                     // also start with a <. We can't bring in any real deps.
363
364                     var firstNonWhitespace = (char)reader.Read();
365                     while (char.IsWhiteSpace(firstNonWhitespace))
366                         firstNonWhitespace = (char)reader.Read();
367
368                     if (firstNonWhitespace != '<')
369                         return null;
370
371                     stream.Seek(0, SeekOrigin.Begin);
372                 }
373
374                 var xaml = reader.ReadToEnd();
375
376                 var pattern = String.Format("x:Class *= *\"{0}\"", type.FullName);
377                 var regex = new Regex(pattern, RegexOptions.ECMAScript);
378                 if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", type.FullName)))
379                     return xaml;
380             }
381             return null;
382         }
383
384         public class RuntimeRootNode : RootNode
385         {
386             public RuntimeRootNode(XmlType xmlType, object root, IXmlNamespaceResolver resolver) : base(xmlType, resolver)
387             {
388                 Root = root;
389             }
390
391             public object Root { get; internal set; }
392         }
393
394         internal static string GetXamlForName(string nameOfXamlFile)
395         {
396             string xaml;
397             string resourceName = nameOfXamlFile + ".xaml";
398             string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
399
400             NUILog.Debug($"resource=({resource})");
401
402             int windowWidth = Window.Instance.Size.Width;
403             int windowHeight = Window.Instance.Size.Height;
404
405             string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName;
406
407             NUILog.Debug($"likelyResourcePath=({likelyResourcePath})");
408
409
410             if (!File.Exists(likelyResourcePath))
411             {
412                 likelyResourcePath = resource + "layout/" + resourceName;
413             }
414
415             //Find the xaml file in the layout folder
416             if (File.Exists(likelyResourcePath))
417             {
418                 StreamReader reader = new StreamReader(likelyResourcePath);
419                 xaml = reader.ReadToEnd();
420
421                 NUILog.Debug($"File is exist!, try with xaml: {xaml}");
422
423                 // Layer
424                 var pattern = String.Format("x:Class *= *\"{0}\"", "Tizen.NUI.Layer");
425                 var regex = new Regex(pattern, RegexOptions.ECMAScript);
426                 if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", "Tizen.NUI.Layer")))
427                 {
428                     return xaml;
429                 }
430                 // View
431                 pattern = String.Format("x:Class *= *\"{0}\"", "Tizen.NUI.BaseComponents.View");
432                 regex = new Regex(pattern, RegexOptions.ECMAScript);
433                 if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", "Tizen.NUI.BaseComponents.View")))
434                 {
435                     return xaml;
436                 }
437
438                 throw new XamlParseException(string.Format("Can't find type {0}", "Tizen.NUI.XamlMainPage nor View nor Layer"), new XmlLineInfo());
439             }
440             return null;
441         }
442
443     }
444 }