[NUI] Adjust directory (#903)
[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://tizen.org/Tizen.NUI/2018/XAML";
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;
278                 XmlnsHelper.ParseXmlns(kvp.Value, out typeName, out ns, out asm);
279             }
280             return prefixes;
281         }
282
283         static IValueNode GetValueNode(object value, XmlReader reader)
284         {
285             var valueString = value as string;
286             if (valueString != null && valueString.Trim().StartsWith("{}", StringComparison.Ordinal))
287             {
288                 return new ValueNode(valueString.Substring(2), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
289                     ((IXmlLineInfo)reader).LinePosition);
290             }
291             if (valueString != null && valueString.Trim().StartsWith("{", StringComparison.Ordinal))
292             {
293                 return new MarkupNode(valueString.Trim(), reader as IXmlNamespaceResolver, ((IXmlLineInfo)reader).LineNumber,
294                     ((IXmlLineInfo)reader).LinePosition);
295             }
296             return new ValueNode(value, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
297                 ((IXmlLineInfo)reader).LinePosition);
298         }
299
300         static IList<XmlnsDefinitionAttribute> s_xmlnsDefinitions;
301         public static IList<Assembly> s_assemblies = new List<Assembly>();// = new Assembly[]{};
302
303         static void GatherXmlnsDefinitionAttributes()
304         {
305             //this could be extended to look for [XmlnsDefinition] in all assemblies
306             // var assemblies = new [] {
307             //  typeof(View).GetTypeInfo().Assembly,
308             //  //typeof(XamlLoader).GetTypeInfo().Assembly,
309             // };
310             // s_assemblies = new Assembly[]{typeof(View).GetTypeInfo().Assembly};
311             s_assemblies.Add(typeof(View).GetTypeInfo().Assembly);
312
313             s_xmlnsDefinitions = new List<XmlnsDefinitionAttribute>();
314
315             foreach (var assembly in s_assemblies)
316                 foreach (XmlnsDefinitionAttribute attribute in assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute))) {
317                     s_xmlnsDefinitions.Add(attribute);
318                     attribute.AssemblyName = attribute.AssemblyName ?? assembly.FullName;
319                 }
320         }
321
322         public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly,
323             out XamlParseException exception)
324         {
325             if (s_xmlnsDefinitions == null)
326                 GatherXmlnsDefinitionAttributes();
327
328             var namespaceURI = xmlType.NamespaceUri;
329             var elementName = xmlType.Name;
330             var typeArguments = xmlType.TypeArguments;
331             exception = null;
332
333             var lookupAssemblies = new List<XmlnsDefinitionAttribute>();
334             var lookupNames = new List<string>();
335
336             foreach (var xmlnsDef in s_xmlnsDefinitions) {
337                 if (xmlnsDef.XmlNamespace != namespaceURI)
338                     continue;
339                 lookupAssemblies.Add(xmlnsDef);
340             }
341
342             if (lookupAssemblies.Count == 0) {
343                 string ns, asmstring, _;
344                 XmlnsHelper.ParseXmlns(namespaceURI, out _, out ns, out asmstring);
345                 lookupAssemblies.Add(new XmlnsDefinitionAttribute(namespaceURI, ns) {
346                     AssemblyName = asmstring ?? currentAssembly.FullName
347                 });
348             }
349
350             lookupNames.Add(elementName);
351             lookupNames.Add(elementName + "Extension");
352
353             for (var i = 0; i < lookupNames.Count; i++)
354             {
355                 var name = lookupNames[i];
356                 if (name.Contains(":"))
357                     name = name.Substring(name.LastIndexOf(':') + 1);
358                 if (typeArguments != null)
359                     name += "`" + typeArguments.Count; //this will return an open generic Type
360                 lookupNames[i] = name;
361             }
362
363             Type type = null;
364             foreach (var asm in lookupAssemblies) {
365                 foreach (var name in lookupNames)
366                     if ((type = Type.GetType($"{asm.ClrNamespace}.{name}, {asm.AssemblyName}")) != null)
367                         break;
368                 if (type != null)
369                     break;
370             }
371
372             if (type != null && typeArguments != null)
373             {
374                 XamlParseException innerexception = null;
375                 var args = typeArguments.Select(delegate(XmlType xmltype)
376                 {
377                     XamlParseException xpe;
378                     var t = GetElementType(xmltype, xmlInfo, currentAssembly, out xpe);
379                     if (xpe != null)
380                     {
381                         innerexception = xpe;
382                         return null;
383                     }
384                     return t;
385                 }).ToArray();
386                 if (innerexception != null)
387                 {
388                     exception = innerexception;
389                     return null;
390                 }
391                 type = type.MakeGenericType(args);
392             }
393
394             if (type == null)
395                 exception = new XamlParseException($"Type {elementName} not found in xmlns {namespaceURI}", xmlInfo);
396
397             return type;
398         }
399     }
400 }