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