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, bool isBaseType = false)
331 TypeData typeData = TypeTranslator.GetTypeData (type);
332 return ImportClassMapping (typeData, root, defaultNamespace, isBaseType);
335 XmlTypeMapping ImportClassMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace, bool isBaseType = false)
337 Type type = typeData.Type;
339 if (!allowPrivateTypes && !isBaseType)
340 ReflectionHelper.CheckSerializableType (type, false);
342 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
343 if (map != null) return map;
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, true);
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, true);
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[]) &&
427 mem.TypeData.Type != typeof(XmlNode[]) &&
428 mem.TypeData.Type != typeof(object[]))
430 throw new InvalidOperationException (String.Format (errSimple2, map.TypeData.TypeName, mem.Name, mem.TypeData.TypeName));
436 void RegisterDerivedMap (XmlTypeMapping map, XmlTypeMapping derivedMap)
438 map.DerivedTypes.Add (derivedMap);
439 map.DerivedTypes.AddRange (derivedMap.DerivedTypes);
441 if (map.BaseMap != null)
442 RegisterDerivedMap (map.BaseMap, derivedMap);
444 XmlTypeMapping obmap = ImportTypeMapping (typeof(object));
446 obmap.DerivedTypes.Add (derivedMap);
450 string GetTypeNamespace (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
452 string typeNamespace = null;
454 XmlAttributes atts = null;
455 if (!typeData.IsListType)
457 if (attributeOverrides != null)
458 atts = attributeOverrides[typeData.Type];
462 atts = new XmlAttributes (typeData.Type);
464 if (atts.XmlType != null)
466 if (atts.XmlType.Namespace != null && atts.XmlType.Namespace.Length != 0 && typeData.SchemaType != SchemaTypes.Enum)
467 typeNamespace = atts.XmlType.Namespace;
470 if (typeNamespace != null && typeNamespace.Length != 0) return typeNamespace;
472 if (atts.XmlRoot != null && root == null)
477 if (root.Namespace != null && root.Namespace.Length != 0)
478 return root.Namespace;
481 if (defaultNamespace == null) return "";
482 else return defaultNamespace;
485 XmlTypeMapping ImportListMapping (Type type, XmlRootAttribute root, string defaultNamespace, XmlAttributes atts, int nestingLevel)
487 TypeData typeData = TypeTranslator.GetTypeData (type);
488 return ImportListMapping (typeData, root, defaultNamespace, atts, nestingLevel);
491 XmlTypeMapping ImportListMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace, XmlAttributes atts, int nestingLevel)
493 Type type = typeData.Type;
494 ListMap obmap = new ListMap ();
496 if (!allowPrivateTypes)
497 ReflectionHelper.CheckSerializableType (type, true);
499 if (atts == null) atts = new XmlAttributes();
500 Type itemType = typeData.ListItemType;
502 // warning: byte[][] should not be considered multiarray
503 bool isMultiArray = (type.IsArray && (TypeTranslator.GetTypeData(itemType).SchemaType == SchemaTypes.Array) && itemType.IsArray);
505 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
507 foreach (XmlArrayItemAttribute att in atts.XmlArrayItems)
509 if (att.Namespace != null && att.Form == XmlSchemaForm.Unqualified)
510 throw new InvalidOperationException ("XmlArrayItemAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
511 if (att.NestingLevel != nestingLevel) continue;
512 Type elemType = (att.Type != null) ? att.Type : itemType;
513 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData(elemType, att.DataType));
514 elem.Namespace = att.Namespace != null ? att.Namespace : defaultNamespace;
515 if (elem.Namespace == null) elem.Namespace = "";
516 elem.Form = att.Form;
517 if (att.Form == XmlSchemaForm.Unqualified)
518 elem.Namespace = string.Empty;
519 elem.IsNullable = att.IsNullable && CanBeNull (elem.TypeData);
520 elem.NestingLevel = att.NestingLevel;
523 elem.MappedType = ImportListMapping (elemType, null, elem.Namespace, atts, nestingLevel + 1);
524 } else if (elem.TypeData.IsComplexType) {
525 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
528 if (att.ElementName.Length != 0) {
529 elem.ElementName = XmlConvert.EncodeLocalName (att.ElementName);
530 } else if (elem.MappedType != null) {
531 elem.ElementName = elem.MappedType.ElementName;
533 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
541 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (null, TypeTranslator.GetTypeData (itemType));
543 elem.MappedType = ImportListMapping (itemType, null, defaultNamespace, atts, nestingLevel + 1);
544 else if (elem.TypeData.IsComplexType)
545 elem.MappedType = ImportTypeMapping (itemType, null, defaultNamespace);
547 if (elem.MappedType != null) {
548 elem.ElementName = elem.MappedType.XmlType;
550 elem.ElementName = TypeTranslator.GetTypeData (itemType).XmlType;
553 elem.Namespace = (defaultNamespace != null) ? defaultNamespace : "";
554 elem.IsNullable = CanBeNull (elem.TypeData);
558 obmap.ItemInfo = list;
560 // If there can be different element names (types) in the array, then its name cannot
561 // be "ArrayOfXXX" it must be something like ArrayOfChoiceNNN
564 if (list.Count > 1) {
565 baseName = "ArrayOfChoice" + (arrayChoiceCount++);
567 XmlTypeMapElementInfo elem = ((XmlTypeMapElementInfo) list[0]);
568 if (elem.MappedType != null) {
569 baseName = TypeTranslator.GetArrayName (elem.MappedType.XmlType);
571 baseName = TypeTranslator.GetArrayName (elem.ElementName);
575 // Avoid name colisions
578 string name = baseName;
581 XmlTypeMapping foundMap = helper.GetRegisteredSchemaType (name, defaultNamespace);
582 if (foundMap == null) nameCount = -1;
583 else if (obmap.Equals (foundMap.ObjectMap) && typeData.Type == foundMap.TypeData.Type) return foundMap;
584 else name = baseName + (nameCount++);
586 while (nameCount != -1);
588 XmlTypeMapping map = CreateTypeMapping (typeData, root, name, defaultNamespace);
589 map.ObjectMap = obmap;
591 // Register any of the including types as a derived class of object
592 XmlIncludeAttribute[] includes = (XmlIncludeAttribute[])type.GetCustomAttributes (typeof (XmlIncludeAttribute), false);
594 XmlTypeMapping objectMapping = ImportTypeMapping (typeof(object));
595 for (int i = 0; i < includes.Length; i++)
597 Type includedType = includes[i].Type;
598 objectMapping.DerivedTypes.Add(ImportTypeMapping (includedType, null, defaultNamespace));
601 // Register this map as a derived class of object
603 helper.RegisterSchemaType (map, name, defaultNamespace);
604 ImportTypeMapping (typeof(object)).DerivedTypes.Add (map);
609 XmlTypeMapping ImportXmlNodeMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
611 Type type = typeData.Type;
612 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
613 if (map != null) return map;
615 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
616 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
618 if (type.BaseType != null)
620 XmlTypeMapping bmap = ImportTypeMapping (type.BaseType, root, defaultNamespace);
621 if (type.BaseType != typeof (object))
624 RegisterDerivedMap (bmap, map);
630 XmlTypeMapping ImportPrimitiveMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
632 Type type = typeData.Type;
633 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
634 if (map != null) return map;
635 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
636 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
640 XmlTypeMapping ImportEnumMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
642 Type type = typeData.Type;
643 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
644 if (map != null) return map;
646 if (!allowPrivateTypes)
647 ReflectionHelper.CheckSerializableType (type, false);
649 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
650 map.IsNullable = false;
651 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
653 ArrayList members = new ArrayList();
654 string [] names = Enum.GetNames (type);
655 foreach (string name in names) {
656 FieldInfo field = type.GetField (name);
657 string xmlName = null;
658 if (field.IsDefined(typeof(XmlIgnoreAttribute), false))
660 object[] atts = field.GetCustomAttributes (typeof(XmlEnumAttribute), false);
661 if (atts.Length > 0) xmlName = ((XmlEnumAttribute)atts[0]).Name;
662 if (xmlName == null) xmlName = name;
663 long value = ((IConvertible) field.GetValue (null)).ToInt64 (CultureInfo.InvariantCulture);
664 members.Add (new EnumMap.EnumMapMember (xmlName, name, value));
667 bool isFlags = type.IsDefined (typeof (FlagsAttribute), false);
668 map.ObjectMap = new EnumMap ((EnumMap.EnumMapMember[])members.ToArray (typeof(EnumMap.EnumMapMember)), isFlags);
669 ImportTypeMapping (typeof(object)).DerivedTypes.Add (map);
673 XmlTypeMapping ImportXmlSerializableMapping (TypeData typeData, XmlRootAttribute root, string defaultNamespace)
675 Type type = typeData.Type;
676 XmlTypeMapping map = helper.GetRegisteredClrType (type, GetTypeNamespace (typeData, root, defaultNamespace));
677 if (map != null) return map;
679 if (!allowPrivateTypes)
680 ReflectionHelper.CheckSerializableType (type, false);
682 map = CreateTypeMapping (typeData, root, null, defaultNamespace);
683 helper.RegisterClrType (map, type, map.XmlTypeNamespace);
687 void ImportIncludedTypes (Type type, string defaultNamespace)
689 XmlIncludeAttribute[] includes = (XmlIncludeAttribute[])type.GetCustomAttributes (typeof (XmlIncludeAttribute), false);
690 for (int n=0; n<includes.Length; n++)
692 Type includedType = includes[n].Type;
693 ImportTypeMapping (includedType, null, defaultNamespace);
697 List<XmlReflectionMember> GetReflectionMembers (Type type)
699 // First we want to find the inheritance hierarchy in reverse order.
700 Type currentType = type;
701 ArrayList typeList = new ArrayList();
702 typeList.Add(currentType);
703 while (currentType != typeof(object))
705 currentType = currentType.BaseType; // Read the base type.
706 typeList.Insert(0, currentType); // Insert at 0 to reverse the order.
709 // Read all Fields via reflection.
710 ArrayList fieldList = new ArrayList();
711 FieldInfo[] tfields = type.GetFields (BindingFlags.Instance | BindingFlags.Public);
713 int currentIndex = 0;
714 foreach (FieldInfo field in tfields)
716 // This statement ensures fields are ordered starting from the base type.
717 if (currentType != field.DeclaringType)
719 currentType = field.DeclaringType;
722 fieldList.Insert(currentIndex++, field);
724 // Read all Properties via reflection.
725 ArrayList propList = new ArrayList();
726 PropertyInfo[] tprops = type.GetProperties (BindingFlags.Instance | BindingFlags.Public);
729 foreach (PropertyInfo prop in tprops)
731 // This statement ensures properties are ordered starting from the base type.
732 if (currentType != prop.DeclaringType)
734 currentType = prop.DeclaringType;
737 if (!prop.CanRead) continue;
738 if (prop.GetIndexParameters().Length > 0) continue;
739 propList.Insert(currentIndex++, prop);
741 var members = new List<XmlReflectionMember>();
744 // We now step through the type hierarchy from the base (object) through
745 // to the supplied class, as each step outputting all Fields, and then
746 // all Properties. This is the exact same ordering as .NET 1.0/1.1.
747 foreach (Type t in typeList)
749 // Add any fields matching the current DeclaringType.
750 while (fieldIndex < fieldList.Count)
752 FieldInfo field = (FieldInfo)fieldList[fieldIndex];
753 if (field.DeclaringType==t)
756 XmlAttributes atts = attributeOverrides[type, field.Name];
757 if (atts == null) atts = new XmlAttributes (field);
758 if (atts.XmlIgnore) continue;
759 XmlReflectionMember member = new XmlReflectionMember(field.Name, field.FieldType, atts);
760 member.DeclaringType = field.DeclaringType;
766 // Add any properties matching the current DeclaringType.
767 while (propIndex < propList.Count)
769 PropertyInfo prop = (PropertyInfo)propList[propIndex];
770 if (prop.DeclaringType==t)
773 XmlAttributes atts = attributeOverrides[type, prop.Name];
774 if (atts == null) atts = new XmlAttributes (prop);
775 if (atts.XmlIgnore) continue;
776 if (!prop.CanWrite) {
777 if (prop.PropertyType.IsInterface && typeof (IEnumerable).IsAssignableFrom (prop.PropertyType)) continue;
778 if (prop.PropertyType.IsGenericType && TypeData.GetGenericListItemType (prop.PropertyType) == null) continue; // check this before calling GetTypeData() which raises error for missing Add(). See bug #704813.
779 if (TypeTranslator.GetTypeData (prop.PropertyType).SchemaType != SchemaTypes.Array || prop.PropertyType.IsArray) continue;
781 XmlReflectionMember member = new XmlReflectionMember(prop.Name, prop.PropertyType, atts);
782 member.DeclaringType = prop.DeclaringType;
792 private XmlTypeMapMember CreateMapMember (Type declaringType, XmlReflectionMember rmember, string defaultNamespace)
794 XmlTypeMapMember mapMember;
795 XmlAttributes atts = rmember.XmlAttributes;
796 TypeData typeData = TypeTranslator.GetTypeData (rmember.MemberType);
798 if (atts.XmlArray != null) {
799 if (atts.XmlArray.Namespace != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
800 throw new InvalidOperationException ("XmlArrayAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
801 if (typeData.SchemaType != SchemaTypes.Array &&
802 !(typeData.SchemaType == SchemaTypes.Primitive && typeData.Type == typeof (byte [])))
803 throw new InvalidOperationException ("XmlArrayAttribute can be applied to members of array or collection type.");
806 if (atts.XmlAnyAttribute != null)
808 if ( (rmember.MemberType.FullName == "System.Xml.XmlAttribute[]") ||
809 (rmember.MemberType.FullName == "System.Xml.XmlNode[]") )
811 mapMember = new XmlTypeMapMemberAnyAttribute();
814 throw new InvalidOperationException ("XmlAnyAttributeAttribute can only be applied to members of type XmlAttribute[] or XmlNode[]");
817 if (atts.XmlAnyElements != null && atts.XmlAnyElements.Count > 0)
819 // no XmlNode type check is done here (seealso: bug #553032).
820 XmlTypeMapMemberAnyElement member = new XmlTypeMapMemberAnyElement();
821 member.ElementInfo = ImportAnyElementInfo (defaultNamespace, rmember, member, atts);
826 XmlTypeMapMemberNamespaces mapNamespaces = new XmlTypeMapMemberNamespaces ();
827 mapMember = mapNamespaces;
829 else if (atts.XmlAttribute != null)
833 if (atts.XmlElements != null && atts.XmlElements.Count > 0)
834 throw new Exception ("XmlAttributeAttribute and XmlElementAttribute cannot be applied to the same member");
836 XmlTypeMapMemberAttribute mapAttribute = new XmlTypeMapMemberAttribute ();
837 if (atts.XmlAttribute.AttributeName.Length == 0)
838 mapAttribute.AttributeName = rmember.MemberName;
840 mapAttribute.AttributeName = atts.XmlAttribute.AttributeName;
842 mapAttribute.AttributeName = XmlConvert.EncodeName (mapAttribute.AttributeName);
844 if (typeData.IsComplexType)
845 mapAttribute.MappedType = ImportTypeMapping (typeData.Type, null, defaultNamespace);
847 if (atts.XmlAttribute.Namespace != null && atts.XmlAttribute.Namespace != defaultNamespace)
849 if (atts.XmlAttribute.Form == XmlSchemaForm.Unqualified)
850 throw new InvalidOperationException ("The Form property may not be 'Unqualified' when an explicit Namespace property is present");
851 mapAttribute.Form = XmlSchemaForm.Qualified;
852 mapAttribute.Namespace = atts.XmlAttribute.Namespace;
856 mapAttribute.Form = atts.XmlAttribute.Form;
857 if (atts.XmlAttribute.Form == XmlSchemaForm.Qualified)
858 mapAttribute.Namespace = defaultNamespace;
860 mapAttribute.Namespace = "";
863 typeData = TypeTranslator.GetTypeData(rmember.MemberType, atts.XmlAttribute.DataType);
864 mapMember = mapAttribute;
866 else if (typeData.SchemaType == SchemaTypes.Array)
868 // If the member has a single XmlElementAttribute and the type is the type of the member,
869 // then it is not a flat list
871 if (atts.XmlElements.Count > 1 ||
872 (atts.XmlElements.Count == 1 && atts.XmlElements[0].Type != typeData.Type) ||
873 (atts.XmlText != null))
877 // check that it does not have XmlArrayAttribute
878 if (atts.XmlArray != null)
879 throw new InvalidOperationException ("XmlArrayAttribute cannot be used with members which also attributed with XmlElementAttribute or XmlTextAttribute.");
881 XmlTypeMapMemberFlatList member = new XmlTypeMapMemberFlatList ();
882 member.ListMap = new ListMap ();
883 member.ListMap.ItemInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName (rmember.MemberName), defaultNamespace, typeData.ListItemType, member, atts);
884 member.ElementInfo = member.ListMap.ItemInfo;
885 member.ListMap.ChoiceMember = member.ChoiceMember;
892 XmlTypeMapMemberList member = new XmlTypeMapMemberList ();
894 // Creates an ElementInfo that identifies the array instance.
895 member.ElementInfo = new XmlTypeMapElementInfoList();
896 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, typeData);
897 elem.ElementName = XmlConvert.EncodeLocalName((atts.XmlArray != null && atts.XmlArray.ElementName.Length != 0) ? atts.XmlArray.ElementName : rmember.MemberName);
898 // note that it could be changed below (when Form is Unqualified)
899 elem.Namespace = (atts.XmlArray != null && atts.XmlArray.Namespace != null) ? atts.XmlArray.Namespace : defaultNamespace;
900 elem.MappedType = ImportListMapping (rmember.MemberType, null, elem.Namespace, atts, 0);
901 elem.IsNullable = (atts.XmlArray != null) ? atts.XmlArray.IsNullable : false;
902 elem.Form = (atts.XmlArray != null) ? atts.XmlArray.Form : XmlSchemaForm.Qualified;
903 elem.ExplicitOrder = (atts.XmlArray != null) ? atts.XmlArray.Order : -1;
904 // This is a bit tricky, but is done
905 // after filling descendant members, so
906 // that array items could be serialized
907 // with proper namespace.
908 if (atts.XmlArray != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
909 elem.Namespace = String.Empty;
911 member.ElementInfo.Add (elem);
919 XmlTypeMapMemberElement member = new XmlTypeMapMemberElement ();
920 member.ElementInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName(rmember.MemberName), defaultNamespace, rmember.MemberType, member, atts);
924 mapMember.DefaultValue = GetDefaultValue (typeData, atts.XmlDefaultValue);
925 mapMember.TypeData = typeData;
926 mapMember.Name = rmember.MemberName;
927 mapMember.IsReturnValue = rmember.IsReturnValue;
931 XmlTypeMapElementInfoList ImportElementInfo (Type cls, string defaultName, string defaultNamespace, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts)
933 EnumMap choiceEnumMap = null;
934 Type choiceEnumType = null;
936 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
937 ImportTextElementInfo (list, defaultType, member, atts, defaultNamespace);
939 if (atts.XmlChoiceIdentifier != null) {
941 throw new InvalidOperationException ("XmlChoiceIdentifierAttribute not supported in this context.");
943 member.ChoiceMember = atts.XmlChoiceIdentifier.MemberName;
944 MemberInfo[] mems = cls.GetMember (member.ChoiceMember, BindingFlags.Instance|BindingFlags.Public);
946 if (mems.Length == 0)
947 throw new InvalidOperationException ("Choice member '" + member.ChoiceMember + "' not found in class '" + cls);
949 if (mems[0] is PropertyInfo) {
950 PropertyInfo pi = (PropertyInfo)mems[0];
951 if (!pi.CanWrite || !pi.CanRead)
952 throw new InvalidOperationException ("Choice property '" + member.ChoiceMember + "' must be read/write.");
953 choiceEnumType = pi.PropertyType;
955 else choiceEnumType = ((FieldInfo)mems[0]).FieldType;
957 member.ChoiceTypeData = TypeTranslator.GetTypeData (choiceEnumType);
959 if (choiceEnumType.IsArray)
960 choiceEnumType = choiceEnumType.GetElementType ();
962 choiceEnumMap = ImportTypeMapping (choiceEnumType).ObjectMap as EnumMap;
963 if (choiceEnumMap == null)
964 throw new InvalidOperationException ("The member '" + mems[0].Name + "' is not a valid target for XmlChoiceIdentifierAttribute.");
967 if (atts.XmlElements.Count == 0 && list.Count == 0)
969 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType));
970 elem.ElementName = defaultName;
971 elem.Namespace = defaultNamespace;
972 if (elem.TypeData.IsComplexType)
973 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
977 bool multiType = (atts.XmlElements.Count > 1);
978 foreach (XmlElementAttribute att in atts.XmlElements)
980 Type elemType = (att.Type != null) ? att.Type : defaultType;
981 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(elemType, att.DataType));
982 elem.Form = att.Form;
983 if (elem.Form != XmlSchemaForm.Unqualified)
984 elem.Namespace = (att.Namespace != null) ? att.Namespace : defaultNamespace;
986 // elem may already be nullable, and IsNullable property in XmlElement is false by default
987 if (att.IsNullable && !elem.IsNullable)
988 elem.IsNullable = att.IsNullable;
990 elem.ExplicitOrder = att.Order;
992 if (elem.IsNullable && !elem.TypeData.IsNullable)
993 throw new InvalidOperationException ("IsNullable may not be 'true' for value type " + elem.TypeData.FullTypeName + " in member '" + defaultName + "'");
995 if (elem.TypeData.IsComplexType)
997 if (att.DataType.Length != 0) throw new InvalidOperationException (
998 string.Format(CultureInfo.InvariantCulture, "'{0}' is "
999 + "an invalid value for '{1}.{2}' of type '{3}'. "
1000 + "The property may only be specified for primitive types.",
1001 att.DataType, cls.FullName, defaultName,
1002 elem.TypeData.FullTypeName));
1003 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
1006 if (att.ElementName.Length != 0) {
1007 elem.ElementName = XmlConvert.EncodeLocalName(att.ElementName);
1008 } else if (multiType) {
1009 if (elem.MappedType != null) {
1010 elem.ElementName = elem.MappedType.ElementName;
1012 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
1015 elem.ElementName = defaultName;
1018 if (choiceEnumMap != null) {
1019 string cname = choiceEnumMap.GetEnumName (choiceEnumType.FullName, elem.ElementName);
1020 if (cname == null && elem.Namespace != null)
1021 cname = choiceEnumMap.GetEnumName (choiceEnumType.FullName,
1022 elem.Namespace.ToString () + ":" + elem.ElementName);
1024 throw new InvalidOperationException (string.Format (
1025 CultureInfo.InvariantCulture, "Type {0} is missing"
1026 + " enumeration value '{1}' for element '{1} from"
1027 + " namespace '{2}'.", choiceEnumType, elem.ElementName,
1029 elem.ChoiceValue = Enum.Parse (choiceEnumType, cname, false);
1037 XmlTypeMapElementInfoList ImportAnyElementInfo (string defaultNamespace, XmlReflectionMember rmember, XmlTypeMapMemberElement member, XmlAttributes atts)
1039 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
1041 ImportTextElementInfo (list, rmember.MemberType, member, atts, defaultNamespace);
1043 foreach (XmlAnyElementAttribute att in atts.XmlAnyElements)
1045 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(typeof(XmlElement)));
1046 if (att.Name.Length != 0)
1048 elem.ElementName = XmlConvert.EncodeLocalName(att.Name);
1049 elem.Namespace = (att.Namespace != null) ? att.Namespace : "";
1053 elem.IsUnnamedAnyElement = true;
1054 elem.Namespace = defaultNamespace;
1055 if (att.Namespace != null)
1056 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.");
1058 elem.ExplicitOrder = att.Order;
1064 void ImportTextElementInfo (XmlTypeMapElementInfoList list, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts, string defaultNamespace)
1066 if (atts.XmlText != null)
1068 member.IsXmlTextCollector = true;
1069 if (atts.XmlText.Type != null) {
1070 TypeData td = TypeTranslator.GetTypeData (defaultType);
1071 if ((td.SchemaType == SchemaTypes.Primitive || td.SchemaType == SchemaTypes.Enum) && atts.XmlText.Type != defaultType) {
1072 throw new InvalidOperationException ("The type for XmlText may not be specified for primitive types.");
1074 defaultType = atts.XmlText.Type;
1076 if (defaultType == typeof(XmlNode)) defaultType = typeof(XmlText); // Nodes must be text nodes
1078 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType, atts.XmlText.DataType));
1080 if (elem.TypeData.SchemaType != SchemaTypes.Primitive &&
1081 elem.TypeData.SchemaType != SchemaTypes.Enum &&
1082 elem.TypeData.SchemaType != SchemaTypes.XmlNode &&
1083 !(elem.TypeData.SchemaType == SchemaTypes.Array && elem.TypeData.ListItemTypeData.SchemaType == SchemaTypes.XmlNode)
1085 throw new InvalidOperationException ("XmlText cannot be used to encode complex types");
1087 if (elem.TypeData.IsComplexType)
1088 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
1089 elem.IsTextElement = true;
1090 elem.WrappedElement = false;
1095 bool CanBeNull (TypeData type)
1097 #if !NET_2_0 // idiotic compatibility
1098 if (type.Type == typeof (XmlQualifiedName))
1101 return !type.Type.IsValueType || type.IsNullable;
1104 public void IncludeType (Type type)
1107 throw new ArgumentNullException ("type");
1109 if (includedTypes == null) includedTypes = new ArrayList ();
1110 if (!includedTypes.Contains (type))
1111 includedTypes.Add (type);
1113 if (relatedMaps.Count > 0) {
1114 foreach (XmlTypeMapping map in (ArrayList) relatedMaps.Clone ()) {
1115 if (map.TypeData.Type == typeof(object))
1116 map.DerivedTypes.Add (ImportTypeMapping (type));
1121 public void IncludeTypes (ICustomAttributeProvider provider)
1123 object[] ats = provider.GetCustomAttributes (typeof(XmlIncludeAttribute), true);
1125 foreach (XmlIncludeAttribute at in ats)
1126 IncludeType (at.Type);
1129 private object GetDefaultValue (TypeData typeData, object defaultValue)
1131 if (defaultValue == DBNull.Value || typeData.SchemaType != SchemaTypes.Enum)
1132 return defaultValue;
1134 // get string representation of enum value
1135 string namedValue = Enum.Format (typeData.Type, defaultValue, "g");
1136 // get decimal representation of enum value
1137 string decimalValue = Enum.Format (typeData.Type, defaultValue, "d");
1138 // if decimal representation matches string representation, then
1139 // the value is not defined in the enum type (as the "g" format
1140 // will return the decimal equivalent of the value if the value
1141 // is not equal to a combination of named enumerated constants
1142 if (namedValue == decimalValue) {
1143 string msg = string.Format (CultureInfo.InvariantCulture,
1144 "Value '{0}' cannot be converted to {1}.", defaultValue,
1145 defaultValue.GetType ().FullName);
1146 throw new InvalidOperationException (msg);
1149 // XmlSerializer expects integral enum value
1150 //return namedValue.Replace (',', ' ');
1151 return defaultValue;
1154 #endregion // Methods