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