using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
namespace Framework.Storage {
/// Very simple XML serializer that outputs only basic XML
public static class SimpleXmlSerializer {
/// Loads the state of an object from a file
/// Type of object that will be loaded
/// Instance into which the state will be loaded
/// File from which the state will be loaded
public static void Load(T instance, string path) {
Load(instance, path, typeof(T).Name);
}
/// Loads the state of an object from a file
/// Type of object that will be loaded
/// Instance into which the state will be loaded
/// File from which the state will be loaded
/// Expected name of the root element
public static void Load(T instance, string path, string rootElementName) {
using(XmlReader reader = XmlReader.Create(path, ReaderSettings)) {
readRootInstance(instance, reader, rootElementName);
}
}
/// Loads the state of an object from a file
/// Type of object that will be loaded
/// Instance into which the state will be loaded
/// Stream from which the state will be loaded
public static void Load(T instance, Stream stream) {
Load(instance, stream, typeof(T).Name);
}
/// Loads the state of an object from a file
/// Type of object that will be loaded
/// Instance into which the state will be loaded
/// Stream from which the state will be loaded
/// Expected name of the root element
public static void Load(T instance, Stream stream, string rootElementName) {
XmlReader reader = XmlReader.Create(stream);
readRootInstance(instance, reader, rootElementName);
}
/// Saves the state of an object into a file
/// Type of object that will be saved
/// Instance whose state will be saved
/// File into which the state will be saved
public static void Save(T instance, string path) {
Save(instance, path, typeof(T).Name);
}
/// Saves the state of an object into a file
/// Type of object that will be saved
/// Instance whose state will be saved
/// File into which the state will be saved
/// Name that will be given to the root element
public static void Save(T instance, string path, string rootElementName) {
using(XmlWriter writer = XmlWriter.Create(path, WriterSettings)) {
writer.WriteStartDocument();
writeRootInstance(instance, writer, rootElementName);
writer.WriteEndDocument();
writer.Flush();
}
}
/// Saves the state of an object into a file
/// Type of object that will be saved
/// Instance whose state will be saved
/// Stream into which the state will be saved
public static void Save(T instance, Stream stream) {
Save(instance, stream, typeof(T).Name);
}
/// Saves the state of an object into a file
/// Type of object that will be saved
/// Instance whose state will be saved
/// Stream into which the state will be saved
/// Name that will be given to the root element
public static void Save(T instance, Stream stream, string rootElementName) {
XmlWriter writer = XmlWriter.Create(stream, WriterSettings);
writer.WriteStartDocument();
writeRootInstance(instance, writer, rootElementName);
writer.WriteEndDocument();
writer.Flush();
}
/// Loads the root object from an XML document
/// Type of root object that will be loaded
/// Instance into which the state will be loaded
/// Reader from which the state will be loaded
/// Expected name of the root element
private static void readRootInstance(
T instance, XmlReader reader, string rootElementName
) {
XmlNodeType nodeType = reader.MoveToContent();
if(nodeType != XmlNodeType.Element) {
throw new XmlException("XML document does not start with an element");
}
// Whoever designed the XmlReader should a) look up 'consistency' on Wikipedia
// and b) cut down on psychedelic drugs or whatever else guides his thoughts.
if(reader.Name != rootElementName) {
throw new XmlException("XML document contains the wrong type of object");
}
readFields(instance, reader);
}
/// Writes the root object into an XML document
/// Type of root object that will be written
/// Instance whose state will be saved
/// Writer into which the state will be written
/// Name that will be given to the root element
private static void writeRootInstance(
T instance, XmlWriter writer, string rootElementName
) {
writer.WriteStartElement(rootElementName);
writeFields(instance, writer);
writer.WriteEndElement();
}
/// Recursively reads the fields of an object from an XML document
/// Instance into which the fields will be loaded
/// Reader from which the fields will be loaded
private static void readFields(object instance, XmlReader reader) {
Type instanceType = instance.GetType();
FieldInfo[] fieldInfos = instanceType.GetFields(
BindingFlags.Public | BindingFlags.Instance
);
while(reader.Read()) {
if(reader.IsStartElement()) {
FieldInfo fieldInfo = getFieldInfoByName(fieldInfos, reader.Name);
if(fieldInfo == null) {
throw new XmlException(
"Element '" + reader.Name + "' on '" + instance.GetType().Name + "' not recognized"
);
}
if(fieldInfo.FieldType == typeof(TimeSpan)) {
string valueAsString = reader.ReadString();
fieldInfo.SetValue(instance, XmlConvert.ToTimeSpan(valueAsString));
} else if(isSimpleType(fieldInfo.FieldType)) {
object valueAsObject = reader.ReadElementContentAs(fieldInfo.FieldType, null);
fieldInfo.SetValue(instance, valueAsObject);
} else {
object valueAsObject = fieldInfo.GetValue(instance);
readFields(valueAsObject, reader);
fieldInfo.SetValue(instance, valueAsObject);
}
} else {
break;
}
}
}
/// Looks up a field info by its name
/// Field infos in which to search
/// Name that will be searched for
/// The field info with the specified name or null if not found
private static FieldInfo getFieldInfoByName(FieldInfo[] fieldInfos, string name) {
for(int index = 0; index < fieldInfos.Length; ++index) {
if(fieldInfos[index].Name == name) {
return fieldInfos[index];
}
}
return null;
}
/// Recursively saves the fields of an object into an XML writer
/// Object whose fields will be saved
/// XML writer into which the fields will be saved
private static void writeFields(object instance, XmlWriter writer) {
Type instanceType = instance.GetType();
FieldInfo[] fieldInfos = instanceType.GetFields(
BindingFlags.Public | BindingFlags.Instance
);
// Save simple fields first so they're easier to associate with the header
for(int index = 0; index < fieldInfos.Length; ++index) {
FieldInfo fieldInfo = fieldInfos[index];
if(isSimpleType(fieldInfo.FieldType)) {
writer.WriteStartElement(fieldInfo.Name);
object value = fieldInfo.GetValue(instance);
if((value != null) && !value.Equals(string.Empty)) {
writer.WriteValue(fieldInfo.GetValue(instance));
}
writer.WriteEndElement();
}
}
// Write complex fields after the simple ones
for(int index = 0; index < fieldInfos.Length; ++index) {
FieldInfo fieldInfo = fieldInfos[index];
if(!isSimpleType(fieldInfo.FieldType)) {
writer.WriteStartElement(fieldInfo.Name);
writeFields(fieldInfo.GetValue(instance), writer);
writer.WriteEndElement();
}
}
}
/// Determines whether a type is simple (i.e. not a complex type)
/// Type that will be checked
/// True if the type is a simple type
///
/// Complex types are usually understood to be types consisting of other types.
/// Thus we labelled non-complex types "simple types," which are all of .NET's
/// primitive types plus strings.
///
private static bool isSimpleType(Type type) {
return
type.IsPrimitive ||
(type == typeof(string)) ||
(type == typeof(DateTime)) ||
(type == typeof(TimeSpan));
}
/// Default settings for the XML reader
private static XmlReaderSettings ReaderSettings {
get {
return new XmlReaderSettings() {
IgnoreComments = true,
CloseInput = true
};
}
}
/// Default settings for the XML writer
private static XmlWriterSettings WriterSettings {
get {
return new XmlWriterSettings() {
Encoding = Encoding.UTF8,
Indent = true,
NewLineOnAttributes = true,
NewLineHandling = NewLineHandling.Replace,
CloseOutput = true
};
}
}
}
} // namespace Framework.Storage