Kill the MOONLIGHT define in System.ServiceModel.Web.
[mono.git] / mcs / class / System.ServiceModel.Web / System.Runtime.Serialization.Json / JsonWriter.cs
1 //
2 // JsonWriter.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections.Generic;
30 using System.Globalization;
31 using System.IO;
32 using System.Text;
33 using System.Xml;
34
35 namespace System.Runtime.Serialization.Json
36 {
37         class JsonWriter : XmlDictionaryWriter, IXmlJsonWriterInitializer
38         {
39                 enum ElementType
40                 {
41                         None,
42                         Null,
43                         Object,
44                         Array,
45                         String,
46                         Number,
47                         Boolean,
48                 }
49
50                 Stream output;
51                 bool close_output;
52                 WriteState state;
53                 Stack<ElementType> element_kinds = new Stack<ElementType> ();
54                 Stack<bool> first_content_flags = new Stack<bool> ();
55                 string attr_name, attr_value, runtime_type;
56                 Encoding encoding;
57                 byte [] encbuf = new byte [1024];
58                 bool no_string_yet = true, is_null, is_ascii_single;
59
60                 public JsonWriter (Stream stream, Encoding encoding, bool closeOutput)
61                 {
62                         SetOutput (stream, encoding, closeOutput);
63                 }
64
65                 public void SetOutput (Stream stream, Encoding encoding, bool ownsStream)
66                 {
67                         if (stream == null)
68                                 throw new ArgumentNullException ("stream");
69                         if (encoding == null)
70                                 throw new ArgumentNullException ("encoding");
71                         output = stream;
72                         this.encoding = encoding;
73                         close_output = ownsStream;
74                         is_ascii_single = encoding is UTF8Encoding || encoding.IsSingleByte;
75                 }
76
77                 void CheckState ()
78                 {
79                         switch (state) {
80                         case WriteState.Closed:
81                         case WriteState.Error:
82                                 throw new InvalidOperationException (String.Format ("This XmlDictionaryReader is already at '{0}' state", state));
83                         }
84                 }
85
86                 // copied from System.Silverlight JavaScriptSerializer.
87                 static string EscapeStringLiteral (string input)
88                 {
89                         StringBuilder sb = null;
90                         int i = 0, start = 0;
91                         for (; i < input.Length; i++) {
92                                 switch (input [i]) {
93                                 case '"':
94                                         AppendBuffer (ref sb, input, start, i, @"\""");
95                                         break;
96                                 case '\\':
97                                         AppendBuffer (ref sb, input, start, i, @"\\");
98                                         break;
99                                 case '/':
100                                         AppendBuffer (ref sb, input, start, i, @"\/");
101                                         break;
102                                 case '\x8':
103                                         AppendBuffer (ref sb, input, start, i, @"\b");
104                                         break;
105                                 case '\f':
106                                         AppendBuffer (ref sb, input, start, i, @"\f");
107                                         break;
108                                 case '\n':
109                                         AppendBuffer (ref sb, input, start, i, /*@"\n"*/@"\u000a");
110                                         break;
111                                 case '\r':
112                                         AppendBuffer (ref sb, input, start, i, /*@"\r"*/@"\u000d");
113                                         break;
114                                 case '\t':
115                                         AppendBuffer (ref sb, input, start, i, /*@"\t"*/@"\u0009");
116                                         break;
117                                 default:
118                                         continue;
119                                 }
120                                 start = i + 1;
121                         }
122                         string remaining = input.Substring (start, i - start);
123                         if (sb != null)
124                                 return sb.Append (remaining).ToString ();
125                         else
126                                 return remaining;
127                 }
128
129                 static void AppendBuffer (ref StringBuilder sb, string input, int start, int i, string append)
130                 {
131                         if (sb == null)
132                                 sb = new StringBuilder ();
133                         if (i != start)
134                                 sb.Append (input, start, i - start);
135                         sb.Append (append);
136                 }
137
138                 public override WriteState WriteState {
139                         get { return state; }
140                 }
141
142                 public override void Close ()
143                 {
144                         // close all open elements
145                         while (element_kinds.Count > 0)
146                                 WriteEndElement ();
147
148                         if (close_output)
149                                 output.Close ();
150                         else
151                                 output.Flush ();
152
153                         state = WriteState.Closed;
154                 }
155
156                 public override void Flush ()
157                 {
158                         output.Flush ();
159                 }
160
161                 public override void WriteStartElement (string prefix, string localName, string ns)
162                 {
163                         CheckState ();
164
165                         if (localName == null)
166                                 throw new ArgumentNullException ("localName");
167                         else if (localName.Length == 0)
168                                 throw new ArgumentException ("Empty string is not a valid localName in this XmlDictionaryWriter");
169
170                         if (!String.IsNullOrEmpty (ns))
171                                 throw new ArgumentException ("Non-empty namespace URI is not allowed in this XmlDictionaryWriter");
172                         if (!String.IsNullOrEmpty (prefix))
173                                 throw new ArgumentException ("Non-empty prefix is not allowed in this XmlDictionaryWriter");
174
175                         if (state == WriteState.Attribute)
176                                 WriteEndAttribute ();
177                         if (state == WriteState.Element)
178                                 CloseStartElement ();
179
180                         else if (state != WriteState.Start && element_kinds.Count == 0)
181                                 throw new XmlException ("This XmlDictionaryWriter does not support multiple top-level elements");
182
183                         if (element_kinds.Count == 0) {
184                                 if (localName != "root")
185                                         throw new XmlException ("Only 'root' is allowed for the name of the top-level element");
186                         } else {
187                                 switch (element_kinds.Peek ()) {
188                                 case ElementType.Array:
189                                         if (localName != "item")
190                                                 throw new XmlException ("Only 'item' is allowed as a content element of an array");
191                                         break;
192                                 case ElementType.String:
193                                         throw new XmlException ("Mixed content is not allowed in this XmlDictionaryWriter");
194                                 case ElementType.Null:
195                                         throw new XmlException ("Current type is null and writing element inside null is not allowed");
196                                 case ElementType.None:
197                                         throw new XmlException ("Before writing a child element, an element needs 'type' attribute to indicate whether the element is a JSON array or a JSON object in this XmlDictionaryWriter");
198                                 }
199
200                                 if (first_content_flags.Peek ()) {
201                                         first_content_flags.Pop ();
202                                         first_content_flags.Push (false);
203                                 }
204                                 else
205                                         OutputAsciiChar (',');
206
207                                 if (element_kinds.Peek () != ElementType.Array) {
208                                         OutputAsciiChar ('"');
209                                         OutputString (localName);
210                                         OutputAsciiChar ('\"');
211                                         OutputAsciiChar (':');
212                                 }
213                         }
214
215                         element_kinds.Push (ElementType.None); // undetermined yet
216
217                         state = WriteState.Element;
218                 }
219
220                 public override void WriteEndElement ()
221                 {
222                         CheckState ();
223
224                         if (state == WriteState.Attribute)
225                                 throw new XmlException ("Cannot end element when an attribute is being written");
226                         if (state == WriteState.Element)
227                                 CloseStartElement ();
228
229                         if (element_kinds.Count == 0)
230                                 throw new XmlException ("There is no open element to close");
231                         switch (element_kinds.Pop ()) {
232                         case ElementType.String:
233                                 if (!is_null) {
234                                         if (no_string_yet)
235                                                 OutputAsciiChar ('"');
236                                         OutputAsciiChar ('"');
237                                 }
238                                 no_string_yet = true;
239                                 is_null = false;
240                                 break;
241                         case ElementType.Array:
242                                 OutputAsciiChar (']');
243                                 break;
244                         case ElementType.Object:
245                                 OutputAsciiChar ('}');
246                                 break;
247                         }
248
249                         // not sure if it is correct though ...
250                         state = WriteState.Content;
251                         first_content_flags.Pop ();
252                 }
253
254                 public override void WriteFullEndElement ()
255                 {
256                         WriteEndElement (); // no such difference in JSON.
257                 }
258
259                 public override void WriteStartAttribute (string prefix, string localName, string ns)
260                 {
261                         CheckState ();
262
263                         if (state != WriteState.Element)
264                                 throw new XmlException ("Cannot write attribute as this XmlDictionaryWriter is not at element state");
265
266                         if (!String.IsNullOrEmpty (ns))
267                                 throw new ArgumentException ("Non-empty namespace URI is not allowed in this XmlDictionaryWriter");
268                         if (!String.IsNullOrEmpty (prefix))
269                                 throw new ArgumentException ("Non-empty prefix is not allowed in this XmlDictionaryWriter");
270
271                         if (localName != "type" && localName != "__type")
272                                 throw new ArgumentException ("Only 'type' and '__type' are allowed as an attribute name in this XmlDictionaryWriter");
273
274                         if (state != WriteState.Element)
275                                 throw new InvalidOperationException (String.Format ("Attribute cannot be written in {0} mode", state));
276
277                         attr_name = localName;
278                         state = WriteState.Attribute;
279                 }
280
281                 public override void WriteEndAttribute ()
282                 {
283                         CheckState ();
284
285                         if (state != WriteState.Attribute)
286                                 throw new XmlException ("Cannot close attribute, as this XmlDictionaryWriter is not at attribute state");
287
288                         if (attr_name == "type") {
289                                 switch (attr_value) {
290                                 case "object":
291                                         element_kinds.Pop ();
292                                         element_kinds.Push (ElementType.Object);
293                                         OutputAsciiChar ('{');
294                                         break;
295                                 case "array":
296                                         element_kinds.Pop ();
297                                         element_kinds.Push (ElementType.Array);
298                                         OutputAsciiChar ('[');
299                                         break;
300                                 case "number":
301                                         element_kinds.Pop ();
302                                         element_kinds.Push (ElementType.Number);
303                                         break;
304                                 case "boolean":
305                                         element_kinds.Pop ();
306                                         element_kinds.Push (ElementType.Boolean);
307                                         break;
308                                 case "string":
309                                         element_kinds.Pop ();
310                                         element_kinds.Push (ElementType.String);
311                                         break;
312                                 case "null":
313                                         element_kinds.Pop ();
314                                         element_kinds.Push (ElementType.Null);
315                                         OutputString ("null");
316                                         break;
317                                 default:
318                                         throw new XmlException (String.Format ("Unexpected type attribute value '{0}'", attr_value));
319                                 }
320                         }
321                         else
322                                 runtime_type = attr_value;
323
324                         state = WriteState.Element;
325                         attr_value = null;
326                 }
327
328                 void CloseStartElement ()
329                 {
330                         if (element_kinds.Peek () == ElementType.None) {
331                                 element_kinds.Pop ();
332                                 element_kinds.Push (ElementType.String);
333                                 no_string_yet = true;
334                                 is_null = false;
335                         }
336
337                         first_content_flags.Push (true);
338
339                         if (runtime_type != null) {
340                                 OutputString ("\"__type\":\"");
341                                 OutputString (runtime_type);
342                                 OutputAsciiChar ('\"');
343                                 runtime_type = null;
344                                 first_content_flags.Pop ();
345                                 first_content_flags.Push (false);
346                         }
347                 }
348
349                 public override void WriteString (string text)
350                 {
351                         CheckState ();
352
353                         if (state == WriteState.Start)
354                                 throw new InvalidOperationException ("Top-level content string is not allowed in this XmlDictionaryWriter");
355
356                         if (state == WriteState.Element) {
357                                 CloseStartElement ();
358                                 state = WriteState.Content;
359                         }
360
361                         if (state == WriteState.Attribute)
362                                 attr_value += text;
363                         else if (text == null) {
364                                 no_string_yet = false;
365                                 is_null = true;
366                                 if (element_kinds.Peek () != ElementType.Null)
367                                         OutputString ("null");
368                         } else {
369                                 switch (element_kinds.Peek ()) {
370                                 case ElementType.String:
371                                         if (no_string_yet) {
372                                                 OutputAsciiChar ('"');
373                                                 no_string_yet = false;
374                                         }
375                                         break;
376                                 case ElementType.Number:
377                                         // .NET is buggy here, it just outputs raw string, which results in invalid JSON format.
378                                         bool isString = false;
379                                         switch (text) {
380                                         case "INF":
381                                         case "-INF":
382                                         case "NaN":
383                                                 isString = true;
384                                                 break;
385                                         }
386                                         if (isString) {
387                                                 element_kinds.Pop ();
388                                                 element_kinds.Push (ElementType.String);
389                                                 goto case ElementType.String;
390                                         }
391                                         break;
392                                 case ElementType.Boolean:
393                                         break;
394                                 default:
395                                         throw new XmlException (String.Format ("Simple content string is allowed only for string, number and boolean types and not for {0} type", element_kinds.Peek ()));
396                                 }
397
398                                 OutputString (EscapeStringLiteral (text));
399                         }
400                 }
401
402                 #region mostly-ignored operations
403
404                 public override string LookupPrefix (string ns)
405                 {
406                         // Since there is no way to declare namespaces in
407                         // this writer, it always returns fixed results.
408                         if (ns == null)
409                                 throw new ArgumentNullException ("ns");
410                         else if (ns.Length == 0)
411                                 return String.Empty;
412                         else if (ns == "http://www.w3.org/2000/xmlns/")
413                                 return "xmlns";
414                         else if (ns == "http://www.w3.org/XML/1998/namespace")
415                                 return "xml";
416                         return null;
417                 }
418
419                 public override void WriteStartDocument ()
420                 {
421                         CheckState ();
422                 }
423
424                 public override void WriteStartDocument (bool standalone)
425                 {
426                         CheckState ();
427                 }
428
429                 public override void WriteEndDocument ()
430                 {
431                         CheckState ();
432                 }
433
434                 #endregion
435
436                 #region unsupported operations
437
438                 public override void WriteDocType (string name, string pubid, string sysid, string intSubset)
439                 {
440                         CheckState ();
441
442                         throw new NotSupportedException ("This XmlDictionaryWriter does not support writing doctype declaration");
443                 }
444
445                 public override void WriteComment (string text)
446                 {
447                         CheckState ();
448
449                         throw new NotSupportedException ("This XmlDictionaryWriter does not support writing comment");
450                 }
451
452                 public override void WriteEntityRef (string text)
453                 {
454                         CheckState ();
455
456                         throw new NotSupportedException ("This XmlDictionaryWriter does not support writing entity reference");
457                 }
458
459                 public override void WriteProcessingInstruction (string target, string data)
460                 {
461                         CheckState ();
462
463                         if (String.Compare (target, "xml", StringComparison.OrdinalIgnoreCase) != 0)
464                                 throw new ArgumentException ("This XmlDictionaryWriter does not support writing processing instruction");
465                 }
466
467                 #endregion
468
469                 #region WriteString() variants
470
471                 public override void WriteRaw (string text)
472                 {
473                         WriteString (text);
474                 }
475
476                 public override void WriteRaw (char [] chars, int start, int length)
477                 {
478                         WriteChars (chars, start, length);
479                 }
480
481                 public override void WriteCData (string text)
482                 {
483                         WriteString (text);
484                 }
485
486                 public override void WriteCharEntity (char entity)
487                 {
488                         WriteString (entity.ToString ());
489                 }
490
491                 public override void WriteChars (char [] chars, int start, int length)
492                 {
493                         WriteString (new string (chars, start, length));
494                 }
495
496                 public override void WriteSurrogateCharEntity (char high, char low)
497                 {
498                         WriteChars (new char [] {high, low}, 0, 2);
499                 }
500
501                 public override void WriteBase64 (byte [] bytes, int start, int length)
502                 {
503                         WriteString (Convert.ToBase64String (bytes, start, length));
504                 }
505
506                 public override void WriteWhitespace (string text)
507                 {
508                         if (text == null)
509                                 throw new ArgumentNullException ("text");
510                         for (int i = 0; i < text.Length; i++) {
511                                 if (text [i] != ' ') {
512                                         for (int j = i; j < text.Length; j++) {
513                                                 switch (text [j]) {
514                                                 case '\t':
515                                                 case ' ':
516                                                 case '\n':
517                                                 case '\r':
518                                                         continue;
519                                                 default:
520                                                         throw new ArgumentException (String.Format ("WriteWhitespace() does not accept non-whitespace character '{0}'", text [j]));
521                                                 }
522                                         }
523                                         break;
524                                 }
525                         }
526                         WriteString (text);
527                 }
528
529                 char [] char_buf = new char [1];
530                 void OutputAsciiChar (char c)
531                 {
532                         if (is_ascii_single)
533                                 output.WriteByte ((byte) c);
534                         else {
535                                 char_buf [0] = c;
536                                 int size = encoding.GetBytes (char_buf, 0, 1, encbuf, 0);
537                                 output.Write (encbuf, 0, size);
538                         }
539                 }
540
541                 void OutputString (string s)
542                 {
543                         int size = encoding.GetByteCount (s);
544                         if (encbuf.Length < size)
545                                 encbuf = new byte [size];
546                         size = encoding.GetBytes (s, 0, s.Length, encbuf, 0);
547                         output.Write (encbuf, 0, size);
548                 }
549
550                 #endregion
551         }
552 }