// // JsonWriter.cs // // Author: // Atsushi Enomoto // // Copyright (C) 2007 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 { class PushbackReader : StreamReader { Stack pushback; public PushbackReader (Stream stream, Encoding encoding) : base (stream, encoding) { pushback = new Stack(); } public PushbackReader (Stream stream) : base (stream, true) { pushback = new Stack(); } 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 elements = new Stack (); 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 (); } } }