77using System . Runtime . CompilerServices ;
88using System . Globalization ;
99using System . Reflection ;
10+ using System . Xml . Linq ;
11+ using System . Collections ;
1012
1113namespace Datamodel . Codecs
1214{
@@ -292,7 +294,7 @@ void WriteAttribute(string name, Type type, object value, bool in_array)
292294 else if ( type == typeof ( Matrix4x4 ) )
293295 {
294296 var castValue = ( Matrix4x4 ) value ;
295- var matrixString =
297+ var matrixString =
296298 $ "{ castValue . M11 } { castValue . M12 } { castValue . M13 } { castValue . M14 } " +
297299 $ " { castValue . M21 } { castValue . M22 } { castValue . M23 } { castValue . M24 } " +
298300 $ " { castValue . M31 } { castValue . M32 } { castValue . M33 } { castValue . M34 } " +
@@ -397,6 +399,44 @@ public void Encode(Datamodel dm, string encoding, int encoding_version, Stream s
397399 #endregion
398400
399401 #region Decode
402+
403+ private class IntermediateData
404+ {
405+ // these store element refs while we process the elements, once were done
406+ // we can go trough these and actually create the attributes
407+ // and add the elements to lists
408+ public Dictionary < Element , List < ( string , Guid ) > > PropertiesToAdd = new ( ) ;
409+ public Dictionary < IList , List < Guid > > ListRefs = new ( ) ;
410+
411+ public void HandleElementProp ( Element element , string attrName , Guid id )
412+ {
413+
414+ PropertiesToAdd . TryGetValue ( element , out var attrList ) ;
415+
416+ if ( attrList == null )
417+ {
418+ attrList = new List < ( string , Guid ) > ( ) ;
419+ PropertiesToAdd . Add ( element , attrList ) ;
420+ }
421+
422+ attrList . Add ( ( attrName , id ) ) ;
423+
424+ }
425+
426+ public void HandleListRefs ( IList list , Guid id )
427+ {
428+ ListRefs . TryGetValue ( list , out var guidList ) ;
429+
430+ if ( guidList == null )
431+ {
432+ guidList = new List < Guid > ( ) ;
433+ ListRefs . Add ( list , guidList ) ;
434+ }
435+
436+ guidList . Add ( id ) ;
437+ }
438+ }
439+
400440 readonly StringBuilder TokenBuilder = new ( ) ;
401441 int Line = 0 ;
402442 string Decode_NextToken ( )
@@ -441,31 +481,17 @@ string Decode_NextToken()
441481 }
442482 }
443483
444- Element Decode_ParseElementId ( )
445- {
446- Element elem ;
447- var id_s = Decode_NextToken ( ) ;
448-
449- if ( string . IsNullOrEmpty ( id_s ) )
450- elem = null ;
451- else
452- {
453- Guid id = new ( id_s ) ;
454- elem = DM . AllElements [ id ] ;
455- elem ??= new Element ( DM , id ) ;
456- }
457- return elem ;
458- }
459-
460- Element Decode_ParseElement ( string class_name )
484+ Element Decode_ParseElement ( string class_name , ReflectionParams reflectionParams , IntermediateData intermediateData )
461485 {
462486 string elem_class = class_name ?? Decode_NextToken ( ) ;
463487 string elem_name = null ;
464488 string elem_id = null ;
465489 Element elem = null ;
466490
491+ var types = CodecUtilities . GetReflectionTypes ( reflectionParams ) ;
492+
467493 string next = Decode_NextToken ( ) ;
468- if ( next != "{" ) throw new CodecException ( String . Format ( "Expected Element opener, got '{0 }'." , next ) ) ;
494+ if ( next != "{" ) throw new CodecException ( $ "Expected Element opener, got '{ next } '.") ;
469495 while ( true )
470496 {
471497 next = Decode_NextToken ( ) ;
@@ -479,16 +505,41 @@ Element Decode_ParseElement(string class_name)
479505 {
480506 elem_id = Decode_NextToken ( ) ;
481507 var id = new Guid ( elem_id ) ;
482- var local_element = DM . AllElements [ id ] ;
483- if ( local_element != null )
508+ if ( elem_class != "$prefix_element$" )
484509 {
485- elem = local_element ;
486- elem . Name = elem_name ;
487- elem . ClassName = elem_class ;
488- elem . Stub = false ;
510+ var matchedType = types . TryGetValue ( elem_class , out var classType ) ;
511+
512+ if ( matchedType && reflectionParams . AttemptReflection )
513+ {
514+ var isElementDerived = Element . IsElementDerived ( classType ) ;
515+ if ( isElementDerived && classType . Name == elem_class )
516+ {
517+ Type derivedType = classType ;
518+
519+ ConstructorInfo ? constructor = typeof ( Element ) . GetConstructor (
520+ BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ,
521+ null ,
522+ new Type [ ] { typeof ( Datamodel ) , typeof ( string ) , typeof ( Guid ) , typeof ( string ) } ,
523+ null
524+ ) ;
525+
526+ if ( constructor == null )
527+ {
528+ throw new InvalidOperationException ( "Failed to get constructor while attemption reflection based deserialisation" ) ;
529+ }
530+
531+ object uninitializedObject = RuntimeHelpers . GetUninitializedObject ( derivedType ) ;
532+ constructor . Invoke ( uninitializedObject , new object [ ] { DM , elem_name , new Guid ( elem_id ) , elem_class } ) ;
533+
534+ elem = ( Element ? ) uninitializedObject ;
535+ }
536+ }
537+
538+ if ( elem == null )
539+ {
540+ elem = new Element ( DM , elem_name , new Guid ( elem_id ) , elem_class ) ;
541+ }
489542 }
490- else if ( elem_class != "$prefix_element$" )
491- elem = new Element ( DM , elem_name , new Guid ( elem_id ) , elem_class ) ;
492543
493544 continue ;
494545 }
@@ -501,19 +552,21 @@ Element Decode_ParseElement(string class_name)
501552 continue ;
502553 }
503554
504- if ( elem == null )
505- continue ;
506-
507555 if ( attr_type_s == "element" )
508556 {
509- elem . Add ( attr_name , Decode_ParseElementId ( ) ) ;
557+ var id_s = Decode_NextToken ( ) ;
558+
559+ if ( ! string . IsNullOrEmpty ( id_s ) )
560+ {
561+ intermediateData . HandleElementProp ( elem , attr_name , new Guid ( id_s ) ) ;
562+ }
510563 continue ;
511564 }
512565
513566 object attr_value = null ;
514567
515568 if ( attr_type == null )
516- attr_value = Decode_ParseElement ( attr_type_s ) ;
569+ attr_value = Decode_ParseElement ( attr_type_s , reflectionParams , intermediateData ) ;
517570 else if ( attr_type_s . EndsWith ( "_array" ) )
518571 {
519572 var array = CodecUtilities . MakeList ( attr_type , 5 ) ; // assume 5 items
@@ -527,15 +580,28 @@ Element Decode_ParseElement(string class_name)
527580 if ( next == "]" ) break ;
528581
529582 if ( next == "element" ) // Element ID reference
530- array . Add ( Decode_ParseElementId ( ) ) ;
531- else if ( attr_type == typeof ( Element ) ) // inline Element
532- array . Add ( Decode_ParseElement ( next ) ) ;
533- else // normal value
534- array . Add ( Decode_ParseValue ( attr_type , next ) ) ;
583+ {
584+ var id_s = Decode_NextToken ( ) ;
585+
586+ if ( ! string . IsNullOrEmpty ( id_s ) )
587+ {
588+ intermediateData . HandleListRefs ( array , new Guid ( id_s ) ) ;
589+ }
590+ }
591+ // inline Element
592+ else if ( attr_type == typeof ( Element ) )
593+ {
594+ array . Add ( Decode_ParseElement ( next , reflectionParams , intermediateData ) ) ;
595+ }
596+ // normal value
597+ else
598+ {
599+ array . Add ( Decode_ParseValue ( attr_type , next , reflectionParams , intermediateData ) ) ;
600+ }
535601 }
536602 }
537603 else
538- attr_value = Decode_ParseValue ( attr_type , Decode_NextToken ( ) ) ;
604+ attr_value = Decode_ParseValue ( attr_type , Decode_NextToken ( ) , reflectionParams , intermediateData ) ;
539605
540606 if ( elem != null )
541607 elem . Add ( attr_name , attr_value ) ;
@@ -545,15 +611,15 @@ Element Decode_ParseElement(string class_name)
545611 return elem ;
546612 }
547613
548- object Decode_ParseValue ( Type type , string value )
614+ object Decode_ParseValue ( Type type , string value , ReflectionParams reflectionParams , IntermediateData intermediateData )
549615 {
550616 if ( type == typeof ( string ) )
551617 return value ;
552618
553619 value = value . Trim ( ) ;
554620
555621 if ( type == typeof ( Element ) )
556- return Decode_ParseElement ( value ) ;
622+ return Decode_ParseElement ( value , reflectionParams , intermediateData ) ;
557623 if ( type == typeof ( int ) )
558624 return int . Parse ( value , CultureInfo . InvariantCulture ) ;
559625 else if ( type == typeof ( float ) )
@@ -562,11 +628,34 @@ object Decode_ParseValue(Type type, string value)
562628 return byte . Parse ( value , CultureInfo . InvariantCulture ) == 1 ;
563629 else if ( type == typeof ( byte [ ] ) )
564630 {
631+ // need to sanitise input here because for example when exporting map as txt,
632+ // hammer will format the binary data to fit nicer on the screen by inserting two tabs
633+ var sb = new StringBuilder ( value . Length ) ;
634+ foreach ( char c in value )
635+ {
636+ switch ( c )
637+ {
638+ case '\\ ' :
639+ case '\r ' :
640+ case '\n ' :
641+ case '\t ' :
642+ case ' ' :
643+ continue ;
644+ default :
645+ sb . Append ( c ) ;
646+ break ;
647+ }
648+ }
649+ value = sb . ToString ( ) ;
650+
565651 byte [ ] result = new byte [ value . Length / 2 ] ;
652+
566653 for ( int i = 0 ; i * 2 < value . Length ; i ++ )
567654 {
568- result [ i ] = byte . Parse ( value . AsSpan ( i * 2 , 2 ) , System . Globalization . NumberStyles . HexNumber , CultureInfo . InvariantCulture ) ;
655+ var slice = value . AsSpan ( i * 2 , 2 ) ;
656+ result [ i ] = byte . Parse ( slice , System . Globalization . NumberStyles . HexNumber , CultureInfo . InvariantCulture ) ;
569657 }
658+
570659 return result ;
571660 }
572661 else if ( type == typeof ( TimeSpan ) )
@@ -611,6 +700,8 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in
611700 Line = 1 ;
612701 string next ;
613702
703+ var intermediateData = new IntermediateData ( ) ;
704+
614705 while ( true )
615706 {
616707 try
@@ -619,11 +710,28 @@ public Datamodel Decode(string encoding, int encoding_version, string format, in
619710 { break ; }
620711
621712 try
622- { Decode_ParseElement ( next ) ; }
713+ { Decode_ParseElement ( next , reflectionParams , intermediateData ) ; }
623714 catch ( Exception err )
624715 { throw new CodecException ( $ "KeyValues2 decode failed on line { Line } :\n \n { err . Message } ", err ) ; }
625716 }
626717
718+ foreach ( var propList in intermediateData . PropertiesToAdd )
719+ {
720+ foreach ( var prop in propList . Value )
721+ {
722+ propList . Key . Add ( prop . Item1 , DM . AllElements [ prop . Item2 ] ) ;
723+ }
724+
725+ }
726+
727+ foreach ( var list in intermediateData . ListRefs )
728+ {
729+ foreach ( var id in list . Value )
730+ {
731+ list . Key . Add ( DM . AllElements [ id ] ) ;
732+ }
733+ }
734+
627735 return DM ;
628736 }
629737 #endregion
0 commit comments