5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
29 using System.Collections.Generic;
30 using System.Globalization;
35 namespace System.Runtime.Serialization.Json
37 class PushbackReader : StreamReader
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 ()
56 public override int Peek ()
58 if (pushback.Count > 0) {
59 return pushback.Peek ();
66 public override int Read ()
68 if (pushback.Count > 0) {
69 return pushback.Pop ();
76 public void Pushback (int ch)
82 // FIXME: quotas check
83 class JsonReader : XmlDictionaryReader, IXmlJsonReaderInitializer, IXmlLineInfo
87 public readonly string Name;
88 public readonly string Type;
89 public bool HasContent;
91 public ElementInfo (string name, string type)
107 PushbackReader reader;
108 XmlDictionaryReaderQuotas quotas;
109 OnXmlDictionaryReaderClose on_close;
110 XmlNameTable name_table = new NameTable ();
112 XmlNodeType current_node;
113 AttributeState attr_state;
116 string current_runtime_type, next_object_content_name;
117 ReadState read_state = ReadState.Initial;
120 Stack<ElementInfo> elements = new Stack<ElementInfo> ();
122 int line = 1, column = 0;
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; }
140 public bool HasLineInfo ()
145 public int LineNumber {
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);
165 reader = new PushbackReader (stream);
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 {
185 switch (attr_state) {
186 case AttributeState.Type:
187 case AttributeState.RuntimeType:
190 case AttributeState.TypeValue:
191 case AttributeState.RuntimeTypeValue:
194 case AttributeState.None:
195 if (NodeType == XmlNodeType.Text)
199 return read_state != ReadState.Interactive ? 0 : elements.Count - 1 + mod;
203 public override bool EOF {
205 switch (read_state) {
206 case ReadState.Closed:
207 case ReadState.EndOfFile:
215 public override bool HasValue {
218 case XmlNodeType.Attribute:
219 case XmlNodeType.Text:
227 public override bool IsEmptyElement {
228 get { return false; }
231 public override string LocalName {
233 switch (attr_state) {
234 case AttributeState.Type:
236 case AttributeState.RuntimeType:
240 case XmlNodeType.Element:
241 case XmlNodeType.EndElement:
242 return elements.Peek ().Name;
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 {
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;
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 {
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;
290 return current_node == XmlNodeType.Text ? simple_value : String.Empty;
295 public override void Close ()
297 if (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)
319 return elements.Peek ().Type;
321 return current_runtime_type;
327 public override string GetAttribute (string localName, string ns)
329 if (ns == String.Empty)
330 return GetAttribute (localName);
335 public override string LookupNamespace (string prefix)
338 throw new ArgumentNullException ("prefix");
339 else if (prefix.Length == 0)
344 public override bool MoveToAttribute (string name)
346 if (current_node != XmlNodeType.Element)
350 attr_state = AttributeState.Type;
353 if (current_runtime_type == null)
355 attr_state = AttributeState.RuntimeType;
362 public override bool MoveToAttribute (string localName, string ns)
364 if (ns != String.Empty)
366 return MoveToAttribute (localName);
369 public override bool MoveToElement ()
371 if (attr_state == AttributeState.None)
373 attr_state = AttributeState.None;
377 public override bool MoveToFirstAttribute ()
379 if (current_node != XmlNodeType.Element)
381 attr_state = AttributeState.Type;
385 public override bool MoveToNextAttribute ()
387 if (attr_state == AttributeState.None)
388 return MoveToFirstAttribute ();
390 return MoveToAttribute ("__type");
393 public override bool ReadAttributeValue ()
395 switch (attr_state) {
396 case AttributeState.Type:
397 attr_state = AttributeState.TypeValue;
399 case AttributeState.RuntimeType:
400 attr_state = AttributeState.RuntimeTypeValue;
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:
418 case ReadState.Initial:
419 read_state = ReadState.Interactive;
420 next_element = "root";
421 current_node = XmlNodeType.Element;
427 if (content_stored) {
428 if (current_node == XmlNodeType.Element) {
429 if (elements.Peek ().Type == "null") {
430 // since null is not consumed as text content, it skips Text state.
431 current_node = XmlNodeType.EndElement;
432 content_stored = false;
435 current_node = XmlNodeType.Text;
437 } else if (current_node == XmlNodeType.Text) {
438 current_node = XmlNodeType.EndElement;
439 content_stored = false;
443 else if (current_node == XmlNodeType.EndElement) {
444 // clear EndElement state
446 if (elements.Count > 0)
447 elements.Peek ().HasContent = true;
454 attr_state = AttributeState.None;
455 // Default. May be overriden only as EndElement or None.
456 current_node = XmlNodeType.Element;
458 if (!ReadContent (false))
461 throw XmlError ("Multiple top-level content is not allowed");
465 bool TryReadString (string str)
467 for (int i = 0; i < str.Length; i ++) {
468 int ch = ReadChar ();
470 for (int j = i; j >= 0; j--)
479 bool ReadContent (bool objectValue)
481 int ch = ReadChar ();
487 bool itemMustFollow = false;
489 if (!objectValue && elements.Count > 0 && elements.Peek ().HasContent) {
491 switch (elements.Peek ().Type) {
496 itemMustFollow = true;
500 else if (ch != '}' && ch != ']')
501 throw XmlError ("Comma is required unless an array or object is at the end");
504 if (elements.Count > 0 && elements.Peek ().Type == "array")
505 next_element = "item";
506 else if (next_object_content_name != null) {
507 next_element = next_object_content_name;
508 next_object_content_name = null;
510 throw XmlError ("':' is expected after a name of an object content");
525 throw XmlError ("Invalid comma before an end of object");
527 throw XmlError ("Invalid end of object as an object content");
532 throw XmlError ("Invalid comma before an end of array");
534 throw XmlError ("Invalid end of array as an object content");
538 bool lame = LameSilverlightLiteralParser && ch != '"';
539 string s = ReadStringLiteral (lame);
540 if (!objectValue && elements.Count > 0 && elements.Peek ().Type == "object") {
549 ReadAsSimpleContent ("string", s);
555 if (TryReadString("ull")) {
556 ReadAsSimpleContent ("null", "null");
560 // the pushback for 'n' is taken care of by the
561 // default case if we're in lame silverlight literal
566 if (TryReadString ("rue")) {
567 ReadAsSimpleContent ("boolean", "true");
571 // the pushback for 't' is taken care of by the
572 // default case if we're in lame silverlight literal
577 if (TryReadString ("alse")) {
578 ReadAsSimpleContent ("boolean", "false");
582 // the pushback for 'f' is taken care of by the
583 // default case if we're in lame silverlight literal
588 if ('0' <= ch && ch <= '9') {
592 if (LameSilverlightLiteralParser) {
596 throw XmlError (String.Format ("Unexpected token: '{0}' ({1:X04})", (char) ch, (int) ch));
600 void ReadStartObject ()
602 ElementInfo ei = new ElementInfo (next_element, "object");
606 if (PeekChar () == '"') { // it isn't premise: the object might be empty
608 string s = ReadStringLiteral ();
614 current_runtime_type = ReadStringLiteral ();
616 ei.HasContent = true;
619 next_object_content_name = s;
623 void ReadStartArray ()
625 elements.Push (new ElementInfo (next_element, "array"));
628 void ReadEndObject ()
630 if (elements.Count == 0 || elements.Peek ().Type != "object")
631 throw XmlError ("Unexpected end of object");
632 current_node = XmlNodeType.EndElement;
637 if (elements.Count == 0 || elements.Peek ().Type != "array")
638 throw XmlError ("Unexpected end of array");
639 current_node = XmlNodeType.EndElement;
642 void ReadEndOfStream ()
644 if (elements.Count > 0)
645 throw XmlError (String.Format ("{0} missing end of arrays or objects", elements.Count));
646 read_state = ReadState.EndOfFile;
647 current_node = XmlNodeType.None;
650 void ReadAsSimpleContent (string type, string value)
652 elements.Push (new ElementInfo (next_element, type));
653 simple_value = value;
654 content_stored = true;
657 void ReadNumber (int ch)
659 elements.Push (new ElementInfo (next_element, "number"));
660 content_stored = true;
664 bool floating = false, exp = false;
666 StringBuilder sb = new StringBuilder ();
669 sb.Append ((char) ch);
673 if (prev == '-' && !IsNumber (ch)) // neither '.', '-' or '+' nor anything else is valid
674 throw XmlError ("Invalid JSON number");
680 throw XmlError ("Invalid JSON number token. Either 'E' or 'e' must not occur more than once");
681 if (!IsNumber (prev))
682 throw XmlError ("Invalid JSON number token. only a number is valid before 'E' or 'e'");
687 throw XmlError ("Invalid JSON number token. '.' must not occur twice");
689 throw XmlError ("Invalid JSON number token. '.' must not occur after 'E' or 'e'");
694 if (prev == 'E' || prev == 'e')
698 if (!IsNumber (ch)) {
706 if (!IsNumber (prev)) // only number is valid at the end
707 throw XmlError ("Invalid JSON number");
709 simple_value = sb.ToString ();
711 if (init == '0' && !floating && !exp && simple_value != "0")
712 throw XmlError ("Invalid JSON number");
715 bool IsNumber (int c)
717 return '0' <= c && c <= '9';
720 StringBuilder vb = new StringBuilder ();
722 string ReadStringLiteral ()
724 return ReadStringLiteral (false);
727 string ReadStringLiteral (bool endWithColon)
733 throw XmlError ("JSON string is not closed");
734 if (c == '"' && !endWithColon)
735 return vb.ToString ();
736 else if (c == ':' && endWithColon)
737 return vb.ToString ();
738 else if (c != '\\') {
739 vb.Append ((char) c);
743 // escaped expression
746 throw XmlError ("Invalid JSON string literal; incomplete escape sequence");
751 vb.Append ((char) c);
770 for (int i = 0; i < 4; i++) {
771 if ((c = ReadChar ()) < 0)
772 throw XmlError ("Incomplete unicode character escape literal");
774 if ('0' <= c && c <= '9')
775 cp += (ushort) (c - '0');
776 if ('A' <= c && c <= 'F')
777 cp += (ushort) (c - 'A' + 10);
778 if ('a' <= c && c <= 'f')
779 cp += (ushort) (c - 'a' + 10);
781 vb.Append ((char) cp);
784 throw XmlError ("Invalid JSON string literal; unexpected escape character");
791 return reader.Peek ();
796 int v = reader.Read ();
806 void PushbackChar (int ch)
808 // FIXME handle lines (and columns? ugh, how?)
809 reader.Pushback (ch);
812 void SkipWhitespaces ()
815 switch (PeekChar ()) {
832 throw XmlError (String.Format ("Expected '{0}' but got EOF", c));
834 throw XmlError (String.Format ("Expected '{0}' but got '{1}'", c, (char) v));
837 Exception XmlError (string s)
839 return new XmlException (String.Format ("{0} ({1},{2})", s, line, column));