2 // Copyright (C) 2010 Novell Inc. http://novell.com
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:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
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.
25 using System.Collections;
26 using System.Collections.Generic;
27 using System.ComponentModel;
29 using System.Windows.Markup;
31 using System.Xaml.Schema;
35 internal struct XamlObjectNodeIterator
37 static readonly XamlObject null_object = new XamlObject (XamlLanguage.Null, null);
39 public XamlObjectNodeIterator (object root, XamlSchemaContext schemaContext, PrefixLookup prefixLookup)
43 value_serializer_ctx = new ValueSerializerContext (prefixLookup, ctx);
46 XamlSchemaContext ctx;
48 IValueSerializerContext value_serializer_ctx;
50 PrefixLookup prefix_lookup {
51 get { return (PrefixLookup) value_serializer_ctx.GetService (typeof (INamespacePrefixLookup)); }
54 public XamlSchemaContext SchemaContext {
58 XamlType GetType (object obj)
60 return ctx.GetXamlType (new InstanceContext (obj).GetWrappedValue ().GetType ());
63 // returns StartObject, StartMember, Value, EndMember and EndObject. (NamespaceDeclaration is not included)
64 public IEnumerable<XamlNodeInfo> GetNodes ()
66 var xobj = new XamlObject (GetType (root), root);
67 foreach (var node in GetNodes (null, xobj))
71 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj)
73 return GetNodes (xm, xobj, null);
76 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj, XamlType overrideMemberType)
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))
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))
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);
106 if (xm == XamlLanguage.Initialization) {
107 yield return new XamlNodeInfo (TypeExtensionMethods.GetStringValue (xobj.Type, xm, xobj.GetRawValue (), value_serializer_ctx));
111 // Value - only for non-top-level node (thus 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 ();
119 foreach (var xn in GetNodes (null, null_object))
122 yield return new XamlNodeInfo (TypeExtensionMethods.GetStringValue (xtt, xm, val, value_serializer_ctx));
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);
136 yield return en.Current;
137 } while (en.MoveNext ());
138 yield return new XamlNodeInfo (XamlNodeType.EndMember, xnm);
140 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
143 yield return new XamlNodeInfo (XamlNodeType.StartObject, xobj);
144 foreach (var xn in GetObjectMemberNodes (xobj))
146 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
150 IEnumerable<XamlNodeInfo> GetObjectMemberNodes (XamlObject xobj)
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 ())
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
166 yield return new XamlNodeInfo (XamlNodeType.StartMember, xce.Current);
167 foreach (var cn in GetNodes (xce.Current.Member, xce.Current.Value))
169 yield return new XamlNodeInfo (XamlNodeType.EndMember, xce.Current);
173 IEnumerable<XamlNodeInfo> GetItemsNodes (XamlMember xm, XamlObject xobj)
175 var obj = xobj.GetRawValue ();
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.
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);
192 var xiobj = new XamlObject (GetType (iobj), iobj);
194 // Key member is written *inside* the item object.
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!
199 var en = GetNodes (null, xiobj).ToArray ();
200 yield return en [0]; // StartObject
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++)
207 foreach (var kn in GetKeyNodes (ikey, xobj.Type.KeyType, xknm))
209 yield return en [en.Length - 1];
211 // key -> value -> endobject
212 foreach (var kn in GetKeyNodes (ikey, xobj.Type.KeyType, xknm))
214 for (int i = 1; i < en.Length - 1; i++)
216 yield return en [en.Length - 1];
220 foreach (var xn in GetNodes (null, xiobj))
225 IEnumerable<XamlNodeInfo> GetKeyNodes (object ikey, XamlType keyType, XamlNodeMember xknm)
227 yield return new XamlNodeInfo (XamlNodeType.StartMember, xknm);
228 foreach (var xn in GetNodes (XamlLanguage.Key, new XamlObject (GetType (ikey), ikey), keyType))
230 yield return new XamlNodeInfo (XamlNodeType.EndMember, xknm);
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 ()
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)
250 prefix_lookup.LookupPrefix (xn.Member.Member.PreferredXamlNamespace);
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);
258 prefix_lookup.Namespaces.Sort ((nd1, nd2) => String.CompareOrdinal (nd1.Prefix, nd2.Prefix));
259 prefix_lookup.IsCollectingNamespaces = false;
262 IEnumerable<string> NamespacesInType (XamlType xt)
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))