Merge remote branch 'upstream/master'
[mono.git] / mcs / class / System.Xaml / System.Xaml / XamlObjectNodeIterator.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
24 using System;
25 using System.Collections;
26 using System.Collections.Generic;
27 using System.ComponentModel;
28 using System.IO;
29 using System.Linq;
30 using System.Windows.Markup;
31 using System.Xaml;
32 using System.Xaml.Schema;
33 using System.Xml;
34 using System.Xml.Serialization;
35
36 namespace System.Xaml
37 {
38         internal class XamlObjectNodeIterator
39         {
40                 static readonly XamlObject null_object = new XamlObject (XamlLanguage.Null, null);
41
42                 public XamlObjectNodeIterator (object root, XamlSchemaContext schemaContext, IValueSerializerContext vctx)
43                 {
44                         ctx = schemaContext;
45                         this.root = root;
46                         value_serializer_ctx = vctx;
47                 }
48                 
49                 XamlSchemaContext ctx;
50                 object root;
51                 IValueSerializerContext value_serializer_ctx;
52                 
53                 PrefixLookup PrefixLookup {
54                         get { return (PrefixLookup) value_serializer_ctx.GetService (typeof (INamespacePrefixLookup)); }
55                 }
56                 XamlNameResolver NameResolver {
57                         get { return (XamlNameResolver) value_serializer_ctx.GetService (typeof (IXamlNameResolver)); }
58                 }
59
60                 public XamlSchemaContext SchemaContext {
61                         get { return ctx; }
62                 }
63                 
64                 XamlType GetType (object obj)
65                 {
66                         return obj == null ? XamlLanguage.Null : ctx.GetXamlType (obj.GetType ());
67                 }
68                 
69                 // returns StartObject, StartMember, Value, EndMember and EndObject. (NamespaceDeclaration is not included)
70                 public IEnumerable<XamlNodeInfo> GetNodes ()
71                 {
72                         var xobj = new XamlObject (GetType (root), root);
73                         foreach (var node in GetNodes (null, xobj))
74                                 yield return node;
75                 }
76                 
77                 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj)
78                 {
79                         return GetNodes (xm, xobj, null, false);
80                 }
81
82                 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj, XamlType overrideMemberType, bool partOfPositionalParameters)
83                 {
84                         // collection items: each item is exposed as a standalone object that has StartObject, EndObject and contents.
85                         if (xm == XamlLanguage.Items) {
86                                 foreach (var xn in GetItemsNodes (xm, xobj))
87                                         yield return xn;
88                                 yield break;
89                         }
90                         
91                         // Arguments: each argument is written as a standalone object
92                         if (xm == XamlLanguage.Arguments) {
93                                 foreach (var argm in xobj.Type.GetSortedConstructorArguments ()) {
94                                         var argv = argm.Invoker.GetValue (xobj.GetRawValue ());
95                                         var xarg = new XamlObject (argm.Type, argv);
96                                         foreach (var cn in GetNodes (null, xarg))
97                                                 yield return cn;
98                                 }
99                                 yield break;
100                         }
101
102                         // PositionalParameters: items are from constructor arguments, written as Value node sequentially. Note that not all of them are in simple string value. Also, null values are not written as NullExtension
103                         if (xm == XamlLanguage.PositionalParameters) {
104                                 foreach (var argm in xobj.Type.GetSortedConstructorArguments ()) {
105                                         foreach (var cn in GetNodes (argm, new XamlObject (argm.Type, xobj.GetMemberValue (argm)), null, true))
106                                                 yield return cn;
107                                 }
108                                 yield break;
109                         }
110
111                         if (xm == XamlLanguage.Initialization) {
112                                 yield return new XamlNodeInfo (TypeExtensionMethods.GetStringValue (xobj.Type, xm, xobj.GetRawValue (), value_serializer_ctx));
113                                 yield break;
114                         }
115
116                         // Value - only for non-top-level node (thus xm != null)
117                         if (xm != null) {
118                                 // overrideMemberType is (so far) used for XamlLanguage.Key.
119                                 var xtt = overrideMemberType ?? xm.Type;
120                                 if (!xtt.IsMarkupExtension && // this condition is to not serialize MarkupExtension whose type has TypeConverterAttribute (e.g. StaticExtension) as a string.
121                                     (xtt.IsContentValue (value_serializer_ctx) || xm.IsContentValue (value_serializer_ctx))) {
122                                         // though null value is special: it is written as a standalone object.
123                                         var val = xobj.GetRawValue ();
124                                         if (val == null) {
125                                                 if (!partOfPositionalParameters)
126                                                         foreach (var xn in GetNodes (null, null_object))
127                                                                 yield return xn;
128                                                 else
129                                                         yield return new XamlNodeInfo (String.Empty);
130                                         }
131                                         else
132                                                 yield return new XamlNodeInfo (TypeExtensionMethods.GetStringValue (xtt, xm, val, value_serializer_ctx));
133                                         yield break;
134                                 }
135                         }
136
137                         // collection items: return GetObject and Items.
138                         if (xm != null && xm.Type.IsCollection && !xm.IsWritePublic) {
139                                 yield return new XamlNodeInfo (XamlNodeType.GetObject, xobj);
140                                 // Write Items member only when there are items (i.e. do not write it if it is empty).
141                                 var xnm = new XamlNodeMember (xobj, XamlLanguage.Items);
142                                 var en = GetNodes (XamlLanguage.Items, xnm.Value).GetEnumerator ();
143                                 if (en.MoveNext ()) {
144                                         yield return new XamlNodeInfo (XamlNodeType.StartMember, xnm);
145                                         do {
146                                                 yield return en.Current;
147                                         } while (en.MoveNext ());
148                                         yield return new XamlNodeInfo (XamlNodeType.EndMember, xnm);
149                                 }
150                                 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
151                         } else if (xm != null && xm.Type.IsXData) {
152                                 var sw = new StringWriter ();
153                                 var xw = XmlWriter.Create (sw, new XmlWriterSettings () { OmitXmlDeclaration = true, ConformanceLevel = ConformanceLevel.Auto });
154                                 var val = xobj.GetRawValue () as IXmlSerializable;
155                                 if (val == null)
156                                         yield break; // do not output anything
157                                 val.WriteXml (xw);
158                                 xw.Close ();
159                                 var obj = new XData () { Text = sw.ToString () };
160                                 foreach (var xn in GetNodes (null, new XamlObject (XamlLanguage.XData, obj)))
161                                         yield return xn;
162                         } else {
163                                 // Object - could become Reference
164                                 var val = xobj.GetRawValue ();
165                                 if (!xobj.Type.IsContentValue (value_serializer_ctx) && val != null) {
166                                         string refName = NameResolver.GetName (val);
167                                         if (refName != null) {
168                                                 // The target object is already retrieved, so we don't return the same object again.
169                                                 NameResolver.SaveAsReferenced (val); // Record it as named object.
170                                                 // Then return Reference object instead.
171                                                 foreach (var xn in GetNodes (null, new XamlObject (XamlLanguage.Reference, new Reference (refName))))
172                                                         yield return xn;
173                                                 yield break;
174                                         } else {
175                                                 // The object appeared in the xaml tree for the first time. So we store the reference with a unique name so that it could be referenced later.
176                                                 refName = GetReferenceName (xobj);
177                                                 if (NameResolver.IsCollectingReferences && NameResolver.Contains (refName))
178                                                         throw new InvalidOperationException (String.Format ("There is already an object of type {0} named as '{1}'. Object names must be unique.", val.GetType (), refName));
179                                                 NameResolver.SetNamedObject (refName, val, true); // probably fullyInitialized is always true here.
180                                         }
181                                 }
182                                 yield return new XamlNodeInfo (XamlNodeType.StartObject, xobj);
183                                 // If this object is referenced and there is no [RuntimeNameProperty] member, then return Name property in addition.
184                                 if (val != null && xobj.Type.GetAliasedProperty (XamlLanguage.Name) == null) {
185                                         string name = NameResolver.GetReferencedName (val);
186                                         if (name != null) {
187                                                 var sobj = new XamlObject (XamlLanguage.String, name);
188                                                 foreach (var cn in GetMemberNodes (new XamlNodeMember (sobj, XamlLanguage.Name), new XamlNodeInfo [] { new XamlNodeInfo (name)}))
189                                                         yield return cn;
190                                         }
191                                 }
192                                 foreach (var xn in GetObjectMemberNodes (xobj))
193                                         yield return xn;
194                                 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
195                         }
196                 }
197                 
198                 int used_reference_ids;
199                 
200                 string GetReferenceName (XamlObject xobj)
201                 {
202                         var xm = xobj.Type.GetAliasedProperty (XamlLanguage.Name);
203                         if (xm != null)
204                                 return (string) xm.Invoker.GetValue (xobj.GetRawValue ());
205                         return "__ReferenceID" + used_reference_ids++;
206                 }
207
208                 IEnumerable<XamlNodeInfo> GetMemberNodes (XamlNodeMember member, IEnumerable<XamlNodeInfo> contents)
209                 {
210                                 yield return new XamlNodeInfo (XamlNodeType.StartMember, member);
211                                 foreach (var cn in contents)
212                                         yield return cn;
213                                 yield return new XamlNodeInfo (XamlNodeType.EndMember, member);
214                 }
215
216                 IEnumerable<XamlNodeMember> GetNodeMembers (XamlObject xobj, IValueSerializerContext vsctx)
217                 {
218                         // XData.XmlReader is not returned.
219                         if (xobj.Type == XamlLanguage.XData) {
220                                 yield return new XamlNodeMember (xobj, XamlLanguage.XData.GetMember ("Text"));
221                                 yield break;
222                         }
223
224                         // FIXME: find out why root Reference has PositionalParameters.
225                         if (xobj.GetRawValue() != root && xobj.Type == XamlLanguage.Reference)
226                                 yield return new XamlNodeMember (xobj, XamlLanguage.PositionalParameters);
227                         else {
228                                 var inst = xobj.GetRawValue ();
229                                 var atts = new KeyValuePair<AttachableMemberIdentifier,object> [AttachablePropertyServices.GetAttachedPropertyCount (inst)];
230                                 AttachablePropertyServices.CopyPropertiesTo (inst, atts, 0);
231                                 foreach (var p in atts) {
232                                         var axt = ctx.GetXamlType (p.Key.DeclaringType);
233                                         yield return new XamlNodeMember (new XamlObject (axt, p.Value), axt.GetAttachableMember (p.Key.MemberName));
234                                 }
235                                 foreach (var xm in xobj.Type.GetAllObjectReaderMembersByType (vsctx))
236                                         yield return new XamlNodeMember (xobj, xm);
237                         }
238                 }
239
240                 IEnumerable<XamlNodeInfo> GetObjectMemberNodes (XamlObject xobj)
241                 {
242                         var xce = GetNodeMembers (xobj, value_serializer_ctx).GetEnumerator ();
243                         while (xce.MoveNext ()) {
244                                 // XamlLanguage.Items does not show up if the content is empty.
245                                 if (xce.Current.Member == XamlLanguage.Items) {
246                                         // FIXME: this is nasty, but this name resolution is the only side effect of this iteration model. Save-Restore procedure is required.
247                                         NameResolver.Save ();
248                                         try {
249                                                 if (!GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ().MoveNext ())
250                                                         continue;
251                                         } finally {
252                                                 NameResolver.Restore ();
253                                         }
254                                 }
255
256                                 // Other collections as well, but needs different iteration (as nodes contain GetObject and EndObject).
257                                 if (!xce.Current.Member.IsWritePublic && xce.Current.Member.Type != null && xce.Current.Member.Type.IsCollection) {
258                                         var e = GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ();
259                                         // FIXME: this is nasty, but this name resolution is the only side effect of this iteration model. Save-Restore procedure is required.
260                                         NameResolver.Save ();
261                                         try {
262                                                 if (!(e.MoveNext () && e.MoveNext () && e.MoveNext ())) // GetObject, EndObject and more
263                                                         continue;
264                                         } finally {
265                                                 NameResolver.Restore ();
266                                         }
267                                 }
268
269                                 foreach (var cn in GetMemberNodes (xce.Current, GetNodes (xce.Current.Member, xce.Current.Value)))
270                                         yield return cn;
271                         }
272                 }
273
274                 IEnumerable<XamlNodeInfo> GetItemsNodes (XamlMember xm, XamlObject xobj)
275                 {
276                         var obj = xobj.GetRawValue ();
277                         if (obj == null)
278                                 yield break;
279                         var ie = xobj.Type.Invoker.GetItems (obj);
280                         while (ie.MoveNext ()) {
281                                 var iobj = ie.Current;
282                                 // If it is dictionary, then retrieve the key, and rewrite the item as the Value part.
283                                 object ikey = null;
284                                 if (xobj.Type.IsDictionary) {
285                                         Type kvpType = iobj.GetType ();
286                                         bool isNonGeneric = kvpType == typeof (DictionaryEntry);
287                                         var kp = isNonGeneric ? null : kvpType.GetProperty ("Key");
288                                         var vp = isNonGeneric ? null : kvpType.GetProperty ("Value");
289                                         ikey = isNonGeneric ? ((DictionaryEntry) iobj).Key : kp.GetValue (iobj, null);
290                                         iobj = isNonGeneric ? ((DictionaryEntry) iobj).Value : vp.GetValue (iobj, null);
291                                 }
292
293                                 var wobj = TypeExtensionMethods.GetExtensionWrapped (iobj);
294                                 var xiobj = new XamlObject (GetType (wobj), wobj);
295                                 if (ikey != null) {
296                                         // Key member is written *inside* the item object.
297                                         //
298                                         // It is messy, but Key and Value are *sorted*. In most cases Key goes first, but for example PositionalParameters comes first.
299                                         // To achieve this behavior, we compare XamlLanguage.Key and value's Member and returns in order. It's all nasty hack, but at least it could be achieved like this!
300
301                                         var en = GetNodes (null, xiobj).ToArray ();
302                                         yield return en [0]; // StartObject
303
304                                         var xknm = new XamlNodeMember (xobj, XamlLanguage.Key);
305                                         var nodes1 = en.Skip (1).Take (en.Length - 2);
306                                         var nodes2 = GetKeyNodes (ikey, xobj.Type.KeyType, xknm);
307                                         foreach (var xn in EnumerateMixingMember (nodes1, XamlLanguage.Key, nodes2))
308                                                 yield return xn;
309                                         yield return en [en.Length - 1];
310                                 }
311                                 else
312                                         foreach (var xn in GetNodes (null, xiobj))
313                                                 yield return xn;
314                         }
315                 }
316                 
317                 IEnumerable<XamlNodeInfo> EnumerateMixingMember (IEnumerable<XamlNodeInfo> nodes1, XamlMember m2, IEnumerable<XamlNodeInfo> nodes2)
318                 {
319                         if (nodes2 == null) {
320                                 foreach (var cn in nodes1)
321                                         yield return cn;
322                                 yield break;
323                         }
324
325                         var e1 = nodes1.GetEnumerator ();
326                         var e2 = nodes2.GetEnumerator ();
327                         int nest = 0;
328                         while (e1.MoveNext ()) {
329                                 if (e1.Current.NodeType == XamlNodeType.StartMember) {
330                                         if (nest > 0)
331                                                 nest++;
332                                         else
333                                                 if (TypeExtensionMethods.CompareMembers (m2, e1.Current.Member.Member) < 0) {
334                                                         while (e2.MoveNext ())
335                                                                 yield return e2.Current;
336                                                 }
337                                                 else
338                                                         nest++;
339                                 }
340                                 else if (e1.Current.NodeType == XamlNodeType.EndMember)
341                                         nest--;
342                                 yield return e1.Current;
343                         }
344                         while (e2.MoveNext ())
345                                 yield return e2.Current;
346                 }
347
348                 IEnumerable<XamlNodeInfo> GetKeyNodes (object ikey, XamlType keyType, XamlNodeMember xknm)
349                 {
350                         foreach (var xn in GetMemberNodes (xknm, GetNodes (XamlLanguage.Key, new XamlObject (GetType (ikey), ikey), keyType, false)))
351                                 yield return xn;
352                 }
353
354                 // Namespace and Reference retrieval.
355                 // It is iterated before iterating the actual object nodes,
356                 // and results are cached for use in XamlObjectReader.
357                 public void PrepareReading ()
358                 {
359                         PrefixLookup.IsCollectingNamespaces = true;
360                         NameResolver.IsCollectingReferences = true;
361                         foreach (var xn in GetNodes ()) {
362                                 if (xn.NodeType == XamlNodeType.GetObject)
363                                         continue; // it is out of consideration here.
364                                 if (xn.NodeType == XamlNodeType.StartObject) {
365                                         foreach (var ns in NamespacesInType (xn.Object.Type))
366                                                 PrefixLookup.LookupPrefix (ns);
367                                 } else if (xn.NodeType == XamlNodeType.StartMember) {
368                                         var xm = xn.Member.Member;
369                                         // This filtering is done as a black list so far. There does not seem to be any usable property on XamlDirective.
370                                         if (xm == XamlLanguage.Items || xm == XamlLanguage.PositionalParameters || xm == XamlLanguage.Initialization)
371                                                 continue;
372                                         PrefixLookup.LookupPrefix (xn.Member.Member.PreferredXamlNamespace);
373                                 } else {
374                                         if (xn.NodeType == XamlNodeType.Value && xn.Value is Type)
375                                                 // this tries to lookup existing prefix, and if there isn't any, then adds a new declaration.
376                                                 TypeExtensionMethods.GetStringValue (XamlLanguage.Type, xn.Member.Member, xn.Value, value_serializer_ctx);
377                                         continue;
378                                 }
379                         }
380                         PrefixLookup.Namespaces.Sort ((nd1, nd2) => String.CompareOrdinal (nd1.Prefix, nd2.Prefix));
381                         PrefixLookup.IsCollectingNamespaces = false;
382                         NameResolver.IsCollectingReferences = false;
383                         NameResolver.NameScopeInitializationCompleted (this);
384                 }
385                 
386                 IEnumerable<string> NamespacesInType (XamlType xt)
387                 {
388                         yield return xt.PreferredXamlNamespace;
389                         if (xt.TypeArguments != null) {
390                                 // It is for x:TypeArguments
391                                 yield return XamlLanguage.Xaml2006Namespace;
392                                 foreach (var targ in xt.TypeArguments)
393                                         foreach (var ns in NamespacesInType (targ))
394                                                 yield return ns;
395                         }
396                 }
397         }
398 }