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
;
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;
433 current_node
= XmlNodeType
.Text
;
435 } else if (current_node
== XmlNodeType
.Text
) {
436 current_node
= XmlNodeType
.EndElement
;
437 content_stored
= false;
441 else if (current_node
== XmlNodeType
.EndElement
) {
442 // clear EndElement state
444 if (elements
.Count
> 0)
445 elements
.Peek ().HasContent
= true;
452 attr_state
= AttributeState
.None
;
453 // Default. May be overriden only as EndElement or None.
454 current_node
= XmlNodeType
.Element
;
456 if (!ReadContent (false))
459 throw XmlError ("Multiple top-level content is not allowed");
463 bool TryReadString (string str
)
465 for (int i
= 0; i
< str
.Length
; i
++) {
466 int ch
= ReadChar ();
468 for (int j
= i
; j
>= 0; j
--)
477 bool ReadContent (bool objectValue
)
479 int ch
= ReadChar ();
485 bool itemMustFollow
= false;
487 if (!objectValue
&& elements
.Count
> 0 && elements
.Peek ().HasContent
) {
489 switch (elements
.Peek ().Type
) {
494 itemMustFollow
= true;
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;
508 throw XmlError ("':' is expected after a name of an object content");
523 throw XmlError ("Invalid comma before an end of object");
525 throw XmlError ("Invalid end of object as an object content");
530 throw XmlError ("Invalid comma before an end of array");
532 throw XmlError ("Invalid end of array as an object content");
536 bool lame
= LameSilverlightLiteralParser
&& ch
!= '"';
537 string s
= ReadStringLiteral (lame
);
538 if (!objectValue
&& elements
.Count
> 0 && elements
.Peek ().Type
== "object") {
547 ReadAsSimpleContent ("string", s
);
553 if (TryReadString("ull")) {
554 ReadAsSimpleContent ("null", "null");
558 // the pushback for 'n' is taken care of by the
559 // default case if we're in lame silverlight literal
564 if (TryReadString ("rue")) {
565 ReadAsSimpleContent ("boolean", "true");
569 // the pushback for 't' is taken care of by the
570 // default case if we're in lame silverlight literal
575 if (TryReadString ("alse")) {
576 ReadAsSimpleContent ("boolean", "false");
580 // the pushback for 'f' is taken care of by the
581 // default case if we're in lame silverlight literal
586 if ('0' <= ch
&& ch
<= '9') {
590 if (LameSilverlightLiteralParser
) {
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");
604 if (PeekChar () == '"') { // it isn't premise: the object might be empty
606 string s
= ReadStringLiteral ();
612 current_runtime_type
= ReadStringLiteral ();
614 ei
.HasContent
= true;
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
;
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;
662 bool floating
= false, exp
= false;
664 StringBuilder sb
= new StringBuilder ();
667 sb
.Append ((char) ch
);
671 if (prev
== '-' && !IsNumber (ch
)) // neither '.', '-' or '+' nor anything else is valid
672 throw XmlError ("Invalid JSON number");
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'");
685 throw XmlError ("Invalid JSON number token. '.' must not occur twice");
687 throw XmlError ("Invalid JSON number token. '.' must not occur after 'E' or 'e'");
692 if (prev
== 'E' || prev
== 'e')
696 if (!IsNumber (ch
)) {
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
)
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
);
741 // escaped expression
744 throw XmlError ("Invalid JSON string literal; incomplete escape sequence");
749 vb
.Append ((char) c
);
768 for (int i
= 0; i
< 4; i
++) {
769 if ((c
= ReadChar ()) < 0)
770 throw XmlError ("Incomplete unicode character escape literal");
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
);
782 throw XmlError ("Invalid JSON string literal; unexpected escape character");
789 return reader
.Peek ();
794 int v
= reader
.Read ();
804 void PushbackChar (int ch
)
806 // FIXME handle lines (and columns? ugh, how?)
807 reader
.Pushback (ch
);
810 void SkipWhitespaces ()
813 switch (PeekChar ()) {
830 throw XmlError (String
.Format ("Expected '{0}' but got EOF", 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
));