2010-06-03 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.ServiceModel.Web / System.Runtime.Serialization.Json / JsonReader.cs
blobba713c349876fa02edbdd84361810b512eaaf19a
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.
28 using System;
29 using System.Collections.Generic;
30 using System.Globalization;
31 using System.IO;
32 using System.Text;
33 using System.Xml;
35 namespace System.Runtime.Serialization.Json
37 class PushbackReader : StreamReader
39 Stack<int> pushback;
41 public PushbackReader (Stream stream, Encoding encoding) : base (stream, encoding)
43 pushback = new Stack<int>();
46 public PushbackReader (Stream stream) : base (stream, true)
48 pushback = new Stack<int>();
51 public override void Close ()
53 pushback.Clear ();
56 public override int Peek ()
58 if (pushback.Count > 0) {
59 return pushback.Peek ();
61 else {
62 return base.Peek ();
66 public override int Read ()
68 if (pushback.Count > 0) {
69 return pushback.Pop ();
71 else {
72 return base.Read ();
76 public void Pushback (int ch)
78 pushback.Push (ch);
82 // FIXME: quotas check
83 class JsonReader : XmlDictionaryReader, IXmlJsonReaderInitializer, IXmlLineInfo
85 class ElementInfo
87 public readonly string Name;
88 public readonly string Type;
89 public bool HasContent;
91 public ElementInfo (string name, string type)
93 this.Name = name;
94 this.Type = type;
98 enum AttributeState
100 None,
101 Type,
102 TypeValue,
103 RuntimeType,
104 RuntimeTypeValue
107 PushbackReader reader;
108 XmlDictionaryReaderQuotas quotas;
109 OnXmlDictionaryReaderClose on_close;
110 XmlNameTable name_table = new NameTable ();
112 XmlNodeType current_node;
113 AttributeState attr_state;
114 string simple_value;
115 string next_element;
116 string current_runtime_type, next_object_content_name;
117 ReadState read_state = ReadState.Initial;
118 bool content_stored;
119 bool finished;
120 Stack<ElementInfo> elements = new Stack<ElementInfo> ();
122 int line = 1, column = 0;
124 // Constructors
126 public JsonReader (byte [] buffer, int offset, int count, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
128 SetInput (buffer, offset, count, encoding, quotas, onClose);
131 public JsonReader (Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
133 SetInput (stream, encoding, quotas, onClose);
136 internal bool LameSilverlightLiteralParser { get; set; }
138 // IXmlLineInfo
140 public bool HasLineInfo ()
142 return true;
145 public int LineNumber {
146 get { return line; }
149 public int LinePosition {
150 get { return column; }
153 // IXmlJsonReaderInitializer
155 public void SetInput (byte [] buffer, int offset, int count, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
157 SetInput (new MemoryStream (buffer, offset, count), encoding, quotas, onClose);
160 public void SetInput (Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
162 if (encoding != null)
163 reader = new PushbackReader (stream, encoding);
164 else
165 reader = new PushbackReader (stream);
166 if (quotas == null)
167 throw new ArgumentNullException ("quotas");
168 this.quotas = quotas;
169 this.on_close = onClose;
172 // XmlDictionaryReader
174 public override int AttributeCount {
175 get { return current_node != XmlNodeType.Element ? 0 : current_runtime_type != null ? 2 : 1; }
178 public override string BaseURI {
179 get { return String.Empty; }
182 public override int Depth {
183 get {
184 int mod = 0;
185 switch (attr_state) {
186 case AttributeState.Type:
187 case AttributeState.RuntimeType:
188 mod++;
189 break;
190 case AttributeState.TypeValue:
191 case AttributeState.RuntimeTypeValue:
192 mod += 2;
193 break;
194 case AttributeState.None:
195 if (NodeType == XmlNodeType.Text)
196 mod++;
197 break;
199 return read_state != ReadState.Interactive ? 0 : elements.Count - 1 + mod;
203 public override bool EOF {
204 get {
205 switch (read_state) {
206 case ReadState.Closed:
207 case ReadState.EndOfFile:
208 return true;
209 default:
210 return false;
215 public override bool HasValue {
216 get {
217 switch (NodeType) {
218 case XmlNodeType.Attribute:
219 case XmlNodeType.Text:
220 return true;
221 default:
222 return false;
227 public override bool IsEmptyElement {
228 get { return false; }
231 public override string LocalName {
232 get {
233 switch (attr_state) {
234 case AttributeState.Type:
235 return "type";
236 case AttributeState.RuntimeType:
237 return "__type";
239 switch (NodeType) {
240 case XmlNodeType.Element:
241 case XmlNodeType.EndElement:
242 return elements.Peek ().Name;
243 default:
244 return String.Empty;
249 public override string NamespaceURI {
250 get { return String.Empty; }
253 public override XmlNameTable NameTable {
254 get { return name_table; }
257 public override XmlNodeType NodeType {
258 get {
259 switch (attr_state) {
260 case AttributeState.Type:
261 case AttributeState.RuntimeType:
262 return XmlNodeType.Attribute;
263 case AttributeState.TypeValue:
264 case AttributeState.RuntimeTypeValue:
265 return XmlNodeType.Text;
266 default:
267 return current_node;
272 public override string Prefix {
273 get { return String.Empty; }
276 public override ReadState ReadState {
277 get { return read_state; }
280 public override string Value {
281 get {
282 switch (attr_state) {
283 case AttributeState.Type:
284 case AttributeState.TypeValue:
285 return elements.Peek ().Type;
286 case AttributeState.RuntimeType:
287 case AttributeState.RuntimeTypeValue:
288 return current_runtime_type;
289 default:
290 return current_node == XmlNodeType.Text ? simple_value : String.Empty;
295 public override void Close ()
297 if (on_close != null) {
298 on_close (this);
299 on_close = null;
301 read_state = ReadState.Closed;
304 public override string GetAttribute (int index)
306 if (index == 0 && current_node == XmlNodeType.Element)
307 return elements.Peek ().Type;
308 else if (index == 1 && current_runtime_type != null)
309 return current_runtime_type;
310 throw new ArgumentOutOfRangeException ("index", "Index is must be either 0 or 1 when there is an explicit __type in the object, and only valid on an element on this XmlDictionaryReader");
313 public override string GetAttribute (string name)
315 if (current_node != XmlNodeType.Element)
316 return null;
317 switch (name) {
318 case "type":
319 return elements.Peek ().Type;
320 case "__type":
321 return current_runtime_type;
322 default:
323 return null;
327 public override string GetAttribute (string localName, string ns)
329 if (ns == String.Empty)
330 return GetAttribute (localName);
331 else
332 return null;
335 public override string LookupNamespace (string prefix)
337 if (prefix == null)
338 throw new ArgumentNullException ("prefix");
339 else if (prefix.Length == 0)
340 return String.Empty;
341 return null;
344 public override bool MoveToAttribute (string name)
346 if (current_node != XmlNodeType.Element)
347 return false;
348 switch (name) {
349 case "type":
350 attr_state = AttributeState.Type;
351 return true;
352 case "__type":
353 if (current_runtime_type == null)
354 return false;
355 attr_state = AttributeState.RuntimeType;
356 return true;
357 default:
358 return false;
362 public override bool MoveToAttribute (string localName, string ns)
364 if (ns != String.Empty)
365 return false;
366 return MoveToAttribute (localName);
369 public override bool MoveToElement ()
371 if (attr_state == AttributeState.None)
372 return false;
373 attr_state = AttributeState.None;
374 return true;
377 public override bool MoveToFirstAttribute ()
379 if (current_node != XmlNodeType.Element)
380 return false;
381 attr_state = AttributeState.Type;
382 return true;
385 public override bool MoveToNextAttribute ()
387 if (attr_state == AttributeState.None)
388 return MoveToFirstAttribute ();
389 else
390 return MoveToAttribute ("__type");
393 public override bool ReadAttributeValue ()
395 switch (attr_state) {
396 case AttributeState.Type:
397 attr_state = AttributeState.TypeValue;
398 return true;
399 case AttributeState.RuntimeType:
400 attr_state = AttributeState.RuntimeTypeValue;
401 return true;
403 return false;
406 public override void ResolveEntity ()
408 throw new NotSupportedException ();
411 public override bool Read ()
413 switch (read_state) {
414 case ReadState.EndOfFile:
415 case ReadState.Closed:
416 case ReadState.Error:
417 return false;
418 case ReadState.Initial:
419 read_state = ReadState.Interactive;
420 next_element = "root";
421 current_node = XmlNodeType.Element;
422 break;
425 if (content_stored) {
426 if (current_node == XmlNodeType.Element) {
427 if (elements.Peek ().Type == "null") {
428 // since null is not consumed as text content, it skips Text state.
429 current_node = XmlNodeType.EndElement;
430 content_stored = false;
432 else
433 current_node = XmlNodeType.Text;
434 return true;
435 } else if (current_node == XmlNodeType.Text) {
436 current_node = XmlNodeType.EndElement;
437 content_stored = false;
438 return true;
441 else if (current_node == XmlNodeType.EndElement) {
442 // clear EndElement state
443 elements.Pop ();
444 if (elements.Count > 0)
445 elements.Peek ().HasContent = true;
446 else
447 finished = true;
450 SkipWhitespaces ();
452 attr_state = AttributeState.None;
453 // Default. May be overriden only as EndElement or None.
454 current_node = XmlNodeType.Element;
456 if (!ReadContent (false))
457 return false;
458 if (finished)
459 throw XmlError ("Multiple top-level content is not allowed");
460 return true;
463 bool TryReadString (string str)
465 for (int i = 0; i < str.Length; i ++) {
466 int ch = ReadChar ();
467 if (ch != str[i]) {
468 for (int j = i; j >= 0; j--)
469 PushbackChar (j);
470 return false;
474 return true;
477 bool ReadContent (bool objectValue)
479 int ch = ReadChar ();
480 if (ch < 0) {
481 ReadEndOfStream ();
482 return false;
485 bool itemMustFollow = false;
487 if (!objectValue && elements.Count > 0 && elements.Peek ().HasContent) {
488 if (ch == ',') {
489 switch (elements.Peek ().Type) {
490 case "object":
491 case "array":
492 SkipWhitespaces ();
493 ch = ReadChar ();
494 itemMustFollow = true;
495 break;
498 else if (ch != '}' && ch != ']')
499 throw XmlError ("Comma is required unless an array or object is at the end");
502 if (elements.Count > 0 && elements.Peek ().Type == "array")
503 next_element = "item";
504 else if (next_object_content_name != null) {
505 next_element = next_object_content_name;
506 next_object_content_name = null;
507 if (ch != ':')
508 throw XmlError ("':' is expected after a name of an object content");
509 SkipWhitespaces ();
510 ReadContent (true);
511 return true;
514 switch (ch) {
515 case '{':
516 ReadStartObject ();
517 return true;
518 case '[':
519 ReadStartArray ();
520 return true;
521 case '}':
522 if (itemMustFollow)
523 throw XmlError ("Invalid comma before an end of object");
524 if (objectValue)
525 throw XmlError ("Invalid end of object as an object content");
526 ReadEndObject ();
527 return true;
528 case ']':
529 if (itemMustFollow)
530 throw XmlError ("Invalid comma before an end of array");
531 if (objectValue)
532 throw XmlError ("Invalid end of array as an object content");
533 ReadEndArray ();
534 return true;
535 case '"':
536 bool lame = LameSilverlightLiteralParser && ch != '"';
537 string s = ReadStringLiteral (lame);
538 if (!objectValue && elements.Count > 0 && elements.Peek ().Type == "object") {
539 next_element = s;
540 SkipWhitespaces ();
541 if (!lame)
542 Expect (':');
543 SkipWhitespaces ();
544 ReadContent (true);
546 else
547 ReadAsSimpleContent ("string", s);
548 return true;
549 case '-':
550 ReadNumber (ch);
551 return true;
552 case 'n':
553 if (TryReadString("ull")) {
554 ReadAsSimpleContent ("null", "null");
555 return true;
557 else {
558 // the pushback for 'n' is taken care of by the
559 // default case if we're in lame silverlight literal
560 // mode
561 goto default;
563 case 't':
564 if (TryReadString ("rue")) {
565 ReadAsSimpleContent ("boolean", "true");
566 return true;
568 else {
569 // the pushback for 't' is taken care of by the
570 // default case if we're in lame silverlight literal
571 // mode
572 goto default;
574 case 'f':
575 if (TryReadString ("alse")) {
576 ReadAsSimpleContent ("boolean", "false");
577 return true;
579 else {
580 // the pushback for 'f' is taken care of by the
581 // default case if we're in lame silverlight literal
582 // mode
583 goto default;
585 default:
586 if ('0' <= ch && ch <= '9') {
587 ReadNumber (ch);
588 return true;
590 if (LameSilverlightLiteralParser) {
591 PushbackChar (ch);
592 goto case '"';
594 throw XmlError (String.Format ("Unexpected token: '{0}' ({1:X04})", (char) ch, (int) ch));
598 void ReadStartObject ()
600 ElementInfo ei = new ElementInfo (next_element, "object");
601 elements.Push (ei);
603 SkipWhitespaces ();
604 if (PeekChar () == '"') { // it isn't premise: the object might be empty
605 ReadChar ();
606 string s = ReadStringLiteral ();
607 if (s == "__type") {
608 SkipWhitespaces ();
609 Expect (':');
610 SkipWhitespaces ();
611 Expect ('"');
612 current_runtime_type = ReadStringLiteral ();
613 SkipWhitespaces ();
614 ei.HasContent = true;
616 else
617 next_object_content_name = s;
621 void ReadStartArray ()
623 elements.Push (new ElementInfo (next_element, "array"));
626 void ReadEndObject ()
628 if (elements.Count == 0 || elements.Peek ().Type != "object")
629 throw XmlError ("Unexpected end of object");
630 current_node = XmlNodeType.EndElement;
633 void ReadEndArray ()
635 if (elements.Count == 0 || elements.Peek ().Type != "array")
636 throw XmlError ("Unexpected end of array");
637 current_node = XmlNodeType.EndElement;
640 void ReadEndOfStream ()
642 if (elements.Count > 0)
643 throw XmlError (String.Format ("{0} missing end of arrays or objects", elements.Count));
644 read_state = ReadState.EndOfFile;
645 current_node = XmlNodeType.None;
648 void ReadAsSimpleContent (string type, string value)
650 elements.Push (new ElementInfo (next_element, type));
651 simple_value = value;
652 content_stored = true;
655 void ReadNumber (int ch)
657 elements.Push (new ElementInfo (next_element, "number"));
658 content_stored = true;
660 int init = ch;
661 int prev;
662 bool floating = false, exp = false;
664 StringBuilder sb = new StringBuilder ();
665 bool cont = true;
666 do {
667 sb.Append ((char) ch);
668 prev = ch;
669 ch = ReadChar ();
671 if (prev == '-' && !IsNumber (ch)) // neither '.', '-' or '+' nor anything else is valid
672 throw XmlError ("Invalid JSON number");
674 switch (ch) {
675 case 'e':
676 case 'E':
677 if (exp)
678 throw XmlError ("Invalid JSON number token. Either 'E' or 'e' must not occur more than once");
679 if (!IsNumber (prev))
680 throw XmlError ("Invalid JSON number token. only a number is valid before 'E' or 'e'");
681 exp = true;
682 break;
683 case '.':
684 if (floating)
685 throw XmlError ("Invalid JSON number token. '.' must not occur twice");
686 if (exp)
687 throw XmlError ("Invalid JSON number token. '.' must not occur after 'E' or 'e'");
688 floating = true;
689 break;
690 case '+':
691 case '-':
692 if (prev == 'E' || prev == 'e')
693 break;
694 goto default;
695 default:
696 if (!IsNumber (ch)) {
697 PushbackChar (ch);
698 cont = false;
700 break;
702 } while (cont);
704 if (!IsNumber (prev)) // only number is valid at the end
705 throw XmlError ("Invalid JSON number");
707 simple_value = sb.ToString ();
709 if (init == '0' && !floating && !exp && simple_value != "0")
710 throw XmlError ("Invalid JSON number");
713 bool IsNumber (int c)
715 return '0' <= c && c <= '9';
718 StringBuilder vb = new StringBuilder ();
720 string ReadStringLiteral ()
722 return ReadStringLiteral (false);
725 string ReadStringLiteral (bool endWithColon)
727 vb.Length = 0;
728 while (true) {
729 int c = ReadChar ();
730 if (c < 0)
731 throw XmlError ("JSON string is not closed");
732 if (c == '"' && !endWithColon)
733 return vb.ToString ();
734 else if (c == ':' && endWithColon)
735 return vb.ToString ();
736 else if (c != '\\') {
737 vb.Append ((char) c);
738 continue;
741 // escaped expression
742 c = ReadChar ();
743 if (c < 0)
744 throw XmlError ("Invalid JSON string literal; incomplete escape sequence");
745 switch (c) {
746 case '"':
747 case '\\':
748 case '/':
749 vb.Append ((char) c);
750 break;
751 case 'b':
752 vb.Append ('\x8');
753 break;
754 case 'f':
755 vb.Append ('\f');
756 break;
757 case 'n':
758 vb.Append ('\n');
759 break;
760 case 'r':
761 vb.Append ('\r');
762 break;
763 case 't':
764 vb.Append ('\t');
765 break;
766 case 'u':
767 ushort cp = 0;
768 for (int i = 0; i < 4; i++) {
769 if ((c = ReadChar ()) < 0)
770 throw XmlError ("Incomplete unicode character escape literal");
771 cp *= 16;
772 if ('0' <= c && c <= '9')
773 cp += (ushort) (c - '0');
774 if ('A' <= c && c <= 'F')
775 cp += (ushort) (c - 'A' + 10);
776 if ('a' <= c && c <= 'f')
777 cp += (ushort) (c - 'a' + 10);
779 vb.Append ((char) cp);
780 break;
781 default:
782 throw XmlError ("Invalid JSON string literal; unexpected escape character");
787 int PeekChar ()
789 return reader.Peek ();
792 int ReadChar ()
794 int v = reader.Read ();
795 if (v == '\n') {
796 line++;
797 column = 0;
799 else
800 column++;
801 return v;
804 void PushbackChar (int ch)
806 // FIXME handle lines (and columns? ugh, how?)
807 reader.Pushback (ch);
810 void SkipWhitespaces ()
812 do {
813 switch (PeekChar ()) {
814 case ' ':
815 case '\t':
816 case '\r':
817 case '\n':
818 ReadChar ();
819 continue;
820 default:
821 return;
823 } while (true);
826 void Expect (char c)
828 int v = ReadChar ();
829 if (v < 0)
830 throw XmlError (String.Format ("Expected '{0}' but got EOF", c));
831 if (v != c)
832 throw XmlError (String.Format ("Expected '{0}' but got '{1}'", c, (char) v));
835 Exception XmlError (string s)
837 return new XmlException (String.Format ("{0} ({1},{2})", s, line, column));