using System; using System.IO; using System.Xml; using System.Runtime.Serialization; //using System.Web.Configuration; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Diagnostics; using System.Runtime.InteropServices; using static System.Net.WebRequestMethods; /* element and attribute names - Element names are case-sensitive - Element names must start with a letter or underscore - Element names cannot start with the letters xml(or XML, or Xml, etc) - Element names can contain letters, digits, hyphens, underscores, and periods - Element names cannot contain spaces * TODO * x) Add the ability for correctly named attributes to be able to fill in classes * x) */ namespace lib { public interface I_Serialize { void OnSerialize(); void OnDeserialize( object enclosing ); } [Flags] public enum Types { Fields = 0b_0001, Props = 0b_0010, Implied= 0b_0100, None = 0b_000, Default = Fields, All = Fields | Props, } public class Ser : Attribute { public Types Types { get; set; } = Types.Default; } public class WLChildAttributes : Attribute { public string[] Values { get; private set; } public WLChildAttributes( params string[] values ) { this.Values = values; } } public class WLChildFieldsAttribute : WLChildAttributes { public WLChildFieldsAttribute( params string[] values ) : base( values ) { } } public class WLChildPropsAttribute : WLChildAttributes { public WLChildPropsAttribute( params string[] values ) : base( values ) { } } public enum Datastructure { Invalid, Tree, Graph, } public class XmlFormatter2Cfg: Config { public readonly Datastructure datastructure = Datastructure.Graph; public readonly int Version = 2; } public class XmlFormatter2: IFormatter { public StreamingContext Context { get; set; } static Random s_rnd = new Random(); int m_rndVal = s_rnd.Next(); XmlFormatter2Cfg m_cfg = new XmlFormatter2Cfg(); #region Unimplimented public ISurrogateSelector SurrogateSelector { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public SerializationBinder Binder { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } #endregion public XmlFormatter2() { Context = new StreamingContext( StreamingContextStates.All ); } public XmlFormatter2( XmlFormatter2Cfg cfg ) { Context = new StreamingContext( StreamingContextStates.All ); m_cfg = cfg; log.warn( $"XML serialization is NOT fast" ); } #region Deserialize private static FormatterConverter s_conv = new FormatterConverter(); public object Deserialize( Stream stream ) { return DeserializeKnownType( stream, null ); } public T Deserialize(Stream stream) { return (T)DeserializeKnownType(stream, typeof(T)); } public object DeserializeKnownType( Stream stream, Type t ) { XmlTextReader reader = new XmlTextReader(stream); object obj = Deserialize(reader, t); return obj; } private object Deserialize( XmlReader reader, Type t ) { m_alreadySerialized.Clear(); m_objectID = new ObjectIDGenerator(); reader.Read(); XmlDocument doc = new XmlDocument(); doc.Load( reader ); if( t == null ) return Deserialize( doc.DocumentElement ); return Deserialize( doc.DocumentElement, null, t, null ); } public void DeserializeInto(Stream stream, T obj) { XmlTextReader reader = new XmlTextReader( stream ); reader.Read(); XmlDocument doc = new XmlDocument(); doc.Load( reader ); HydrateObject( doc.DocumentElement, null, obj ); } private object Deserialize( XmlElement elem ) { //lib.log.info( "object Deserialize( XmlElement elem ) {0} {1}", m_rndVal, m_alreadySerialized.Count ); string typename = elem.HasAttribute("_.t") ? elem.GetAttribute("_.t") : elem.Name; return Deserialize( elem, null, typename ); } private object Deserialize( XmlElement elem, MemberInfo mi, string typename ) { AppDomain currentDomain = AppDomain.CurrentDomain; Assembly[] assems = currentDomain.GetAssemblies(); Type type = null; // @@@@: This should go backwards, we tend to lookup our own stuff, then builtins. // Also, cache a typename into its assembly. foreach( Assembly a in assems ) { type = a.GetType( typename ); if( type != null ) break; } if( type == null ) { return null; } return Deserialize( elem, null, type, null ); } private object Deserialize( XmlElement elem, MemberInfo mi, Type type, object existing /*, object enclosing = null*/ ) { TypeCode typeCode = Type.GetTypeCode(type); if( typeCode != TypeCode.Object ) { return DeserializeConcrete( elem, mi, type ); } else { if( !type.IsArray ) { object obj = DeserializeObject(elem, mi, type, existing); if( obj is I_Serialize ) { var iser = obj as I_Serialize; iser.OnDeserialize( null ); } return obj; } else { return DeserializeArray( elem, mi, type ); } } } Type[] mm_types = new Type[1]; private object GetDefault( Type t ) { mm_types[0] = t; var fn = GetType().GetMethod("GetDefaultGeneric").MakeGenericMethod(mm_types); return fn.Invoke( this, null ); } public T GetDefaultGeneric() { return default( T ); } private object DeserializeConcrete( XmlElement elem, MemberInfo mi, Type type ) { string val = ""; if( elem.HasAttribute("v") ) { val = elem.GetAttribute("v"); } else { val = elem.InnerText; } if ( !type.IsEnum ) { try { return s_conv.Convert( val, type ); } catch( Exception ) { return GetDefault( type ); } } else { return Enum.Parse( type, val ); } } private XmlElement getNamedChild( XmlNodeList list, string name ) { foreach( XmlNode node in list ) { if( node.Name == name ) { return (XmlElement)node; } } return null; } private Type FindType( string shortname ) { Assembly[] ass = AppDomain.CurrentDomain.GetAssemblies(); foreach( Assembly a in ass ) { Type t = a.GetType(shortname); if( t != null ) { return t; } } return null; } private Type[] mm_consType = new Type[2]; private object[] mm_args = new object[2]; private object DeserializeObject( XmlElement elem, MemberInfo mi, Type type, object existing ) { Type finalType; var obj = GetObjectForDeser( elem, type, out finalType, existing ); return HydrateObject( elem, mi, finalType, obj ); } public object HydrateObject( XmlElement elem, MemberInfo mi, T obj ) { return HydrateObject( elem, mi, typeof( T ), obj ); } private object HydrateObject( XmlElement elem, MemberInfo mi, Type finalType, object obj ) { if( obj is IList ) { var list = obj as IList; return DeserializeList( elem, mi, finalType, list ); } Type typeISerializable = typeof( ISerializable ); if( obj is ISerializable ) // type.IsSubclassOf( typeISerializable ) ) { XmlNodeList allChildren = elem.ChildNodes; //ISerializable ser = obj as ISerializable; var serInfo = new SerializationInfo( finalType, new FormatterConverter() ); //var serInfoForTypes = new SerializationInfo( type, new FormatterConverter() ); //ser.GetObjectData( serInfoForTypes, Context ); foreach( var objNode in allChildren ) { var node = objNode as XmlElement; String name = node.Name; String childType = node.GetAttribute( "_.t" ); name = refl.TypeToIdentifier( name ); XmlElement childElem = getNamedChild( allChildren, name ); var des = Deserialize( childElem, mi, childType ); serInfo.AddValue( name, des, des.GetType() ); } //ConstructorInfo[] allCons = obj.GetType().GetConstructors( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); //var serMem = FormatterServices.GetSerializableMembers( finalType ); //object objUn = FormatterServices.GetSafeUninitializedObject( finalType ); IDeserializationCallback objUnOnDeser = obj as IDeserializationCallback; mm_consType[0] = typeof( SerializationInfo ); mm_consType[1] = typeof( StreamingContext ); ConstructorInfo serCons = finalType.GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, mm_consType, null ); mm_args[0] = serInfo; mm_args[1] = Context; serCons.Invoke( obj, mm_args ); if( objUnOnDeser != null ) { objUnOnDeser.OnDeserialization( objUnOnDeser ); } /* ser.GetObjectData( serInfo, Context ); //var serEnum = ; foreach( var serMember in serInfo ) { String name = serMember.Name; name = refl.TypeToIdentifier( name ); XmlElement childElem = getNamedChild( allChildren, name ); var des = Deserialize( childElem, name ); } */ } else { XmlNodeList allChildren = elem.ChildNodes; bool filterFields, filterProps, doImpls, doFields, doProps; HashSet whitelistFields, whitelistProps; GetFilters( mi, finalType, out filterFields, out filterProps, out doImpls, out doFields, out doProps, out whitelistFields, out whitelistProps ); /* List members = new(); if( doFields || doImpls ) { members.AddRange( refl.GetAllFields( finalType ) ); } if( doProps || doImpls ) { members.AddRange( refl.GetAllProperties( finalType ) ); } */ if( doFields || doImpls ) { var fields = refl.GetAllFields( finalType ); foreach( FieldInfo childFi in fields ) { String name = childFi.Name; //This is to convert c# names that would be bad as XML tags name = refl.TypeToIdentifier( name ); if( FilterField( filterFields, doImpls, whitelistFields, childFi as MemberInfo, name ) ) continue; XmlElement childElem = getNamedChild( allChildren, name ); if( childElem != null ) { object existingObj = childFi.GetValue( obj ); object childObj = Deserialize( childElem, childFi, childFi.FieldType, existingObj ); childFi.SetValue( obj, childObj ); } } } if( doProps || doImpls ) { var props = refl.GetAllProperties( finalType ); foreach( var childPi in props ) { String name = childPi.Name; name = refl.TypeToIdentifier( name ); if( FilterField( filterFields, doImpls, whitelistFields, childPi as MemberInfo, name ) ) continue; XmlElement childElem = getNamedChild( allChildren, name ); if( childElem != null ) { object existingObj = childPi.GetValue( obj ); object childObj = Deserialize( childElem, childPi, childPi.PropertyType, existingObj ); childPi.SetValue( obj, childObj ); } else { log.warn( $"" ); } } } } return obj; } private static bool FilterField( bool filterFields, bool doImpls, HashSet whitelistFields, MemberInfo mi, string name ) { if( doImpls ) { if( mi.GetCustomAttribute() == null ) return true; } if( filterFields && !whitelistFields.Contains( name ) ) return true; return false; } private object GetObjectForDeser( XmlElement elem, Type type, out Type finalType, object obj ) { finalType = type; if( elem.HasAttribute( "_.t" ) ) { var typename = elem.GetAttribute( "_.t" ); finalType = FindType( typename ); if( finalType == null ) finalType = type; } string refString = elem.GetAttribute( "ref" ); int refInt = refString.Length > 0 ? Convert.ToInt32( refString ) : -1; obj = createObject( finalType, refInt, obj ); return obj; } private object DeserializeList( XmlElement elem, MemberInfo mi, Type type, IList list ) { XmlNodeList arrNodeList = elem.ChildNodes; Type t = list.GetType(); Type[] genT = t.GetGenericArguments(); Debug.Assert( genT.Length == 1 ); for( int i = 0; i < arrNodeList.Count; ++i ) { if( arrNodeList.Item( i ) is XmlElement ) { XmlElement arrElem = (XmlElement)arrNodeList.Item(i); list.Add( Deserialize( arrElem, mi, genT[0], null ) ); } } return list; } private object DeserializeArray( XmlElement elem, MemberInfo mi, Type type ) { Type typeElem = type.GetElementType(); string refString = elem.GetAttribute("ref"); int refInt = refString.Length > 0 ? Convert.ToInt32(refString) : -1; XmlNodeList arrNodeList = elem.ChildNodes; int length = arrNodeList.Count; Array arr = createArray(typeElem, refInt, length); for( int i = 0; i < arr.Length; ++i ) { if( arrNodeList.Item( i ) is XmlElement ) { XmlElement arrElem = (XmlElement)arrNodeList.Item(i); var finalType = typeElem; if (arrElem.HasAttribute("_.t")) { var typename = arrElem.GetAttribute("_.t"); finalType = FindType(typename); if (finalType == null) finalType = typeElem; } arr.SetValue( Deserialize( arrElem, mi, finalType, null), i ); } } return arr; } private object createObject( string typename, int refInt, object obj ) { Type type = Type.GetType(typename); return createObject( type, refInt, obj ); } private object createObject( Type type, int refInt, object existingObj ) { TypeCode tc = Type.GetTypeCode(type); if( m_cfg.datastructure == Datastructure.Graph && refInt > 0 && m_alreadySerialized.ContainsKey( refInt ) ) { //lib.log.info( "Reusing object for {0}", refInt ); return m_alreadySerialized[refInt]; } if( existingObj != null ) { var existingObjType = existingObj.GetType(); if( type == existingObjType ) return existingObj; } { object obj = null; try { //Trying the nice way to creat objects first. obj = Activator.CreateInstance( type ); } catch( Exception ) { try { obj = System.Runtime.Serialization.FormatterServices.GetUninitializedObject( type ); } catch( Exception exInner ) { log.error( $"Got exception {exInner.Message} trying to make an uninitialized object" ); } } if( obj == null ) { log.warn( $"Could not create object of type {type.Name}" ); return obj; } if( m_cfg.datastructure == Datastructure.Graph && refInt > 0 ) { m_alreadySerialized[refInt] = obj; } return obj; } } private Array createArray( string elemTypename, int refInt, int length ) { Type elemType = Type.GetType(elemTypename); return createArray( elemType, refInt, length ); } private Array createArray( Type elemType, int refInt, int length ) { TypeCode elemTC = Type.GetTypeCode(elemType); if( m_cfg.datastructure == Datastructure.Graph && refInt > 0 && m_alreadySerialized.ContainsKey( refInt ) ) { return (Array)m_alreadySerialized[refInt]; } else { Array arr = Array.CreateInstance(elemType, length); if( m_cfg.datastructure == Datastructure.Graph ) { m_alreadySerialized[refInt] = arr; } return arr; } } private ObjectIDGenerator m_objectID = new ObjectIDGenerator(); private Dictionary m_alreadySerialized = new Dictionary(); #endregion #region Serialize private string getTypeName( Type type ) { //Assembly ass = type.Assembly; //string assName = ass.GetName().Name; return type.FullName; // + ", " + assName; } public void Serialize( Stream stream, object root ) { Serialize( stream, null, root ); } public void Serialize( Stream stream, MemberInfo mi, object root ) { //lib.log.info( "Serialize( Stream stream, object root ) {0} {1}", m_rndVal, m_alreadySerialized.Count ); m_alreadySerialized.Clear(); m_objectID = new ObjectIDGenerator(); XmlTextWriter writer = new XmlTextWriter(stream, System.Text.Encoding.ASCII); writer.Formatting = Formatting.Indented; Serialize( writer, mi, root ); //Rely on the parent closing the stream. //writer.Close(); writer.Flush(); //lib.log.info( "Serialize END ( Stream stream, object root ) {0} {1}", m_rndVal, m_alreadySerialized.Count ); } private void Serialize( XmlWriter writer, MemberInfo mi, object root ) { //writer.WriteStartDocument(); Serialize( writer, mi, root, "root", 1, true ); //writer.WriteEndDocument(); } private void Serialize( XmlWriter writer, MemberInfo mi, object root, string name, int depth, bool forceType ) { writer.WriteStartElement( name ); if( root != null ) { Type type = root.GetType(); TypeCode typeCode = Type.GetTypeCode(type); if( typeCode != TypeCode.Object ) { SerializeConcrete( writer, mi, root, forceType ); } else { if( !type.IsArray ) { SerializeObject( writer, mi, root, depth ); } else { SerializeArray( writer, mi, root, depth ); } } } else { writer.WriteAttributeString( "v", "null" ); } writer.WriteEndElement(); } private void SerializeConcrete( XmlWriter writer, MemberInfo mi, object root, bool forceType ) { //TODO: Only write this out if debugging. if( forceType ) { writer.WriteAttributeString( "_.t", getTypeName( root.GetType() ) ); } writer.WriteAttributeString( "v", root.ToString() ); } private void SerializeObject( XmlWriter writer, MemberInfo mi, object root, int depth ) { writer.WriteAttributeString( "_.t", getTypeName( root.GetType() ) ); if(depth == 1) { writer.WriteAttributeString( "_.version.", $"{m_cfg.Version}" ); } /* if( root is IList ) { var list = root as IList; Type t = root.GetType(); Type[] genT = t.GetGenericArguments(); } */ bool first; long refInt = m_objectID.GetId(root, out first); if( m_cfg.datastructure == Datastructure.Graph ) { writer.WriteAttributeString( "ref", refInt.ToString() ); } if( first ) { if( m_cfg.datastructure == Datastructure.Graph ) { m_alreadySerialized[refInt] = root; } Type type = root.GetType(); //var whitelistProp = type.GetCustomAttribute(); //var useWhitelist = whitelistProp != null; //* Type typeISerializable = typeof(ISerializable); if( root is ISerializable ) // type.IsSubclassOf( typeISerializable ) ) { ISerializable ser = root as ISerializable; var serInfo = new SerializationInfo(type, new FormatterConverter()); ser.GetObjectData( serInfo, Context ); //var serEnum = ; foreach( var serMember in serInfo ) { String name = serMember.Name; name = refl.TypeToIdentifier( name ); Serialize( writer, mi, serMember.Value, name, depth, true ); } //var sc = new SerializationContext( //ser.GetObjectData( } else //*/ { bool filterFields, filterProps, doImpls, doFields, doProps; HashSet whitelistFields, whitelistProps; GetFilters( mi, type, out filterFields, out filterProps, out doImpls, out doFields, out doProps, out whitelistFields, out whitelistProps ); if( doFields || doImpls ) { var fields = refl.GetAllFields( type ); foreach( var childFi in fields ) { String name = childFi.Name; if( FilterField( filterFields, doImpls, whitelistFields, childFi as MemberInfo, name ) ) continue; object[] objs = childFi.GetCustomAttributes( typeof( NonSerializedAttribute ), true ); if( objs.Length > 0 ) { continue; } //if( childFi.GetCustomAttribute() ) name = refl.TypeToIdentifier( name ); Serialize( writer, childFi, childFi.GetValue( root ), name, depth + 1, false ); } } if( doProps || doImpls ) { var props = refl.GetAllProperties( type ); foreach( var childPi in props ) { String name = childPi.Name; if( FilterField( filterProps, doImpls, whitelistProps, childPi as MemberInfo, name ) ) continue; object[] objs = childPi.GetCustomAttributes( typeof( NonSerializedAttribute ), true ); if( objs.Length > 0 ) { continue; } name = refl.TypeToIdentifier( name ); Serialize( writer, childPi, childPi.GetValue( root ), name, depth + 1, false ); } } } } } private static void GetFilters( MemberInfo mi, Type type, out bool filterFields, out bool filterProps, out bool doImpls, out bool doFields, out bool doProps, out HashSet whitelistFields, out HashSet whitelistProps ) { var custWLFields = mi?.GetCustomAttribute(); var custWLProps = mi?.GetCustomAttribute(); filterFields = custWLFields != null; filterProps = custWLProps != null; var typesTodo = type.GetCustomAttribute()?.Types ?? Types.None; doImpls = typesTodo.HasFlag( Types.Implied ); doFields = filterFields || typesTodo.HasFlag( Types.Fields ); doProps = filterProps || typesTodo.HasFlag( Types.Props ); whitelistFields = new( custWLFields?.Values ?? new string[0] ); whitelistProps = new( custWLProps?.Values ?? new string[0] ); } private void SerializeArray( XmlWriter writer, MemberInfo mi, object root, int depth ) { Array arr = (Array)root; Type typeElem = arr.GetType().GetElementType(); Type type = root.GetType(); writer.WriteAttributeString( "_.t", getTypeName( type ) ); bool first; long refInt = m_objectID.GetId(root, out first); if( m_cfg.datastructure == Datastructure.Graph ) { writer.WriteAttributeString( "ref", refInt.ToString() ); } if( first ) { if( m_cfg.datastructure == Datastructure.Graph ) { m_alreadySerialized[refInt] = root; } for( int i = 0; i < arr.Length; ++i ) { Serialize( writer, mi, arr.GetValue( i ), "i" + i.ToString(), depth + 1, false ); } } } #endregion } }