2 * Copyright(c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 // Stephane Delcroix <stephane@mi8.be>
23 // Copyright (c) 2013 Mobile Inception
24 // Copyright (c) 2013-2014 Xamarin, Inc
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:
33 // The above copyright notice and this permission notice shall be included in
34 // all copies or substantial portions of the Software.
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
45 using System.Collections.Generic;
46 using System.Diagnostics;
48 using System.Reflection;
50 using Tizen.NUI.Binding;
51 using Tizen.NUI.BaseComponents;
52 using Tizen.NUI.Binding.Internals;
54 namespace Tizen.NUI.Xaml
56 internal static class XamlParser
58 public const string XFUri = "http://tizen.org/Tizen.NUI/2018/XAML";
59 public const string NUI2018Uri = "http://tizen.org/Tizen.NUI/2018/XAML";
60 public const string X2006Uri = "http://schemas.microsoft.com/winfx/2006/xaml";
61 public const string X2009Uri = "http://schemas.microsoft.com/winfx/2009/xaml";
62 public const string McUri = "http://schemas.openxmlformats.org/markup-compatibility/2006";
64 public static void ParseXaml(RootNode rootNode, XmlReader reader)
66 // Reset xmlnsDefinitions to re-gather them for the new assembly.
67 s_xmlnsDefinitions = null;
69 IList<KeyValuePair<string, string>> xmlns;
70 var attributes = ParseXamlAttributes(reader, out xmlns);
71 var prefixes = PrefixesToIgnore(xmlns);
72 (rootNode.IgnorablePrefixes ?? (rootNode.IgnorablePrefixes = new List<string>())).AddRange(prefixes);
73 rootNode.Properties.AddRange(attributes);
74 ParseXamlElementFor(rootNode, reader);
77 static void ParseXamlElementFor(IElementNode node, XmlReader reader)
79 Debug.Assert(reader.NodeType == XmlNodeType.Element);
81 var elementName = reader.Name;
82 var isEmpty = reader.IsEmptyElement;
89 switch (reader.NodeType)
91 case XmlNodeType.EndElement:
92 Debug.Assert(reader.Name == elementName); //make sure we close the right element
94 case XmlNodeType.Element:
95 // 1. Property Element.
96 if (reader.Name.Contains("."))
99 if (reader.Name.StartsWith(elementName + ".", StringComparison.Ordinal))
100 name = new XmlName(reader.NamespaceURI, reader.Name.Substring(elementName.Length + 1));
102 name = new XmlName(reader.NamespaceURI, reader.LocalName);
104 var prop = ReadNode(reader);
106 node.Properties.Add(name, prop);
108 // 2. Xaml2009 primitives, x:Arguments, ...
109 else if (reader.NamespaceURI == X2009Uri && reader.LocalName == "Arguments")
111 var prop = ReadNode(reader);
113 node.Properties.Add(XmlName.xArguments, prop);
115 // 3. DataTemplate (should be handled by 4.)
116 else if ((node.XmlType.NamespaceUri == XFUri || node.XmlType.NamespaceUri == NUI2018Uri) &&
117 (node.XmlType.Name == "DataTemplate" || node.XmlType.Name == "ControlTemplate"))
119 var prop = ReadNode(reader, true);
121 node.Properties.Add(XmlName._CreateContent, prop);
123 // 4. Implicit content, implicit collection, or collection syntax. Add to CollectionItems, resolve case later.
126 var item = ReadNode(reader, true);
128 node.CollectionItems.Add(item);
131 case XmlNodeType.Whitespace:
133 case XmlNodeType.Text:
134 case XmlNodeType.CDATA:
135 if (node.CollectionItems.Count == 1 && node.CollectionItems[0] is ValueNode)
136 ((ValueNode)node.CollectionItems[0]).Value += reader.Value.Trim();
138 node.CollectionItems.Add(new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader));
141 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
147 static INode ReadNode(XmlReader reader, bool nested = false)
149 var skipFirstRead = nested;
150 Debug.Assert(reader.NodeType == XmlNodeType.Element);
151 var name = reader.Name;
152 List<INode> nodes = new List<INode>();
155 while (skipFirstRead || reader.Read())
157 skipFirstRead = false;
159 switch (reader.NodeType)
161 case XmlNodeType.EndElement:
162 Debug.Assert(reader.Name == name);
163 if (nodes.Count == 0) //Empty element
165 if (nodes.Count == 1)
167 return new ListNode(nodes, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
168 ((IXmlLineInfo)reader).LinePosition);
169 case XmlNodeType.Element:
170 var isEmpty = reader.IsEmptyElement && reader.Name == name;
171 var elementName = reader.Name;
172 var elementNsUri = reader.NamespaceURI;
173 var elementXmlInfo = (IXmlLineInfo)reader;
174 IList<KeyValuePair<string, string>> xmlns;
176 var attributes = ParseXamlAttributes(reader, out xmlns);
177 var prefixes = PrefixesToIgnore(xmlns);
179 IList<XmlType> typeArguments = null;
180 if (attributes.Any(kvp => kvp.Key == XmlName.xTypeArguments))
183 ((ValueNode)attributes.First(kvp => kvp.Key == XmlName.xTypeArguments).Value).Value as IList<XmlType>;
186 node = new ElementNode(new XmlType(elementNsUri, elementName, typeArguments), elementNsUri,
187 reader as IXmlNamespaceResolver, elementXmlInfo.LineNumber, elementXmlInfo.LinePosition);
188 ((IElementNode)node).Properties.AddRange(attributes);
189 (node.IgnorablePrefixes ?? (node.IgnorablePrefixes = new List<string>()))?.AddRange(prefixes);
191 ParseXamlElementFor((IElementNode)node, reader);
193 if (isEmpty || nested)
196 case XmlNodeType.Text:
197 node = new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
198 ((IXmlLineInfo)reader).LinePosition);
201 case XmlNodeType.Whitespace:
204 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
208 throw new XamlParseException("Closing PropertyElement expected", (IXmlLineInfo)reader);
211 static IList<KeyValuePair<XmlName, INode>> ParseXamlAttributes(XmlReader reader, out IList<KeyValuePair<string, string>> xmlns)
213 Debug.Assert(reader.NodeType == XmlNodeType.Element);
214 var attributes = new List<KeyValuePair<XmlName, INode>>();
215 xmlns = new List<KeyValuePair<string, string>>();
216 for (var i = 0; i < reader.AttributeCount; i++)
218 reader.MoveToAttribute(i);
221 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
223 xmlns.Add(new KeyValuePair<string, string>(reader.LocalName, reader.Value));
227 var namespaceUri = reader.NamespaceURI;
228 if (reader.LocalName.Contains(".") && string.IsNullOrEmpty(namespaceUri))
229 namespaceUri = ((IXmlNamespaceResolver)reader).LookupNamespace("");
230 var propertyName = new XmlName(namespaceUri, reader.LocalName);
232 object value = reader.Value;
234 if (reader.NamespaceURI == X2006Uri)
239 propertyName = XmlName.xKey;
242 propertyName = XmlName.xName;
245 case "x:FieldModifier":
248 Debug.WriteLine("Unhandled attribute {0}", reader.Name);
253 if (reader.NamespaceURI == X2009Uri)
258 propertyName = XmlName.xKey;
261 propertyName = XmlName.xName;
263 case "x:TypeArguments":
264 propertyName = XmlName.xTypeArguments;
265 value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader);
268 propertyName = XmlName.xDataType;
271 case "x:FieldModifier":
273 case "x:FactoryMethod":
274 propertyName = XmlName.xFactoryMethod;
277 propertyName = XmlName.xArguments;
280 Debug.WriteLine("Unhandled attribute {0}", reader.Name);
285 var propertyNode = GetValueNode(value, reader);
286 attributes.Add(new KeyValuePair<XmlName, INode>(propertyName, propertyNode));
288 reader.MoveToElement();
292 static IList<string> PrefixesToIgnore(IList<KeyValuePair<string, string>> xmlns)
294 var prefixes = new List<string>();
295 foreach (var kvp in xmlns)
297 var prefix = kvp.Key;
299 string typeName = null, ns = null, asm = null;
300 XmlnsHelper.ParseXmlns(kvp.Value, out typeName, out ns, out asm);
305 static IValueNode GetValueNode(object value, XmlReader reader)
307 var valueString = value as string;
308 if (valueString != null && valueString.Trim().StartsWith("{}", StringComparison.Ordinal))
310 return new ValueNode(valueString.Substring(2), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
311 ((IXmlLineInfo)reader).LinePosition);
313 if (valueString != null && valueString.Trim().StartsWith("{", StringComparison.Ordinal))
315 return new MarkupNode(valueString.Trim(), reader as IXmlNamespaceResolver, ((IXmlLineInfo)reader).LineNumber,
316 ((IXmlLineInfo)reader).LinePosition);
318 return new ValueNode(value, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
319 ((IXmlLineInfo)reader).LinePosition);
322 static IList<XmlnsDefinitionAttribute> s_xmlnsDefinitions;
323 public static IList<Assembly> s_assemblies = new List<Assembly>();// = new Assembly[]{};
325 static void GatherXmlnsDefinitionAttributes(Assembly currentAssembly)
327 //this could be extended to look for [XmlnsDefinition] in all assemblies
328 // var assemblies = new [] {
329 // typeof(View).GetTypeInfo().Assembly,
330 // //typeof(XamlLoader).GetTypeInfo().Assembly,
332 // s_assemblies = new Assembly[]{typeof(View).GetTypeInfo().Assembly};
333 if (null == currentAssembly)
335 s_assemblies.Add(typeof(View).GetTypeInfo().Assembly);
339 s_assemblies.Add(currentAssembly);
341 var assemblies = currentAssembly?.GetReferencedAssemblies();
343 if (null != assemblies)
345 foreach (var assembly in assemblies)
349 s_assemblies.Add(Assembly.Load(assembly));
353 Tizen.Log.Fatal("NUI", "Load referenced assemblies e.Message: " + e.Message);
354 Console.WriteLine("\n[FATAL] Load referenced assemblies e.Message: {0}\n", e.Message);
355 throw new XamlParseException(e.Message);
361 s_xmlnsDefinitions = new List<XmlnsDefinitionAttribute>();
363 foreach (var assembly in s_assemblies)
364 foreach (XmlnsDefinitionAttribute attribute in assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute)))
366 s_xmlnsDefinitions.Add(attribute);
367 attribute.AssemblyName = attribute.AssemblyName ?? assembly.FullName;
371 public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly,
372 out XamlParseException exception)
374 if (s_xmlnsDefinitions == null)
375 GatherXmlnsDefinitionAttributes(currentAssembly);
377 var namespaceURI = xmlType.NamespaceUri;
378 var elementName = xmlType.Name;
379 var typeArguments = xmlType.TypeArguments;
382 if (elementName.Contains("-"))
384 elementName = elementName.Replace('-', '+');
387 var lookupAssemblies = new List<XmlnsDefinitionAttribute>();
388 var lookupNames = new List<string>();
390 foreach (var xmlnsDef in s_xmlnsDefinitions)
392 if (xmlnsDef.XmlNamespace != namespaceURI)
394 lookupAssemblies.Add(xmlnsDef);
397 if (lookupAssemblies.Count == 0)
399 string ns, asmstring, _;
400 XmlnsHelper.ParseXmlns(namespaceURI, out _, out ns, out asmstring);
401 lookupAssemblies.Add(new XmlnsDefinitionAttribute(namespaceURI, ns)
403 AssemblyName = asmstring ?? currentAssembly.FullName
407 lookupNames.Add(elementName);
408 lookupNames.Add(elementName + "Extension");
410 for (var i = 0; i < lookupNames.Count; i++)
412 var name = lookupNames[i];
413 if (name.Contains(":"))
414 name = name.Substring(name.LastIndexOf(':') + 1);
415 if (typeArguments != null)
416 name += "`" + typeArguments.Count; //this will return an open generic Type
417 lookupNames[i] = name;
421 foreach (var asm in lookupAssemblies)
423 foreach (var name in lookupNames)
425 if ((type = Type.GetType($"{asm.ClrNamespace}.{name}, {asm.AssemblyName}")) != null)
428 if ('?' == name.Last())
430 string nameOfNotNull = name.Substring(0, name.Length - 1);
431 Type typeofNotNull = Type.GetType($"{asm.ClrNamespace}.{nameOfNotNull}, {asm.AssemblyName}");
433 if (null != typeofNotNull)
435 type = typeof(Nullable<>).MakeGenericType(new Type[] { typeofNotNull });
445 if (type != null && typeArguments != null)
447 XamlParseException innerexception = null;
448 var args = typeArguments.Select(delegate (XmlType xmltype)
450 XamlParseException xpe;
451 var t = GetElementType(xmltype, xmlInfo, currentAssembly, out xpe);
454 innerexception = xpe;
459 if (innerexception != null)
461 exception = innerexception;
464 type = type.MakeGenericType(args);
469 var message = $"Type {elementName} not found in xmlns {namespaceURI}\n";
470 message += "\n - Make sure the all used assemblies (e.g. Tizen.NUI.Components) are included in the application project.";
471 message += "\n - Make sure the type and namespace are correct.\n";
472 exception = new XamlParseException($"message", xmlInfo);