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 bool hasTypeNamespace = !string.IsNullOrEmpty (defaultNamespace);
251 string rootNamespace = null;
252 string typeNamespace = null;
254 bool includeInSchema = true;
255 XmlAttributes atts = null;
256 bool nullable = CanBeNull (typeData);
258 if (defaultXmlType == null) defaultXmlType = typeData.XmlType;
260 if (!typeData.IsListType)
262 if (attributeOverrides != null)
263 atts = attributeOverrides[typeData.Type];
265 if (atts != null && typeData.SchemaType == SchemaTypes.Primitive)
266 throw new InvalidOperationException ("XmlRoot and XmlType attributes may not be specified for the type " + typeData.FullTypeName);
270 atts = new XmlAttributes (typeData.Type);
272 if (atts.XmlRoot != null && root == null)
275 if (atts.XmlType != null)
277 if (atts.XmlType.Namespace != null) {
278 typeNamespace = atts.XmlType.Namespace;
279 hasTypeNamespace = true;
282 if (atts.XmlType.TypeName != null && atts.XmlType.TypeName != string.Empty)
283 defaultXmlType = XmlConvert.EncodeLocalName (atts.XmlType.TypeName);
285 includeInSchema = atts.XmlType.IncludeInSchema;
288 elementName = defaultXmlType;
292 if (root.ElementName.Length != 0)
293 elementName = XmlConvert.EncodeLocalName(root.ElementName);
294 if (root.Namespace != null) {
295 rootNamespace = root.Namespace;
296 hasTypeNamespace = true;
298 nullable = root.IsNullable;
301 rootNamespace = rootNamespace ?? defaultNamespace ?? string.Empty;
302 typeNamespace = typeNamespace ?? rootNamespace;
305 switch (typeData.SchemaType) {
306 case SchemaTypes.XmlSerializable:
307 map = new XmlSerializableMapping (root, elementName, rootNamespace, typeData, defaultXmlType, typeNamespace);
309 case SchemaTypes.Primitive:
310 if (!typeData.IsXsdType)
311 map = new XmlTypeMapping (elementName, rootNamespace,
312 typeData, defaultXmlType, XmlSerializer.WsdlTypesNamespace);
314 map = new XmlTypeMapping (elementName, rootNamespace,
315 typeData, defaultXmlType, typeNamespace);
318 map = new XmlTypeMapping (elementName, rootNamespace, typeData, defaultXmlType, hasTypeNamespace ? typeNamespace : null);
322 map.IncludeInSchema = includeInSchema;
323 map.IsNullable = nullable;
324 relatedMaps.Add (map);
329 XmlTypeMapping ImportClassMapping (Type type, XmlRootAttribute root, string defaultNamespace)
331 TypeData typeData = TypeTranslator.GetTypeData (type);
332 return ImportClassMapping (typeData, root, defaultNamespace);
335 XmlTypeMapping ImportClassMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
337 Type type = typeData.Type;
339 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
340 if (map != null) return map;
342 if (!allowPrivateTypes)
343 ReflectionHelper.CheckSerializableType (type, false);
345 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
346 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
347 helper.RegisterSchemaType (map, map.XmlType, map.XmlTypeNamespace);
351 ClassMap classMap = new ClassMap ();
352 map.ObjectMap = classMap;
354 var members = GetReflectionMembers (type);
355 bool? isOrderExplicit = null;
356 foreach (XmlReflectionMember rmember in members)
358 int? order = rmember.XmlAttributes.Order;
359 if (isOrderExplicit == null)
362 isOrderExplicit = (int) order >= 0;
364 else if (order != null && isOrderExplicit != ((int) order >= 0))
365 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.");
367 if (isOrderExplicit == true)
368 members.Sort ((m1, m2) => (int) m1.XmlAttributes.SortableOrder - (int) m2.XmlAttributes.SortableOrder);
370 foreach (XmlReflectionMember rmember in members)
372 string ns = map.XmlTypeNamespace;
373 if (rmember.XmlAttributes.XmlIgnore) continue;
374 if (rmember.DeclaringType != null && rmember.DeclaringType != type) {
375 XmlTypeMapping bmap = ImportClassMapping (rmember.DeclaringType, root, defaultNamespace);
376 if (bmap.HasXmlTypeNamespace)
377 ns = bmap.XmlTypeNamespace;
381 XmlTypeMapMember mem = CreateMapMember (type, rmember, ns);
382 mem.CheckOptionalValueType (type);
383 classMap.AddMember (mem);
384 } catch (Exception ex) {
385 throw new InvalidOperationException (string.Format (
386 CultureInfo.InvariantCulture, "There was an error" +
387 " reflecting field '{0}'.", rmember.MemberName), ex);
391 // Import extra classes
393 if (type == typeof (object) && includedTypes != null)
395 foreach (Type intype in includedTypes)
396 map.DerivedTypes.Add (ImportTypeMapping (intype, defaultNamespace));
399 // Register inheritance relations
401 if (type.BaseType != null)
403 XmlTypeMapping bmap = ImportClassMapping (type.BaseType, root, defaultNamespace);
404 ClassMap cbmap = bmap.ObjectMap as ClassMap;
406 if (type.BaseType != typeof (object)) {
408 if (!cbmap.HasSimpleContent)
409 classMap.SetCanBeSimpleType (false);
412 // At this point, derived classes of this map must be already registered
414 RegisterDerivedMap (bmap, map);
416 if (cbmap.HasSimpleContent && classMap.ElementMembers != null && classMap.ElementMembers.Count != 1)
417 throw new InvalidOperationException (String.Format (errSimple, map.TypeData.TypeName, map.BaseMap.TypeData.TypeName));
420 ImportIncludedTypes (type, defaultNamespace);
422 if (classMap.XmlTextCollector != null && !classMap.HasSimpleContent)
424 XmlTypeMapMember mem = classMap.XmlTextCollector;
425 if (mem.TypeData.Type != typeof(string) &&
426 mem.TypeData.Type != typeof(string[]) &&
428 mem.TypeData.Type != typeof(XmlNode[]) &&
430 mem.TypeData.Type != typeof(object[]))
432 throw new InvalidOperationException (String.Format (errSimple2, map.TypeData.TypeName, mem.Name, mem.TypeData.TypeName));
438 void RegisterDerivedMap (XmlTypeMapping map, XmlTypeMapping derivedMap)
440 map.DerivedTypes.Add (derivedMap);
441 map.DerivedTypes.AddRange (derivedMap.DerivedTypes);
443 if (map.BaseMap != null)
444 RegisterDerivedMap (map.BaseMap, derivedMap);
446 XmlTypeMapping obmap = ImportTypeMapping (typeof(object));
448 obmap.DerivedTypes.Add (derivedMap);
452 string GetTypeNamespace (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
454 string typeNamespace = null;
456 XmlAttributes atts = null;
457 if (!typeData.IsListType)
459 if (attributeOverrides != null)
460 atts = attributeOverrides[typeData.Type];
464 atts = new XmlAttributes (typeData.Type);
466 if (atts.XmlType != null)
468 if (atts.XmlType.Namespace != null && atts.XmlType.Namespace.Length != 0 && typeData.SchemaType != SchemaTypes.Enum)
469 typeNamespace = atts.XmlType.Namespace;
472 if (typeNamespace != null && typeNamespace.Length != 0) return typeNamespace;
474 if (atts.XmlRoot != null && root == null)
479 if (root.Namespace != null && root.Namespace.Length != 0)
480 return root.Namespace;
483 if (defaultNamespace == null) return "";
484 else return defaultNamespace;
487 XmlTypeMapping ImportListMapping (Type type, XmlRootAttribute root, string defaultNamespace, XmlAttributes atts, int nestingLevel)
489 TypeData typeData = TypeTranslator.GetTypeData (type);
490 return ImportListMapping (typeData, root, defaultNamespace, atts, nestingLevel);
493 XmlTypeMapping ImportListMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace, XmlAttributes atts, int nestingLevel)
495 Type type = typeData.Type;
496 ListMap obmap = new ListMap ();
498 if (!allowPrivateTypes)
499 ReflectionHelper.CheckSerializableType (type, true);
501 if (atts == null) atts = new XmlAttributes();
502 Type itemType = typeData.ListItemType;
504 // warning: byte[][] should not be considered multiarray
505 bool isMultiArray = (type.IsArray && (TypeTranslator.GetTypeData(itemType).SchemaType == SchemaTypes.Array) && itemType.IsArray);
507 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
509 foreach (XmlArrayItemAttribute att in atts.XmlArrayItems)
511 if (att.Namespace != null && att.Form == XmlSchemaForm.Unqualified)
512 throw new InvalidOperationException ("XmlArrayItemAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
513 if (att.NestingLevel != nestingLevel) continue;
514 Type elemType = (att.Type != null) ? att.Type : itemType;
515 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData(elemType, att.DataType));
516 elem.Namespace = att.Namespace != null ? att.Namespace : defaultNamespace;
517 if (elem.Namespace == null) elem.Namespace = "";
518 elem.Form = att.Form;
519 if (att.Form == XmlSchemaForm.Unqualified)
520 elem.Namespace = string.Empty;
521 elem.IsNullable = att.IsNullable && CanBeNull (elem.TypeData);
522 elem.NestingLevel = att.NestingLevel;
525 elem.MappedType = ImportListMapping (elemType, null, elem.Namespace, atts, nestingLevel + 1);
526 } else if (elem.TypeData.IsComplexType) {
527 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
530 if (att.ElementName.Length != 0) {
531 elem.ElementName = XmlConvert.EncodeLocalName (att.ElementName);
532 } else if (elem.MappedType != null) {
533 elem.ElementName = elem.MappedType.ElementName;
535 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
543 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData (itemType));
545 elem.MappedType = ImportListMapping (itemType, null, defaultNamespace, atts, nestingLevel + 1);
546 else if (elem.TypeData.IsComplexType)
547 elem.MappedType = ImportTypeMapping (itemType, null, defaultNamespace);
549 if (elem.MappedType != null) {
550 elem.ElementName = elem.MappedType.XmlType;
552 elem.ElementName = TypeTranslator.GetTypeData (itemType).XmlType;
555 elem.Namespace = (defaultNamespace != null) ? defaultNamespace : "";
556 elem.IsNullable = CanBeNull (elem.TypeData);
560 obmap.ItemInfo = list;
562 // If there can be different element names (types) in the array, then its name cannot
563 // be "ArrayOfXXX" it must be something like ArrayOfChoiceNNN
566 if (list.Count > 1) {
567 baseName = "ArrayOfChoice" + (arrayChoiceCount++);
569 XmlTypeMapElementInfo elem = ((XmlTypeMapElementInfo) list[0]);
570 if (elem.MappedType != null) {
571 baseName = TypeTranslator.GetArrayName (elem.MappedType.XmlType);
573 baseName = TypeTranslator.GetArrayName (elem.ElementName);
577 // Avoid name colisions
580 string name = baseName;
583 XmlTypeMapping foundMap = helper.GetRegisteredSchemaType (name, defaultNamespace);
584 if (foundMap == null) nameCount = -1;
585 else if (obmap.Equals (foundMap.ObjectMap) && typeData.Type == foundMap.TypeData.Type) return foundMap;
586 else name = baseName + (nameCount++);
588 while (nameCount != -1);
590 XmlTypeMapping map = CreateTypeMapping (typeData, root, name, defaultNamespace);
591 map.ObjectMap = obmap;
593 // Register any of the including types as a derived class of object
594 XmlIncludeAttribute[] includes = (XmlIncludeAttribute[])type.GetCustomAttributes (typeof (XmlIncludeAttribute), false);
596 XmlTypeMapping objectMapping = ImportTypeMapping (typeof(object));
597 for (int i = 0; i < includes.Length; i++)
599 Type includedType = includes[i].Type;
600 objectMapping.DerivedTypes.Add(ImportTypeMapping (includedType, null, defaultNamespace));
603 // Register this map as a derived class of object
605 helper.RegisterSchemaType (map, name, defaultNamespace);
606 ImportTypeMapping (typeof(object)).DerivedTypes.Add (map);
611 XmlTypeMapping ImportXmlNodeMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
613 Type type = typeData.Type;
614 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
615 if (map != null) return map;
617 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
618 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
620 if (type.BaseType != null)
622 XmlTypeMapping bmap = ImportTypeMapping (type.BaseType, root, defaultNamespace);
623 if (type.BaseType != typeof (object))
626 RegisterDerivedMap (bmap, map);
632 XmlTypeMapping ImportPrimitiveMapping (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;
637 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
638 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
642 // Enum.GetNames is not available in SL API
643 public static System.Collections.Generic.IEnumerable<string> GetEnumNames (Type type)
645 System.Collections.Generic.List<string> names = new System.Collections.Generic.List<string> ();
646 foreach (FieldInfo fi in type.GetFields (BindingFlags.Static | BindingFlags.Public))
651 XmlTypeMapping ImportEnumMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
653 Type type = typeData.Type;
654 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
655 if (map != null) return map;
657 if (!allowPrivateTypes)
658 ReflectionHelper.CheckSerializableType (type, false);
660 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
661 map.IsNullable = false;
662 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
664 ArrayList members = new ArrayList();
666 foreach (string name in GetEnumNames (type)) {
668 string [] names = Enum.GetNames (type);
669 foreach (string name in names) {
671 FieldInfo field = type.GetField (name);
672 string xmlName = null;
673 if (field.IsDefined(typeof(XmlIgnoreAttribute), false))
675 object[] atts = field.GetCustomAttributes (typeof(XmlEnumAttribute), false);
676 if (atts.Length > 0) xmlName = ((XmlEnumAttribute)atts[0]).Name;
677 if (xmlName == null) xmlName = name;
678 long value = ((IConvertible) field.GetValue (null)).ToInt64 (CultureInfo.InvariantCulture);
679 members.Add (new EnumMap.EnumMapMember (xmlName, name, value));
682 bool isFlags = type.IsDefined (typeof (FlagsAttribute), false);
683 map.ObjectMap = new EnumMap ((EnumMap.EnumMapMember[])members.ToArray (typeof(EnumMap.EnumMapMember)), isFlags);
684 ImportTypeMapping (typeof(object)).DerivedTypes.Add (map);
688 XmlTypeMapping ImportXmlSerializableMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
690 Type type = typeData.Type;
691 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
692 if (map != null) return map;
694 if (!allowPrivateTypes)
695 ReflectionHelper.CheckSerializableType (type, false);
697 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
698 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
702 void ImportIncludedTypes (Type type, string defaultNamespace)
704 XmlIncludeAttribute[] includes = (XmlIncludeAttribute[])type.GetCustomAttributes (typeof (XmlIncludeAttribute), false);
705 for (int n=0; n<includes.Length; n++)
707 Type includedType = includes[n].Type;
708 ImportTypeMapping (includedType, null, defaultNamespace);
712 List<XmlReflectionMember> GetReflectionMembers (Type type)
714 // First we want to find the inheritance hierarchy in reverse order.
715 Type currentType = type;
716 ArrayList typeList = new ArrayList();
717 typeList.Add(currentType);
718 while (currentType != typeof(object))
720 currentType = currentType.BaseType; // Read the base type.
721 typeList.Insert(0, currentType); // Insert at 0 to reverse the order.
724 // Read all Fields via reflection.
725 ArrayList fieldList = new ArrayList();
726 FieldInfo[] tfields = type.GetFields (BindingFlags.Instance | BindingFlags.Public);
728 // This statement ensures fields are ordered starting from the base type.
729 for (int ti=0; ti<typeList.Count; ti++) {
730 for (int i=0; i<tfields.Length; i++) {
731 FieldInfo field = tfields[i];
732 if (field.DeclaringType == typeList[ti])
733 fieldList.Add (field);
738 int currentIndex = 0;
739 foreach (FieldInfo field in tfields)
741 // This statement ensures fields are ordered starting from the base type.
742 if (currentType != field.DeclaringType)
744 currentType = field.DeclaringType;
747 fieldList.Insert(currentIndex++, field);
750 // Read all Properties via reflection.
751 ArrayList propList = new ArrayList();
752 PropertyInfo[] tprops = type.GetProperties (BindingFlags.Instance | BindingFlags.Public);
754 // This statement ensures properties are ordered starting from the base type.
755 for (int ti=0; ti<typeList.Count; ti++) {
756 for (int i=0; i<tprops.Length; i++) {
757 PropertyInfo prop = tprops[i];
758 if (!prop.CanRead) continue;
759 if (prop.GetIndexParameters().Length > 0) continue;
760 if (prop.DeclaringType == typeList[ti])
767 foreach (PropertyInfo prop in tprops)
769 // This statement ensures properties are ordered starting from the base type.
770 if (currentType != prop.DeclaringType)
772 currentType = prop.DeclaringType;
775 if (!prop.CanRead) continue;
776 if (prop.GetIndexParameters().Length > 0) continue;
777 propList.Insert(currentIndex++, prop);
780 var members = new List<XmlReflectionMember>();
783 // We now step through the type hierarchy from the base (object) through
784 // to the supplied class, as each step outputting all Fields, and then
785 // all Properties. This is the exact same ordering as .NET 1.0/1.1.
786 foreach (Type t in typeList)
788 // Add any fields matching the current DeclaringType.
789 while (fieldIndex < fieldList.Count)
791 FieldInfo field = (FieldInfo)fieldList[fieldIndex];
792 if (field.DeclaringType==t)
795 XmlAttributes atts = attributeOverrides[type, field.Name];
796 if (atts == null) atts = new XmlAttributes (field);
797 if (atts.XmlIgnore) continue;
798 XmlReflectionMember member = new XmlReflectionMember(field.Name, field.FieldType, atts);
799 member.DeclaringType = field.DeclaringType;
805 // Add any properties matching the current DeclaringType.
806 while (propIndex < propList.Count)
808 PropertyInfo prop = (PropertyInfo)propList[propIndex];
809 if (prop.DeclaringType==t)
812 XmlAttributes atts = attributeOverrides[type, prop.Name];
813 if (atts == null) atts = new XmlAttributes (prop);
814 if (atts.XmlIgnore) continue;
815 if (!prop.CanWrite) {
816 if (prop.PropertyType.IsGenericType && TypeData.GetGenericListItemType (prop.PropertyType) == null) continue; // check this before calling GetTypeData() which raises error for missing Add(). See bug #704813.
817 if (TypeTranslator.GetTypeData (prop.PropertyType).SchemaType != SchemaTypes.Array || prop.PropertyType.IsArray) continue;
819 XmlReflectionMember member = new XmlReflectionMember(prop.Name, prop.PropertyType, atts);
820 member.DeclaringType = prop.DeclaringType;
830 private XmlTypeMapMember CreateMapMember (Type declaringType, XmlReflectionMember rmember, string defaultNamespace)
832 XmlTypeMapMember mapMember;
833 XmlAttributes atts = rmember.XmlAttributes;
834 TypeData typeData = TypeTranslator.GetTypeData (rmember.MemberType);
836 if (atts.XmlArray != null) {
837 if (atts.XmlArray.Namespace != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
838 throw new InvalidOperationException ("XmlArrayAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
839 if (typeData.SchemaType != SchemaTypes.Array &&
840 !(typeData.SchemaType == SchemaTypes.Primitive && typeData.Type == typeof (byte [])))
841 throw new InvalidOperationException ("XmlArrayAttribute can be applied to members of array or collection type.");
845 if (atts.XmlAnyAttribute != null)
847 if ( (rmember.MemberType.FullName == "System.Xml.XmlAttribute[]") ||
848 (rmember.MemberType.FullName == "System.Xml.XmlNode[]") )
850 mapMember = new XmlTypeMapMemberAnyAttribute();
853 throw new InvalidOperationException ("XmlAnyAttributeAttribute can only be applied to members of type XmlAttribute[] or XmlNode[]");
857 if (atts.XmlAnyElements != null && atts.XmlAnyElements.Count > 0)
859 // no XmlNode type check is done here (seealso: bug #553032).
860 XmlTypeMapMemberAnyElement member = new XmlTypeMapMemberAnyElement();
861 member.ElementInfo = ImportAnyElementInfo (defaultNamespace, rmember, member, atts);
866 XmlTypeMapMemberNamespaces mapNamespaces = new XmlTypeMapMemberNamespaces ();
867 mapMember = mapNamespaces;
869 else if (atts.XmlAttribute != null)
873 if (atts.XmlElements != null && atts.XmlElements.Count > 0)
874 throw new Exception ("XmlAttributeAttribute and XmlElementAttribute cannot be applied to the same member");
876 XmlTypeMapMemberAttribute mapAttribute = new XmlTypeMapMemberAttribute ();
877 if (atts.XmlAttribute.AttributeName.Length == 0)
878 mapAttribute.AttributeName = rmember.MemberName;
880 mapAttribute.AttributeName = atts.XmlAttribute.AttributeName;
882 mapAttribute.AttributeName = XmlConvert.EncodeLocalName (mapAttribute.AttributeName);
884 if (typeData.IsComplexType)
885 mapAttribute.MappedType = ImportTypeMapping (typeData.Type, null, defaultNamespace);
887 if (atts.XmlAttribute.Namespace != null && atts.XmlAttribute.Namespace != defaultNamespace)
889 if (atts.XmlAttribute.Form == XmlSchemaForm.Unqualified)
890 throw new InvalidOperationException ("The Form property may not be 'Unqualified' when an explicit Namespace property is present");
891 mapAttribute.Form = XmlSchemaForm.Qualified;
892 mapAttribute.Namespace = atts.XmlAttribute.Namespace;
896 mapAttribute.Form = atts.XmlAttribute.Form;
897 if (atts.XmlAttribute.Form == XmlSchemaForm.Qualified)
898 mapAttribute.Namespace = defaultNamespace;
900 mapAttribute.Namespace = "";
903 typeData = TypeTranslator.GetTypeData(rmember.MemberType, atts.XmlAttribute.DataType);
904 mapMember = mapAttribute;
906 else if (typeData.SchemaType == SchemaTypes.Array)
908 // If the member has a single XmlElementAttribute and the type is the type of the member,
909 // then it is not a flat list
911 if (atts.XmlElements.Count > 1 ||
912 (atts.XmlElements.Count == 1 && atts.XmlElements[0].Type != typeData.Type) ||
913 (atts.XmlText != null))
917 // check that it does not have XmlArrayAttribute
918 if (atts.XmlArray != null)
919 throw new InvalidOperationException ("XmlArrayAttribute cannot be used with members which also attributed with XmlElementAttribute or XmlTextAttribute.");
921 XmlTypeMapMemberFlatList member = new XmlTypeMapMemberFlatList ();
922 member.ListMap = new ListMap ();
923 member.ListMap.ItemInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName (rmember.MemberName), defaultNamespace, typeData.ListItemType, member, atts);
924 member.ElementInfo = member.ListMap.ItemInfo;
925 member.ListMap.ChoiceMember = member.ChoiceMember;
932 XmlTypeMapMemberList member = new XmlTypeMapMemberList ();
934 // Creates an ElementInfo that identifies the array instance.
935 member.ElementInfo = new XmlTypeMapElementInfoList();
936 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, typeData);
937 elem.ElementName = XmlConvert.EncodeLocalName((atts.XmlArray != null && atts.XmlArray.ElementName.Length != 0) ? atts.XmlArray.ElementName : rmember.MemberName);
938 // note that it could be changed below (when Form is Unqualified)
939 elem.Namespace = (atts.XmlArray != null && atts.XmlArray.Namespace != null) ? atts.XmlArray.Namespace : defaultNamespace;
940 elem.MappedType = ImportListMapping (rmember.MemberType, null, elem.Namespace, atts, 0);
941 elem.IsNullable = (atts.XmlArray != null) ? atts.XmlArray.IsNullable : false;
942 elem.Form = (atts.XmlArray != null) ? atts.XmlArray.Form : XmlSchemaForm.Qualified;
943 elem.ExplicitOrder = (atts.XmlArray != null) ? atts.XmlArray.Order : -1;
944 // This is a bit tricky, but is done
945 // after filling descendant members, so
946 // that array items could be serialized
947 // with proper namespace.
948 if (atts.XmlArray != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
949 elem.Namespace = String.Empty;
951 member.ElementInfo.Add (elem);
959 XmlTypeMapMemberElement member = new XmlTypeMapMemberElement ();
960 member.ElementInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName(rmember.MemberName), defaultNamespace, rmember.MemberType, member, atts);
964 mapMember.DefaultValue = GetDefaultValue (typeData, atts.XmlDefaultValue);
965 mapMember.TypeData = typeData;
966 mapMember.Name = rmember.MemberName;
967 mapMember.IsReturnValue = rmember.IsReturnValue;
971 XmlTypeMapElementInfoList ImportElementInfo (Type cls, string defaultName, string defaultNamespace, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts)
973 EnumMap choiceEnumMap = null;
974 Type choiceEnumType = null;
976 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
977 ImportTextElementInfo (list, defaultType, member, atts, defaultNamespace);
979 if (atts.XmlChoiceIdentifier != null) {
981 throw new InvalidOperationException ("XmlChoiceIdentifierAttribute not supported in this context.");
983 member.ChoiceMember = atts.XmlChoiceIdentifier.MemberName;
984 MemberInfo[] mems = cls.GetMember (member.ChoiceMember, BindingFlags.Instance|BindingFlags.Public);
986 if (mems.Length == 0)
987 throw new InvalidOperationException ("Choice member '" + member.ChoiceMember + "' not found in class '" + cls);
989 if (mems[0] is PropertyInfo) {
990 PropertyInfo pi = (PropertyInfo)mems[0];
991 if (!pi.CanWrite || !pi.CanRead)
992 throw new InvalidOperationException ("Choice property '" + member.ChoiceMember + "' must be read/write.");
993 choiceEnumType = pi.PropertyType;
995 else choiceEnumType = ((FieldInfo)mems[0]).FieldType;
997 member.ChoiceTypeData = TypeTranslator.GetTypeData (choiceEnumType);
999 if (choiceEnumType.IsArray)
1000 choiceEnumType = choiceEnumType.GetElementType ();
1002 choiceEnumMap = ImportTypeMapping (choiceEnumType).ObjectMap as EnumMap;
1003 if (choiceEnumMap == null)
1004 throw new InvalidOperationException ("The member '" + mems[0].Name + "' is not a valid target for XmlChoiceIdentifierAttribute.");
1007 if (atts.XmlElements.Count == 0 && list.Count == 0)
1009 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType));
1010 elem.ElementName = defaultName;
1011 elem.Namespace = defaultNamespace;
1012 if (elem.TypeData.IsComplexType)
1013 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
1017 bool multiType = (atts.XmlElements.Count > 1);
1018 foreach (XmlElementAttribute att in atts.XmlElements)
1020 Type elemType = (att.Type != null) ? att.Type : defaultType;
1021 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(elemType, att.DataType));
1022 elem.Form = att.Form;
1023 if (elem.Form != XmlSchemaForm.Unqualified)
1024 elem.Namespace = (att.Namespace != null) ? att.Namespace : defaultNamespace;
1026 // elem may already be nullable, and IsNullable property in XmlElement is false by default
1027 if (att.IsNullable && !elem.IsNullable)
1028 elem.IsNullable = att.IsNullable;
1030 elem.ExplicitOrder = att.Order;
1032 if (elem.IsNullable && !elem.TypeData.IsNullable)
1033 throw new InvalidOperationException ("IsNullable may not be 'true' for value type " + elem.TypeData.FullTypeName + " in member '" + defaultName + "'");
1035 if (elem.TypeData.IsComplexType)
1037 if (att.DataType.Length != 0) throw new InvalidOperationException (
1038 string.Format(CultureInfo.InvariantCulture, "'{0}' is "
1039 + "an invalid value for '{1}.{2}' of type '{3}'. "
1040 + "The property may only be specified for primitive types.",
1041 att.DataType, cls.FullName, defaultName,
1042 elem.TypeData.FullTypeName));
1043 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
1046 if (att.ElementName.Length != 0) {
1047 elem.ElementName = XmlConvert.EncodeLocalName(att.ElementName);
1048 } else if (multiType) {
1049 if (elem.MappedType != null) {
1050 elem.ElementName = elem.MappedType.ElementName;
1052 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
1055 elem.ElementName = defaultName;
1058 if (choiceEnumMap != null) {
1059 string cname = choiceEnumMap.GetEnumName (choiceEnumType.FullName, elem.ElementName);
1061 throw new InvalidOperationException (string.Format (
1062 CultureInfo.InvariantCulture, "Type {0} is missing"
1063 + " enumeration value '{1}' for element '{1} from"
1064 + " namespace '{2}'.", choiceEnumType, elem.ElementName,
1066 elem.ChoiceValue = Enum.Parse (choiceEnumType, cname, false);
1074 XmlTypeMapElementInfoList ImportAnyElementInfo (string defaultNamespace, XmlReflectionMember rmember, XmlTypeMapMemberElement member, XmlAttributes atts)
1076 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
1078 ImportTextElementInfo (list, rmember.MemberType, member, atts, defaultNamespace);
1080 #if !MOONLIGHT // no practical anyElement support
1081 foreach (XmlAnyElementAttribute att in atts.XmlAnyElements)
1083 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(typeof(XmlElement)));
1084 if (att.Name.Length != 0)
1086 elem.ElementName = XmlConvert.EncodeLocalName(att.Name);
1087 elem.Namespace = (att.Namespace != null) ? att.Namespace : "";
1091 elem.IsUnnamedAnyElement = true;
1092 elem.Namespace = defaultNamespace;
1093 if (att.Namespace != null)
1094 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.");
1096 elem.ExplicitOrder = att.Order;
1103 void ImportTextElementInfo (XmlTypeMapElementInfoList list, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts, string defaultNamespace)
1105 if (atts.XmlText != null)
1107 member.IsXmlTextCollector = true;
1108 if (atts.XmlText.Type != null) {
1109 TypeData td = TypeTranslator.GetTypeData (defaultType);
1110 if ((td.SchemaType == SchemaTypes.Primitive || td.SchemaType == SchemaTypes.Enum) && atts.XmlText.Type != defaultType) {
1111 throw new InvalidOperationException ("The type for XmlText may not be specified for primitive types.");
1113 defaultType = atts.XmlText.Type;
1116 if (defaultType == typeof(XmlNode)) defaultType = typeof(XmlText); // Nodes must be text nodes
1119 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType, atts.XmlText.DataType));
1121 if (elem.TypeData.SchemaType != SchemaTypes.Primitive &&
1122 elem.TypeData.SchemaType != SchemaTypes.Enum &&
1123 elem.TypeData.SchemaType != SchemaTypes.XmlNode &&
1124 !(elem.TypeData.SchemaType == SchemaTypes.Array && elem.TypeData.ListItemTypeData.SchemaType == SchemaTypes.XmlNode)
1126 throw new InvalidOperationException ("XmlText cannot be used to encode complex types");
1128 if (elem.TypeData.IsComplexType)
1129 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
1130 elem.IsTextElement = true;
1131 elem.WrappedElement = false;
1136 bool CanBeNull (TypeData type)
1138 #if !NET_2_0 // idiotic compatibility
1139 if (type.Type == typeof (XmlQualifiedName))
1142 return !type.Type.IsValueType || type.IsNullable;
1145 public void IncludeType (Type type)
1148 throw new ArgumentNullException ("type");
1150 if (includedTypes == null) includedTypes = new ArrayList ();
1151 if (!includedTypes.Contains (type))
1152 includedTypes.Add (type);
1154 if (relatedMaps.Count > 0) {
1155 foreach (XmlTypeMapping map in (ArrayList) relatedMaps.Clone ()) {
1156 if (map.TypeData.Type == typeof(object))
1157 map.DerivedTypes.Add (ImportTypeMapping (type));
1162 public void IncludeTypes (ICustomAttributeProvider provider)
1164 object[] ats = provider.GetCustomAttributes (typeof(XmlIncludeAttribute), true);
1166 foreach (XmlIncludeAttribute at in ats)
1167 IncludeType (at.Type);
1170 private object GetDefaultValue (TypeData typeData, object defaultValue)
1172 if (defaultValue == DBNull.Value || typeData.SchemaType != SchemaTypes.Enum)
1173 return defaultValue;
1176 string namedValue = (defaultValue as Enum).ToString ("g");
1177 string decimalValue = (defaultValue as Enum).ToString ("d");
1179 // get string representation of enum value
1180 string namedValue = Enum.Format (typeData.Type, defaultValue, "g");
1181 // get decimal representation of enum value
1182 string decimalValue = Enum.Format (typeData.Type, defaultValue, "d");
1184 // if decimal representation matches string representation, then
1185 // the value is not defined in the enum type (as the "g" format
1186 // will return the decimal equivalent of the value if the value
1187 // is not equal to a combination of named enumerated constants
1188 if (namedValue == decimalValue) {
1189 string msg = string.Format (CultureInfo.InvariantCulture,
1190 "Value '{0}' cannot be converted to {1}.", defaultValue,
1191 defaultValue.GetType ().FullName);
1192 throw new InvalidOperationException (msg);
1195 // XmlSerializer expects integral enum value
1196 //return namedValue.Replace (',', ' ');
1197 return defaultValue;
1200 #endregion // Methods