Introduce new XamlObjectReader implementation.
[mono.git] / mcs / class / System.Xaml / System.Xaml / XamlNode.cs
1 //
2 // Copyright (C) 2010 Novell Inc. http://novell.com
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
11 // 
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 // 
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 //
23 using System;
24 using System.Collections;
25 using System.Collections.Generic;
26 using System.Globalization;
27 using System.Linq;
28 using System.Reflection;
29 using System.Windows.Markup;
30 using System.Xaml.Schema;
31 using System.Xml;
32
33 namespace System.Xaml
34 {
35         internal class PrefixLookup : INamespacePrefixLookup
36         {
37                 public PrefixLookup (XamlSchemaContext schemaContext)
38                 {
39                         sctx = schemaContext;
40                         Namespaces = new List<NamespaceDeclaration> ();
41                 }
42                 
43                 XamlSchemaContext sctx;
44                 
45                 public bool IsCollectingNamespaces { get; set; }
46                 
47                 public List<NamespaceDeclaration> Namespaces { get; private set; }
48
49                 public string LookupPrefix (string ns)
50                 {
51                         var nd = Namespaces.FirstOrDefault (n => n.Namespace == ns);
52                         if (nd == null && IsCollectingNamespaces)
53                                 return AddNamespace (ns);
54                         else
55                                 return nd != null ? nd.Prefix : null;
56                 }
57                 
58                 public string AddNamespace (string ns)
59                 {
60                         var l = Namespaces;
61                         string prefix, s;
62                         if (ns == XamlLanguage.Xaml2006Namespace)
63                                 prefix = "x";
64                         else if (!l.Any (i => i.Prefix == String.Empty))
65                                 prefix = String.Empty;
66                         else if ((s = GetAcronym (ns)) != null && !l.Any (i => i.Prefix == s))
67                                 prefix = s;
68                         else
69                                 prefix = sctx.GetPreferredPrefix (ns);
70                         l.Add (new NamespaceDeclaration (ns, prefix));
71                         return prefix;
72                 }
73                 
74                 string GetAcronym (string ns)
75                 {
76                         int idx = ns.IndexOf (';');
77                         if (idx < 0)
78                                 return null;
79                         string pre = "clr-namespace:";
80                         if (!ns.StartsWith (pre, StringComparison.Ordinal))
81                                 return null;
82                         ns = ns.Substring (pre.Length, idx - pre.Length);
83                         string ac = "";
84                         foreach (string nsp in ns.Split ('.'))
85                                 if (nsp.Length > 0)
86                                         ac += nsp [0];
87                         return ac.Length > 0 ? ac.ToLower (CultureInfo.InvariantCulture) : null;
88                 }
89         }
90
91         internal struct XamlNodeIterator
92         {
93                 static readonly XamlObject null_object = new XamlObject (XamlLanguage.Null, null);
94
95                 public XamlNodeIterator (object root, XamlSchemaContext schemaContext, PrefixLookup prefixLookup)
96                 {
97                         ctx = schemaContext;
98                         this.root = root;
99                         this.prefix_lookup = prefixLookup;
100                 }
101                 
102                 XamlSchemaContext ctx;
103                 object root;
104                 // FIXME: this will become IServiceProvider.
105                 PrefixLookup prefix_lookup;
106                 
107                 public XamlSchemaContext SchemaContext {
108                         get { return ctx; }
109                 }
110                 
111                 XamlType GetType (object obj)
112                 {
113                         return ctx.GetXamlType (new InstanceContext (obj).GetWrappedValue ().GetType ());
114                 }
115                 
116                 // returns StartObject, StartMember, Value, EndMember and EndObject. (NamespaceDeclaration is not included)
117                 public IEnumerable<XamlNodeInfo> GetNodes ()
118                 {
119                         var xobj = new XamlObject (GetType (root), root);
120                         foreach (var node in GetNodes (null, xobj))
121                                 yield return node;
122                 }
123                 
124                 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj)
125                 {
126                         return GetNodes (xm, xobj, null);
127                 }
128
129                 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj, XamlType overrideMemberType)
130                 {
131                         // collection items: each item is exposed as a standalone object that has StartObject, EndObject and contents.
132                         if (xm == XamlLanguage.Items) {
133                                 foreach (var xn in GetItemsNodes (xm, xobj))
134                                         yield return xn;
135                                 yield break;
136                         }
137                         
138                         // Arguments: each argument is written as a standalone object
139                         if (xm == XamlLanguage.Arguments) {
140                                 foreach (var argm in xobj.Type.GetSortedConstructorArguments ()) {
141                                         var argv = argm.Invoker.GetValue (xobj.GetRawValue ());
142                                         var xarg = new XamlObject (argm.Type, argv);
143                                         foreach (var cn in GetNodes (null, xarg))
144                                                 yield return cn;
145                                 }
146                                 yield break;
147                         }
148
149                         // PositionalParameters: items are from constructor arguments, and are all in simple string value, written as Value node sequentially.
150                         if (xm == XamlLanguage.PositionalParameters) {
151                                 foreach (var argm in xobj.Type.GetSortedConstructorArguments ()) {
152                                         // Unlike XamlLanguage.Items, it only outputs string value. So, convert values here.
153                                         var argv = argm.Type.GetStringValue (xobj.GetMemberValue (argm), prefix_lookup);
154                                         yield return new XamlNodeInfo ((string) argv);
155                                 }
156                                 yield break;
157                         }
158
159                         if (xm == XamlLanguage.Initialization) {
160                                 yield return new XamlNodeInfo (xobj.Type.GetStringValue (xobj.GetRawValue (), prefix_lookup));
161                                 yield break;
162                         }
163
164                         // Value - only for non-top-level node (thus xm != null)
165                         if (xm != null) {
166                                 // overrideMemberType is (so far) used for XamlLanguage.Key.
167                                 var xtt = overrideMemberType ?? xm.Type;
168                                 if (xtt.IsContentValue ()) {
169                                         // though null value is special: it is written as a standalone object.
170                                         var val = xobj.GetRawValue ();
171                                         if (val == null)
172                                                 foreach (var xn in GetNodes (null, null_object))
173                                                         yield return xn;
174                                         else
175                                                 yield return new XamlNodeInfo (xtt.GetStringValue (val, prefix_lookup));
176                                         yield break;
177                                 }
178                         }
179
180                         // collection items: return GetObject and Items.
181                         if (xm != null && xm.Type.IsCollection && xm.IsReadOnly) {
182                                 yield return new XamlNodeInfo (XamlNodeType.GetObject, xobj);
183                                 // Write Items member only when there are items (i.e. do not write it if it is empty).
184                                 var xnm = new XamlNodeMember (xobj, XamlLanguage.Items);
185                                 var en = GetNodes (XamlLanguage.Items, xnm.Value).GetEnumerator ();
186                                 if (en.MoveNext ()) {
187                                         yield return new XamlNodeInfo (XamlNodeType.StartMember, xnm);
188                                         do {
189                                                 yield return en.Current;
190                                         } while (en.MoveNext ());
191                                         yield return new XamlNodeInfo (XamlNodeType.EndMember, xnm);
192                                 }
193                                 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
194                         } else {
195                                 // Object
196                                 yield return new XamlNodeInfo (XamlNodeType.StartObject, xobj);
197                                 foreach (var xn in GetObjectMemberNodes (xobj))
198                                         yield return xn;
199                                 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
200                         }
201                 }
202
203                 IEnumerable<XamlNodeInfo> GetObjectMemberNodes (XamlObject xobj)
204                 {
205                         var xce = xobj.Children ().GetEnumerator ();
206                         while (xce.MoveNext ()) {
207                                 // XamlLanguage.Items does not show up if the content is empty.
208                                 if (xce.Current.Member == XamlLanguage.Items)
209                                         if (!GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ().MoveNext ())
210                                                 continue;
211
212                                 // Other collections as well, but needs different iteration (as nodes contain GetObject and EndObject).
213                                 if (xce.Current.Member.IsReadOnly && xce.Current.Member.Type != null && xce.Current.Member.Type.IsCollection) {
214                                         var e = GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ();
215                                         if (!(e.MoveNext () && e.MoveNext () && e.MoveNext ())) // GetObject, EndObject and more
216                                                 continue;
217                                 }
218
219                                 yield return new XamlNodeInfo (XamlNodeType.StartMember, xce.Current);
220                                 foreach (var cn in GetNodes (xce.Current.Member, xce.Current.Value))
221                                         yield return cn;
222                                 yield return new XamlNodeInfo (XamlNodeType.EndMember, xce.Current);
223                         }
224                 }
225
226                 IEnumerable<XamlNodeInfo> GetItemsNodes (XamlMember xm, XamlObject xobj)
227                 {
228                         var ie = xobj.Type.Invoker.GetItems (xobj.GetRawValue ());
229                         while (ie.MoveNext ()) {
230                                 var iobj = ie.Current;
231                                 // If it is dictionary, then retrieve the key, and rewrite the item as the Value part.
232                                 object ikey = null;
233                                 XamlNodeMember xknm = default (XamlNodeMember);
234                                 if (xobj.Type.IsDictionary) {
235                                         Type kvpType = iobj.GetType ();
236                                         bool isNonGeneric = kvpType == typeof (DictionaryEntry);
237                                         var kp = isNonGeneric ? null : kvpType.GetProperty ("Key");
238                                         var vp = isNonGeneric ? null : kvpType.GetProperty ("Value");
239                                         xknm = new XamlNodeMember (xobj, XamlLanguage.Key);
240                                         ikey = isNonGeneric ? ((DictionaryEntry) iobj).Key : kp.GetValue (iobj, null);
241                                         iobj = isNonGeneric ? ((DictionaryEntry) iobj).Value : vp.GetValue (iobj, null);
242                                 }
243
244                                 var xiobj = new XamlObject (GetType (iobj), iobj);
245                                 var en = GetNodes (null, xiobj).GetEnumerator ();
246                                 en.MoveNext ();
247                                 yield return en.Current;
248                                 if (ikey != null) {
249                                         // Key member is written *inside* the item object.
250                                         yield return new XamlNodeInfo (XamlNodeType.StartMember, xknm);
251                                         foreach (var xn in GetNodes (XamlLanguage.Key, new XamlObject (GetType (ikey), ikey), xobj.Type.KeyType))
252                                                 yield return xn;
253                                         yield return new XamlNodeInfo (XamlNodeType.EndMember, xknm);
254                                 }
255                                 while (en.MoveNext ())
256                                         yield return en.Current;
257                         }
258                 }
259
260                 // Namespace retrieval. 
261                 // It is iterated before iterating the actual object nodes,
262                 // and results are cached for use in XamlObjectReader.
263                 public void CollectNamespaces ()
264                 {
265                         prefix_lookup.IsCollectingNamespaces = true;
266                         foreach (var xn in GetNodes ()) {
267                                 if (xn.NodeType == XamlNodeType.GetObject)
268                                         continue; // it is out of consideration here.
269                                 if (xn.NodeType == XamlNodeType.StartObject) {
270                                         foreach (var ns in NamespacesInType (xn.Object.Type))
271                                                 prefix_lookup.LookupPrefix (ns);
272                                 } else if (xn.NodeType == XamlNodeType.StartMember) {
273                                         if (xn.Member.Member is XamlDirective)
274                                                 continue;
275                                         prefix_lookup.LookupPrefix (xn.Member.Member.PreferredXamlNamespace);
276                                 } else {
277                                         if (xn.NodeType == XamlNodeType.Value && xn.Value is Type)
278                                                 // this tries to lookup existing prefix, and if there isn't any, then adds a new declaration.
279                                                 XamlLanguage.Type.GetStringValue (xn.Value, prefix_lookup);
280                                         continue;
281                                 }
282                         }
283                         prefix_lookup.Namespaces.Sort ((nd1, nd2) => String.CompareOrdinal (nd1.Prefix, nd2.Prefix));
284                         prefix_lookup.IsCollectingNamespaces = false;
285                 }
286                 
287                 IEnumerable<string> NamespacesInType (XamlType xt)
288                 {
289                         yield return xt.PreferredXamlNamespace;
290                         if (xt.TypeArguments != null) {
291                                 // It is for x:TypeArguments
292                                 yield return XamlLanguage.Xaml2006Namespace;
293                                 foreach (var targ in xt.TypeArguments)
294                                         foreach (var ns in NamespacesInType (targ))
295                                                 yield return ns;
296                         }
297                 }
298         }
299         
300         internal struct XamlNodeInfo
301         {
302                 public XamlNodeInfo (XamlNodeType nodeType, XamlObject value)
303                 {
304                         node_type = nodeType;
305                         this.value = value;
306                         member = default (XamlNodeMember);
307                 }
308                 
309                 public XamlNodeInfo (XamlNodeType nodeType, XamlNodeMember member)
310                 {
311                         node_type = nodeType;
312                         this.value = default (XamlObject);
313                         this.member = member;
314                 }
315                 
316                 public XamlNodeInfo (string value)
317                 {
318                         node_type = XamlNodeType.Value;
319                         this.value = value;
320                         member = default (XamlNodeMember);
321                 }
322                 
323                 XamlNodeType node_type;
324                 object value;
325                 XamlNodeMember member;
326                 
327                 public XamlNodeType NodeType {
328                         get { return node_type; }
329                 }
330                 public XamlObject Object {
331                         get { return (XamlObject) value; }
332                 }
333                 public XamlNodeMember Member {
334                         get { return member; }
335                 }
336                 public object Value {
337                         get { return value; }
338                 }
339         }
340         
341         internal struct XamlObject
342         {
343                 public XamlObject (XamlType type, object instance)
344                         : this (type, new InstanceContext (instance))
345                 {
346                 }
347
348                 public XamlObject (XamlType type, InstanceContext context)
349                 {
350                         this.type = type;
351                         this.context = context;
352                 }
353                 
354                 readonly XamlType type;
355                 readonly InstanceContext context;
356                 
357                 public XamlType Type {
358                         get { return type; }
359                 }
360                 
361                 public InstanceContext Context {
362                         get { return context; }
363                 }
364                 
365                 XamlType GetType (object obj)
366                 {
367                         return type.SchemaContext.GetXamlType (new InstanceContext (obj).GetWrappedValue ().GetType ());
368                 }
369                 
370                 public IEnumerable<XamlNodeMember> Children ()
371                 {
372                         // FIXME: consider XamlLanguage.Key
373                         foreach (var xm in type.GetAllObjectReaderMembersByType (null))
374                                 yield return new XamlNodeMember (this, xm);
375                 }
376                 
377                 public object GetRawValue ()
378                 {
379                         return context.GetRawValue ();
380                 }
381                 
382                 public object GetWrappedValue ()
383                 {
384                         return context.GetWrappedValue ();
385                 }
386         }
387         
388         internal struct XamlNodeMember
389         {
390                 public XamlNodeMember (XamlObject owner, XamlMember member)
391                 {
392                         this.owner = owner;
393                         this.member = member;
394                 }
395                 
396                 readonly XamlObject owner;
397                 readonly XamlMember member;
398                 
399                 public XamlObject Owner {
400                         get { return owner; }
401                 }
402                 public XamlMember Member {
403                         get { return member; }
404                 }
405                 public XamlObject Value {
406                         get {
407                                 var mv = Owner.GetMemberValue (Member);
408                                 return new XamlObject (GetType (mv), mv);
409                         }
410                 }
411
412                 XamlType GetType (object obj)
413                 {
414                         return owner.Type.SchemaContext.GetXamlType (new InstanceContext (obj).GetWrappedValue ().GetType ());
415                 }
416         }
417         
418         // Its original purpose was to enable delayed reflection, but it's not supported yet.
419         internal struct InstanceContext
420         {
421                 static readonly NullExtension null_value = new NullExtension ();
422
423                 public InstanceContext (object value)
424                 {
425                         this.value = value;
426                 }
427                 
428                 object value;
429                 
430                 public object GetWrappedValue ()
431                 {
432                         var o = GetRawValue ();
433
434                         // FIXME: should this manually checked, or is there any way to automate it?
435                         if (o == null)
436                                 return null_value;
437                         if (o is Array)
438                                 return new ArrayExtension ((Array) o);
439                         if (o is Type)
440                                 return new TypeExtension ((Type) o);
441                         return o;
442                 }
443                 
444                 public object GetRawValue ()
445                 {
446                         return value; // so far.
447                 }
448         }
449
450         internal static class TypeExtensionMethods2
451         {
452                 static bool ExaminePositionalParametersApplicable (this XamlType type)
453                 {
454                         if (!type.IsMarkupExtension || type.UnderlyingType == null)
455                                 return false;
456
457                         var args = type.GetSortedConstructorArguments ();
458                         if (args == null)
459                                 return false;
460
461                         Type [] argTypes = (from arg in args select arg.Type.UnderlyingType).ToArray ();
462                         if (argTypes.Any (at => at == null))
463                                 return false;
464                         var ci = type.UnderlyingType.GetConstructor (argTypes);
465                         return ci != null;
466                 }
467         
468                 // Note that this returns XamlMember which might not actually appear in XamlObjectReader. For example, XamlLanguage.Items won't be returned when there is no item in the collection.
469                 public static IEnumerable<XamlMember> GetAllObjectReaderMembersByType (this XamlType type, object dictionaryKey)
470                 {
471                         // FIXME: find out why only TypeExtension and StaticExtension yield this directive. Seealso XamlObjectReaderTest.Read_CustomMarkupExtension*()
472                         if (type == XamlLanguage.Type ||
473                             type == XamlLanguage.Static ||
474                             ExaminePositionalParametersApplicable (type) && type.ConstructionRequiresArguments) {
475                                 yield return XamlLanguage.PositionalParameters;
476                                 yield break;
477                         }
478
479                         // Note that if the XamlType has the default constructor, we don't need "Arguments".
480                         IEnumerable<XamlMember> args = type.ConstructionRequiresArguments ? type.GetSortedConstructorArguments () : null;
481                         if (args != null && args.Any ())
482                                 yield return XamlLanguage.Arguments;
483
484                         if (dictionaryKey != null)
485                                 yield return XamlLanguage.Key;
486
487                         if (type.TypeConverter != null || type.IsContentValue ()) {
488                                 yield return XamlLanguage.Initialization;
489                                 yield break;
490                         }
491
492                         if (type.IsDictionary) {
493                                 yield return XamlLanguage.Items;
494                                 yield break;
495                         }
496
497                         foreach (var m in type.GetAllMembers ()) {
498                                 // do not read constructor arguments twice (they are written inside Arguments).
499                                 if (args != null && args.Contains (m))
500                                         continue;
501                                 // do not return non-public members. Not sure why .NET filters out them though.
502                                 if (!m.IsReadPublic)
503                                         continue;
504
505                                 yield return m;
506                         }
507                         
508                         if (type.IsCollection)
509                                 yield return XamlLanguage.Items;
510                 }
511         }
512         
513         internal static class XamlNodeExtensions
514         {
515                 internal static object GetMemberValue (this XamlObject xobj, XamlMember xm)
516                 {
517                         if (xm.IsUnknown)
518                                 return null;
519                         // FIXME: this looks like an ugly hack. Is this really true? What if there's MarkupExtension that uses another MarkupExtension type as a member type.
520                         var obj = xobj.Context.GetRawValue ();
521                         if (xm == XamlLanguage.Initialization)
522                                 return obj;
523                         if (xm == XamlLanguage.Items) // collection itself.
524                                 return obj;
525                         if (xm == XamlLanguage.Arguments) // object itself
526                                 return obj;
527                         if (xm == XamlLanguage.PositionalParameters)
528                                 return xobj.GetWrappedValue (); // dummy value
529                         return xm.Invoker.GetValue (xobj.GetWrappedValue ());
530                 }
531                 
532 #if DOTNET
533                 internal static ICustomAttributeProvider GetCustomAttributeProvider (this XamlType type)
534                 {
535                         return type.UnderlyingType;
536                 }
537                 
538                 internal static ICustomAttributeProvider GetCustomAttributeProvider (this XamlMember member)
539                 {
540                         return member.UnderlyingMember;
541                 }
542 #endif
543         }
544 }