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