2 // XmlTypeMapping.cs:
\r
5 // John Donagher (john@webmeta.com)
\r
6 // Lluis Sanchez Gual (lluis@ximian.com)
\r
8 // (C) 2002 John Donagher
\r
12 // Permission is hereby granted, free of charge, to any person obtaining
\r
13 // a copy of this software and associated documentation files (the
\r
14 // "Software"), to deal in the Software without restriction, including
\r
15 // without limitation the rights to use, copy, modify, merge, publish,
\r
16 // distribute, sublicense, and/or sell copies of the Software, and to
\r
17 // permit persons to whom the Software is furnished to do so, subject to
\r
18 // the following conditions:
\r
20 // The above copyright notice and this permission notice shall be
\r
21 // included in all copies or substantial portions of the Software.
\r
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\r
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
\r
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\r
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
\r
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
\r
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
\r
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
34 using System.Collections;
\r
35 using System.Globalization;
\r
36 using System.Xml.Schema;
\r
38 namespace System.Xml.Serialization
\r
40 public class XmlTypeMapping : XmlMapping
\r
42 private string xmlType;
\r
43 private string xmlTypeNamespace;
\r
45 XmlTypeMapping baseMap;
\r
46 bool multiReferenceType = false;
\r
48 string documentation;
\r
49 bool includeInSchema;
\r
50 bool isNullable = true;
\r
52 ArrayList _derivedTypes = new ArrayList();
\r
54 internal XmlTypeMapping(string elementName, string ns, TypeData typeData, string xmlType, string xmlTypeNamespace)
\r
55 : base (elementName, ns)
\r
57 this.type = typeData;
\r
58 this.xmlType = xmlType;
\r
59 this.xmlTypeNamespace = xmlTypeNamespace;
\r
63 public string ElementName
\r
65 get { return _elementName; }
\r
68 public string Namespace
\r
70 get { return _namespace; }
\r
74 public string TypeFullName
\r
76 get { return type.FullTypeName; }
\r
79 public string TypeName
\r
81 get { return type.TypeName; }
\r
84 internal TypeData TypeData
\r
86 get { return type; }
\r
89 internal string XmlType
\r
91 get { return xmlType; }
\r
94 internal string XmlTypeNamespace
\r
96 get { return xmlTypeNamespace; }
\r
99 internal ArrayList DerivedTypes
\r
101 get { return _derivedTypes; }
\r
102 set { _derivedTypes = value; }
\r
105 internal bool MultiReferenceType
\r
107 get { return multiReferenceType; }
\r
108 set { multiReferenceType = value; }
\r
111 internal XmlTypeMapping BaseMap
\r
113 get { return baseMap; }
\r
114 set { baseMap = value; }
\r
117 internal bool IsSimpleType
\r
119 get { return isSimpleType; }
\r
120 set { isSimpleType = value; }
\r
123 internal string Documentation
\r
125 set { documentation = value; }
\r
126 get { return documentation; }
\r
129 internal bool IncludeInSchema
\r
131 get { return includeInSchema; }
\r
132 set { includeInSchema = value; }
\r
135 internal bool IsNullable
\r
137 get { return isNullable; }
\r
138 set { isNullable = value; }
\r
141 internal XmlTypeMapping GetRealTypeMap (string objectFullTypeName)
\r
143 if (TypeData.SchemaType == SchemaTypes.Enum)
\r
146 // Returns the map for a subtype of this map's type
\r
147 objectFullTypeName = objectFullTypeName.Replace ('+','.');
\r
148 if (TypeFullName == objectFullTypeName) return this;
\r
149 for (int n=0; n<_derivedTypes.Count; n++) {
\r
150 XmlTypeMapping map = (XmlTypeMapping) _derivedTypes[n];
\r
151 if (map.TypeFullName == objectFullTypeName) return map;
\r
157 internal XmlTypeMapping GetRealElementMap (string name, string ens)
\r
159 if (xmlType == name && xmlTypeNamespace == ens) return this;
\r
160 foreach (XmlTypeMapping map in _derivedTypes)
\r
161 if (map.xmlType == name && map.xmlTypeNamespace == ens) return map;
\r
166 internal void UpdateRoot (XmlQualifiedName qname)
\r
168 if (qname != null) {
\r
169 this._elementName = qname.Name;
\r
170 this._namespace = qname.Namespace;
\r
176 // Mapping info for XmlSerializable
\r
177 internal class XmlSerializableMapping : XmlTypeMapping
\r
181 internal XmlSerializableMapping(string elementName, string ns, TypeData typeData, string xmlType, string xmlTypeNamespace)
\r
182 : base(elementName, ns, typeData, xmlType, xmlTypeNamespace)
\r
184 IXmlSerializable serializable = (IXmlSerializable)Activator.CreateInstance(typeData.Type);
\r
185 _schema = serializable.GetSchema();
\r
186 if (_schema != null)
\r
188 if (_schema.Id == null || _schema.Id.Length == 0)
\r
189 throw new InvalidOperationException("Schema Id is missing. The schema returned from " + typeData.Type.FullName + ".GetSchema() must have an Id.");
\r
193 internal XmlSchema Schema
\r
195 get { return _schema; }
\r
200 // Mapping info for classes and structs
\r
202 internal class ClassMap: ObjectMap
\r
204 Hashtable _elements = new Hashtable ();
\r
205 ArrayList _elementMembers;
\r
206 Hashtable _attributeMembers;
\r
207 XmlTypeMapMemberAttribute[] _attributeMembersArray;
\r
208 XmlTypeMapElementInfo[] _elementsByIndex;
\r
209 ArrayList _flatLists;
\r
210 ArrayList _allMembers = new ArrayList ();
\r
211 ArrayList _membersWithDefault;
\r
212 ArrayList _listMembers;
\r
213 XmlTypeMapMemberAnyElement _defaultAnyElement;
\r
214 XmlTypeMapMemberAnyAttribute _defaultAnyAttribute;
\r
215 XmlTypeMapMemberNamespaces _namespaceDeclarations;
\r
216 XmlTypeMapMember _xmlTextCollector;
\r
217 XmlTypeMapMember _returnMember;
\r
218 bool _ignoreMemberNamespace;
\r
219 bool _canBeSimpleType = true;
\r
221 public void AddMember (XmlTypeMapMember member)
\r
223 _allMembers.Add (member);
\r
225 if (!(member.DefaultValue is System.DBNull) && member.DefaultValue != null) {
\r
226 if (_membersWithDefault == null) _membersWithDefault = new ArrayList ();
\r
227 _membersWithDefault.Add (member);
\r
230 if (member.IsReturnValue)
\r
231 _returnMember = member;
\r
233 if (member is XmlTypeMapMemberAttribute)
\r
235 XmlTypeMapMemberAttribute atm = (XmlTypeMapMemberAttribute)member;
\r
236 if (_attributeMembers == null) _attributeMembers = new Hashtable();
\r
237 string key = BuildKey (atm.AttributeName, atm.Namespace);
\r
238 if (_attributeMembers.ContainsKey (key))
\r
239 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.");
\r
240 member.Index = _attributeMembers.Count;
\r
241 _attributeMembers.Add (key, member);
\r
244 else if (member is XmlTypeMapMemberFlatList)
\r
246 RegisterFlatList ((XmlTypeMapMemberFlatList)member);
\r
248 else if (member is XmlTypeMapMemberAnyElement)
\r
250 XmlTypeMapMemberAnyElement mem = (XmlTypeMapMemberAnyElement) member;
\r
251 if (mem.IsDefaultAny) _defaultAnyElement = mem;
\r
252 if (mem.TypeData.IsListType) RegisterFlatList (mem);
\r
254 else if (member is XmlTypeMapMemberAnyAttribute)
\r
256 _defaultAnyAttribute = (XmlTypeMapMemberAnyAttribute) member;
\r
259 else if (member is XmlTypeMapMemberNamespaces)
\r
261 _namespaceDeclarations = (XmlTypeMapMemberNamespaces) member;
\r
265 if (member is XmlTypeMapMemberElement && ((XmlTypeMapMemberElement)member).IsXmlTextCollector)
\r
267 if (_xmlTextCollector != null) throw new InvalidOperationException ("XmlTextAttribute can only be applied once in a class");
\r
268 _xmlTextCollector = member;
\r
271 if (_elementMembers == null) {
\r
272 _elementMembers = new ArrayList();
\r
273 _elements = new Hashtable();
\r
276 member.Index = _elementMembers.Count;
\r
277 _elementMembers.Add (member);
\r
279 ICollection elemsInfo = ((XmlTypeMapMemberElement)member).ElementInfo;
\r
280 foreach (XmlTypeMapElementInfo elem in elemsInfo)
\r
282 string key = BuildKey (elem.ElementName, elem.Namespace);
\r
283 if (_elements.ContainsKey (key))
\r
284 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.");
\r
285 _elements.Add (key, elem);
\r
288 if (member.TypeData.IsListType && member.TypeData.Type != null && !member.TypeData.Type.IsArray) {
\r
289 if (_listMembers == null) _listMembers = new ArrayList ();
\r
290 _listMembers.Add (member);
\r
294 void RegisterFlatList (XmlTypeMapMemberExpandable member)
\r
296 if (_flatLists == null) _flatLists = new ArrayList ();
\r
297 member.FlatArrayIndex = _flatLists.Count;
\r
298 _flatLists.Add (member);
\r
301 public XmlTypeMapMemberAttribute GetAttribute (string name, string ns)
\r
303 if (_attributeMembers == null) return null;
\r
304 return (XmlTypeMapMemberAttribute)_attributeMembers [BuildKey(name,ns)];
\r
307 public XmlTypeMapElementInfo GetElement (string name, string ns)
\r
309 if (_elements == null) return null;
\r
310 return (XmlTypeMapElementInfo)_elements [BuildKey(name,ns)];
\r
313 public XmlTypeMapElementInfo GetElement (int index)
\r
315 if (_elements == null) return null;
\r
317 if (_elementsByIndex == null)
\r
319 _elementsByIndex = new XmlTypeMapElementInfo [_elementMembers.Count];
\r
320 foreach (XmlTypeMapMemberElement mem in _elementMembers)
\r
322 if (mem.ElementInfo.Count != 1)
\r
323 throw new InvalidOperationException ("Read by order only possible for encoded/bare format");
\r
325 _elementsByIndex [mem.Index] = (XmlTypeMapElementInfo) mem.ElementInfo [0];
\r
329 return _elementsByIndex [index];
\r
332 private string BuildKey (string name, string ns)
\r
334 if (_ignoreMemberNamespace) return name;
\r
335 else return name + " / " + ns;
\r
338 public ICollection AllElementInfos
\r
340 get { return _elements.Values; }
\r
344 public bool IgnoreMemberNamespace
\r
346 get { return _ignoreMemberNamespace; }
\r
347 set { _ignoreMemberNamespace = value; }
\r
350 public XmlTypeMapMember FindMember (string name)
\r
352 for (int n=0; n<_allMembers.Count; n++)
\r
353 if (((XmlTypeMapMember)_allMembers[n]).Name == name) return (XmlTypeMapMember)_allMembers[n];
\r
357 public XmlTypeMapMemberAnyElement DefaultAnyElementMember
\r
359 get { return _defaultAnyElement; }
\r
362 public XmlTypeMapMemberAnyAttribute DefaultAnyAttributeMember
\r
364 get { return _defaultAnyAttribute; }
\r
367 public XmlTypeMapMemberNamespaces NamespaceDeclarations
\r
369 get { return _namespaceDeclarations; }
\r
372 public ICollection AttributeMembers
\r
376 if (_attributeMembers == null) return null;
\r
377 if (_attributeMembersArray != null) return _attributeMembersArray;
\r
379 _attributeMembersArray = new XmlTypeMapMemberAttribute[_attributeMembers.Count];
\r
380 foreach (XmlTypeMapMemberAttribute mem in _attributeMembers.Values)
\r
381 _attributeMembersArray [mem.Index] = mem;
\r
382 return _attributeMembersArray;
\r
386 public ICollection ElementMembers
\r
388 get { return _elementMembers; }
\r
391 public ArrayList AllMembers
\r
393 get { return _allMembers; }
\r
396 public ArrayList FlatLists
\r
398 get { return _flatLists; }
\r
401 public ArrayList MembersWithDefault
\r
403 get { return _membersWithDefault; }
\r
406 public ArrayList ListMembers
\r
408 get { return _listMembers; }
\r
411 public XmlTypeMapMember XmlTextCollector
\r
413 get { return _xmlTextCollector; }
\r
416 public XmlTypeMapMember ReturnMember
\r
418 get { return _returnMember; }
\r
421 public XmlQualifiedName SimpleContentBaseType
\r
425 if (!_canBeSimpleType || _elementMembers == null || _elementMembers.Count != 1) return null;
\r
426 XmlTypeMapMemberElement member = (XmlTypeMapMemberElement) _elementMembers[0];
\r
427 if (member.ElementInfo.Count != 1) return null;
\r
428 XmlTypeMapElementInfo einfo = (XmlTypeMapElementInfo) member.ElementInfo[0];
\r
429 if (!einfo.IsTextElement) return null;
\r
430 if (member.TypeData.SchemaType == SchemaTypes.Primitive || member.TypeData.SchemaType == SchemaTypes.Enum)
\r
431 return new XmlQualifiedName (einfo.TypeData.XmlType, einfo.DataTypeNamespace);
\r
436 public void SetCanBeSimpleType (bool can)
\r
438 _canBeSimpleType = can;
\r
441 public bool HasSimpleContent
\r
445 return SimpleContentBaseType != null;
\r
451 // Mapping info for arrays and lists
\r
453 internal class ListMap: ObjectMap
\r
455 XmlTypeMapElementInfoList _itemInfo;
\r
456 bool _gotNestedMapping;
\r
457 XmlTypeMapping _nestedArrayMapping;
\r
458 string _choiceMember;
\r
460 public bool IsMultiArray
\r
464 return (NestedArrayMapping != null);
\r
468 public string ChoiceMember
\r
470 get { return _choiceMember; }
\r
471 set { _choiceMember = value; }
\r
474 public XmlTypeMapping NestedArrayMapping
\r
478 if (_gotNestedMapping) return _nestedArrayMapping;
\r
479 _gotNestedMapping = true;
\r
481 _nestedArrayMapping = ((XmlTypeMapElementInfo)_itemInfo[0]).MappedType;
\r
483 if (_nestedArrayMapping == null) return null;
\r
485 if (_nestedArrayMapping.TypeData.SchemaType != SchemaTypes.Array) {
\r
486 _nestedArrayMapping = null; return null;
\r
489 foreach (XmlTypeMapElementInfo elem in _itemInfo)
\r
490 if (elem.MappedType != _nestedArrayMapping) {
\r
491 _nestedArrayMapping = null;
\r
495 return _nestedArrayMapping;
\r
499 public XmlTypeMapElementInfoList ItemInfo
\r
502 get { return _itemInfo; }
\r
503 set { _itemInfo = value; }
\r
506 public XmlTypeMapElementInfo FindElement (object ob, int index, object memberValue)
\r
508 if (_itemInfo.Count == 1)
\r
509 return (XmlTypeMapElementInfo) _itemInfo[0];
\r
510 else if (_choiceMember != null && index != -1)
\r
512 Array values = (Array) XmlTypeMapMember.GetValue (ob, _choiceMember);
\r
513 if (values == null || index >= values.Length)
\r
514 throw new InvalidOperationException ("Invalid or missing choice enum value in member '" + _choiceMember + "'.");
\r
515 object val = values.GetValue (index);
\r
516 foreach (XmlTypeMapElementInfo elem in _itemInfo)
\r
517 if (elem.ChoiceValue != null && elem.ChoiceValue.Equals (val))
\r
522 if (memberValue == null) return null;
\r
523 Type type = memberValue.GetType();
\r
524 foreach (XmlTypeMapElementInfo elem in _itemInfo)
\r
525 if (elem.TypeData.Type == type) return elem;
\r
530 public XmlTypeMapElementInfo FindElement (string elementName, string ns)
\r
532 foreach (XmlTypeMapElementInfo elem in _itemInfo)
\r
533 if (elem.ElementName == elementName && elem.Namespace == ns) return elem;
\r
537 public XmlTypeMapElementInfo FindTextElement ()
\r
539 foreach (XmlTypeMapElementInfo elem in _itemInfo)
\r
540 if (elem.IsTextElement) return elem;
\r
544 public string GetSchemaArrayName ()
\r
546 XmlTypeMapElementInfo einfo = (XmlTypeMapElementInfo) _itemInfo[0];
\r
547 if (einfo.MappedType != null) return TypeTranslator.GetArrayName (einfo.MappedType.XmlType);
\r
548 else return TypeTranslator.GetArrayName (einfo.TypeData.XmlType);
\r
551 public void GetArrayType (int itemCount, out string localName, out string ns)
\r
554 if (itemCount != -1) arrayDim = "[" + itemCount + "]";
\r
555 else arrayDim = "[]";
\r
557 XmlTypeMapElementInfo info = (XmlTypeMapElementInfo) _itemInfo[0];
\r
558 if (info.TypeData.SchemaType == SchemaTypes.Array)
\r
561 ((ListMap)info.MappedType.ObjectMap).GetArrayType (-1, out nm, out ns);
\r
562 localName = nm + arrayDim;
\r
566 if (info.MappedType != null)
\r
568 localName = info.MappedType.XmlType + arrayDim;
\r
569 ns = info.MappedType.Namespace;
\r
573 localName = info.TypeData.XmlType + arrayDim;
\r
574 ns = info.DataTypeNamespace;
\r
579 public override bool Equals (object other)
\r
581 ListMap lmap = other as ListMap;
\r
582 if (lmap == null) return false;
\r
584 if (_itemInfo.Count != lmap._itemInfo.Count) return false;
\r
585 for (int n=0; n<_itemInfo.Count; n++)
\r
586 if (!_itemInfo[n].Equals (lmap._itemInfo[n])) return false;
\r
590 public override int GetHashCode ()
\r
592 return base.GetHashCode ();
\r
596 internal class EnumMap: ObjectMap
\r
598 readonly EnumMapMember[] _members;
\r
599 readonly bool _isFlags;
\r
600 readonly string[] _enumNames = null;
\r
601 readonly string[] _xmlNames = null;
\r
602 readonly long[] _values = null;
\r
604 public class EnumMapMember
\r
606 readonly string _xmlName;
\r
607 readonly string _enumName;
\r
608 readonly long _value;
\r
609 string _documentation;
\r
611 public EnumMapMember (string xmlName, string enumName)
\r
612 : this (xmlName, enumName, 0)
\r
616 public EnumMapMember (string xmlName, string enumName, long value)
\r
618 _xmlName = xmlName;
\r
619 _enumName = enumName;
\r
623 public string XmlName
\r
625 get { return _xmlName; }
\r
628 public string EnumName
\r
630 get { return _enumName; }
\r
635 get { return _value; }
\r
638 public string Documentation
\r
640 get { return _documentation; }
\r
641 set { _documentation = value; }
\r
645 public EnumMap (EnumMapMember[] members, bool isFlags)
\r
647 _members = members;
\r
648 _isFlags = isFlags;
\r
650 _enumNames = new string[_members.Length];
\r
651 _xmlNames = new string[_members.Length];
\r
652 _values = new long[_members.Length];
\r
654 for (int i = 0; i < _members.Length; i++) {
\r
655 EnumMapMember mem = _members[i];
\r
656 _enumNames[i] = mem.EnumName;
\r
657 _xmlNames[i] = mem.XmlName;
\r
658 _values[i] = mem.Value;
\r
662 public bool IsFlags
\r
664 get { return _isFlags; }
\r
667 public EnumMapMember[] Members
\r
669 get { return _members; }
\r
672 public string[] EnumNames
\r
679 public string[] XmlNames
\r
686 public long[] Values
\r
693 public string GetXmlName (string typeName, object enumValue)
\r
695 if (enumValue is string) {
\r
696 throw new InvalidCastException ();
\r
702 value = ((IConvertible) enumValue).ToInt64 (CultureInfo.CurrentCulture);
\r
703 } catch (FormatException) {
\r
704 throw new InvalidCastException ();
\r
707 for (int i = 0; i < Values.Length; i++) {
\r
708 if (Values[i] == value)
\r
709 return XmlNames[i];
\r
712 if (IsFlags && value == 0)
\r
713 return string.Empty;
\r
715 string xmlName = string.Empty;
\r
718 xmlName = XmlCustomFormatter.FromEnum (value, XmlNames, Values, typeName);
\r
720 xmlName = XmlCustomFormatter.FromEnum (value, XmlNames, Values);
\r
724 if (xmlName.Length == 0) {
\r
726 throw new InvalidOperationException (string.Format(CultureInfo.CurrentCulture,
\r
727 "'{0}' is not a valid value for {1}.", value, typeName));
\r
729 return value.ToString (CultureInfo.InvariantCulture);
\r
735 public string GetEnumName (string typeName, string xmlName)
\r
738 xmlName = xmlName.Trim ();
\r
739 if (xmlName.Length == 0)
\r
742 System.Text.StringBuilder sb = new System.Text.StringBuilder ();
\r
743 string[] enumNames = xmlName.Split (null);
\r
744 foreach (string name in enumNames) {
\r
745 if (name == string.Empty) continue;
\r
746 string foundEnumValue = null;
\r
747 for (int i = 0; i < XmlNames.Length; i++)
\r
748 if (XmlNames[i] == name) {
\r
749 foundEnumValue = EnumNames[i];
\r
753 if (foundEnumValue != null) {
\r
756 sb.Append (foundEnumValue);
\r
758 throw new InvalidOperationException (string.Format (CultureInfo.CurrentCulture,
\r
759 "'{0}' is not a valid value for {1}.", name, typeName));
\r
762 return sb.ToString ();
\r
765 foreach (EnumMapMember mem in _members)
\r
766 if (mem.XmlName == xmlName) return mem.EnumName;
\r