f2b8e91d4671b9bb015935ff86674d24dd68d9f8
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Xaml / XamlParser.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 // XamlParser.cs
19 //
20 // Author:
21 //       Stephane Delcroix <stephane@mi8.be>
22 //
23 // Copyright (c) 2013 Mobile Inception
24 // Copyright (c) 2013-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.Diagnostics;
47 using System.Linq;
48 using System.Reflection;
49 using System.Xml;
50 using Tizen.NUI.Binding;
51 using Tizen.NUI.BaseComponents;
52 using Tizen.NUI.Binding.Internals;
53
54 namespace Tizen.NUI.Xaml
55 {
56     internal static class XamlParser
57     {
58         public const string XFUri = "http://tizen.org/Tizen.NUI/2018/XAML";
59         public const string NUI2018Uri = "http://tizen.org/Tizen.NUI/2018/XAML";
60         public const string X2006Uri = "http://schemas.microsoft.com/winfx/2006/xaml";
61         public const string X2009Uri = "http://schemas.microsoft.com/winfx/2009/xaml";
62         public const string McUri = "http://schemas.openxmlformats.org/markup-compatibility/2006";
63
64         public static void ParseXaml(RootNode rootNode, XmlReader reader)
65         {
66             IList<KeyValuePair<string, string>> xmlns;
67             var attributes = ParseXamlAttributes(reader, out xmlns);
68             var prefixes = PrefixesToIgnore(xmlns);
69             (rootNode.IgnorablePrefixes ?? (rootNode.IgnorablePrefixes = new List<string>())).AddRange(prefixes);
70             rootNode.Properties.AddRange(attributes);
71             ParseXamlElementFor(rootNode, reader);
72         }
73
74         static void ParseXamlElementFor(IElementNode node, XmlReader reader)
75         {
76             Debug.Assert(reader.NodeType == XmlNodeType.Element);
77
78             var elementName = reader.Name;
79             var isEmpty = reader.IsEmptyElement;
80
81             if (isEmpty)
82                 return;
83
84             while (reader.Read())
85             {
86                 switch (reader.NodeType)
87                 {
88                     case XmlNodeType.EndElement:
89                         Debug.Assert(reader.Name == elementName); //make sure we close the right element
90                         return;
91                     case XmlNodeType.Element:
92                         // 1. Property Element.
93                         if (reader.Name.Contains("."))
94                         {
95                             XmlName name;
96                             if (reader.Name.StartsWith(elementName + ".", StringComparison.Ordinal))
97                                 name = new XmlName(reader.NamespaceURI, reader.Name.Substring(elementName.Length + 1));
98                             else //Attached DP
99                                 name = new XmlName(reader.NamespaceURI, reader.LocalName);
100
101                             var prop = ReadNode(reader);
102                             if (prop != null)
103                                 node.Properties.Add(name, prop);
104                         }
105                         // 2. Xaml2009 primitives, x:Arguments, ...
106                         else if (reader.NamespaceURI == X2009Uri && reader.LocalName == "Arguments")
107                         {
108                             var prop = ReadNode(reader);
109                             if (prop != null)
110                                 node.Properties.Add(XmlName.xArguments, prop);
111                         }
112                         // 3. DataTemplate (should be handled by 4.)
113                         else if ((node.XmlType.NamespaceUri == XFUri || node.XmlType.NamespaceUri == NUI2018Uri) &&
114                                  (node.XmlType.Name == "DataTemplate" || node.XmlType.Name == "ControlTemplate"))
115                         {
116                             var prop = ReadNode(reader, true);
117                             if (prop != null)
118                                 node.Properties.Add(XmlName._CreateContent, prop);
119                         }
120                         // 4. Implicit content, implicit collection, or collection syntax. Add to CollectionItems, resolve case later.
121                         else
122                         {
123                             var item = ReadNode(reader, true);
124                             if (item != null)
125                                 node.CollectionItems.Add(item);
126                         }
127                         break;
128                     case XmlNodeType.Whitespace:
129                         break;
130                     case XmlNodeType.Text:
131                     case XmlNodeType.CDATA:
132                         if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode)
133                             ((ValueNode)node.CollectionItems[0]).Value += reader.Value.Trim();
134                         else
135                             node.CollectionItems.Add(new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader));
136                         break;
137                     default:
138                         Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
139                         break;
140                 }
141             }
142         }
143
144         static INode ReadNode(XmlReader reader, bool nested = false)
145         {
146             var skipFirstRead = nested;
147             Debug.Assert(reader.NodeType == XmlNodeType.Element);
148             var name = reader.Name;
149             List<INode> nodes = new List<INode>();
150             INode node = null;
151
152             while (skipFirstRead || reader.Read())
153             {
154                 skipFirstRead = false;
155
156                 switch (reader.NodeType)
157                 {
158                     case XmlNodeType.EndElement:
159                         Debug.Assert(reader.Name == name);
160                         if (nodes.Count == 0) //Empty element
161                             return null;
162                         if (nodes.Count == 1)
163                             return nodes[0];
164                         return new ListNode(nodes, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
165                             ((IXmlLineInfo)reader).LinePosition);
166                     case XmlNodeType.Element:
167                         var isEmpty = reader.IsEmptyElement && reader.Name == name;
168                         var elementName = reader.Name;
169                         var elementNsUri = reader.NamespaceURI;
170                         var elementXmlInfo = (IXmlLineInfo)reader;
171                         IList<KeyValuePair<string, string>> xmlns;
172
173                         var attributes = ParseXamlAttributes(reader, out xmlns);
174                         var prefixes = PrefixesToIgnore(xmlns);
175
176                         IList<XmlType> typeArguments = null;
177                         if (attributes.Any(kvp => kvp.Key == XmlName.xTypeArguments))
178                         {
179                             typeArguments =
180                                 ((ValueNode)attributes.First(kvp => kvp.Key == XmlName.xTypeArguments).Value).Value as IList<XmlType>;
181                         }
182
183                         node = new ElementNode(new XmlType(elementNsUri, elementName, typeArguments), elementNsUri,
184                             reader as IXmlNamespaceResolver, elementXmlInfo.LineNumber, elementXmlInfo.LinePosition);
185                         ((IElementNode)node).Properties.AddRange(attributes);
186                         (node.IgnorablePrefixes ?? (node.IgnorablePrefixes = new List<string>()))?.AddRange(prefixes);
187
188                         ParseXamlElementFor((IElementNode)node, reader);
189                         nodes.Add(node);
190                         if (isEmpty || nested)
191                             return node;
192                         break;
193                     case XmlNodeType.Text:
194                         node = new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
195                             ((IXmlLineInfo)reader).LinePosition);
196                         nodes.Add(node);
197                         break;
198                     case XmlNodeType.Whitespace:
199                         break;
200                     default:
201                         Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
202                         break;
203                 }
204             }
205             throw new XamlParseException("Closing PropertyElement expected", (IXmlLineInfo)reader);
206         }
207
208         static IList<KeyValuePair<XmlName, INode>> ParseXamlAttributes(XmlReader reader, out IList<KeyValuePair<string, string>> xmlns)
209         {
210             Debug.Assert(reader.NodeType == XmlNodeType.Element);
211             var attributes = new List<KeyValuePair<XmlName, INode>>();
212             xmlns = new List<KeyValuePair<string, string>>();
213             for (var i = 0; i < reader.AttributeCount; i++)
214             {
215                 reader.MoveToAttribute(i);
216
217                 //skip xmlns
218                 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
219                 {
220                     xmlns.Add(new KeyValuePair<string, string>(reader.LocalName, reader.Value));
221                     continue;
222                 }
223
224                 var namespaceUri = reader.NamespaceURI;
225                 if (reader.LocalName.Contains(".") && string.IsNullOrEmpty(namespaceUri))
226                     namespaceUri = ((IXmlNamespaceResolver)reader).LookupNamespace("");
227                 var propertyName = new XmlName(namespaceUri, reader.LocalName);
228
229                 object value = reader.Value;
230
231                 if (reader.NamespaceURI == X2006Uri)
232                 {
233                     switch (reader.Name)
234                     {
235                         case "x:Key":
236                             propertyName = XmlName.xKey;
237                             break;
238                         case "x:Name":
239                             propertyName = XmlName.xName;
240                             break;
241                         case "x:Class":
242                         case "x:FieldModifier":
243                             continue;
244                         default:
245                             Debug.WriteLine("Unhandled attribute {0}", reader.Name);
246                             continue;
247                     }
248                 }
249
250                 if (reader.NamespaceURI == X2009Uri)
251                 {
252                     switch (reader.Name)
253                     {
254                         case "x:Key":
255                             propertyName = XmlName.xKey;
256                             break;
257                         case "x:Name":
258                             propertyName = XmlName.xName;
259                             break;
260                         case "x:TypeArguments":
261                             propertyName = XmlName.xTypeArguments;
262                             value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader);
263                             break;
264                         case "x:DataType":
265                             propertyName = XmlName.xDataType;
266                             break;
267                         case "x:Class":
268                         case "x:FieldModifier":
269                             continue;
270                         case "x:FactoryMethod":
271                             propertyName = XmlName.xFactoryMethod;
272                             break;
273                         case "x:Arguments":
274                             propertyName = XmlName.xArguments;
275                             break;
276                         default:
277                             Debug.WriteLine("Unhandled attribute {0}", reader.Name);
278                             continue;
279                     }
280                 }
281
282                 var propertyNode = GetValueNode(value, reader);
283                 attributes.Add(new KeyValuePair<XmlName, INode>(propertyName, propertyNode));
284             }
285             reader.MoveToElement();
286             return attributes;
287         }
288
289         static IList<string> PrefixesToIgnore(IList<KeyValuePair<string, string>> xmlns)
290         {
291             var prefixes = new List<string>();
292             foreach (var kvp in xmlns)
293             {
294                 var prefix = kvp.Key;
295
296                 string typeName = null, ns = null, asm = null;
297                 XmlnsHelper.ParseXmlns(kvp.Value, out typeName, out ns, out asm);
298             }
299             return prefixes;
300         }
301
302         static IValueNode GetValueNode(object value, XmlReader reader)
303         {
304             var valueString = value as string;
305             if (valueString != null && valueString.Trim().StartsWith("{}", StringComparison.Ordinal))
306             {
307                 return new ValueNode(valueString.Substring(2), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
308                     ((IXmlLineInfo)reader).LinePosition);
309             }
310             if (valueString != null && valueString.Trim().StartsWith("{", StringComparison.Ordinal))
311             {
312                 return new MarkupNode(valueString.Trim(), reader as IXmlNamespaceResolver, ((IXmlLineInfo)reader).LineNumber,
313                     ((IXmlLineInfo)reader).LinePosition);
314             }
315             return new ValueNode(value, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
316                 ((IXmlLineInfo)reader).LinePosition);
317         }
318
319         static IList<XmlnsDefinitionAttribute> s_xmlnsDefinitions;
320         public static IList<Assembly> s_assemblies = new List<Assembly>();// = new Assembly[]{};
321
322         static void GatherXmlnsDefinitionAttributes(Assembly currentAssembly)
323         {
324             //this could be extended to look for [XmlnsDefinition] in all assemblies
325             // var assemblies = new [] {
326             //  typeof(View).GetTypeInfo().Assembly,
327             //  //typeof(XamlLoader).GetTypeInfo().Assembly,
328             // };
329             // s_assemblies = new Assembly[]{typeof(View).GetTypeInfo().Assembly};
330             if (null == currentAssembly)
331             {
332                 s_assemblies.Add(typeof(View).GetTypeInfo().Assembly);
333             }
334             else
335             {
336                 s_assemblies.Add(currentAssembly);
337
338                 var assemblies = currentAssembly?.GetReferencedAssemblies();
339
340                 if (null != assemblies)
341                 {
342                     foreach (var assembly in assemblies)
343                     {
344                         try
345                         {
346                             s_assemblies.Add(Assembly.Load(assembly));
347                         }
348                         catch (Exception e)
349                         {
350                             Tizen.Log.Fatal("NUI", "Load referenced assemblies e.Message: " + e.Message);
351                             Console.WriteLine("\n[FATAL] Load referenced assemblies e.Message: {0}\n", e.Message);
352                             throw new XamlParseException(e.Message);
353                         }
354                     }
355                 }
356             }
357
358             s_xmlnsDefinitions = new List<XmlnsDefinitionAttribute>();
359
360             foreach (var assembly in s_assemblies)
361                 foreach (XmlnsDefinitionAttribute attribute in assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute)))
362                 {
363                     s_xmlnsDefinitions.Add(attribute);
364                     attribute.AssemblyName = attribute.AssemblyName ?? assembly.FullName;
365                 }
366         }
367
368         public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly,
369             out XamlParseException exception)
370         {
371             if (s_xmlnsDefinitions == null)
372                 GatherXmlnsDefinitionAttributes(currentAssembly);
373
374             var namespaceURI = xmlType.NamespaceUri;
375             var elementName = xmlType.Name;
376             var typeArguments = xmlType.TypeArguments;
377             exception = null;
378
379             var lookupAssemblies = new List<XmlnsDefinitionAttribute>();
380             var lookupNames = new List<string>();
381
382             foreach (var xmlnsDef in s_xmlnsDefinitions)
383             {
384                 if (xmlnsDef.XmlNamespace != namespaceURI)
385                     continue;
386                 lookupAssemblies.Add(xmlnsDef);
387             }
388
389             if (lookupAssemblies.Count == 0)
390             {
391                 string ns, asmstring, _;
392                 XmlnsHelper.ParseXmlns(namespaceURI, out _, out ns, out asmstring);
393                 lookupAssemblies.Add(new XmlnsDefinitionAttribute(namespaceURI, ns)
394                 {
395                     AssemblyName = asmstring ?? currentAssembly.FullName
396                 });
397             }
398
399             lookupNames.Add(elementName);
400             lookupNames.Add(elementName + "Extension");
401
402             for (var i = 0; i < lookupNames.Count; i++)
403             {
404                 var name = lookupNames[i];
405                 if (name.Contains(":"))
406                     name = name.Substring(name.LastIndexOf(':') + 1);
407                 if (typeArguments != null)
408                     name += "`" + typeArguments.Count; //this will return an open generic Type
409                 lookupNames[i] = name;
410             }
411
412             Type type = null;
413             foreach (var asm in lookupAssemblies)
414             {
415                 foreach (var name in lookupNames)
416                 {
417                     if ((type = Type.GetType($"{asm.ClrNamespace}.{name}, {asm.AssemblyName}")) != null)
418                         break;
419
420                     if ('?' == name.Last())
421                     {
422                         string nameOfNotNull = name.Substring(0, name.Length - 1);
423                         Type typeofNotNull = Type.GetType($"{asm.ClrNamespace}.{nameOfNotNull}, {asm.AssemblyName}");
424
425                         if (null != typeofNotNull)
426                         {
427                             type = typeof(Nullable<>).MakeGenericType(new Type[] { typeofNotNull });
428                             break;
429                         }
430                     }
431                 }
432
433                 if (type != null)
434                     break;
435             }
436
437             if (type != null && typeArguments != null)
438             {
439                 XamlParseException innerexception = null;
440                 var args = typeArguments.Select(delegate (XmlType xmltype)
441                 {
442                     XamlParseException xpe;
443                     var t = GetElementType(xmltype, xmlInfo, currentAssembly, out xpe);
444                     if (xpe != null)
445                     {
446                         innerexception = xpe;
447                         return null;
448                     }
449                     return t;
450                 }).ToArray();
451                 if (innerexception != null)
452                 {
453                     exception = innerexception;
454                     return null;
455                 }
456                 type = type.MakeGenericType(args);
457             }
458
459             if (type == null)
460             {
461                 var message = $"Type {elementName} not found in xmlns {namespaceURI}\n";
462                 message += "\n  - Make sure the all used assemblies (e.g. Tizen.NUI.Components) are included in the application project.";
463                 message += "\n  - Make sure the type and namespace are correct.\n";
464                 exception = new XamlParseException($"message", xmlInfo);
465             }
466
467             return type;
468         }
469     }
470 }