5 // Stephane Delcroix <stephane@mi8.be>
7 // Copyright (c) 2013 Mobile Inception
8 // Copyright (c) 2013-2014 Xamarin, Inc
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:
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
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
29 using System.Collections.Generic;
30 using System.Diagnostics;
32 using System.Reflection;
34 using Tizen.NUI.Binding;
35 using Tizen.NUI.Binding.Internals;
37 namespace Tizen.NUI.Xaml
39 internal static class XamlParser
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";
48 public static void ParseXaml(RootNode rootNode, XmlReader reader)
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);
58 static void ParseXamlElementFor(IElementNode node, XmlReader reader)
60 Debug.Assert(reader.NodeType == XmlNodeType.Element);
62 var elementName = reader.Name;
63 var isEmpty = reader.IsEmptyElement;
70 switch (reader.NodeType)
72 case XmlNodeType.EndElement:
73 Debug.Assert(reader.Name == elementName); //make sure we close the right element
75 case XmlNodeType.Element:
76 // 1. Property Element.
77 if (reader.Name.Contains("."))
80 if (reader.Name.StartsWith(elementName + ".", StringComparison.Ordinal))
81 name = new XmlName(reader.NamespaceURI, reader.Name.Substring(elementName.Length + 1));
83 name = new XmlName(reader.NamespaceURI, reader.LocalName);
85 var prop = ReadNode(reader);
87 node.Properties.Add(name, prop);
89 // 2. Xaml2009 primitives, x:Arguments, ...
90 else if (reader.NamespaceURI == X2009Uri && reader.LocalName == "Arguments")
92 var prop = ReadNode(reader);
94 node.Properties.Add(XmlName.xArguments, prop);
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"))
100 var prop = ReadNode(reader, true);
102 node.Properties.Add(XmlName._CreateContent, prop);
104 // 4. Implicit content, implicit collection, or collection syntax. Add to CollectionItems, resolve case later.
107 var item = ReadNode(reader, true);
109 node.CollectionItems.Add(item);
112 case XmlNodeType.Whitespace:
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();
119 node.CollectionItems.Add(new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader));
122 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
128 static INode ReadNode(XmlReader reader, bool nested = false)
130 var skipFirstRead = nested;
131 Debug.Assert(reader.NodeType == XmlNodeType.Element);
132 var name = reader.Name;
133 List<INode> nodes = new List<INode>();
136 while (skipFirstRead || reader.Read())
138 skipFirstRead = false;
140 switch (reader.NodeType)
142 case XmlNodeType.EndElement:
143 Debug.Assert(reader.Name == name);
144 if (nodes.Count == 0) //Empty element
146 if (nodes.Count == 1)
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;
157 var attributes = ParseXamlAttributes(reader, out xmlns);
158 var prefixes = PrefixesToIgnore(xmlns);
160 IList<XmlType> typeArguments = null;
161 if (attributes.Any(kvp => kvp.Key == XmlName.xTypeArguments))
164 ((ValueNode)attributes.First(kvp => kvp.Key == XmlName.xTypeArguments).Value).Value as IList<XmlType>;
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);
172 ParseXamlElementFor((IElementNode)node, reader);
174 if (isEmpty || nested)
177 case XmlNodeType.Text:
178 node = new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
179 ((IXmlLineInfo)reader).LinePosition);
182 case XmlNodeType.Whitespace:
185 Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
189 throw new XamlParseException("Closing PropertyElement expected", (IXmlLineInfo)reader);
192 static IList<KeyValuePair<XmlName, INode>> ParseXamlAttributes(XmlReader reader, out IList<KeyValuePair<string,string>> xmlns)
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++)
199 reader.MoveToAttribute(i);
202 if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/") {
203 xmlns.Add(new KeyValuePair<string, string>(reader.LocalName, reader.Value));
207 var namespaceUri = reader.NamespaceURI;
208 if (reader.LocalName.Contains(".") && namespaceUri == "")
209 namespaceUri = ((IXmlNamespaceResolver)reader).LookupNamespace("");
210 var propertyName = new XmlName(namespaceUri, reader.LocalName);
212 object value = reader.Value;
214 if (reader.NamespaceURI == X2006Uri)
216 switch (reader.Name) {
218 propertyName = XmlName.xKey;
221 propertyName = XmlName.xName;
224 case "x:FieldModifier":
227 Debug.WriteLine("Unhandled attribute {0}", reader.Name);
232 if (reader.NamespaceURI == X2009Uri)
234 switch (reader.Name) {
236 propertyName = XmlName.xKey;
239 propertyName = XmlName.xName;
241 case "x:TypeArguments":
242 propertyName = XmlName.xTypeArguments;
243 value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader);
246 propertyName = XmlName.xDataType;
249 case "x:FieldModifier":
251 case "x:FactoryMethod":
252 propertyName = XmlName.xFactoryMethod;
255 propertyName = XmlName.xArguments;
258 Debug.WriteLine("Unhandled attribute {0}", reader.Name);
263 var propertyNode = GetValueNode(value, reader);
264 attributes.Add(new KeyValuePair<XmlName, INode>(propertyName, propertyNode));
266 reader.MoveToElement();
270 static IList<string> PrefixesToIgnore(IList<KeyValuePair<string, string>> xmlns)
272 var prefixes = new List<string>();
273 foreach (var kvp in xmlns) {
274 var prefix = kvp.Key;
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)
281 if (targetPlatform != Device.RuntimePlatform)
283 // Special case for Windows backward compatibility
284 if (targetPlatform == "Windows" && Device.RuntimePlatform == Device.UWP)
287 prefixes.Add(prefix);
289 } catch (InvalidOperationException) {
290 prefixes.Add(prefix);
296 static IValueNode GetValueNode(object value, XmlReader reader)
298 var valueString = value as string;
299 if (valueString != null && valueString.Trim().StartsWith("{}", StringComparison.Ordinal))
301 return new ValueNode(valueString.Substring(2), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
302 ((IXmlLineInfo)reader).LinePosition);
304 if (valueString != null && valueString.Trim().StartsWith("{", StringComparison.Ordinal))
306 return new MarkupNode(valueString.Trim(), reader as IXmlNamespaceResolver, ((IXmlLineInfo)reader).LineNumber,
307 ((IXmlLineInfo)reader).LinePosition);
309 return new ValueNode(value, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
310 ((IXmlLineInfo)reader).LinePosition);
313 static IList<XmlnsDefinitionAttribute> s_xmlnsDefinitions;
314 public static IList<Assembly> s_assemblies = new List<Assembly>();// = new Assembly[]{};
316 static void GatherXmlnsDefinitionAttributes()
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,
323 // s_assemblies = new Assembly[]{typeof(View).GetTypeInfo().Assembly};
324 s_assemblies.Add(typeof(Element).GetTypeInfo().Assembly);
326 s_xmlnsDefinitions = new List<XmlnsDefinitionAttribute>();
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;
335 public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly,
336 out XamlParseException exception)
338 if (s_xmlnsDefinitions == null)
339 GatherXmlnsDefinitionAttributes();
341 var namespaceURI = xmlType.NamespaceUri;
342 var elementName = xmlType.Name;
343 var typeArguments = xmlType.TypeArguments;
346 if (elementName.Contains("-"))
348 elementName = elementName.Replace('-', '+');
351 var lookupAssemblies = new List<XmlnsDefinitionAttribute>();
352 var lookupNames = new List<string>();
354 foreach (var xmlnsDef in s_xmlnsDefinitions) {
355 if (xmlnsDef.XmlNamespace != namespaceURI)
357 lookupAssemblies.Add(xmlnsDef);
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
368 lookupNames.Add(elementName);
369 lookupNames.Add(elementName + "Extension");
371 for (var i = 0; i < lookupNames.Count; i++)
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;
382 foreach (var asm in lookupAssemblies) {
383 foreach (var name in lookupNames)
384 if ((type = Type.GetType($"{asm.ClrNamespace}.{name}, {asm.AssemblyName}")) != null)
390 if (type != null && typeArguments != null)
392 XamlParseException innerexception = null;
393 var args = typeArguments.Select(delegate(XmlType xmltype)
395 XamlParseException xpe;
396 var t = GetElementType(xmltype, xmlInfo, currentAssembly, out xpe);
399 innerexception = xpe;
404 if (innerexception != null)
406 exception = innerexception;
409 type = type.MakeGenericType(args);
413 exception = new XamlParseException($"Type {elementName} not found in xmlns {namespaceURI}", xmlInfo);