refactoring for performance
[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 (ob.GetType ().IsArray && typeof (XmlNode).IsAssignableFrom (ob.GetType ().GetElementType ())) {
118                                 Writer.WriteStartElement (element, namesp);
119                                 foreach (XmlNode node in (IEnumerable) ob)
120                                         node.WriteTo (Writer);
121                                 Writer.WriteEndElement ();
122                                 return;
123                         }
124
125                         if (typeMap.TypeData.SchemaType == SchemaTypes.XmlSerializable)
126                         {
127                                 WriteSerializable ((IXmlSerializable)ob, element, namesp, isNullable);
128                                 return;
129                         }
130
131                         XmlTypeMapping map = typeMap.GetRealTypeMap (ob.GetType());
132
133                         if (map == null) 
134                         {
135                                 WriteTypedPrimitive (element, namesp, ob, true);
136                                 return;
137                         }
138
139                         if (writeWrappingElem)
140                         {
141                                 if (map != typeMap || _format == SerializationFormat.Encoded) needType = true;
142                                 WriteStartElement (element, namesp, ob);
143                         }
144
145                         if (needType) 
146                                 WriteXsiType(map.XmlType, map.XmlTypeNamespace);
147
148                         switch (map.TypeData.SchemaType)
149                         {
150                                 case SchemaTypes.Class: WriteObjectElement (map, ob, element, namesp); break;
151                                 case SchemaTypes.Array: WriteListElement (map, ob, element, namesp); break;
152                                 case SchemaTypes.Primitive: WritePrimitiveElement (map, ob, element, namesp); break;
153                                 case SchemaTypes.Enum: WriteEnumElement (map, ob, element, namesp); break;
154                         }
155
156                         if (writeWrappingElem)
157                                 WriteEndElement (ob);
158                 }
159
160                 protected virtual void WriteMessage (XmlMembersMapping membersMap, object[] parameters)
161                 {
162                         if (membersMap.HasWrapperElement) {
163                                 TopLevelElement ();
164                                 WriteStartElement(membersMap.ElementName, membersMap.Namespace, (_format == SerializationFormat.Encoded));
165
166                                 if (Writer.LookupPrefix (XmlSchema.Namespace) == null)
167                                         WriteAttribute ("xmlns","xsd",XmlSchema.Namespace,XmlSchema.Namespace);
168         
169                                 if (Writer.LookupPrefix (XmlSchema.InstanceNamespace) == null)
170                                         WriteAttribute ("xmlns","xsi",XmlSchema.InstanceNamespace,XmlSchema.InstanceNamespace);
171                         }
172                         
173                         WriteMembers ((ClassMap)membersMap.ObjectMap, parameters, true);
174
175                         if (membersMap.HasWrapperElement)
176                                 WriteEndElement();
177                 }
178
179                 protected virtual void WriteObjectElement (XmlTypeMapping typeMap, object ob, string element, string namesp)
180                 {
181                         ClassMap map = (ClassMap)typeMap.ObjectMap;
182                         if (map.NamespaceDeclarations != null)
183                                 WriteNamespaceDeclarations ((XmlSerializerNamespaces) map.NamespaceDeclarations.GetValue (ob));
184                         
185                         WriteObjectElementAttributes (typeMap, ob);
186                         WriteObjectElementElements (typeMap, ob);
187                 }
188                 
189                 protected virtual void WriteObjectElementAttributes (XmlTypeMapping typeMap, object ob)
190                 {
191                         ClassMap map = (ClassMap)typeMap.ObjectMap;
192                         WriteAttributeMembers (map, ob, false);
193                 }
194
195                 protected virtual void WriteObjectElementElements (XmlTypeMapping typeMap, object ob)
196                 {
197                         ClassMap map = (ClassMap)typeMap.ObjectMap;
198                         WriteElementMembers (map, ob, false);
199                 }
200
201                 void WriteMembers (ClassMap map, object ob, bool isValueList)
202                 {
203                         WriteAttributeMembers (map, ob, isValueList);
204                         WriteElementMembers (map, ob, isValueList);
205                 }
206                 
207                 void WriteAttributeMembers (ClassMap map, object ob, bool isValueList)
208                 {
209                         // Write attributes
210
211                         XmlTypeMapMember anyAttrMember = map.DefaultAnyAttributeMember;
212                         if (anyAttrMember != null && MemberHasValue (anyAttrMember, ob, isValueList))
213                         {
214                                 ICollection extraAtts = (ICollection) GetMemberValue (anyAttrMember, ob, isValueList);
215                                 if (extraAtts != null) 
216                                 {
217                                         foreach (XmlAttribute attr in extraAtts)
218                                                 if (attr.NamespaceURI != xmlNamespace)
219                                                         WriteXmlAttribute (attr, ob);
220                                 }
221                         }
222
223                         ICollection attributes = map.AttributeMembers;
224                         if (attributes != null)
225                         {
226                                 foreach (XmlTypeMapMemberAttribute attr in attributes) {
227                                         if (MemberHasValue (attr, ob, isValueList))
228                                                 WriteAttribute (attr.AttributeName, attr.Namespace, GetStringValue (attr.MappedType, attr.TypeData, GetMemberValue (attr, ob, isValueList)));
229                                 }
230                         }
231                 }
232
233                 void WriteElementMembers (ClassMap map, object ob, bool isValueList)
234                 {
235                         ICollection members = map.ElementMembers;
236                         if (members != null)
237                         {
238                                 foreach (XmlTypeMapMemberElement member in members)
239                                 {
240                                         if (!MemberHasValue (member, ob, isValueList)) continue;
241                                         object memberValue = GetMemberValue (member, ob, isValueList);
242                                         Type memType = member.GetType();
243
244                                         if (memType == typeof(XmlTypeMapMemberList))
245                                         {
246                                                 WriteMemberElement ((XmlTypeMapElementInfo) member.ElementInfo[0], memberValue);
247                                         }
248                                         else if (memType == typeof(XmlTypeMapMemberFlatList))
249                                         {
250                                                 if (memberValue != null)
251                                                         WriteListContent (ob, member.TypeData, ((XmlTypeMapMemberFlatList)member).ListMap, memberValue, null);
252                                         }
253                                         else if (memType == typeof(XmlTypeMapMemberAnyElement))
254                                         {
255                                                 if (memberValue != null)
256                                                         WriteAnyElementContent ((XmlTypeMapMemberAnyElement)member, memberValue);
257                                         }
258                                         else if (memType == typeof(XmlTypeMapMemberAnyAttribute))
259                                         {
260                                                 // Ignore
261                                         }
262                                         else if (memType == typeof(XmlTypeMapMemberElement))
263                                         {
264                                                 XmlTypeMapElementInfo elem = member.FindElement (ob, memberValue);
265                                                 WriteMemberElement (elem, memberValue);
266                                         }
267                                         else
268                                                 throw new InvalidOperationException ("Unknown member type");
269                                 }
270                         }
271                 }
272
273                 object GetMemberValue (XmlTypeMapMember member, object ob, bool isValueList)
274                 {
275                         if (isValueList) return ((object[])ob)[member.GlobalIndex];
276                         else return member.GetValue (ob);
277                 }
278
279                 bool MemberHasValue (XmlTypeMapMember member, object ob, bool isValueList)
280                 {
281                         if (isValueList) {
282                                 return member.GlobalIndex < ((object[])ob).Length;
283                         }
284                         else if (member.DefaultValue != System.DBNull.Value) {
285                                 object val = GetMemberValue (member, ob, isValueList);
286                                 if (val == null && member.DefaultValue == null) return false;
287                                 if (val != null && val.GetType().IsEnum)
288                                 {
289                                         if (val.Equals (member.DefaultValue)) return false;
290                                         Type t = Enum.GetUnderlyingType(val.GetType());
291                                         val = Convert.ChangeType (val, t);
292                                 }
293                                 if (val != null && val.Equals (member.DefaultValue)) return false;
294                         }
295                         else if (member.IsOptionalValueType)
296                                 return member.GetValueSpecified (ob);
297
298                         return true;
299                 }
300
301                 void WriteMemberElement (XmlTypeMapElementInfo elem, object memberValue)
302                 {
303                         switch (elem.TypeData.SchemaType)
304                         {
305                                 case SchemaTypes.XmlNode:
306                                         string elemName = elem.WrappedElement ? elem.ElementName : "";
307                                         if (_format == SerializationFormat.Literal) WriteElementLiteral(((XmlNode)memberValue), elemName, elem.Namespace, elem.IsNullable, false);
308                                         else WriteElementEncoded(((XmlNode)memberValue), elemName, elem.Namespace, elem.IsNullable, false);
309                                         break;
310
311                                 case SchemaTypes.Enum:
312                                 case SchemaTypes.Primitive:
313                                         if (_format == SerializationFormat.Literal) 
314                                                 WritePrimitiveValueLiteral (memberValue, elem.ElementName, elem.Namespace, elem.MappedType, elem.TypeData, elem.WrappedElement, elem.IsNullable);
315                                         else 
316                                                 WritePrimitiveValueEncoded (memberValue, elem.ElementName, elem.Namespace, new XmlQualifiedName (elem.DataTypeName, elem.DataTypeNamespace), elem.MappedType, elem.TypeData, elem.WrappedElement, elem.IsNullable);
317                                         break;
318
319                                 case SchemaTypes.Array:
320                                         if (memberValue == null) {
321                                                 if (!elem.IsNullable) return;
322                                                 if (_format == SerializationFormat.Literal) WriteNullTagLiteral (elem.ElementName, elem.Namespace);
323                                                 else WriteNullTagEncoded (elem.ElementName, elem.Namespace);
324                                         }
325                                         else if (elem.MappedType.MultiReferenceType) 
326                                                 WriteReferencingElement (elem.ElementName, elem.Namespace, memberValue, elem.IsNullable);
327                                         else {
328                                                 WriteStartElement(elem.ElementName, elem.Namespace, memberValue);
329                                                 WriteListContent (null, elem.TypeData, (ListMap) elem.MappedType.ObjectMap, memberValue, null);
330                                                 WriteEndElement (memberValue);
331                                         }
332                                         break;
333
334                                 case SchemaTypes.Class:
335                                         if (elem.MappedType.MultiReferenceType) {
336                                                 if (elem.MappedType.TypeData.Type == typeof(object))
337                                                         WritePotentiallyReferencingElement (elem.ElementName, elem.Namespace, memberValue, null, false, elem.IsNullable);
338                                                 else
339                                                         WriteReferencingElement (elem.ElementName, elem.Namespace, memberValue, elem.IsNullable);
340                                         }
341                                         else WriteObject (elem.MappedType, memberValue, elem.ElementName, elem.Namespace, elem.IsNullable, false, true);
342                                         break;
343
344                                 case SchemaTypes.XmlSerializable:
345                                         WriteSerializable ((IXmlSerializable) memberValue, elem.ElementName, elem.Namespace, elem.IsNullable);
346                                         break;
347
348                                 default:
349                                         throw new NotSupportedException ("Invalid value type");
350                         }
351                 }
352
353                 void WritePrimitiveValueLiteral (object memberValue, string name, string ns, XmlTypeMapping mappedType, TypeData typeData, bool wrapped, bool isNullable)
354                 {
355                         if (!wrapped) {
356                                 WriteValue (GetStringValue (mappedType, typeData, memberValue));
357                         }
358                         else if (isNullable) {
359                                 if (typeData.Type == typeof(XmlQualifiedName)) WriteNullableQualifiedNameLiteral (name, ns, (XmlQualifiedName)memberValue);
360                                 else WriteNullableStringLiteral (name, ns, GetStringValue (mappedType, typeData, memberValue));
361                         }
362                         else {
363                                 if (typeData.Type == typeof(XmlQualifiedName)) WriteElementQualifiedName (name, ns, (XmlQualifiedName)memberValue);
364                                 else WriteElementString (name, ns, GetStringValue (mappedType, typeData, memberValue));
365                         }
366                 }
367
368                 void WritePrimitiveValueEncoded (object memberValue, string name, string ns, XmlQualifiedName xsiType, XmlTypeMapping mappedType, TypeData typeData, bool wrapped, bool isNullable)
369                 {
370                         if (!wrapped) {
371                                 WriteValue (GetStringValue (mappedType, typeData, memberValue));
372                         }
373                         else if (isNullable) {
374                                 if (typeData.Type == typeof(XmlQualifiedName)) WriteNullableQualifiedNameEncoded (name, ns, (XmlQualifiedName)memberValue, xsiType);
375                                 else WriteNullableStringEncoded (name, ns, GetStringValue (mappedType, typeData, memberValue), xsiType);
376                         }
377                         else {
378                                 if (typeData.Type == typeof(XmlQualifiedName)) WriteElementQualifiedName (name, ns, (XmlQualifiedName)memberValue, xsiType);
379                                 else WriteElementString (name, ns, GetStringValue (mappedType, typeData, memberValue), xsiType);
380                         }
381                 }
382
383                 protected virtual void WriteListElement (XmlTypeMapping typeMap, object ob, string element, string namesp)
384                 {
385                         if (_format == SerializationFormat.Encoded)
386                         {
387                                 string n, ns;
388                                 int itemCount = GetListCount (typeMap.TypeData, ob);
389                                 ((ListMap) typeMap.ObjectMap).GetArrayType (itemCount, out n, out ns);
390                                 string arrayType = (ns != string.Empty) ? FromXmlQualifiedName (new XmlQualifiedName(n,ns)) : n;
391                                 WriteAttribute ("arrayType", XmlSerializer.EncodingNamespace, arrayType);
392                         }
393                         WriteListContent (null, typeMap.TypeData, (ListMap) typeMap.ObjectMap, ob, null);
394                 }
395
396                 void WriteListContent (object container, TypeData listType, ListMap map, object ob, StringBuilder targetString)
397                 {
398                         if (listType.Type.IsArray)
399                         {
400                                 Array array = (Array)ob;
401                                 for (int n=0; n<array.Length; n++)
402                                 {
403                                         object item = array.GetValue (n);
404                                         XmlTypeMapElementInfo info = map.FindElement (container, n, item);
405                                         if (info != null && targetString == null) WriteMemberElement (info, item);
406                                         else if (info != null && targetString != null) targetString.Append (GetStringValue (info.MappedType, info.TypeData, item)).Append (" ");
407                                         else if (item != null) throw CreateUnknownTypeException (item);
408                                 }
409                         }
410                         else if (ob is ICollection)
411                         {
412                                 int count = (int) ob.GetType().GetProperty ("Count").GetValue(ob,null);
413                                 PropertyInfo itemProp = TypeData.GetIndexerProperty (listType.Type);
414                                 object[] index = new object[1];
415                                 for (int n=0; n<count; n++)
416                                 {
417                                         index[0] = n;
418                                         object item = itemProp.GetValue (ob, index);
419                                         XmlTypeMapElementInfo info = map.FindElement (container, n, item);
420                                         if (info != null && targetString == null) WriteMemberElement (info, item);
421                                         else if (info != null && targetString != null) targetString.Append (GetStringValue (info.MappedType, info.TypeData, item)).Append (" ");
422                                         else if (item != null) throw CreateUnknownTypeException (item);
423                                 }
424                         }
425                         else if (ob is IEnumerable)
426                         {
427                                 IEnumerable e = (IEnumerable)ob;
428                                 foreach (object item in e)
429                                 {
430                                         XmlTypeMapElementInfo info = map.FindElement (container, -1, item);
431                                         if (info != null && targetString == null) WriteMemberElement (info, item);
432                                         else if (info != null && targetString != null) targetString.Append (GetStringValue (info.MappedType, info.TypeData, item)).Append (" ");
433                                         else if (item != null) throw CreateUnknownTypeException (item);
434                                 }
435                         }
436                         else
437                                 throw new Exception ("Unsupported collection type");
438                 }
439
440                 int GetListCount (TypeData listType, object ob)
441                 {
442                         if (listType.Type.IsArray)
443                                 return ((Array)ob).Length;
444                         else
445                                 return (int) listType.Type.GetProperty ("Count").GetValue(ob,null);
446                 }
447
448                 void WriteAnyElementContent (XmlTypeMapMemberAnyElement member, object memberValue)
449                 {
450                         if (member.TypeData.Type == typeof (XmlElement)) {
451                                 memberValue = new object[] { memberValue };
452                         }
453
454                         Array elems = (Array) memberValue;
455                         foreach (XmlNode elem in elems)
456                         {
457                                 if (elem is XmlElement) 
458                                 {
459                                         if (member.IsElementDefined (elem.Name, elem.NamespaceURI))
460                                         {
461                                                 if (_format == SerializationFormat.Literal) WriteElementLiteral (elem, "", "", false, true);
462                                                 else WriteElementEncoded (elem, "", "", false, true);
463                                         }
464                                         else
465                                                 throw CreateUnknownAnyElementException (elem.Name, elem.NamespaceURI);
466                                 }
467                                 else
468                                         elem.WriteTo (Writer);
469                         }
470                 }
471
472                 protected virtual void WritePrimitiveElement (XmlTypeMapping typeMap, object ob, string element, string namesp)
473                 {
474                         Writer.WriteString (GetStringValue (typeMap, typeMap.TypeData, ob));
475                 }
476
477                 protected virtual void WriteEnumElement (XmlTypeMapping typeMap, object ob, string element, string namesp)
478                 {
479                         Writer.WriteString (GetEnumXmlValue (typeMap, ob));
480                 }
481
482                 string GetStringValue (XmlTypeMapping typeMap, TypeData type, object value)
483                 {
484                         if (type.SchemaType == SchemaTypes.Array) {
485                                 if (value == null) return null;
486                                 StringBuilder sb = new StringBuilder ();
487                                 WriteListContent (null, typeMap.TypeData, (ListMap)typeMap.ObjectMap, value, sb);
488                                 return sb.ToString ().Trim ();
489                         }
490                         else if (type.SchemaType == SchemaTypes.Enum)
491                                 return GetEnumXmlValue (typeMap, value);
492                         else if (type.Type == typeof (XmlQualifiedName))
493                                 return FromXmlQualifiedName ((XmlQualifiedName)value);
494                         else if (value == null)
495                                 return null;
496                         else
497                                 return XmlCustomFormatter.ToXmlString (type, value);
498                 }
499
500                 string GetEnumXmlValue (XmlTypeMapping typeMap, object ob)
501                 {
502                         EnumMap map = (EnumMap)typeMap.ObjectMap;
503                         return map.GetXmlName (typeMap.TypeFullName, ob);
504                 }
505
506                 class CallbackInfo
507                 {
508                         XmlSerializationWriterInterpreter _swi;
509                         XmlTypeMapping _typeMap;
510
511                         public CallbackInfo (XmlSerializationWriterInterpreter swi, XmlTypeMapping typeMap)
512                         {
513                                 _swi = swi;
514                                 _typeMap = typeMap;
515                         }
516
517                         internal void WriteObject (object ob)
518                         {
519                                 _swi.WriteObject (_typeMap, ob, _typeMap.ElementName, _typeMap.Namespace, false, false, false);
520                         }
521
522                         internal void WriteEnum (object ob)
523                         {
524                                 _swi.WriteObject (_typeMap, ob, _typeMap.ElementName, _typeMap.Namespace, false, true, false);
525                         }
526                 }
527
528         }
529 }