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