55using System . Collections . Generic ;
66using System . Diagnostics ;
77using System . Diagnostics . CodeAnalysis ;
8+ using System . Globalization ;
89using System . Reflection ;
910using System . Text ;
1011using System . Xml . Schema ;
@@ -125,7 +126,7 @@ private void WriteMember(object? o, object? choiceSource, ElementAccessor[] elem
125126 }
126127 else
127128 {
128- WriteElements ( o , elements , text , choice , writeAccessors , memberTypeDesc . IsNullable ) ;
129+ WriteElements ( o , choiceSource , elements , text , choice , writeAccessors , memberTypeDesc . IsNullable ) ;
129130 }
130131 }
131132
@@ -150,11 +151,11 @@ private void WriteArray(object o, object? choiceSource, ElementAccessor[] elemen
150151 }
151152 }
152153
153- WriteArrayItems ( elements , text , choice , o ) ;
154+ WriteArrayItems ( elements , text , choice , o , choiceSource ) ;
154155 }
155156
156157 [ RequiresUnreferencedCode ( "calls WriteElements" ) ]
157- private void WriteArrayItems ( ElementAccessor [ ] elements , TextAccessor ? text , ChoiceIdentifierAccessor ? choice , object o )
158+ private void WriteArrayItems ( ElementAccessor [ ] elements , TextAccessor ? text , ChoiceIdentifierAccessor ? choice , object o , object ? choiceSources )
158159 {
159160 var arr = o as IList ;
160161
@@ -163,7 +164,8 @@ private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, Cho
163164 for ( int i = 0 ; i < arr . Count ; i ++ )
164165 {
165166 object ? ai = arr [ i ] ;
166- WriteElements ( ai , elements , text , choice , true , true ) ;
167+ var choiceSource = ( ( Array ? ) choiceSources ) ? . GetValue ( i ) ;
168+ WriteElements ( ai , choiceSource , elements , text , choice , true , true ) ;
167169 }
168170 }
169171 else
@@ -174,17 +176,18 @@ private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, Cho
174176 IEnumerator e = a . GetEnumerator ( ) ;
175177 if ( e != null )
176178 {
179+ int c = 0 ;
177180 while ( e . MoveNext ( ) )
178181 {
179182 object ai = e . Current ;
180- WriteElements ( ai , elements , text , choice , true , true ) ;
183+ var choiceSource = ( ( Array ? ) choiceSources ) ? . GetValue ( c ++ ) ;
184+ WriteElements ( ai , choiceSource , elements , text , choice , true , true ) ;
181185 }
182186 }
183187 }
184188 }
185-
186189 [ RequiresUnreferencedCode ( "calls CreateUnknownTypeException" ) ]
187- private void WriteElements ( object ? o , ElementAccessor [ ] elements , TextAccessor ? text , ChoiceIdentifierAccessor ? choice , bool writeAccessors , bool isNullable )
190+ private void WriteElements ( object ? o , object ? choiceSource , ElementAccessor [ ] elements , TextAccessor ? text , ChoiceIdentifierAccessor ? choice , bool writeAccessors , bool isNullable )
188191 {
189192 if ( elements . Length == 0 && text == null )
190193 return ;
@@ -222,16 +225,35 @@ private void WriteElements(object? o, ElementAccessor[] elements, TextAccessor?
222225 }
223226 else if ( choice != null )
224227 {
225- if ( o != null && o . GetType ( ) == element . Mapping ! . TypeDesc ! . Type )
228+ // This looks heavy - getting names of enums in string form for comparison rather than just comparing values.
229+ // But this faithfully mimics NetFx, and is necessary to prevent confusion between different enum types.
230+ // ie EnumType.ValueX could == 1, but TotallyDifferentEnumType.ValueY could also == 1.
231+ TypeDesc td = element . Mapping ! . TypeDesc ! ;
232+ bool enumUseReflection = choice . Mapping ! . TypeDesc ! . UseReflection ;
233+ string enumTypeName = choice . Mapping ! . TypeDesc ! . FullName ;
234+ string enumFullName = ( enumUseReflection ? "" : enumTypeName + ".@" ) + FindChoiceEnumValue ( element , ( EnumMapping ) choice . Mapping , enumUseReflection ) ;
235+ string choiceFullName = ( enumUseReflection ? "" : choiceSource ! . GetType ( ) . FullName + ".@" ) + choiceSource ! . ToString ( ) ;
236+
237+ if ( choiceFullName == enumFullName )
226238 {
227- WriteElement ( o , element , writeAccessors ) ;
228- return ;
239+ // Object is either non-null, or it is allowed to be null
240+ if ( o != null || ( ! isNullable || element . IsNullable ) )
241+ {
242+ // But if Object is non-null, it's got to match types
243+ if ( o != null && ! td . Type ! . IsAssignableFrom ( o ! . GetType ( ) ) )
244+ {
245+ throw CreateMismatchChoiceException ( td . FullName , choice . MemberName ! , enumFullName ) ;
246+ }
247+
248+ WriteElement ( o , element , writeAccessors ) ;
249+ return ;
250+ }
229251 }
230252 }
231253 else
232254 {
233255 TypeDesc td = element . IsUnbounded ? element . Mapping ! . TypeDesc ! . CreateArrayTypeDesc ( ) : element . Mapping ! . TypeDesc ! ;
234- if ( o ! . GetType ( ) == td . Type )
256+ if ( td . Type ! . IsAssignableFrom ( o ! . GetType ( ) ) )
235257 {
236258 WriteElement ( o , element , writeAccessors ) ;
237259 return ;
@@ -280,6 +302,58 @@ private void WriteElements(object? o, ElementAccessor[] elements, TextAccessor?
280302 }
281303 }
282304
305+ private static string FindChoiceEnumValue ( ElementAccessor element , EnumMapping choiceMapping , bool useReflection )
306+ {
307+ string ? enumValue = null ;
308+
309+ for ( int i = 0 ; i < choiceMapping . Constants ! . Length ; i ++ )
310+ {
311+ string xmlName = choiceMapping . Constants [ i ] . XmlName ;
312+
313+ if ( element . Any && element . Name . Length == 0 )
314+ {
315+ if ( xmlName == "##any:" )
316+ {
317+ if ( useReflection )
318+ enumValue = choiceMapping . Constants [ i ] . Value . ToString ( CultureInfo . InvariantCulture ) ;
319+ else
320+ enumValue = choiceMapping . Constants [ i ] . Name ;
321+ break ;
322+ }
323+ continue ;
324+ }
325+ int colon = xmlName . LastIndexOf ( ':' ) ;
326+ string ? choiceNs = colon < 0 ? choiceMapping . Namespace : xmlName . Substring ( 0 , colon ) ;
327+ string choiceName = colon < 0 ? xmlName : xmlName . Substring ( colon + 1 ) ;
328+
329+ if ( element . Name == choiceName )
330+ {
331+ if ( ( element . Form == XmlSchemaForm . Unqualified && string . IsNullOrEmpty ( choiceNs ) ) || element . Namespace == choiceNs )
332+ {
333+ if ( useReflection )
334+ enumValue = choiceMapping . Constants [ i ] . Value . ToString ( CultureInfo . InvariantCulture ) ;
335+ else
336+ enumValue = choiceMapping . Constants [ i ] . Name ;
337+ break ;
338+ }
339+ }
340+ }
341+
342+ if ( string . IsNullOrEmpty ( enumValue ) )
343+ {
344+ if ( element . Any && element . Name . Length == 0 )
345+ {
346+ // Type {0} is missing enumeration value '##any' for XmlAnyElementAttribute.
347+ throw new InvalidOperationException ( SR . Format ( SR . XmlChoiceMissingAnyValue , choiceMapping . TypeDesc ! . FullName ) ) ;
348+ }
349+ // Type {0} is missing value for '{1}'.
350+ throw new InvalidOperationException ( SR . Format ( SR . XmlChoiceMissingValue , choiceMapping . TypeDesc ! . FullName , element . Namespace + ":" + element . Name , element . Name , element . Namespace ) ) ;
351+ }
352+ if ( ! useReflection )
353+ CodeIdentifier . CheckValidIdentifier ( enumValue ) ;
354+ return enumValue ;
355+ }
356+
283357 private void WriteText ( object o , TextAccessor text )
284358 {
285359 if ( text . Mapping is PrimitiveMapping primitiveMapping )
@@ -376,7 +450,7 @@ private void WriteElement(object? o, ElementAccessor element, bool writeAccessor
376450 if ( o != null )
377451 {
378452 WriteStartElement ( name , ns , false ) ;
379- WriteArrayItems ( mapping . ElementsSortedByDerivation ! , null , null , o ) ;
453+ WriteArrayItems ( mapping . ElementsSortedByDerivation ! , null , null , o , null ) ;
380454 WriteEndElement ( ) ;
381455 }
382456 }
0 commit comments