New tests.
[mono.git] / mcs / class / System.Xaml / System.Xaml / XamlXmlWriter.cs
1 //
2 // Copyright (C) 2010 Novell Inc. http://novell.com
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
11 // 
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 // 
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 //
23 using System;
24 using System.Collections.Generic;
25 using System.ComponentModel;
26 using System.IO;
27 using System.Linq;
28 using System.Windows.Markup;
29 using System.Xml;
30
31 /*
32
33 ** Value output node type
34
35 When an object contains a member:
36 - it becomes an attribute when it contains a value.
37 - it becomes an element when it contains an object.
38 */
39
40 namespace System.Xaml
41 {
42         public class XamlXmlWriter : XamlWriter
43         {
44                 public XamlXmlWriter (Stream stream, XamlSchemaContext schemaContext)
45                         : this (stream, schemaContext, null)
46                 {
47                 }
48                 
49                 public XamlXmlWriter (Stream stream, XamlSchemaContext schemaContext, XamlXmlWriterSettings settings)
50                         : this (XmlWriter.Create (stream), schemaContext, null)
51                 {
52                 }
53                 
54                 public XamlXmlWriter (TextWriter textWriter, XamlSchemaContext schemaContext)
55                         : this (XmlWriter.Create (textWriter), schemaContext, null)
56                 {
57                 }
58                 
59                 public XamlXmlWriter (TextWriter textWriter, XamlSchemaContext schemaContext, XamlXmlWriterSettings settings)
60                         : this (XmlWriter.Create (textWriter), schemaContext, null)
61                 {
62                 }
63                 
64                 public XamlXmlWriter (XmlWriter xmlWriter, XamlSchemaContext schemaContext)
65                         : this (xmlWriter, schemaContext, null)
66                 {
67                 }
68                 
69                 public XamlXmlWriter (XmlWriter xmlWriter, XamlSchemaContext schemaContext, XamlXmlWriterSettings settings)
70                 {
71                         if (xmlWriter == null)
72                                 throw new ArgumentNullException ("xmlWriter");
73                         if (schemaContext == null)
74                                 throw new ArgumentNullException ("schemaContext");
75                         this.w = xmlWriter;
76                         this.sctx = schemaContext;
77                         this.settings = settings ?? new XamlXmlWriterSettings ();
78                         this.manager = new XamlWriterStateManager<XamlXmlWriterException, InvalidOperationException> (true);
79                 }
80
81                 XmlWriter w;
82                 XamlSchemaContext sctx;
83                 XamlXmlWriterSettings settings;
84                 IValueSerializerContext serializer_context;
85
86                 XamlWriterStateManager manager;
87
88                 Stack<object> nodes = new Stack<object> ();
89                 bool is_first_member_content, has_namespace;
90                 object first_member_value;
91
92                 public override XamlSchemaContext SchemaContext {
93                         get { return sctx; }
94                 }
95
96                 public XamlXmlWriterSettings Settings {
97                         get { return settings; }
98                 }
99
100                 protected override void Dispose (bool disposing)
101                 {
102                         if (!disposing)
103                                 return;
104
105                         while (nodes.Count > 0) {
106                                 var obj = nodes.Peek ();
107                                 if (obj is XamlMember) {
108                                         manager.OnClosingItem ();
109                                         WriteEndMember ();
110                                 }
111                                 else if (obj is XamlType)
112                                         WriteEndObject ();
113                                 else
114                                         nodes.Pop ();
115                         }
116                         if (settings.CloseOutput)
117                                 w.Close ();
118                 }
119
120                 public void Flush ()
121                 {
122                         w.Flush ();
123                 }
124
125                 public override void WriteEndMember ()
126                 {
127                         manager.EndMember ();
128                         WriteStackedStartMember (XamlNodeType.EndMember);
129                         DoEndMember ();
130
131                 }
132
133                 public override void WriteEndObject ()
134                 {
135                         manager.EndObject (nodes.Count > 1);
136                         w.WriteEndElement ();
137                         nodes.Pop ();
138                 }
139
140                 public override void WriteGetObject ()
141                 {
142                         manager.GetObject ();
143                         WriteStackedStartMember (XamlNodeType.GetObject);
144
145                         var xm = (XamlMember) GetNonNamespaceNode ();
146                         if (!xm.Type.IsCollection)
147                                 throw new InvalidOperationException (String.Format ("WriteGetObject method can be invoked only when current member '{0}' is of collection type", xm.Name));
148
149                         DoEndMember ();
150                         
151                         // FIXME: it likely has to write the "retrieved" object here.
152                 }
153
154                 public override void WriteNamespace (NamespaceDeclaration namespaceDeclaration)
155                 {
156                         if (namespaceDeclaration == null)
157                                 throw new ArgumentNullException ("namespaceDeclaration");
158
159                         manager.Namespace ();
160
161                         nodes.Push (namespaceDeclaration);
162                         has_namespace = true;
163                 }
164
165                 public override void WriteStartMember (XamlMember property)
166                 {
167                         if (property == null)
168                                 throw new ArgumentNullException ("property");
169
170                         manager.StartMember ();
171                         nodes.Push (property);
172
173                         is_first_member_content = true;
174                 }
175
176                 public override void WriteStartObject (XamlType xamlType)
177                 {
178                         if (xamlType == null)
179                                 throw new ArgumentNullException ("xamlType");
180
181                         manager.StartObject ();
182
183                         WriteStackedStartMember (XamlNodeType.StartObject);
184
185                         nodes.Push (xamlType);
186                         DoWriteStartObject (xamlType);
187                 }
188                 
189                 public override void WriteValue (object value)
190                 {
191                         manager.Value ();
192
193                         var xt = GetCurrentType ();
194                         
195                         var xm = GetNonNamespaceNode () as XamlMember;
196                         if (xm == XamlLanguage.Initialization) {
197                                 // do not reject type mismatch, as the value will be a string.
198                         }
199                         else if (xt != null && xt.UnderlyingType != null && !xt.UnderlyingType.IsInstanceOfType (value))
200                                 throw new ArgumentException (String.Format ("Value is not of type {0} but {1}", xt, value != null ? value.GetType ().FullName : "(null)"));
201
202                         if (!is_first_member_content) {
203                                 WriteStackedStartMember (XamlNodeType.Value);
204                                 DoWriteValue (value);
205                         }
206                         else
207                                 first_member_value = value;
208                 }
209
210                 void DoEndMember ()
211                 {
212                         var xm = nodes.Pop (); // XamlMember
213                         if (xm == XamlLanguage.Initialization)
214                                 ; // do nothing
215                         else if (w.WriteState == WriteState.Content)
216                                 w.WriteEndElement ();
217                         else
218                                 w.WriteEndAttribute ();
219
220                         is_first_member_content = false;
221                         first_member_value = null;
222                 }
223
224                 void WriteStackedStartMember (XamlNodeType next)
225                 {
226                         if (!is_first_member_content)
227                                 return;
228
229                         var xm = GetNonNamespaceNode () as XamlMember;
230                         if (xm == null)
231                                 return;
232
233                         if (xm == XamlLanguage.Initialization)
234                                 ; // do nothing
235                         else if (next == XamlNodeType.StartObject || w.WriteState == WriteState.Content || has_namespace)
236                                 DoWriteStartMemberElement (xm);
237                         else
238                                 DoWriteStartMemberAttribute (xm);
239                         if (first_member_value != null)
240                                 DoWriteValue (first_member_value);
241                         is_first_member_content = false;
242                 }
243
244                 void DoWriteStartObject (XamlType xamlType)
245                 {
246                         string prefix = GetPrefix (xamlType.PreferredXamlNamespace);
247                         w.WriteStartElement (prefix, xamlType.Name, xamlType.PreferredXamlNamespace);
248                         WriteAndClearNamespaces ();
249                 }
250                 
251                 void DoWriteStartMemberElement (XamlMember xm)
252                 {
253                         var xt = GetCurrentType ();
254                         string prefix = GetPrefix (xm.PreferredXamlNamespace);
255                         w.WriteStartElement (prefix, String.Concat (xt.Name, ".", xm.Name), xm.PreferredXamlNamespace);
256                         WriteAndClearNamespaces ();
257                 }
258                 
259                 void DoWriteStartMemberAttribute (XamlMember xm)
260                 {
261                         WriteAndClearNamespaces ();
262                         
263                         var xt = GetCurrentType ();
264                         if (xt.PreferredXamlNamespace == xm.PreferredXamlNamespace)
265                                 w.WriteStartAttribute (xm.Name);
266                         else {
267                                 string prefix = GetPrefix (xm.PreferredXamlNamespace);
268                                 w.WriteStartAttribute (prefix, xm.Name, xm.PreferredXamlNamespace);
269                         }
270                 }
271
272                 void DoWriteValue (object value)
273                 {
274                         var xt = value == null ? XamlLanguage.Null : SchemaContext.GetXamlType (value.GetType ());
275                         var vs = xt.TypeConverter;
276                         var c = vs != null ? vs.ConverterInstance : null;
277                         if (c != null && c.CanConvertTo (typeof (string)))
278                                 w.WriteString (c.ConvertToInvariantString (value));
279                         else
280                                 w.WriteValue (value);
281                 }
282
283                 object GetNonNamespaceNode ()
284                 {
285                         if (nodes.Count == 0)
286                                 return null;
287                         var obj = nodes.Pop ();
288                         try {
289                                 if (obj is NamespaceDeclaration)
290                                         return GetNonNamespaceNode ();
291                                 else
292                                         return obj;
293                         } finally {
294                                 nodes.Push (obj);
295                         }
296                 }
297
298                 XamlType GetCurrentType ()
299                 {
300                         if (nodes.Count == 0)
301                                 return null;
302                         var obj = nodes.Pop ();
303                         try {
304                                 if (obj is XamlType)
305                                         return (XamlType) obj;
306                                 else
307                                         return GetCurrentType ();
308                         } finally {
309                                 nodes.Push (obj);
310                         }
311                 }
312
313                 string GetPrefix (string ns)
314                 {
315                         var decl = nodes.LastOrDefault (d => d is NamespaceDeclaration && ((NamespaceDeclaration) d).Namespace == ns) as NamespaceDeclaration;
316                         if (decl != null)
317                                 return decl.Prefix;
318                         return w.LookupPrefix (ns);
319                 }
320
321                 Stack<NamespaceDeclaration> tmp_nss = new Stack<NamespaceDeclaration> ();
322
323                 void WriteAndClearNamespaces ()
324                 {
325                         // write namespace that are put *before* current item.
326
327                         var top = nodes.Pop (); // temporarily pop out
328
329                         while (nodes.Count > 0) {
330                                 var obj = nodes.Pop ();
331                                 var nd = obj as NamespaceDeclaration;
332                                 if (nd == null) {
333                                         nodes.Push (obj);
334                                         break;
335                                 }
336                                 tmp_nss.Push (nd);
337                         }
338                         while (tmp_nss.Count > 0) {
339                                 var nd = tmp_nss.Pop ();
340                                 DoWriteNamespace (nd);
341                         }
342                         has_namespace = false;
343                         manager.NamespaceCleanedUp ();
344
345                         nodes.Push (top); // push back
346                 }
347
348                 void DoWriteNamespace (NamespaceDeclaration nd)
349                 {
350                         if (String.IsNullOrEmpty (nd.Prefix))
351                                 w.WriteAttributeString ("xmlns", nd.Namespace);
352                         else
353                                 w.WriteAttributeString ("xmlns", nd.Prefix, XamlLanguage.Xmlns2000Namespace, nd.Namespace);
354                 }
355         }
356 }