using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Xml; namespace ser; // --- Primitive Handler --- public partial class PrimitiveHandler : ser.ITypeHandler { public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing ) { string val = elem.HasAttribute( "v" ) ? elem.GetAttribute( "v" ) : elem.InnerText; if( val == "null" ) return null; // So this is an interesting one. Why not use the expected type? Well, we know we have // data in the XML, it just wont convert to what we want. return xml._resolver.ConvertSimple( val, expectedType ); } } // --- Proxy Handler --- public partial class ProxyHandler : ITypeHandler { public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing ) { var ti = xml._meta.Get( expectedType ); // Re-get to ensure we have proxy info if( !elem.HasAttribute( "proxy" ) || !ti.ProxyDef.HasValue ) { log.warn( $"Proxy read failed for {expectedType.Name}. Fallback needed." ); return null; // Should fall back or throw } var proxyVal = elem.GetAttribute( "proxy" ); return ti.ProxyDef.Value.fnDes( expectedType.FullName, proxyVal ); } } // --- ISerializable Handler --- public partial class ISerializableHandler : ITypeHandler { public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing ) { // Create/Get instance (needs FormatterServices for ISerializable) object obj = existing ?? FormatterServices.GetUninitializedObject( expectedType ); long id = xml.TrackIfGraph( obj, elem ); // Track it var serInfo = new SerializationInfo( expectedType, new FormatterConverter() ); foreach( XmlNode objNode in elem.ChildNodes ) { if( objNode is XmlElement childElem ) { string childName = childElem.Name; Type? childType = xml._resolver.FindType( childElem.GetAttribute( "_.t" ) ); if( childType != null ) { var desValue = xml.ReadNode( childElem, childType, null ); serInfo.AddValue( childName, desValue, childType ); } } } var context = new StreamingContext( StreamingContextStates.All ); // Or use xml.Context var cons = expectedType.GetConstructor( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { typeof( SerializationInfo ), typeof( StreamingContext ) }, null ); if( cons != null ) { cons.Invoke( obj, new object[] { serInfo, context } ); } else { log.error( $"ISerializable type {expectedType.Name} lacks the required constructor." ); } if( obj is IDeserializationCallback cb ) cb.OnDeserialization( obj ); return obj; } } // --- Collection Handler --- public partial class CollectionHandler : ITypeHandler { public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing ) { // Determine element type Type elemType = GetElementType( expectedType ); // Create a temporary list var listType = typeof( List<> ).MakeGenericType( elemType ); var list = (IList)Activator.CreateInstance( listType )!; xml.TrackIfGraph( list, elem ); // Track list if graph // Populate the list foreach( XmlNode node in elem.ChildNodes ) { if( node is XmlElement childElem ) { list.Add( xml.ReadNode( childElem, elemType, null ) ); } } // Convert to the final expected type (Array, Immutable*, List) return ConvertToFinalCollection( list, expectedType, elemType ); } } // --- Object Handler (Default/Complex) --- public partial class ObjectHandler : ITypeHandler { public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing ) { var actualType = xml._resolver.Resolve( elem, expectedType ); var ti = xml._meta.Get( actualType ); // 1. Get/Create Instance var (obj, _) = GetOrCreateInstance( xml, elem, actualType, existing ); if( obj == null ) return null; // Handle graph refs (if already processed) if( xml._cfg.Structure == Datastructure.Graph && elem.HasAttribute( "ref" ) ) { long id = long.Parse( elem.GetAttribute( "ref" ) ); if( xml._processed.TryGetValue( id, out var processedObj ) ) return processedObj; } // Track if it's new xml.TrackIfGraph( obj, elem ); // 2. Hydrate foreach( var memberMeta in ti.Members ) { { var (valueSource, isAttribute) = FindValueSource( elem, memberMeta.XmlName ); if( valueSource != null ) { object? memberValue; object? currentMemberValue = memberMeta.GetValue( obj ); if( isAttribute ) { memberValue = xml._resolver.ConvertSimple( valueSource.Value!, memberMeta.Type ); } else // Child Element { memberValue = xml.ReadNode( (XmlElement)valueSource, memberMeta.Type, currentMemberValue ); } // Set value, respecting ser.Do/ser.Dont and pre-hydration if( ShouldSetValue( memberMeta, existing != null ) ) { memberMeta.SetValue( obj, memberValue ); } } } } // 3. Post-processing if( obj is ser.I_Serialize iSer ) obj = iSer.OnDeserialize( null ); if( ti.IsImm && obj is imm.Obj immObj ) return immObj.Record( $"From XML {elem.Name}" ); return obj; } }