38d78b8b5b6c115c8246074791320fa63fd9bd67
[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.Internals;
38
39 namespace Tizen.NUI.Xaml
40 {
41         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
316                 static void GatherXmlnsDefinitionAttributes()
317                 {
318                         //this could be extended to look for [XmlnsDefinition] in all assemblies
319                         var assemblies = new [] {
320                                 typeof(View).GetTypeInfo().Assembly,
321                                 typeof(XamlLoader).GetTypeInfo().Assembly,
322                         };
323
324                         s_xmlnsDefinitions = new List<XmlnsDefinitionAttribute>();
325
326                         foreach (var assembly in assemblies)
327                                 foreach (XmlnsDefinitionAttribute attribute in assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute))) {
328                                         s_xmlnsDefinitions.Add(attribute);
329                                         attribute.AssemblyName = attribute.AssemblyName ?? assembly.FullName;
330                                 }
331                 }
332
333                 public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly,
334                         out XamlParseException exception)
335                 {
336                         if (s_xmlnsDefinitions == null)
337                                 GatherXmlnsDefinitionAttributes();
338
339                         var namespaceURI = xmlType.NamespaceUri;
340                         var elementName = xmlType.Name;
341                         var typeArguments = xmlType.TypeArguments;
342                         exception = null;
343
344                         var lookupAssemblies = new List<XmlnsDefinitionAttribute>();
345                         var lookupNames = new List<string>();
346
347                         foreach (var xmlnsDef in s_xmlnsDefinitions) {
348                                 if (xmlnsDef.XmlNamespace != namespaceURI)
349                                         continue;
350                                 lookupAssemblies.Add(xmlnsDef);
351                         }
352
353                         if (lookupAssemblies.Count == 0) {
354                                 string ns, asmstring, _;
355                                 XmlnsHelper.ParseXmlns(namespaceURI, out _, out ns, out asmstring, out _);
356                                 lookupAssemblies.Add(new XmlnsDefinitionAttribute(namespaceURI, ns) {
357                                         AssemblyName = asmstring ?? currentAssembly.FullName
358                                 });
359                         }
360
361                         lookupNames.Add(elementName);
362                         lookupNames.Add(elementName + "Extension");
363
364                         for (var i = 0; i < lookupNames.Count; i++)
365                         {
366                                 var name = lookupNames[i];
367                                 if (name.Contains(":"))
368                                         name = name.Substring(name.LastIndexOf(':') + 1);
369                                 if (typeArguments != null)
370                                         name += "`" + typeArguments.Count; //this will return an open generic Type
371                                 lookupNames[i] = name;
372                         }
373
374                         Type type = null;
375                         foreach (var asm in lookupAssemblies) {
376                                 foreach (var name in lookupNames)
377                                         if ((type = Type.GetType($"{asm.ClrNamespace}.{name}, {asm.AssemblyName}")) != null)
378                                                 break;
379                                 if (type != null)
380                                         break;
381                         }
382
383             if (type == null)
384             {
385                 List<Tuple<string, Assembly>> lookupAssemblies2 = new List<Tuple<string, Assembly>>();
386                 if (namespaceURI == NUI2018Uri)
387                 {
388                     // Got the type of Tizen.NUI wiedget here, then CreateValueVisitor will create the instance of Tizen.NUI widget
389                     lookupAssemblies2.Add(new Tuple<string, Assembly>("Tizen.NUI", typeof(Tizen.NUI.BaseComponents.View).GetTypeInfo().Assembly));
390                     lookupAssemblies2.Add(new Tuple<string, Assembly>("Tizen.NUI.BaseComponents", typeof(Tizen.NUI.BaseComponents.View).GetTypeInfo().Assembly));
391                     lookupAssemblies2.Add(new Tuple<string, Assembly>("Tizen.NUI.UIComponents", typeof(Tizen.NUI.BaseComponents.View).GetTypeInfo().Assembly));
392                     lookupAssemblies2.Add(new Tuple<string, Assembly>("Tizen.NUI.Xaml", typeof(XamlLoader).GetTypeInfo().Assembly));
393                     lookupAssemblies2.Add(new Tuple<string, Assembly>("Tizen.NUI.Binding", typeof(Tizen.NUI.BaseComponents.View).GetTypeInfo().Assembly));
394                 }
395                 else if (namespaceURI == X2009Uri || namespaceURI == X2006Uri)
396                 {
397                     lookupAssemblies2.Add(new Tuple<string, Assembly>("Tizen.NUI.Xaml", typeof(XamlLoader).GetTypeInfo().Assembly));
398                     lookupAssemblies2.Add(new Tuple<string, Assembly>("System", typeof(object).GetTypeInfo().Assembly));
399                     lookupAssemblies2.Add(new Tuple<string, Assembly>("System", typeof(Uri).GetTypeInfo().Assembly)); //System.dll
400                 }
401                 else
402                 {
403                     string ns;
404                     string typename;
405                     string asmstring;
406                     Assembly asm;
407                     XmlnsHelper.ParseXmlns(namespaceURI, out typename, out ns, out asmstring, out _);
408                     asm = asmstring == null ? currentAssembly : Assembly.Load(new AssemblyName(asmstring));
409                     lookupAssemblies2.Add(new Tuple<string, Assembly>(ns, asm));
410                 }
411
412                 foreach (var asm in lookupAssemblies2)
413                 {
414                     if (type != null)
415                         break;
416                     foreach (var name in lookupNames)
417                     {
418                         if (type != null)
419                             break;
420                         type = asm.Item2.GetType(asm.Item1 + "." + name);
421                     }
422                 }
423             }
424
425                         if (type != null && typeArguments != null)
426                         {
427                                 XamlParseException innerexception = null;
428                                 var args = typeArguments.Select(delegate(XmlType xmltype)
429                                 {
430                                         XamlParseException xpe;
431                                         var t = GetElementType(xmltype, xmlInfo, currentAssembly, out xpe);
432                                         if (xpe != null)
433                                         {
434                                                 innerexception = xpe;
435                                                 return null;
436                                         }
437                                         return t;
438                                 }).ToArray();
439                                 if (innerexception != null)
440                                 {
441                                         exception = innerexception;
442                                         return null;
443                                 }
444                                 type = type.MakeGenericType(args);
445                         }
446
447                         if (type == null)
448                                 exception = new XamlParseException($"Type {elementName} not found in xmlns {namespaceURI}", xmlInfo);
449
450                         return type;
451                 }
452         }
453 }