2 // System.Xml.Serialization.XmlReflectionImporter
5 // Tim Coleman (tim@timcoleman.com)
6 // Erik LeBel (eriklebel@yahoo.ca)
7 // Lluis Sanchez Gual (lluis@ximian.com)
9 // Copyright (C) Tim Coleman, 2002
10 // (C) 2003 Erik LeBel
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.Collections.Generic;
35 using System.Globalization;
36 using System.Reflection;
37 using System.Xml.Schema;
39 namespace System.Xml.Serialization {
40 public class XmlReflectionImporter {
42 string initialDefaultNamespace;
43 XmlAttributeOverrides attributeOverrides;
44 ArrayList includedTypes;
45 ReflectionHelper helper = new ReflectionHelper();
46 int arrayChoiceCount = 1;
47 ArrayList relatedMaps = new ArrayList ();
48 bool allowPrivateTypes = false;
50 static readonly string errSimple = "Cannot serialize object of type '{0}'. Base " +
51 "type '{1}' has simpleContent and can be only extended by adding XmlAttribute " +
52 "elements. Please consider changing XmlText member of the base class to string array";
54 static readonly string errSimple2 = "Cannot serialize object of type '{0}'. " +
55 "Consider changing type of XmlText member '{1}' from '{2}' to string or string array";
59 public XmlReflectionImporter ()
64 public XmlReflectionImporter (string defaultNamespace)
65 : this (null, defaultNamespace)
69 public XmlReflectionImporter (XmlAttributeOverrides attributeOverrides)
70 : this (attributeOverrides, null)
74 public XmlReflectionImporter (XmlAttributeOverrides attributeOverrides, string defaultNamespace)
76 if (defaultNamespace == null)
77 this.initialDefaultNamespace = String.Empty;
79 this.initialDefaultNamespace = defaultNamespace;
81 if (attributeOverrides == null)
82 this.attributeOverrides = new XmlAttributeOverrides();
84 this.attributeOverrides = attributeOverrides;
89 helper = new ReflectionHelper();
94 internal bool AllowPrivateTypes
96 get { return allowPrivateTypes; }
97 set { allowPrivateTypes = value; }
100 #endregion // Constructors
104 public XmlMembersMapping ImportMembersMapping (string elementName,
106 XmlReflectionMember [] members,
107 bool hasWrapperElement)
109 return ImportMembersMapping (elementName, ns, members, hasWrapperElement, true);
114 XmlMembersMapping ImportMembersMapping (string elementName,
116 XmlReflectionMember[] members,
117 bool hasWrapperElement,
120 return ImportMembersMapping (elementName, ns, members, hasWrapperElement, rpc, true);
125 XmlMembersMapping ImportMembersMapping (string elementName,
127 XmlReflectionMember[] members,
128 bool hasWrapperElement,
132 return ImportMembersMapping (elementName, ns, members, hasWrapperElement, rpc, openModel, XmlMappingAccess.Read | XmlMappingAccess.Write);
135 [MonoTODO] // FIXME: handle writeAccessors, validate, and mapping access
137 XmlMembersMapping ImportMembersMapping (string elementName,
139 XmlReflectionMember[] members,
140 bool hasWrapperElement,
143 XmlMappingAccess access)
145 // Reset (); Disabled. See ChangeLog
147 ArrayList mapping = new ArrayList ();
148 for (int n=0; n<members.Length; n++)
150 XmlTypeMapMember mapMem = CreateMapMember (null, members[n], ns);
151 mapMem.GlobalIndex = n;
152 mapMem.CheckOptionalValueType (members);
153 mapping.Add (new XmlMemberMapping (members[n].MemberName, ns, mapMem, false));
155 elementName = XmlConvert.EncodeLocalName (elementName);
156 XmlMembersMapping mps = new XmlMembersMapping (elementName, ns, hasWrapperElement, false, (XmlMemberMapping[])mapping.ToArray (typeof(XmlMemberMapping)));
157 mps.RelatedMaps = relatedMaps;
158 mps.Format = SerializationFormat.Literal;
159 Type[] extraTypes = includedTypes != null ? (Type[])includedTypes.ToArray(typeof(Type)) : null;
161 mps.Source = new MembersSerializationSource (elementName, hasWrapperElement, members, false, true, ns, extraTypes);
162 if (allowPrivateTypes) mps.Source.CanBeGenerated = false;
167 public XmlTypeMapping ImportTypeMapping (Type type)
169 return ImportTypeMapping (type, null, null);
172 public XmlTypeMapping ImportTypeMapping (Type type, string defaultNamespace)
174 return ImportTypeMapping (type, null, defaultNamespace);
177 public XmlTypeMapping ImportTypeMapping (Type type, XmlRootAttribute root)
179 return ImportTypeMapping (type, root, null);
182 public XmlTypeMapping ImportTypeMapping (Type type, XmlRootAttribute root, string defaultNamespace)
185 throw new ArgumentNullException ("type");
187 if (type == typeof (void))
188 throw new NotSupportedException ("The type " + type.FullName + " may not be serialized.");
190 return ImportTypeMapping (TypeTranslator.GetTypeData (type), root,
194 internal XmlTypeMapping ImportTypeMapping (TypeData typeData, string defaultNamespace)
196 return ImportTypeMapping (typeData, (XmlRootAttribute) null,
200 private XmlTypeMapping ImportTypeMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
202 if (typeData == null)
203 throw new ArgumentNullException ("typeData");
205 if (typeData.Type == null)
206 throw new ArgumentException ("Specified TypeData instance does not have Type set.");
208 if (defaultNamespace == null) defaultNamespace = initialDefaultNamespace;
209 if (defaultNamespace == null) defaultNamespace = string.Empty;
214 switch (typeData.SchemaType) {
215 case SchemaTypes.Class: map = ImportClassMapping (typeData, root, defaultNamespace); break;
216 case SchemaTypes.Array: map = ImportListMapping (typeData, root, defaultNamespace, null, 0); break;
217 case SchemaTypes.XmlNode: map = ImportXmlNodeMapping (typeData, root, defaultNamespace); break;
218 case SchemaTypes.Primitive: map = ImportPrimitiveMapping (typeData, root, defaultNamespace); break;
219 case SchemaTypes.Enum: map = ImportEnumMapping (typeData, root, defaultNamespace); break;
220 case SchemaTypes.XmlSerializable: map = ImportXmlSerializableMapping (typeData, root, defaultNamespace); break;
221 default: throw new NotSupportedException ("Type " + typeData.Type.FullName + " not supported for XML stialization");
225 map.SetKey (typeData.Type.ToString ());
226 map.RelatedMaps = relatedMaps;
227 map.Format = SerializationFormat.Literal;
228 Type[] extraTypes = includedTypes != null ? (Type[]) includedTypes.ToArray (typeof (Type)) : null;
230 map.Source = new XmlTypeSerializationSource (typeData.Type, root, attributeOverrides, defaultNamespace, extraTypes);
231 if (allowPrivateTypes) map.Source.CanBeGenerated = false;
234 } catch (InvalidOperationException ex) {
235 throw new InvalidOperationException (string.Format (CultureInfo.InvariantCulture,
236 "There was an error reflecting type '{0}'.", typeData.Type.FullName), ex);
240 XmlTypeMapping CreateTypeMapping (TypeData typeData, XmlRootAttribute root, string defaultXmlType, string defaultNamespace)
242 bool hasTypeNamespace = !string.IsNullOrEmpty (defaultNamespace);
243 string rootNamespace = null;
244 string typeNamespace = null;
246 bool includeInSchema = true;
247 XmlAttributes atts = null;
248 bool nullable = CanBeNull (typeData);
250 if (defaultXmlType == null) defaultXmlType = typeData.XmlType;
252 if (!typeData.IsListType)
254 if (attributeOverrides != null)
255 atts = attributeOverrides[typeData.Type];
257 if (atts != null && typeData.SchemaType == SchemaTypes.Primitive)
258 throw new InvalidOperationException ("XmlRoot and XmlType attributes may not be specified for the type " + typeData.FullTypeName);
262 atts = new XmlAttributes (typeData.Type);
264 if (atts.XmlRoot != null && root == null)
267 if (atts.XmlType != null)
269 if (atts.XmlType.Namespace != null) {
270 typeNamespace = atts.XmlType.Namespace;
271 hasTypeNamespace = true;
274 if (atts.XmlType.TypeName != null && atts.XmlType.TypeName != string.Empty)
275 defaultXmlType = XmlConvert.EncodeLocalName (atts.XmlType.TypeName);
277 includeInSchema = atts.XmlType.IncludeInSchema;
280 elementName = defaultXmlType;
284 if (root.ElementName.Length != 0)
285 elementName = XmlConvert.EncodeLocalName(root.ElementName);
286 if (root.Namespace != null) {
287 rootNamespace = root.Namespace;
288 hasTypeNamespace = true;
290 nullable = root.IsNullable;
293 rootNamespace = rootNamespace ?? defaultNamespace ?? string.Empty;
294 typeNamespace = typeNamespace ?? rootNamespace;
297 switch (typeData.SchemaType) {
298 case SchemaTypes.XmlSerializable:
299 map = new XmlSerializableMapping (root, elementName, rootNamespace, typeData, defaultXmlType, typeNamespace);
301 case SchemaTypes.Primitive:
302 if (!typeData.IsXsdType)
303 map = new XmlTypeMapping (elementName, rootNamespace,
304 typeData, defaultXmlType, XmlSerializer.WsdlTypesNamespace);
306 map = new XmlTypeMapping (elementName, rootNamespace,
307 typeData, defaultXmlType, typeNamespace);
310 map = new XmlTypeMapping (elementName, rootNamespace, typeData, defaultXmlType, hasTypeNamespace ? typeNamespace : null);
314 map.IncludeInSchema = includeInSchema;
315 map.IsNullable = nullable;
316 relatedMaps.Add (map);
321 XmlTypeMapping ImportClassMapping (Type type, XmlRootAttribute root, string defaultNamespace, bool isBaseType = false)
323 TypeData typeData = TypeTranslator.GetTypeData (type);
324 return ImportClassMapping (typeData, root, defaultNamespace, isBaseType);
327 XmlTypeMapping ImportClassMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace, bool isBaseType = false)
329 Type type = typeData.Type;
331 if (!allowPrivateTypes && !isBaseType)
332 ReflectionHelper.CheckSerializableType (type, false);
334 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
335 if (map != null) return map;
337 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
338 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
339 helper.RegisterSchemaType (map, map.XmlType, map.XmlTypeNamespace);
343 ClassMap classMap = new ClassMap ();
344 map.ObjectMap = classMap;
346 var members = GetReflectionMembers (type);
347 bool? isOrderExplicit = null;
348 foreach (XmlReflectionMember rmember in members)
350 int? order = rmember.XmlAttributes.Order;
351 if (isOrderExplicit == null)
354 isOrderExplicit = (int) order >= 0;
356 else if (order != null && isOrderExplicit != ((int) order >= 0))
357 throw new InvalidOperationException ("Inconsistent XML sequence was detected. If there are XmlElement/XmlArray/XmlAnyElement attributes with explicit Order, then every other member must have an explicit order too.");
359 if (isOrderExplicit == true)
360 members.Sort ((m1, m2) => (int) m1.XmlAttributes.SortableOrder - (int) m2.XmlAttributes.SortableOrder);
362 foreach (XmlReflectionMember rmember in members)
364 string ns = map.XmlTypeNamespace;
365 if (rmember.XmlAttributes.XmlIgnore) continue;
366 if (rmember.DeclaringType != null && rmember.DeclaringType != type) {
367 XmlTypeMapping bmap = ImportClassMapping (rmember.DeclaringType, root, defaultNamespace, true);
368 if (bmap.HasXmlTypeNamespace)
369 ns = bmap.XmlTypeNamespace;
373 XmlTypeMapMember mem = CreateMapMember (type, rmember, ns);
374 mem.CheckOptionalValueType (type);
375 classMap.AddMember (mem);
376 } catch (Exception ex) {
377 throw new InvalidOperationException (string.Format (
378 CultureInfo.InvariantCulture, "There was an error" +
379 " reflecting field '{0}'.", rmember.MemberName), ex);
383 // Import extra classes
385 if (type == typeof (object) && includedTypes != null)
387 foreach (Type intype in includedTypes)
388 map.DerivedTypes.Add (ImportTypeMapping (intype, defaultNamespace));
391 // Register inheritance relations
393 if (type.BaseType != null)
395 XmlTypeMapping bmap = ImportClassMapping (type.BaseType, root, defaultNamespace, true);
396 ClassMap cbmap = bmap.ObjectMap as ClassMap;
398 if (type.BaseType != typeof (object)) {
400 if (!cbmap.HasSimpleContent)
401 classMap.SetCanBeSimpleType (false);
404 // At this point, derived classes of this map must be already registered
406 RegisterDerivedMap (bmap, map);
408 if (cbmap.HasSimpleContent && classMap.ElementMembers != null && classMap.ElementMembers.Count != 1)
409 throw new InvalidOperationException (String.Format (errSimple, map.TypeData.TypeName, map.BaseMap.TypeData.TypeName));
412 ImportIncludedTypes (type, defaultNamespace);
414 if (classMap.XmlTextCollector != null && !classMap.HasSimpleContent)
416 XmlTypeMapMember mem = classMap.XmlTextCollector;
417 if (mem.TypeData.Type != typeof(string) &&
418 mem.TypeData.Type != typeof(string[]) &&
419 mem.TypeData.Type != typeof(XmlNode[]) &&
420 mem.TypeData.Type != typeof(object[]))
422 throw new InvalidOperationException (String.Format (errSimple2, map.TypeData.TypeName, mem.Name, mem.TypeData.TypeName));
428 void RegisterDerivedMap (XmlTypeMapping map, XmlTypeMapping derivedMap)
430 map.DerivedTypes.Add (derivedMap);
431 map.DerivedTypes.AddRange (derivedMap.DerivedTypes);
433 if (map.BaseMap != null)
434 RegisterDerivedMap (map.BaseMap, derivedMap);
436 XmlTypeMapping obmap = ImportTypeMapping (typeof(object));
438 obmap.DerivedTypes.Add (derivedMap);
442 string GetTypeNamespace (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
444 string typeNamespace = null;
446 XmlAttributes atts = null;
447 if (!typeData.IsListType)
449 if (attributeOverrides != null)
450 atts = attributeOverrides[typeData.Type];
454 atts = new XmlAttributes (typeData.Type);
456 if (atts.XmlType != null)
458 if (atts.XmlType.Namespace != null && atts.XmlType.Namespace.Length != 0 && typeData.SchemaType != SchemaTypes.Enum)
459 typeNamespace = atts.XmlType.Namespace;
462 if (typeNamespace != null && typeNamespace.Length != 0) return typeNamespace;
464 if (atts.XmlRoot != null && root == null)
469 if (root.Namespace != null && root.Namespace.Length != 0)
470 return root.Namespace;
473 if (defaultNamespace == null) return "";
474 else return defaultNamespace;
477 XmlTypeMapping ImportListMapping (Type type, XmlRootAttribute root, string defaultNamespace, XmlAttributes atts, int nestingLevel)
479 TypeData typeData = TypeTranslator.GetTypeData (type);
480 return ImportListMapping (typeData, root, defaultNamespace, atts, nestingLevel);
483 XmlTypeMapping ImportListMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace, XmlAttributes atts, int nestingLevel)
485 Type type = typeData.Type;
486 ListMap obmap = new ListMap ();
488 if (!allowPrivateTypes)
489 ReflectionHelper.CheckSerializableType (type, true);
491 if (atts == null) atts = new XmlAttributes();
492 Type itemType = typeData.ListItemType;
494 // warning: byte[][] should not be considered multiarray
495 bool isMultiArray = (type.IsArray && (TypeTranslator.GetTypeData(itemType).SchemaType == SchemaTypes.Array) && itemType.IsArray);
497 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
499 foreach (XmlArrayItemAttribute att in atts.XmlArrayItems)
501 if (att.Namespace != null && att.Form == XmlSchemaForm.Unqualified)
502 throw new InvalidOperationException ("XmlArrayItemAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
503 if (att.NestingLevel != nestingLevel) continue;
504 Type elemType = (att.Type != null) ? att.Type : itemType;
505 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData(elemType, att.DataType));
506 elem.Namespace = att.Namespace != null ? att.Namespace : defaultNamespace;
507 if (elem.Namespace == null) elem.Namespace = "";
508 elem.Form = att.Form;
509 if (att.Form == XmlSchemaForm.Unqualified)
510 elem.Namespace = string.Empty;
511 elem.IsNullable = (!att.IsNullableSpecified || att.IsNullable) && CanBeNull (elem.TypeData);
512 elem.NestingLevel = att.NestingLevel;
515 elem.MappedType = ImportListMapping (elemType, null, elem.Namespace, atts, nestingLevel + 1);
516 } else if (elem.TypeData.IsComplexType) {
517 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
520 if (att.ElementName.Length != 0) {
521 elem.ElementName = XmlConvert.EncodeLocalName (att.ElementName);
522 } else if (elem.MappedType != null) {
523 elem.ElementName = elem.MappedType.ElementName;
525 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
533 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData (itemType));
535 elem.MappedType = ImportListMapping (itemType, null, defaultNamespace, atts, nestingLevel + 1);
536 else if (elem.TypeData.IsComplexType)
537 elem.MappedType = ImportTypeMapping (itemType, null, defaultNamespace);
539 if (elem.MappedType != null) {
540 elem.ElementName = elem.MappedType.XmlType;
542 elem.ElementName = TypeTranslator.GetTypeData (itemType).XmlType;
545 elem.Namespace = (defaultNamespace != null) ? defaultNamespace : "";
546 elem.IsNullable = CanBeNull (elem.TypeData);
550 obmap.ItemInfo = list;
552 // If there can be different element names (types) in the array, then its name cannot
553 // be "ArrayOfXXX" it must be something like ArrayOfChoiceNNN
556 if (list.Count > 1) {
557 baseName = "ArrayOfChoice" + (arrayChoiceCount++);
559 XmlTypeMapElementInfo elem = ((XmlTypeMapElementInfo) list[0]);
560 if (elem.MappedType != null) {
561 baseName = TypeTranslator.GetArrayName (elem.MappedType.XmlType);
563 baseName = TypeTranslator.GetArrayName (elem.ElementName);
567 // Avoid name colisions
570 string name = baseName;
573 XmlTypeMapping foundMap = helper.GetRegisteredSchemaType (name, defaultNamespace);
574 if (foundMap == null) nameCount = -1;
575 else if (obmap.Equals (foundMap.ObjectMap) && typeData.Type == foundMap.TypeData.Type) return foundMap;
576 else name = baseName + (nameCount++);
578 while (nameCount != -1);
580 XmlTypeMapping map = CreateTypeMapping (typeData, root, name, defaultNamespace);
581 map.ObjectMap = obmap;
583 // Register any of the including types as a derived class of object
584 XmlIncludeAttribute[] includes = (XmlIncludeAttribute[])type.GetCustomAttributes (typeof (XmlIncludeAttribute), false);
586 XmlTypeMapping objectMapping = ImportTypeMapping (typeof(object));
587 for (int i = 0; i < includes.Length; i++)
589 Type includedType = includes[i].Type;
590 objectMapping.DerivedTypes.Add(ImportTypeMapping (includedType, null, defaultNamespace));
593 // Register this map as a derived class of object
595 helper.RegisterSchemaType (map, name, defaultNamespace);
596 ImportTypeMapping (typeof(object)).DerivedTypes.Add (map);
601 XmlTypeMapping ImportXmlNodeMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
603 Type type = typeData.Type;
604 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
605 if (map != null) return map;
607 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
608 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
610 if (type.BaseType != null)
612 XmlTypeMapping bmap = ImportTypeMapping (type.BaseType, root, defaultNamespace);
613 if (type.BaseType != typeof (object))
616 RegisterDerivedMap (bmap, map);
622 XmlTypeMapping ImportPrimitiveMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
624 Type type = typeData.Type;
625 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
626 if (map != null) return map;
627 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
628 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
632 XmlTypeMapping ImportEnumMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
634 Type type = typeData.Type;
635 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
636 if (map != null) return map;
638 if (!allowPrivateTypes)
639 ReflectionHelper.CheckSerializableType (type, false);
641 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
642 map.IsNullable = false;
643 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
645 ArrayList members = new ArrayList();
646 string [] names = Enum.GetNames (type);
647 foreach (string name in names) {
648 FieldInfo field = type.GetField (name);
649 string xmlName = null;
650 if (field.IsDefined(typeof(XmlIgnoreAttribute), false))
652 object[] atts = field.GetCustomAttributes (typeof(XmlEnumAttribute), false);
653 if (atts.Length > 0) xmlName = ((XmlEnumAttribute)atts[0]).Name;
654 if (xmlName == null) xmlName = name;
655 long value = ((IConvertible) field.GetValue (null)).ToInt64 (CultureInfo.InvariantCulture);
656 members.Add (new EnumMap.EnumMapMember (xmlName, name, value));
659 bool isFlags = type.IsDefined (typeof (FlagsAttribute), false);
660 map.ObjectMap = new EnumMap ((EnumMap.EnumMapMember[])members.ToArray (typeof(EnumMap.EnumMapMember)), isFlags);
661 ImportTypeMapping (typeof(object)).DerivedTypes.Add (map);
665 XmlTypeMapping ImportXmlSerializableMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
667 Type type = typeData.Type;
668 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
669 if (map != null) return map;
671 if (!allowPrivateTypes)
672 ReflectionHelper.CheckSerializableType (type, false);
674 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
675 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
679 void ImportIncludedTypes (Type type, string defaultNamespace)
681 XmlIncludeAttribute[] includes = (XmlIncludeAttribute[])type.GetCustomAttributes (typeof (XmlIncludeAttribute), false);
682 for (int n=0; n<includes.Length; n++)
684 Type includedType = includes[n].Type;
685 ImportTypeMapping (includedType, null, defaultNamespace);
689 List<XmlReflectionMember> GetReflectionMembers (Type type)
691 // First we want to find the inheritance hierarchy in reverse order.
692 Type currentType = type;
693 ArrayList typeList = new ArrayList();
694 typeList.Add(currentType);
695 while (currentType != typeof(object))
697 currentType = currentType.BaseType; // Read the base type.
698 typeList.Insert(0, currentType); // Insert at 0 to reverse the order.
701 // Read all Fields via reflection.
702 ArrayList fieldList = new ArrayList();
703 FieldInfo[] tfields = type.GetFields (BindingFlags.Instance | BindingFlags.Public);
705 int currentIndex = 0;
706 foreach (FieldInfo field in tfields)
708 // This statement ensures fields are ordered starting from the base type.
709 if (currentType != field.DeclaringType)
711 currentType = field.DeclaringType;
714 fieldList.Insert(currentIndex++, field);
716 // Read all Properties via reflection.
717 ArrayList propList = new ArrayList();
718 PropertyInfo[] tprops = type.GetProperties (BindingFlags.Instance | BindingFlags.Public);
721 foreach (PropertyInfo prop in tprops)
723 // This statement ensures properties are ordered starting from the base type.
724 if (currentType != prop.DeclaringType)
726 currentType = prop.DeclaringType;
729 if (!prop.CanRead) continue;
730 if (prop.GetIndexParameters().Length > 0) continue;
731 propList.Insert(currentIndex++, prop);
733 var members = new List<XmlReflectionMember>();
736 // We now step through the type hierarchy from the base (object) through
737 // to the supplied class, as each step outputting all Fields, and then
738 // all Properties. This is the exact same ordering as .NET 1.0/1.1.
739 foreach (Type t in typeList)
741 // Add any fields matching the current DeclaringType.
742 while (fieldIndex < fieldList.Count)
744 FieldInfo field = (FieldInfo)fieldList[fieldIndex];
745 if (field.DeclaringType==t)
748 XmlAttributes atts = attributeOverrides[type, field.Name];
749 if (atts == null) atts = new XmlAttributes (field);
750 if (atts.XmlIgnore) continue;
751 XmlReflectionMember member = new XmlReflectionMember(field.Name, field.FieldType, atts);
752 member.DeclaringType = field.DeclaringType;
758 // Add any properties matching the current DeclaringType.
759 while (propIndex < propList.Count)
761 PropertyInfo prop = (PropertyInfo)propList[propIndex];
762 if (prop.DeclaringType==t)
765 XmlAttributes atts = attributeOverrides[type, prop.Name];
766 if (atts == null) atts = new XmlAttributes (prop);
767 if (atts.XmlIgnore) continue;
768 if (!prop.CanWrite) {
769 if (prop.PropertyType.IsInterface && typeof (IEnumerable).IsAssignableFrom (prop.PropertyType)) continue;
770 if (prop.PropertyType.IsGenericType && TypeData.GetGenericListItemType (prop.PropertyType) == null) continue; // check this before calling GetTypeData() which raises error for missing Add(). See bug #704813.
771 if (TypeTranslator.GetTypeData (prop.PropertyType).SchemaType != SchemaTypes.Array || prop.PropertyType.IsArray) continue;
773 XmlReflectionMember member = new XmlReflectionMember(prop.Name, prop.PropertyType, atts);
774 member.DeclaringType = prop.DeclaringType;
784 private XmlTypeMapMember CreateMapMember (Type declaringType, XmlReflectionMember rmember, string defaultNamespace)
786 XmlTypeMapMember mapMember;
787 XmlAttributes atts = rmember.XmlAttributes;
788 TypeData typeData = TypeTranslator.GetTypeData (rmember.MemberType);
790 if (atts.XmlArray != null) {
791 if (atts.XmlArray.Namespace != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
792 throw new InvalidOperationException ("XmlArrayAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
793 if (typeData.SchemaType != SchemaTypes.Array &&
794 !(typeData.SchemaType == SchemaTypes.Primitive && typeData.Type == typeof (byte [])))
795 throw new InvalidOperationException ("XmlArrayAttribute can be applied to members of array or collection type.");
798 if (atts.XmlAnyAttribute != null)
800 if ( (rmember.MemberType.FullName == "System.Xml.XmlAttribute[]") ||
801 (rmember.MemberType.FullName == "System.Xml.XmlNode[]") )
803 mapMember = new XmlTypeMapMemberAnyAttribute();
806 throw new InvalidOperationException ("XmlAnyAttributeAttribute can only be applied to members of type XmlAttribute[] or XmlNode[]");
809 if (atts.XmlAnyElements != null && atts.XmlAnyElements.Count > 0)
811 // no XmlNode type check is done here (seealso: bug #553032).
812 XmlTypeMapMemberAnyElement member = new XmlTypeMapMemberAnyElement();
813 member.ElementInfo = ImportAnyElementInfo (defaultNamespace, rmember, member, atts);
818 XmlTypeMapMemberNamespaces mapNamespaces = new XmlTypeMapMemberNamespaces ();
819 mapMember = mapNamespaces;
821 else if (atts.XmlAttribute != null)
825 if (atts.XmlElements != null && atts.XmlElements.Count > 0)
826 throw new Exception ("XmlAttributeAttribute and XmlElementAttribute cannot be applied to the same member");
828 XmlTypeMapMemberAttribute mapAttribute = new XmlTypeMapMemberAttribute ();
829 if (atts.XmlAttribute.AttributeName.Length == 0)
830 mapAttribute.AttributeName = rmember.MemberName;
832 mapAttribute.AttributeName = atts.XmlAttribute.AttributeName;
834 mapAttribute.AttributeName = XmlConvert.EncodeName (mapAttribute.AttributeName);
836 if (typeData.IsComplexType)
837 mapAttribute.MappedType = ImportTypeMapping (typeData.Type, null, defaultNamespace);
839 if (atts.XmlAttribute.Namespace != null && atts.XmlAttribute.Namespace != defaultNamespace)
841 if (atts.XmlAttribute.Form == XmlSchemaForm.Unqualified)
842 throw new InvalidOperationException ("The Form property may not be 'Unqualified' when an explicit Namespace property is present");
843 mapAttribute.Form = XmlSchemaForm.Qualified;
844 mapAttribute.Namespace = atts.XmlAttribute.Namespace;
848 mapAttribute.Form = atts.XmlAttribute.Form;
849 if (atts.XmlAttribute.Form == XmlSchemaForm.Qualified)
850 mapAttribute.Namespace = defaultNamespace;
852 mapAttribute.Namespace = "";
855 typeData = TypeTranslator.GetTypeData(rmember.MemberType, atts.XmlAttribute.DataType);
856 mapMember = mapAttribute;
858 else if (typeData.SchemaType == SchemaTypes.Array)
860 // If the member has a single XmlElementAttribute and the type is the type of the member,
861 // then it is not a flat list
863 if (atts.XmlElements.Count > 1 ||
864 (atts.XmlElements.Count == 1 && atts.XmlElements[0].Type != typeData.Type) ||
865 (atts.XmlText != null))
869 // check that it does not have XmlArrayAttribute
870 if (atts.XmlArray != null)
871 throw new InvalidOperationException ("XmlArrayAttribute cannot be used with members which also attributed with XmlElementAttribute or XmlTextAttribute.");
873 XmlTypeMapMemberFlatList member = new XmlTypeMapMemberFlatList ();
874 member.ListMap = new ListMap ();
875 member.ListMap.ItemInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName (rmember.MemberName), defaultNamespace, typeData.ListItemType, member, atts);
876 member.ElementInfo = member.ListMap.ItemInfo;
877 member.ListMap.ChoiceMember = member.ChoiceMember;
884 XmlTypeMapMemberList member = new XmlTypeMapMemberList ();
886 // Creates an ElementInfo that identifies the array instance.
887 member.ElementInfo = new XmlTypeMapElementInfoList();
888 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, typeData);
889 elem.ElementName = XmlConvert.EncodeLocalName((atts.XmlArray != null && atts.XmlArray.ElementName.Length != 0) ? atts.XmlArray.ElementName : rmember.MemberName);
890 // note that it could be changed below (when Form is Unqualified)
891 elem.Namespace = (atts.XmlArray != null && atts.XmlArray.Namespace != null) ? atts.XmlArray.Namespace : defaultNamespace;
892 elem.MappedType = ImportListMapping (rmember.MemberType, null, elem.Namespace, atts, 0);
893 elem.IsNullable = (atts.XmlArray != null) ? atts.XmlArray.IsNullable : false;
894 elem.Form = (atts.XmlArray != null) ? atts.XmlArray.Form : XmlSchemaForm.Qualified;
895 elem.ExplicitOrder = (atts.XmlArray != null) ? atts.XmlArray.Order : -1;
896 // This is a bit tricky, but is done
897 // after filling descendant members, so
898 // that array items could be serialized
899 // with proper namespace.
900 if (atts.XmlArray != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
901 elem.Namespace = String.Empty;
903 member.ElementInfo.Add (elem);
911 XmlTypeMapMemberElement member = new XmlTypeMapMemberElement ();
912 member.ElementInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName(rmember.MemberName), defaultNamespace, rmember.MemberType, member, atts);
916 mapMember.DefaultValue = GetDefaultValue (typeData, atts.XmlDefaultValue);
917 mapMember.TypeData = typeData;
918 mapMember.Name = rmember.MemberName;
919 mapMember.IsReturnValue = rmember.IsReturnValue;
923 XmlTypeMapElementInfoList ImportElementInfo (Type cls, string defaultName, string defaultNamespace, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts)
925 EnumMap choiceEnumMap = null;
926 Type choiceEnumType = null;
928 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
929 ImportTextElementInfo (list, defaultType, member, atts, defaultNamespace);
931 if (atts.XmlChoiceIdentifier != null) {
933 throw new InvalidOperationException ("XmlChoiceIdentifierAttribute not supported in this context.");
935 member.ChoiceMember = atts.XmlChoiceIdentifier.MemberName;
936 MemberInfo[] mems = cls.GetMember (member.ChoiceMember, BindingFlags.Instance|BindingFlags.Public);
938 if (mems.Length == 0)
939 throw new InvalidOperationException ("Choice member '" + member.ChoiceMember + "' not found in class '" + cls);
941 if (mems[0] is PropertyInfo) {
942 PropertyInfo pi = (PropertyInfo)mems[0];
943 if (!pi.CanWrite || !pi.CanRead)
944 throw new InvalidOperationException ("Choice property '" + member.ChoiceMember + "' must be read/write.");
945 choiceEnumType = pi.PropertyType;
947 else choiceEnumType = ((FieldInfo)mems[0]).FieldType;
949 member.ChoiceTypeData = TypeTranslator.GetTypeData (choiceEnumType);
951 if (choiceEnumType.IsArray)
952 choiceEnumType = choiceEnumType.GetElementType ();
954 choiceEnumMap = ImportTypeMapping (choiceEnumType).ObjectMap as EnumMap;
955 if (choiceEnumMap == null)
956 throw new InvalidOperationException ("The member '" + mems[0].Name + "' is not a valid target for XmlChoiceIdentifierAttribute.");
959 if (atts.XmlElements.Count == 0 && list.Count == 0)
961 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType));
962 elem.ElementName = defaultName;
963 elem.Namespace = defaultNamespace;
964 if (elem.TypeData.IsComplexType)
965 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
969 bool multiType = (atts.XmlElements.Count > 1);
970 foreach (XmlElementAttribute att in atts.XmlElements)
972 Type elemType = (att.Type != null) ? att.Type : defaultType;
973 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(elemType, att.DataType));
974 elem.Form = att.Form;
975 if (elem.Form != XmlSchemaForm.Unqualified)
976 elem.Namespace = (att.Namespace != null) ? att.Namespace : defaultNamespace;
978 // elem may already be nullable, and IsNullable property in XmlElement is false by default
979 if (att.IsNullable && !elem.IsNullable)
980 elem.IsNullable = att.IsNullable;
982 elem.ExplicitOrder = att.Order;
984 if (elem.IsNullable && !elem.TypeData.IsNullable)
985 throw new InvalidOperationException ("IsNullable may not be 'true' for value type " + elem.TypeData.FullTypeName + " in member '" + defaultName + "'");
987 if (elem.TypeData.IsComplexType)
989 if (att.DataType.Length != 0) throw new InvalidOperationException (
990 string.Format(CultureInfo.InvariantCulture, "'{0}' is "
991 + "an invalid value for '{1}.{2}' of type '{3}'. "
992 + "The property may only be specified for primitive types.",
993 att.DataType, cls.FullName, defaultName,
994 elem.TypeData.FullTypeName));
995 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
998 if (att.ElementName.Length != 0) {
999 elem.ElementName = XmlConvert.EncodeLocalName(att.ElementName);
1000 } else if (multiType) {
1001 if (elem.MappedType != null) {
1002 elem.ElementName = elem.MappedType.ElementName;
1004 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
1007 elem.ElementName = defaultName;
1010 if (choiceEnumMap != null) {
1011 string cname = choiceEnumMap.GetEnumName (choiceEnumType.FullName, elem.ElementName);
1012 if (cname == null && elem.Namespace != null)
1013 cname = choiceEnumMap.GetEnumName (choiceEnumType.FullName,
1014 elem.Namespace.ToString () + ":" + elem.ElementName);
1016 throw new InvalidOperationException (string.Format (
1017 CultureInfo.InvariantCulture, "Type {0} is missing"
1018 + " enumeration value '{1}' for element '{1} from"
1019 + " namespace '{2}'.", choiceEnumType, elem.ElementName,
1021 elem.ChoiceValue = Enum.Parse (choiceEnumType, cname, false);
1029 XmlTypeMapElementInfoList ImportAnyElementInfo (string defaultNamespace, XmlReflectionMember rmember, XmlTypeMapMemberElement member, XmlAttributes atts)
1031 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
1033 ImportTextElementInfo (list, rmember.MemberType, member, atts, defaultNamespace);
1035 foreach (XmlAnyElementAttribute att in atts.XmlAnyElements)
1037 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(typeof(XmlElement)));
1038 if (att.Name.Length != 0)
1040 elem.ElementName = XmlConvert.EncodeLocalName(att.Name);
1041 elem.Namespace = (att.Namespace != null) ? att.Namespace : "";
1045 elem.IsUnnamedAnyElement = true;
1046 elem.Namespace = defaultNamespace;
1047 if (att.Namespace != null)
1048 throw new InvalidOperationException ("The element " + rmember.MemberName + " has been attributed with an XmlAnyElementAttribute and a namespace '" + att.Namespace + "', but no name. When a namespace is supplied, a name is also required. Supply a name or remove the namespace.");
1050 elem.ExplicitOrder = att.Order;
1056 void ImportTextElementInfo (XmlTypeMapElementInfoList list, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts, string defaultNamespace)
1058 if (atts.XmlText != null)
1060 member.IsXmlTextCollector = true;
1061 if (atts.XmlText.Type != null) {
1062 TypeData td = TypeTranslator.GetTypeData (defaultType);
1063 if ((td.SchemaType == SchemaTypes.Primitive || td.SchemaType == SchemaTypes.Enum) && atts.XmlText.Type != defaultType) {
1064 throw new InvalidOperationException ("The type for XmlText may not be specified for primitive types.");
1066 defaultType = atts.XmlText.Type;
1068 if (defaultType == typeof(XmlNode)) defaultType = typeof(XmlText); // Nodes must be text nodes
1070 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType, atts.XmlText.DataType));
1072 if (elem.TypeData.SchemaType != SchemaTypes.Primitive &&
1073 elem.TypeData.SchemaType != SchemaTypes.Enum &&
1074 elem.TypeData.SchemaType != SchemaTypes.XmlNode &&
1075 !(elem.TypeData.SchemaType == SchemaTypes.Array && elem.TypeData.ListItemTypeData.SchemaType == SchemaTypes.XmlNode)
1077 throw new InvalidOperationException ("XmlText cannot be used to encode complex types");
1079 if (elem.TypeData.IsComplexType)
1080 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
1081 elem.IsTextElement = true;
1082 elem.WrappedElement = false;
1087 bool CanBeNull (TypeData type)
1089 return !type.Type.IsValueType || type.IsNullable;
1092 public void IncludeType (Type type)
1095 throw new ArgumentNullException ("type");
1097 if (includedTypes == null) includedTypes = new ArrayList ();
1098 if (!includedTypes.Contains (type))
1099 includedTypes.Add (type);
1101 if (relatedMaps.Count > 0) {
1102 foreach (XmlTypeMapping map in (ArrayList) relatedMaps.Clone ()) {
1103 if (map.TypeData.Type == typeof(object))
1104 map.DerivedTypes.Add (ImportTypeMapping (type));
1109 public void IncludeTypes (ICustomAttributeProvider provider)
1111 object[] ats = provider.GetCustomAttributes (typeof(XmlIncludeAttribute), true);
1113 foreach (XmlIncludeAttribute at in ats)
1114 IncludeType (at.Type);
1117 private object GetDefaultValue (TypeData typeData, object defaultValue)
1119 if (defaultValue == DBNull.Value || typeData.SchemaType != SchemaTypes.Enum)
1120 return defaultValue;
1122 // get string representation of enum value
1123 string namedValue = Enum.Format (typeData.Type, defaultValue, "g");
1124 // get decimal representation of enum value
1125 string decimalValue = Enum.Format (typeData.Type, defaultValue, "d");
1126 // if decimal representation matches string representation, then
1127 // the value is not defined in the enum type (as the "g" format
1128 // will return the decimal equivalent of the value if the value
1129 // is not equal to a combination of named enumerated constants
1130 if (namedValue == decimalValue) {
1131 string msg = string.Format (CultureInfo.InvariantCulture,
1132 "Value '{0}' cannot be converted to {1}.", defaultValue,
1133 defaultValue.GetType ().FullName);
1134 throw new InvalidOperationException (msg);
1137 // XmlSerializer expects integral enum value
1138 //return namedValue.Replace (',', ' ');
1139 return defaultValue;
1142 #endregion // Methods