/// <summary> /// Deserializes properties of the given object from the node. /// </summary> /// <param name="obj"></param> /// <param name="node"></param> public static void DeserializePropertiesFromXML(object obj, XmlNode node) { DeserializeIgnoreAttribute[] ignoreAttributes = (DeserializeIgnoreAttribute[])obj.GetType().GetCustomAttributes(typeof(DeserializeIgnoreAttribute), true); foreach (XmlNode child in node.ChildNodes) { string elementValue = child.InnerText; PropertyInfo prop = obj.GetType().GetProperty(child.Name); if (prop == null) { if (ignoreAttributes.Length == 0 || !Array.Exists(ignoreAttributes, DeserializeIgnoreAttribute.MatchesPropertyName(child.Name))) { throw new InvalidDataException("The property '" + child.Name + "' could not be read. This game may require a newer version of AGS."); } continue; } // Process any existing value conversions; this helps to upgrade game from older version DeserializeConvertValueAttribute[] conversions = (DeserializeConvertValueAttribute[])prop.PropertyType.GetCustomAttributes(typeof(DeserializeConvertValueAttribute), true); if (conversions.Length > 0) { foreach (DeserializeConvertValueAttribute conversion in conversions) { elementValue = conversion.Convert(elementValue); } } if (!prop.CanWrite) { // do nothing, read-only } else if (child.Attributes.GetNamedItem("IsNull") != null) { prop.SetValue(obj, null, null); } else if (prop.PropertyType == typeof(Boolean)) { prop.SetValue(obj, Convert.ToBoolean(elementValue), null); } else if (prop.PropertyType == typeof(int)) { prop.SetValue(obj, Convert.ToInt32(elementValue), null); } else if (prop.PropertyType == typeof(short)) { prop.SetValue(obj, Convert.ToInt16(elementValue), null); } // We must use InvariantCulture for floats and doubles, because their // format depends on local system settings used when the project was saved else if (prop.PropertyType == typeof(float)) { prop.SetValue(obj, Single.Parse(elementValue, CultureInfo.InvariantCulture), null); } else if (prop.PropertyType == typeof(double)) { prop.SetValue(obj, Double.Parse(elementValue, CultureInfo.InvariantCulture), null); } else if (prop.PropertyType == typeof(string)) { prop.SetValue(obj, elementValue, null); } else if (prop.PropertyType == typeof(DateTime)) { // Must use CultureInfo.InvariantCulture otherwise DateTime.Parse // crashes if the system regional settings short date format has // spaces in it (.NET bug) DateTime dateTime = DateTime.MinValue; if (DateTime.TryParseExact(elementValue, "u", CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) { // Get and set audio files time stamps prop.SetValue(obj, dateTime, null); } else { // Release Date timestamp doesn't store time of the day, and as such it ends up in the else-statement prop.SetValue(obj, DateTime.Parse(elementValue, CultureInfo.InvariantCulture), null); } } else if (prop.PropertyType.IsEnum) { prop.SetValue(obj, Enum.Parse(prop.PropertyType, elementValue), null); } else if (prop.PropertyType.IsClass) { ConstructorInfo constructor = prop.PropertyType.GetConstructor(new Type[] { typeof(XmlNode) }); prop.SetValue(obj, constructor.Invoke(new object[] { child }), null); } // For compatibility with various Custom Resolution beta builds // TODO: find a generic solution for doing a conversions like this without // using hard-coded property name (some serialization attribute perhaps) else if (prop.PropertyType == typeof(Size) && prop.Name == "CustomResolution") { prop.SetValue(obj, CompatStringToResolution(elementValue), null); } else { throw new InvalidDataException("Unknown data type: " + prop.PropertyType.Name); } } }