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