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[]) &&
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 // This statement ensures fields are ordered starting from the base type.
714 for (int ti=0; ti<typeList.Count; ti++) {
715 for (int i=0; i<tfields.Length; i++) {
716 FieldInfo field = tfields[i];
717 if (field.DeclaringType == typeList[ti])
718 fieldList.Add (field);
723 int currentIndex = 0;
724 foreach (FieldInfo field in tfields)
726 // This statement ensures fields are ordered starting from the base type.
727 if (currentType != field.DeclaringType)
729 currentType = field.DeclaringType;
732 fieldList.Insert(currentIndex++, field);
735 // Read all Properties via reflection.
736 ArrayList propList = new ArrayList();
737 PropertyInfo[] tprops = type.GetProperties (BindingFlags.Instance | BindingFlags.Public);
739 // This statement ensures properties are ordered starting from the base type.
740 for (int ti=0; ti<typeList.Count; ti++) {
741 for (int i=0; i<tprops.Length; i++) {
742 PropertyInfo prop = tprops[i];
743 if (!prop.CanRead) continue;
744 if (prop.GetIndexParameters().Length > 0) continue;
745 if (prop.DeclaringType == typeList[ti])
752 foreach (PropertyInfo prop in tprops)
754 // This statement ensures properties are ordered starting from the base type.
755 if (currentType != prop.DeclaringType)
757 currentType = prop.DeclaringType;
760 if (!prop.CanRead) continue;
761 if (prop.GetIndexParameters().Length > 0) continue;
762 propList.Insert(currentIndex++, prop);
765 var members = new List<XmlReflectionMember>();
768 // We now step through the type hierarchy from the base (object) through
769 // to the supplied class, as each step outputting all Fields, and then
770 // all Properties. This is the exact same ordering as .NET 1.0/1.1.
771 foreach (Type t in typeList)
773 // Add any fields matching the current DeclaringType.
774 while (fieldIndex < fieldList.Count)
776 FieldInfo field = (FieldInfo)fieldList[fieldIndex];
777 if (field.DeclaringType==t)
780 XmlAttributes atts = attributeOverrides[type, field.Name];
781 if (atts == null) atts = new XmlAttributes (field);
782 if (atts.XmlIgnore) continue;
783 XmlReflectionMember member = new XmlReflectionMember(field.Name, field.FieldType, atts);
784 member.DeclaringType = field.DeclaringType;
790 // Add any properties matching the current DeclaringType.
791 while (propIndex < propList.Count)
793 PropertyInfo prop = (PropertyInfo)propList[propIndex];
794 if (prop.DeclaringType==t)
797 XmlAttributes atts = attributeOverrides[type, prop.Name];
798 if (atts == null) atts = new XmlAttributes (prop);
799 if (atts.XmlIgnore) continue;
800 if (!prop.CanWrite) {
801 if (prop.PropertyType.IsGenericType && TypeData.GetGenericListItemType (prop.PropertyType) == null) continue; // check this before calling GetTypeData() which raises error for missing Add(). See bug #704813.
802 if (TypeTranslator.GetTypeData (prop.PropertyType).SchemaType != SchemaTypes.Array || prop.PropertyType.IsArray) continue;
804 XmlReflectionMember member = new XmlReflectionMember(prop.Name, prop.PropertyType, atts);
805 member.DeclaringType = prop.DeclaringType;
815 private XmlTypeMapMember CreateMapMember (Type declaringType, XmlReflectionMember rmember, string defaultNamespace)
817 XmlTypeMapMember mapMember;
818 XmlAttributes atts = rmember.XmlAttributes;
819 TypeData typeData = TypeTranslator.GetTypeData (rmember.MemberType);
821 if (atts.XmlArray != null) {
822 if (atts.XmlArray.Namespace != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
823 throw new InvalidOperationException ("XmlArrayAttribute.Form must not be Unqualified when it has an explicit Namespace value.");
824 if (typeData.SchemaType != SchemaTypes.Array &&
825 !(typeData.SchemaType == SchemaTypes.Primitive && typeData.Type == typeof (byte [])))
826 throw new InvalidOperationException ("XmlArrayAttribute can be applied to members of array or collection type.");
829 if (atts.XmlAnyAttribute != null)
831 if ( (rmember.MemberType.FullName == "System.Xml.XmlAttribute[]") ||
832 (rmember.MemberType.FullName == "System.Xml.XmlNode[]") )
834 mapMember = new XmlTypeMapMemberAnyAttribute();
837 throw new InvalidOperationException ("XmlAnyAttributeAttribute can only be applied to members of type XmlAttribute[] or XmlNode[]");
840 if (atts.XmlAnyElements != null && atts.XmlAnyElements.Count > 0)
842 // no XmlNode type check is done here (seealso: bug #553032).
843 XmlTypeMapMemberAnyElement member = new XmlTypeMapMemberAnyElement();
844 member.ElementInfo = ImportAnyElementInfo (defaultNamespace, rmember, member, atts);
849 XmlTypeMapMemberNamespaces mapNamespaces = new XmlTypeMapMemberNamespaces ();
850 mapMember = mapNamespaces;
852 else if (atts.XmlAttribute != null)
856 if (atts.XmlElements != null && atts.XmlElements.Count > 0)
857 throw new Exception ("XmlAttributeAttribute and XmlElementAttribute cannot be applied to the same member");
859 XmlTypeMapMemberAttribute mapAttribute = new XmlTypeMapMemberAttribute ();
860 if (atts.XmlAttribute.AttributeName.Length == 0)
861 mapAttribute.AttributeName = rmember.MemberName;
863 mapAttribute.AttributeName = atts.XmlAttribute.AttributeName;
865 mapAttribute.AttributeName = XmlConvert.EncodeLocalName (mapAttribute.AttributeName);
867 if (typeData.IsComplexType)
868 mapAttribute.MappedType = ImportTypeMapping (typeData.Type, null, defaultNamespace);
870 if (atts.XmlAttribute.Namespace != null && atts.XmlAttribute.Namespace != defaultNamespace)
872 if (atts.XmlAttribute.Form == XmlSchemaForm.Unqualified)
873 throw new InvalidOperationException ("The Form property may not be 'Unqualified' when an explicit Namespace property is present");
874 mapAttribute.Form = XmlSchemaForm.Qualified;
875 mapAttribute.Namespace = atts.XmlAttribute.Namespace;
879 mapAttribute.Form = atts.XmlAttribute.Form;
880 if (atts.XmlAttribute.Form == XmlSchemaForm.Qualified)
881 mapAttribute.Namespace = defaultNamespace;
883 mapAttribute.Namespace = "";
886 typeData = TypeTranslator.GetTypeData(rmember.MemberType, atts.XmlAttribute.DataType);
887 mapMember = mapAttribute;
889 else if (typeData.SchemaType == SchemaTypes.Array)
891 // If the member has a single XmlElementAttribute and the type is the type of the member,
892 // then it is not a flat list
894 if (atts.XmlElements.Count > 1 ||
895 (atts.XmlElements.Count == 1 && atts.XmlElements[0].Type != typeData.Type) ||
896 (atts.XmlText != null))
900 // check that it does not have XmlArrayAttribute
901 if (atts.XmlArray != null)
902 throw new InvalidOperationException ("XmlArrayAttribute cannot be used with members which also attributed with XmlElementAttribute or XmlTextAttribute.");
904 XmlTypeMapMemberFlatList member = new XmlTypeMapMemberFlatList ();
905 member.ListMap = new ListMap ();
906 member.ListMap.ItemInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName (rmember.MemberName), defaultNamespace, typeData.ListItemType, member, atts);
907 member.ElementInfo = member.ListMap.ItemInfo;
908 member.ListMap.ChoiceMember = member.ChoiceMember;
915 XmlTypeMapMemberList member = new XmlTypeMapMemberList ();
917 // Creates an ElementInfo that identifies the array instance.
918 member.ElementInfo = new XmlTypeMapElementInfoList();
919 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, typeData);
920 elem.ElementName = XmlConvert.EncodeLocalName((atts.XmlArray != null && atts.XmlArray.ElementName.Length != 0) ? atts.XmlArray.ElementName : rmember.MemberName);
921 // note that it could be changed below (when Form is Unqualified)
922 elem.Namespace = (atts.XmlArray != null && atts.XmlArray.Namespace != null) ? atts.XmlArray.Namespace : defaultNamespace;
923 elem.MappedType = ImportListMapping (rmember.MemberType, null, elem.Namespace, atts, 0);
924 elem.IsNullable = (atts.XmlArray != null) ? atts.XmlArray.IsNullable : false;
925 elem.Form = (atts.XmlArray != null) ? atts.XmlArray.Form : XmlSchemaForm.Qualified;
926 elem.ExplicitOrder = (atts.XmlArray != null) ? atts.XmlArray.Order : -1;
927 // This is a bit tricky, but is done
928 // after filling descendant members, so
929 // that array items could be serialized
930 // with proper namespace.
931 if (atts.XmlArray != null && atts.XmlArray.Form == XmlSchemaForm.Unqualified)
932 elem.Namespace = String.Empty;
934 member.ElementInfo.Add (elem);
942 XmlTypeMapMemberElement member = new XmlTypeMapMemberElement ();
943 member.ElementInfo = ImportElementInfo (declaringType, XmlConvert.EncodeLocalName(rmember.MemberName), defaultNamespace, rmember.MemberType, member, atts);
947 mapMember.DefaultValue = GetDefaultValue (typeData, atts.XmlDefaultValue);
948 mapMember.TypeData = typeData;
949 mapMember.Name = rmember.MemberName;
950 mapMember.IsReturnValue = rmember.IsReturnValue;
954 XmlTypeMapElementInfoList ImportElementInfo (Type cls, string defaultName, string defaultNamespace, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts)
956 EnumMap choiceEnumMap = null;
957 Type choiceEnumType = null;
959 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
960 ImportTextElementInfo (list, defaultType, member, atts, defaultNamespace);
962 if (atts.XmlChoiceIdentifier != null) {
964 throw new InvalidOperationException ("XmlChoiceIdentifierAttribute not supported in this context.");
966 member.ChoiceMember = atts.XmlChoiceIdentifier.MemberName;
967 MemberInfo[] mems = cls.GetMember (member.ChoiceMember, BindingFlags.Instance|BindingFlags.Public);
969 if (mems.Length == 0)
970 throw new InvalidOperationException ("Choice member '" + member.ChoiceMember + "' not found in class '" + cls);
972 if (mems[0] is PropertyInfo) {
973 PropertyInfo pi = (PropertyInfo)mems[0];
974 if (!pi.CanWrite || !pi.CanRead)
975 throw new InvalidOperationException ("Choice property '" + member.ChoiceMember + "' must be read/write.");
976 choiceEnumType = pi.PropertyType;
978 else choiceEnumType = ((FieldInfo)mems[0]).FieldType;
980 member.ChoiceTypeData = TypeTranslator.GetTypeData (choiceEnumType);
982 if (choiceEnumType.IsArray)
983 choiceEnumType = choiceEnumType.GetElementType ();
985 choiceEnumMap = ImportTypeMapping (choiceEnumType).ObjectMap as EnumMap;
986 if (choiceEnumMap == null)
987 throw new InvalidOperationException ("The member '" + mems[0].Name + "' is not a valid target for XmlChoiceIdentifierAttribute.");
990 if (atts.XmlElements.Count == 0 && list.Count == 0)
992 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType));
993 elem.ElementName = defaultName;
994 elem.Namespace = defaultNamespace;
995 if (elem.TypeData.IsComplexType)
996 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
1000 bool multiType = (atts.XmlElements.Count > 1);
1001 foreach (XmlElementAttribute att in atts.XmlElements)
1003 Type elemType = (att.Type != null) ? att.Type : defaultType;
1004 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(elemType, att.DataType));
1005 elem.Form = att.Form;
1006 if (elem.Form != XmlSchemaForm.Unqualified)
1007 elem.Namespace = (att.Namespace != null) ? att.Namespace : defaultNamespace;
1009 // elem may already be nullable, and IsNullable property in XmlElement is false by default
1010 if (att.IsNullable && !elem.IsNullable)
1011 elem.IsNullable = att.IsNullable;
1013 elem.ExplicitOrder = att.Order;
1015 if (elem.IsNullable && !elem.TypeData.IsNullable)
1016 throw new InvalidOperationException ("IsNullable may not be 'true' for value type " + elem.TypeData.FullTypeName + " in member '" + defaultName + "'");
1018 if (elem.TypeData.IsComplexType)
1020 if (att.DataType.Length != 0) throw new InvalidOperationException (
1021 string.Format(CultureInfo.InvariantCulture, "'{0}' is "
1022 + "an invalid value for '{1}.{2}' of type '{3}'. "
1023 + "The property may only be specified for primitive types.",
1024 att.DataType, cls.FullName, defaultName,
1025 elem.TypeData.FullTypeName));
1026 elem.MappedType = ImportTypeMapping (elemType, null, elem.Namespace);
1029 if (att.ElementName.Length != 0) {
1030 elem.ElementName = XmlConvert.EncodeLocalName(att.ElementName);
1031 } else if (multiType) {
1032 if (elem.MappedType != null) {
1033 elem.ElementName = elem.MappedType.ElementName;
1035 elem.ElementName = TypeTranslator.GetTypeData (elemType).XmlType;
1038 elem.ElementName = defaultName;
1041 if (choiceEnumMap != null) {
1042 string cname = choiceEnumMap.GetEnumName (choiceEnumType.FullName, elem.ElementName);
1044 throw new InvalidOperationException (string.Format (
1045 CultureInfo.InvariantCulture, "Type {0} is missing"
1046 + " enumeration value '{1}' for element '{1} from"
1047 + " namespace '{2}'.", choiceEnumType, elem.ElementName,
1049 elem.ChoiceValue = Enum.Parse (choiceEnumType, cname, false);
1057 XmlTypeMapElementInfoList ImportAnyElementInfo (string defaultNamespace, XmlReflectionMember rmember, XmlTypeMapMemberElement member, XmlAttributes atts)
1059 XmlTypeMapElementInfoList list = new XmlTypeMapElementInfoList();
1061 ImportTextElementInfo (list, rmember.MemberType, member, atts, defaultNamespace);
1063 foreach (XmlAnyElementAttribute att in atts.XmlAnyElements)
1065 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(typeof(XmlElement)));
1066 if (att.Name.Length != 0)
1068 elem.ElementName = XmlConvert.EncodeLocalName(att.Name);
1069 elem.Namespace = (att.Namespace != null) ? att.Namespace : "";
1073 elem.IsUnnamedAnyElement = true;
1074 elem.Namespace = defaultNamespace;
1075 if (att.Namespace != null)
1076 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.");
1078 elem.ExplicitOrder = att.Order;
1084 void ImportTextElementInfo (XmlTypeMapElementInfoList list, Type defaultType, XmlTypeMapMemberElement member, XmlAttributes atts, string defaultNamespace)
1086 if (atts.XmlText != null)
1088 member.IsXmlTextCollector = true;
1089 if (atts.XmlText.Type != null) {
1090 TypeData td = TypeTranslator.GetTypeData (defaultType);
1091 if ((td.SchemaType == SchemaTypes.Primitive || td.SchemaType == SchemaTypes.Enum) && atts.XmlText.Type != defaultType) {
1092 throw new InvalidOperationException ("The type for XmlText may not be specified for primitive types.");
1094 defaultType = atts.XmlText.Type;
1096 if (defaultType == typeof(XmlNode)) defaultType = typeof(XmlText); // Nodes must be text nodes
1098 XmlTypeMapElementInfo elem = new XmlTypeMapElementInfo (member, TypeTranslator.GetTypeData(defaultType, atts.XmlText.DataType));
1100 if (elem.TypeData.SchemaType != SchemaTypes.Primitive &&
1101 elem.TypeData.SchemaType != SchemaTypes.Enum &&
1102 elem.TypeData.SchemaType != SchemaTypes.XmlNode &&
1103 !(elem.TypeData.SchemaType == SchemaTypes.Array && elem.TypeData.ListItemTypeData.SchemaType == SchemaTypes.XmlNode)
1105 throw new InvalidOperationException ("XmlText cannot be used to encode complex types");
1107 if (elem.TypeData.IsComplexType)
1108 elem.MappedType = ImportTypeMapping (defaultType, null, defaultNamespace);
1109 elem.IsTextElement = true;
1110 elem.WrappedElement = false;
1115 bool CanBeNull (TypeData type)
1117 #if !NET_2_0 // idiotic compatibility
1118 if (type.Type == typeof (XmlQualifiedName))
1121 return !type.Type.IsValueType || type.IsNullable;
1124 public void IncludeType (Type type)
1127 throw new ArgumentNullException ("type");
1129 if (includedTypes == null) includedTypes = new ArrayList ();
1130 if (!includedTypes.Contains (type))
1131 includedTypes.Add (type);
1133 if (relatedMaps.Count > 0) {
1134 foreach (XmlTypeMapping map in (ArrayList) relatedMaps.Clone ()) {
1135 if (map.TypeData.Type == typeof(object))
1136 map.DerivedTypes.Add (ImportTypeMapping (type));
1141 public void IncludeTypes (ICustomAttributeProvider provider)
1143 object[] ats = provider.GetCustomAttributes (typeof(XmlIncludeAttribute), true);
1145 foreach (XmlIncludeAttribute at in ats)
1146 IncludeType (at.Type);
1149 private object GetDefaultValue (TypeData typeData, object defaultValue)
1151 if (defaultValue == DBNull.Value || typeData.SchemaType != SchemaTypes.Enum)
1152 return defaultValue;
1154 // get string representation of enum value
1155 string namedValue = Enum.Format (typeData.Type, defaultValue, "g");
1156 // get decimal representation of enum value
1157 string decimalValue = Enum.Format (typeData.Type, defaultValue, "d");
1158 // if decimal representation matches string representation, then
1159 // the value is not defined in the enum type (as the "g" format
1160 // will return the decimal equivalent of the value if the value
1161 // is not equal to a combination of named enumerated constants
1162 if (namedValue == decimalValue) {
1163 string msg = string.Format (CultureInfo.InvariantCulture,
1164 "Value '{0}' cannot be converted to {1}.", defaultValue,
1165 defaultValue.GetType ().FullName);
1166 throw new InvalidOperationException (msg);
1169 // XmlSerializer expects integral enum value
1170 //return namedValue.Replace (',', ' ');
1171 return defaultValue;
1174 #endregion // Methods