faae3b2abf3e536a4bc5eadd9fc68527955d8b11
[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.Linq;
34 using System.Reflection;
35 using System.Text.RegularExpressions;
36 using System.Xml;
37 using Tizen.NUI.BaseComponents;
38 using Tizen.NUI.Binding;
39 using Tizen.NUI.Binding.Internals;
40
41 namespace Tizen.NUI.Xaml.Internals
42 {
43     /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
44     [EditorBrowsable(EditorBrowsableState.Never)]
45     [Obsolete ("Replaced by ResourceLoader")]
46     public static class XamlLoader
47     {
48         static Func<Type, string> xamlFileProvider;
49
50         /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
51         [EditorBrowsable(EditorBrowsableState.Never)]
52         public static Func<Type, string> XamlFileProvider {
53             get { return xamlFileProvider; }
54             internal set
55             {
56                 xamlFileProvider = value;
57                 Tizen.NUI.Xaml.DesignMode.IsDesignModeEnabled = true;
58                 //¯\_(??_/¯ the previewer forgot to set that bool
59                 DoNotThrowOnExceptions = value != null;
60             }
61         }
62
63         internal static bool DoNotThrowOnExceptions { get; set; }
64     }
65 }
66
67 namespace Tizen.NUI.Xaml
68 {
69     static internal class XamlLoader
70     {
71         public static void Load(object view, Type callingType)
72         {
73             try
74             {
75                 var xaml = GetXamlForType(callingType);
76                 if (string.IsNullOrEmpty(xaml))
77                     throw new XamlParseException(string.Format("Can't get xaml from type {0}", callingType), new XmlLineInfo());
78                 Load(view, xaml);
79             }
80             catch (XamlParseException e)
81             {
82                 Tizen.Log.Fatal("NUI", "XamlParseException e.Message: " + e.Message);
83                 Console.WriteLine("\n[FATAL] XamlParseException e.Message: {0}\n", e.Message);
84             }
85         }
86
87         public static T LoadObject<T>(string path)
88         {
89             var xaml = GetAnimationXaml(path);
90             if (string.IsNullOrEmpty(xaml))
91                 throw new XamlParseException(string.Format("No embeddedresource found for {0}", path), new XmlLineInfo());
92             Type type = typeof(T);
93             T ret = (T)type.Assembly.CreateInstance(type.FullName);
94
95             NameScopeExtensions.PushElement(ret);
96
97             using (var textReader = new StringReader(xaml))
98             using (var reader = XmlReader.Create(textReader))
99             {
100                 while (reader.Read())
101                 {
102                     //Skip until element
103                     if (reader.NodeType == XmlNodeType.Whitespace)
104                         continue;
105                     if (reader.NodeType == XmlNodeType.XmlDeclaration)
106                         continue;
107                     if (reader.NodeType != XmlNodeType.Element)
108                     {
109                         Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
110                         continue;
111                     }
112
113                     var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), ret, (IXmlNamespaceResolver)reader);
114                     XamlParser.ParseXaml(rootnode, reader);
115                     Visit(rootnode, new HydrationContext
116                     {
117                         RootElement = ret,
118 #pragma warning disable 0618
119                         ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action<Exception>)null)
120 #pragma warning restore 0618
121                     });
122                     break;
123                 }
124             }
125
126             NameScopeExtensions.PopElement();
127             return ret;
128         }
129
130         public static void Load(object view, string xaml)
131         {
132             using (var textReader = new StringReader(xaml))
133             using (var reader = XmlReader.Create(textReader))
134             {
135                 Load(view, reader);
136             }
137         }
138
139         public static void Load(object view, XmlReader reader)
140         {
141             if (reader != null)
142             {
143                 while (reader.Read())
144                 {
145                     //Skip until element
146                     if (reader.NodeType == XmlNodeType.Whitespace)
147                         continue;
148                     if (reader.NodeType == XmlNodeType.XmlDeclaration)
149                         continue;
150                     if (reader.NodeType != XmlNodeType.Element)
151                     {
152                         Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
153                         continue;
154                     }
155
156                     if (view is Element)
157                     {
158                         (view as Element).IsCreateByXaml = true;
159                     }
160
161                     var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), view, (IXmlNamespaceResolver)reader);
162                     XamlParser.ParseXaml(rootnode, reader);
163                     Visit(rootnode, new HydrationContext
164                     {
165                         RootElement = view,
166 #pragma warning disable 0618
167                         ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action<Exception>)null)
168 #pragma warning restore 0618
169                     });
170                     break;
171                 }
172             }
173         }
174
175         [Obsolete("Use the XamlFileProvider to provide xaml files. We will remove this when Cycle 8 hits Stable.")]
176         public static object Create(string xaml, bool doNotThrow = false)
177         {
178             object inflatedView = null;
179             using (var textreader = new StringReader(xaml))
180             using (var reader = XmlReader.Create(textreader))
181             {
182                 inflatedView = Create(reader, doNotThrow);
183             }
184             return inflatedView;
185         }
186
187         public static object Create(XmlReader reader, bool doNotThrow = false)
188         {
189             object inflatedView = null;
190             if (reader != null)
191             {
192                 while (reader.Read())
193                 {
194                     //Skip until element
195                     if (reader.NodeType == XmlNodeType.Whitespace)
196                         continue;
197                     if (reader.NodeType == XmlNodeType.XmlDeclaration)
198                         continue;
199                     if (reader.NodeType != XmlNodeType.Element)
200                     {
201                         Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
202                         continue;
203                     }
204
205                     var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), null, (IXmlNamespaceResolver)reader);
206                     XamlParser.ParseXaml(rootnode, reader);
207                     var visitorContext = new HydrationContext
208                     {
209                         ExceptionHandler = doNotThrow ? e => { } : (Action<Exception>)null,
210                     };
211                     var cvv = new CreateValuesVisitor(visitorContext);
212
213                     // Visit Parameter Properties to create instance from parameterized constructor
214                     var type = XamlParser.GetElementType(rootnode.XmlType, rootnode, null, out XamlParseException xpe);
215                     if (xpe != null)
216                         throw xpe;
217
218                     var ctorInfo =
219                         type.GetTypeInfo()
220                             .DeclaredConstructors.FirstOrDefault(
221                                 ci =>
222                                     ci.GetParameters().Length != 0 && ci.IsPublic &&
223                                     ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
224                     if (ctorInfo != null)
225                     {
226                         foreach (var parameter in ctorInfo.GetParameters())
227                         {
228                             var propname =
229                                 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")?
230                                     .ConstructorArguments.First()
231                                     .Value as string;
232
233                             var name = new XmlName("", propname);
234                             if (rootnode.Properties.TryGetValue(name, out INode node) && node is ValueNode)
235                             {
236                                 node.Accept(cvv, rootnode);
237                             }
238                         }
239                     }
240
241
242                     cvv.Visit((ElementNode)rootnode, null);
243                     inflatedView = rootnode.Root = visitorContext.Values[rootnode];
244                     visitorContext.RootElement = inflatedView as BindableObject;
245
246                     Visit(rootnode, visitorContext);
247                     break;
248                 }
249             }
250             return inflatedView;
251         }
252
253         static void Visit(RootNode rootnode, HydrationContext visitorContext)
254         {
255             rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null); //set parents for {StaticResource}
256             rootnode.Accept(new ExpandMarkupsVisitor(visitorContext), null);
257             rootnode.Accept(new PruneIgnoredNodesVisitor(), null);
258             rootnode.Accept(new NamescopingVisitor(visitorContext), null); //set namescopes for {x:Reference}
259             rootnode.Accept(new CreateValuesVisitor(visitorContext), null);
260             rootnode.Accept(new RegisterXNamesVisitor(visitorContext), null);
261             rootnode.Accept(new FillResourceDictionariesVisitor(visitorContext), null);
262             rootnode.Accept(new ApplyPropertiesVisitor(visitorContext, true), null);
263         }
264
265         static string GetAnimationXaml(string animationXamlPath)
266         {
267             string xaml;
268             if (File.Exists(animationXamlPath))
269             {
270                 StreamReader reader = new StreamReader(animationXamlPath);
271                 xaml = reader.ReadToEnd();
272                 reader.Close();
273                 reader.Dispose();
274                 Tizen.Log.Fatal("NUI", "File is exist!, try with xaml: " + xaml);
275                 return xaml;
276             }
277
278             return null;
279         }
280         static string GetXamlForType(Type type)
281         {
282             //the Previewer might want to provide it's own xaml for this... let them do that
283             //the check at the end is preferred (using ResourceLoader). keep this until all the previewers are updated
284
285             string xaml;
286             string resourceName = type.Name + ".xaml";
287             string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
288
289             Tizen.Log.Fatal("NUI", "the resource path: " + resource);
290             int windowWidth = NUIApplication.GetDefaultWindow().Size.Width;
291             int windowHeight = NUIApplication.GetDefaultWindow().Size.Height;
292
293             string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName;
294             Tizen.Log.Fatal("NUI", "the resource path: " + likelyResourcePath);
295
296             if (!File.Exists(likelyResourcePath))
297             {
298                 likelyResourcePath = resource + "layout/" + resourceName;
299             }
300
301             //Find the xaml file in the layout folder
302             if (File.Exists(likelyResourcePath))
303             {
304                 StreamReader reader = new StreamReader(likelyResourcePath);
305                 xaml = reader.ReadToEnd();
306                 reader.Close();
307                 reader.Dispose();
308                 Tizen.Log.Fatal("NUI", "File is exist!, try with xaml: " + xaml);
309                 var pattern = String.Format("x:Class *= *\"{0}\"", type.FullName);
310                 var regex = new Regex(pattern, RegexOptions.ECMAScript);
311                 if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", type.FullName)))
312                 {
313                     return xaml;
314                 }
315                 else
316                 {
317                     throw new XamlParseException(string.Format("Can't find type {0}", type.FullName), new XmlLineInfo());
318                 }
319             }
320             else
321             {
322                 Assembly assembly = type.Assembly;
323
324                 var resourceId = XamlResourceIdAttribute.GetResourceIdForType(type);
325                 if (null == resourceId)
326                 {
327                     throw new XamlParseException(string.Format("Can't find type {0} in embedded resource", type.FullName), new XmlLineInfo());
328                 }
329                 else
330                 {
331                     Stream stream = assembly.GetManifestResourceStream(resourceId);
332
333                     if (null != stream)
334                     {
335                         Byte[] buffer = new byte[stream.Length];
336                         stream.Read(buffer, 0, (int)stream.Length);
337
338                         string ret = System.Text.Encoding.Default.GetString(buffer);
339                         return ret;
340                     }
341                     else
342                     {
343                         throw new XamlParseException(string.Format("Can't get xaml stream {0} in embedded resource", type.FullName), new XmlLineInfo());
344                     }
345                 }
346             }
347         }
348
349         //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
350         static readonly Dictionary<Type, string> XamlResources = new Dictionary<Type, string>();
351         static string LegacyGetXamlForType(Type type)
352         {
353             var assembly = type.GetTypeInfo().Assembly;
354
355             string resourceId;
356             if (XamlResources.TryGetValue(type, out resourceId))
357             {
358                 var result = ReadResourceAsXaml(type, assembly, resourceId);
359                 if (result != null)
360                     return result;
361             }
362
363             var likelyResourceName = type.Name + ".xaml";
364             var resourceNames = assembly.GetManifestResourceNames();
365             string resourceName = null;
366
367             // first pass, pray to find it because the user named it correctly
368
369             foreach (var resource in resourceNames)
370             {
371                 if (ResourceMatchesFilename(assembly, resource, likelyResourceName))
372                 {
373                     resourceName = resource;
374                     var xaml = ReadResourceAsXaml(type, assembly, resource);
375                     if (xaml != null)
376                         return xaml;
377                 }
378             }
379
380             // okay maybe they at least named it .xaml
381
382             foreach (var resource in resourceNames)
383             {
384                 if (!resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
385                     continue;
386
387                 resourceName = resource;
388                 var xaml = ReadResourceAsXaml(type, assembly, resource);
389                 if (xaml != null)
390                     return xaml;
391             }
392
393             foreach (var resource in resourceNames)
394             {
395                 if (resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
396                     continue;
397
398                 resourceName = resource;
399                 var xaml = ReadResourceAsXaml(type, assembly, resource, true);
400                 if (xaml != null)
401                     return xaml;
402             }
403
404             return null;
405         }
406
407         //legacy...
408         static bool ResourceMatchesFilename(Assembly assembly, string resource, string filename)
409         {
410             try
411             {
412                 var info = assembly.GetManifestResourceInfo(resource);
413
414                 if (!string.IsNullOrEmpty(info.FileName) &&
415                     string.Compare(info.FileName, filename, StringComparison.OrdinalIgnoreCase) == 0)
416                     return true;
417             }
418             catch (PlatformNotSupportedException)
419             {
420                 // Because Win10 + .NET Native
421             }
422
423             if (resource.EndsWith("." + filename, StringComparison.OrdinalIgnoreCase) ||
424                 string.Compare(resource, filename, StringComparison.OrdinalIgnoreCase) == 0)
425                 return true;
426
427             return false;
428         }
429
430         //part of the legacy as well...
431         static string ReadResourceAsXaml(Type type, Assembly assembly, string likelyTargetName, bool validate = false)
432         {
433             using (var stream = assembly.GetManifestResourceStream(likelyTargetName))
434             using (var reader = new StreamReader(stream))
435             {
436                 if (validate)
437                 {
438                     // terrible validation of XML. Unfortunately it will probably work most of the time since comments
439                     // also start with a <. We can't bring in any real deps.
440
441                     var firstNonWhitespace = (char)reader.Read();
442                     while (char.IsWhiteSpace(firstNonWhitespace))
443                         firstNonWhitespace = (char)reader.Read();
444
445                     if (firstNonWhitespace != '<')
446                         return null;
447
448                     stream.Seek(0, SeekOrigin.Begin);
449                 }
450
451                 var xaml = reader.ReadToEnd();
452
453                 var pattern = String.Format("x:Class *= *\"{0}\"", type.FullName);
454                 var regex = new Regex(pattern, RegexOptions.ECMAScript);
455                 if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", type.FullName)))
456                     return xaml;
457             }
458             return null;
459         }
460
461         public class RuntimeRootNode : RootNode
462         {
463             public RuntimeRootNode(XmlType xmlType, object root, IXmlNamespaceResolver resolver) : base(xmlType, resolver)
464             {
465                 Root = root;
466             }
467
468             public object Root { get; internal set; }
469         }
470
471         internal static string GetXamlForName(string nameOfXamlFile)
472         {
473             string xaml;
474             string resourceName = nameOfXamlFile + ".xaml";
475             string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
476
477             NUILog.Debug($"resource=({resource})");
478
479             int windowWidth = NUIApplication.GetDefaultWindow().Size.Width;
480             int windowHeight = NUIApplication.GetDefaultWindow().Size.Height;
481
482             string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName;
483
484             NUILog.Debug($"likelyResourcePath=({likelyResourcePath})");
485
486
487             if (!File.Exists(likelyResourcePath))
488             {
489                 likelyResourcePath = resource + "layout/" + resourceName;
490             }
491
492             //Find the xaml file in the layout folder
493             if (File.Exists(likelyResourcePath))
494             {
495                 StreamReader reader = new StreamReader(likelyResourcePath);
496                 xaml = reader.ReadToEnd();
497
498                 NUILog.Debug($"File is exist!, try with xaml: {xaml}");
499
500                 // Layer
501                 var pattern = String.Format("x:Class *= *\"{0}\"", "Tizen.NUI.Layer");
502                 var regex = new Regex(pattern, RegexOptions.ECMAScript);
503                 if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", "Tizen.NUI.Layer")))
504                 {
505                     reader.Dispose();
506                     return xaml;
507                 }
508                 // View
509                 pattern = String.Format("x:Class *= *\"{0}\"", "Tizen.NUI.BaseComponents.View");
510                 regex = new Regex(pattern, RegexOptions.ECMAScript);
511                 if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", "Tizen.NUI.BaseComponents.View")))
512                 {
513                     reader.Dispose();
514                     return xaml;
515                 }
516
517                 reader.Dispose();
518                 throw new XamlParseException(string.Format("Can't find type {0}", "Tizen.NUI.XamlMainPage nor View nor Layer"), new XmlLineInfo());
519             }
520             return null;
521         }
522
523     }
524 }