//
// Copyright (C) 2010 Novell Inc. http://novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Markup;
using System.Xaml.Schema;
namespace System.Xaml{
public class XamlObjectReader : XamlReader
{
#region nested types
class NSList : List<NamespaceDeclaration>
{
public NSList (XamlNodeType ownerType, IEnumerable<NamespaceDeclaration> nsdecls)
: base (nsdecls)
{
OwnerType = ownerType;
}
public XamlNodeType OwnerType { get; set; }
public IEnumerator<NamespaceDeclaration> GetEnumerator ()
{
return new NSEnumerator (this, base.GetEnumerator ());
}
}
class NSEnumerator : IEnumerator<NamespaceDeclaration>
{
NSList list;
IEnumerator<NamespaceDeclaration> e;
public NSEnumerator (NSList list, IEnumerator<NamespaceDeclaration> e)
{
this.list= list;
this.e = e;
}
public XamlNodeType OwnerType {
get { return list.OwnerType; }
}
public void Dispose ()
{
}
public bool MoveNext ()
{
return e.MoveNext ();
}
public NamespaceDeclaration Current {
get { return e.Current; }
}
object IEnumerator.Current {
get { return Current; }
}
public void Reset ()
{
throw new NotSupportedException ();
}
}
class PrefixLookup : INamespacePrefixLookup
{
XamlObjectReader source;
public PrefixLookup (XamlObjectReader source)
{
this.source = source;
}
public string LookupPrefix (string ns)
{
return source.LookupPrefix (ns);
}
}
#endregion nested types
public XamlObjectReader (object instance)
: this (instance, new XamlSchemaContext (null, null), null)
{
}
public XamlObjectReader (object instance, XamlObjectReaderSettings settings)
: this (instance, new XamlSchemaContext (null, null), settings)
{
}
public XamlObjectReader (object instance, XamlSchemaContext schemaContext)
: this (instance, schemaContext, null)
{
}
public XamlObjectReader (object instance, XamlSchemaContext schemaContext, XamlObjectReaderSettings settings)
{
if (schemaContext == null)
throw new ArgumentNullException ("schemaContext");
// FIXME: special case? or can it be generalized?
if (instance is Type)
instance = new TypeExtension ((Type) instance);
this.instance = instance;
sctx = schemaContext;
this.settings = settings;
prefix_lookup = new PrefixLookup (this);
if (instance != null) {
// check type validity. Note that some checks are done at Read() phase.
var type = instance.GetType ();
if (!type.IsPublic)
throw new XamlObjectReaderException (String.Format ("instance type '{0}' must be public and non-nested.", type));
root_type = SchemaContext.GetXamlType (instance.GetType ());
if (root_type.ConstructionRequiresArguments && root_type.TypeConverter == null)
throw new XamlObjectReaderException (String.Format ("instance type '{0}' has no default constructor.", type));
}
else
root_type = XamlLanguage.Null;
}
object instance;
XamlType root_type;
XamlSchemaContext sctx;
XamlObjectReaderSettings settings;
INamespacePrefixLookup prefix_lookup;
Stack<XamlType> types = new Stack<XamlType> ();
Stack<object> objects = new Stack<object> ();
Stack<IEnumerator<XamlMember>> members_stack = new Stack<IEnumerator<XamlMember>> ();
NSList namespaces;
IEnumerator<NamespaceDeclaration> ns_iterator;
XamlNodeType node_type = XamlNodeType.None;
bool is_eof;
public virtual object Instance {
get { return NodeType == XamlNodeType.StartObject && objects.Count > 0 ? objects.Peek () : null; }
}
public override bool IsEof {
get { return is_eof; }
}
public override XamlMember Member {
get { return NodeType == XamlNodeType.StartMember ? members_stack.Peek ().Current : null; }
}
public override NamespaceDeclaration Namespace {
get { return NodeType == XamlNodeType.NamespaceDeclaration ? ns_iterator.Current : null; }
}
public override XamlNodeType NodeType {
get { return node_type; }
}
public override XamlSchemaContext SchemaContext {
get { return sctx; }
}
public override XamlType Type {
get { return NodeType == XamlNodeType.StartObject ? types.Peek () : null; }
}
public override object Value {
get { return NodeType == XamlNodeType.Value ? objects.Peek () : null; }
}
internal string LookupPrefix (string ns)
{
foreach (var nsd in namespaces)
if (nsd.Namespace == ns)
return nsd.Prefix;
return null;
}
public override bool Read ()
{
if (IsDisposed)
throw new ObjectDisposedException ("reader");
if (IsEof)
return false;
IEnumerator<XamlMember> members;
switch (NodeType) {
case XamlNodeType.None:
default:
// -> namespaces
var d = new Dictionary<string,string> ();
//l.Sort ((p1, p2) => String.CompareOrdinal (p1.Key, p2.Key));
CollectNamespaces (d, instance, root_type);
var nss = from k in d.Keys select new NamespaceDeclaration (k, d [k]);
namespaces = new NSList (XamlNodeType.StartObject, nss);
namespaces.Sort ((n1, n2) => String.CompareOrdinal (n1.Prefix, n2.Prefix));
ns_iterator = namespaces.GetEnumerator ();
ns_iterator.MoveNext ();
node_type = XamlNodeType.NamespaceDeclaration;
return true;
case XamlNodeType.NamespaceDeclaration:
if (ns_iterator.MoveNext ())
return true;
node_type = ((NSEnumerator) ns_iterator).OwnerType; // StartObject or StartMember
if (node_type == XamlNodeType.StartObject)
StartNextObject ();
else
StartNextMemberOrNamespace ();
return true;
case XamlNodeType.StartObject:
var xt = types.Peek ();
members = xt.GetAllReadWriteMembers ().GetEnumerator ();
if (members.MoveNext ()) {
members_stack.Push (members);
StartNextMemberOrNamespace ();
return true;
}
else
node_type = XamlNodeType.EndObject;
return true;
case XamlNodeType.StartMember:
if (!members_stack.Peek ().Current.IsContentValue ())
StartNextObject ();
else {
var obj = GetMemberValueOrRootInstance ();
objects.Push (obj);
node_type = XamlNodeType.Value;
}
return true;
case XamlNodeType.Value:
objects.Pop ();
node_type = XamlNodeType.EndMember;
return true;
case XamlNodeType.GetObject:
// how do we get here?
throw new NotImplementedException ();
case XamlNodeType.EndMember:
members = members_stack.Peek ();
if (members.MoveNext ()) {
members_stack.Push (members);
StartNextMemberOrNamespace ();
} else {
members_stack.Pop ();
node_type = XamlNodeType.EndObject;
}
return true;
case XamlNodeType.EndObject:
// It might be either end of the entire object tree or just the end of an object value.
types.Pop ();
objects.Pop ();
if (objects.Count == 0) {
node_type = XamlNodeType.None;
is_eof = true;
return false;
}
members = members_stack.Peek ();
if (members.MoveNext ()) {
StartNextMemberOrNamespace ();
return true;
}
// then, move to the end of current object member.
node_type = XamlNodeType.EndMember;
return true;
}
}
void CollectNamespaces (Dictionary<string,string> d, object o, XamlType xt)
{
if (xt == null)
return;
if (o == null) {
// it becomes NullExtension, so check standard ns.
CheckAddNamespace (d, XamlLanguage.Xaml2006Namespace);
return;
}
var ns = xt.PreferredXamlNamespace;
CheckAddNamespace (d, ns);
foreach (var xm in xt.GetAllMembers ()) {
ns = xm.PreferredXamlNamespace;
if (xm is XamlDirective && ns == XamlLanguage.Xaml2006Namespace)
continue;
if (xm.Type.IsCollection || xm.Type.IsDictionary || xm.Type.IsArray)
continue; // FIXME: process them too.
var mv = GetMemberValueOf (xm, o, xt, d);
CollectNamespaces (d, mv, xm.Type);
}
}
// This assumes that the next member is already on current position on current iterator.
void StartNextMemberOrNamespace ()
{
// FIXME: there might be NamespaceDeclarations.
node_type = XamlNodeType.StartMember;
}
void StartNextObject ()
{
var obj = GetMemberValueOrRootInstance ();
var xt = Object.ReferenceEquals (obj, instance) ? root_type : obj != null ? SchemaContext.GetXamlType (obj.GetType ()) : XamlLanguage.Null;
// FIXME: enable these lines.
// FIXME: if there is an applicable instance descriptor, then it could be still valid.
//var type = xt.UnderlyingType;
//if (type.GetConstructor (System.Type.EmptyTypes) == null)
// throw new XamlObjectReaderException (String.Format ("Type {0} has no default constructor or an instance descriptor.", type));
objects.Push (obj);
types.Push (xt);
node_type = XamlNodeType.StartObject;
}
object GetMemberValueOrRootInstance ()
{
if (objects.Count == 0)
return instance;
var xm = members_stack.Peek ().Current;
var obj = objects.Peek ();
var xt = types.Peek ();
return GetMemberValueOf (xm, obj, xt, null);
}
object GetMemberValueOf (XamlMember xm, object obj, XamlType xt, Dictionary<string,string> collectingNamespaces)
{
object retobj;
XamlType retxt;
if (xt.IsContentValue ()) {
retxt = xt;
retobj = obj;
} else {
retxt = xm.Type;
retobj = xm.GetMemberValueForObjectReader (xt, obj, prefix_lookup);
}
if (collectingNamespaces != null) {
if (retobj is Type || retobj is TypeExtension) {
var type = (retobj as Type) ?? ((TypeExtension) retobj).Type;
if (type == null) // only TypeExtension.TypeName
return null;
var xtt = SchemaContext.GetXamlType (type);
var ns = xtt.PreferredXamlNamespace;
var nss = collectingNamespaces;
CheckAddNamespace (collectingNamespaces, ns);
return null;
}
else if (retxt.IsContentValue ())
return null;
else
return retobj;
} else if (retxt.IsContentValue ()) {
// FIXME: I'm not sure if this should be really done
// here, but every primitive values seem to be exposed
// as a string, not a typed object in XamlObjectReader.
return retxt.GetStringValue (retobj, prefix_lookup);
}
else
return retobj;
}
void CheckAddNamespace (Dictionary<string,string> d, string ns)
{
if (ns == XamlLanguage.Xaml2006Namespace)
d [XamlLanguage.Xaml2006Namespace] = "x";
else if (!d.ContainsValue (String.Empty))
d [ns] = String.Empty;
else if (!d.ContainsKey (ns))
d.Add (ns, SchemaContext.GetPreferredPrefix (ns));
}
}
}
|