e1062a72998d94beab033a4daf19f218966b6808
[mono.git] / mcs / class / System.XML / System.Xml.Serialization / XmlSerializationWriterInterpreter.cs
1 //
2 // XmlSerializationWriterInterpreter.cs: 
3 //
4 // Author:
5 //   Lluis Sanchez Gual (lluis@ximian.com)
6 //
7 // (C) 2002, 2003 Ximian, Inc.  http://www.ximian.com
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.Text;
33 using System.Collections;
34 using System.Reflection;
35 using System.Xml.Schema;
36
37 namespace System.Xml.Serialization
38 {
39         internal class XmlSerializationWriterInterpreter: XmlSerializationWriter
40         {
41                 XmlMapping _typeMap;
42                 SerializationFormat _format;
43                 const string xmlNamespace = "http://www.w3.org/2000/xmlns/";
44
45                 public XmlSerializationWriterInterpreter (XmlMapping typeMap)
46                 {
47                         _typeMap = typeMap;
48                         _format = typeMap.Format;
49                 }
50
51                 protected override void InitCallbacks ()
52                 {
53                         ArrayList maps = _typeMap.RelatedMaps;
54                         if (maps != null)
55                         {
56                                 foreach (XmlTypeMapping map in maps)  {
57                                         CallbackInfo info = new CallbackInfo (this, map);
58                                         if (map.TypeData.SchemaType == SchemaTypes.Enum) AddWriteCallback(map.TypeData.Type, map.XmlType, map.Namespace, new XmlSerializationWriteCallback (info.WriteEnum));
59                                         else AddWriteCallback(map.TypeData.Type, map.XmlType, map.Namespace, new XmlSerializationWriteCallback (info.WriteObject));
60                                 }
61                         }
62                 }
63
64                 public void WriteRoot (object ob)
65                 {
66                         WriteStartDocument ();
67
68                         if (_typeMap is XmlTypeMapping)
69                         {
70                                 XmlTypeMapping mp = (XmlTypeMapping) _typeMap;
71                                 if (mp.TypeData.SchemaType == SchemaTypes.Class || mp.TypeData.SchemaType == SchemaTypes.Array) 
72                                         TopLevelElement ();
73
74                                 if (_format == SerializationFormat.Literal)
75                                         WriteObject (mp, ob, mp.ElementName, mp.Namespace, true, false, true);
76                                 else
77                                         WritePotentiallyReferencingElement (mp.ElementName, mp.Namespace, ob, mp.TypeData.Type, true, false);
78                         }
79                         else if (ob is object[])
80                                 WriteMessage ((XmlMembersMapping)_typeMap, (object[]) ob);
81                         else
82                                 throw CreateUnknownTypeException (ob);
83
84                         WriteReferencedElements ();
85                 }
86                 
87                 protected XmlTypeMapping GetTypeMap (Type type)
88                 {
89                         ArrayList maps = _typeMap.RelatedMaps;
90                         if (maps != null)
91                         {
92                                 foreach (XmlTypeMapping map in maps)
93                                         if (map.TypeData.Type == type) return map;
94                         }
95                         throw new InvalidOperationException ("Type " + type + " not mapped");
96                 }
97
98                 protected virtual void WriteObject (XmlTypeMapping typeMap, object ob, string element, string namesp, bool isNullable, bool needType, bool writeWrappingElem)
99                 {
100                         if (ob == null)
101                         {
102                                 if (isNullable) 
103                                 {
104                                         if (_format == SerializationFormat.Literal) WriteNullTagLiteral(element, namesp);
105                                         else WriteNullTagEncoded (element, namesp);
106                                 }
107                                 return;
108                         }
109
110                         if (ob is XmlNode)
111                         {
112                                 if (_format == SerializationFormat.Literal) WriteElementLiteral((XmlNode)ob, "", "", true, false);
113                                 else WriteElementEncoded((XmlNode)ob, "", "", true, false);
114                                 return;
115                         }
116
117                         if (typeMap.TypeData.SchemaType == SchemaTypes.XmlSerializable)
118                         {
119                                 WriteSerializable ((IXmlSerializable)ob, element, namesp, isNullable);
120                                 return;
121                         }
122
123                         XmlTypeMapping map = typeMap.GetRealTypeMap (ob.GetType().FullName);
124
125                         if (map == null) 
126                         {
127                                 WriteTypedPrimitive (element, namesp, ob, true);
128                                 return;
129                         }
130
131                         if (writeWrappingElem)
132                         {
133                                 if (map != typeMap || _format == SerializationFormat.Encoded) needType = true;
134                                 WriteStartElement (element, namesp, ob);
135                         }
136
137                         if (needType) 
138                                 WriteXsiType(map.XmlType, map.XmlTypeNamespace);
139
140                         switch (map.TypeData.SchemaType)
141                         {
142                                 case SchemaTypes.Class: WriteObjectElement (map, ob, element, namesp); break;
143                                 case SchemaTypes.Array: WriteListElement (map, ob, element, namesp); break;
144                                 case SchemaTypes.Primitive: WritePrimitiveElement (map, ob, element, namesp); break;
145                                 case SchemaTypes.Enum: WriteEnumElement (map, ob, element, namesp); break;
146                         }
147
148                         if (writeWrappingElem)
149                                 WriteEndElement (ob);
150                 }
151
152                 protected virtual void WriteMessage (XmlMembersMapping membersMap, object[] parameters)
153                 {
154                         if (membersMap.HasWrapperElement) {
155                                 TopLevelElement ();
156                                 WriteStartElement(membersMap.ElementName, membersMap.Namespace, (_format == SerializationFormat.Encoded));
157
158                                 if (Writer.LookupPrefix (XmlSchema.Namespace) == null)
159                                         WriteAttribute ("xmlns","xsd",XmlSchema.Namespace,XmlSchema.Namespace);
160         
161                                 if (Writer.LookupPrefix (XmlSchema.InstanceNamespace) == null)
162                                         WriteAttribute ("xmlns","xsi",XmlSchema.InstanceNamespace,XmlSchema.InstanceNamespace);
163                         }
164                         
165                         WriteMembers ((ClassMap)membersMap.ObjectMap, parameters, true);
166
167                         if (membersMap.HasWrapperElement)
168                                 WriteEndElement();
169                 }
170
171                 protected virtual void WriteObjectElement (XmlTypeMapping typeMap, object ob, string element, string namesp)
172                 {
173                         ClassMap map = (ClassMap)typeMap.ObjectMap;
174                         if (map.NamespaceDeclarations != null)
175                                 WriteNamespaceDeclarations ((XmlSerializerNamespaces) map.NamespaceDeclarations.GetValue (ob));
176                         
177                         WriteObjectElementAttributes (typeMap, ob);
178                         WriteObjectElementElements (typeMap, ob);
179                 }
180                 
181                 protected virtual void WriteObjectElementAttributes (XmlTypeMapping typeMap, object ob)
182                 {
183                         ClassMap map = (ClassMap)typeMap.ObjectMap;
184                         WriteAttributeMembers (map, ob, false);
185                 }
186
187                 protected virtual void WriteObjectElementElements (XmlTypeMapping typeMap, object ob)
188                 {
189                         ClassMap map = (ClassMap)typeMap.ObjectMap;
190                         WriteElementMembers (map, ob, false);
191                 }
192
193                 void WriteMembers (ClassMap map, object ob, bool isValueList)
194                 {
195                         WriteAttributeMembers (map, ob, isValueList);
196                         WriteElementMembers (map, ob, isValueList);
197                 }
198                 
199                 void WriteAttributeMembers (ClassMap map, object ob, bool isValueList)
200                 {
201                         // Write attributes
202
203                         XmlTypeMapMember anyAttrMember = map.DefaultAnyAttributeMember;
204                         if (anyAttrMember != null && MemberHasValue (anyAttrMember, ob, isValueList))
205                         {
206                                 ICollection extraAtts = (ICollection) GetMemberValue (anyAttrMember, ob, isValueList);
207                                 if (extraAtts != null) 
208                                 {
209                                         foreach (XmlAttribute attr in extraAtts)
210                                                 if (attr.NamespaceURI != xmlNamespace)
211                                                         WriteXmlAttribute (attr, ob);
212                                 }
213                         }
214
215                         ICollection attributes = map.AttributeMembers;
216                         if (attributes != null)
217                         {
218                                 foreach (XmlTypeMapMemberAttribute attr in attributes) {
219                                         if (MemberHasValue (attr, ob, isValueList))
220                                                 WriteAttribute (attr.AttributeName, attr.Namespace, GetStringValue (attr.MappedType, attr.TypeData, GetMemberValue (attr, ob, isValueList)));
221                                 }
222                         }
223                 }
224
225                 void WriteElementMembers (ClassMap map, object ob, bool isValueList)
226                 {
227                         ICollection members = map.ElementMembers;
228                         if (members != null)
229                         {
230                                 foreach (XmlTypeMapMemberElement member in members)
231                                 {
232                                         if (!MemberHasValue (member, ob, isValueList)) continue;
233                                         object memberValue = GetMemberValue (member, ob, isValueList);
234                                         Type memType = member.GetType();
235
236                                         if (memType == typeof(XmlTypeMapMemberList))
237                                         {
238                                                 WriteMemberElement ((XmlTypeMapElementInfo) member.ElementInfo[0], memberValue);
239                                         }
240                                         else if (memType == typeof(XmlTypeMapMemberFlatList))
241                                         {
242                                                 if (memberValue != null)
243                                                         WriteListContent (ob, member.TypeData, ((XmlTypeMapMemberFlatList)member).ListMap, memberValue, null);
244                                         }
245                                         else if (memType == typeof(XmlTypeMapMemberAnyElement))
246                                         {
247                                                 if (memberValue != null)
248                                                         WriteAnyElementContent ((XmlTypeMapMemberAnyElement)member, memberValue);
249                                         }
250                                         else if (memType == typeof(XmlTypeMapMemberAnyAttribute))
251                                         {
252                                                 // Ignore
253                                         }
254                                         else if (memType == typeof(XmlTypeMapMemberElement))
255                                         {
256                                                 XmlTypeMapElementInfo elem = member.FindElement (ob, memberValue);
257                                                 WriteMemberElement (elem, memberValue);
258                                         }
259                                         else
260                                                 throw new InvalidOperationException ("Unknown member type");
261                                 }
262                         }
263                 }
264
265                 object GetMemberValue (XmlTypeMapMember member, object ob, bool isValueList)
266                 {
267                         if (isValueList) return ((object[])ob)[member.Index];
268                         else return member.GetValue (ob);
269                 }
270
271                 bool MemberHasValue (XmlTypeMapMember member, object ob, bool isValueList)
272                 {
273                         if (isValueList) {
274                                 return member.Index < ((object[])ob).Length;
275                         }
276                         else if (member.DefaultValue != System.DBNull.Value) {
277                                 object val = GetMemberValue (member, ob, isValueList);
278                                 if (val == null && member.DefaultValue == null) return false;
279                                 if (val != null && val.GetType().IsEnum)
280                                 {
281                                         if (val.Equals (member.DefaultValue)) return false;
282                                         Type t = Enum.GetUnderlyingType(val.GetType());
283                                         val = Convert.ChangeType (val, t);
284                                 }
285                                 if (val != null && val.Equals (member.DefaultValue)) return false;
286                         }
287                         else if (member.IsOptionalValueType)
288                                 return member.GetValueSpecified (ob);
289
290                         return true;
291                 }
292
293                 void WriteMemberElement (XmlTypeMapElementInfo elem, object memberValue)
294                 {
295                         switch (elem.TypeData.SchemaType)
296                         {
297                                 case SchemaTypes.XmlNode:
298                                         string elemName = elem.WrappedElement ? elem.ElementName : "";
299                                         if (_format == SerializationFormat.Literal) WriteElementLiteral(((XmlNode)memberValue), elemName, elem.Namespace, elem.IsNullable, false);
300                                         else WriteElementEncoded(((XmlNode)memberValue), elemName, elem.Namespace, elem.IsNullable, false);
301                                         break;
302
303                                 case SchemaTypes.Enum:
304                                 case SchemaTypes.Primitive:
305                                         if (_format == SerializationFormat.Literal) 
306                                                 WritePrimitiveValueLiteral (memberValue, elem.ElementName, elem.Namespace, elem.MappedType, elem.TypeData, elem.WrappedElement, elem.IsNullable);
307                                         else 
308                                                 WritePrimitiveValueEncoded (memberValue, elem.ElementName, elem.Namespace, new XmlQualifiedName (elem.DataTypeName, elem.DataTypeNamespace), elem.MappedType, elem.TypeData, elem.WrappedElement, elem.IsNullable);
309                                         break;
310
311                                 case SchemaTypes.Array:
312                                         if (memberValue == null) {
313                                                 if (!elem.IsNullable) return;
314                                                 if (_format == SerializationFormat.Literal) WriteNullTagLiteral (elem.ElementName, elem.Namespace);
315                                                 else WriteNullTagEncoded (elem.ElementName, elem.Namespace);
316                                         }
317                                         else if (elem.MappedType.MultiReferenceType) 
318                                                 WriteReferencingElement (elem.ElementName, elem.Namespace, memberValue, elem.IsNullable);
319                                         else {
320                                                 WriteStartElement(elem.ElementName, elem.Namespace, memberValue);
321                                                 WriteListContent (null, elem.TypeData, (ListMap) elem.MappedType.ObjectMap, memberValue, null);
322                                                 WriteEndElement (memberValue);
323                                         }
324                                         break;
325
326                                 case SchemaTypes.Class:
327                                         if (elem.MappedType.MultiReferenceType) {
328                                                 if (elem.MappedType.TypeData.Type == typeof(object))
329                                                         WritePotentiallyReferencingElement (elem.ElementName, elem.Namespace, memberValue, null, false, elem.IsNullable);
330                                                 else
331                                                         WriteReferencingElement (elem.ElementName, elem.Namespace, memberValue, elem.IsNullable);
332                                         }
333                                         else WriteObject (elem.MappedType, memberValue, elem.ElementName, elem.Namespace, elem.IsNullable, false, true);
334                                         break;
335
336                                 case SchemaTypes.XmlSerializable:
337                                         WriteSerializable ((IXmlSerializable) memberValue, elem.ElementName, elem.Namespace, elem.IsNullable);
338                                         break;
339
340                                 default:
341                                         throw new NotSupportedException ("Invalid value type");
342                         }
343                 }
344
345                 void WritePrimitiveValueLiteral (object memberValue, string name, string ns, XmlTypeMapping mappedType, TypeData typeData, bool wrapped, bool isNullable)
346                 {
347                         if (!wrapped) {
348                                 WriteValue (GetStringValue (mappedType, typeData, memberValue));
349                         }
350                         else if (isNullable) {
351                                 if (typeData.Type == typeof(XmlQualifiedName)) WriteNullableQualifiedNameLiteral (name, ns, (XmlQualifiedName)memberValue);
352                                 else WriteNullableStringLiteral (name, ns, GetStringValue (mappedType, typeData, memberValue));
353                         }
354                         else {
355                                 if (typeData.Type == typeof(XmlQualifiedName)) WriteElementQualifiedName (name, ns, (XmlQualifiedName)memberValue);
356                                 else WriteElementString (name, ns, GetStringValue (mappedType, typeData, memberValue));
357                         }
358                 }
359
360                 void WritePrimitiveValueEncoded (object memberValue, string name, string ns, XmlQualifiedName xsiType, XmlTypeMapping mappedType, TypeData typeData, bool wrapped, bool isNullable)
361                 {
362                         if (!wrapped) {
363                                 WriteValue (GetStringValue (mappedType, typeData, memberValue));
364                         }
365                         else if (isNullable) {
366                                 if (typeData.Type == typeof(XmlQualifiedName)) WriteNullableQualifiedNameEncoded (name, ns, (XmlQualifiedName)memberValue, xsiType);
367                                 else WriteNullableStringEncoded (name, ns, GetStringValue (mappedType, typeData, memberValue), xsiType);
368                         }
369                         else {
370                                 if (typeData.Type == typeof(XmlQualifiedName)) WriteElementQualifiedName (name, ns, (XmlQualifiedName)memberValue, xsiType);
371                                 else WriteElementString (name, ns, GetStringValue (mappedType, typeData, memberValue), xsiType);
372                         }
373                 }
374
375                 protected virtual void WriteListElement (XmlTypeMapping typeMap, object ob, string element, string namesp)
376                 {
377                         if (_format == SerializationFormat.Encoded)
378                         {
379                                 string n, ns;
380                                 int itemCount = GetListCount (typeMap.TypeData, ob);
381                                 ((ListMap) typeMap.ObjectMap).GetArrayType (itemCount, out n, out ns);
382                                 string arrayType = (ns != string.Empty) ? FromXmlQualifiedName (new XmlQualifiedName(n,ns)) : n;
383                                 WriteAttribute ("arrayType", XmlSerializer.EncodingNamespace, arrayType);
384                         }
385                         WriteListContent (null, typeMap.TypeData, (ListMap) typeMap.ObjectMap, ob, null);
386                 }
387
388                 void WriteListContent (object container, TypeData listType, ListMap map, object ob, StringBuilder targetString)
389                 {
390                         if (listType.Type.IsArray)
391                         {
392                                 Array array = (Array)ob;
393                                 for (int n=0; n<array.Length; n++)
394                                 {
395                                         object item = array.GetValue (n);
396                                         XmlTypeMapElementInfo info = map.FindElement (container, n, item);
397                                         if (info != null && targetString == null) WriteMemberElement (info, item);
398                                         else if (info != null && targetString != null) targetString.Append (GetStringValue (info.MappedType, info.TypeData, item)).Append (" ");
399                                         else if (item != null) throw CreateUnknownTypeException (item);
400                                 }
401                         }
402                         else if (ob is ICollection)
403                         {
404                                 int count = (int) ob.GetType().GetProperty ("Count").GetValue(ob,null);
405                                 PropertyInfo itemProp = TypeData.GetIndexerProperty (listType.Type);
406                                 object[] index = new object[1];
407                                 for (int n=0; n<count; n++)
408                                 {
409                                         index[0] = n;
410                                         object item = itemProp.GetValue (ob, index);
411                                         XmlTypeMapElementInfo info = map.FindElement (container, n, item);
412                                         if (info != null && targetString == null) WriteMemberElement (info, item);
413                                         else if (info != null && targetString != null) targetString.Append (GetStringValue (info.MappedType, info.TypeData, item)).Append (" ");
414                                         else if (item != null) throw CreateUnknownTypeException (item);
415                                 }
416                         }
417                         else if (ob is IEnumerable)
418                         {
419                                 IEnumerable e = (IEnumerable)ob;
420                                 foreach (object item in e)
421                                 {
422                                         XmlTypeMapElementInfo info = map.FindElement (container, -1, item);
423                                         if (info != null && targetString == null) WriteMemberElement (info, item);
424                                         else if (info != null && targetString != null) targetString.Append (GetStringValue (info.MappedType, info.TypeData, item)).Append (" ");
425                                         else if (item != null) throw CreateUnknownTypeException (item);
426                                 }
427                         }
428                         else
429                                 throw new Exception ("Unsupported collection type");
430                 }
431
432                 int GetListCount (TypeData listType, object ob)
433                 {
434                         if (listType.Type.IsArray)
435                                 return ((Array)ob).Length;
436                         else
437                                 return (int) listType.Type.GetProperty ("Count").GetValue(ob,null);
438                 }
439
440                 void WriteAnyElementContent (XmlTypeMapMemberAnyElement member, object memberValue)
441                 {
442                         if (member.TypeData.Type == typeof (XmlElement)) {
443                                 memberValue = new object[] { memberValue };
444                         }
445
446                         Array elems = (Array) memberValue;
447                         foreach (XmlNode elem in elems)
448                         {
449                                 if (elem is XmlElement) 
450                                 {
451                                         if (member.IsElementDefined (elem.Name, elem.NamespaceURI))
452                                         {
453                                                 if (_format == SerializationFormat.Literal) WriteElementLiteral (elem, "", "", false, true);
454                                                 else WriteElementEncoded (elem, "", "", false, true);
455                                         }
456                                         else
457                                                 throw CreateUnknownAnyElementException (elem.Name, elem.NamespaceURI);
458                                 }
459                                 else
460                                         elem.WriteTo (Writer);
461                         }
462                 }
463
464                 protected virtual void WritePrimitiveElement (XmlTypeMapping typeMap, object ob, string element, string namesp)
465                 {
466                         Writer.WriteString (GetStringValue (typeMap, typeMap.TypeData, ob));
467                 }
468
469                 protected virtual void WriteEnumElement (XmlTypeMapping typeMap, object ob, string element, string namesp)
470                 {
471                         Writer.WriteString (GetEnumXmlValue (typeMap, ob));
472                 }
473
474                 string GetStringValue (XmlTypeMapping typeMap, TypeData type, object value)
475                 {
476                         if (type.SchemaType == SchemaTypes.Array) {
477                                 if (value == null) return null;
478                                 StringBuilder sb = new StringBuilder ();
479                                 WriteListContent (null, typeMap.TypeData, (ListMap)typeMap.ObjectMap, value, sb);
480                                 return sb.ToString ().Trim ();
481                         }
482                         else if (type.SchemaType == SchemaTypes.Enum)
483                                 return GetEnumXmlValue (typeMap, value);
484                         else if (type.Type == typeof (XmlQualifiedName))
485                                 return FromXmlQualifiedName ((XmlQualifiedName)value);
486                         else if (value == null)
487                                 return null;
488                         else
489                                 return XmlCustomFormatter.ToXmlString (type, value);
490                 }
491
492                 string GetEnumXmlValue (XmlTypeMapping typeMap, object ob)
493                 {
494                         EnumMap map = (EnumMap)typeMap.ObjectMap;
495                         return map.GetXmlName (typeMap.TypeFullName, ob);
496                 }
497
498                 class CallbackInfo
499                 {
500                         XmlSerializationWriterInterpreter _swi;
501                         XmlTypeMapping _typeMap;
502
503                         public CallbackInfo (XmlSerializationWriterInterpreter swi, XmlTypeMapping typeMap)
504                         {
505                                 _swi = swi;
506                                 _typeMap = typeMap;
507                         }
508
509                         internal void WriteObject (object ob)
510                         {
511                                 _swi.WriteObject (_typeMap, ob, _typeMap.ElementName, _typeMap.Namespace, false, false, false);
512                         }
513
514                         internal void WriteEnum (object ob)
515                         {
516                                 _swi.WriteObject (_typeMap, ob, _typeMap.ElementName, _typeMap.Namespace, false, true, false);
517                         }
518                 }
519
520         }
521 }