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