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 {
53 get { return xamlFileProvider; }
56 xamlFileProvider = value;
57 Tizen.NUI.Xaml.DesignMode.IsDesignModeEnabled = true;
58 //¯\_(??_/¯ the previewer forgot to set that bool
59 DoNotThrowOnExceptions = value != null;
63 internal static bool DoNotThrowOnExceptions { get; set; }
67 namespace Tizen.NUI.Xaml
69 static internal class XamlLoader
71 public static void Load(object view, Type callingType)
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());
80 catch (XamlParseException e)
82 Tizen.Log.Fatal("NUI", "XamlParseException e.Message: " + e.Message);
83 Console.WriteLine("\n[FATAL] XamlParseException e.Message: {0}\n", e.Message);
87 public static T LoadObject<T>(string path)
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);
95 NameScopeExtensions.PushElement(ret);
97 using (var textReader = new StringReader(xaml))
98 using (var reader = XmlReader.Create(textReader))
100 while (reader.Read())
103 if (reader.NodeType == XmlNodeType.Whitespace)
105 if (reader.NodeType == XmlNodeType.XmlDeclaration)
107 if (reader.NodeType != XmlNodeType.Element)
109 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
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
118 #pragma warning disable 0618
119 ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action<Exception>)null)
120 #pragma warning restore 0618
126 NameScopeExtensions.PopElement();
130 public static void Load(object view, string xaml)
132 using (var textReader = new StringReader(xaml))
133 using (var reader = XmlReader.Create(textReader))
139 public static void Load(object view, XmlReader reader)
143 while (reader.Read())
146 if (reader.NodeType == XmlNodeType.Whitespace)
148 if (reader.NodeType == XmlNodeType.XmlDeclaration)
150 if (reader.NodeType != XmlNodeType.Element)
152 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
158 (view as Element).IsCreateByXaml = true;
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
166 #pragma warning disable 0618
167 ExceptionHandler = ResourceLoader.ExceptionHandler ?? (Internals.XamlLoader.DoNotThrowOnExceptions ? e => { } : (Action<Exception>)null)
168 #pragma warning restore 0618
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)
178 object inflatedView = null;
179 using (var textreader = new StringReader(xaml))
180 using (var reader = XmlReader.Create(textreader))
182 inflatedView = Create(reader, doNotThrow);
187 public static object Create(XmlReader reader, bool doNotThrow = false)
189 object inflatedView = null;
192 while (reader.Read())
195 if (reader.NodeType == XmlNodeType.Whitespace)
197 if (reader.NodeType == XmlNodeType.XmlDeclaration)
199 if (reader.NodeType != XmlNodeType.Element)
201 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
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
209 ExceptionHandler = doNotThrow ? e => { } : (Action<Exception>)null,
211 var cvv = new CreateValuesVisitor(visitorContext);
213 // Visit Parameter Properties to create instance from parameterized constructor
214 var type = XamlParser.GetElementType(rootnode.XmlType, rootnode, null, out XamlParseException xpe);
220 .DeclaredConstructors.FirstOrDefault(
222 ci.GetParameters().Length != 0 && ci.IsPublic &&
223 ci.GetParameters().All(pi => pi.CustomAttributes.Any(attr => attr.AttributeType == typeof(ParameterAttribute))));
224 if (ctorInfo != null)
226 foreach (var parameter in ctorInfo.GetParameters())
229 parameter.CustomAttributes.First(ca => ca.AttributeType.FullName == "Tizen.NUI.Binding.ParameterAttribute")?
230 .ConstructorArguments.First()
233 var name = new XmlName("", propname);
234 if (rootnode.Properties.TryGetValue(name, out INode node) && node is ValueNode)
236 node.Accept(cvv, rootnode);
242 cvv.Visit((ElementNode)rootnode, null);
243 inflatedView = rootnode.Root = visitorContext.Values[rootnode];
244 visitorContext.RootElement = inflatedView as BindableObject;
246 Visit(rootnode, visitorContext);
253 static void Visit(RootNode rootnode, HydrationContext visitorContext)
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);
265 static string GetAnimationXaml(string animationXamlPath)
268 if (File.Exists(animationXamlPath))
270 StreamReader reader = new StreamReader(animationXamlPath);
271 xaml = reader.ReadToEnd();
274 Tizen.Log.Fatal("NUI", "File is exist!, try with xaml: " + xaml);
280 static string GetXamlForType(Type type)
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
286 string resourceName = type.Name + ".xaml";
287 string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
289 Tizen.Log.Fatal("NUI", "the resource path: " + resource);
290 int windowWidth = NUIApplication.GetDefaultWindow().Size.Width;
291 int windowHeight = NUIApplication.GetDefaultWindow().Size.Height;
293 string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName;
294 Tizen.Log.Fatal("NUI", "the resource path: " + likelyResourcePath);
296 if (!File.Exists(likelyResourcePath))
298 likelyResourcePath = resource + "layout/" + resourceName;
301 //Find the xaml file in the layout folder
302 if (File.Exists(likelyResourcePath))
304 StreamReader reader = new StreamReader(likelyResourcePath);
305 xaml = reader.ReadToEnd();
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)))
317 throw new XamlParseException(string.Format("Can't find type {0}", type.FullName), new XmlLineInfo());
322 Assembly assembly = type.Assembly;
324 var resourceId = XamlResourceIdAttribute.GetResourceIdForType(type);
325 if (null == resourceId)
327 throw new XamlParseException(string.Format("Can't find type {0} in embedded resource", type.FullName), new XmlLineInfo());
331 Stream stream = assembly.GetManifestResourceStream(resourceId);
335 Byte[] buffer = new byte[stream.Length];
336 stream.Read(buffer, 0, (int)stream.Length);
338 string ret = System.Text.Encoding.Default.GetString(buffer);
343 throw new XamlParseException(string.Format("Can't get xaml stream {0} in embedded resource", type.FullName), new XmlLineInfo());
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)
353 var assembly = type.GetTypeInfo().Assembly;
356 if (XamlResources.TryGetValue(type, out resourceId))
358 var result = ReadResourceAsXaml(type, assembly, resourceId);
363 var likelyResourceName = type.Name + ".xaml";
364 var resourceNames = assembly.GetManifestResourceNames();
365 string resourceName = null;
367 // first pass, pray to find it because the user named it correctly
369 foreach (var resource in resourceNames)
371 if (ResourceMatchesFilename(assembly, resource, likelyResourceName))
373 resourceName = resource;
374 var xaml = ReadResourceAsXaml(type, assembly, resource);
380 // okay maybe they at least named it .xaml
382 foreach (var resource in resourceNames)
384 if (!resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
387 resourceName = resource;
388 var xaml = ReadResourceAsXaml(type, assembly, resource);
393 foreach (var resource in resourceNames)
395 if (resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
398 resourceName = resource;
399 var xaml = ReadResourceAsXaml(type, assembly, resource, true);
408 static bool ResourceMatchesFilename(Assembly assembly, string resource, string filename)
412 var info = assembly.GetManifestResourceInfo(resource);
414 if (!string.IsNullOrEmpty(info.FileName) &&
415 string.Compare(info.FileName, filename, StringComparison.OrdinalIgnoreCase) == 0)
418 catch (PlatformNotSupportedException)
420 // Because Win10 + .NET Native
423 if (resource.EndsWith("." + filename, StringComparison.OrdinalIgnoreCase) ||
424 string.Compare(resource, filename, StringComparison.OrdinalIgnoreCase) == 0)
430 //part of the legacy as well...
431 static string ReadResourceAsXaml(Type type, Assembly assembly, string likelyTargetName, bool validate = false)
433 using (var stream = assembly.GetManifestResourceStream(likelyTargetName))
434 using (var reader = new StreamReader(stream))
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.
441 var firstNonWhitespace = (char)reader.Read();
442 while (char.IsWhiteSpace(firstNonWhitespace))
443 firstNonWhitespace = (char)reader.Read();
445 if (firstNonWhitespace != '<')
448 stream.Seek(0, SeekOrigin.Begin);
451 var xaml = reader.ReadToEnd();
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)))
461 public class RuntimeRootNode : RootNode
463 public RuntimeRootNode(XmlType xmlType, object root, IXmlNamespaceResolver resolver) : base(xmlType, resolver)
468 public object Root { get; internal set; }
471 internal static string GetXamlForName(string nameOfXamlFile)
474 string resourceName = nameOfXamlFile + ".xaml";
475 string resource = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
477 NUILog.Debug($"resource=({resource})");
479 int windowWidth = NUIApplication.GetDefaultWindow().Size.Width;
480 int windowHeight = NUIApplication.GetDefaultWindow().Size.Height;
482 string likelyResourcePath = resource + "layout/" + windowWidth.ToString() + "x" + windowHeight.ToString() + "/" + resourceName;
484 NUILog.Debug($"likelyResourcePath=({likelyResourcePath})");
487 if (!File.Exists(likelyResourcePath))
489 likelyResourcePath = resource + "layout/" + resourceName;
492 //Find the xaml file in the layout folder
493 if (File.Exists(likelyResourcePath))
495 StreamReader reader = new StreamReader(likelyResourcePath);
496 xaml = reader.ReadToEnd();
498 NUILog.Debug($"File is exist!, try with xaml: {xaml}");
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")))
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")))
518 throw new XamlParseException(string.Format("Can't find type {0}", "Tizen.NUI.XamlMainPage nor View nor Layer"), new XmlLineInfo());