5
5
using System . Collections . Generic ;
6
6
using System . Diagnostics ;
7
7
using System . Diagnostics . CodeAnalysis ;
8
+ using System . Globalization ;
8
9
using System . Reflection ;
9
10
using System . Text ;
10
11
using System . Xml . Schema ;
@@ -125,7 +126,7 @@ private void WriteMember(object? o, object? choiceSource, ElementAccessor[] elem
125
126
}
126
127
else
127
128
{
128
- WriteElements ( o , elements , text , choice , writeAccessors , memberTypeDesc . IsNullable ) ;
129
+ WriteElements ( o , choiceSource , elements , text , choice , writeAccessors , memberTypeDesc . IsNullable ) ;
129
130
}
130
131
}
131
132
@@ -150,11 +151,11 @@ private void WriteArray(object o, object? choiceSource, ElementAccessor[] elemen
150
151
}
151
152
}
152
153
153
- WriteArrayItems ( elements , text , choice , o ) ;
154
+ WriteArrayItems ( elements , text , choice , o , choiceSource ) ;
154
155
}
155
156
156
157
[ 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 )
158
159
{
159
160
var arr = o as IList ;
160
161
@@ -163,7 +164,8 @@ private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, Cho
163
164
for ( int i = 0 ; i < arr . Count ; i ++ )
164
165
{
165
166
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 ) ;
167
169
}
168
170
}
169
171
else
@@ -174,17 +176,18 @@ private void WriteArrayItems(ElementAccessor[] elements, TextAccessor? text, Cho
174
176
IEnumerator e = a . GetEnumerator ( ) ;
175
177
if ( e != null )
176
178
{
179
+ int c = 0 ;
177
180
while ( e . MoveNext ( ) )
178
181
{
179
182
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 ) ;
181
185
}
182
186
}
183
187
}
184
188
}
185
-
186
189
[ 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 )
188
191
{
189
192
if ( elements . Length == 0 && text == null )
190
193
return ;
@@ -222,16 +225,35 @@ private void WriteElements(object? o, ElementAccessor[] elements, TextAccessor?
222
225
}
223
226
else if ( choice != null )
224
227
{
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 )
226
238
{
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
+ }
229
251
}
230
252
}
231
253
else
232
254
{
233
255
TypeDesc td = element . IsUnbounded ? element . Mapping ! . TypeDesc ! . CreateArrayTypeDesc ( ) : element . Mapping ! . TypeDesc ! ;
234
- if ( o ! . GetType ( ) == td . Type )
256
+ if ( td . Type ! . IsAssignableFrom ( o ! . GetType ( ) ) )
235
257
{
236
258
WriteElement ( o , element , writeAccessors ) ;
237
259
return ;
@@ -280,6 +302,58 @@ private void WriteElements(object? o, ElementAccessor[] elements, TextAccessor?
280
302
}
281
303
}
282
304
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
+
283
357
private void WriteText ( object o , TextAccessor text )
284
358
{
285
359
if ( text . Mapping is PrimitiveMapping primitiveMapping )
@@ -376,7 +450,7 @@ private void WriteElement(object? o, ElementAccessor element, bool writeAccessor
376
450
if ( o != null )
377
451
{
378
452
WriteStartElement ( name , ns , false ) ;
379
- WriteArrayItems ( mapping . ElementsSortedByDerivation ! , null , null , o ) ;
453
+ WriteArrayItems ( mapping . ElementsSortedByDerivation ! , null , null , o , null ) ;
380
454
WriteEndElement ( ) ;
381
455
}
382
456
}
0 commit comments