// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Purpose: // : // Chart serializer allows persisting of all chart data and // settings into the stream or file using XML or binary format. // This data can be later loaded back into the chart completely // restoring its state. Serialize can also be used to reset chart // control state to its default values. // Both XML and Binary serialization methods use reflection to // discover class properties which need to be serialized. Only // properties with non-default values are persisted. Full Trust // is required to use chartserialization. // SerializeBase class implements all the chart serializer // properties and methods to reset chart content. XmlFormatSerializer // and BinaryFormatSerializer classes derive from the SerializeBase // class and provide saving and loading functionality for XML and // binary format. // By default, all chart content is Saved, Loaded or Reset, but // this can be changed using serializer Content, SerializableContent // and NonSerializableContent properties. Content property allows a // simple way to serialize everything, appearance or just chart data. // SerializableContent and NonSerializableContent properties provide // more control over what is beign persisted and they override the // Content property settings. Each of the properties is a string // which is a comma-separated listing of all chart properties to be // serialized. The syntax of this property is "Class.Property[,Class.Property]", // and wildcards may be used (represented by an asterisk). For example, // to serialize all chart BackColor properties set this property to // "*.BackColor". // using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; using System.Globalization; using System.IO; using System.Reflection; using System.Security; using System.Text; using System.Xml; #if NETSTANDARD || !NETCOREWIN using FastReport.TypeConverters; using FontConverter = FastReport.TypeConverters.FontConverter; #endif namespace FastReport.DataVisualization.Charting.Utilities { using Size = System.Drawing.Size; #region Serialization enumerations /// /// Enumeration which describes how to persist property during the serialization /// internal enum SerializationVisibility { /// /// Do not serialize /// Hidden, /// /// Serialize as XML attribute /// Attribute, /// /// Serialize as XML element /// Element } /// /// Determines chart current serialization status. /// internal enum SerializationStatus { /// /// Chart is not serializing /// None, /// /// Chart is loading /// Loading, /// /// Chart is saving /// Saving, /// /// Chart is resetting /// Resetting } #endregion /// /// Attribute which describes how to persist property during the serialization. /// [AttributeUsage(AttributeTargets.All)] internal sealed class SerializationVisibilityAttribute : System.Attribute { #region Fields // Visibility style private SerializationVisibility _visibility = SerializationVisibility.Attribute; #endregion #region Constructor /// /// Public constructor /// /// Serialization visibility. internal SerializationVisibilityAttribute(SerializationVisibility visibility) { this._visibility = visibility; } #endregion #region Properties /// /// Serialization visibility property /// public SerializationVisibility Visibility { get { return _visibility; } //set //{ // _visibility = value; //} } #endregion } /// /// Base class of the serializers. Common properties and methods for all serializers. /// internal abstract class SerializerBase { #region Fields /// /// Indicates that unknown properties and elements are ignored /// private bool _isUnknownAttributeIgnored = false; /// /// Indicates that serializer works in template creation mode /// private bool _isTemplateMode = false; /// /// Indicates that object properties are reset before loading /// private bool _isResetWhenLoading = true; /// /// Comma separated list of serializable (Save/Load/Reset) properties. "ClassName.PropertyName" /// private string _serializableContent = ""; /// /// Comma separated list of NON serializable (Save/Load/Reset) properties. "ClassName.PropertyName" /// private string _nonSerializableContent = ""; /// /// Font converters used while serializing/deserializing /// internal static FontConverter fontConverter = new FontConverter(); /// /// Color converters used while serializing/deserializing /// internal static ColorConverter colorConverter = new ColorConverter(); /// /// Hash code provider. /// protected static StringComparer hashCodeProvider = StringComparer.OrdinalIgnoreCase; /// /// Contains chart specific converters /// HybridDictionary _converterDict = new HybridDictionary(); #endregion #region Public properties /// /// Indicates that unknown properties and elements will be /// ignored without throwing an exception. /// internal bool IsUnknownAttributeIgnored { get { return _isUnknownAttributeIgnored; } set { _isUnknownAttributeIgnored = value; } } /// /// Indicates that serializer works in template creation mode /// internal bool IsTemplateMode { get { return _isTemplateMode; } set { _isTemplateMode = value; } } /// /// Indicates that object properties are reset to default /// values before loading. /// internal bool IsResetWhenLoading { get { return _isResetWhenLoading; } set { _isResetWhenLoading = value; } } /// /// Comma separated list of serializable (Save/Load/Reset) properties. /// "ClassName.PropertyName,[ClassName.PropertyName]". /// internal string SerializableContent { get { return _serializableContent; } set { _serializableContent = value; // Reset list serializableContentList = null; } } /// /// Comma separated list of serializable (Save/Load/Reset) properties. /// "ClassName.PropertyName,[ClassName.PropertyName]". /// internal string NonSerializableContent { get { return _nonSerializableContent; } set { _nonSerializableContent = value; // Reset list nonSerializableContentList = null; } } #endregion #region Resetting methods /// /// Reset properties of the object to default values. /// /// Object to be reset. virtual internal void ResetObjectProperties(object objectToReset) { // Reset object properties ResetObjectProperties(objectToReset, null, GetObjectName(objectToReset)); } /// /// Reset properties of the object to default values. /// Method is called recursively to reset child objects properties. /// /// Object to be reset. /// Parent of the reset object. /// Object element name. virtual internal void ResetObjectProperties(object objectToReset, object parent, string elementName) { // Check input parameters if(objectToReset == null) { return; } IList list = objectToReset as IList; // Check if object is a list if(list != null && IsSerializableContent(elementName, parent)) { // Reset list by clearing all the items list.Clear(); return; } // Retrive properties list of the object PropertyInfo[] properties = objectToReset.GetType().GetProperties(); if(properties != null) { // Loop through all properties and reset public properties foreach(PropertyInfo pi in properties) { // Get property descriptor PropertyDescriptor pd = TypeDescriptor.GetProperties(objectToReset)[pi.Name]; // Check XmlFormatSerializerStyle attribute if(pd != null) { SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if(styleAttribute != null) { // Hidden property if(styleAttribute.Visibility == SerializationVisibility.Hidden) { continue; } } } // Check if this property should be reset bool resetProperty = IsSerializableContent(pi.Name, objectToReset); // Skip inherited properties from the root object if(IsChartBaseProperty(objectToReset, parent, pi)) { continue; } // Reset list if(pi.CanRead && pi.PropertyType.GetInterface("IList", true) != null) { if(resetProperty) { // Check if collection has "Reset" method bool resetComplete = false; MethodInfo mi = objectToReset.GetType().GetMethod("Reset" + pi.Name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if(mi != null) { mi.Invoke(objectToReset, null); resetComplete = true; } // Reset list by clearing all the items if(!resetComplete) { ((IList)pi.GetValue(objectToReset, null)).Clear(); } } else { // Reset objects of the list foreach(object listObject in ((IList)pi.GetValue(objectToReset, null))) { ResetObjectProperties(listObject, objectToReset, this.GetObjectName(listObject)); } } } // Reset public properties with Get and Set methods else if(pi.CanRead && pi.CanWrite) { // Skip indexes if(pi.Name == "Item") { continue; } // Skip Names if (pi.Name == "Name") { continue; } // Reset inner properies if(ShouldSerializeAsAttribute(pi, objectToReset)) { if(resetProperty) { // Reset the property using property descriptor if(pd != null) { // Get property object object objectProperty = pi.GetValue(objectToReset, null); // Get default value of the property DefaultValueAttribute defValueAttribute = (DefaultValueAttribute)pd.Attributes[typeof(DefaultValueAttribute)]; if(defValueAttribute != null) { if(objectProperty == null) { if(defValueAttribute.Value != null) { pd.SetValue(objectToReset, defValueAttribute.Value); } } else if(! objectProperty.Equals(defValueAttribute.Value)) { pd.SetValue(objectToReset, defValueAttribute.Value); } } else { // Check if property has "Reset" method MethodInfo mi = objectToReset.GetType().GetMethod("Reset" + pi.Name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if(mi != null) { mi.Invoke(objectToReset, null); } } } } } else { // Reset inner object ResetObjectProperties(pi.GetValue(objectToReset, null), objectToReset, pi.Name); } } } } return; } #endregion #region Abstract Serialization/Deserialization methods /// /// Serialize specified object into the destination object. /// /// Object to be serialized. /// Destination of the serialization. internal abstract void Serialize(object objectToSerialize, object destination); /// /// Deserialize specified object from the source object. /// /// Object to be deserialized. /// Source of the deserialization. internal abstract void Deserialize(object objectToDeserialize, object source); #endregion #region Protected helper methods /// /// Converts specified font object into a string. /// /// Font object to convert. /// String that contains font data. internal static string FontToString(Font font) { // Save basic properties persisted by font converter string fontData = (string)SerializerBase.fontConverter.ConvertToInvariantString(font); // Persist properties not serialiazed by the converter if(font.GdiCharSet != 1) { fontData += ", GdiCharSet=" + font.GdiCharSet.ToString(System.Globalization.CultureInfo.InvariantCulture); } if(font.GdiVerticalFont) { fontData += ", GdiVerticalFont"; } return fontData; } /// /// Converts string data into a font object. /// /// String with font data. /// Newly created font object. internal static Font FontFromString(string fontString) { // Check if string contains non-standard values "GdiCharSet" or "GdiVerticalFont" string standardData = fontString; byte gdiCharSet = 1; bool gdiVerticalFont = false; int charIndex = fontString.IndexOf(", GdiCharSet=", StringComparison.Ordinal); if(charIndex >= 0) { // Read value string val = fontString.Substring(charIndex + 13); int commaIndex = val.IndexOf(",", StringComparison.Ordinal); if(commaIndex >= 0) { val = val.Substring(0, commaIndex); } gdiCharSet = (byte)Int32.Parse(val, System.Globalization.CultureInfo.InvariantCulture); // Truncate standard data string if(standardData.Length > charIndex) { standardData = standardData.Substring(0, charIndex); } } charIndex = fontString.IndexOf(", GdiVerticalFont", StringComparison.Ordinal); if(charIndex >= 0) { gdiVerticalFont = true; // Truncate standard data string if(standardData.Length > charIndex) { standardData = standardData.Substring(0, charIndex); } } // Create Font object from standard parameters Font font = (Font)SerializerBase.fontConverter.ConvertFromInvariantString(standardData); // check if non-standard parameters provided if(gdiVerticalFont || gdiCharSet != 1) { Font newFont = new Font( font.Name, font.SizeInPoints, font.Style, GraphicsUnit.Point, gdiCharSet, gdiVerticalFont); font.Dispose(); return newFont; } return font; } /// /// Returns a hash code of a specified string. /// /// String to get the hash code for. /// String hash code. internal static short GetStringHashCode(string str) { return (short)(hashCodeProvider.GetHashCode(str) + str.Length * 2); } /// /// Reads hash ID from the specified binary reader. /// /// Binary reader to get the data from. /// Property name or collection member type ID. internal Int16 ReadHashID(BinaryReader reader) { // For later versions return ID without transformations return reader.ReadInt16(); } /// /// Checks if property belongs to the base class of the chart "Control". /// /// Serializable object. /// Object parent. /// Serializable property information. /// True if property belongs to the base class. internal bool IsChartBaseProperty(object objectToSerialize, object parent, PropertyInfo pi) { bool result = false; // Check only for the root object if(parent == null) { Type currentType = objectToSerialize.GetType(); while(currentType != null) { if(pi.DeclaringType == currentType) { result = false; break; } // Check if it's a chart class if( currentType == typeof(Chart)) { result = true; break; } // Get base class type currentType = currentType.BaseType; } } return result; } /// /// Converts Image object into the BASE64 encoded string /// /// Image to convert. /// BASE64 encoded image data. internal static string ImageToString(System.Drawing.Image image) { // Save image into the stream using BASE64 encoding MemoryStream imageStream = new MemoryStream(); image.Save(imageStream, ImageFormat.Png); imageStream.Seek(0, SeekOrigin.Begin); // Create XmlTextWriter and save image in BASE64 StringBuilder stringBuilder = new StringBuilder(); XmlTextWriter textWriter = new XmlTextWriter(new StringWriter(stringBuilder, CultureInfo.InvariantCulture)); byte[] imageByteData = imageStream.ToArray(); textWriter.WriteBase64(imageByteData, 0, imageByteData.Length); // Close image stream textWriter.Close(); imageStream.Close(); return stringBuilder.ToString(); } /// /// Converts BASE64 encoded string to image. /// /// BASE64 encoded data. /// Image. internal static System.Drawing.Image ImageFromString(string data) { // Create XML text reader byte[] buffer = new byte[1000]; MemoryStream imageStream = new MemoryStream(); XmlTextReader textReader = new XmlTextReader(new StringReader("" + data + "")) { DtdProcessing = DtdProcessing.Ignore }; // Read tags and BASE64 encoded data textReader.Read(); int bytesRead = 0; while((bytesRead = textReader.ReadBase64(buffer, 0, 1000)) > 0) { imageStream.Write(buffer, 0, bytesRead); } textReader.Read(); // Create image from stream imageStream.Seek(0, SeekOrigin.Begin); System.Drawing.Image tempImage = System.Drawing.Image.FromStream(imageStream); System.Drawing.Bitmap image = new Bitmap(tempImage); // !!! .Net bug when image source stream is closed - can create brush using the image image.SetResolution(tempImage.HorizontalResolution, tempImage.VerticalResolution); //The bitmap created using the constructor does not copy the resolution of the image // Close image stream textReader.Close(); imageStream.Close(); return image; } /// /// Get the name of the object class /// /// Object to get the name of. /// Name of the object class (without namespace). internal string GetObjectName(object obj) { string name = obj.GetType().ToString(); return name.Substring(name.LastIndexOf('.') + 1); } /// /// Create new empty item for the list. /// AxisName of the objects is determined by the return type of the indexer. /// /// List used to detect type of the item objects. /// Name of collection type. /// Optional item name to return. /// Indicates that object with specified name was already in the collection and it being reused. /// New list item object. internal object GetListNewItem(IList list, string itemTypeName, ref string itemName, ref bool reusedObject) { // Get type of item in collection Type itemType = null; if(itemTypeName.Length > 0) { itemType = Type.GetType(typeof(Chart).Namespace + "." + itemTypeName, false, true); } reusedObject = false; PropertyInfo pi = list.GetType().GetProperty("Item", itemType, new Type[] {typeof(string)} ); MethodInfo mi = list.GetType().GetMethod("IndexOf", new Type[] { typeof(String) }); ConstructorInfo ci = null; if(pi != null) { // Try to get object by name using the indexer if(itemName != null && itemName.Length > 0) { bool itemChecked = false; if (mi != null) { try { int index = -1; object oindex = mi.Invoke(list, new object[] { itemName }); if (oindex is int) { index = (int)oindex; itemChecked = true; } if (index != -1) { object objByName = list[index]; if (objByName != null) { // Remove found object from the list list.Remove(objByName); // Return found object reusedObject = true; return objByName; } } } catch (ArgumentException) { } catch (TargetException) { } catch (TargetInvocationException) { } } if (!itemChecked) { object objByName = null; try { objByName = pi.GetValue(list, new object[] { itemName }); } catch (ArgumentException) { objByName = null; } catch (TargetException) { objByName = null; } catch (TargetInvocationException) { objByName = null; } if (objByName != null) { try { // Remove found object from the list list.Remove(objByName); } catch (NotSupportedException) { } // Return found object reusedObject = true; return objByName; } } itemName = null; } } // Get the constructor of the type returned by indexer if (itemType != null) { ci = itemType.GetConstructor(Type.EmptyTypes); } else { ci = pi.PropertyType.GetConstructor(Type.EmptyTypes); } if (ci == null) { throw (new InvalidOperationException(SR.ExceptionChartSerializerDefaultConstructorUndefined(pi.PropertyType.ToString()))); } return ci.Invoke(null); } /// /// Returns true if the object property should be serialized as /// parent element attribute. Otherwise as a child element. /// /// Property information. /// Object that the property belongs to. /// True if property should be serialized as attribute. internal bool ShouldSerializeAsAttribute(PropertyInfo pi, object parent) { // Check if SerializationVisibilityAttribute is set if(parent != null) { PropertyDescriptor pd = TypeDescriptor.GetProperties(parent)[pi.Name]; if(pd != null) { SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if(styleAttribute != null) { if(styleAttribute.Visibility == SerializationVisibility.Attribute) { return true; } else if(styleAttribute.Visibility == SerializationVisibility.Element) { return false; } } } } // If a simple type - serialize as property if(!pi.PropertyType.IsClass) { return true; } // Some classes are serialized as properties if(pi.PropertyType == typeof(string) || pi.PropertyType == typeof(Font) || pi.PropertyType == typeof(Color) || pi.PropertyType == typeof(System.Drawing.Image)) { return true; } return false; } /// /// Determines if this property should be serialized as attribute /// /// Property information. /// Object that the property belongs to. /// True if should be serialized as attribute internal bool SerializeICollAsAtribute(PropertyInfo pi, object objectToSerialize) { if (objectToSerialize != null) { PropertyDescriptor pd = TypeDescriptor.GetProperties(objectToSerialize)[pi.Name]; if (pd != null) { SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if (styleAttribute != null) { if (styleAttribute.Visibility == SerializationVisibility.Attribute) { return true; } } } } return false; } /// /// Returns true if the object property is serializable. /// /// Property name. /// Object that the property belongs to. /// True if property is serializable. internal bool IsSerializableContent(string propertyName, object parent) { bool serializable = true; if(_serializableContent.Length > 0 || _nonSerializableContent.Length > 0) { int serialzableClassFitType = 0; // 0 - undefined; 1 - '*'; 2 - 'Back*'; 3 - Exact int serialzablePropertyFitType = 0; // 0 - undefined; 1 - '*'; 2 - 'Back*'; 3 - Exact string ownerClassName = GetObjectName(parent); // Check if property in this class is part of the serializable content serializable = IsPropertyInList(GetSerializableContentList(), ownerClassName, propertyName, out serialzableClassFitType, out serialzablePropertyFitType); // Check if property in this class is part of the NON serializable content if(serializable) { int nonSerialzableClassFitType = 0; // 0 - undefined; 1 - '*'; 2 - 'Back*'; 3 - Exact int nonSerialzablePropertyFitType = 0; // 0 - undefined; 1 - '*'; 2 - 'Back*'; 3 - Exact bool nonSerializable = IsPropertyInList(GetNonSerializableContentList(), ownerClassName, propertyName, out nonSerialzableClassFitType, out nonSerialzablePropertyFitType); // If property was found in non serializable content list - check the type priority // Priority order: Exact match, 'Back*' mask match, '*' all mask match if(nonSerializable) { // Check priority if((nonSerialzableClassFitType + nonSerialzablePropertyFitType) > (serialzableClassFitType + serialzablePropertyFitType)) { serializable = false; } } } } return serializable; } /// /// Checks if property belongs is defined in the mask list. /// /// Array list of class/property items. /// Class name. /// Property name. /// Return class mask fit type. /// Return property mask fit type. /// True if property was found in the list. private bool IsPropertyInList(ArrayList contentList, string className, string propertyName, out int classFitType, out int propertyFitType) { // Initialize result values classFitType = 0; propertyFitType = 0; if(contentList != null) { // Loop through all items in the list using step 2 for(int itemIndex = 0; itemIndex < contentList.Count; itemIndex += 2) { // Initialize result values classFitType = 0; propertyFitType = 0; // Check if object class and property name match the mask if(NameMatchMask((ItemInfo)contentList[itemIndex], className, out classFitType)) { if(NameMatchMask((ItemInfo)contentList[itemIndex + 1], propertyName, out propertyFitType)) { return true; } } } } return false; } /// /// Compares class/property name with the specified mask /// /// Class/Property item information. /// Class/Property name. /// AxisName of matching. 0-No Match; 1-'*' any wild card; 2-'Back*' contain wild card; 3-exact match /// True if name match the mask. private bool NameMatchMask(ItemInfo itemInfo, string objectName, out int type) { // Initialize type type = 0; // Any class mask if(itemInfo.any) { type = 1; return true; } // Ends with class mask if(itemInfo.endsWith) { if(itemInfo.name.Length <= objectName.Length) { if(objectName.Substring(0, itemInfo.name.Length) == itemInfo.name) { type = 2; return true; } } } // Starts with class mask if(itemInfo.startsWith) { if(itemInfo.name.Length <= objectName.Length) { if(objectName.Substring(objectName.Length - itemInfo.name.Length, itemInfo.name.Length) == itemInfo.name) { type = 2; return true; } } } // Exact name is specified if(itemInfo.name == objectName) { type = 3; return true; } return false; } /// /// Finds a converter by property descriptor. /// /// Property descriptor. /// A converter registered in TypeConverterAttribute or by property type internal TypeConverter FindConverter(PropertyDescriptor pd) { TypeConverter result; TypeConverterAttribute typeConverterAttrib = (TypeConverterAttribute)pd.Attributes[typeof(TypeConverterAttribute)]; if (typeConverterAttrib != null && typeConverterAttrib.ConverterTypeName.Length > 0) { result = this.FindConverterByType(typeConverterAttrib); if (result != null) { return result; } try { return pd.Converter; } catch (SecurityException) { } catch (MethodAccessException) { } } return TypeDescriptor.GetConverter(pd.PropertyType); } /// /// Finds a converter by TypeConverterAttribute. /// /// TypeConverterAttribute. /// TypeConvetrer or null internal TypeConverter FindConverterByType( TypeConverterAttribute attr) { // In default Inranet zone (partial trust) ConsrtuctorInfo.Invoke (PropertyDescriptor.Converter) // throws SecurityException or MethodAccessException when the converter class is internal. // Thats why we have this giant if - elseif here - to create type converters whitout reflection. if (_converterDict.Contains(attr.ConverterTypeName)) { return (TypeConverter)_converterDict[attr.ConverterTypeName]; } String typeStr = attr.ConverterTypeName; if (attr.ConverterTypeName.Contains(",") ) { typeStr = attr.ConverterTypeName.Split(',')[0]; } TypeConverter result = null; if (typeStr.EndsWith(".CustomPropertiesTypeConverter", StringComparison.OrdinalIgnoreCase)) { result = new CustomPropertiesTypeConverter(); } else if (typeStr.EndsWith(".DoubleNanValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new DoubleNanValueConverter(); } else if (typeStr.EndsWith(".DoubleDateNanValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new DoubleDateNanValueConverter(); } else if (typeStr.EndsWith(".ElementPositionConverter", StringComparison.OrdinalIgnoreCase)) { result = new ElementPositionConverter(); } else if (typeStr.EndsWith(".SeriesAreaNameConverter", StringComparison.OrdinalIgnoreCase)) { result = new SeriesAreaNameConverter(); } else if (typeStr.EndsWith(".ChartDataSourceConverter", StringComparison.OrdinalIgnoreCase)) { result = new ChartDataSourceConverter(); } else if (typeStr.EndsWith(".SeriesDataSourceMemberConverter", StringComparison.OrdinalIgnoreCase)) { result = new SeriesDataSourceMemberConverter(); } else if (typeStr.EndsWith(".SeriesLegendNameConverter", StringComparison.OrdinalIgnoreCase)) { result = new SeriesLegendNameConverter(); } else if (typeStr.EndsWith(".ChartTypeConverter", StringComparison.OrdinalIgnoreCase)) { result = new ChartTypeConverter(); } else if (typeStr.EndsWith(".SeriesNameConverter", StringComparison.OrdinalIgnoreCase)) { result = new SeriesNameConverter(); } else if (typeStr.EndsWith(".NoNameExpandableObjectConverter", StringComparison.OrdinalIgnoreCase)) { result = new NoNameExpandableObjectConverter(); } else if (typeStr.EndsWith(".DoubleArrayConverter", StringComparison.OrdinalIgnoreCase)) { result = new DoubleArrayConverter(); } else if (typeStr.EndsWith(".DataPointValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new DataPointValueConverter(); } else if (typeStr.EndsWith(".SeriesYValueTypeConverter", StringComparison.OrdinalIgnoreCase)) { result = new SeriesYValueTypeConverter(typeof(ChartValueType)); } else if (typeStr.EndsWith(".ColorArrayConverter", StringComparison.OrdinalIgnoreCase)) { result = new ColorArrayConverter(); } else if (typeStr.EndsWith(".LegendAreaNameConverter", StringComparison.OrdinalIgnoreCase)) { result = new LegendAreaNameConverter(); } else if (typeStr.EndsWith(".LegendConverter", StringComparison.OrdinalIgnoreCase)) { result = new LegendConverter(); } else if (typeStr.EndsWith(".SizeEmptyValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new SizeEmptyValueConverter(); } else if (typeStr.EndsWith(".MarginExpandableObjectConverter", StringComparison.OrdinalIgnoreCase)) { result = new MarginExpandableObjectConverter(); } else if (typeStr.EndsWith(".IntNanValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new IntNanValueConverter(); } else if (typeStr.EndsWith(".AxesArrayConverter", StringComparison.OrdinalIgnoreCase)) { result = new AxesArrayConverter(); } else if (typeStr.EndsWith(".AxisLabelDateValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new AxisLabelDateValueConverter(); } else if (typeStr.EndsWith(".AxisMinMaxValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new AxisMinMaxValueConverter(); } else if (typeStr.EndsWith(".AxisCrossingValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new AxisCrossingValueConverter(); } else if (typeStr.EndsWith(".AxisMinMaxAutoValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new AxisMinMaxAutoValueConverter(); } else if (typeStr.EndsWith(".StripLineTitleAngleConverter", StringComparison.OrdinalIgnoreCase)) { result = new StripLineTitleAngleConverter(); } else if (typeStr.EndsWith(".AxisIntervalValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new AxisIntervalValueConverter(); } else if (typeStr.EndsWith(".AxisElementIntervalValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new AxisElementIntervalValueConverter(); } else if (typeStr.EndsWith(".AnchorPointValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new AnchorPointValueConverter(); } else if (typeStr.EndsWith(".AnnotationAxisValueConverter", StringComparison.OrdinalIgnoreCase)) { result = new AnnotationAxisValueConverter(); } if (result != null) _converterDict[attr.ConverterTypeName] = result; return result; } #endregion #region Serializable content list managment fields, methods and classes /// /// Stores information about content item (class or property) /// private class ItemInfo { public string name = ""; public bool any = false; public bool startsWith = false; public bool endsWith = false; } // Storage for serializable content items private ArrayList serializableContentList = null; // Storage for non serializable content items private ArrayList nonSerializableContentList = null; /// /// Return serializable content list. /// /// Serializable content list. private ArrayList GetSerializableContentList() { if(serializableContentList == null) { serializableContentList = new ArrayList(); FillContentList( serializableContentList, (this.SerializableContent.Length > 0 ) ? this.SerializableContent : "*.*"); } return serializableContentList; } /// /// Return non serializable content list. /// /// Non serializable content list. private ArrayList GetNonSerializableContentList() { if(nonSerializableContentList == null) { nonSerializableContentList = new ArrayList(); FillContentList(nonSerializableContentList, this.NonSerializableContent); } return nonSerializableContentList; } /// /// Fill content list from the string. /// /// Array list class. /// Content string. private void FillContentList(ArrayList list, string content) { if(content.Length > 0) { string[] classPropertyPairs = content.Split(','); foreach(string item in classPropertyPairs) { // Create two content items: one for the class and one for the property ItemInfo classInfo = new ItemInfo(); ItemInfo propertyInfo = new ItemInfo(); // Find class and property name int pointIndex = item.IndexOf('.'); if(pointIndex == -1) { throw (new ArgumentException(SR.ExceptionChartSerializerContentStringFormatInvalid)); } classInfo.name = item.Substring(0, pointIndex).Trim(); propertyInfo.name = item.Substring(pointIndex + 1).Trim(); if(classInfo.name.Length == 0) { throw (new ArgumentException(SR.ExceptionChartSerializerClassNameUndefined)); } if(propertyInfo.name.Length == 0) { throw (new ArgumentException(SR.ExceptionChartSerializerPropertyNameUndefined)); } // Make sure property name do not have point character if(propertyInfo.name.IndexOf('.') != -1) { throw (new ArgumentException(SR.ExceptionChartSerializerContentStringFormatInvalid)); } // Check for wildcards in names CheckWildCars(classInfo); CheckWildCars(propertyInfo); // Add class & property items into the array list.Add(classInfo); list.Add(propertyInfo); } } } /// /// Checks wildcards in the name of the item. /// Possible values: /// "*" /// "*Name" /// "Name*" /// /// Item information class. private void CheckWildCars(ItemInfo info) { // Any class mask if(info.name == "*") { info.any = true; } // Ends with class mask else if(info.name[info.name.Length - 1] == '*') { info.endsWith = true; info.name = info.name.TrimEnd('*'); } // Starts with class mask else if(info.name[0] == '*') { info.startsWith = true; info.name = info.name.TrimStart('*'); } } #endregion } /// /// Utility class which serialize object using XML format /// internal class XmlFormatSerializer : SerializerBase { #region Serialization public methods /// /// Serialize specified object into the stream. /// /// Object to be serialized. /// The stream used to write the XML document. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void Serialize(object objectToSerialize, Stream stream) { Serialize(objectToSerialize, (object)stream); } /// /// Serialize specified object into the XML writer. /// /// Object to be serialized. /// The XmlWriter used to write the XML document. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void Serialize(object objectToSerialize, XmlWriter xmlWriter) { Serialize(objectToSerialize, (object)xmlWriter); } /// /// Serialize specified object into the text writer. /// /// Object to be serialized. /// The TextWriter used to write the XML document. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void Serialize(object objectToSerialize, TextWriter textWriter) { Serialize(objectToSerialize, (object)textWriter); } /// /// Serialize specified object into the file. /// /// Object to be serialized. /// The file name used to write the XML document. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void Serialize(object objectToSerialize, string fileName) { Serialize(objectToSerialize, (object)fileName); } #endregion #region Serialization private methods /// /// Serialize specified object into different types of writers using XML format. /// Here is what is serialized in the object: /// - all public properties with Set and Get methods /// - all public properties with Get method which derived from ICollection /// /// Object to be serialized. /// Defines the serialization destination. Can be Stream, TextWriter, XmlWriter or String (file name). internal override void Serialize(object objectToSerialize, object writer) { // the possible writer types Stream stream = writer as Stream; TextWriter textWriter = writer as TextWriter; XmlWriter xmlWriter = writer as XmlWriter; string writerStr = writer as string; // Check input parameters if(objectToSerialize == null) { throw(new ArgumentNullException("objectToSerialize")); } if(writer == null) { throw(new ArgumentNullException("writer")); } if(stream == null && textWriter == null && xmlWriter == null && writerStr == null) { throw (new ArgumentException(SR.ExceptionChartSerializerWriterObjectInvalid, "writer")); } // Create XML document XmlDocument xmlDocument = new XmlDocument(); // Create document fragment XmlDocumentFragment docFragment = xmlDocument.CreateDocumentFragment(); // Serialize object SerializeObject(objectToSerialize, null, GetObjectName(objectToSerialize), docFragment, xmlDocument); // Append document fragment xmlDocument.AppendChild(docFragment); // Remove empty child nodes RemoveEmptyChildNodes(xmlDocument); // Save XML document into the writer if(stream != null) { xmlDocument.Save(stream); // Flush stream and seek to the beginning stream.Flush(); stream.Seek(0, SeekOrigin.Begin); } if(writerStr != null) { xmlDocument.Save(writerStr); } if(xmlWriter != null) { xmlDocument.Save(xmlWriter); } if(textWriter != null) { xmlDocument.Save(textWriter); } } /// /// Serialize specified object into the XML format. /// Method is called recursively to serialize child objects. /// /// Object to be serialized. /// Parent of the serialized object. /// Object element name. /// The XmlNode of the parent object to serialize the data in. /// The XmlDocument the parent node belongs to. virtual protected void SerializeObject(object objectToSerialize, object parent, string elementName, XmlNode xmlParentNode, XmlDocument xmlDocument) { // Check input parameters if(objectToSerialize == null) { return; } // Check if object should be serialized if(parent != null) { PropertyDescriptor pd = TypeDescriptor.GetProperties(parent)[elementName]; if(pd != null) { SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if(styleAttribute != null) { // Hidden property if(styleAttribute.Visibility == SerializationVisibility.Hidden) { return; } } } } // Check if object is a collection if(objectToSerialize is ICollection) { // Serialize collection SerializeCollection(objectToSerialize, elementName, xmlParentNode, xmlDocument); return; } // Create object element inside the parents node XmlNode xmlNode = xmlDocument.CreateElement(elementName); xmlParentNode.AppendChild(xmlNode); // Write template data into collection items bool templateListItem = false; IList parentList = parent as IList; if(this.IsTemplateMode && parentList != null) { // Create "_Template_" attribute XmlAttribute attrib = xmlDocument.CreateAttribute("_Template_"); // Check number of items in collection if (parentList.Count == 1) { // If only one iten in collection, set "All" value. // This means that style of this object should be applied to all // existing items of the collection. attrib.Value = "All"; } else { // If there is more than one item, use it's index. // When loading, style of these items will be applied to existing // items in collection in the loop. int itemIndex = parentList.IndexOf(objectToSerialize); attrib.Value = itemIndex.ToString(CultureInfo.InvariantCulture); } // Add "_Template_" attribute into the XML node xmlNode.Attributes.Append(attrib); templateListItem = true; } // Retrive properties list of the object PropertyInfo[] properties = objectToSerialize.GetType().GetProperties(); if (properties != null) { // Loop through all properties and serialize public properties foreach(PropertyInfo pi in properties) { // Skip "Name" property from collection items in template mode if(templateListItem && pi.Name == "Name") { continue; } // Skip inherited properties from the root object if(IsChartBaseProperty(objectToSerialize, parent, pi)) { continue; } // Check if this property is serializable content if (!IsSerializableContent(pi.Name, objectToSerialize)) { continue; } // Serialize collection if (pi.CanRead && pi.PropertyType.GetInterface("ICollection", true) != null && !this.SerializeICollAsAtribute(pi, objectToSerialize)) { // Check if SerializationVisibilityAttribute is set bool serialize = true; if(objectToSerialize != null) { PropertyDescriptor pd = TypeDescriptor.GetProperties(objectToSerialize)[pi.Name]; if(pd != null) { SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if(styleAttribute != null) { if(styleAttribute.Visibility == SerializationVisibility.Hidden) { serialize = false; } } } } // Check if collection has "ShouldSerialize" method MethodInfo mi = objectToSerialize.GetType().GetMethod("ShouldSerialize" + pi.Name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public ); if(mi != null) { object result = mi.Invoke(objectToSerialize, null); if(result is bool && ((bool)result) == false) { // Do not serialize collection serialize = false; } } // Serialize collection if(serialize) { SerializeCollection(pi.GetValue(objectToSerialize, null), pi.Name, xmlNode, xmlDocument); } } // Serialize public properties with Get and Set methods else if(pi.CanRead && pi.CanWrite) { // Skip indexes if(pi.Name == "Item") { continue; } // Check if an object should be serialized as a property or as a class if(ShouldSerializeAsAttribute(pi, objectToSerialize)) { // Serialize property SerializeProperty(pi.GetValue(objectToSerialize, null), objectToSerialize, pi.Name, xmlNode, xmlDocument); } else { // Serialize inner object SerializeObject(pi.GetValue(objectToSerialize, null), objectToSerialize, pi.Name, xmlNode, xmlDocument); } } } } return; } /// /// Serializes the data point. /// /// The object to serialize. /// The XML parent node. /// The XML document. internal void SerializeDataPoint(object objectToSerialize, XmlNode xmlParentNode, XmlDocument xmlDocument) { // Create object element inside the parents node XmlNode xmlNode = xmlDocument.CreateElement(GetObjectName(objectToSerialize)); xmlParentNode.AppendChild(xmlNode); DataPoint dataPoint = objectToSerialize as DataPoint; if (dataPoint.XValue != 0d && IsSerializableContent("XValue", objectToSerialize)) { XmlAttribute attrib = xmlDocument.CreateAttribute("XValue"); attrib.Value = GetXmlValue(dataPoint.XValue, dataPoint, "XValue"); xmlNode.Attributes.Append(attrib); } if (dataPoint.YValues.Length > 0 && IsSerializableContent("YValues", objectToSerialize)) { XmlAttribute attrib = xmlDocument.CreateAttribute("YValues"); attrib.Value = GetXmlValue(dataPoint.YValues, dataPoint, "YValues"); xmlNode.Attributes.Append(attrib); } if (dataPoint.IsEmpty && IsSerializableContent("IsEmpty", objectToSerialize)) { XmlAttribute attrib = xmlDocument.CreateAttribute("IsEmpty"); attrib.Value = GetXmlValue(dataPoint.isEmptyPoint, dataPoint, "IsEmpty"); xmlNode.Attributes.Append(attrib); } bool hasCustomProperties = false; foreach (DictionaryEntry entry in dataPoint.properties) { if (entry.Key is int) { CommonCustomProperties propertyType = (CommonCustomProperties)((int)entry.Key); String properyName = propertyType.ToString(); if (IsSerializableContent(properyName, objectToSerialize)) { XmlAttribute attrib = xmlDocument.CreateAttribute(properyName); attrib.Value = GetXmlValue(entry.Value, dataPoint, properyName); xmlNode.Attributes.Append(attrib); } } else { hasCustomProperties = true; } } if (hasCustomProperties && !String.IsNullOrEmpty(dataPoint.CustomProperties) && IsSerializableContent("CustomProperties", objectToSerialize)) { XmlAttribute attrib = xmlDocument.CreateAttribute("CustomProperties"); attrib.Value = GetXmlValue(dataPoint.CustomProperties, dataPoint, "CustomProperties"); xmlNode.Attributes.Append(attrib); } } /// /// Serialize specified object into the XML text writer. /// Method is called recursively to serialize child objects. /// /// Object to be serialized. /// Object element name. /// The XmlNode of the parent object to serialize the data in. /// The XmlDocument the parent node belongs to. virtual protected void SerializeCollection(object objectToSerialize, string elementName, XmlNode xmlParentNode, XmlDocument xmlDocument) { ICollection collection = objectToSerialize as ICollection; if(collection != null) { // Create object element inside the parents node XmlNode xmlNode = xmlDocument.CreateElement(elementName); xmlParentNode.AppendChild(xmlNode); // Enumerate through all objects in collection and serialize them foreach(object obj in collection) { if (obj is DataPoint) { SerializeDataPoint(obj, xmlNode, xmlDocument); continue; } SerializeObject(obj, objectToSerialize, GetObjectName(obj), xmlNode, xmlDocument); } } } /// /// Serialize specified object into the XML text writer. /// Method is called recursively to serialize child objects. /// /// Object to be serialized. /// Parent of the serialized object. /// Object element name. /// The XmlNode of the parent object to serialize the data in. /// The XmlDocument the parent node belongs to. virtual protected void SerializeProperty(object objectToSerialize, object parent, string elementName, XmlNode xmlParentNode, XmlDocument xmlDocument) { // Check input parameters if(objectToSerialize == null || parent == null) { return; } // Check if property has non-default value PropertyDescriptor pd = TypeDescriptor.GetProperties(parent)[elementName]; if(pd != null) { DefaultValueAttribute defValueAttribute = (DefaultValueAttribute)pd.Attributes[typeof(DefaultValueAttribute)]; if(defValueAttribute != null) { if(objectToSerialize.Equals(defValueAttribute.Value)) { // Do not serialize properties with default values return; } } else { // Check if property has "ShouldSerialize" method MethodInfo mi = parent.GetType().GetMethod("ShouldSerialize" + elementName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if(mi != null) { object result = mi.Invoke(parent, null); if(result is bool && ((bool)result) == false) { // Do not serialize properties with default values return; } } } // Check XmlFormatSerializerStyle attribute SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if(styleAttribute != null) { // Hidden property if(styleAttribute.Visibility == SerializationVisibility.Hidden) { return; } } } // Serialize property as a parents node attribute XmlAttribute attrib = xmlDocument.CreateAttribute(elementName); attrib.Value = GetXmlValue(objectToSerialize, parent, elementName); xmlParentNode.Attributes.Append(attrib); } /// /// Converts object value into the string. /// /// Object to convert. /// Object parent. /// Object name. /// Object value as strig. protected string GetXmlValue(object obj, object parent, string elementName) { string objStr = obj as string; if(objStr != null) { return objStr; } Font font = obj as Font; if(font != null) { return SerializerBase.FontToString(font); } if(obj is Color) { return colorConverter.ConvertToString(null, System.Globalization.CultureInfo.InvariantCulture, obj); } Color[] colors = obj as Color[]; if(colors != null) { return ColorArrayConverter.ColorArrayToString(colors); } System.Drawing.Image image = obj as System.Drawing.Image; if(image != null) { return ImageToString(image); } // Look for the converter set with the attibute PropertyDescriptor pd = TypeDescriptor.GetProperties(parent)[elementName]; if(pd != null) { TypeConverter converter = this.FindConverter(pd); if (converter != null && converter.CanConvertTo(typeof(string))) { return converter.ConvertToString(null, System.Globalization.CultureInfo.InvariantCulture, obj); } } // Try using default string convertion return obj.ToString(); } /// /// Removes all empty nodes from the XML document. /// Method is called recursively to remove empty child nodes first. /// /// The node where to start the removing. private void RemoveEmptyChildNodes(XmlNode xmlNode) { // Loop through all child nodes for(int nodeIndex = 0; nodeIndex < xmlNode.ChildNodes.Count; nodeIndex++) { // Remove empty child nodes of the child RemoveEmptyChildNodes(xmlNode.ChildNodes[nodeIndex]); // Check if there are any non-empty nodes left XmlNode currentNode = xmlNode.ChildNodes[nodeIndex]; if( currentNode.ParentNode != null && !(currentNode.ParentNode is XmlDocument) ) { if(!currentNode.HasChildNodes && (currentNode.Attributes == null || currentNode.Attributes.Count == 0)) { // Remove node xmlNode.RemoveChild(xmlNode.ChildNodes[nodeIndex]); --nodeIndex; } } // Remove node with one "_Template_" attribute if(!currentNode.HasChildNodes && currentNode.Attributes.Count == 1 && currentNode.Attributes["_Template_"] != null) { // Remove node xmlNode.RemoveChild(xmlNode.ChildNodes[nodeIndex]); --nodeIndex; } } } #endregion #region Deserialization public methods /// /// Deserialize specified object from the stream. /// /// Object to be deserialized. /// The stream used to read the XML document from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void Deserialize(object objectToDeserialize, Stream stream) { Deserialize(objectToDeserialize, (object)stream); } /// /// Deserialize specified object from the XML reader. /// /// Object to be deserialized. /// The XmlReader used to read the XML document from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void Deserialize(object objectToDeserialize, XmlReader xmlReader) { Deserialize(objectToDeserialize, (object)xmlReader); } /// /// Deserialize specified object from the text reader. /// /// Object to be deserialized. /// The TextReader used to write the XML document from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void Deserialize(object objectToDeserialize, TextReader textReader) { Deserialize(objectToDeserialize, (object)textReader); } /// /// Deserialize specified object from the file. /// /// Object to be deserialized. /// The file name used to read the XML document from. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal void Deserialize(object objectToDeserialize, string fileName) { Deserialize(objectToDeserialize, (object)fileName); } #endregion #region Deserialization private methods /// /// Deserialize object from different types of readers using XML format. /// /// Object to be deserialized. /// Defines the deserialization data source. Can be Stream, TextReader, XmlReader or String (file name). internal override void Deserialize(object objectToDeserialize, object reader) { // the four possible types of readers Stream stream = reader as Stream; TextReader textReader = reader as TextReader; XmlReader xmlReader = reader as XmlReader; string readerStr = reader as string; // Check input parameters if(objectToDeserialize == null) { throw(new ArgumentNullException("objectToDeserialize")); } if(reader == null) { throw(new ArgumentNullException("reader")); } if(stream == null && textReader == null && xmlReader == null && readerStr == null) { throw (new ArgumentException(SR.ExceptionChartSerializerReaderObjectInvalid, "reader")); } // Create XML document XmlDocument xmlDocument = new XmlDocument(); XmlReader xmlBaseReader = null; try { // process files without DTD XmlReaderSettings settings = new XmlReaderSettings(); // settings.ProhibitDtd is obsolete inn NetFx 4.0, the #ifdef stays for compilation under NetFx 3.5. #if OLD_DTD settings.ProhibitDtd = true; #else settings.DtdProcessing = DtdProcessing.Prohibit; //don't allow DTD #endif // Load XML document from the reader if (stream != null) { xmlBaseReader = XmlReader.Create(stream, settings); } if (readerStr != null) { xmlBaseReader = XmlReader.Create(readerStr, settings); } if (xmlReader != null) { xmlBaseReader = XmlReader.Create(xmlReader, settings); } if (textReader != null) { xmlBaseReader = XmlReader.Create(textReader, settings); } xmlDocument.Load(xmlBaseReader); // Reset properties of the root object if (IsResetWhenLoading) { ResetObjectProperties(objectToDeserialize); } // Deserialize object DeserializeObject(objectToDeserialize, null, GetObjectName(objectToDeserialize), xmlDocument.DocumentElement, xmlDocument); } finally { if (xmlBaseReader != null) { ((IDisposable)xmlBaseReader).Dispose(); } } } /// /// Deserialize object from the XML format. /// Method is called recursively to deserialize child objects. /// /// Object to be deserialized. /// Parent of the deserialized object. /// Object element name. /// The XmlNode of the parent object to deserialize the data from. /// The XmlDocument the parent node belongs to. /// Number of properties set. virtual internal int DeserializeObject(object objectToDeserialize, object parent, string elementName, XmlNode xmlParentNode, XmlDocument xmlDocument) { int setPropertiesNumber = 0; // Check input parameters if(objectToDeserialize == null) { return setPropertiesNumber; } // Loop through all node properties foreach(XmlAttribute attr in xmlParentNode.Attributes) { // Skip template collection item attribute if(attr.Name == "_Template_") { continue; } // Check if this property is serializable content if(IsSerializableContent(attr.Name, objectToDeserialize)) { SetXmlValue(objectToDeserialize, attr.Name, attr.Value); ++setPropertiesNumber; } } // Read template data into the collection IList list = objectToDeserialize as IList; if(this.IsTemplateMode && list != null && xmlParentNode.FirstChild.Attributes["_Template_"] != null) { // Loop through all items in collection int itemIndex = 0; foreach(object listItem in list) { // Find XML node appropriate for the item from the collection XmlNode listItemNode = null; // Loop through all child nodes foreach(XmlNode childNode in xmlParentNode.ChildNodes) { string templateString = childNode.Attributes["_Template_"].Value; if(templateString != null && templateString.Length > 0) { if(templateString == "All") { listItemNode = childNode; break; } else { // If there is more items in collection than XML node in template // apply items in a loop int loopItemIndex = itemIndex; while(loopItemIndex > xmlParentNode.ChildNodes.Count - 1) { loopItemIndex -= xmlParentNode.ChildNodes.Count; } // Convert attribute value to index int nodeIndex = int.Parse(templateString, CultureInfo.InvariantCulture); if(nodeIndex == loopItemIndex) { listItemNode = childNode; break; } } } } // Load data from the node if(listItemNode != null) { // Load object data DeserializeObject(listItem, objectToDeserialize, "", listItemNode, xmlDocument); } // Increase item index ++itemIndex; } // No futher loading required return 0; } // Loop through all child elements int listItemIndex = 0; foreach(XmlNode childNode in xmlParentNode.ChildNodes) { // Special handling for the collections // Bug VSTS #235707 - The collections IsSerializableContent are already checked as a property in the else statement. if (list != null) { // Create new item object string itemName = null; if (childNode.Attributes["Name"] != null) { itemName = childNode.Attributes["Name"].Value; } bool reusedObject = false; object listItem = GetListNewItem(list, childNode.Name, ref itemName, ref reusedObject); // Deserialize list item object int itemSetProperties = DeserializeObject(listItem, objectToDeserialize, "", childNode, xmlDocument); setPropertiesNumber += itemSetProperties; // Add item object into the list if (itemSetProperties > 0 || reusedObject) { list.Insert(listItemIndex++, listItem); } } else { // Check if this property is serializable content if (IsSerializableContent(childNode.Name, objectToDeserialize)) { // Deserialize the property using property descriptor PropertyDescriptor pd = TypeDescriptor.GetProperties(objectToDeserialize)[childNode.Name]; if (pd != null) { object innerObject = pd.GetValue(objectToDeserialize); // Deserialize list item object setPropertiesNumber += DeserializeObject(innerObject, objectToDeserialize, childNode.Name, childNode, xmlDocument); } else if (!IsUnknownAttributeIgnored) { throw (new InvalidOperationException(SR.ExceptionChartSerializerPropertyNameUnknown(childNode.Name, objectToDeserialize.GetType().ToString()))); } } } } return setPropertiesNumber; } /// /// Sets a property of an object using name and value as string. /// /// Object to set. /// Attribute (property) name. /// Object value.. /// Object value as strig. private void SetXmlValue(object obj, string attrName, string attrValue) { PropertyInfo pi = obj.GetType().GetProperty(attrName); if(pi != null) { // Convert string to object value object objValue = attrValue; if(pi.PropertyType == typeof(string)) { objValue = attrValue; } else if(pi.PropertyType == typeof(Font)) { objValue = SerializerBase.FontFromString(attrValue); } else if(pi.PropertyType == typeof(Color)) { objValue = (Color)colorConverter.ConvertFromString(null, System.Globalization.CultureInfo.InvariantCulture, attrValue); } else if(pi.PropertyType == typeof(System.Drawing.Image)) { objValue = ImageFromString(attrValue); } else { // Look for the converter set with the attibute PropertyDescriptor pd = TypeDescriptor.GetProperties(obj)[attrName]; if(pd != null) { TypeConverter converter = this.FindConverter(pd); if (converter != null && converter.CanConvertFrom(typeof(string))) { objValue = converter.ConvertFromString(null, System.Globalization.CultureInfo.InvariantCulture, attrValue); } } } // Set object value pi.SetValue(obj, objValue, null); } else if(!IsUnknownAttributeIgnored) { throw(new InvalidOperationException(SR.ExceptionChartSerializerPropertyNameUnknown( attrName,obj.GetType().ToString()))); } } #endregion } /// /// Utility class which serialize object using binary format /// internal class BinaryFormatSerializer : SerializerBase { #region Serialization methods /// /// Serialize specified object into the destination using binary format. /// /// Object to be serialized. /// Serialization destination. internal override void Serialize(object objectToSerialize, object destination) { // Check input parameters if (objectToSerialize == null) { throw (new ArgumentNullException("objectToSerialize")); } if (destination == null) { throw (new ArgumentNullException("destination")); } string destinationStr = destination as string; if (destinationStr != null) { Serialize(objectToSerialize, destinationStr); return; } Stream stream = destination as Stream; if (stream != null) { Serialize(objectToSerialize, stream); return; } BinaryWriter binaryWriter = destination as BinaryWriter; if (binaryWriter != null) { Serialize(objectToSerialize, binaryWriter); return; } throw (new ArgumentException(SR.ExceptionChartSerializerDestinationObjectInvalid, "destination")); } /// /// Serialize specified object into the file using binary format. /// /// Object to be serialized. /// File name to serialize the data in. internal void Serialize(object objectToSerialize, string fileName) { FileStream stream = new FileStream(fileName, FileMode.Create); Serialize(objectToSerialize, new BinaryWriter(stream)); stream.Close(); } /// /// Serialize specified object into the stream using binary format. /// /// Object to be serialized. /// Defines the serialization destination. internal void Serialize(object objectToSerialize, Stream stream) { Serialize(objectToSerialize, new BinaryWriter(stream)); } /// /// Serialize specified object into different types of writers using binary format. /// Here is what is serialized in the object: /// - all public properties with Set and Get methods /// - all public properties with Get method which derived from ICollection /// /// Object to be serialized. /// Defines the serialization destination. internal void Serialize(object objectToSerialize, BinaryWriter writer) { // Check input parameters if(objectToSerialize == null) { throw(new ArgumentNullException("objectToSerialize")); } if(writer == null) { throw(new ArgumentNullException("writer")); } // Write bnary format header into the stream, which consist of 15 characters char[] header = new char[15] {'D', 'C', 'B', 'F', '4', '0', '0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'}; writer.Write(header); // Serialize object SerializeObject(objectToSerialize, null, GetObjectName(objectToSerialize), writer); // Flush the writer stream writer.Flush(); // Reset stream position writer.Seek(0, SeekOrigin.Begin); } /// /// Serialize specified object into the binary format. /// Method is called recursively to serialize child objects. /// /// Object to be serialized. /// Parent of the serialized object. /// Object element name. /// Binary writer object. virtual internal void SerializeObject(object objectToSerialize, object parent, string elementName, BinaryWriter writer) { // Check input parameters if(objectToSerialize == null) { return; } // Check if object should be serialized if(parent != null) { PropertyDescriptor pd = TypeDescriptor.GetProperties(parent)[elementName]; if(pd != null) { SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if(styleAttribute != null) { // Hidden property if(styleAttribute.Visibility == SerializationVisibility.Hidden) { return; } } } } // Check if object is a collection if(objectToSerialize is ICollection) { // Serialize collection SerializeCollection(objectToSerialize, elementName, writer); return; } // Write object ID (hash of the name) into the writer writer.Write(SerializerBase.GetStringHashCode(elementName)); // Remember position where object data is started long elementStartPosition = writer.Seek(0, SeekOrigin.Current); // Retrive properties list of the object ArrayList propNamesList = new ArrayList(); PropertyInfo[] properties = objectToSerialize.GetType().GetProperties(); if(properties != null) { // Loop through all properties and serialize public properties foreach(PropertyInfo pi in properties) { // Skip inherited properties from the root object if(IsChartBaseProperty(objectToSerialize, parent, pi)) { continue; } // Serialize collection if (pi.CanRead && pi.PropertyType.GetInterface("ICollection", true) != null && !this.SerializeICollAsAtribute(pi, objectToSerialize)) { bool serialize = IsSerializableContent(pi.Name, objectToSerialize); // fixing Axes Array Framework 2.0 side effect // fixed by:DT if (serialize && objectToSerialize != null) { PropertyDescriptor pd = TypeDescriptor.GetProperties(objectToSerialize)[pi.Name]; if (pd != null) { SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if (styleAttribute != null) { if (styleAttribute.Visibility == SerializationVisibility.Hidden) { serialize = false; } } } } MethodInfo mi = objectToSerialize.GetType().GetMethod("ShouldSerialize" + pi.Name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if( serialize && mi != null) { object result = mi.Invoke(objectToSerialize, null); if(result is bool && ((bool)result) == false) { // Do not serialize collection serialize = false; } } // Serialize collection if(serialize) { propNamesList.Add(pi.Name); SerializeCollection(pi.GetValue(objectToSerialize, null), pi.Name, writer); } } // Serialize public properties with Get and Set methods else if(pi.CanRead && pi.CanWrite) { // Skip indexes if(pi.Name == "Item") { continue; } // Check if this property is serializable content if (IsSerializableContent(pi.Name, objectToSerialize)) { // Check if an object should be serialized as a property or as a class if (ShouldSerializeAsAttribute(pi, objectToSerialize)) { // Serialize property SerializeProperty(pi.GetValue(objectToSerialize, null), objectToSerialize, pi.Name, writer); } else { // Serialize inner object SerializeObject(pi.GetValue(objectToSerialize, null), objectToSerialize, pi.Name, writer); } } propNamesList.Add(pi.Name); } } // Check that all properties have unique IDs CheckPropertiesID(propNamesList); } // If position of the writer did not change - nothing was written if(writer.Seek(0, SeekOrigin.Current) == elementStartPosition) { // Remove object ID from the stream writer.Seek(-2, SeekOrigin.Current); writer.Write((short)0); writer.Seek(-2, SeekOrigin.Current); } else { // Write the end objectTag writer.Write((short)0); } return; } /// /// Serializes the data point. /// /// The object to serialize. /// Name of the element. /// The writer. private void SerializeDataPoint(object objectToSerialize, string elementName, BinaryWriter writer) { // Write object ID (hash of the name) into the writer writer.Write(SerializerBase.GetStringHashCode(elementName)); // Remember position where object data is started long elementStartPosition = writer.Seek(0, SeekOrigin.Current); DataPoint dataPoint = objectToSerialize as DataPoint; if (dataPoint.XValue != 0d && IsSerializableContent("XValue", objectToSerialize)) { SerializeProperty(dataPoint.XValue, dataPoint, "XValue", writer); } if (dataPoint.YValues.Length > 0 && IsSerializableContent("YValues", objectToSerialize)) { SerializeProperty(dataPoint.YValues, dataPoint, "YValues", writer); } if (dataPoint.IsEmpty && IsSerializableContent("IsEmpty", objectToSerialize)) { SerializeProperty(dataPoint.IsEmpty, dataPoint, "IsEmpty", writer); } bool hasCustomProperties = false; foreach (DictionaryEntry entry in dataPoint.properties) { if (entry.Key is int) { CommonCustomProperties propertyType = (CommonCustomProperties)((int)entry.Key); String properyName = propertyType.ToString(); if (IsSerializableContent(properyName, objectToSerialize)) { SerializeProperty(entry.Value, dataPoint, properyName, writer); } } else { hasCustomProperties = true; } } if (hasCustomProperties && !String.IsNullOrEmpty(dataPoint.CustomProperties) && IsSerializableContent("CustomProperties", objectToSerialize)) { SerializeProperty(dataPoint.CustomProperties, dataPoint, "CustomProperties", writer); } // If position of the writer did not change - nothing was written if (writer.Seek(0, SeekOrigin.Current) == elementStartPosition) { // Remove object ID from the stream writer.Seek(-2, SeekOrigin.Current); writer.Write((short)0); writer.Seek(-2, SeekOrigin.Current); } else { // Write the end objectTag writer.Write((short)0); } } /// /// Serialize specified object into the binary writer. /// Method is called recursively to serialize child objects. /// /// Object to be serialized. /// Object element name. /// Binary writer. virtual internal void SerializeCollection(object objectToSerialize, string elementName, BinaryWriter writer) { ICollection collection = objectToSerialize as ICollection; if(collection != null) { // Write object ID (hash of the name) into the writer writer.Write(SerializerBase.GetStringHashCode(elementName)); // Remember position where object data is started long elementStartPosition = writer.Seek(0, SeekOrigin.Current); // Enumerate through all objects in collection and serialize them foreach (object obj in collection) { if (obj is DataPoint) { SerializeDataPoint(obj, GetObjectName(obj), writer); continue; } SerializeObject(obj, objectToSerialize, GetObjectName(obj), writer); } // If position of the writer did not change - nothing was written if(writer.Seek(0, SeekOrigin.Current) == elementStartPosition) { // Remove object ID from the stream writer.Seek(-2, SeekOrigin.Current); writer.Write((short)0); writer.Seek(-2, SeekOrigin.Current); } else { // Write the end objectTag writer.Write((short)0); } } } /// /// Serialize specified object into the binary writer. /// Method is called recursively to serialize child objects. /// /// Object to be serialized. /// Parent of the serialized object. /// Object element name. /// Binary writer. virtual internal void SerializeProperty(object objectToSerialize, object parent, string elementName, BinaryWriter writer) { // Check input parameters if(objectToSerialize == null || parent == null) { return; } // Check if property has non-default value PropertyDescriptor pd = TypeDescriptor.GetProperties(parent)[elementName]; if(pd != null) { DefaultValueAttribute defValueAttribute = (DefaultValueAttribute)pd.Attributes[typeof(DefaultValueAttribute)]; if(defValueAttribute != null) { if(objectToSerialize.Equals(defValueAttribute.Value)) { // Do not serialize properties with default values return; } } else { // Check if property has "ShouldSerialize" method MethodInfo mi = parent.GetType().GetMethod("ShouldSerialize" + elementName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public ); if(mi != null) { object result = mi.Invoke(parent, null); if(result is bool && ((bool)result) == false) { // Do not serialize properties with default values return; } } } // Check XmlFormatSerializerStyle attribute SerializationVisibilityAttribute styleAttribute = (SerializationVisibilityAttribute)pd.Attributes[typeof(SerializationVisibilityAttribute)]; if(styleAttribute != null) { // Hidden property if(styleAttribute.Visibility == SerializationVisibility.Hidden) { return; } } } // Write property WritePropertyValue(objectToSerialize, elementName, writer); } /// /// Converts object value into the string. /// /// Object to convert. /// Object name. /// Binary writer. /// Object value as strig. [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Too large of a code change to justify making this change")] internal void WritePropertyValue(object obj, string elementName, BinaryWriter writer) { // Write property ID (hash of the name) into the writer writer.Write(SerializerBase.GetStringHashCode(elementName)); if(obj is bool) { writer.Write(((bool)obj)); } else if(obj is double) { writer.Write(((double)obj)); } else if(obj is string) { writer.Write(((string)obj)); } else if(obj is int) { writer.Write(((int)obj)); } else if(obj is long) { writer.Write(((long)obj)); } else if(obj is float) { writer.Write(((float)obj)); } else if(obj.GetType().IsEnum) { // NOTE: Using 'ToString' method instead of the 'Enum.GetName' fixes // an issue (#4314 & #4424) with flagged enumerations when there are // more then 1 values set. string enumValue = obj.ToString(); writer.Write(enumValue); } else if(obj is byte) { // Write as long writer.Write((byte)obj); } else if(obj is Font) { // Write as string writer.Write(SerializerBase.FontToString((Font)obj)); } else if(obj is Color) { // Write as int writer.Write(((Color)obj).ToArgb()); } else if(obj is DateTime) { // Write as long writer.Write(((DateTime)obj).Ticks); } else if(obj is Size) { // Write as two integers writer.Write(((Size)obj).Width); writer.Write(((Size)obj).Height); } else if(obj is double[]) { double[] arr = (double[])obj; // Write the size of the array (int) writer.Write(arr.Length); // Write each element of the array foreach(double d in arr) { writer.Write(d); } } else if(obj is Color[]) { Color[] arr = (Color[])obj; // Write the size of the array (int) writer.Write(arr.Length); // Write each element of the array foreach(Color color in arr) { writer.Write(color.ToArgb()); } } else if(obj is System.Drawing.Image) { // Save image into the memory stream MemoryStream imageStream = new MemoryStream(); ((System.Drawing.Image)obj).Save(imageStream, ((System.Drawing.Image)obj).RawFormat); // Write the size of the data int imageSize = (int)imageStream.Seek(0, SeekOrigin.End); imageStream.Seek(0, SeekOrigin.Begin); writer.Write(imageSize); // Write the data writer.Write(imageStream.ToArray()); imageStream.Close(); } else if(obj is Margins) { // Write as 4 integers writer.Write(((Margins)obj).Top); writer.Write(((Margins)obj).Bottom); writer.Write(((Margins)obj).Left); writer.Write(((Margins)obj).Right); } else { throw (new InvalidOperationException(SR.ExceptionChartSerializerBinaryTypeUnsupported(obj.GetType().ToString()))); } } /// /// Checks if all properties will have a unique ID. /// Property ID is a hash of it's name. /// !!!USED IN DEBUG BUILD ONLY!!! /// /// Array of properties names. internal void CheckPropertiesID(ArrayList propNames) { #if DEBUG if(propNames != null) { // Loop through all properties and check the hash values foreach(string name1 in propNames) { foreach(string name2 in propNames) { if(name1 != name2) { if( SerializerBase.GetStringHashCode(name1) == SerializerBase.GetStringHashCode(name2) ) { throw (new InvalidOperationException(SR.ExceptionChartSerializerBinaryHashCodeDuplicate(name1,name2))); } } } } } #endif } #endregion #region Deserialization methods /// /// Deserialize specified object from the source using binary format. /// /// Object to be deserialized. /// Deserialization source. internal override void Deserialize(object objectToDeserialize, object source) { // Check input parameters if (objectToDeserialize == null) { throw (new ArgumentNullException("objectToDeserialize")); } if (source == null) { throw (new ArgumentNullException("source")); } string sourceStr = source as string; if (sourceStr != null) { Deserialize(objectToDeserialize, sourceStr); return; } Stream stream = source as Stream; if (stream != null) { Deserialize(objectToDeserialize, stream); return; } BinaryWriter binaryWriter = source as BinaryWriter; if (binaryWriter != null) { Deserialize(objectToDeserialize, binaryWriter); return; } throw (new ArgumentException(SR.ExceptionChartSerializerSourceObjectInvalid, "source")); } /// /// Deserialize object from the file using binary format. /// /// Object to be deserialized. /// File name to read the data from. public void Deserialize(object objectToDeserialize, string fileName) { FileStream stream = new FileStream(fileName, FileMode.Open); Deserialize(objectToDeserialize, new BinaryReader(stream)); stream.Close(); } /// /// Deserialize object from the stream using binary format. /// /// Object to be deserialized. /// Stream to read the data from. public void Deserialize(object objectToDeserialize, Stream stream) { Deserialize(objectToDeserialize, new BinaryReader(stream)); } /// /// Deserialize object from different types of readers using binary format. /// /// Object to be deserialized. /// Binary reader. public void Deserialize(object objectToDeserialize, BinaryReader reader) { // Check input parameters if(objectToDeserialize == null) { throw(new ArgumentNullException("objectToDeserialize")); } if(reader == null) { throw(new ArgumentNullException("reader")); } // Binary deserializer do not support IsUnknownAttributeIgnored property if(base.IsUnknownAttributeIgnored) { throw (new InvalidOperationException(SR.ExceptionChartSerializerBinaryIgnoreUnknownAttributesUnsupported)); } // Read 15 characters of file header char[] header = reader.ReadChars(15); if(header[0] != 'D' || header[1] != 'C' || header[2] != 'B' || header[3] != 'F') { throw (new InvalidOperationException(SR.ExceptionChartSerializerBinaryFromatInvalid)); } // Get ID of the root object this.ReadHashID(reader); // Reset properties of the root object if(IsResetWhenLoading) { ResetObjectProperties(objectToDeserialize); } // Deserialize object DeserializeObject(objectToDeserialize, null, GetObjectName(objectToDeserialize), reader, false); } /// /// Deserialize object from the binary format. /// Method is called recursively to deserialize child objects. /// /// Object to be deserialized. /// Parent of the deserialized object. /// Object element name. /// Binary reader object. /// if set to true the element will not be set. /// Number of properties set. virtual protected int DeserializeObject(object objectToDeserialize, object parent, string elementName, BinaryReader reader, bool skipElement) { int setPropertiesNumber = 0; // Check input parameters if(objectToDeserialize == null) { return setPropertiesNumber; } // Special handling for the collections Type[] assemblyTypes = null; int listItemIndex = 0; IList list = objectToDeserialize as IList; if(list != null) { // Loop through all list items Int16 typeHash = 0; PropertyInfo listItemPI = objectToDeserialize.GetType().GetProperty("Item", new Type[] { typeof(int) }); while ((typeHash = this.ReadHashID(reader)) != 0) { // Get collection item type from hashed type name string typeName = String.Empty; if(listItemPI != null) { if ((SerializerBase.GetStringHashCode(listItemPI.PropertyType.Name)) == typeHash) { typeName = listItemPI.PropertyType.Name; } else { Assembly assembly = listItemPI.PropertyType.Assembly; if (assembly != null) { // Find all classes derived from this type if (assemblyTypes == null) { assemblyTypes = assembly.GetExportedTypes(); } foreach (Type type in assemblyTypes) { if (type.IsSubclassOf(listItemPI.PropertyType)) { if ((SerializerBase.GetStringHashCode(type.Name)) == typeHash) { typeName = type.Name; break; } } } } } } // Create new item object string itemName = null; bool reusedObject = false; object listItem = GetListNewItem(list, typeName, ref itemName, ref reusedObject); // Deserialize list item object int itemSetProperties = DeserializeObject(listItem, objectToDeserialize, "", reader, skipElement); // Add item object into the list if (!skipElement && (itemSetProperties > 0 || reusedObject)) { list.Insert(listItemIndex++, listItem); } // TD: here was removed a code which doesn't work but cause heavy workload: GetListNewItem removes the reusedObject from the list. // Add properties set for collection item setPropertiesNumber += itemSetProperties; } return setPropertiesNumber; } // Get list of object's properties PropertyInfo[] properties = objectToDeserialize.GetType().GetProperties(); if(properties == null) { return setPropertiesNumber; } // Get property information by reading the ID PropertyInfo pi = null; while ( (pi = ReadPropertyInfo(objectToDeserialize, parent, properties, reader)) != null) { // Read simple properties if(ShouldSerializeAsAttribute(pi, objectToDeserialize)) { if(SetPropertyValue(objectToDeserialize, pi, reader, skipElement)) { ++setPropertiesNumber; } } else { // Get property descriptor PropertyDescriptor pd = TypeDescriptor.GetProperties(objectToDeserialize)[pi.Name]; if(pd != null) { object innerObject = pd.GetValue(objectToDeserialize); // Deserialize inner item object setPropertiesNumber += DeserializeObject(innerObject, objectToDeserialize, pi.Name, reader, !IsSerializableContent(pi.Name, objectToDeserialize)); } else if(!IsUnknownAttributeIgnored) { throw(new InvalidOperationException(SR.ExceptionChartSerializerPropertyNameUnknown( pi.Name,objectToDeserialize.GetType().ToString()))); } } } return setPropertiesNumber; } /// /// Reads and sets a property of an object. /// /// Object to set. /// Property information. /// Binary reader. /// if set to true the property will not be set. /// True if property was set. private bool SetPropertyValue(object obj, PropertyInfo pi, BinaryReader reader, bool skipElement) { if(pi != null) { object objValue = null; if(pi.PropertyType == typeof(bool)) { objValue = reader.ReadBoolean(); } else if(pi.PropertyType == typeof(double)) { objValue = reader.ReadDouble(); } else if(pi.PropertyType == typeof(string)) { objValue = reader.ReadString(); } else if(pi.PropertyType == typeof(int)) { objValue = reader.ReadInt32(); } else if(pi.PropertyType == typeof(long)) { objValue = reader.ReadInt64(); } else if(pi.PropertyType == typeof(float)) { objValue = reader.ReadSingle(); } else if(pi.PropertyType.IsEnum) { // Read as string objValue = Enum.Parse(pi.PropertyType, reader.ReadString()); } else if(pi.PropertyType == typeof(byte)) { objValue = reader.ReadByte(); } else if(pi.PropertyType == typeof(Font)) { // Read as string objValue = SerializerBase.FontFromString(reader.ReadString()); } else if(pi.PropertyType == typeof(Color)) { // Read as int objValue = Color.FromArgb(reader.ReadInt32()); } else if(pi.PropertyType == typeof(DateTime)) { // Read as long objValue = new DateTime(reader.ReadInt64()); } else if(pi.PropertyType == typeof(Size)) { // Read as two integers objValue = new Size(reader.ReadInt32(), reader.ReadInt32()); } else if(pi.PropertyType == typeof(Margins) ) { // Read as 4 integers objValue = new Margins( reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32()); } else if(pi.PropertyType == typeof(double[])) { // Allocate array double[] array = new double[reader.ReadInt32()]; // Read each element of the array for(int arrayIndex = 0; arrayIndex < array.Length; arrayIndex++) { array[arrayIndex] = reader.ReadDouble(); } objValue = array; } else if(pi.PropertyType == typeof(Color[])) { // Allocate array Color[] array = new Color[reader.ReadInt32()]; // Read each element of the array for(int arrayIndex = 0; arrayIndex < array.Length; arrayIndex++) { array[arrayIndex] = Color.FromArgb(reader.ReadInt32()); } objValue = array; } else if(pi.PropertyType == typeof(System.Drawing.Image)) { // Get image data size int imageSize = reader.ReadInt32(); // Create image stream MemoryStream imageStream = new MemoryStream(imageSize + 10); // Copy image data into separate stream imageStream.Write(reader.ReadBytes(imageSize), 0, imageSize); // Create image object objValue = new Bitmap(System.Drawing.Image.FromStream(imageStream)); // !!! .Net bug when image source stream is closed - can create brush using the image // Close image stream imageStream.Close(); } else { throw(new InvalidOperationException(SR.ExceptionChartSerializerBinaryTypeUnsupported( obj.GetType().ToString() ))); } // Check if this property is serializable content if (!skipElement && IsSerializableContent(pi.Name, obj)) { // Set object value pi.SetValue(obj, objValue, null); return true; } } return false; } /// /// Reads property ID and return property information /// /// Object to be deserialized. /// Parent of the deserialized object. /// List of properties information. /// Binary reader. /// Property information. private PropertyInfo ReadPropertyInfo(object objectToDeserialize, object parent, PropertyInfo[] properties, BinaryReader reader) { // Read property ID short propertyID = this.ReadHashID(reader); // End objectTag reached if(propertyID == 0) { return null; } // Loop through all properties and check properties IDs (hash code of name) foreach(PropertyInfo pi in properties) { // Skip inherited properties from the root object if(IsChartBaseProperty(objectToDeserialize, parent, pi)) { continue; } // Check collection if (pi.CanRead && pi.PropertyType.GetInterface("ICollection", true) != null) { if((SerializerBase.GetStringHashCode(pi.Name)) == propertyID) { return pi; } } // Check public properties with Get and Set methods else if(pi.CanRead && pi.CanWrite) { // Skip indexes if(pi.Name == "Item") { continue; } if((SerializerBase.GetStringHashCode(pi.Name)) == propertyID) { return pi; } } } // Property was not found throw (new InvalidOperationException(SR.ExceptionChartSerializerPropertyNotFound)); } #endregion } }