Merge branch 'xml-fixes' of https://github.com/myeisha/mono into myeisha-xml-fixes
[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.Linq;
29 using System.Windows.Markup;
30 using System.Xaml;
31 using System.Xaml.Schema;
32
33 namespace System.Xaml
34 {
35         internal struct XamlObjectNodeIterator
36         {
37                 static readonly XamlObject null_object = new XamlObject (XamlLanguage.Null, null);
38
39                 public XamlObjectNodeIterator (object root, XamlSchemaContext schemaContext, PrefixLookup prefixLookup)
40                 {
41                         ctx = schemaContext;
42                         this.root = root;
43                         value_serializer_ctx = new ValueSerializerContext (prefixLookup, ctx);
44                 }
45                 
46                 XamlSchemaContext ctx;
47                 object root;
48                 IValueSerializerContext value_serializer_ctx;
49                 
50                 PrefixLookup prefix_lookup {
51                         get { return (PrefixLookup) value_serializer_ctx.GetService (typeof (INamespacePrefixLookup)); }
52                 }
53
54                 public XamlSchemaContext SchemaContext {
55                         get { return ctx; }
56                 }
57                 
58                 XamlType GetType (object obj)
59                 {
60                         return ctx.GetXamlType (new InstanceContext (obj).GetWrappedValue ().GetType ());
61                 }
62                 
63                 // returns StartObject, StartMember, Value, EndMember and EndObject. (NamespaceDeclaration is not included)
64                 public IEnumerable<XamlNodeInfo> GetNodes ()
65                 {
66                         var xobj = new XamlObject (GetType (root), root);
67                         foreach (var node in GetNodes (null, xobj))
68                                 yield return node;
69                 }
70                 
71                 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj)
72                 {
73                         return GetNodes (xm, xobj, null);
74                 }
75
76                 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj, XamlType overrideMemberType)
77                 {
78                         // collection items: each item is exposed as a standalone object that has StartObject, EndObject and contents.
79                         if (xm == XamlLanguage.Items) {
80                                 foreach (var xn in GetItemsNodes (xm, xobj))
81                                         yield return xn;
82                                 yield break;
83                         }
84                         
85                         // Arguments: each argument is written as a standalone object
86                         if (xm == XamlLanguage.Arguments) {
87                                 foreach (var argm in xobj.Type.GetSortedConstructorArguments ()) {
88                                         var argv = argm.Invoker.GetValue (xobj.GetRawValue ());
89                                         var xarg = new XamlObject (argm.Type, argv);
90                                         foreach (var cn in GetNodes (null, xarg))
91                                                 yield return cn;
92                                 }
93                                 yield break;
94                         }
95
96                         // PositionalParameters: items are from constructor arguments, and are all in simple string value, written as Value node sequentially.
97                         if (xm == XamlLanguage.PositionalParameters) {
98                                 foreach (var argm in xobj.Type.GetSortedConstructorArguments ()) {
99                                         // Unlike XamlLanguage.Items, it only outputs string value. So, convert values here.
100                                         var argv = TypeExtensionMethods.GetStringValue (argm.Type, argm, xobj.GetMemberValue (argm), value_serializer_ctx);
101                                         yield return new XamlNodeInfo ((string) argv);
102                                 }
103                                 yield break;
104                         }
105
106                         if (xm == XamlLanguage.Initialization) {
107                                 yield return new XamlNodeInfo (TypeExtensionMethods.GetStringValue (xobj.Type, xm, xobj.GetRawValue (), value_serializer_ctx));
108                                 yield break;
109                         }
110
111                         // Value - only for non-top-level node (thus xm != null)
112                         if (xm != null) {
113                                 // overrideMemberType is (so far) used for XamlLanguage.Key.
114                                 var xtt = overrideMemberType ?? xm.Type;
115                                 if (xtt.IsContentValue (value_serializer_ctx) || xm.IsContentValue (value_serializer_ctx)) {
116                                         // though null value is special: it is written as a standalone object.
117                                         var val = xobj.GetRawValue ();
118                                         if (val == null)
119                                                 foreach (var xn in GetNodes (null, null_object))
120                                                         yield return xn;
121                                         else
122                                                 yield return new XamlNodeInfo (TypeExtensionMethods.GetStringValue (xtt, xm, val, value_serializer_ctx));
123                                         yield break;
124                                 }
125                         }
126
127                         // collection items: return GetObject and Items.
128                         if (xm != null && xm.Type.IsCollection && !xm.IsWritePublic) {
129                                 yield return new XamlNodeInfo (XamlNodeType.GetObject, xobj);
130                                 // Write Items member only when there are items (i.e. do not write it if it is empty).
131                                 var xnm = new XamlNodeMember (xobj, XamlLanguage.Items);
132                                 var en = GetNodes (XamlLanguage.Items, xnm.Value).GetEnumerator ();
133                                 if (en.MoveNext ()) {
134                                         yield return new XamlNodeInfo (XamlNodeType.StartMember, xnm);
135                                         do {
136                                                 yield return en.Current;
137                                         } while (en.MoveNext ());
138                                         yield return new XamlNodeInfo (XamlNodeType.EndMember, xnm);
139                                 }
140                                 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
141                         } else {
142                                 // Object
143                                 yield return new XamlNodeInfo (XamlNodeType.StartObject, xobj);
144                                 foreach (var xn in GetObjectMemberNodes (xobj))
145                                         yield return xn;
146                                 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
147                         }
148                 }
149
150                 IEnumerable<XamlNodeInfo> GetObjectMemberNodes (XamlObject xobj)
151                 {
152                         var xce = xobj.Children (value_serializer_ctx).GetEnumerator ();
153                         while (xce.MoveNext ()) {
154                                 // XamlLanguage.Items does not show up if the content is empty.
155                                 if (xce.Current.Member == XamlLanguage.Items)
156                                         if (!GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ().MoveNext ())
157                                                 continue;
158
159                                 // Other collections as well, but needs different iteration (as nodes contain GetObject and EndObject).
160                                 if (!xce.Current.Member.IsWritePublic && xce.Current.Member.Type != null && xce.Current.Member.Type.IsCollection) {
161                                         var e = GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ();
162                                         if (!(e.MoveNext () && e.MoveNext () && e.MoveNext ())) // GetObject, EndObject and more
163                                                 continue;
164                                 }
165
166                                 yield return new XamlNodeInfo (XamlNodeType.StartMember, xce.Current);
167                                 foreach (var cn in GetNodes (xce.Current.Member, xce.Current.Value))
168                                         yield return cn;
169                                 yield return new XamlNodeInfo (XamlNodeType.EndMember, xce.Current);
170                         }
171                 }
172
173                 IEnumerable<XamlNodeInfo> GetItemsNodes (XamlMember xm, XamlObject xobj)
174                 {
175                         var obj = xobj.GetRawValue ();
176                         if (obj == null)
177                                 yield break;
178                         var ie = xobj.Type.Invoker.GetItems (obj);
179                         while (ie.MoveNext ()) {
180                                 var iobj = ie.Current;
181                                 // If it is dictionary, then retrieve the key, and rewrite the item as the Value part.
182                                 object ikey = null;
183                                 if (xobj.Type.IsDictionary) {
184                                         Type kvpType = iobj.GetType ();
185                                         bool isNonGeneric = kvpType == typeof (DictionaryEntry);
186                                         var kp = isNonGeneric ? null : kvpType.GetProperty ("Key");
187                                         var vp = isNonGeneric ? null : kvpType.GetProperty ("Value");
188                                         ikey = isNonGeneric ? ((DictionaryEntry) iobj).Key : kp.GetValue (iobj, null);
189                                         iobj = isNonGeneric ? ((DictionaryEntry) iobj).Value : vp.GetValue (iobj, null);
190                                 }
191
192                                 var xiobj = new XamlObject (GetType (iobj), iobj);
193                                 if (ikey != null) {
194                                         // Key member is written *inside* the item object.
195                                         //
196                                         // It is messy, but Key and Value are *sorted*. In most cases Key goes first, but for example PositionalParameters comes first.
197                                         // 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!
198
199                                         var en = GetNodes (null, xiobj).ToArray ();
200                                         yield return en [0]; // StartObject
201
202                                         var xknm = new XamlNodeMember (xobj, XamlLanguage.Key);
203                                         if (TypeExtensionMethods.CompareMembers (en [1].Member.Member, XamlLanguage.Key) < 0) { // en[1] is the StartMember of the first member.
204                                                 // value -> key -> endobject
205                                                 for (int i = 1; i < en.Length - 1; i++)
206                                                         yield return en [i];
207                                                 foreach (var kn in GetKeyNodes (ikey, xobj.Type.KeyType, xknm))
208                                                         yield return kn;
209                                                 yield return en [en.Length - 1];
210                                         } else {
211                                                 // key -> value -> endobject
212                                                 foreach (var kn in GetKeyNodes (ikey, xobj.Type.KeyType, xknm))
213                                                         yield return kn;
214                                                 for (int i = 1; i < en.Length - 1; i++)
215                                                         yield return en [i];
216                                                 yield return en [en.Length - 1];
217                                         }
218                                 }
219                                 else
220                                         foreach (var xn in GetNodes (null, xiobj))
221                                                 yield return xn;
222                         }
223                 }
224
225                 IEnumerable<XamlNodeInfo> GetKeyNodes (object ikey, XamlType keyType, XamlNodeMember xknm)
226                 {
227                         yield return new XamlNodeInfo (XamlNodeType.StartMember, xknm);
228                         foreach (var xn in GetNodes (XamlLanguage.Key, new XamlObject (GetType (ikey), ikey), keyType))
229                                 yield return xn;
230                         yield return new XamlNodeInfo (XamlNodeType.EndMember, xknm);
231                 }
232
233                 // Namespace retrieval. 
234                 // It is iterated before iterating the actual object nodes,
235                 // and results are cached for use in XamlObjectReader.
236                 public void CollectNamespaces ()
237                 {
238                         prefix_lookup.IsCollectingNamespaces = true;
239                         foreach (var xn in GetNodes ()) {
240                                 if (xn.NodeType == XamlNodeType.GetObject)
241                                         continue; // it is out of consideration here.
242                                 if (xn.NodeType == XamlNodeType.StartObject) {
243                                         foreach (var ns in NamespacesInType (xn.Object.Type))
244                                                 prefix_lookup.LookupPrefix (ns);
245                                 } else if (xn.NodeType == XamlNodeType.StartMember) {
246                                         var xm = xn.Member.Member;
247                                         // This filtering is done as a black list so far. There does not seem to be any usable property on XamlDirective.
248                                         if (xm == XamlLanguage.Items || xm == XamlLanguage.PositionalParameters || xm == XamlLanguage.Initialization)
249                                                 continue;
250                                         prefix_lookup.LookupPrefix (xn.Member.Member.PreferredXamlNamespace);
251                                 } else {
252                                         if (xn.NodeType == XamlNodeType.Value && xn.Value is Type)
253                                                 // this tries to lookup existing prefix, and if there isn't any, then adds a new declaration.
254                                                 TypeExtensionMethods.GetStringValue (XamlLanguage.Type, xn.Member.Member, xn.Value, value_serializer_ctx);
255                                         continue;
256                                 }
257                         }
258                         prefix_lookup.Namespaces.Sort ((nd1, nd2) => String.CompareOrdinal (nd1.Prefix, nd2.Prefix));
259                         prefix_lookup.IsCollectingNamespaces = false;
260                 }
261                 
262                 IEnumerable<string> NamespacesInType (XamlType xt)
263                 {
264                         yield return xt.PreferredXamlNamespace;
265                         if (xt.TypeArguments != null) {
266                                 // It is for x:TypeArguments
267                                 yield return XamlLanguage.Xaml2006Namespace;
268                                 foreach (var targ in xt.TypeArguments)
269                                         foreach (var ns in NamespacesInType (targ))
270                                                 yield return ns;
271                         }
272                 }
273         }
274 }