189 lines
5.1 KiB
C#
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;
|
|
}
|
|
}
|
|
|