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);
116 XmlMembersMapping ImportMembersMapping (string elementName,
118 XmlReflectionMember[] members,
119 bool hasWrapperElement,
122 return ImportMembersMapping (elementName, ns, members, hasWrapperElement, rpc, true);
129 XmlMembersMapping ImportMembersMapping (string elementName,
131 XmlReflectionMember[] members,
132 bool hasWrapperElement,
136 return ImportMembersMapping (elementName, ns, members, hasWrapperElement, rpc, openModel, XmlMappingAccess.Read | XmlMappingAccess.Write);
140 [MonoTODO] // FIXME: handle writeAccessors, validate, and mapping access
143 XmlMembersMapping ImportMembersMapping (string elementName,
145 XmlReflectionMember[] members,
146 bool hasWrapperElement,
149 XmlMappingAccess access)
151 // Reset (); Disabled. See ChangeLog
153 ArrayList mapping = new ArrayList ();
154 for (int n=0; n<members.Length; n++)
156 XmlTypeMapMember mapMem = CreateMapMember (null, members[n], ns);
157 mapMem.GlobalIndex = n;
158 mapMem.CheckOptionalValueType (members);
159 mapping.Add (new XmlMemberMapping (members[n].MemberName, ns, mapMem, false));
161 elementName = XmlConvert.EncodeLocalName (elementName);
162 XmlMembersMapping mps = new XmlMembersMapping (elementName, ns, hasWrapperElement, false, (XmlMemberMapping[])mapping.ToArray (typeof(XmlMemberMapping)));
163 mps.RelatedMaps = relatedMaps;
164 mps.Format = SerializationFormat.Literal;
165 Type[] extraTypes = includedTypes != null ? (Type[])includedTypes.ToArray(typeof(Type)) : null;
167 mps.Source = new MembersSerializationSource (elementName, hasWrapperElement, members, false, true, ns, extraTypes);
168 if (allowPrivateTypes) mps.Source.CanBeGenerated = false;
173 public XmlTypeMapping ImportTypeMapping (Type type)
175 return ImportTypeMapping (type, null, null);
178 public XmlTypeMapping ImportTypeMapping (Type type, string defaultNamespace)
180 return ImportTypeMapping (type, null, defaultNamespace);
183 public XmlTypeMapping ImportTypeMapping (Type type, XmlRootAttribute root)
185 return ImportTypeMapping (type, root, null);
188 public XmlTypeMapping ImportTypeMapping (Type type, XmlRootAttribute root, string defaultNamespace)
191 throw new ArgumentNullException ("type");
193 if (type == typeof (void))
194 throw new NotSupportedException ("The type " + type.FullName + " may not be serialized.");
196 return ImportTypeMapping (TypeTranslator.GetTypeData (type), root,
200 internal XmlTypeMapping ImportTypeMapping (TypeData typeData, string defaultNamespace)
202 return ImportTypeMapping (typeData, (XmlRootAttribute) null,
206 private XmlTypeMapping ImportTypeMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
208 if (typeData == null)
209 throw new ArgumentNullException ("typeData");
211 if (typeData.Type == null)
212 throw new ArgumentException ("Specified TypeData instance does not have Type set.");
214 if (defaultNamespace == null) defaultNamespace = initialDefaultNamespace;
215 if (defaultNamespace == null) defaultNamespace = string.Empty;
220 switch (typeData.SchemaType) {
221 case SchemaTypes.Class: map = ImportClassMapping (typeData, root, defaultNamespace); break;
222 case SchemaTypes.Array: map = ImportListMapping (typeData, root, defaultNamespace, null, 0); break;
223 case SchemaTypes.XmlNode: map = ImportXmlNodeMapping (typeData, root, defaultNamespace); break;
224 case SchemaTypes.Primitive: map = ImportPrimitiveMapping (typeData, root, defaultNamespace); break;
225 case SchemaTypes.Enum: map = ImportEnumMapping (typeData, root, defaultNamespace); break;
226 case SchemaTypes.XmlSerializable: map = ImportXmlSerializableMapping (typeData, root, defaultNamespace); break;
227 default: throw new NotSupportedException ("Type " + typeData.Type.FullName + " not supported for XML stialization");
232 map.SetKey (typeData.Type.ToString ());
234 map.RelatedMaps = relatedMaps;
235 map.Format = SerializationFormat.Literal;
236 Type[] extraTypes = includedTypes != null ? (Type[]) includedTypes.ToArray (typeof (Type)) : null;
238 map.Source = new XmlTypeSerializationSource (typeData.Type, root, attributeOverrides, defaultNamespace, extraTypes);
239 if (allowPrivateTypes) map.Source.CanBeGenerated = false;
242 } catch (InvalidOperationException ex) {
243 throw new InvalidOperationException (string.Format (CultureInfo.InvariantCulture,
244 "There was an error reflecting type '{0}'.", typeData.Type.FullName), ex);
248 XmlTypeMapping CreateTypeMapping (TypeData typeData, XmlRootAttribute root, string defaultXmlType, string defaultNamespace)
250 string rootNamespace = defaultNamespace;
251 string typeNamespace = null;
253 bool includeInSchema = true;
254 XmlAttributes atts = null;
255 bool nullable = CanBeNull (typeData);
257 if (defaultXmlType == null) defaultXmlType = typeData.XmlType;
259 if (!typeData.IsListType)
261 if (attributeOverrides != null)
262 atts = attributeOverrides[typeData.Type];
264 if (atts != null && typeData.SchemaType == SchemaTypes.Primitive)
265 throw new InvalidOperationException ("XmlRoot and XmlType attributes may not be specified for the type " + typeData.FullTypeName);
269 atts = new XmlAttributes (typeData.Type);
271 if (atts.XmlRoot != null && root == null)
274 if (atts.XmlType != null)
276 if (atts.XmlType.Namespace != null)
277 typeNamespace = atts.XmlType.Namespace;
279 if (atts.XmlType.TypeName != null && atts.XmlType.TypeName != string.Empty)
280 defaultXmlType = XmlConvert.EncodeLocalName (atts.XmlType.TypeName);
282 includeInSchema = atts.XmlType.IncludeInSchema;
285 elementName = defaultXmlType;
289 if (root.ElementName.Length != 0)
290 elementName = XmlConvert.EncodeLocalName(root.ElementName);
291 if (root.Namespace != null)
292 rootNamespace = root.Namespace;
293 nullable = root.IsNullable;
296 if (rootNamespace == null) rootNamespace = "";
297 if (typeNamespace == null) typeNamespace = rootNamespace;
300 switch (typeData.SchemaType) {
301 case SchemaTypes.XmlSerializable:
302 map = new XmlSerializableMapping (root, elementName, rootNamespace, typeData, defaultXmlType, typeNamespace);
304 case SchemaTypes.Primitive:
305 if (!typeData.IsXsdType)
306 map = new XmlTypeMapping (elementName, rootNamespace,
307 typeData, defaultXmlType, XmlSerializer.WsdlTypesNamespace);
309 map = new XmlTypeMapping (elementName, rootNamespace,
310 typeData, defaultXmlType, typeNamespace);
313 map = new XmlTypeMapping (elementName, rootNamespace, typeData, defaultXmlType, typeNamespace);
317 map.IncludeInSchema = includeInSchema;
318 map.IsNullable = nullable;
319 relatedMaps.Add (map);
324 XmlTypeMapping ImportClassMapping (Type type, XmlRootAttribute root, string defaultNamespace)
326 TypeData typeData = TypeTranslator.GetTypeData (type);
327 return ImportClassMapping (typeData, root, defaultNamespace);
330 XmlTypeMapping ImportClassMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
332 Type type = typeData.Type;
334 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
335 if (map != null) return map;
337 if (!allowPrivateTypes)
338 ReflectionHelper.CheckSerializableType (type, false);
340 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
341 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
342 helper.RegisterSchemaType (map, map.XmlType, map.XmlTypeNamespace);
346 ClassMap classMap = new ClassMap ();
347 map.ObjectMap = classMap;
349 var members = GetReflectionMembers (type);
350 bool? isOrderExplicit = null;
351 foreach (XmlReflectionMember rmember in members)
353 int? order = rmember.XmlAttributes.Order;
354 if (isOrderExplicit == null)
357 isOrderExplicit = (int) order >= 0;
359 else if (order != null && isOrderExplicit != ((int) order >= 0))
360 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.");
362 if (isOrderExplicit == true)
363 members.Sort ((m1, m2) => (int) m1.XmlAttributes.SortableOrder - (int) m2.XmlAttributes.SortableOrder);
365 foreach (XmlReflectionMember rmember in members)
367 string ns = map.XmlTypeNamespace;
368 if (rmember.XmlAttributes.XmlIgnore) continue;
369 if (rmember.DeclaringType != null && rmember.DeclaringType != type) {
370 XmlTypeMapping bmap = ImportClassMapping (rmember.DeclaringType, root, defaultNamespace);
371 ns = bmap.XmlTypeNamespace;
375 XmlTypeMapMember mem = CreateMapMember (type, rmember, ns);
376 mem.CheckOptionalValueType (type);
377 classMap.AddMember (mem);
378 } catch (Exception ex) {
379 throw new InvalidOperationException (string.Format (
380 CultureInfo.InvariantCulture, "There was an error" +
381 " reflecting field '{0}'.", rmember.MemberName), ex);
385 // Import extra classes
387 if (type == typeof (object) && includedTypes != null)
389 foreach (Type intype in includedTypes)
390 map.DerivedTypes.Add (ImportTypeMapping (intype, defaultNamespace));
393 // Register inheritance relations
395 if (type.BaseType != null)
397 XmlTypeMapping bmap = ImportClassMapping (type.BaseType, root, defaultNamespace);
398 ClassMap cbmap = bmap.ObjectMap as ClassMap;
400 if (type.BaseType != typeof (object)) {
402 if (!cbmap.HasSimpleContent)
403 classMap.SetCanBeSimpleType (false);
406 // At this point, derived classes of this map must be already registered
408 RegisterDerivedMap (bmap, map);
410 if (cbmap.HasSimpleContent && classMap.ElementMembers != null && classMap.ElementMembers.Count != 1)
411 throw new InvalidOperationException (String.Format (errSimple, map.TypeData.TypeName, map.BaseMap.TypeData.TypeName));
414 ImportIncludedTypes (type, defaultNamespace);
416 if (classMap.XmlTextCollector != null && !classMap.HasSimpleContent)
418 XmlTypeMapMember mem = classMap.XmlTextCollector;
419 if (mem.TypeData.Type != typeof(string) &&
420 mem.TypeData.Type != typeof(string[]) &&
422 mem.TypeData.Type != typeof(XmlNode[]) &&
424 mem.TypeData.Type != typeof(object[]))
426 throw new InvalidOperationException (String.Format (errSimple2, map.TypeData.TypeName, mem.Name, mem.TypeData.TypeName));
432 void RegisterDerivedMap (XmlTypeMapping map, XmlTypeMapping derivedMap)
434 map.DerivedTypes.Add (derivedMap);
435 map.DerivedTypes.AddRange (derivedMap.DerivedTypes);
437 if (map.BaseMap != null)
438 RegisterDerivedMap (map.BaseMap, derivedMap);
440 XmlTypeMapping obmap = ImportTypeMapping (typeof(object));
442 obmap.DerivedTypes.Add (derivedMap);
446 string GetTypeNamespace (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
448 string typeNamespace = null;
450 XmlAttributes atts = null;
451 if (!typeData.IsListType)
453 if (attributeOverrides != null)
454 atts = attributeOverrides[typeData.Type];
458 atts = new XmlAttributes (typeData.Type);
460 if (atts.XmlType != null)
462 if (atts.XmlType.Namespace != null && atts.XmlType.Namespace.Length != 0 && typeData.SchemaType != SchemaTypes.Enum)
463 typeNamespace = atts.XmlType.Namespace;
466 if (typeNamespace != null && typeNamespace.Length != 0) return typeNamespace;
468 if (atts.XmlRoot != null && root == null)
473 if (root.Namespace != null && root.Namespace.Length != 0)
474 return root.Namespace;
477 if (defaultNamespace == null) return "";
478 else return defaultNamespace;
481 XmlTypeMapping ImportListMapping (Type type, XmlRootAttribute root, string defaultNamespace, XmlAttributes atts, int nestingLevel)
483 TypeData typeData = TypeTranslator.GetTypeData (type);
484 return ImportListMapping (typeData, root, defaultNamespace, atts, nestingLevel);
487 XmlTypeMapping ImportListMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace, XmlAttributes atts, int nestingLevel)
489 Type type = typeData.Type;
490 ListMap obmap = new ListMap ();
492 if (!allowPrivateTypes)
493 ReflectionHelper.CheckSerializableType (type, true);
495 if (atts == null) atts = new XmlAttributes();
496 Type itemType = typeData.ListItemType;
498 // warning: byte[][] should not be considered multiarray
499 bool isMultiArray = (type.IsArray && (TypeTranslator.GetTypeData(itemType).SchemaType == SchemaTypes.Array) && itemType.IsArray);
501 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
503 foreach (XmlArrayItemAttribute att in atts.XmlArrayItems)
505 if (att.Namespace != null && att.Form == XmlSchemaForm.Unqualified)
506 throw new InvalidOperationException ("XmlArrayItemAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
507 if (att.NestingLevel != nestingLevel) continue;
508 Type elemType = (att.Type != null) ? att.Type : itemType;
509 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData(elemType, att.DataType));
510 elem.Namespace = att.Namespace != null ? att.Namespace : defaultNamespace;
511 if (elem.Namespace == null) elem.Namespace = "";
512 elem.Form = att.Form;
513 if (att.Form == XmlSchemaForm.Unqualified)
514 elem.Namespace = string.Empty;
515 elem.IsNullable = att.IsNullable && CanBeNull (elem.TypeData);
516 elem.NestingLevel = att.NestingLevel;
519 elem.MappedType = ImportListMapping (elemType, null, elem.Namespace, atts, nestingLevel + 1);
520 } else if (elem.TypeData.IsComplexType) {
521 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
524 if (att.ElementName.Length != 0) {
525 elem.ElementName = XmlConvert.EncodeLocalName (att.ElementName);
526 } else if (elem.MappedType != null) {
527 elem.ElementName = elem.MappedType.ElementName;
529 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
537 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData (itemType));
539 elem.MappedType = ImportListMapping (itemType, null, defaultNamespace, atts, nestingLevel + 1);
540 else if (elem.TypeData.IsComplexType)
541 elem.MappedType = ImportTypeMapping (itemType, null, defaultNamespace);
543 if (elem.MappedType != null) {
544 elem.ElementName = elem.MappedType.XmlType;
546 elem.ElementName = TypeTranslator.GetTypeData (itemType).XmlType;
549 elem.Namespace = (defaultNamespace != null) ? defaultNamespace : "";
550 elem.IsNullable = CanBeNull (elem.TypeData);
554 obmap.ItemInfo = list;
556 // If there can be different element names (types) in the array, then its name cannot
557 // be "ArrayOfXXX" it must be something like ArrayOfChoiceNNN
560 if (list.Count > 1) {
561 baseName = "ArrayOfChoice" + (arrayChoiceCount++);
563 XmlTypeMapElementInfo elem = ((XmlTypeMapElementInfo) list[0]);
564 if (elem.MappedType != null) {
565 baseName = TypeTranslator.GetArrayName (elem.MappedType.XmlType);
567 baseName = TypeTranslator.GetArrayName (elem.ElementName);
571 // Avoid name colisions
574 string name = baseName;
577 XmlTypeMapping foundMap = helper.GetRegisteredSchemaType (name, defaultNamespace);
578 if (foundMap == null) nameCount = -1;
579 else if (obmap.Equals (foundMap.ObjectMap) && typeData.Type == foundMap.TypeData.Type) return foundMap;
580 else name = baseName + (nameCount++);
582 while (nameCount != -1);
584 XmlTypeMapping map = CreateTypeMapping (typeData, root, name, defaultNamespace);
585 map.ObjectMap = obmap;
587 // Register any of the including types as a derived class of object
588 XmlIncludeAttribute[] includes = (XmlIncludeAttribute[])type.GetCustomAttributes (typeof (XmlIncludeAttribute), false);
590 XmlTypeMapping objectMapping = ImportTypeMapping (typeof(object));
591 for (int i = 0; i < includes.Length; i++)
593 Type includedType = includes[i].Type;
594 objectMapping.DerivedTypes.Add(ImportTypeMapping (includedType, null, defaultNamespace));
597 // Register this map as a derived class of object
599 helper.RegisterSchemaType (map, name, defaultNamespace);
600 ImportTypeMapping (typeof(object)).DerivedTypes.Add (map);
605 XmlTypeMapping ImportXmlNodeMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
607 Type type = typeData.Type;
608 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
609 if (map != null) return map;
611 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
612 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
614 if (type.BaseType != null)
616 XmlTypeMapping bmap = ImportTypeMapping (type.BaseType, root, defaultNamespace);
617 if (type.BaseType != typeof (object))
620 RegisterDerivedMap (bmap, map);
626 XmlTypeMapping ImportPrimitiveMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
628 Type type = typeData.Type;
629 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
630 if (map != null) return map;
631 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
632 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
636 // Enum.GetNames is not available in SL API
637 public static System.Collections.Generic.IEnumerable<string> GetEnumNames (Type type)
639 System.Collections.Generic.List<string> names = new System.Collections.Generic.List<string> ();
640 foreach (FieldInfo fi in type.GetFields (BindingFlags.Static | BindingFlags.Public))
645 XmlTypeMapping ImportEnumMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
647 Type type = typeData.Type;
648 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
649 if (map != null) return map;
651 if (!allowPrivateTypes)
652 ReflectionHelper.CheckSerializableType (type, false);
654 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
655 map.IsNullable = false;
656 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
658 ArrayList members = new ArrayList();
660 foreach (string name in GetEnumNames (type)) {
662 string [] names = Enum.GetNames (type);
663 foreach (string name in names) {
665 FieldInfo field = type.GetField (name);
666 string xmlName = null;
667 if (field.IsDefined(typeof(XmlIgnoreAttribute), false))
669 object[] atts = field.GetCustomAttributes (typeof(XmlEnumAttribute), false);
670 if (atts.Length > 0) xmlName = ((XmlEnumAttribute)atts[0]).Name;
671 if (xmlName == null) xmlName = name;
672 long value = ((IConvertible) field.GetValue (null)).ToInt64 (CultureInfo.InvariantCulture);
673 members.Add (new EnumMap.EnumMapMember (xmlName, name, value));
676 bool isFlags = type.IsDefined (typeof (FlagsAttribute), false);
677 map.ObjectMap = new EnumMap ((EnumMap.EnumMapMember[])members.ToArray (typeof(EnumMap.EnumMapMember)), isFlags);
678 ImportTypeMapping (typeof(object)).DerivedTypes.Add (map);
682 XmlTypeMapping ImportXmlSerializableMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
684 Type type = typeData.Type;
685 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
686 if (map != null) return map;
688 if (!allowPrivateTypes)
689 ReflectionHelper.CheckSerializableType (type, false);
691 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
692 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
696 void ImportIncludedTypes (Type type, string defaultNamespace)
698 XmlIncludeAttribute[] includes = (XmlIncludeAttribute[])type.GetCustomAttributes (typeof (XmlIncludeAttribute), false);
699 for (int n=0; n<includes.Length; n++)
701 Type includedType = includes[n].Type;
702 ImportTypeMapping (includedType, null, defaultNamespace);
706 List<XmlReflectionMember> GetReflectionMembers (Type type)
708 // First we want to find the inheritance hierarchy in reverse order.
709 Type currentType = type;
710 ArrayList typeList = new ArrayList();
711 typeList.Add(currentType);
712 while (currentType != typeof(object))
714 currentType = currentType.BaseType; // Read the base type.
715 typeList.Insert(0, currentType); // Insert at 0 to reverse the order.
718 // Read all Fields via reflection.
719 ArrayList fieldList = new ArrayList();
720 FieldInfo[] tfields = type.GetFields (BindingFlags.Instance | BindingFlags.Public);
722 // This statement ensures fields are ordered starting from the base type.
723 for (int ti=0; ti<typeList.Count; ti++) {
724 for (int i=0; i<tfields.Length; i++) {
725 FieldInfo field = tfields[i];
726 if (field.DeclaringType == typeList[ti])
727 fieldList.Add (field);
732 int currentIndex = 0;
733 foreach (FieldInfo field in tfields)
735 // This statement ensures fields are ordered starting from the base type.
736 if (currentType != field.DeclaringType)
738 currentType = field.DeclaringType;
741 fieldList.Insert(currentIndex++, field);
744 // Read all Properties via reflection.
745 ArrayList propList = new ArrayList();
746 PropertyInfo[] tprops = type.GetProperties (BindingFlags.Instance | BindingFlags.Public);
748 // This statement ensures properties are ordered starting from the base type.
749 for (int ti=0; ti<typeList.Count; ti++) {
750 for (int i=0; i<tprops.Length; i++) {
751 PropertyInfo prop = tprops[i];
752 if (!prop.CanRead) continue;
753 if (prop.GetIndexParameters().Length > 0) continue;
754 if (prop.DeclaringType == typeList[ti])
761 foreach (PropertyInfo prop in tprops)
763 // This statement ensures properties are ordered starting from the base type.
764 if (currentType != prop.DeclaringType)
766 currentType = prop.DeclaringType;
769 if (!prop.CanRead) continue;
770 if (prop.GetIndexParameters().Length > 0) continue;
771 propList.Insert(currentIndex++, prop);
774 var members = new List<XmlReflectionMember>();
777 // We now step through the type hierarchy from the base (object) through
778 // to the supplied class, as each step outputting all Fields, and then
779 // all Properties. This is the exact same ordering as .NET 1.0/1.1.
780 foreach (Type t in typeList)
782 // Add any fields matching the current DeclaringType.
783 while (fieldIndex < fieldList.Count)
785 FieldInfo field = (FieldInfo)fieldList[fieldIndex];
786 if (field.DeclaringType==t)
789 XmlAttributes atts = attributeOverrides[type, field.Name];
790 if (atts == null) atts = new XmlAttributes (field);
791 if (atts.XmlIgnore) continue;
792 XmlReflectionMember member = new XmlReflectionMember(field.Name, field.FieldType, atts);
793 member.DeclaringType = field.DeclaringType;
799 // Add any properties matching the current DeclaringType.
800 while (propIndex < propList.Count)
802 PropertyInfo prop = (PropertyInfo)propList[propIndex];
803 if (prop.DeclaringType==t)
806 XmlAttributes atts = attributeOverrides[type, prop.Name];
807 if (atts == null) atts = new XmlAttributes (prop);
808 if (atts.XmlIgnore) continue;
809 if (!prop.CanWrite) {
810 if (prop.PropertyType.IsGenericType && TypeData.GetGenericListItemType (prop.PropertyType) == null) continue; // check this before calling GetTypeData() which raises error for missing Add(). See bug #704813.
811 if (TypeTranslator.GetTypeData (prop.PropertyType).SchemaType != SchemaTypes.Array || prop.PropertyType.IsArray) continue;
813 XmlReflectionMember member = new XmlReflectionMember(prop.Name, prop.PropertyType, atts);
814 member.DeclaringType = prop.DeclaringType;
824 private XmlTypeMapMember CreateMapMember (Type declaringType, XmlReflectionMember rmember, string defaultNamespace)
826 XmlTypeMapMember mapMember;
827 XmlAttributes atts = rmember.XmlAttributes;
828 TypeData typeData = TypeTranslator.GetTypeData (rmember.MemberType);
830 if (atts.XmlArray != null) {
831 if (atts.XmlArray.Namespace != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
832 throw new InvalidOperationException ("XmlArrayAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
833 if (typeData.SchemaType != SchemaTypes.Array &&
834 !(typeData.SchemaType == SchemaTypes.Primitive && typeData.Type == typeof (byte [])))
835 throw new InvalidOperationException ("XmlArrayAttribute can be applied to members of array or collection type.");
839 if (atts.XmlAnyAttribute != null)
841 if ( (rmember.MemberType.FullName == "System.Xml.XmlAttribute[]") ||
842 (rmember.MemberType.FullName == "System.Xml.XmlNode[]") )
844 mapMember = new XmlTypeMapMemberAnyAttribute();
847 throw new InvalidOperationException ("XmlAnyAttributeAttribute can only be applied to members of type XmlAttribute[] or XmlNode[]");
851 if (atts.XmlAnyElements != null && atts.XmlAnyElements.Count > 0)
853 // no XmlNode type check is done here (seealso: bug #553032).
854 XmlTypeMapMemberAnyElement member = new XmlTypeMapMemberAnyElement();
855 member.ElementInfo = ImportAnyElementInfo (defaultNamespace, rmember, member, atts);
860 XmlTypeMapMemberNamespaces mapNamespaces = new XmlTypeMapMemberNamespaces ();
861 mapMember = mapNamespaces;
863 else if (atts.XmlAttribute != null)
867 if (atts.XmlElements != null && atts.XmlElements.Count > 0)
868 throw new Exception ("XmlAttributeAttribute and XmlElementAttribute cannot be applied to the same member");
870 XmlTypeMapMemberAttribute mapAttribute = new XmlTypeMapMemberAttribute ();
871 if (atts.XmlAttribute.AttributeName.Length == 0)
872 mapAttribute.AttributeName = rmember.MemberName;
874 mapAttribute.AttributeName = atts.XmlAttribute.AttributeName;
876 mapAttribute.AttributeName = XmlConvert.EncodeLocalName (mapAttribute.AttributeName);
878 if (typeData.IsComplexType)
879 mapAttribute.MappedType = ImportTypeMapping (typeData.Type, null, defaultNamespace);
881 if (atts.XmlAttribute.Namespace != null && atts.XmlAttribute.Namespace != defaultNamespace)
883 if (atts.XmlAttribute.Form == XmlSchemaForm.Unqualified)
884 throw new InvalidOperationException ("The Form property may not be 'Unqualified' when an explicit Namespace property is present");
885 mapAttribute.Form = XmlSchemaForm.Qualified;
886 mapAttribute.Namespace = atts.XmlAttribute.Namespace;
890 mapAttribute.Form = atts.XmlAttribute.Form;
891 if (atts.XmlAttribute.Form == XmlSchemaForm.Qualified)
892 mapAttribute.Namespace = defaultNamespace;
894 mapAttribute.Namespace = "";
897 typeData = TypeTranslator.GetTypeData(rmember.MemberType, atts.XmlAttribute.DataType);
898 mapMember = mapAttribute;
900 else if (typeData.SchemaType == SchemaTypes.Array)
902 // If the member has a single XmlElementAttribute and the type is the type of the member,
903 // then it is not a flat list
905 if (atts.XmlElements.Count > 1 ||
906 (atts.XmlElements.Count == 1 && atts.XmlElements[0].Type != typeData.Type) ||
907 (atts.XmlText != null))
911 // check that it does not have XmlArrayAttribute
912 if (atts.XmlArray != null)
913 throw new InvalidOperationException ("XmlArrayAttribute cannot be used with members which also attributed with XmlElementAttribute or XmlTextAttribute.");
915 XmlTypeMapMemberFlatList member = new XmlTypeMapMemberFlatList ();
916 member.ListMap = new ListMap ();
917 member.ListMap.ItemInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName (rmember.MemberName), defaultNamespace, typeData.ListItemType, member, atts);
918 member.ElementInfo = member.ListMap.ItemInfo;
919 member.ListMap.ChoiceMember = member.ChoiceMember;
926 XmlTypeMapMemberList member = new XmlTypeMapMemberList ();
928 // Creates an ElementInfo that identifies the array instance.
929 member.ElementInfo = new XmlTypeMapElementInfoList();
930 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, typeData);
931 elem.ElementName = XmlConvert.EncodeLocalName((atts.XmlArray != null && atts.XmlArray.ElementName.Length != 0) ? atts.XmlArray.ElementName : rmember.MemberName);
932 // note that it could be changed below (when Form is Unqualified)
933 elem.Namespace = (atts.XmlArray != null && atts.XmlArray.Namespace != null) ? atts.XmlArray.Namespace : defaultNamespace;
934 elem.MappedType = ImportListMapping (rmember.MemberType, null, elem.Namespace, atts, 0);
935 elem.IsNullable = (atts.XmlArray != null) ? atts.XmlArray.IsNullable : false;
936 elem.Form = (atts.XmlArray != null) ? atts.XmlArray.Form : XmlSchemaForm.Qualified;
937 elem.ExplicitOrder = (atts.XmlArray != null) ? atts.XmlArray.Order : -1;
938 // This is a bit tricky, but is done
939 // after filling descendant members, so
940 // that array items could be serialized
941 // with proper namespace.
942 if (atts.XmlArray != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
943 elem.Namespace = String.Empty;
945 member.ElementInfo.Add (elem);
953 XmlTypeMapMemberElement member = new XmlTypeMapMemberElement ();
954 member.ElementInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName(rmember.MemberName), defaultNamespace, rmember.MemberType, member, atts);
958 mapMember.DefaultValue = GetDefaultValue (typeData, atts.XmlDefaultValue);
959 mapMember.TypeData = typeData;
960 mapMember.Name = rmember.MemberName;
961 mapMember.IsReturnValue = rmember.IsReturnValue;
965 XmlTypeMapElementInfoList ImportElementInfo (Type cls, string defaultName, string defaultNamespace, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts)
967 EnumMap choiceEnumMap = null;
968 Type choiceEnumType = null;
970 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
971 ImportTextElementInfo (list, defaultType, member, atts, defaultNamespace);
973 if (atts.XmlChoiceIdentifier != null) {
975 throw new InvalidOperationException ("XmlChoiceIdentifierAttribute not supported in this context.");
977 member.ChoiceMember = atts.XmlChoiceIdentifier.MemberName;
978 MemberInfo[] mems = cls.GetMember (member.ChoiceMember, BindingFlags.Instance|BindingFlags.Public);
980 if (mems.Length == 0)
981 throw new InvalidOperationException ("Choice member '" + member.ChoiceMember + "' not found in class '" + cls);
983 if (mems[0] is PropertyInfo) {
984 PropertyInfo pi = (PropertyInfo)mems[0];
985 if (!pi.CanWrite || !pi.CanRead)
986 throw new InvalidOperationException ("Choice property '" + member.ChoiceMember + "' must be read/write.");
987 choiceEnumType = pi.PropertyType;
989 else choiceEnumType = ((FieldInfo)mems[0]).FieldType;
991 member.ChoiceTypeData = TypeTranslator.GetTypeData (choiceEnumType);
993 if (choiceEnumType.IsArray)
994 choiceEnumType = choiceEnumType.GetElementType ();
996 choiceEnumMap = ImportTypeMapping (choiceEnumType).ObjectMap as EnumMap;
997 if (choiceEnumMap == null)
998 throw new InvalidOperationException ("The member '" + mems[0].Name + "' is not a valid target for XmlChoiceIdentifierAttribute.");
1001 if (atts.XmlElements.Count == 0 && list.Count == 0)
1003 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType));
1004 elem.ElementName = defaultName;
1005 elem.Namespace = defaultNamespace;
1006 if (elem.TypeData.IsComplexType)
1007 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
1011 bool multiType = (atts.XmlElements.Count > 1);
1012 foreach (XmlElementAttribute att in atts.XmlElements)
1014 Type elemType = (att.Type != null) ? att.Type : defaultType;
1015 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(elemType, att.DataType));
1016 elem.Form = att.Form;
1017 if (elem.Form != XmlSchemaForm.Unqualified)
1018 elem.Namespace = (att.Namespace != null) ? att.Namespace : defaultNamespace;
1020 // elem may already be nullable, and IsNullable property in XmlElement is false by default
1021 if (att.IsNullable && !elem.IsNullable)
1022 elem.IsNullable = att.IsNullable;
1024 elem.ExplicitOrder = att.Order;
1026 if (elem.IsNullable && !elem.TypeData.IsNullable)
1027 throw new InvalidOperationException ("IsNullable may not be 'true' for value type " + elem.TypeData.FullTypeName + " in member '" + defaultName + "'");
1029 if (elem.TypeData.IsComplexType)
1031 if (att.DataType.Length != 0) throw new InvalidOperationException (
1032 string.Format(CultureInfo.InvariantCulture, "'{0}' is "
1033 + "an invalid value for '{1}.{2}' of type '{3}'. "
1034 + "The property may only be specified for primitive types.",
1035 att.DataType, cls.FullName, defaultName,
1036 elem.TypeData.FullTypeName));
1037 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
1040 if (att.ElementName.Length != 0) {
1041 elem.ElementName = XmlConvert.EncodeLocalName(att.ElementName);
1042 } else if (multiType) {
1043 if (elem.MappedType != null) {
1044 elem.ElementName = elem.MappedType.ElementName;
1046 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
1049 elem.ElementName = defaultName;
1052 if (choiceEnumMap != null) {
1053 string cname = choiceEnumMap.GetEnumName (choiceEnumType.FullName, elem.ElementName);
1055 throw new InvalidOperationException (string.Format (
1056 CultureInfo.InvariantCulture, "Type {0} is missing"
1057 + " enumeration value '{1}' for element '{1} from"
1058 + " namespace '{2}'.", choiceEnumType, elem.ElementName,
1060 elem.ChoiceValue = Enum.Parse (choiceEnumType, cname, false);
1068 XmlTypeMapElementInfoList ImportAnyElementInfo (string defaultNamespace, XmlReflectionMember rmember, XmlTypeMapMemberElement member, XmlAttributes atts)
1070 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
1072 ImportTextElementInfo (list, rmember.MemberType, member, atts, defaultNamespace);
1074 #if !MOONLIGHT // no practical anyElement support
1075 foreach (XmlAnyElementAttribute att in atts.XmlAnyElements)
1077 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(typeof(XmlElement)));
1078 if (att.Name.Length != 0)
1080 elem.ElementName = XmlConvert.EncodeLocalName(att.Name);
1081 elem.Namespace = (att.Namespace != null) ? att.Namespace : "";
1085 elem.IsUnnamedAnyElement = true;
1086 elem.Namespace = defaultNamespace;
1087 if (att.Namespace != null)
1088 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.");
1090 elem.ExplicitOrder = att.Order;
1097 void ImportTextElementInfo (XmlTypeMapElementInfoList list, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts, string defaultNamespace)
1099 if (atts.XmlText != null)
1101 member.IsXmlTextCollector = true;
1102 if (atts.XmlText.Type != null) {
1103 TypeData td = TypeTranslator.GetTypeData (defaultType);
1104 if ((td.SchemaType == SchemaTypes.Primitive || td.SchemaType == SchemaTypes.Enum) && atts.XmlText.Type != defaultType) {
1105 throw new InvalidOperationException ("The type for XmlText may not be specified for primitive types.");
1107 defaultType = atts.XmlText.Type;
1110 if (defaultType == typeof(XmlNode)) defaultType = typeof(XmlText); // Nodes must be text nodes
1113 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType, atts.XmlText.DataType));
1115 if (elem.TypeData.SchemaType != SchemaTypes.Primitive &&
1116 elem.TypeData.SchemaType != SchemaTypes.Enum &&
1117 elem.TypeData.SchemaType != SchemaTypes.XmlNode &&
1118 !(elem.TypeData.SchemaType == SchemaTypes.Array && elem.TypeData.ListItemTypeData.SchemaType == SchemaTypes.XmlNode)
1120 throw new InvalidOperationException ("XmlText cannot be used to encode complex types");
1122 if (elem.TypeData.IsComplexType)
1123 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
1124 elem.IsTextElement = true;
1125 elem.WrappedElement = false;
1130 bool CanBeNull (TypeData type)
1132 #if !NET_2_0 // idiotic compatibility
1133 if (type.Type == typeof (XmlQualifiedName))
1136 return !type.Type.IsValueType || type.IsNullable;
1139 public void IncludeType (Type type)
1142 throw new ArgumentNullException ("type");
1144 if (includedTypes == null) includedTypes = new ArrayList ();
1145 if (!includedTypes.Contains (type))
1146 includedTypes.Add (type);
1148 if (relatedMaps.Count > 0) {
1149 foreach (XmlTypeMapping map in (ArrayList) relatedMaps.Clone ()) {
1150 if (map.TypeData.Type == typeof(object))
1151 map.DerivedTypes.Add (ImportTypeMapping (type));
1156 public void IncludeTypes (ICustomAttributeProvider provider)
1158 object[] ats = provider.GetCustomAttributes (typeof(XmlIncludeAttribute), true);
1160 foreach (XmlIncludeAttribute at in ats)
1161 IncludeType (at.Type);
1164 private object GetDefaultValue (TypeData typeData, object defaultValue)
1166 if (defaultValue == DBNull.Value || typeData.SchemaType != SchemaTypes.Enum)
1167 return defaultValue;
1170 string namedValue = (defaultValue as Enum).ToString ("g");
1171 string decimalValue = (defaultValue as Enum).ToString ("d");
1173 // get string representation of enum value
1174 string namedValue = Enum.Format (typeData.Type, defaultValue, "g");
1175 // get decimal representation of enum value
1176 string decimalValue = Enum.Format (typeData.Type, defaultValue, "d");
1178 // if decimal representation matches string representation, then
1179 // the value is not defined in the enum type (as the "g" format
1180 // will return the decimal equivalent of the value if the value
1181 // is not equal to a combination of named enumerated constants
1182 if (namedValue == decimalValue) {
1183 string msg = string.Format (CultureInfo.InvariantCulture,
1184 "Value '{0}' cannot be converted to {1}.", defaultValue,
1185 defaultValue.GetType ().FullName);
1186 throw new InvalidOperationException (msg);
1189 // XmlSerializer expects integral enum value
1190 //return namedValue.Replace (',', ' ');
1191 return defaultValue;
1194 #endregion // Methods