1100 lines
25 KiB
C#
1100 lines
25 KiB
C#
|
//
|
||
|
// JsonReader.cs
|
||
|
//
|
||
|
// Author:
|
||
|
// Atsushi Enomoto <atsushi@ximian.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
|
||
|
// "Software"), to deal in the Software without restriction, including
|
||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||
|
// the following conditions:
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be
|
||
|
// included in all copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
//
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Globalization;
|
||
|
using System.IO;
|
||
|
using System.Text;
|
||
|
using System.Xml;
|
||
|
|
||
|
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
|
||
|
{
|
||
|
class ElementInfo
|
||
|
{
|
||
|
public readonly string Name;
|
||
|
public readonly string Type;
|
||
|
public bool HasContent;
|
||
|
|
||
|
public ElementInfo (string name, string type)
|
||
|
{
|
||
|
this.Name = name;
|
||
|
this.Type = type;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
enum AttributeState
|
||
|
{
|
||
|
None,
|
||
|
Type,
|
||
|
TypeValue,
|
||
|
RuntimeType,
|
||
|
RuntimeTypeValue
|
||
|
}
|
||
|
|
||
|
PushbackReader reader;
|
||
|
XmlDictionaryReaderQuotas quotas;
|
||
|
OnXmlDictionaryReaderClose on_close;
|
||
|
XmlNameTable name_table = new NameTable ();
|
||
|
|
||
|
XmlNodeType current_node;
|
||
|
AttributeState attr_state;
|
||
|
string simple_value;
|
||
|
string next_element;
|
||
|
string current_runtime_type, next_object_content_name;
|
||
|
ReadState read_state = ReadState.Initial;
|
||
|
bool content_stored;
|
||
|
bool finished;
|
||
|
Stack<ElementInfo> elements = new Stack<ElementInfo> ();
|
||
|
|
||
|
int line = 1, column = 0;
|
||
|
|
||
|
// Constructors
|
||
|
|
||
|
public JsonReader (byte [] buffer, int offset, int count, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
|
||
|
{
|
||
|
SetInput (buffer, offset, count, encoding, quotas, onClose);
|
||
|
}
|
||
|
|
||
|
public JsonReader (Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
|
||
|
{
|
||
|
SetInput (stream, encoding, quotas, onClose);
|
||
|
}
|
||
|
|
||
|
internal bool LameSilverlightLiteralParser { get; set; }
|
||
|
|
||
|
// IXmlLineInfo
|
||
|
|
||
|
public bool HasLineInfo ()
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public int LineNumber {
|
||
|
get { return line; }
|
||
|
}
|
||
|
|
||
|
public int LinePosition {
|
||
|
get { return column; }
|
||
|
}
|
||
|
|
||
|
// IXmlJsonReaderInitializer
|
||
|
|
||
|
public void SetInput (byte [] buffer, int offset, int count, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
|
||
|
{
|
||
|
SetInput (new MemoryStream (buffer, offset, count), encoding, quotas, onClose);
|
||
|
}
|
||
|
|
||
|
public void SetInput (Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
|
||
|
{
|
||
|
if (encoding != null)
|
||
|
reader = new PushbackReader (stream, encoding);
|
||
|
else
|
||
|
reader = new PushbackReader (stream);
|
||
|
if (quotas == null)
|
||
|
throw new ArgumentNullException ("quotas");
|
||
|
this.quotas = quotas;
|
||
|
this.on_close = onClose;
|
||
|
}
|
||
|
|
||
|
// XmlDictionaryReader
|
||
|
|
||
|
public override int AttributeCount {
|
||
|
get { return current_node != XmlNodeType.Element ? 0 : current_runtime_type != null ? 2 : 1; }
|
||
|
}
|
||
|
|
||
|
public override string BaseURI {
|
||
|
get { return String.Empty; }
|
||
|
}
|
||
|
|
||
|
public override int Depth {
|
||
|
get {
|
||
|
int mod = 0;
|
||
|
switch (attr_state) {
|
||
|
case AttributeState.Type:
|
||
|
case AttributeState.RuntimeType:
|
||
|
mod++;
|
||
|
break;
|
||
|
case AttributeState.TypeValue:
|
||
|
case AttributeState.RuntimeTypeValue:
|
||
|
mod += 2;
|
||
|
break;
|
||
|
case AttributeState.None:
|
||
|
if (NodeType == XmlNodeType.Text)
|
||
|
mod++;
|
||
|
break;
|
||
|
}
|
||
|
return read_state != ReadState.Interactive ? 0 : elements.Count - 1 + mod;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override bool EOF {
|
||
|
get {
|
||
|
switch (read_state) {
|
||
|
case ReadState.Closed:
|
||
|
case ReadState.EndOfFile:
|
||
|
return true;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override bool HasValue {
|
||
|
get {
|
||
|
switch (NodeType) {
|
||
|
case XmlNodeType.Attribute:
|
||
|
case XmlNodeType.Text:
|
||
|
return true;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override bool IsEmptyElement {
|
||
|
get { return false; }
|
||
|
}
|
||
|
|
||
|
public override string LocalName {
|
||
|
get {
|
||
|
switch (attr_state) {
|
||
|
case AttributeState.Type:
|
||
|
return "type";
|
||
|
case AttributeState.RuntimeType:
|
||
|
return "__type";
|
||
|
}
|
||
|
switch (NodeType) {
|
||
|
case XmlNodeType.Element:
|
||
|
case XmlNodeType.EndElement:
|
||
|
return elements.Peek ().Name;
|
||
|
default:
|
||
|
return String.Empty;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override string NamespaceURI {
|
||
|
get { return String.Empty; }
|
||
|
}
|
||
|
|
||
|
public override XmlNameTable NameTable {
|
||
|
get { return name_table; }
|
||
|
}
|
||
|
|
||
|
public override XmlNodeType NodeType {
|
||
|
get {
|
||
|
switch (attr_state) {
|
||
|
case AttributeState.Type:
|
||
|
case AttributeState.RuntimeType:
|
||
|
return XmlNodeType.Attribute;
|
||
|
case AttributeState.TypeValue:
|
||
|
case AttributeState.RuntimeTypeValue:
|
||
|
return XmlNodeType.Text;
|
||
|
default:
|
||
|
return current_node;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override string Prefix {
|
||
|
get { return String.Empty; }
|
||
|
}
|
||
|
|
||
|
public override ReadState ReadState {
|
||
|
get { return read_state; }
|
||
|
}
|
||
|
|
||
|
public override string Value {
|
||
|
get {
|
||
|
switch (attr_state) {
|
||
|
case AttributeState.Type:
|
||
|
case AttributeState.TypeValue:
|
||
|
return elements.Peek ().Type;
|
||
|
case AttributeState.RuntimeType:
|
||
|
case AttributeState.RuntimeTypeValue:
|
||
|
return current_runtime_type;
|
||
|
default:
|
||
|
return current_node == XmlNodeType.Text ? simple_value : String.Empty;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void Close ()
|
||
|
{
|
||
|
if (on_close != null) {
|
||
|
on_close (this);
|
||
|
on_close = null;
|
||
|
}
|
||
|
read_state = ReadState.Closed;
|
||
|
}
|
||
|
|
||
|
public override string GetAttribute (int index)
|
||
|
{
|
||
|
if (index == 0 && current_node == XmlNodeType.Element)
|
||
|
return elements.Peek ().Type;
|
||
|
else if (index == 1 && current_runtime_type != null)
|
||
|
return current_runtime_type;
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
public override string GetAttribute (string name)
|
||
|
{
|
||
|
if (current_node != XmlNodeType.Element)
|
||
|
return null;
|
||
|
switch (name) {
|
||
|
case "type":
|
||
|
return elements.Peek ().Type;
|
||
|
case "__type":
|
||
|
return current_runtime_type;
|
||
|
default:
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override string GetAttribute (string localName, string ns)
|
||
|
{
|
||
|
if (ns == String.Empty)
|
||
|
return GetAttribute (localName);
|
||
|
else
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public override string LookupNamespace (string prefix)
|
||
|
{
|
||
|
if (prefix == null)
|
||
|
throw new ArgumentNullException ("prefix");
|
||
|
else if (prefix.Length == 0)
|
||
|
return String.Empty;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public override bool MoveToAttribute (string name)
|
||
|
{
|
||
|
if (current_node != XmlNodeType.Element)
|
||
|
return false;
|
||
|
switch (name) {
|
||
|
case "type":
|
||
|
attr_state = AttributeState.Type;
|
||
|
return true;
|
||
|
case "__type":
|
||
|
if (current_runtime_type == null)
|
||
|
return false;
|
||
|
attr_state = AttributeState.RuntimeType;
|
||
|
return true;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override bool MoveToAttribute (string localName, string ns)
|
||
|
{
|
||
|
if (ns != String.Empty)
|
||
|
return false;
|
||
|
return MoveToAttribute (localName);
|
||
|
}
|
||
|
|
||
|
public override bool MoveToElement ()
|
||
|
{
|
||
|
if (attr_state == AttributeState.None)
|
||
|
return false;
|
||
|
attr_state = AttributeState.None;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public override bool MoveToFirstAttribute ()
|
||
|
{
|
||
|
if (current_node != XmlNodeType.Element)
|
||
|
return false;
|
||
|
attr_state = AttributeState.Type;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public override bool MoveToNextAttribute ()
|
||
|
{
|
||
|
if (attr_state == AttributeState.None)
|
||
|
return MoveToFirstAttribute ();
|
||
|
else
|
||
|
return MoveToAttribute ("__type");
|
||
|
}
|
||
|
|
||
|
public override bool ReadAttributeValue ()
|
||
|
{
|
||
|
switch (attr_state) {
|
||
|
case AttributeState.Type:
|
||
|
attr_state = AttributeState.TypeValue;
|
||
|
return true;
|
||
|
case AttributeState.RuntimeType:
|
||
|
attr_state = AttributeState.RuntimeTypeValue;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public override void ResolveEntity ()
|
||
|
{
|
||
|
throw new NotSupportedException ();
|
||
|
}
|
||
|
|
||
|
public override bool Read ()
|
||
|
{
|
||
|
switch (read_state) {
|
||
|
case ReadState.EndOfFile:
|
||
|
case ReadState.Closed:
|
||
|
case ReadState.Error:
|
||
|
return false;
|
||
|
case ReadState.Initial:
|
||
|
read_state = ReadState.Interactive;
|
||
|
next_element = "root";
|
||
|
current_node = XmlNodeType.Element;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
MoveToElement ();
|
||
|
|
||
|
if (content_stored) {
|
||
|
if (current_node == XmlNodeType.Element) {
|
||
|
if (elements.Peek ().Type == "null") {
|
||
|
// since null is not consumed as text content, it skips Text state.
|
||
|
current_node = XmlNodeType.EndElement;
|
||
|
content_stored = false;
|
||
|
}
|
||
|
else
|
||
|
current_node = XmlNodeType.Text;
|
||
|
return true;
|
||
|
} else if (current_node == XmlNodeType.Text) {
|
||
|
current_node = XmlNodeType.EndElement;
|
||
|
content_stored = false;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else if (current_node == XmlNodeType.EndElement) {
|
||
|
// clear EndElement state
|
||
|
elements.Pop ();
|
||
|
if (elements.Count > 0)
|
||
|
elements.Peek ().HasContent = true;
|
||
|
else
|
||
|
finished = true;
|
||
|
}
|
||
|
|
||
|
SkipWhitespaces ();
|
||
|
|
||
|
attr_state = AttributeState.None;
|
||
|
// Default. May be overriden only as EndElement or None.
|
||
|
current_node = XmlNodeType.Element;
|
||
|
|
||
|
if (!ReadContent (false))
|
||
|
return false;
|
||
|
if (finished)
|
||
|
throw XmlError ("Multiple top-level content is not allowed");
|
||
|
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 ();
|
||
|
if (ch < 0) {
|
||
|
ReadEndOfStream ();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool itemMustFollow = false;
|
||
|
|
||
|
if (!objectValue && elements.Count > 0 && elements.Peek ().HasContent) {
|
||
|
if (ch == ',') {
|
||
|
switch (elements.Peek ().Type) {
|
||
|
case "object":
|
||
|
case "array":
|
||
|
SkipWhitespaces ();
|
||
|
ch = ReadChar ();
|
||
|
itemMustFollow = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else if (ch != '}' && ch != ']')
|
||
|
throw XmlError ("Comma is required unless an array or object is at the end");
|
||
|
}
|
||
|
|
||
|
if (elements.Count > 0 && elements.Peek ().Type == "array")
|
||
|
next_element = "item";
|
||
|
else if (next_object_content_name != null) {
|
||
|
next_element = next_object_content_name;
|
||
|
next_object_content_name = null;
|
||
|
if (ch != ':')
|
||
|
throw XmlError ("':' is expected after a name of an object content");
|
||
|
SkipWhitespaces ();
|
||
|
ReadContent (true);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
switch (ch) {
|
||
|
case '{':
|
||
|
ReadStartObject ();
|
||
|
return true;
|
||
|
case '[':
|
||
|
ReadStartArray ();
|
||
|
return true;
|
||
|
case '}':
|
||
|
if (itemMustFollow)
|
||
|
throw XmlError ("Invalid comma before an end of object");
|
||
|
if (objectValue)
|
||
|
throw XmlError ("Invalid end of object as an object content");
|
||
|
ReadEndObject ();
|
||
|
return true;
|
||
|
case ']':
|
||
|
if (itemMustFollow)
|
||
|
throw XmlError ("Invalid comma before an end of array");
|
||
|
if (objectValue)
|
||
|
throw XmlError ("Invalid end of array as an object content");
|
||
|
ReadEndArray ();
|
||
|
return true;
|
||
|
case '"':
|
||
|
bool lame = LameSilverlightLiteralParser && ch != '"';
|
||
|
string s = ReadStringLiteral (lame);
|
||
|
if (!objectValue && elements.Count > 0 && elements.Peek ().Type == "object") {
|
||
|
next_element = s;
|
||
|
SkipWhitespaces ();
|
||
|
if (!lame)
|
||
|
Expect (':');
|
||
|
SkipWhitespaces ();
|
||
|
ReadContent (true);
|
||
|
}
|
||
|
else
|
||
|
ReadAsSimpleContent ("string", s);
|
||
|
return true;
|
||
|
case '-':
|
||
|
ReadNumber (ch);
|
||
|
return true;
|
||
|
case 'n':
|
||
|
if (TryReadString("ull")) {
|
||
|
ReadAsSimpleContent ("null", "null");
|
||
|
return true;
|
||
|
}
|
||
|
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 (TryReadString ("rue")) {
|
||
|
ReadAsSimpleContent ("boolean", "true");
|
||
|
return true;
|
||
|
}
|
||
|
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 (TryReadString ("alse")) {
|
||
|
ReadAsSimpleContent ("boolean", "false");
|
||
|
return true;
|
||
|
}
|
||
|
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) {
|
||
|
PushbackChar (ch);
|
||
|
goto case '"';
|
||
|
}
|
||
|
throw XmlError (String.Format ("Unexpected token: '{0}' ({1:X04})", (char) ch, (int) ch));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ReadStartObject ()
|
||
|
{
|
||
|
ElementInfo ei = new ElementInfo (next_element, "object");
|
||
|
elements.Push (ei);
|
||
|
|
||
|
SkipWhitespaces ();
|
||
|
if (PeekChar () == '"') { // it isn't premise: the object might be empty
|
||
|
ReadChar ();
|
||
|
string s = ReadStringLiteral ();
|
||
|
if (s == "__type") {
|
||
|
SkipWhitespaces ();
|
||
|
Expect (':');
|
||
|
SkipWhitespaces ();
|
||
|
Expect ('"');
|
||
|
current_runtime_type = ReadStringLiteral ();
|
||
|
SkipWhitespaces ();
|
||
|
ei.HasContent = true;
|
||
|
}
|
||
|
else
|
||
|
next_object_content_name = s;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ReadStartArray ()
|
||
|
{
|
||
|
elements.Push (new ElementInfo (next_element, "array"));
|
||
|
}
|
||
|
|
||
|
void ReadEndObject ()
|
||
|
{
|
||
|
if (elements.Count == 0 || elements.Peek ().Type != "object")
|
||
|
throw XmlError ("Unexpected end of object");
|
||
|
current_node = XmlNodeType.EndElement;
|
||
|
}
|
||
|
|
||
|
void ReadEndArray ()
|
||
|
{
|
||
|
if (elements.Count == 0 || elements.Peek ().Type != "array")
|
||
|
throw XmlError ("Unexpected end of array");
|
||
|
current_node = XmlNodeType.EndElement;
|
||
|
}
|
||
|
|
||
|
void ReadEndOfStream ()
|
||
|
{
|
||
|
if (elements.Count > 0)
|
||
|
throw XmlError (String.Format ("{0} missing end of arrays or objects", elements.Count));
|
||
|
read_state = ReadState.EndOfFile;
|
||
|
current_node = XmlNodeType.None;
|
||
|
}
|
||
|
|
||
|
void ReadAsSimpleContent (string type, string value)
|
||
|
{
|
||
|
elements.Push (new ElementInfo (next_element, type));
|
||
|
simple_value = value;
|
||
|
content_stored = true;
|
||
|
}
|
||
|
|
||
|
void ReadNumber (int ch)
|
||
|
{
|
||
|
elements.Push (new ElementInfo (next_element, "number"));
|
||
|
content_stored = true;
|
||
|
|
||
|
int init = ch;
|
||
|
int prev;
|
||
|
bool floating = false, exp = false;
|
||
|
|
||
|
StringBuilder sb = new StringBuilder ();
|
||
|
bool cont = true;
|
||
|
do {
|
||
|
sb.Append ((char) ch);
|
||
|
prev = ch;
|
||
|
ch = ReadChar ();
|
||
|
|
||
|
if (prev == '-' && !IsNumber (ch)) // neither '.', '-' or '+' nor anything else is valid
|
||
|
throw XmlError ("Invalid JSON number");
|
||
|
|
||
|
switch (ch) {
|
||
|
case 'e':
|
||
|
case 'E':
|
||
|
if (exp)
|
||
|
throw XmlError ("Invalid JSON number token. Either 'E' or 'e' must not occur more than once");
|
||
|
if (!IsNumber (prev))
|
||
|
throw XmlError ("Invalid JSON number token. only a number is valid before 'E' or 'e'");
|
||
|
exp = true;
|
||
|
break;
|
||
|
case '.':
|
||
|
if (floating)
|
||
|
throw XmlError ("Invalid JSON number token. '.' must not occur twice");
|
||
|
if (exp)
|
||
|
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)) {
|
||
|
PushbackChar (ch);
|
||
|
cont = false;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
} while (cont);
|
||
|
|
||
|
if (!IsNumber (prev)) // only number is valid at the end
|
||
|
throw XmlError ("Invalid JSON number");
|
||
|
|
||
|
simple_value = sb.ToString ();
|
||
|
|
||
|
if (init == '0' && !floating && !exp && simple_value != "0")
|
||
|
throw XmlError ("Invalid JSON number");
|
||
|
}
|
||
|
|
||
|
bool IsNumber (int c)
|
||
|
{
|
||
|
return '0' <= c && c <= '9';
|
||
|
}
|
||
|
|
||
|
StringBuilder vb = new StringBuilder ();
|
||
|
|
||
|
string ReadStringLiteral ()
|
||
|
{
|
||
|
return ReadStringLiteral (false);
|
||
|
}
|
||
|
|
||
|
string ReadStringLiteral (bool endWithColon)
|
||
|
{
|
||
|
vb.Length = 0;
|
||
|
while (true) {
|
||
|
int c = ReadChar ();
|
||
|
if (c < 0)
|
||
|
throw XmlError ("JSON string is not closed");
|
||
|
if (c == '"' && !endWithColon)
|
||
|
return vb.ToString ();
|
||
|
else if (c == ':' && endWithColon)
|
||
|
return vb.ToString ();
|
||
|
else if (c != '\\') {
|
||
|
vb.Append ((char) c);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// escaped expression
|
||
|
c = ReadChar ();
|
||
|
if (c < 0)
|
||
|
throw XmlError ("Invalid JSON string literal; incomplete escape sequence");
|
||
|
switch (c) {
|
||
|
case '"':
|
||
|
case '\\':
|
||
|
case '/':
|
||
|
vb.Append ((char) c);
|
||
|
break;
|
||
|
case 'b':
|
||
|
vb.Append ('\x8');
|
||
|
break;
|
||
|
case 'f':
|
||
|
vb.Append ('\f');
|
||
|
break;
|
||
|
case 'n':
|
||
|
vb.Append ('\n');
|
||
|
break;
|
||
|
case 'r':
|
||
|
vb.Append ('\r');
|
||
|
break;
|
||
|
case 't':
|
||
|
vb.Append ('\t');
|
||
|
break;
|
||
|
case 'u':
|
||
|
ushort cp = 0;
|
||
|
for (int i = 0; i < 4; i++) {
|
||
|
if ((c = ReadChar ()) < 0)
|
||
|
throw XmlError ("Incomplete unicode character escape literal");
|
||
|
cp *= 16;
|
||
|
if ('0' <= c && c <= '9')
|
||
|
cp += (ushort) (c - '0');
|
||
|
if ('A' <= c && c <= 'F')
|
||
|
cp += (ushort) (c - 'A' + 10);
|
||
|
if ('a' <= c && c <= 'f')
|
||
|
cp += (ushort) (c - 'a' + 10);
|
||
|
}
|
||
|
vb.Append ((char) cp);
|
||
|
break;
|
||
|
default:
|
||
|
throw XmlError ("Invalid JSON string literal; unexpected escape character");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int PeekChar ()
|
||
|
{
|
||
|
return reader.Peek ();
|
||
|
}
|
||
|
|
||
|
int ReadChar ()
|
||
|
{
|
||
|
int v = reader.Read ();
|
||
|
if (v == '\n') {
|
||
|
line++;
|
||
|
column = 0;
|
||
|
}
|
||
|
else
|
||
|
column++;
|
||
|
return v;
|
||
|
}
|
||
|
|
||
|
void PushbackChar (int ch)
|
||
|
{
|
||
|
// FIXME handle lines (and columns? ugh, how?)
|
||
|
reader.Pushback (ch);
|
||
|
}
|
||
|
|
||
|
void SkipWhitespaces ()
|
||
|
{
|
||
|
do {
|
||
|
switch (PeekChar ()) {
|
||
|
case ' ':
|
||
|
case '\t':
|
||
|
case '\r':
|
||
|
case '\n':
|
||
|
ReadChar ();
|
||
|
continue;
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
} while (true);
|
||
|
}
|
||
|
|
||
|
void Expect (char c)
|
||
|
{
|
||
|
int v = ReadChar ();
|
||
|
if (v < 0)
|
||
|
throw XmlError (String.Format ("Expected '{0}' but got EOF", c));
|
||
|
if (v != c)
|
||
|
throw XmlError (String.Format ("Expected '{0}' but got '{1}'", c, (char) v));
|
||
|
}
|
||
|
|
||
|
Exception XmlError (string s)
|
||
|
{
|
||
|
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 ();
|
||
|
}
|
||
|
}
|
||
|
}
|