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.
24 using System
.Collections
;
25 using System
.Collections
.Generic
;
26 using System
.ComponentModel
;
28 using System
.Windows
.Markup
;
29 using System
.Xaml
.Schema
;
33 public class XamlObjectReader
: XamlReader
37 class NSList
: List
<NamespaceDeclaration
>
39 public NSList (XamlNodeType ownerType
, IEnumerable
<NamespaceDeclaration
> nsdecls
)
42 OwnerType
= ownerType
;
45 public XamlNodeType OwnerType { get; set; }
47 public IEnumerator
<NamespaceDeclaration
> GetEnumerator ()
49 return new NSEnumerator (this, base.GetEnumerator ());
53 class NSEnumerator
: IEnumerator
<NamespaceDeclaration
>
56 IEnumerator
<NamespaceDeclaration
> e
;
58 public NSEnumerator (NSList list
, IEnumerator
<NamespaceDeclaration
> e
)
64 public XamlNodeType OwnerType
{
65 get { return list.OwnerType; }
68 public void Dispose ()
72 public bool MoveNext ()
77 public NamespaceDeclaration Current
{
78 get { return e.Current; }
81 object IEnumerator
.Current
{
82 get { return Current; }
87 throw new NotSupportedException ();
91 class PrefixLookup
: INamespacePrefixLookup
93 XamlObjectReader source
;
95 public PrefixLookup (XamlObjectReader source
)
100 public string LookupPrefix (string ns
)
102 return source
.LookupPrefix (ns
);
106 #endregion nested types
108 public XamlObjectReader (object instance
)
109 : this (instance
, new XamlSchemaContext (null, null), null)
113 public XamlObjectReader (object instance
, XamlObjectReaderSettings settings
)
114 : this (instance
, new XamlSchemaContext (null, null), settings
)
118 public XamlObjectReader (object instance
, XamlSchemaContext schemaContext
)
119 : this (instance
, schemaContext
, null)
123 public XamlObjectReader (object instance
, XamlSchemaContext schemaContext
, XamlObjectReaderSettings settings
)
125 if (schemaContext
== null)
126 throw new ArgumentNullException ("schemaContext");
127 // FIXME: special case? or can it be generalized?
128 if (instance
is Type
)
129 instance
= new TypeExtension ((Type
) instance
);
131 this.instance
= instance
;
132 sctx
= schemaContext
;
133 this.settings
= settings
;
135 prefix_lookup
= new PrefixLookup (this);
137 if (instance
!= null) {
138 // check type validity. Note that some checks are done at Read() phase.
139 var type
= instance
.GetType ();
141 throw new XamlObjectReaderException (String
.Format ("instance type '{0}' must be public and non-nested.", type
));
142 root_type
= SchemaContext
.GetXamlType (instance
.GetType ());
143 if (root_type
.ConstructionRequiresArguments
&& root_type
.TypeConverter
== null)
144 throw new XamlObjectReaderException (String
.Format ("instance type '{0}' has no default constructor.", type
));
147 root_type
= XamlLanguage
.Null
;
152 XamlSchemaContext sctx
;
153 XamlObjectReaderSettings settings
;
155 INamespacePrefixLookup prefix_lookup
;
157 Stack
<XamlType
> types
= new Stack
<XamlType
> ();
158 Stack
<object> objects
= new Stack
<object> ();
159 Stack
<IEnumerator
<XamlMember
>> members_stack
= new Stack
<IEnumerator
<XamlMember
>> ();
161 IEnumerator
<NamespaceDeclaration
> ns_iterator
;
162 XamlNodeType node_type
= XamlNodeType
.None
;
165 public virtual object Instance
{
166 get { return NodeType == XamlNodeType.StartObject && objects.Count > 0 ? objects.Peek () : null; }
169 public override bool IsEof
{
170 get { return is_eof; }
173 public override XamlMember Member
{
174 get { return NodeType == XamlNodeType.StartMember ? members_stack.Peek ().Current : null; }
177 public override NamespaceDeclaration Namespace
{
178 get { return NodeType == XamlNodeType.NamespaceDeclaration ? ns_iterator.Current : null; }
181 public override XamlNodeType NodeType
{
182 get { return node_type; }
185 public override XamlSchemaContext SchemaContext
{
189 public override XamlType Type
{
190 get { return NodeType == XamlNodeType.StartObject ? types.Peek () : null; }
193 public override object Value
{
194 get { return NodeType == XamlNodeType.Value ? objects.Peek () : null; }
197 internal string LookupPrefix (string ns
)
199 foreach (var nsd
in namespaces
)
200 if (nsd
.Namespace
== ns
)
205 public override bool Read ()
208 throw new ObjectDisposedException ("reader");
211 IEnumerator
<XamlMember
> members
;
213 case XamlNodeType
.None
:
216 var d
= new Dictionary
<string,string> ();
217 //l.Sort ((p1, p2) => String.CompareOrdinal (p1.Key, p2.Key));
218 CollectNamespaces (d
, instance
, root_type
);
219 var nss
= from k
in d
.Keys
select new NamespaceDeclaration (k
, d
[k
]);
220 namespaces
= new NSList (XamlNodeType
.StartObject
, nss
);
221 namespaces
.Sort ((n1
, n2
) => String
.CompareOrdinal (n1
.Prefix
, n2
.Prefix
));
222 ns_iterator
= namespaces
.GetEnumerator ();
224 ns_iterator
.MoveNext ();
225 node_type
= XamlNodeType
.NamespaceDeclaration
;
228 case XamlNodeType
.NamespaceDeclaration
:
229 if (ns_iterator
.MoveNext ())
231 node_type
= ((NSEnumerator
) ns_iterator
).OwnerType
; // StartObject or StartMember
232 if (node_type
== XamlNodeType
.StartObject
)
235 StartNextMemberOrNamespace ();
238 case XamlNodeType
.StartObject
:
239 var xt
= types
.Peek ();
240 members
= xt
.GetAllReadWriteMembers ().GetEnumerator ();
241 if (members
.MoveNext ()) {
242 members_stack
.Push (members
);
243 StartNextMemberOrNamespace ();
247 node_type
= XamlNodeType
.EndObject
;
250 case XamlNodeType
.StartMember
:
251 if (!members_stack
.Peek ().Current
.IsContentValue ())
254 var obj
= GetMemberValueOrRootInstance ();
256 node_type
= XamlNodeType
.Value
;
260 case XamlNodeType
.Value
:
262 node_type
= XamlNodeType
.EndMember
;
265 case XamlNodeType
.GetObject
:
266 // how do we get here?
267 throw new NotImplementedException ();
269 case XamlNodeType
.EndMember
:
270 members
= members_stack
.Peek ();
271 if (members
.MoveNext ()) {
272 members_stack
.Push (members
);
273 StartNextMemberOrNamespace ();
275 members_stack
.Pop ();
276 node_type
= XamlNodeType
.EndObject
;
280 case XamlNodeType
.EndObject
:
281 // It might be either end of the entire object tree or just the end of an object value.
284 if (objects
.Count
== 0) {
285 node_type
= XamlNodeType
.None
;
289 members
= members_stack
.Peek ();
290 if (members
.MoveNext ()) {
291 StartNextMemberOrNamespace ();
294 // then, move to the end of current object member.
295 node_type
= XamlNodeType
.EndMember
;
300 void CollectNamespaces (Dictionary
<string,string> d
, object o
, XamlType xt
)
305 // it becomes NullExtension, so check standard ns.
306 CheckAddNamespace (d
, XamlLanguage
.Xaml2006Namespace
);
309 var ns
= xt
.PreferredXamlNamespace
;
310 CheckAddNamespace (d
, ns
);
312 foreach (var xm
in xt
.GetAllMembers ()) {
313 ns
= xm
.PreferredXamlNamespace
;
314 if (xm
is XamlDirective
&& ns
== XamlLanguage
.Xaml2006Namespace
)
316 if (xm
.Type
.IsCollection
|| xm
.Type
.IsDictionary
|| xm
.Type
.IsArray
)
317 continue; // FIXME: process them too.
318 var mv
= GetMemberValueOf (xm
, o
, xt
, d
);
319 CollectNamespaces (d
, mv
, xm
.Type
);
323 // This assumes that the next member is already on current position on current iterator.
324 void StartNextMemberOrNamespace ()
326 // FIXME: there might be NamespaceDeclarations.
327 node_type
= XamlNodeType
.StartMember
;
330 void StartNextObject ()
332 var obj
= GetMemberValueOrRootInstance ();
333 var xt
= Object
.ReferenceEquals (obj
, instance
) ? root_type
: obj
!= null ? SchemaContext
.GetXamlType (obj
.GetType ()) : XamlLanguage
.Null
;
335 // FIXME: enable these lines.
336 // FIXME: if there is an applicable instance descriptor, then it could be still valid.
337 //var type = xt.UnderlyingType;
338 //if (type.GetConstructor (System.Type.EmptyTypes) == null)
339 // throw new XamlObjectReaderException (String.Format ("Type {0} has no default constructor or an instance descriptor.", type));
343 node_type
= XamlNodeType
.StartObject
;
346 object GetMemberValueOrRootInstance ()
348 if (objects
.Count
== 0)
351 var xm
= members_stack
.Peek ().Current
;
352 var obj
= objects
.Peek ();
353 var xt
= types
.Peek ();
354 return GetMemberValueOf (xm
, obj
, xt
, null);
357 object GetMemberValueOf (XamlMember xm
, object obj
, XamlType xt
, Dictionary
<string,string> collectingNamespaces
)
361 if (xt
.IsContentValue ()) {
366 retobj
= xm
.GetMemberValueForObjectReader (xt
, obj
, prefix_lookup
);
369 if (collectingNamespaces
!= null) {
370 if (retobj
is Type
|| retobj
is TypeExtension
) {
371 var type
= (retobj
as Type
) ?? ((TypeExtension
) retobj
).Type
;
372 if (type
== null) // only TypeExtension.TypeName
374 var xtt
= SchemaContext
.GetXamlType (type
);
375 var ns
= xtt
.PreferredXamlNamespace
;
376 var nss
= collectingNamespaces
;
377 CheckAddNamespace (collectingNamespaces
, ns
);
380 else if (retxt
.IsContentValue ())
384 } else if (retxt
.IsContentValue ()) {
385 // FIXME: I'm not sure if this should be really done
386 // here, but every primitive values seem to be exposed
387 // as a string, not a typed object in XamlObjectReader.
388 return retxt
.GetStringValue (retobj
, prefix_lookup
);
394 void CheckAddNamespace (Dictionary
<string,string> d
, string ns
)
396 if (ns
== XamlLanguage
.Xaml2006Namespace
)
397 d
[XamlLanguage
.Xaml2006Namespace
] = "x";
398 else if (!d
.ContainsValue (String
.Empty
))
399 d
[ns
] = String
.Empty
;
400 else if (!d
.ContainsKey (ns
))
401 d
.Add (ns
, SchemaContext
.GetPreferredPrefix (ns
));