//
-// JsonWriter.cs
+// JsonReader.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
-// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2007-2011 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
namespace System.Runtime.Serialization.Json
{
+ // It is a subset of XmlInputStream from System.XML.
+ class EncodingDetecingInputStream : Stream
+ {
+ internal static readonly Encoding StrictUTF8, Strict1234UTF32, StrictBigEndianUTF16, StrictUTF16;
+
+ static EncodingDetecingInputStream ()
+ {
+ StrictUTF8 = new UTF8Encoding (false, true);
+ Strict1234UTF32 = new UTF32Encoding (true, false, true);
+ StrictBigEndianUTF16 = new UnicodeEncoding (true, false, true);
+ StrictUTF16 = new UnicodeEncoding (false, false, true);
+ }
+
+ Encoding enc;
+ Stream stream;
+ byte[] buffer;
+ int bufLength;
+ int bufPos;
+
+ static XmlException encodingException = new XmlException ("invalid encoding specification.");
+
+ public EncodingDetecingInputStream (Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException ("stream");
+ Initialize (stream);
+ }
+
+ private void Initialize (Stream stream)
+ {
+ buffer = new byte [6];
+ this.stream = stream;
+ enc = StrictUTF8; // Default to UTF8 if we can't guess it
+ bufLength = stream.Read (buffer, 0, buffer.Length);
+ if (bufLength == -1 || bufLength == 0) {
+ return;
+ }
+
+ int c = ReadByteSpecial ();
+ switch (c) {
+ case 0xFF:
+ c = ReadByteSpecial ();
+ if (c == 0xFE) {
+ // BOM-ed little endian utf-16
+ enc = Encoding.Unicode;
+ } else {
+ // It doesn't start from "<?xml" then its encoding is utf-8
+ bufPos = 0;
+ }
+ break;
+ case 0xFE:
+ c = ReadByteSpecial ();
+ if (c == 0xFF) {
+ // BOM-ed big endian utf-16
+ enc = Encoding.BigEndianUnicode;
+ return;
+ } else {
+ // It doesn't start from "<?xml" then its encoding is utf-8
+ bufPos = 0;
+ }
+ break;
+ case 0xEF:
+ c = ReadByteSpecial ();
+ if (c == 0xBB) {
+ c = ReadByteSpecial ();
+ if (c != 0xBF) {
+ bufPos = 0;
+ }
+ } else {
+ buffer [--bufPos] = 0xEF;
+ }
+ break;
+ case 0:
+ // It could still be 1234/2143/3412 variants of UTF32, but only 1234 version is available on .NET.
+ c = ReadByteSpecial ();
+ if (c == 0)
+ enc = Strict1234UTF32;
+ else
+ enc = StrictBigEndianUTF16;
+ break;
+ default:
+ c = ReadByteSpecial ();
+ if (c == 0)
+ enc = StrictUTF16;
+ bufPos = 0;
+ break;
+ }
+ }
+
+ // Just like readbyte, but grows the buffer too.
+ int ReadByteSpecial ()
+ {
+ if (bufLength > bufPos)
+ return buffer [bufPos++];
+
+ byte [] newbuf = new byte [buffer.Length * 2];
+ Buffer.BlockCopy (buffer, 0, newbuf, 0, bufLength);
+ int nbytes = stream.Read (newbuf, bufLength, buffer.Length);
+ if (nbytes == -1 || nbytes == 0)
+ return -1;
+
+ bufLength += nbytes;
+ buffer = newbuf;
+ return buffer [bufPos++];
+ }
+
+ public Encoding ActualEncoding {
+ get { return enc; }
+ }
+
+ #region Public Overrides
+ public override bool CanRead {
+ get {
+ if (bufLength > bufPos)
+ return true;
+ else
+ return stream.CanRead;
+ }
+ }
+
+ // FIXME: It should support base stream's CanSeek.
+ public override bool CanSeek {
+ get { return false; } // stream.CanSeek; }
+ }
+
+ public override bool CanWrite {
+ get { return false; }
+ }
+
+ public override long Length {
+ get {
+ return stream.Length;
+ }
+ }
+
+ public override long Position {
+ get {
+ return stream.Position - bufLength + bufPos;
+ }
+ set {
+ if(value < bufLength)
+ bufPos = (int)value;
+ else
+ stream.Position = value - bufLength;
+ }
+ }
+
+ public override void Close ()
+ {
+ stream.Close ();
+ }
+
+ public override void Flush ()
+ {
+ stream.Flush ();
+ }
+
+ public override int Read (byte[] buffer, int offset, int count)
+ {
+ int ret;
+ if (count <= bufLength - bufPos) { // all from buffer
+ Buffer.BlockCopy (this.buffer, bufPos, buffer, offset, count);
+ bufPos += count;
+ ret = count;
+ } else {
+ int bufRest = bufLength - bufPos;
+ if (bufLength > bufPos) {
+ Buffer.BlockCopy (this.buffer, bufPos, buffer, offset, bufRest);
+ bufPos += bufRest;
+ }
+ ret = bufRest +
+ stream.Read (buffer, offset + bufRest, count - bufRest);
+ }
+ return ret;
+ }
+
+ public override int ReadByte ()
+ {
+ if (bufLength > bufPos) {
+ return buffer [bufPos++];
+ }
+ return stream.ReadByte ();
+ }
+
+ public override long Seek (long offset, System.IO.SeekOrigin origin)
+ {
+ int bufRest = bufLength - bufPos;
+ if (origin == SeekOrigin.Current)
+ if (offset < bufRest)
+ return buffer [bufPos + offset];
+ else
+ return stream.Seek (offset - bufRest, origin);
+ else
+ return stream.Seek (offset, origin);
+ }
+
+ public override void SetLength (long value)
+ {
+ stream.SetLength (value);
+ }
+
+ public override void Write (byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException ();
+ }
+ #endregion
+ }
+
+ class PushbackReader : StreamReader
+ {
+ Stack<int> pushback;
+
+ public PushbackReader (Stream stream, Encoding encoding) : base (stream, encoding)
+ {
+ pushback = new Stack<int>();
+ }
+
+ public PushbackReader (Stream stream) : this (new EncodingDetecingInputStream (stream))
+ {
+ }
+
+ public PushbackReader (EncodingDetecingInputStream stream) : this (stream, stream.ActualEncoding)
+ {
+ }
+
+ public override void Close ()
+ {
+ pushback.Clear ();
+ }
+
+ public override int Peek ()
+ {
+ if (pushback.Count > 0) {
+ return pushback.Peek ();
+ }
+ else {
+ return base.Peek ();
+ }
+ }
+
+ public override int Read ()
+ {
+ if (pushback.Count > 0) {
+ return pushback.Pop ();
+ }
+ else {
+ return base.Read ();
+ }
+ }
+
+ public void Pushback (int ch)
+ {
+ pushback.Push (ch);
+ }
+ }
+
// FIXME: quotas check
class JsonReader : XmlDictionaryReader, IXmlJsonReaderInitializer, IXmlLineInfo
{
RuntimeTypeValue
}
- TextReader reader;
+ PushbackReader reader;
XmlDictionaryReaderQuotas quotas;
OnXmlDictionaryReaderClose on_close;
XmlNameTable name_table = new NameTable ();
int line = 1, column = 0;
- int saved_char = -1;
-
// Constructors
public JsonReader (byte [] buffer, int offset, int count, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
public void SetInput (Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
{
- reader = new StreamReader (stream, encoding ?? Encoding.UTF8);
+ if (encoding != null)
+ reader = new PushbackReader (stream, encoding);
+ else
+ reader = new PushbackReader (stream);
if (quotas == null)
throw new ArgumentNullException ("quotas");
this.quotas = quotas;
break;
}
+ MoveToElement ();
+
if (content_stored) {
if (current_node == XmlNodeType.Element) {
if (elements.Peek ().Type == "null") {
return true;
}
+ bool TryReadString (string str)
+ {
+ for (int i = 0; i < str.Length; i ++) {
+ int ch = ReadChar ();
+ if (ch != str[i]) {
+ for (int j = i; j >= 0; j--)
+ PushbackChar (j);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
bool ReadContent (bool objectValue)
{
int ch = ReadChar ();
ReadNumber (ch);
return true;
case 'n':
- if (ReadChar () == 'u' &&
- ReadChar () == 'l' &&
- ReadChar () == 'l') {
- ReadAsSimpleContent ("null", "null");
+ if (TryReadString("ull")) {
+ ReadAsSimpleContent ("null", "null");
return true;
}
- goto default;
+ else {
+ // the pushback for 'n' is taken care of by the
+ // default case if we're in lame silverlight literal
+ // mode
+ goto default;
+ }
case 't':
- if (ReadChar () == 'r' &&
- ReadChar () == 'u' &&
- ReadChar () == 'e') {
- ReadAsSimpleContent ("boolean", "true");
+ if (TryReadString ("rue")) {
+ ReadAsSimpleContent ("boolean", "true");
return true;
}
- goto default;
+ else {
+ // the pushback for 't' is taken care of by the
+ // default case if we're in lame silverlight literal
+ // mode
+ goto default;
+ }
case 'f':
- if (ReadChar () == 'a' &&
- ReadChar () == 'l' &&
- ReadChar () == 's' &&
- ReadChar () == 'e') {
- ReadAsSimpleContent ("boolean", "false");
+ if (TryReadString ("alse")) {
+ ReadAsSimpleContent ("boolean", "false");
return true;
}
- goto default;
+ else {
+ // the pushback for 'f' is taken care of by the
+ // default case if we're in lame silverlight literal
+ // mode
+ goto default;
+ }
default:
if ('0' <= ch && ch <= '9') {
ReadNumber (ch);
return true;
}
- if (LameSilverlightLiteralParser)
+ if (LameSilverlightLiteralParser) {
+ PushbackChar (ch);
goto case '"';
+ }
throw XmlError (String.Format ("Unexpected token: '{0}' ({1:X04})", (char) ch, (int) ch));
}
}
throw XmlError ("Invalid JSON number token. '.' must not occur after 'E' or 'e'");
floating = true;
break;
+ case '+':
+ case '-':
+ if (prev == 'E' || prev == 'e')
+ break;
+ goto default;
default:
if (!IsNumber (ch)) {
- saved_char = ch;
+ PushbackChar (ch);
cont = false;
}
break;
int PeekChar ()
{
- if (saved_char < 0)
- saved_char = reader.Read ();
- return saved_char;
+ return reader.Peek ();
}
int ReadChar ()
{
- int v = saved_char >= 0 ? saved_char : reader.Read ();
- saved_char = -1;
+ int v = reader.Read ();
if (v == '\n') {
line++;
column = 0;
return v;
}
+ void PushbackChar (int ch)
+ {
+ // FIXME handle lines (and columns? ugh, how?)
+ reader.Pushback (ch);
+ }
+
void SkipWhitespaces ()
{
do {
{
return new XmlException (String.Format ("{0} ({1},{2})", s, line, column));
}
+
+ // This reads the current element and all its content as a string,
+ // with no processing done except for advancing the reader.
+ public override string ReadInnerXml ()
+ {
+
+ if (NodeType != XmlNodeType.Element)
+ return base.ReadInnerXml ();
+
+ StringBuilder sb = new StringBuilder ();
+ bool isobject = elements.Peek ().Type == "object";
+ char end = isobject ? '}' : ']';
+ char start = isobject ? '{' : '[';
+ int count = 1;
+
+ sb.Append (start);
+
+ // add the first child manually, it's already been read
+ // but hasn't been processed yet
+ if (isobject && !String.IsNullOrEmpty (next_object_content_name))
+ sb.Append ("\"" + next_object_content_name + "\"");
+
+ // keep reading until we hit the end marker, no processing is
+ // done on anything
+ do {
+ char c = (char)ReadChar ();
+ sb.Append (c);
+ if (c == start)
+ ++count;
+ else if (c == end)
+ --count;
+ } while (count > 0);
+
+ // Replace the content we've read with an empty object so it gets
+ // skipped on the following Read
+ reader.Pushback (end);
+ if (isobject) {
+ reader.Pushback ('"');
+ reader.Pushback ('"');
+ reader.Pushback (':');
+ }
+
+ // Skip the element
+ Read ();
+ return sb.ToString ();
+ }
}
}