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;
30 using System.Windows.Markup;
32 using System.Xaml.Schema;
34 using System.Xml.Serialization;
38 internal class XamlObjectNodeIterator
40 static readonly XamlObject null_object = new XamlObject (XamlLanguage.Null, null);
42 public XamlObjectNodeIterator (object root, XamlSchemaContext schemaContext, IValueSerializerContext vctx)
46 value_serializer_ctx = vctx;
49 XamlSchemaContext ctx;
51 IValueSerializerContext value_serializer_ctx;
53 PrefixLookup PrefixLookup {
54 get { return (PrefixLookup) value_serializer_ctx.GetService (typeof (INamespacePrefixLookup)); }
56 XamlNameResolver NameResolver {
57 get { return (XamlNameResolver) value_serializer_ctx.GetService (typeof (IXamlNameResolver)); }
60 public XamlSchemaContext SchemaContext {
64 XamlType GetType (object obj)
66 return obj == null ? XamlLanguage.Null : ctx.GetXamlType (obj.GetType ());
69 // returns StartObject, StartMember, Value, EndMember and EndObject. (NamespaceDeclaration is not included)
70 public IEnumerable<XamlNodeInfo> GetNodes ()
72 var xobj = new XamlObject (GetType (root), root);
73 foreach (var node in GetNodes (null, xobj))
77 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj)
79 return GetNodes (xm, xobj, null, false);
82 IEnumerable<XamlNodeInfo> GetNodes (XamlMember xm, XamlObject xobj, XamlType overrideMemberType, bool partOfPositionalParameters)
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))
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))
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))
111 if (xm == XamlLanguage.Initialization) {
112 yield return new XamlNodeInfo (TypeExtensionMethods.GetStringValue (xobj.Type, xm, xobj.GetRawValue (), value_serializer_ctx));
116 // Value - only for non-top-level node (thus 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 ();
125 if (!partOfPositionalParameters)
126 foreach (var xn in GetNodes (null, null_object))
129 yield return new XamlNodeInfo (String.Empty);
132 yield return new XamlNodeInfo (TypeExtensionMethods.GetStringValue (xtt, xm, val, value_serializer_ctx));
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);
146 yield return en.Current;
147 } while (en.MoveNext ());
148 yield return new XamlNodeInfo (XamlNodeType.EndMember, xnm);
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;
156 yield break; // do not output anything
159 var obj = new XData () { Text = sw.ToString () };
160 foreach (var xn in GetNodes (null, new XamlObject (XamlLanguage.XData, obj)))
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))))
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.
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);
187 var sobj = new XamlObject (XamlLanguage.String, name);
188 foreach (var cn in GetMemberNodes (new XamlNodeMember (sobj, XamlLanguage.Name), new XamlNodeInfo [] { new XamlNodeInfo (name)}))
192 foreach (var xn in GetObjectMemberNodes (xobj))
194 yield return new XamlNodeInfo (XamlNodeType.EndObject, xobj);
198 int used_reference_ids;
200 string GetReferenceName (XamlObject xobj)
202 var xm = xobj.Type.GetAliasedProperty (XamlLanguage.Name);
204 return (string) xm.Invoker.GetValue (xobj.GetRawValue ());
205 return "__ReferenceID" + used_reference_ids++;
208 IEnumerable<XamlNodeInfo> GetMemberNodes (XamlNodeMember member, IEnumerable<XamlNodeInfo> contents)
210 yield return new XamlNodeInfo (XamlNodeType.StartMember, member);
211 foreach (var cn in contents)
213 yield return new XamlNodeInfo (XamlNodeType.EndMember, member);
216 IEnumerable<XamlNodeMember> GetNodeMembers (XamlObject xobj, IValueSerializerContext vsctx)
218 // XData.XmlReader is not returned.
219 if (xobj.Type == XamlLanguage.XData) {
220 yield return new XamlNodeMember (xobj, XamlLanguage.XData.GetMember ("Text"));
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);
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));
235 foreach (var xm in xobj.Type.GetAllObjectReaderMembersByType (vsctx))
236 yield return new XamlNodeMember (xobj, xm);
240 IEnumerable<XamlNodeInfo> GetObjectMemberNodes (XamlObject xobj)
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 ();
249 if (!GetNodes (xce.Current.Member, xce.Current.Value).GetEnumerator ().MoveNext ())
252 NameResolver.Restore ();
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 ();
262 if (!(e.MoveNext () && e.MoveNext () && e.MoveNext ())) // GetObject, EndObject and more
265 NameResolver.Restore ();
269 foreach (var cn in GetMemberNodes (xce.Current, GetNodes (xce.Current.Member, xce.Current.Value)))
274 IEnumerable<XamlNodeInfo> GetItemsNodes (XamlMember xm, XamlObject xobj)
276 var obj = xobj.GetRawValue ();
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.
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);
293 var wobj = TypeExtensionMethods.GetExtensionWrapped (iobj);
294 var xiobj = new XamlObject (GetType (wobj), wobj);
296 // Key member is written *inside* the item object.
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!
301 var en = GetNodes (null, xiobj).ToArray ();
302 yield return en [0]; // StartObject
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))
309 yield return en [en.Length - 1];
312 foreach (var xn in GetNodes (null, xiobj))
317 IEnumerable<XamlNodeInfo> EnumerateMixingMember (IEnumerable<XamlNodeInfo> nodes1, XamlMember m2, IEnumerable<XamlNodeInfo> nodes2)
319 if (nodes2 == null) {
320 foreach (var cn in nodes1)
325 var e1 = nodes1.GetEnumerator ();
326 var e2 = nodes2.GetEnumerator ();
328 while (e1.MoveNext ()) {
329 if (e1.Current.NodeType == XamlNodeType.StartMember) {
333 if (TypeExtensionMethods.CompareMembers (m2, e1.Current.Member.Member) < 0) {
334 while (e2.MoveNext ())
335 yield return e2.Current;
340 else if (e1.Current.NodeType == XamlNodeType.EndMember)
342 yield return e1.Current;
344 while (e2.MoveNext ())
345 yield return e2.Current;
348 IEnumerable<XamlNodeInfo> GetKeyNodes (object ikey, XamlType keyType, XamlNodeMember xknm)
350 foreach (var xn in GetMemberNodes (xknm, GetNodes (XamlLanguage.Key, new XamlObject (GetType (ikey), ikey), keyType, false)))
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 ()
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)
372 PrefixLookup.LookupPrefix (xn.Member.Member.PreferredXamlNamespace);
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);
380 PrefixLookup.Namespaces.Sort ((nd1, nd2) => String.CompareOrdinal (nd1.Prefix, nd2.Prefix));
381 PrefixLookup.IsCollectingNamespaces = false;
382 NameResolver.IsCollectingReferences = false;
383 NameResolver.NameScopeInitializationCompleted (this);
386 IEnumerable<string> NamespacesInType (XamlType xt)
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))