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