2 // System.Xml.Serialization.XmlSerializationWriter.cs
5 // Tim Coleman (tim@timcoleman.com)
6 // Lluis Sanchez Gual (lluis@ximian.com)
8 // Copyright (C) Tim Coleman, 2002
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.
33 using System.Collections;
34 using System.Globalization;
37 using System.Xml.Schema;
38 using System.Runtime.Serialization;
40 namespace System.Xml.Serialization {
41 public abstract class XmlSerializationWriter {
45 ObjectIDGenerator idGenerator;
47 bool topLevelElement = false;
51 Queue referencedElements;
53 Hashtable serializedObjects;
54 const string xmlNamespace = "http://www.w3.org/2000/xmlns/";
55 const string unexpectedTypeError = "The type {0} was not expected. Use the" +
56 " XmlInclude or SoapInclude attribute to specify types that are not known statically.";
62 protected XmlSerializationWriter ()
65 serializedObjects = new Hashtable ();
68 internal void Initialize (XmlWriter writer, XmlSerializerNamespaces nss)
73 namespaces = new ArrayList ();
74 foreach (XmlQualifiedName ns in nss.ToArray())
80 #endregion // Constructors
84 protected ArrayList Namespaces {
85 get { return namespaces; }
86 set { namespaces = value; }
89 protected XmlWriter Writer {
90 get { return writer; }
91 set { writer = value; }
94 #endregion // Properties
98 protected void AddWriteCallback (Type type, string typeName, string typeNs, XmlSerializationWriteCallback callback)
100 WriteCallbackInfo info = new WriteCallbackInfo ();
102 info.TypeName = typeName;
103 info.TypeNs = typeNs;
104 info.Callback = callback;
106 if (callbacks == null) callbacks = new Hashtable ();
107 callbacks.Add (type, info);
110 protected Exception CreateChoiceIdentifierValueException (string value, string identifier, string name, string ns)
112 string message = string.Format ("Value '{0}' of the choice"
113 + " identifier '{1}' does not match element '{2}'"
114 + " from namespace '{3}'.", value, identifier,
116 return new InvalidOperationException (message);
119 protected Exception CreateInvalidChoiceIdentifierValueException (string type, string identifier)
121 string message = string.Format ("Invalid or missing choice"
122 + " identifier '{0}' of type '{1}'.", identifier,
124 return new InvalidOperationException (message);
127 protected Exception CreateMismatchChoiceException (string value, string elementName, string enumValue)
129 string message = String.Format ("Value of {0} mismatches the type of {1}, you need to set it to {2}.", elementName, value, enumValue);
130 return new InvalidOperationException (message);
133 protected Exception CreateUnknownAnyElementException (string name, string ns)
135 string message = String.Format ("The XML element named '{0}' from namespace '{1}' was not expected. The XML element name and namespace must match those provided via XmlAnyElementAttribute(s).", name, ns);
136 return new InvalidOperationException (message);
139 protected Exception CreateUnknownTypeException (object o)
141 return CreateUnknownTypeException (o.GetType ());
144 protected Exception CreateUnknownTypeException (Type type)
146 string message = String.Format ("The type {0} may not be used in this context.", type);
147 return new InvalidOperationException (message);
150 protected static byte[] FromByteArrayBase64 (byte[] value)
155 protected static string FromByteArrayHex (byte[] value)
157 return XmlCustomFormatter.FromByteArrayHex (value);
160 protected static string FromChar (char value)
162 return XmlCustomFormatter.FromChar (value);
165 protected static string FromDate (DateTime value)
167 return XmlCustomFormatter.FromDate (value);
170 protected static string FromDateTime (DateTime value)
172 return XmlCustomFormatter.FromDateTime (value);
175 protected static string FromEnum (long value, string[] values, long[] ids)
177 return XmlCustomFormatter.FromEnum (value, values, ids);
180 protected static string FromTime (DateTime value)
182 return XmlCustomFormatter.FromTime (value);
185 protected static string FromXmlName (string name)
187 return XmlCustomFormatter.FromXmlName (name);
190 protected static string FromXmlNCName (string ncName)
192 return XmlCustomFormatter.FromXmlNCName (ncName);
195 protected static string FromXmlNmToken (string nmToken)
197 return XmlCustomFormatter.FromXmlNmToken (nmToken);
200 protected static string FromXmlNmTokens (string nmTokens)
202 return XmlCustomFormatter.FromXmlNmTokens (nmTokens);
205 protected string FromXmlQualifiedName (XmlQualifiedName xmlQualifiedName)
207 if (xmlQualifiedName == null || xmlQualifiedName == XmlQualifiedName.Empty)
210 return GetQualifiedName (xmlQualifiedName.Name, xmlQualifiedName.Namespace);
213 private string GetId (object o, bool addToReferencesList)
215 if (idGenerator == null) idGenerator = new ObjectIDGenerator ();
218 long lid = idGenerator.GetId (o, out firstTime);
219 return String.Format (CultureInfo.InvariantCulture, "id{0}", lid);
223 bool AlreadyQueued (object ob)
225 if (idGenerator == null) return false;
228 idGenerator.HasId (ob, out firstTime);
232 private string GetNamespacePrefix (string ns)
234 string prefix = Writer.LookupPrefix (ns);
237 prefix = String.Format (CultureInfo.InvariantCulture, "q{0}", ++qnameCount);
238 WriteAttribute ("xmlns", prefix, null, ns);
243 private string GetQualifiedName (string name, string ns)
245 if (ns == String.Empty) return name;
247 string prefix = GetNamespacePrefix (ns);
248 if (prefix == String.Empty)
251 return String.Format ("{0}:{1}", prefix, name);
254 protected abstract void InitCallbacks ();
256 protected void TopLevelElement ()
258 topLevelElement = true;
261 protected void WriteAttribute (string localName, byte[] value)
263 WriteAttribute (localName, String.Empty, value);
266 protected void WriteAttribute (string localName, string value)
268 WriteAttribute (String.Empty, localName, String.Empty, value);
271 protected void WriteAttribute (string localName, string ns, byte[] value)
276 Writer.WriteStartAttribute (localName, ns);
278 Writer.WriteEndAttribute ();
281 protected void WriteAttribute (string localName, string ns, string value)
283 WriteAttribute (null, localName, ns, value);
286 protected void WriteAttribute (string prefix, string localName, string ns, string value)
291 Writer.WriteAttributeString (prefix, localName, ns, value);
294 protected void WriteElementEncoded (XmlNode node, string name, string ns, bool isNullable, bool any)
296 if (name != string.Empty)
301 WriteNullTagEncoded (name, ns);
305 Writer.WriteStartElement (name, ns);
306 node.WriteTo (Writer);
307 Writer.WriteEndElement ();
311 node.WriteTo (Writer);
314 protected void WriteElementLiteral (XmlNode node, string name, string ns, bool isNullable, bool any)
316 if (name != string.Empty)
321 WriteNullTagLiteral (name, ns);
325 Writer.WriteStartElement (name, ns);
326 node.WriteTo (Writer);
327 Writer.WriteEndElement ();
331 node.WriteTo (Writer);
334 protected void WriteElementQualifiedName (string localName, XmlQualifiedName value)
336 WriteElementQualifiedName (localName, String.Empty, value, null);
339 protected void WriteElementQualifiedName (string localName, string ns, XmlQualifiedName value)
341 WriteElementQualifiedName (localName, ns, value, null);
344 protected void WriteElementQualifiedName (string localName, XmlQualifiedName value, XmlQualifiedName xsiType)
346 WriteElementQualifiedName (localName, String.Empty, value, xsiType);
349 protected void WriteElementQualifiedName (string localName, string ns, XmlQualifiedName value, XmlQualifiedName xsiType)
351 localName = XmlCustomFormatter.FromXmlNCName (localName);
352 WriteStartElement (localName, ns);
353 if (xsiType != null) WriteXsiType (xsiType.Name, xsiType.Namespace);
354 Writer.WriteString (FromXmlQualifiedName (value));
358 protected void WriteElementString (string localName, string value)
360 WriteElementString (localName, String.Empty, value, null);
363 protected void WriteElementString (string localName, string ns, string value)
365 WriteElementString (localName, ns, value, null);
368 protected void WriteElementString (string localName, string value, XmlQualifiedName xsiType)
370 WriteElementString (localName, String.Empty, value, xsiType);
373 protected void WriteElementString (string localName, string ns, string value, XmlQualifiedName xsiType)
375 if (value == null) return;
377 if (xsiType != null) {
378 localName = XmlCustomFormatter.FromXmlNCName (localName);
379 WriteStartElement (localName, ns);
380 WriteXsiType (xsiType.Name, xsiType.Namespace);
381 Writer.WriteString (value);
385 Writer.WriteElementString (localName, ns, value);
388 protected void WriteElementStringRaw (string localName, byte[] value)
390 WriteElementStringRaw (localName, String.Empty, value, null);
393 protected void WriteElementStringRaw (string localName, string value)
395 WriteElementStringRaw (localName, String.Empty, value, null);
398 protected void WriteElementStringRaw (string localName, byte[] value, XmlQualifiedName xsiType)
400 WriteElementStringRaw (localName, String.Empty, value, xsiType);
403 protected void WriteElementStringRaw (string localName, string ns, byte[] value)
405 WriteElementStringRaw (localName, ns, value, null);
408 protected void WriteElementStringRaw (string localName, string ns, string value)
410 WriteElementStringRaw (localName, ns, value, null);
413 protected void WriteElementStringRaw (string localName, string value, XmlQualifiedName xsiType)
415 WriteElementStringRaw (localName, String.Empty, value, null);
418 protected void WriteElementStringRaw (string localName, string ns, byte[] value, XmlQualifiedName xsiType)
423 WriteStartElement (localName, ns);
426 WriteXsiType (xsiType.Name, xsiType.Namespace);
428 if (value.Length > 0)
429 Writer.WriteBase64(value,0,value.Length);
433 protected void WriteElementStringRaw (string localName, string ns, string value, XmlQualifiedName xsiType)
435 localName = XmlCustomFormatter.FromXmlNCName (localName);
436 WriteStartElement (localName, ns);
439 WriteXsiType (xsiType.Name, xsiType.Namespace);
441 Writer.WriteRaw (value);
445 protected void WriteEmptyTag (string name)
447 WriteEmptyTag (name, String.Empty);
450 protected void WriteEmptyTag (string name, string ns)
452 name = XmlCustomFormatter.FromXmlName (name);
453 WriteStartElement (name, ns);
457 protected void WriteEndElement ()
459 WriteEndElement (null);
462 protected void WriteEndElement (object o)
465 serializedObjects.Remove (o);
467 Writer.WriteEndElement ();
470 protected void WriteId (object o)
472 WriteAttribute ("id", GetId (o, true));
475 protected void WriteNamespaceDeclarations (XmlSerializerNamespaces ns)
480 ICollection namespaces = ns.Namespaces.Values;
481 foreach (XmlQualifiedName qn in namespaces) {
482 if (qn.Namespace != String.Empty && Writer.LookupPrefix (qn.Namespace) == null)
483 WriteAttribute ("xmlns", qn.Name, xmlNamespace, qn.Namespace);
487 protected void WriteNullableQualifiedNameEncoded (string name, string ns, XmlQualifiedName value, XmlQualifiedName xsiType)
490 WriteElementQualifiedName (name, ns, value, xsiType);
492 WriteNullTagEncoded (name, ns);
495 protected void WriteNullableQualifiedNameLiteral (string name, string ns, XmlQualifiedName value)
498 WriteElementQualifiedName (name, ns, value);
500 WriteNullTagLiteral (name, ns);
503 protected void WriteNullableStringEncoded (string name, string ns, string value, XmlQualifiedName xsiType)
506 WriteElementString (name, ns, value, xsiType);
508 WriteNullTagEncoded (name, ns);
511 protected void WriteNullableStringEncodedRaw (string name, string ns, byte[] value, XmlQualifiedName xsiType)
514 WriteNullTagEncoded (name, ns);
516 WriteElementStringRaw (name, ns, value, xsiType);
519 protected void WriteNullableStringEncodedRaw (string name, string ns, string value, XmlQualifiedName xsiType)
522 WriteNullTagEncoded (name, ns);
524 WriteElementStringRaw (name, ns, value, xsiType);
527 protected void WriteNullableStringLiteral (string name, string ns, string value)
530 WriteElementString (name, ns, value, null);
532 WriteNullTagLiteral (name, ns);
535 protected void WriteNullableStringLiteralRaw (string name, string ns, byte[] value)
538 WriteNullTagLiteral (name, ns);
540 WriteElementStringRaw (name, ns, value);
543 protected void WriteNullableStringLiteralRaw (string name, string ns, string value)
546 WriteNullTagLiteral (name, ns);
548 WriteElementStringRaw (name, ns, value);
551 protected void WriteNullTagEncoded (string name)
553 WriteNullTagEncoded (name, String.Empty);
556 protected void WriteNullTagEncoded (string name, string ns)
558 Writer.WriteStartElement (name, ns);
560 Writer.WriteAttributeString ("nil", XmlSchema.InstanceNamespace, "true");
562 Writer.WriteAttributeString ("null", XmlSchema.InstanceNamespace, "1");
564 Writer.WriteEndElement ();
567 protected void WriteNullTagLiteral (string name)
569 WriteNullTagLiteral (name, String.Empty);
572 protected void WriteNullTagLiteral (string name, string ns)
574 WriteStartElement (name, ns);
575 Writer.WriteAttributeString ("nil", XmlSchema.InstanceNamespace, "true");
579 protected void WritePotentiallyReferencingElement (string n, string ns, object o)
581 WritePotentiallyReferencingElement (n, ns, o, null, false, false);
584 protected void WritePotentiallyReferencingElement (string n, string ns, object o, Type ambientType)
586 WritePotentiallyReferencingElement (n, ns, o, ambientType, false, false);
589 protected void WritePotentiallyReferencingElement (string n, string ns, object o, Type ambientType, bool suppressReference)
591 WritePotentiallyReferencingElement (n, ns, o, ambientType, suppressReference, false);
594 protected void WritePotentiallyReferencingElement (string n, string ns, object o, Type ambientType, bool suppressReference, bool isNullable)
598 if (isNullable) WriteNullTagEncoded (n, ns);
602 WriteStartElement (n, ns, true);
604 CheckReferenceQueue ();
606 if (callbacks.ContainsKey (o.GetType ()))
608 WriteCallbackInfo info = (WriteCallbackInfo) callbacks[o.GetType()];
609 if (o.GetType ().IsEnum) {
612 else if (suppressReference) {
613 Writer.WriteAttributeString ("id", GetId (o, false));
614 if (ambientType != o.GetType ()) WriteXsiType(info.TypeName, info.TypeNs);
618 if (!AlreadyQueued (o)) referencedElements.Enqueue (o);
619 Writer.WriteAttributeString ("href", "#" + GetId (o, true));
624 // Must be a primitive type
625 TypeData td = TypeTranslator.GetTypeData (o.GetType ());
626 if (td.SchemaType != SchemaTypes.Primitive)
627 throw new InvalidOperationException ("Invalid type: " + o.GetType().FullName);
628 WriteXsiType(td.XmlType, XmlSchema.Namespace);
629 Writer.WriteString (XmlCustomFormatter.ToXmlString (td, o));
635 protected void WriteReferencedElements ()
637 if (referencedElements == null) return;
638 if (callbacks == null) return;
640 while (referencedElements.Count > 0)
642 object o = referencedElements.Dequeue ();
643 TypeData td = TypeTranslator.GetTypeData (o.GetType ());
644 WriteCallbackInfo info = (WriteCallbackInfo) callbacks[o.GetType()];
645 WriteStartElement (info.TypeName, info.TypeNs, true);
646 Writer.WriteAttributeString ("id", GetId (o, false));
648 if (td.SchemaType != SchemaTypes.Array) // Array use its own "arrayType" attribute
649 WriteXsiType(info.TypeName, info.TypeNs);
656 protected void WriteReferencingElement (string n, string ns, object o)
658 WriteReferencingElement (n, ns, o, false);
661 protected void WriteReferencingElement (string n, string ns, object o, bool isNullable)
665 if (isNullable) WriteNullTagEncoded (n, ns);
670 CheckReferenceQueue ();
671 if (!AlreadyQueued (o)) referencedElements.Enqueue (o);
673 Writer.WriteStartElement (n, ns);
674 Writer.WriteAttributeString ("href", "#" + GetId (o, true));
675 Writer.WriteEndElement ();
679 void CheckReferenceQueue ()
681 if (referencedElements == null)
683 referencedElements = new Queue ();
689 protected void WriteRpcResult (string name, string ns)
691 throw new NotImplementedException ();
694 protected void WriteSerializable (IXmlSerializable serializable, string name, string ns, bool isNullable)
696 if (serializable == null)
698 if (isNullable) WriteNullTagLiteral (name, ns);
703 Writer.WriteStartElement (name, ns);
704 serializable.WriteXml (Writer);
705 Writer.WriteEndElement ();
709 protected void WriteStartDocument ()
711 if (Writer.WriteState == WriteState.Start)
712 Writer.WriteStartDocument ();
715 protected void WriteStartElement (string name)
717 WriteStartElement (name, String.Empty, null, false);
720 protected void WriteStartElement (string name, string ns)
722 WriteStartElement (name, ns, null, false);
725 protected void WriteStartElement (string name, string ns, bool writePrefixed)
727 WriteStartElement (name, ns, null, writePrefixed);
730 protected void WriteStartElement (string name, string ns, object o)
732 WriteStartElement (name, ns, o, false);
735 protected void WriteStartElement (string name, string ns, object o, bool writePrefixed)
739 if (serializedObjects.Contains (o))
740 throw new InvalidOperationException ("A cirtular reference was detected while serializing an object of type " + o.GetType().Name);
742 serializedObjects [o] = o;
745 WriteState oldState = Writer.WriteState;
747 string prefix = null;
749 if (topLevelElement && ns != null && ns.Length != 0)
751 foreach (XmlQualifiedName qn in namespaces)
752 if (qn.Namespace == ns) {
754 writePrefixed = true;
759 if (writePrefixed && ns != string.Empty)
761 name = XmlCustomFormatter.FromXmlName (name);
764 prefix = Writer.LookupPrefix (ns);
765 if (prefix == null || prefix.Length == 0)
766 prefix = "q" + (++qnameCount);
767 Writer.WriteStartElement (prefix, name, ns);
769 Writer.WriteStartElement (name, ns);
773 if (namespaces != null) {
774 foreach (XmlQualifiedName qn in namespaces)
776 string currentPrefix = Writer.LookupPrefix (qn.Namespace);
777 if (currentPrefix != null && currentPrefix.Length != 0) continue;
779 WriteAttribute ("xmlns",qn.Name,xmlNamespace,qn.Namespace);
782 topLevelElement = false;
786 protected void WriteTypedPrimitive (string name, string ns, object o, bool xsiType)
789 TypeData td = TypeTranslator.GetTypeData (o.GetType ());
791 name = XmlCustomFormatter.FromXmlName (name);
792 Writer.WriteStartElement (name, ns);
794 if (o is XmlQualifiedName)
795 value = FromXmlQualifiedName ((XmlQualifiedName) o);
797 value = XmlCustomFormatter.ToXmlString (td, o);
801 if (td.SchemaType != SchemaTypes.Primitive)
802 throw new InvalidOperationException (string.Format (unexpectedTypeError, o.GetType().FullName));
803 WriteXsiType (td.XmlType, XmlSchema.Namespace);
807 Writer.WriteEndElement ();
810 protected void WriteValue (byte[] value)
812 Writer.WriteBase64 (value, 0, value.Length);
815 protected void WriteValue (string value)
818 Writer.WriteString (value);
821 protected void WriteXmlAttribute (XmlNode node)
823 WriteXmlAttribute (node, null);
826 protected void WriteXmlAttribute (XmlNode node, object container)
828 XmlAttribute attr = node as XmlAttribute;
830 throw new InvalidOperationException ("The node must be either type XmlAttribute or a derived type.");
832 if (attr.NamespaceURI == XmlSerializer.WsdlNamespace)
834 // The wsdl arrayType attribute needs special handling
835 if (attr.LocalName == "arrayType") {
836 string ns, type, dimensions;
837 TypeTranslator.ParseArrayType (attr.Value, out type, out ns, out dimensions);
838 string value = GetQualifiedName (type + dimensions, ns);
839 WriteAttribute (attr.Prefix, attr.LocalName, attr.NamespaceURI, value);
844 WriteAttribute (attr.Prefix, attr.LocalName, attr.NamespaceURI, attr.Value);
847 protected void WriteXsiType (string name, string ns)
849 if (ns != null && ns != string.Empty)
850 WriteAttribute ("type", XmlSchema.InstanceNamespace, GetQualifiedName (name, ns));
852 WriteAttribute ("type", XmlSchema.InstanceNamespace, name);
857 class WriteCallbackInfo
860 public string TypeName;
861 public string TypeNs;
862 public XmlSerializationWriteCallback Callback;