sharplib/ser/XmlSer_Read.cs

189 lines
5.1 KiB
C#

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;
}
}