5 // Stephane Delcroix <stephane@mi8.be>
7 // Copyright (c) 2018 Mobile Inception
8 // Copyright (c) 2018-2014 Xamarin, Inc
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:
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
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
29 using System.Collections.Generic;
30 using System.ComponentModel;
31 using System.Diagnostics;
34 using System.Reflection;
35 using System.Text.RegularExpressions;
37 using Tizen.NUI.BaseComponents;
38 using Tizen.NUI.Binding;
39 using Tizen.NUI.Binding.Internals;
41 namespace Tizen.NUI.Xaml.Internals
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
48 static Func<Type, string> xamlFileProvider;
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
54 get { return xamlFileProvider; }
57 xamlFileProvider = value;
58 Tizen.NUI.Xaml.DesignMode.IsDesignModeEnabled = true;
59 //¯\_(??_/¯ the previewer forgot to set that bool
60 DoNotThrowOnExceptions = value != null;
64 internal static bool DoNotThrowOnExceptions { get; set; }
68 namespace Tizen.NUI.Xaml
70 static internal class XamlLoader
72 public static void Load(object view, Type callingType)
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());
81 catch (XamlParseException e)
83 Tizen.Log.Fatal("NUI", "XamlParseException e.Message: " + e.Message);
84 Console.WriteLine("\n[FATAL] XamlParseException e.Message: {0}\n", e.Message);
88 public static T LoadObject<T>(string path)
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);
96 NameScopeExtensions.PushElement(ret);
98 using (var textReader = new StringReader(xaml))
99 using (var reader = XmlReader.Create(textReader))
101 while (reader.Read())
104 if (reader.NodeType == XmlNodeType.Whitespace)
106 if (reader.NodeType == XmlNodeType.XmlDeclaration)
108 if (reader.NodeType != XmlNodeType.Element)
110 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
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
119 #pragma warning disable 0618
120 ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action<Exception>)null)
121 #pragma warning restore 0618
127 NameScopeExtensions.PopElement();
131 public static void Load(object view, string xaml)
133 using (var textReader = new StringReader(xaml))
134 using (var reader = XmlReader.Create(textReader))
140 public static void Load(object view, XmlReader reader)
144 while (reader.Read())
147 if (reader.NodeType == XmlNodeType.Whitespace)
149 if (reader.NodeType == XmlNodeType.XmlDeclaration)
151 if (reader.NodeType != XmlNodeType.Element)
153 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
159 (view as Element).IsCreateByXaml = true;
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
167 #pragma warning disable 0618
168 ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action<Exception>)null)
169 #pragma warning restore 0618
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)
179 object inflatedView = null;
180 using (var textreader = new StringReader(xaml))
181 using (var reader = XmlReader.Create(textreader))
183 inflatedView = Create(reader, doNotThrow);
188 public static object Create(XmlReader reader, bool doNotThrow = false)
190 object inflatedView = null;
193 while (reader.Read())
196 if (reader.NodeType == XmlNodeType.Whitespace)
198 if (reader.NodeType == XmlNodeType.XmlDeclaration)
200 if (reader.NodeType != XmlNodeType.Element)
202 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
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
210 ExceptionHandler = doNotThrow ? e => { } : (Action<Exception>)null,
212 var cvv = new CreateValuesVisitor(visitorContext);
214 // Visit Parameter Properties to create instance from parameterized constructor
215 var type = XamlParser.GetElementType(rootnode.XmlType, rootnode, null, out XamlParseException xpe);
221 .DeclaredConstructors.FirstOrDefault(
223 ci.GetParameters().Length != 0 && ci.IsPublic &&
224 ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
225 if (ctorInfo != null)
227 foreach (var parameter in ctorInfo.GetParameters())
230 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")?
231 .ConstructorArguments.First()
234 var name = new XmlName("", propname);
235 if (rootnode.Properties.TryGetValue(name, out INode node) && node is ValueNode)
237 node.Accept(cvv, rootnode);
243 cvv.Visit((ElementNode)rootnode, null);
244 inflatedView = rootnode.Root = visitorContext.Values[rootnode];
245 visitorContext.RootElement = inflatedView as BindableObject;
247 Visit(rootnode, visitorContext);
254 static void Visit(RootNode rootnode, HydrationContext visitorContext)
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);
266 static string GetAnimationXaml(string animationXamlPath)
269 if (File.Exists(animationXamlPath))
271 StreamReader reader = new StreamReader(animationXamlPath);
272 xaml = reader.ReadToEnd();
275 Tizen.Log.Fatal("NUI", "File is exist!, try with xaml: " + xaml);
281 static string GetXamlForType(Type type)
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
287 string resourceName = type.Name + ".xaml";
288 string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
290 Tizen.Log.Fatal("NUI", "the resource path: " + resource);
291 int windowWidth = NUIApplication.GetDefaultWindow().Size.Width;
292 int windowHeight = NUIApplication.GetDefaultWindow().Size.Height;
294 string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName;
295 Tizen.Log.Fatal("NUI", "the resource path: " + likelyResourcePath);
297 if (!File.Exists(likelyResourcePath))
299 likelyResourcePath = resource + "layout/" + resourceName;
302 //Find the xaml file in the layout folder
303 if (File.Exists(likelyResourcePath))
305 StreamReader reader = new StreamReader(likelyResourcePath);
306 xaml = reader.ReadToEnd();
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)))
318 throw new XamlParseException(string.Format("Can't find type {0}", type.FullName), new XmlLineInfo());
323 Assembly assembly = type.Assembly;
325 var resourceId = XamlResourceIdAttribute.GetResourceIdForType(type);
326 if (null == resourceId)
328 throw new XamlParseException(string.Format("Can't find type {0} in embedded resource", type.FullName), new XmlLineInfo());
332 Stream stream = assembly.GetManifestResourceStream(resourceId);
336 Byte[] buffer = new byte[stream.Length];
337 stream.Read(buffer, 0, (int)stream.Length);
339 string ret = System.Text.Encoding.Default.GetString(buffer);
344 throw new XamlParseException(string.Format("Can't get xaml stream {0} in embedded resource", type.FullName), new XmlLineInfo());
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)
354 var assembly = type.GetTypeInfo().Assembly;
357 if (XamlResources.TryGetValue(type, out resourceId))
359 var result = ReadResourceAsXaml(type, assembly, resourceId);
364 var likelyResourceName = type.Name + ".xaml";
365 var resourceNames = assembly.GetManifestResourceNames();
366 string resourceName = null;
368 // first pass, pray to find it because the user named it correctly
370 foreach (var resource in resourceNames)
372 if (ResourceMatchesFilename(assembly, resource, likelyResourceName))
374 resourceName = resource;
375 var xaml = ReadResourceAsXaml(type, assembly, resource);
381 // okay maybe they at least named it .xaml
383 foreach (var resource in resourceNames)
385 if (!resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
388 resourceName = resource;
389 var xaml = ReadResourceAsXaml(type, assembly, resource);
394 foreach (var resource in resourceNames)
396 if (resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
399 resourceName = resource;
400 var xaml = ReadResourceAsXaml(type, assembly, resource, true);
409 static bool ResourceMatchesFilename(Assembly assembly, string resource, string filename)
413 var info = assembly.GetManifestResourceInfo(resource);
415 if (!string.IsNullOrEmpty(info.FileName) &&
416 string.Compare(info.FileName, filename, StringComparison.OrdinalIgnoreCase) == 0)
419 catch (PlatformNotSupportedException)
421 // Because Win10 + .NET Native
424 if (resource.EndsWith("." + filename, StringComparison.OrdinalIgnoreCase) ||
425 string.Compare(resource, filename, StringComparison.OrdinalIgnoreCase) == 0)
431 //part of the legacy as well...
432 static string ReadResourceAsXaml(Type type, Assembly assembly, string likelyTargetName, bool validate = false)
434 using (var stream = assembly.GetManifestResourceStream(likelyTargetName))
435 using (var reader = new StreamReader(stream))
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.
442 var firstNonWhitespace = (char)reader.Read();
443 while (char.IsWhiteSpace(firstNonWhitespace))
444 firstNonWhitespace = (char)reader.Read();
446 if (firstNonWhitespace != '<')
449 stream.Seek(0, SeekOrigin.Begin);
452 var xaml = reader.ReadToEnd();
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)))
462 public class RuntimeRootNode : RootNode
464 public RuntimeRootNode(XmlType xmlType, object root, IXmlNamespaceResolver resolver) : base(xmlType, resolver)
469 public object Root { get; internal set; }
472 internal static string GetXamlForName(string nameOfXamlFile)
475 string resourceName = nameOfXamlFile + ".xaml";
476 string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
478 NUILog.Debug($"resource=({resource})");
480 int windowWidth = NUIApplication.GetDefaultWindow().Size.Width;
481 int windowHeight = NUIApplication.GetDefaultWindow().Size.Height;
483 string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName;
485 NUILog.Debug($"likelyResourcePath=({likelyResourcePath})");
488 if (!File.Exists(likelyResourcePath))
490 likelyResourcePath = resource + "layout/" + resourceName;
493 //Find the xaml file in the layout folder
494 if (File.Exists(likelyResourcePath))
496 StreamReader reader = new StreamReader(likelyResourcePath);
497 xaml = reader.ReadToEnd();
499 NUILog.Debug($"File is exist!, try with xaml: {xaml}");
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")))
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")))
519 throw new XamlParseException(string.Format("Can't find type {0}", "Tizen.NUI.XamlMainPage nor View nor Layer"), new XmlLineInfo());