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