5 // John Donagher (john@webmeta.com)
6 // Lluis Sanchez Gual (lluis@ximian.com)
8 // (C) 2002 John Donagher
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections;
35 using System.Globalization;
36 using System.Xml.Schema;
37 using System.Reflection;
39 namespace System.Xml.Serialization
41 public class XmlTypeMapping : XmlMapping
43 private string xmlType;
44 private string xmlTypeNamespace;
46 XmlTypeMapping baseMap;
47 bool multiReferenceType = false;
51 bool isNullable = true;
54 ArrayList _derivedTypes = new ArrayList();
56 internal XmlTypeMapping(string elementName, string ns, TypeData typeData, string xmlType, string xmlTypeNamespace)
57 : base (elementName, ns)
60 this.xmlType = xmlType;
61 this.xmlTypeNamespace = xmlTypeNamespace;
65 public string ElementName
67 get { return _elementName; }
70 public string Namespace
72 get { return _namespace; }
76 public string TypeFullName
78 get { return type.FullTypeName; }
81 public string TypeName
83 get { return type.TypeName; }
87 public string XsdTypeName
89 get { return XmlType; }
92 public string XsdTypeNamespace
94 get { return XmlTypeNamespace; }
98 internal TypeData TypeData
103 internal string XmlType
105 get { return xmlType; }
106 set { xmlType = value; }
109 internal string XmlTypeNamespace
111 get { return xmlTypeNamespace ?? string.Empty; }
112 set { xmlTypeNamespace = value; }
115 internal bool HasXmlTypeNamespace
117 get { return xmlTypeNamespace != null; }
120 internal ArrayList DerivedTypes
122 get { return _derivedTypes; }
123 set { _derivedTypes = value; }
126 internal bool MultiReferenceType
128 get { return multiReferenceType; }
129 set { multiReferenceType = value; }
132 internal XmlTypeMapping BaseMap
134 get { return baseMap; }
135 set { baseMap = value; }
138 internal bool IsSimpleType
140 get { return isSimpleType; }
141 set { isSimpleType = value; }
144 internal string Documentation
146 set { documentation = value; }
147 get { return documentation; }
150 internal bool IncludeInSchema
152 get { return includeInSchema; }
153 set { includeInSchema = value; }
156 internal bool IsNullable
158 get { return isNullable; }
159 set { isNullable = value; }
164 get { return isAny; }
165 set { isAny = value; }
168 internal XmlTypeMapping GetRealTypeMap (Type objectType)
170 if (TypeData.SchemaType == SchemaTypes.Enum)
173 // Returns the map for a subtype of this map's type
174 if (TypeData.Type == objectType) return this;
175 for (int n=0; n<_derivedTypes.Count; n++) {
176 XmlTypeMapping map = (XmlTypeMapping) _derivedTypes[n];
177 if (map.TypeData.Type == objectType) return map;
183 internal XmlTypeMapping GetRealElementMap (string name, string ens)
185 if (xmlType == name && XmlTypeNamespace == ens) return this;
186 foreach (XmlTypeMapping map in _derivedTypes)
187 if (map.xmlType == name && map.XmlTypeNamespace == ens) return map;
192 internal void UpdateRoot (XmlQualifiedName qname)
195 this._elementName = qname.Name;
196 this._namespace = qname.Namespace;
202 // Mapping info for XmlSerializable
203 internal class XmlSerializableMapping : XmlTypeMapping
206 XmlSchemaComplexType _schemaType;
207 XmlQualifiedName _schemaTypeName;
209 internal XmlSerializableMapping(XmlRootAttribute root, string elementName, string ns, TypeData typeData, string xmlType, string xmlTypeNamespace)
210 : base(elementName, ns, typeData, xmlType, xmlTypeNamespace)
212 XmlSchemaProviderAttribute schemaProvider = (XmlSchemaProviderAttribute) Attribute.GetCustomAttribute (typeData.Type, typeof (XmlSchemaProviderAttribute));
214 if (schemaProvider != null) {
215 _schemaTypeName = XmlQualifiedName.Empty;
217 if (schemaProvider.IsAny) {
222 string method = schemaProvider.MethodName;
223 MethodInfo mi = typeData.Type.GetMethod (method, BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
225 throw new InvalidOperationException (String.Format ("Type '{0}' must implement public static method '{1}'", typeData.Type, method));
226 if (!typeof (XmlQualifiedName).IsAssignableFrom (mi.ReturnType) &&
227 // LAMESPEC: it is undocumented. (We don't have to tell users about it in the error message.)
228 // Also do not add such a silly compatibility test to assert that it does not raise an error.
229 !typeof (XmlSchemaComplexType).IsAssignableFrom (mi.ReturnType))
230 throw new InvalidOperationException (String.Format ("Method '{0}' indicated by XmlSchemaProviderAttribute must have its return type as XmlQualifiedName", method));
231 XmlSchemaSet xs = new XmlSchemaSet ();
232 object retVal = mi.Invoke (null, new object [] { xs });
236 if (retVal is XmlSchemaComplexType) {
237 _schemaType = (XmlSchemaComplexType) retVal;
238 if (!_schemaType.QualifiedName.IsEmpty)
239 _schemaTypeName = _schemaType.QualifiedName;
241 _schemaTypeName = new XmlQualifiedName (xmlType, xmlTypeNamespace);
243 else if (retVal is XmlQualifiedName) {
244 _schemaTypeName = (XmlQualifiedName)retVal;
247 throw new InvalidOperationException (
248 String.Format ("Method {0}.{1}() specified by XmlSchemaProviderAttribute has invalid signature: return type must be compatible with System.Xml.XmlQualifiedName.", typeData.Type.Name, method));
250 // defaultNamespace at XmlReflectionImporter takes precedence for Namespace, but not for XsdTypeNamespace.
251 UpdateRoot (new XmlQualifiedName (root != null ? root.ElementName : _schemaTypeName.Name, root != null ? root.Namespace : Namespace ?? _schemaTypeName.Namespace));
252 XmlTypeNamespace = _schemaTypeName.Namespace;
253 XmlType = _schemaTypeName.Name;
255 if (!_schemaTypeName.IsEmpty && xs.Count > 0) {
256 XmlSchema [] schemas = new XmlSchema [xs.Count];
257 xs.CopyTo (schemas, 0);
258 _schema = schemas [0];
264 IXmlSerializable serializable = (IXmlSerializable)Activator.CreateInstance (typeData.Type, true);
266 _schema = serializable.GetSchema();
267 } catch (Exception) {
268 // LAMESPEC: .NET has a bad exception catch and swallows it silently.
273 if (_schema.Id == null || _schema.Id.Length == 0)
274 throw new InvalidOperationException("Schema Id is missing. The schema returned from " + typeData.Type.FullName + ".GetSchema() must have an Id.");
278 internal XmlSchema Schema
280 get { return _schema; }
283 internal XmlSchemaType SchemaType {
284 get { return _schemaType; }
287 internal XmlQualifiedName SchemaTypeName {
288 get { return _schemaTypeName; }
293 // Mapping info for classes and structs
295 internal class ClassMap: ObjectMap
297 Hashtable _elements = new Hashtable ();
298 ArrayList _elementMembers;
299 Hashtable _attributeMembers;
300 XmlTypeMapMemberAttribute[] _attributeMembersArray;
301 XmlTypeMapElementInfo[] _elementsByIndex;
302 ArrayList _flatLists;
303 ArrayList _allMembers = new ArrayList ();
304 ArrayList _membersWithDefault;
305 ArrayList _listMembers;
306 XmlTypeMapMemberAnyElement _defaultAnyElement;
307 XmlTypeMapMemberAnyAttribute _defaultAnyAttribute;
308 XmlTypeMapMemberNamespaces _namespaceDeclarations;
309 XmlTypeMapMember _xmlTextCollector;
310 XmlTypeMapMember _returnMember;
311 bool _ignoreMemberNamespace;
312 bool _canBeSimpleType = true;
313 bool? _isOrderDependentMap;
315 public void AddMember (XmlTypeMapMember member)
317 // If GlobalIndex has not been set, set it now
318 if (member.GlobalIndex == -1)
319 member.GlobalIndex = _allMembers.Count;
321 _allMembers.Add (member);
323 if (!(member.DefaultValue is System.DBNull) && member.DefaultValue != null) {
324 if (_membersWithDefault == null) _membersWithDefault = new ArrayList ();
325 _membersWithDefault.Add (member);
328 if (member.IsReturnValue)
329 _returnMember = member;
331 if (member is XmlTypeMapMemberAttribute)
333 XmlTypeMapMemberAttribute atm = (XmlTypeMapMemberAttribute)member;
334 if (_attributeMembers == null) _attributeMembers = new Hashtable();
335 string key = BuildKey (atm.AttributeName, atm.Namespace, -1);
336 if (_attributeMembers.ContainsKey (key))
337 throw new InvalidOperationException ("The XML attribute named '" + atm.AttributeName + "' from namespace '" + atm.Namespace + "' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the attribute.");
338 member.Index = _attributeMembers.Count;
339 _attributeMembers.Add (key, member);
342 else if (member is XmlTypeMapMemberFlatList)
344 RegisterFlatList ((XmlTypeMapMemberFlatList)member);
346 else if (member is XmlTypeMapMemberAnyElement)
348 XmlTypeMapMemberAnyElement mem = (XmlTypeMapMemberAnyElement) member;
349 if (mem.IsDefaultAny) _defaultAnyElement = mem;
350 if (mem.TypeData.IsListType) RegisterFlatList (mem);
352 else if (member is XmlTypeMapMemberAnyAttribute)
354 _defaultAnyAttribute = (XmlTypeMapMemberAnyAttribute) member;
357 else if (member is XmlTypeMapMemberNamespaces)
359 _namespaceDeclarations = (XmlTypeMapMemberNamespaces) member;
363 if (member is XmlTypeMapMemberElement && ((XmlTypeMapMemberElement)member).IsXmlTextCollector)
365 if (_xmlTextCollector != null) throw new InvalidOperationException ("XmlTextAttribute can only be applied once in a class");
366 _xmlTextCollector = member;
369 if (_elementMembers == null) {
370 _elementMembers = new ArrayList();
371 _elements = new Hashtable();
374 member.Index = _elementMembers.Count;
375 _elementMembers.Add (member);
377 ICollection elemsInfo = ((XmlTypeMapMemberElement)member).ElementInfo;
378 foreach (XmlTypeMapElementInfo elem in elemsInfo)
380 string key = BuildKey (elem.ElementName, elem.Namespace, elem.ExplicitOrder);
381 if (_elements.ContainsKey (key))
382 throw new InvalidOperationException ("The XML element named '" + elem.ElementName + "' from namespace '" + elem.Namespace + "' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.");
383 _elements.Add (key, elem);
386 if (member.TypeData.IsListType && member.TypeData.Type != null && !member.TypeData.Type.IsArray) {
387 if (_listMembers == null) _listMembers = new ArrayList ();
388 _listMembers.Add (member);
392 void RegisterFlatList (XmlTypeMapMemberExpandable member)
394 if (_flatLists == null) _flatLists = new ArrayList ();
395 member.FlatArrayIndex = _flatLists.Count;
396 _flatLists.Add (member);
399 public XmlTypeMapMemberAttribute GetAttribute (string name, string ns)
401 if (_attributeMembers == null) return null;
402 return (XmlTypeMapMemberAttribute)_attributeMembers [BuildKey (name,ns, -1)];
405 public XmlTypeMapElementInfo GetElement(string name, string ns, int minimalOrder)
407 if (_elements == null) return null;
409 foreach (XmlTypeMapElementInfo info in _elements.Values)
410 if (info.ElementName == name && info.Namespace == ns && info.ExplicitOrder >= minimalOrder)
416 public XmlTypeMapElementInfo GetElement(string name, string ns)
418 if (_elements == null) return null;
420 foreach (XmlTypeMapElementInfo info in _elements.Values)
421 if (info.ElementName == name && info.Namespace == ns)
427 public XmlTypeMapElementInfo GetElement (int index)
429 if (_elements == null) return null;
431 if (_elementsByIndex == null)
433 _elementsByIndex = new XmlTypeMapElementInfo [_elementMembers.Count];
434 foreach (XmlTypeMapMemberElement mem in _elementMembers)
436 if (mem.ElementInfo.Count != 1)
437 throw new InvalidOperationException ("Read by order only possible for encoded/bare format");
439 _elementsByIndex [mem.Index] = (XmlTypeMapElementInfo) mem.ElementInfo [0];
442 if (index >= _elementMembers.Count)
444 return _elementsByIndex [index];
447 private string BuildKey (string name, string ns, int explicitOrder)
449 if (_ignoreMemberNamespace) return name;
450 else return name + " / " + ns + (explicitOrder < 0 ? "" : "/" + explicitOrder);
453 public ICollection AllElementInfos
455 get { return _elements.Values; }
459 public bool IgnoreMemberNamespace
461 get { return _ignoreMemberNamespace; }
462 set { _ignoreMemberNamespace = value; }
465 public bool IsOrderDependentMap {
467 if (_isOrderDependentMap == null) {
468 _isOrderDependentMap = false;
469 foreach (XmlTypeMapElementInfo ei in _elements.Values)
470 if (ei.ExplicitOrder >= 0) {
471 _isOrderDependentMap = true;
475 return (bool) _isOrderDependentMap;
479 public XmlTypeMapMember FindMember (string name)
481 for (int n=0; n<_allMembers.Count; n++)
482 if (((XmlTypeMapMember)_allMembers[n]).Name == name) return (XmlTypeMapMember)_allMembers[n];
486 public XmlTypeMapMemberAnyElement DefaultAnyElementMember
488 get { return _defaultAnyElement; }
491 public XmlTypeMapMemberAnyAttribute DefaultAnyAttributeMember
493 get { return _defaultAnyAttribute; }
496 public XmlTypeMapMemberNamespaces NamespaceDeclarations
498 get { return _namespaceDeclarations; }
501 public ICollection AttributeMembers
505 if (_attributeMembers == null) return null;
506 if (_attributeMembersArray != null) return _attributeMembersArray;
508 _attributeMembersArray = new XmlTypeMapMemberAttribute[_attributeMembers.Count];
509 foreach (XmlTypeMapMemberAttribute mem in _attributeMembers.Values)
510 _attributeMembersArray [mem.Index] = mem;
511 return _attributeMembersArray;
515 public ICollection ElementMembers
517 get { return _elementMembers; }
520 public ArrayList AllMembers
522 get { return _allMembers; }
525 public ArrayList FlatLists
527 get { return _flatLists; }
530 public ArrayList MembersWithDefault
532 get { return _membersWithDefault; }
535 public ArrayList ListMembers
537 get { return _listMembers; }
540 public XmlTypeMapMember XmlTextCollector
542 get { return _xmlTextCollector; }
545 public XmlTypeMapMember ReturnMember
547 get { return _returnMember; }
550 public XmlQualifiedName SimpleContentBaseType
554 if (!_canBeSimpleType || _elementMembers == null || _elementMembers.Count != 1) return null;
555 XmlTypeMapMemberElement member = (XmlTypeMapMemberElement) _elementMembers[0];
556 if (member.ElementInfo.Count != 1) return null;
557 XmlTypeMapElementInfo einfo = (XmlTypeMapElementInfo) member.ElementInfo[0];
558 if (!einfo.IsTextElement) return null;
559 if (member.TypeData.SchemaType == SchemaTypes.Primitive || member.TypeData.SchemaType == SchemaTypes.Enum)
560 return new XmlQualifiedName (einfo.TypeData.XmlType, einfo.DataTypeNamespace);
565 public void SetCanBeSimpleType (bool can)
567 _canBeSimpleType = can;
570 public bool HasSimpleContent
574 return SimpleContentBaseType != null;
580 // Mapping info for arrays and lists
582 internal class ListMap: ObjectMap
584 XmlTypeMapElementInfoList _itemInfo;
585 bool _gotNestedMapping;
586 XmlTypeMapping _nestedArrayMapping;
587 string _choiceMember;
589 public bool IsMultiArray
593 return (NestedArrayMapping != null);
597 public string ChoiceMember
599 get { return _choiceMember; }
600 set { _choiceMember = value; }
603 public XmlTypeMapping NestedArrayMapping
607 if (_gotNestedMapping) return _nestedArrayMapping;
608 _gotNestedMapping = true;
610 _nestedArrayMapping = ((XmlTypeMapElementInfo)_itemInfo[0]).MappedType;
612 if (_nestedArrayMapping == null) return null;
614 if (_nestedArrayMapping.TypeData.SchemaType != SchemaTypes.Array) {
615 _nestedArrayMapping = null; return null;
618 foreach (XmlTypeMapElementInfo elem in _itemInfo)
619 if (elem.MappedType != _nestedArrayMapping) {
620 _nestedArrayMapping = null;
624 return _nestedArrayMapping;
628 public XmlTypeMapElementInfoList ItemInfo
631 get { return _itemInfo; }
632 set { _itemInfo = value; }
635 public XmlTypeMapElementInfo FindElement (object ob, int index, object memberValue)
637 if (_itemInfo.Count == 1)
638 return (XmlTypeMapElementInfo) _itemInfo[0];
639 else if (_choiceMember != null && index != -1)
641 Array values = (Array) XmlTypeMapMember.GetValue (ob, _choiceMember);
642 if (values == null || index >= values.Length)
643 throw new InvalidOperationException ("Invalid or missing choice enum value in member '" + _choiceMember + "'.");
644 object val = values.GetValue (index);
645 foreach (XmlTypeMapElementInfo elem in _itemInfo)
646 if (elem.ChoiceValue != null && elem.ChoiceValue.Equals (val))
651 if (memberValue == null) return null;
652 Type type = memberValue.GetType();
653 foreach (XmlTypeMapElementInfo elem in _itemInfo)
654 if (elem.TypeData.Type == type) return elem;
659 public XmlTypeMapElementInfo FindElement (string elementName, string ns)
661 foreach (XmlTypeMapElementInfo elem in _itemInfo)
662 if (elem.ElementName == elementName && elem.Namespace == ns) return elem;
666 public XmlTypeMapElementInfo FindTextElement ()
668 foreach (XmlTypeMapElementInfo elem in _itemInfo)
669 if (elem.IsTextElement) return elem;
673 public string GetSchemaArrayName ()
675 XmlTypeMapElementInfo einfo = (XmlTypeMapElementInfo) _itemInfo[0];
676 if (einfo.MappedType != null) return TypeTranslator.GetArrayName (einfo.MappedType.XmlType);
677 else return TypeTranslator.GetArrayName (einfo.TypeData.XmlType);
680 public void GetArrayType (int itemCount, out string localName, out string ns)
683 if (itemCount != -1) arrayDim = "[" + itemCount + "]";
684 else arrayDim = "[]";
686 XmlTypeMapElementInfo info = (XmlTypeMapElementInfo) _itemInfo[0];
687 if (info.TypeData.SchemaType == SchemaTypes.Array)
690 ((ListMap)info.MappedType.ObjectMap).GetArrayType (-1, out nm, out ns);
691 localName = nm + arrayDim;
695 if (info.MappedType != null)
697 localName = info.MappedType.XmlType + arrayDim;
698 ns = info.MappedType.Namespace;
702 localName = info.TypeData.XmlType + arrayDim;
703 ns = info.DataTypeNamespace;
708 public override bool Equals (object other)
710 ListMap lmap = other as ListMap;
711 if (lmap == null) return false;
713 if (_itemInfo.Count != lmap._itemInfo.Count) return false;
714 for (int n=0; n<_itemInfo.Count; n++)
715 if (!_itemInfo[n].Equals (lmap._itemInfo[n])) return false;
719 public override int GetHashCode ()
721 return base.GetHashCode ();
725 internal class EnumMap: ObjectMap
727 readonly EnumMapMember[] _members;
728 readonly bool _isFlags;
729 readonly string[] _enumNames = null;
730 readonly string[] _xmlNames = null;
731 readonly long[] _values = null;
733 public class EnumMapMember
735 readonly string _xmlName;
736 readonly string _enumName;
737 readonly long _value;
738 string _documentation;
740 public EnumMapMember (string xmlName, string enumName)
741 : this (xmlName, enumName, 0)
745 public EnumMapMember (string xmlName, string enumName, long value)
748 _enumName = enumName;
752 public string XmlName
754 get { return _xmlName; }
757 public string EnumName
759 get { return _enumName; }
764 get { return _value; }
767 public string Documentation
769 get { return _documentation; }
770 set { _documentation = value; }
774 public EnumMap (EnumMapMember[] members, bool isFlags)
779 _enumNames = new string[_members.Length];
780 _xmlNames = new string[_members.Length];
781 _values = new long[_members.Length];
783 for (int i = 0; i < _members.Length; i++) {
784 EnumMapMember mem = _members[i];
785 _enumNames[i] = mem.EnumName;
786 _xmlNames[i] = mem.XmlName;
787 _values[i] = mem.Value;
793 get { return _isFlags; }
796 public EnumMapMember[] Members
798 get { return _members; }
801 public string[] EnumNames
808 public string[] XmlNames
822 public string GetXmlName (string typeName, object enumValue)
824 if (enumValue is string) {
825 throw new InvalidCastException ();
831 value = ((IConvertible) enumValue).ToInt64 (CultureInfo.CurrentCulture);
832 } catch (FormatException) {
833 throw new InvalidCastException ();
836 for (int i = 0; i < Values.Length; i++) {
837 if (Values[i] == value)
841 if (IsFlags && value == 0)
844 string xmlName = string.Empty;
847 xmlName = XmlCustomFormatter.FromEnum (value, XmlNames, Values, typeName);
849 xmlName = XmlCustomFormatter.FromEnum (value, XmlNames, Values);
853 if (xmlName.Length == 0) {
855 throw new InvalidOperationException (string.Format(CultureInfo.CurrentCulture,
856 "'{0}' is not a valid value for {1}.", value, typeName));
858 return value.ToString (CultureInfo.InvariantCulture);
864 public string GetEnumName (string typeName, string xmlName)
867 xmlName = xmlName.Trim ();
868 if (xmlName.Length == 0)
871 System.Text.StringBuilder sb = new System.Text.StringBuilder ();
872 string[] enumNames = xmlName.Split (null);
873 foreach (string name in enumNames) {
874 if (name == string.Empty) continue;
875 string foundEnumValue = null;
876 for (int i = 0; i < XmlNames.Length; i++)
877 if (XmlNames[i] == name) {
878 foundEnumValue = EnumNames[i];
882 if (foundEnumValue != null) {
885 sb.Append (foundEnumValue);
887 throw new InvalidOperationException (string.Format (CultureInfo.CurrentCulture,
888 "'{0}' is not a valid value for {1}.", name, typeName));
891 return sb.ToString ();
894 foreach (EnumMapMember mem in _members)
895 if (mem.XmlName == xmlName) return mem.EnumName;