//
// XmlSerializer.cs:
//
// Author:
// John Donagher (john@webmeta.com)
// Ajay kumar Dwivedi (adwiv@yahoo.com)
// (C) 2002 John Donagher, Ajay kumar Dwivedi
//
using System.Xml.Serialization;
using System.Xml;
using System.IO;
using System;
using System.Collections;
using System.Reflection;
namespace System.Xml.Serialization
{
///
/// Summary description for XmlSerializer.
///
public class XmlSerializer
{
private Type type;
private XmlAttributeOverrides overrides;
private Type[] extraTypes;
private XmlRootAttribute rootAttribute;
private string defaultNamespace;
private static Hashtable typeTable;
private bool useOrder;
private bool isNullable;
public bool UseOrder
{
get{ return useOrder; }
set{ useOrder = value; }
}
#region constructors
protected XmlSerializer ()
{
}
public XmlSerializer (Type type)
: this(type, null, null, null, null)
{}
[MonoTODO]
public XmlSerializer (XmlTypeMapping xmltypemapping)
{}
public XmlSerializer (Type type, string defaultNamespace)
: this(type, null, null, null, defaultNamespace)
{}
public XmlSerializer (Type type, Type[] extraTypes)
: this(type, null, extraTypes, null, null)
{}
public XmlSerializer (Type type, XmlAttributeOverrides overrides)
: this(type, overrides, null, null, null)
{}
public XmlSerializer (Type type, XmlRootAttribute root)
: this(type, null, null, root, null)
{}
internal XmlSerializer(Hashtable typeTable)
{
typeTable = typeTable;
}
public XmlSerializer (Type type, XmlAttributeOverrides overrides, Type[] extraTypes, XmlRootAttribute root, string defaultNamespace)
{
if(type == null)
throw new ArgumentNullException("type", "XmlSerializer can't be consturcted with a null type");
this.type = type;
this.overrides = overrides;
this.extraTypes = (extraTypes == null ? new Type[0] : extraTypes);
this.rootAttribute = root;
this.defaultNamespace = defaultNamespace;
if(typeTable == null)
typeTable = new Hashtable();
FillTypeTable(type);
}
#endregion
#region Events
[MonoTODO]
public event XmlAttributeEventHandler UnknownAttribute;
[MonoTODO]
public event XmlElementEventHandler UnknownElement;
[MonoTODO]
public event XmlNodeEventHandler UnknownNode;
[MonoTODO]
public event UnreferencedObjectEventHandler UnreferencedObject;
#endregion
#region Deserialize
[MonoTODO]
public virtual bool CanDeserialize (XmlReader xmlReader)
{
throw new NotImplementedException ();
}
[MonoTODO]
public object Deserialize (Stream stream)
{
throw new NotImplementedException ();
}
[MonoTODO]
public object Deserialize (TextReader textReader)
{
throw new NotImplementedException ();
}
[MonoTODO]
public object Deserialize (XmlReader xmlReader)
{
throw new NotImplementedException ();
}
#endregion
#region Serialize Delegates
public void Serialize (Stream stream, object o)
{
XmlTextWriter xmlWriter = new XmlTextWriter(stream, System.Text.Encoding.Default);
xmlWriter.Formatting = Formatting.Indented;
Serialize(xmlWriter, o, null);
}
public void Serialize (TextWriter textWriter, object o)
{
XmlTextWriter xmlWriter = new XmlTextWriter(textWriter);
xmlWriter.Formatting = Formatting.Indented;
Serialize(xmlWriter, o, null);
}
public void Serialize (XmlWriter xmlWriter, object o)
{
Serialize(xmlWriter, o);
}
public void Serialize (Stream stream, object o, XmlSerializerNamespaces namespaces)
{
XmlTextWriter xmlWriter = new XmlTextWriter(stream, System.Text.Encoding.Default);
xmlWriter.Formatting = Formatting.Indented;
Serialize(xmlWriter, o, namespaces);
}
public void Serialize (TextWriter textWriter, object o, XmlSerializerNamespaces namespaces)
{
XmlTextWriter xmlWriter = new XmlTextWriter(textWriter);
xmlWriter.Formatting = Formatting.Indented;
Serialize(xmlWriter, o, namespaces);
}
#endregion
public void Serialize (XmlWriter writer, object o, XmlSerializerNamespaces namespaces)
{
if(namespaces == null)
{
namespaces = new XmlSerializerNamespaces();
}
if(namespaces.Count == 0)
{
namespaces.Add("xsd", System.Xml.Schema.XmlSchema.Namespace);
namespaces.Add("xsi", System.Xml.Schema.XmlSchema.InstanceNamespace);
}
Type objType = o.GetType();
string rootName = objType.Name;
string rootNs = null;
XmlSerializerNamespaces nss = new XmlSerializerNamespaces();
XmlQualifiedName[] qnames;
writer.WriteStartDocument();
object[] memberObj = (object[])typeTable[objType];
if(memberObj == null)
throw new Exception("Unknown Type "+objType+" encounterd during Serialization");
Hashtable memberTable = (Hashtable)memberObj[0];
XmlAttributes attrs = (XmlAttributes)memberTable[""];
//If we have been passed an XmlRoot, set it on the base class
if(rootAttribute != null)
attrs.XmlRoot = rootAttribute;
if(attrs.XmlRoot != null)
{
isNullable = attrs.XmlRoot.IsNullable;
if(attrs.XmlRoot.ElementName != null)
rootName = attrs.XmlRoot.ElementName;
rootNs = attrs.XmlRoot.Namespace;
}
//XMLNS attributes in the Root
XmlAttributes XnsAttrs = (XmlAttributes)((object[])typeTable[objType])[1];
if(XnsAttrs != null)
{
MemberInfo member = XnsAttrs.MemberInfo;
FieldInfo fInfo = member as FieldInfo;
PropertyInfo propInfo = member as PropertyInfo;
XmlSerializerNamespaces xns;
if(fInfo != null)
xns = (XmlSerializerNamespaces) fInfo.GetValue(o);
else
xns = (XmlSerializerNamespaces) propInfo.GetValue(o,null);
qnames = xns.ToArray();
foreach(XmlQualifiedName qname in qnames)
{
nss.Add(qname.Name, qname.Namespace);
}
}
//XmlNs from the namespaces passed
qnames = namespaces.ToArray();
foreach(XmlQualifiedName qname in qnames)
{
if(writer.LookupPrefix(qname.Namespace) != qname.Name)
{
nss.Add(qname.Name, qname.Namespace);
}
}
if(namespaces.GetPrefix(rootNs) != null)
writer.WriteStartElement(namespaces.GetPrefix(rootNs),rootName, rootNs);
qnames = nss.ToArray();
foreach(XmlQualifiedName qname in qnames)
{
if(writer.LookupPrefix(qname.Namespace) != qname.Name)
{
writer.WriteAttributeString("xmlns", qname.Name, null, qname.Namespace);
}
}
SerializeMembers(writer, o, true);//, namespaces);
writer.WriteEndElement();
}
private void SerializeMembers ( XmlWriter writer, object o, bool isRoot)
{
Type objType = o.GetType();
XmlAttributes XnsAttrs = (XmlAttributes)((object[])typeTable[objType])[1];
ArrayList attrList = (ArrayList)((object[])typeTable[objType])[2];
ArrayList elemList = (ArrayList)((object[])typeTable[objType])[3];
if(!isRoot && XnsAttrs != null)
{
MemberInfo member = XnsAttrs.MemberInfo;
FieldInfo fInfo = member as FieldInfo;
PropertyInfo propInfo = member as PropertyInfo;
XmlSerializerNamespaces xns;
if(fInfo != null)
xns = (XmlSerializerNamespaces) fInfo.GetValue(o);
else
xns = (XmlSerializerNamespaces) propInfo.GetValue(o,null);
XmlQualifiedName[] qnames = xns.ToArray();
foreach(XmlQualifiedName qname in qnames)
{
if(writer.LookupPrefix(qname.Namespace) != qname.Name)
writer.WriteAttributeString("xmlns", qname.Name, null, qname.Namespace);
}
}
//Serialize the Attributes.
foreach(XmlAttributes attrs in attrList)
{
MemberInfo member = attrs.MemberInfo;
FieldInfo fInfo = member as FieldInfo;
PropertyInfo propInfo = member as PropertyInfo;
Type attributeType;
object attributeValue;
string attributeValString;
string attributeName;
string attributeNs;
if(fInfo != null)
{
attributeType = fInfo.FieldType;
attributeValue = fInfo.GetValue(o);
}
else if(propInfo != null)
{
attributeType = propInfo.PropertyType;
attributeValue = propInfo.GetValue(o,null);
}
else
throw new Exception("Should never Happen. Neither field or property");
attributeName = attrs.GetAttributeName(attributeType, member.Name);
attributeNs = attrs.GetAttributeNamespace(attributeType);
if(attributeValue is XmlQualifiedName)
{
XmlQualifiedName qname = (XmlQualifiedName) attributeValue;
if(qname.IsEmpty)
continue;
writer.WriteStartAttribute(attributeName, attributeNs);
writer.WriteQualifiedName(qname.Name, qname.Namespace);
writer.WriteEndAttribute();
continue;
}
else if(attributeValue is XmlQualifiedName[])
{
XmlQualifiedName[] qnames = (XmlQualifiedName[]) attributeValue;
writer.WriteStartAttribute(attributeName, attributeNs);
int count = 0;
foreach(XmlQualifiedName qname in qnames)
{
if(qname.IsEmpty)
continue;
if(count++ > 0)
writer.WriteWhitespace(" ");
writer.WriteQualifiedName(qname.Name, qname.Namespace);
}
writer.WriteEndAttribute();
continue;
}
else if(attributeValue is XmlAttribute[])
{
XmlAttribute[] xmlattrs = (XmlAttribute[]) attributeValue;
foreach(XmlAttribute xmlattr in xmlattrs)
xmlattr.WriteTo(writer);
continue;
}
attributeValString = GetXmlValue(attributeValue);
if(attributeValString != GetXmlValue(attrs.XmlDefaultValue))
{
writer.WriteAttributeString(attributeName, attributeNs, attributeValString);
}
}
//Serialize Elements
foreach(XmlAttributes attrs in elemList)
{
MemberInfo member = attrs.MemberInfo;
FieldInfo fInfo = member as FieldInfo;
PropertyInfo propInfo = member as PropertyInfo;
Type elementType;
object elementValue;
string elementName;
string elementNs;
if(fInfo != null)
{
elementType = fInfo.FieldType;
elementValue = fInfo.GetValue(o);
}
else if(propInfo != null)
{
elementType = propInfo.PropertyType;
elementValue = propInfo.GetValue(o,null);
}
else throw new Exception("should never happpen. Element is neither field nor property");
elementName = attrs.GetElementName(elementType, member.Name);
elementNs = attrs.GetElementNamespace(elementType);
WriteElement(writer, attrs, elementName, elementNs, elementType, elementValue);
}
}
private void WriteElement(XmlWriter writer, XmlAttributes attrs,
string name, string ns, Type type, Object value)
{
if(IsInbuiltType(type))
{
writer.WriteElementString(name, ns, "" + GetXmlValue(value));
}
else if(attrs.XmlText != null && value != null)
{
if(type == typeof(object[]))
{
}
else if(type == typeof(string[]))
{
}
else if(type == typeof(XmlNode))
{
((XmlNode)value).WriteTo(writer);
}
else if(type == typeof(XmlNode[]))
{
XmlNode[] nodes = (XmlNode[])value;
foreach(XmlNode node in nodes)
node.WriteTo(writer);
}
}
else if(type.IsArray && value != null)
{
writer.WriteStartElement(name, ns);
SerializeArray(writer, value);
writer.WriteEndElement();
}
else if(value is ICollection)
{
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
//Find a non indexer Count Property with return type of int
PropertyInfo countProp = type.GetProperty("Count", flags, null, typeof(int),new Type[0], null);
PropertyInfo itemProp = type.GetProperty("Item", flags, null, null, new Type[1]{typeof(int)}, null);
int count = (int)countProp.GetValue(value,null);
object[] items = new object[1];
if(count > 0)
{
for(int i=0;i
/// If the type is a string, valuetype or primitive type we do not populate the TypeTable.
/// If the type is an array, we populate the TypeTable with Element type of the array.
/// If the type implements ICollection, it is handled differently. We do not care for its members.
/// If the type implements IEnumberable, we check that it implements Add(). Don't care for members.
///
private void FillTypeTable(Type type)
{
if(typeTable.Contains(type))
return;
//For value types and strings we don't need the members.
//FIXME: We will need the enum types probably.
if(IsInbuiltType(type))
return;
//Array, ICollection and IEnumberable are treated differenty
if(type.IsArray)
{
FillArrayType(type);
return;
}
else if(type.IsEnum)
{
FillEnum(type);
return;
}
else
{
//There must be a public constructor
if(!HasDefaultConstructor(type))
{
throw new Exception("Can't Serialize Type " + type.Name + " since it does not have default Constructor");
}
if(type.GetInterface("ICollection") == typeof(System.Collections.ICollection))
{
FillICollectionType(type);
return;
}
if(type.GetInterface("IEnumerable") == typeof(System.Collections.IEnumerable))
{
FillIEnumerableType(type);
return;
}
}
//Add the Class to the hashtable.
//Each value of the hashtable has two objects, one is the hashtable with key of membername (for deserialization)
//Other is an Array of XmlSerializernames, Array of XmlAttributes & Array of XmlElements.
Object[] memberObj = new Object[4];
typeTable.Add(type,memberObj);
Hashtable memberTable = new Hashtable();
memberObj[0] = memberTable;
memberTable.Add("", XmlAttributes.FromClass(type));
memberObj[1] = null;
ArrayList attrList = new ArrayList();
memberObj[2] = attrList;
ArrayList elemList = new ArrayList();
memberObj[3] = elemList;
//Get the graph of the members. Graph is nothing but the order
//in which MS implementation serializes the members.
MemberInfo[] minfo = GetGraph(type);
foreach(MemberInfo member in minfo)
{
FieldInfo fInfo = (member as FieldInfo);
PropertyInfo propInfo = (member as PropertyInfo);
if(fInfo != null)
{
//If field is readOnly or const, do not serialize it.
if(fInfo.IsLiteral || fInfo.IsInitOnly)
continue;
XmlAttributes attrs = XmlAttributes.FromField(member,fInfo);
//If XmlAttributes have XmlIgnore, ignore this member
if(attrs.XmlIgnore)
continue;
//If this member is a XmlNs type, set the XmlNs object.
if(attrs.Xmlns)
{
memberObj[1] = attrs;
continue;
}
//If the member is a attribute Type, Add to attribute list
if(attrs.isAttribute)
attrList.Add(attrs);
else //Add to elements
{
elemList.Add(attrs);
}
//Add in the Hashtable.
memberTable.Add(member.Name, attrs);
Type fieldType = fInfo.FieldType;
if(attrs.XmlAnyAttribute != null || attrs.XmlText != null)
continue;
if(attrs.XmlElements.Count > 0)
{
foreach(XmlElementAttribute elem in attrs.XmlElements)
{
if(elem.Type != null)
FillTypeTable(elem.Type);
else
FillTypeTable(fieldType);
}
continue;
}
if(!IsInbuiltType(fieldType))
FillTypeTable(fieldType);
}
else if(propInfo != null)
{
//If property is readonly or writeonly, do not serialize it.
//Exceptions are properties whose return type is array, ICollection or IEnumerable
//Indexers are not serialized unless the class Implements ICollection.
if(!(propInfo.PropertyType.IsArray || Implements(propInfo.PropertyType,typeof(ICollection)) ||
(propInfo.PropertyType != typeof(string) && Implements(propInfo.PropertyType,typeof(IEnumerable)))))
{
if(!(propInfo.CanRead && propInfo.CanWrite) || propInfo.GetIndexParameters().Length != 0)
continue;
}
XmlAttributes attrs = XmlAttributes.FromProperty(member,propInfo);
//If XmlAttributes have XmlIgnore, ignore this member
if(attrs.XmlIgnore)
continue;
//If this member is a XmlNs type, set the XmlNs object.
if(attrs.Xmlns)
{
memberObj[1] = attrs;
continue;
}
//If the member is a attribute Type, Add to attribute list
if(attrs.isAttribute)
attrList.Add(attrs);
else //Add to elements
{
elemList.Add(attrs);
}
//OtherWise add in the Hashtable.
memberTable.Add(member.Name, attrs);
Type propType = propInfo.PropertyType;
if(attrs.XmlAnyAttribute != null || attrs.XmlText != null)
continue;
if(attrs.XmlElements.Count > 0)
{
foreach(XmlElementAttribute elem in attrs.XmlElements)
{
if(elem.Type != null)
FillTypeTable(elem.Type);
else
FillTypeTable(propType);
}
continue;
}
if(!IsInbuiltType(propType))
FillTypeTable(propType);
}
}
//Sort the attributes for the members according to their Order
//This is an extension to MS's Implementation and will be useful
//if our reflection does not return the same order of elements
//as MS .NET impl
if(useOrder)
BubbleSort(elemList,XmlAttributes.attrComparer);
}
private void FillArrayType(Type type)
{
if(!type.IsArray)
throw new Exception("Should never happen. Type is not an array");
if(type.GetArrayRank() != 1)
throw new Exception("MultiDimensional Arrays are not Supported");
Type arrayType = type.GetElementType();
if(arrayType.IsArray)
{
FillArrayType(arrayType);
}
else if(!IsInbuiltType(arrayType))
{
FillTypeTable(arrayType);
}
}
private void FillICollectionType(Type type)
{
//Must have an public Indexer that takes an integer and
//a public Count Property which returns an int.
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
//Find a non indexer Count Property with return type of int
PropertyInfo countProp = type.GetProperty("Count", flags, null, typeof(int),new Type[0], null);
if(countProp == null || !countProp.CanRead)
throw new Exception("Cannot Serialize "+type+" because it implements ICollectoion, but does not implement public Count property");
//Find a indexer Item Property which takes an int
PropertyInfo itemProp = type.GetProperty("Item", flags, null, null, new Type[1]{typeof(int)}, null);
if(itemProp == null || !itemProp.CanRead || !itemProp.CanWrite)
throw new Exception("Cannot Serialize "+type+" because it does not have a read/write indexer property that takes an int as argument");
}
private void FillIEnumerableType(Type type)
{
//Must implement a public Add method that takes a single parameter.
//The Add method's parameter must be of the same type as is returned from
// the Current property on the value returned from GetEnumerator, or one of that type's bases.
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
MethodInfo enumMethod = type.GetMethod("GetEnumerator", flags, null, new Type[0], null);
if(enumMethod == null)
throw new Exception("Cannot serialize "+type+" because it does not implement GetEnumerator");
Type returnType = enumMethod.ReturnType;
while(returnType != null)
{
MethodInfo addMethod = type.GetMethod("Add", flags, null, new Type[1]{returnType},null);
if(addMethod != null)
return;
returnType = returnType.BaseType;
}
throw new Exception("Cannot serialize "+type+" because it does not have a Add method which takes "
+enumMethod.ReturnType+" or one of its base types.");
}
private void FillEnum(Type type)
{
Hashtable memberTable = new Hashtable();
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
typeTable.Add(type,memberTable);
string[] names = Enum.GetNames(type);
foreach(string name in names)
{
MemberInfo[] members = type.GetMember(name);
if(members.Length != 1)
throw new Exception("Should never happen. Enum member not present or more than one. "+name);
XmlAttributes attrs = new XmlAttributes(members[0]);
if(attrs.XmlIgnore)
continue;
if(attrs.XmlEnum != null)
{
memberTable.Add(members[0].Name, attrs.XmlEnum.Name);
}
else
{
memberTable.Add(members[0].Name, members[0].Name);
}
}
}
private bool HasDefaultConstructor(Type type)
{
ConstructorInfo defaultConstructor = type.GetConstructor(new Type[0]);
if(defaultConstructor == null || defaultConstructor.IsAbstract || defaultConstructor.IsStatic
|| !defaultConstructor.IsPublic)
return false;
return true;
}
private bool IsInbuiltType(Type type)
{
if(type.IsEnum)
return false;
if(type.IsValueType || type == typeof(string) || type.IsPrimitive)
return true;
return false;
}
private static MemberInfo[] GetGraph(Type type)
{
ArrayList typeGraph = new ArrayList();
GetGraph(type, typeGraph);
return (MemberInfo[]) typeGraph.ToArray(typeof(MemberInfo));
}
private static void GetGraph(Type type, ArrayList typeGraph)
{
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
if(type.BaseType == null)
return;
GetGraph(type.BaseType,typeGraph);
typeGraph.AddRange(type.GetFields(flags));
typeGraph.AddRange(type.GetProperties(flags));
}
private string GetXmlValue(object value)
{
if(value == null)
return null;
if(value is Enum)
{
Type type = value.GetType();
if(typeTable.ContainsKey(type))
{
Hashtable memberTable = (Hashtable)(typeTable[type]);
if(type.IsDefined(typeof(FlagsAttribute),false))
{
//If value is exactly a single enum member
if(memberTable.Contains(value.ToString()))
return (string)memberTable[value.ToString()];
string retval = "";
int count=0;
int enumval = (int) value;
string[] names = Enum.GetNames(type);
foreach(string key in names)
{
if(!memberTable.ContainsKey(key))
continue;
//Otherwise multiple values.
int val = (int)Enum.Parse(type, key);
if(val != 0 && (enumval & val) == val)
{
retval += " " + (string)memberTable[Enum.GetName(type,val)];
}
}
retval = retval.Trim();
if(retval.Length == 0)
return null;
return retval;
}
else
{
if(memberTable.ContainsKey(value.ToString()))
return (string)memberTable[value.ToString()];
else
return null;
}
}
else
{
throw new Exception("Unknown Enumeration");
}
}
if(value is bool)
{
return (bool)value ? "true" : "false";
}
if(value is XmlQualifiedName)
{
if(((XmlQualifiedName)value).IsEmpty)
return null;
}
return (value==null) ? null : value.ToString();
}
private static void ProcessAttributes(XmlAttributes attrs, Hashtable memberTable)
{
if(attrs.XmlAnyAttribute != null)
{
}
foreach(XmlAnyElementAttribute anyelem in attrs.XmlAnyElements)
{
memberTable.Add(new XmlQualifiedName(anyelem.Name, anyelem.Namespace), attrs);
}
if(attrs.XmlArray != null)
{
}
foreach(XmlArrayItemAttribute item in attrs.XmlArrayItems)
{
memberTable.Add(new XmlQualifiedName(item.ElementName, item.Namespace), attrs);
}
if(attrs.XmlAttribute != null)
{
memberTable.Add(new XmlQualifiedName(attrs.XmlAttribute.AttributeName,attrs.XmlAttribute.Namespace), attrs);
}
if(attrs.XmlChoiceIdentifier != null)
{
}
foreach(XmlElementAttribute elem in attrs.XmlElements)
{
memberTable.Add(new XmlQualifiedName(elem.ElementName, elem.Namespace), attrs);
}
if(attrs.XmlEnum != null)
{
}
if(attrs.XmlType != null)
{
memberTable.Add(new XmlQualifiedName(attrs.XmlType.TypeName, attrs.XmlType.Namespace), attrs);
}
}
private bool Implements(Type type, Type interfaceType)
{
if(type.GetInterface(interfaceType.FullName) == interfaceType)
return true;
return false;
}
private static void BubbleSort(ArrayList array, IComparer comparer)
{
int len = array.Count;
object obj1, obj2;
for (int i=0; i < len; i++)
{
for (int j=0; j < len -i -1; j++)
{
obj1 = array[j];
obj2 = array[j+1];
if (comparer.Compare( obj2 , obj1 ) < 0)
{
array[j] = obj2;
array[j+1] = obj1;
}
}
}
}
}
}