/* Copyright 2010-2014 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;
namespace MongoDB.Bson.IO
{
///
/// Represents a BSON reader for a JSON string.
///
public class JsonReader : BsonReader
{
// private fields
private JsonBuffer _buffer;
private JsonReaderSettings _jsonReaderSettings; // same value as in base class just declared as derived class
private JsonReaderContext _context;
private JsonToken _currentToken;
private BsonValue _currentValue;
private JsonToken _pushedToken;
// constructors
///
/// Initializes a new instance of the JsonReader class.
///
/// The buffer.
/// The reader settings.
public JsonReader(JsonBuffer buffer, JsonReaderSettings settings)
: base(settings)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
_buffer = buffer;
_jsonReaderSettings = settings; // already frozen by base class
_context = new JsonReaderContext(null, ContextType.TopLevel);
}
// public methods
///
/// Closes the reader.
///
public override void Close()
{
// Close can be called on Disposed objects
if (State != BsonReaderState.Closed)
{
State = BsonReaderState.Closed;
}
}
///
/// Gets a bookmark to the reader's current position and state.
///
/// A bookmark.
public override BsonReaderBookmark GetBookmark()
{
return new JsonReaderBookmark(State, CurrentBsonType, CurrentName, _context, _currentToken, _currentValue, _pushedToken, _buffer.Position);
}
///
/// Reads BSON binary data from the reader.
///
/// A BsonBinaryData.
public override BsonBinaryData ReadBinaryData()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadBinaryData", BsonType.Binary);
State = GetNextState();
return _currentValue.AsBsonBinaryData;
}
///
/// Reads a BSON boolean from the reader.
///
/// A Boolean.
public override bool ReadBoolean()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadBoolean", BsonType.Boolean);
State = GetNextState();
return _currentValue.AsBoolean;
}
///
/// Reads a BsonType from the reader.
///
/// The type of the BsonTrie values.
/// An optional trie to search for a value that matches the next element name.
/// Set to true if a matching value was found in the trie.
/// Set to the matching value found in the trie or null if no matching value was found.
/// A BsonType.
public override BsonType ReadBsonType(BsonTrie bsonTrie, out bool found, out TValue value)
{
if (Disposed) { ThrowObjectDisposedException(); }
found = false;
value = default(TValue);
if (State == BsonReaderState.Initial || State == BsonReaderState.Done || State == BsonReaderState.ScopeDocument)
{
// in JSON the top level value can be of any type so fall through
State = BsonReaderState.Type;
}
if (State != BsonReaderState.Type)
{
ThrowInvalidState("ReadBsonType", BsonReaderState.Type);
}
if (_context.ContextType == ContextType.Document)
{
var nameToken = PopToken();
switch (nameToken.Type)
{
case JsonTokenType.String:
case JsonTokenType.UnquotedString:
if (bsonTrie != null)
{
found = bsonTrie.TryGetValue(nameToken.StringValue, out value);
}
CurrentName = nameToken.StringValue;
break;
case JsonTokenType.EndObject:
State = BsonReaderState.EndOfDocument;
return BsonType.EndOfDocument;
default:
var message = string.Format("JSON reader was expecting a name but found '{0}'.", nameToken.Lexeme);
throw new Exception(message);
}
var colonToken = PopToken();
if (colonToken.Type != JsonTokenType.Colon)
{
var message = string.Format("JSON reader was expecting ':' but found '{0}'.", colonToken.Lexeme);
throw new Exception(message);
}
}
var valueToken = PopToken();
if (_context.ContextType == ContextType.Array && valueToken.Type == JsonTokenType.EndArray)
{
State = BsonReaderState.EndOfArray;
return BsonType.EndOfDocument;
}
var noValueFound = false;
switch (valueToken.Type)
{
case JsonTokenType.BeginArray:
CurrentBsonType = BsonType.Array;
break;
case JsonTokenType.BeginObject:
CurrentBsonType = ParseExtendedJson();
break;
case JsonTokenType.DateTime:
CurrentBsonType = BsonType.DateTime;
_currentValue = valueToken.DateTimeValue;
break;
case JsonTokenType.Double:
CurrentBsonType = BsonType.Double;
_currentValue = valueToken.DoubleValue;
break;
case JsonTokenType.EndOfFile:
CurrentBsonType = BsonType.EndOfDocument;
break;
case JsonTokenType.Int32:
CurrentBsonType = BsonType.Int32;
_currentValue = valueToken.Int32Value;
break;
case JsonTokenType.Int64:
CurrentBsonType = BsonType.Int64;
_currentValue = valueToken.Int64Value;
break;
case JsonTokenType.ObjectId:
CurrentBsonType = BsonType.ObjectId;
_currentValue = valueToken.ObjectIdValue;
break;
case JsonTokenType.RegularExpression:
CurrentBsonType = BsonType.RegularExpression;
_currentValue = valueToken.RegularExpressionValue;
break;
case JsonTokenType.String:
CurrentBsonType = BsonType.String;
_currentValue = valueToken.StringValue;
break;
case JsonTokenType.UnquotedString:
switch (valueToken.Lexeme)
{
case "false":
case "true":
CurrentBsonType = BsonType.Boolean;
_currentValue = XmlConvert.ToBoolean(valueToken.Lexeme);
break;
case "Infinity":
CurrentBsonType = BsonType.Double;
_currentValue = double.PositiveInfinity;
break;
case "NaN":
CurrentBsonType = BsonType.Double;
_currentValue = double.NaN;
break;
case "null":
CurrentBsonType = BsonType.Null;
break;
case "undefined":
CurrentBsonType = BsonType.Undefined;
break;
case "BinData":
CurrentBsonType = BsonType.Binary;
_currentValue = ParseBinDataConstructor();
break;
case "Date":
CurrentBsonType = BsonType.String;
_currentValue = ParseDateTimeConstructor(false); // withNew = false
break;
case "HexData":
CurrentBsonType = BsonType.Binary;
_currentValue = ParseHexDataConstructor();
break;
case "ISODate":
CurrentBsonType = BsonType.DateTime;
_currentValue = ParseISODateTimeConstructor();
break;
case "MaxKey":
CurrentBsonType = BsonType.MaxKey;
_currentValue = BsonMaxKey.Value;
break;
case "MinKey":
CurrentBsonType = BsonType.MinKey;
_currentValue = BsonMinKey.Value;
break;
case "Number":
case "NumberInt":
CurrentBsonType = BsonType.Int32;
_currentValue = ParseNumberConstructor();
break;
case "NumberLong":
CurrentBsonType = BsonType.Int64;
_currentValue = ParseNumberLongConstructor();
break;
case "ObjectId":
CurrentBsonType = BsonType.ObjectId;
_currentValue = ParseObjectIdConstructor();
break;
case "RegExp":
CurrentBsonType = BsonType.RegularExpression;
_currentValue = ParseRegularExpressionConstructor();
break;
case "Timestamp":
CurrentBsonType = BsonType.Timestamp;
_currentValue = ParseTimestampConstructor();
break;
case "UUID":
case "GUID":
case "CSUUID":
case "CSGUID":
case "JUUID":
case "JGUID":
case "PYUUID":
case "PYGUID":
CurrentBsonType = BsonType.Binary;
_currentValue = ParseUUIDConstructor(valueToken.Lexeme);
break;
case "new":
CurrentBsonType = ParseNew(out _currentValue);
break;
default:
noValueFound = true;
break;
}
break;
default:
noValueFound = true;
break;
}
if (noValueFound)
{
var message = string.Format("JSON reader was expecting a value but found '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
_currentToken = valueToken;
if (_context.ContextType == ContextType.Array || _context.ContextType == ContextType.Document)
{
var commaToken = PopToken();
if (commaToken.Type != JsonTokenType.Comma)
{
PushToken(commaToken);
}
}
switch (_context.ContextType)
{
case ContextType.Document:
case ContextType.ScopeDocument:
default:
State = BsonReaderState.Name;
break;
case ContextType.Array:
case ContextType.JavaScriptWithScope:
case ContextType.TopLevel:
State = BsonReaderState.Value;
break;
}
return CurrentBsonType;
}
///
/// Reads BSON binary data from the reader.
///
/// A byte array.
public override byte[] ReadBytes()
{
#pragma warning disable 618
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadBinaryData", BsonType.Binary);
State = GetNextState();
var binaryData = _currentValue.AsBsonBinaryData;
var subType = binaryData.SubType;
if (subType != BsonBinarySubType.Binary && subType != BsonBinarySubType.OldBinary)
{
var message = string.Format("ReadBytes requires the binary sub type to be Binary, not {2}.", subType);
throw new Exception(message);
}
return binaryData.Bytes;
#pragma warning restore
}
///
/// Reads a BSON DateTime from the reader.
///
/// The number of milliseconds since the Unix epoch.
public override long ReadDateTime()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadDateTime", BsonType.DateTime);
State = GetNextState();
return _currentValue.AsBsonDateTime.MillisecondsSinceEpoch;
}
///
/// Reads a BSON Double from the reader.
///
/// A Double.
public override double ReadDouble()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadDouble", BsonType.Double);
State = GetNextState();
return _currentValue.AsDouble;
}
///
/// Reads the end of a BSON array from the reader.
///
public override void ReadEndArray()
{
if (Disposed) { ThrowObjectDisposedException(); }
if (_context.ContextType != ContextType.Array)
{
ThrowInvalidContextType("ReadEndArray", _context.ContextType, ContextType.Array);
}
if (State == BsonReaderState.Type)
{
ReadBsonType(); // will set state to EndOfArray if at end of array
}
if (State != BsonReaderState.EndOfArray)
{
ThrowInvalidState("ReadEndArray", BsonReaderState.EndOfArray);
}
_context = _context.PopContext();
switch (_context.ContextType)
{
case ContextType.Array: State = BsonReaderState.Type; break;
case ContextType.Document: State = BsonReaderState.Type; break;
case ContextType.TopLevel: State = BsonReaderState.Done; break;
default: throw new BsonInternalException("Unexpected ContextType.");
}
if (_context.ContextType == ContextType.Array || _context.ContextType == ContextType.Document)
{
var commaToken = PopToken();
if (commaToken.Type != JsonTokenType.Comma)
{
PushToken(commaToken);
}
}
}
///
/// Reads the end of a BSON document from the reader.
///
public override void ReadEndDocument()
{
if (Disposed) { ThrowObjectDisposedException(); }
if (_context.ContextType != ContextType.Document && _context.ContextType != ContextType.ScopeDocument)
{
ThrowInvalidContextType("ReadEndDocument", _context.ContextType, ContextType.Document, ContextType.ScopeDocument);
}
if (State == BsonReaderState.Type)
{
ReadBsonType(); // will set state to EndOfDocument if at end of document
}
if (State != BsonReaderState.EndOfDocument)
{
ThrowInvalidState("ReadEndDocument", BsonReaderState.EndOfDocument);
}
_context = _context.PopContext();
if (_context != null && _context.ContextType == ContextType.JavaScriptWithScope)
{
_context = _context.PopContext(); // JavaScriptWithScope
VerifyToken("}"); // outermost closing bracket for JavaScriptWithScope
}
switch (_context.ContextType)
{
case ContextType.Array: State = BsonReaderState.Type; break;
case ContextType.Document: State = BsonReaderState.Type; break;
case ContextType.TopLevel: State = BsonReaderState.Done; break;
default: throw new BsonInternalException("Unexpected ContextType");
}
if (_context.ContextType == ContextType.Array || _context.ContextType == ContextType.Document)
{
var commaToken = PopToken();
if (commaToken.Type != JsonTokenType.Comma)
{
PushToken(commaToken);
}
}
}
///
/// Reads a BSON Int32 from the reader.
///
/// An Int32.
public override int ReadInt32()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadInt32", BsonType.Int32);
State = GetNextState();
return _currentValue.AsInt32;
}
///
/// Reads a BSON Int64 from the reader.
///
/// An Int64.
public override long ReadInt64()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadInt64", BsonType.Int64);
State = GetNextState();
return _currentValue.AsInt64;
}
///
/// Reads a BSON JavaScript from the reader.
///
/// A string.
public override string ReadJavaScript()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadJavaScript", BsonType.JavaScript);
State = GetNextState();
return _currentValue.AsString;
}
///
/// Reads a BSON JavaScript with scope from the reader (call ReadStartDocument next to read the scope).
///
/// A string.
public override string ReadJavaScriptWithScope()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadJavaScriptWithScope", BsonType.JavaScriptWithScope);
_context = new JsonReaderContext(_context, ContextType.JavaScriptWithScope);
State = BsonReaderState.ScopeDocument;
return _currentValue.AsString;
}
///
/// Reads a BSON MaxKey from the reader.
///
public override void ReadMaxKey()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadMaxKey", BsonType.MaxKey);
State = GetNextState();
}
///
/// Reads a BSON MinKey from the reader.
///
public override void ReadMinKey()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadMinKey", BsonType.MinKey);
State = GetNextState();
}
///
/// Reads a BSON null from the reader.
///
public override void ReadNull()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadNull", BsonType.Null);
State = GetNextState();
}
///
/// Reads a BSON ObjectId from the reader.
///
/// An ObjectId.
public override ObjectId ReadObjectId()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadObjectId", BsonType.ObjectId);
State = GetNextState();
return _currentValue.AsObjectId;
}
///
/// Reads a BSON regular expression from the reader.
///
/// A BsonRegularExpression.
public override BsonRegularExpression ReadRegularExpression()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadRegularExpression", BsonType.RegularExpression);
State = GetNextState();
return _currentValue.AsBsonRegularExpression;
}
///
/// Reads the start of a BSON array.
///
public override void ReadStartArray()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadStartArray", BsonType.Array);
_context = new JsonReaderContext(_context, ContextType.Array);
State = BsonReaderState.Type;
}
///
/// Reads the start of a BSON document.
///
public override void ReadStartDocument()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadStartDocument", BsonType.Document);
_context = new JsonReaderContext(_context, ContextType.Document);
State = BsonReaderState.Type;
}
///
/// Reads a BSON string from the reader.
///
/// A String.
public override string ReadString()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadString", BsonType.String);
State = GetNextState();
return _currentValue.AsString;
}
///
/// Reads a BSON symbol from the reader.
///
/// A string.
public override string ReadSymbol()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadSymbol", BsonType.Symbol);
State = GetNextState();
return _currentValue.AsString;
}
///
/// Reads a BSON timestamp from the reader.
///
/// The combined timestamp/increment.
public override long ReadTimestamp()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadTimestamp", BsonType.Timestamp);
State = GetNextState();
var timestamp = _currentValue.AsBsonTimestamp;
return timestamp.Value;
}
///
/// Reads a BSON undefined from the reader.
///
public override void ReadUndefined()
{
if (Disposed) { ThrowObjectDisposedException(); }
VerifyBsonType("ReadUndefined", BsonType.Undefined);
State = GetNextState();
}
///
/// Returns the reader to previously bookmarked position and state.
///
/// The bookmark.
public override void ReturnToBookmark(BsonReaderBookmark bookmark)
{
if (Disposed) { ThrowObjectDisposedException(); }
var jsonReaderBookmark = (JsonReaderBookmark)bookmark;
State = jsonReaderBookmark.State;
CurrentBsonType = jsonReaderBookmark.CurrentBsonType;
CurrentName = jsonReaderBookmark.CurrentName;
_context = jsonReaderBookmark.CloneContext();
_currentToken = jsonReaderBookmark.CurrentToken;
_currentValue = jsonReaderBookmark.CurrentValue;
_pushedToken = jsonReaderBookmark.PushedToken;
_buffer.Position = jsonReaderBookmark.Position;
}
///
/// Skips the name (reader must be positioned on a name).
///
public override void SkipName()
{
if (Disposed) { ThrowObjectDisposedException(); }
if (State != BsonReaderState.Name)
{
ThrowInvalidState("SkipName", BsonReaderState.Name);
}
State = BsonReaderState.Value;
}
///
/// Skips the value (reader must be positioned on a value).
///
public override void SkipValue()
{
if (Disposed) { ThrowObjectDisposedException(); }
if (State != BsonReaderState.Value)
{
ThrowInvalidState("SkipValue", BsonReaderState.Value);
}
switch (CurrentBsonType)
{
case BsonType.Array:
ReadStartArray();
while (ReadBsonType() != BsonType.EndOfDocument)
{
SkipValue();
}
ReadEndArray();
break;
case BsonType.Binary:
ReadBinaryData();
break;
case BsonType.Boolean:
ReadBoolean();
break;
case BsonType.DateTime:
ReadDateTime();
break;
case BsonType.Document:
ReadStartDocument();
while (ReadBsonType() != BsonType.EndOfDocument)
{
SkipName();
SkipValue();
}
ReadEndDocument();
break;
case BsonType.Double:
ReadDouble();
break;
case BsonType.Int32:
ReadInt32();
break;
case BsonType.Int64:
ReadInt64();
break;
case BsonType.JavaScript:
ReadJavaScript();
break;
case BsonType.JavaScriptWithScope:
ReadJavaScriptWithScope();
ReadStartDocument();
while (ReadBsonType() != BsonType.EndOfDocument)
{
SkipName();
SkipValue();
}
ReadEndDocument();
break;
case BsonType.MaxKey:
ReadMaxKey();
break;
case BsonType.MinKey:
ReadMinKey();
break;
case BsonType.Null:
ReadNull();
break;
case BsonType.ObjectId:
ReadObjectId();
break;
case BsonType.RegularExpression:
ReadRegularExpression();
break;
case BsonType.String:
ReadString();
break;
case BsonType.Symbol:
ReadSymbol();
break;
case BsonType.Timestamp:
ReadTimestamp();
break;
case BsonType.Undefined:
ReadUndefined();
break;
default:
throw new BsonInternalException("Invalid BsonType.");
}
}
// protected methods
///
/// Disposes of any resources used by the reader.
///
/// True if called from Dispose.
protected override void Dispose(bool disposing)
{
if (disposing)
{
try
{
Close();
}
catch { } // ignore exceptions
}
base.Dispose(disposing);
}
// private methods
private string FormatInvalidTokenMessage(JsonToken token)
{
return string.Format("Invalid JSON token: '{0}'", token.Lexeme);
}
private string FormatJavaScriptDateTimeString(DateTime dateTime)
{
var utc = BsonUtils.ToUniversalTime(dateTime);
var local = BsonUtils.ToLocalTime(utc);
var offset = local - utc;
var offsetSign = "+";
if (offset < TimeSpan.Zero)
{
offset = -offset;
offsetSign = "-";
}
var timeZone = TimeZone.CurrentTimeZone;
var timeZoneName = local.IsDaylightSavingTime() ? timeZone.DaylightName : timeZone.StandardName;
var dateTimeString = string.Format(
"{0} GMT{1}{2:D2}{3:D2} ({4})",
local.ToString("ddd MMM dd yyyy HH:mm:ss"), offsetSign, offset.Hours, offset.Minutes, timeZoneName);
return dateTimeString;
}
private BsonReaderState GetNextState()
{
switch (_context.ContextType)
{
case ContextType.Array:
case ContextType.Document:
return BsonReaderState.Type;
case ContextType.TopLevel:
return BsonReaderState.Done;
default:
throw new BsonInternalException("Unexpected ContextType.");
}
}
private BsonValue ParseBinDataConstructor()
{
VerifyToken("(");
var subTypeToken = PopToken();
if (subTypeToken.Type != JsonTokenType.Int32)
{
var message = string.Format("JSON reader expected a binary subtype but found '{0}'.", subTypeToken.Lexeme);
throw new Exception(message);
}
VerifyToken(",");
var bytesToken = PopToken();
if (bytesToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", bytesToken.Lexeme);
throw new Exception(message);
}
VerifyToken(")");
var bytes = Convert.FromBase64String(bytesToken.StringValue);
var subType = (BsonBinarySubType)subTypeToken.Int32Value;
GuidRepresentation guidRepresentation;
switch (subType)
{
case BsonBinarySubType.UuidLegacy: guidRepresentation = _jsonReaderSettings.GuidRepresentation; break;
case BsonBinarySubType.UuidStandard: guidRepresentation = GuidRepresentation.Standard; break;
default: guidRepresentation = GuidRepresentation.Unspecified; break;
}
return new BsonBinaryData(bytes, subType, guidRepresentation);
}
private BsonValue ParseBinDataExtendedJson()
{
VerifyToken(":");
var bytesToken = PopToken();
if (bytesToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", bytesToken.Lexeme);
throw new Exception(message);
}
VerifyToken(",");
VerifyString("$type");
VerifyToken(":");
var subTypeToken = PopToken();
if (subTypeToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", subTypeToken.Lexeme);
throw new Exception(message);
}
VerifyToken("}");
var bytes = Convert.FromBase64String(bytesToken.StringValue);
var subType = (BsonBinarySubType)Convert.ToInt32(subTypeToken.StringValue, 16);
GuidRepresentation guidRepresentation;
switch (subType)
{
case BsonBinarySubType.UuidLegacy: guidRepresentation = _jsonReaderSettings.GuidRepresentation; break;
case BsonBinarySubType.UuidStandard: guidRepresentation = GuidRepresentation.Standard; break;
default: guidRepresentation = GuidRepresentation.Unspecified; break;
}
return new BsonBinaryData(bytes, subType, guidRepresentation);
}
private BsonValue ParseHexDataConstructor()
{
VerifyToken("(");
var subTypeToken = PopToken();
if (subTypeToken.Type != JsonTokenType.Int32)
{
var message = string.Format("JSON reader expected a binary subtype but found '{0}'.", subTypeToken.Lexeme);
throw new Exception(message);
}
VerifyToken(",");
var bytesToken = PopToken();
if (bytesToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", bytesToken.Lexeme);
throw new Exception(message);
}
VerifyToken(")");
var bytes = BsonUtils.ParseHexString(bytesToken.StringValue);
var subType = (BsonBinarySubType)subTypeToken.Int32Value;
GuidRepresentation guidRepresentation;
switch (subType)
{
case BsonBinarySubType.UuidLegacy: guidRepresentation = _jsonReaderSettings.GuidRepresentation; break;
case BsonBinarySubType.UuidStandard: guidRepresentation = GuidRepresentation.Standard; break;
default: guidRepresentation = GuidRepresentation.Unspecified; break;
}
return new BsonBinaryData(bytes, subType, guidRepresentation);
}
private BsonType ParseJavaScriptExtendedJson(out BsonValue value)
{
VerifyToken(":");
var codeToken = PopToken();
if (codeToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", codeToken.Lexeme);
throw new Exception(message);
}
var nextToken = PopToken();
switch (nextToken.Type)
{
case JsonTokenType.Comma:
VerifyString("$scope");
VerifyToken(":");
State = BsonReaderState.Value;
value = codeToken.StringValue;
return BsonType.JavaScriptWithScope;
case JsonTokenType.EndObject:
value = codeToken.StringValue;
return BsonType.JavaScript;
default:
var message = string.Format("JSON reader expected ',' or '}' but found '{0}'.", codeToken.Lexeme);
throw new Exception(message);
}
}
private BsonValue ParseISODateTimeConstructor()
{
VerifyToken("(");
var valueToken = PopToken();
if (valueToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
VerifyToken(")");
var formats = new string[] { "yyyy-MM-ddK", "yyyy-MM-ddTHH:mm:ssK", "yyyy-MM-ddTHH:mm:ss.FFFFFFFK" };
var utcDateTime = DateTime.ParseExact(valueToken.StringValue, formats, null, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
return new BsonDateTime(utcDateTime);
}
private BsonValue ParseDateTimeExtendedJson()
{
VerifyToken(":");
var valueToken = PopToken();
long millisecondsSinceEpoch;
if (valueToken.Type == JsonTokenType.Int32 || valueToken.Type == JsonTokenType.Int64)
{
millisecondsSinceEpoch = valueToken.Int64Value;
}
else if (valueToken.Type == JsonTokenType.String)
{
DateTime dateTime;
if (!DateTime.TryParse(valueToken.StringValue, out dateTime))
{
var message = string.Format("Invalid $date string: '{0}'.", valueToken.StringValue);
throw new Exception(message);
}
millisecondsSinceEpoch = BsonUtils.ToMillisecondsSinceEpoch(dateTime);
}
else
{
var message = string.Format("JSON reader expected an integer or an ISO 8601 string for $date but found a '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
VerifyToken("}");
return new BsonDateTime(millisecondsSinceEpoch);
}
private BsonValue ParseDateTimeConstructor(bool withNew)
{
VerifyToken("(");
// Date when used without "new" behaves differently (JavaScript has some weird parts)
if (!withNew)
{
VerifyToken(")");
var dateTimeString = FormatJavaScriptDateTimeString(DateTime.UtcNow);
return new BsonString(dateTimeString);
}
var token = PopToken();
if (token.Lexeme == ")")
{
return new BsonDateTime(DateTime.UtcNow);
}
else if (token.Type == JsonTokenType.String)
{
VerifyToken(")");
var dateTimeString = token.StringValue;
var dateTime = ParseJavaScriptDateTimeString(dateTimeString);
return new BsonDateTime(dateTime);
}
else if (token.Type == JsonTokenType.Int32 || token.Type == JsonTokenType.Int64)
{
var args = new List();
while (true)
{
args.Add(token.Int64Value);
token = PopToken();
if (token.Lexeme == ")")
{
break;
}
if (token.Lexeme != ",")
{
var message = string.Format("JSON reader expected a ',' or a ')' but found '{0}'.", token.Lexeme);
throw new Exception(message);
}
token = PopToken();
if (token.Type != JsonTokenType.Int32 && token.Type != JsonTokenType.Int64)
{
var message = string.Format("JSON reader expected an integer but found '{0}'.", token.Lexeme);
throw new Exception(message);
}
}
switch (args.Count)
{
case 1:
return new BsonDateTime(args[0]);
case 3:
case 4:
case 5:
case 6:
case 7:
var year = (int)args[0];
var month = (int)args[1] + 1; // JavaScript starts at 0 but .NET starts at 1
var day = (int)args[2];
var hours = (args.Count >= 4) ? (int)args[3] : 0;
var minutes = (args.Count >= 5) ? (int)args[4] : 0;
var seconds = (args.Count >= 6) ? (int)args[5] : 0;
var milliseconds = (args.Count == 7) ? (int)args[6] : 0;
var dateTime = new DateTime(year, month, day, hours, minutes, seconds, milliseconds, DateTimeKind.Utc);
return new BsonDateTime(dateTime);
default:
var message = string.Format("JSON reader expected 1 or 3-7 integers but found {0}.", args.Count);
throw new Exception(message);
}
}
else
{
var message = string.Format("JSON reader expected an integer or a string but found '{0}'.", token.Lexeme);
throw new Exception(message);
}
}
private BsonType ParseExtendedJson()
{
var nameToken = PopToken();
if (nameToken.Type == JsonTokenType.String || nameToken.Type == JsonTokenType.UnquotedString)
{
switch (nameToken.StringValue)
{
case "$binary": _currentValue = ParseBinDataExtendedJson(); return BsonType.Binary;
case "$code": return ParseJavaScriptExtendedJson(out _currentValue);
case "$date": _currentValue = ParseDateTimeExtendedJson(); return BsonType.DateTime;
case "$maxkey": case "$maxKey": _currentValue = ParseMaxKeyExtendedJson(); return BsonType.MaxKey;
case "$minkey": case "$minKey": _currentValue = ParseMinKeyExtendedJson(); return BsonType.MinKey;
case "$numberLong": _currentValue = ParseNumberLongExtendedJson(); return BsonType.Int64;
case "$oid": _currentValue = ParseObjectIdExtendedJson(); return BsonType.ObjectId;
case "$regex": _currentValue = ParseRegularExpressionExtendedJson(); return BsonType.RegularExpression;
case "$symbol": _currentValue = ParseSymbolExtendedJson(); return BsonType.Symbol;
case "$timestamp": _currentValue = ParseTimestampExtendedJson(); return BsonType.Timestamp;
case "$undefined": _currentValue = ParseUndefinedExtendedJson(); return BsonType.Undefined;
}
}
PushToken(nameToken);
return BsonType.Document;
}
private DateTime ParseJavaScriptDateTimeString(string dateTimeString)
{
// if DateTime.TryParse succeeds we're done, otherwise assume it's an RFC 822 formatted DateTime string
DateTime dateTime;
if (DateTime.TryParse(dateTimeString, out dateTime))
{
return dateTime;
}
else
{
var rfc822DateTimePattern =
@"^((?(Mon|Tue|Wed|Thu|Fri|Sat|Sun)), )?" +
@"(?\d{1,2}) +" +
@"(?Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) " +
@"(?\d{2}|\d{4}) " +
@"(?\d{1,2}):" +
@"(?\d{1,2}):" +
@"(?\d{1,2}(.\d{1,7})?) " +
@"(?UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-Z]|([+-]\d{4}))$";
var match = Regex.Match(dateTimeString, rfc822DateTimePattern);
if (match.Success)
{
var day = int.Parse(match.Groups["day"].Value);
int month;
var monthName = match.Groups["monthName"].Value;
switch (monthName)
{
case "Jan": month = 1; break;
case "Feb": month = 2; break;
case "Mar": month = 3; break;
case "Apr": month = 4; break;
case "May": month = 5; break;
case "Jun": month = 6; break;
case "Jul": month = 7; break;
case "Aug": month = 8; break;
case "Sep": month = 9; break;
case "Oct": month = 10; break;
case "Nov": month = 11; break;
case "Dec": month = 12; break;
default:
var message = string.Format("\"{0}\" is not a valid RFC 822 month name.", monthName);
throw new Exception(message);
}
var yearString = match.Groups["year"].Value;
int year = int.Parse(yearString);
if (yearString.Length == 2)
{
year += 2000;
if (year - DateTime.UtcNow.Year >= 19) { year -= 100; }
}
var hour = int.Parse(match.Groups["hour"].Value);
var minutes = int.Parse(match.Groups["minutes"].Value);
var secondsString = match.Groups["seconds"].Value;
int seconds;
double milliseconds;
if (secondsString.IndexOf('.') != -1)
{
var timeSpan = TimeSpan.FromSeconds(double.Parse(secondsString));
seconds = timeSpan.Seconds;
milliseconds = timeSpan.TotalMilliseconds - seconds * 1000;
}
else
{
seconds = int.Parse(secondsString);
milliseconds = 0;
}
dateTime = new DateTime(year, month, day, hour, minutes, seconds, DateTimeKind.Utc).AddMilliseconds(milliseconds);
// check day of week before converting to UTC
var dayOfWeekString = match.Groups["dayOfWeek"].Value;
if (dayOfWeekString != "")
{
DayOfWeek dayOfWeek;
switch (dayOfWeekString)
{
case "Mon": dayOfWeek = DayOfWeek.Monday; break;
case "Tue": dayOfWeek = DayOfWeek.Tuesday; break;
case "Wed": dayOfWeek = DayOfWeek.Wednesday; break;
case "Thu": dayOfWeek = DayOfWeek.Thursday; break;
case "Fri": dayOfWeek = DayOfWeek.Friday; break;
case "Sat": dayOfWeek = DayOfWeek.Saturday; break;
case "Sun": dayOfWeek = DayOfWeek.Sunday; break;
default:
var message = string.Format("\"{0}\" is not a valid RFC 822 day name.", dayOfWeekString);
throw new Exception(message);
}
if (dateTime.DayOfWeek != dayOfWeek)
{
var message = string.Format("\"{0}\" is not the right day of the week for {1}.", dayOfWeekString, dateTime.ToString("o"));
throw new Exception(message);
}
}
TimeSpan offset;
var zone = match.Groups["zone"].Value;
switch (zone)
{
case "UT": case "GMT": case "Z": offset = TimeSpan.Zero; break;
case "EST": offset = TimeSpan.FromHours(-5); break;
case "EDT": offset = TimeSpan.FromHours(-4); break;
case "CST": offset = TimeSpan.FromHours(-6); break;
case "CDT": offset = TimeSpan.FromHours(-5); break;
case "MST": offset = TimeSpan.FromHours(-7); break;
case "MDT": offset = TimeSpan.FromHours(-6); break;
case "PST": offset = TimeSpan.FromHours(-8); break;
case "PDT": offset = TimeSpan.FromHours(-7); break;
case "A": offset = TimeSpan.FromHours(-1); break;
case "B": offset = TimeSpan.FromHours(-2); break;
case "C": offset = TimeSpan.FromHours(-3); break;
case "D": offset = TimeSpan.FromHours(-4); break;
case "E": offset = TimeSpan.FromHours(-5); break;
case "F": offset = TimeSpan.FromHours(-6); break;
case "G": offset = TimeSpan.FromHours(-7); break;
case "H": offset = TimeSpan.FromHours(-8); break;
case "I": offset = TimeSpan.FromHours(-9); break;
case "K": offset = TimeSpan.FromHours(-10); break;
case "L": offset = TimeSpan.FromHours(-11); break;
case "M": offset = TimeSpan.FromHours(-12); break;
case "N": offset = TimeSpan.FromHours(1); break;
case "O": offset = TimeSpan.FromHours(2); break;
case "P": offset = TimeSpan.FromHours(3); break;
case "Q": offset = TimeSpan.FromHours(4); break;
case "R": offset = TimeSpan.FromHours(5); break;
case "S": offset = TimeSpan.FromHours(6); break;
case "T": offset = TimeSpan.FromHours(7); break;
case "U": offset = TimeSpan.FromHours(8); break;
case "V": offset = TimeSpan.FromHours(9); break;
case "W": offset = TimeSpan.FromHours(10); break;
case "X": offset = TimeSpan.FromHours(11); break;
case "Y": offset = TimeSpan.FromHours(12); break;
default:
var offsetSign = zone.Substring(0);
var offsetHours = zone.Substring(1, 2);
var offsetMinutes = zone.Substring(3, 2);
offset = TimeSpan.FromHours(int.Parse(offsetHours)) + TimeSpan.FromMinutes(int.Parse(offsetMinutes));
if (offsetSign == "-")
{
offset = -offset;
}
break;
}
return dateTime.Add(-offset);
}
else
{
var message = string.Format("The DateTime string \"{0}\" is not a valid DateTime string for either .NET or JavaScript.", dateTimeString);
throw new Exception(message);
}
}
}
private BsonValue ParseMaxKeyExtendedJson()
{
VerifyToken(":");
VerifyToken("1");
VerifyToken("}");
return BsonMaxKey.Value;
}
private BsonValue ParseMinKeyExtendedJson()
{
VerifyToken(":");
VerifyToken("1");
VerifyToken("}");
return BsonMinKey.Value;
}
private BsonType ParseNew(out BsonValue value)
{
var typeToken = PopToken();
if (typeToken.Type != JsonTokenType.UnquotedString)
{
var message = string.Format("JSON reader expected a type name but found '{0}'.", typeToken.Lexeme);
throw new Exception(message);
}
switch (typeToken.Lexeme)
{
case "BinData":
value = ParseBinDataConstructor();
return BsonType.Binary;
case "Date":
value = ParseDateTimeConstructor(true); // withNew = true
return BsonType.DateTime;
case "HexData":
value = ParseHexDataConstructor();
return BsonType.Binary;
case "ISODate":
value = ParseISODateTimeConstructor();
return BsonType.DateTime;
case "NumberInt":
value = ParseNumberConstructor();
return BsonType.Int32;
case "NumberLong":
value = ParseNumberLongConstructor();
return BsonType.Int64;
case "ObjectId":
value = ParseObjectIdConstructor();
return BsonType.ObjectId;
case "RegExp":
value = ParseRegularExpressionConstructor();
return BsonType.RegularExpression;
case "Timestamp":
value = ParseTimestampConstructor();
return BsonType.Timestamp;
case "UUID":
case "GUID":
case "CSUUID":
case "CSGUID":
case "JUUID":
case "JGUID":
case "PYUUID":
case "PYGUID":
value = ParseUUIDConstructor(typeToken.Lexeme);
return BsonType.Binary;
default:
var message = string.Format("JSON reader expected a type name but found '{0}'.", typeToken.Lexeme);
throw new Exception(message);
}
}
private BsonValue ParseNumberConstructor()
{
VerifyToken("(");
var valueToken = PopToken();
int value;
if (valueToken.IsNumber)
{
value = valueToken.Int32Value;
}
else if (valueToken.Type == JsonTokenType.String)
{
value = int.Parse(valueToken.StringValue);
}
else
{
var message = string.Format("JSON reader expected an integer or a string but found '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
VerifyToken(")");
return new BsonInt32(value);
}
private BsonValue ParseNumberLongConstructor()
{
VerifyToken("(");
var valueToken = PopToken();
long value;
if (valueToken.Type == JsonTokenType.Int32 || valueToken.Type == JsonTokenType.Int64)
{
value = valueToken.Int64Value;
}
else if (valueToken.Type == JsonTokenType.String)
{
value = long.Parse(valueToken.StringValue);
}
else
{
var message = string.Format("JSON reader expected an integer or a string but found '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
VerifyToken(")");
return new BsonInt64(value);
}
private BsonValue ParseNumberLongExtendedJson()
{
VerifyToken(":");
var valueToken = PopToken();
if (valueToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
VerifyToken("}");
return new BsonInt64(long.Parse(valueToken.StringValue));
}
private BsonValue ParseObjectIdConstructor()
{
VerifyToken("(");
var valueToken = PopToken();
if (valueToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
VerifyToken(")");
return new BsonObjectId(ObjectId.Parse(valueToken.StringValue));
}
private BsonValue ParseObjectIdExtendedJson()
{
VerifyToken(":");
var valueToken = PopToken();
if (valueToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
VerifyToken("}");
return new BsonObjectId(ObjectId.Parse(valueToken.StringValue));
}
private BsonValue ParseRegularExpressionConstructor()
{
VerifyToken("(");
var patternToken = PopToken();
if (patternToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", patternToken.Lexeme);
throw new Exception(message);
}
var options = "";
var commaToken = PopToken();
if (commaToken.Lexeme == ",")
{
var optionsToken = PopToken();
if (optionsToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", optionsToken.Lexeme);
throw new Exception(message);
}
options = optionsToken.StringValue;
}
else
{
PushToken(commaToken);
}
VerifyToken(")");
return new BsonRegularExpression(patternToken.StringValue, options);
}
private BsonValue ParseRegularExpressionExtendedJson()
{
VerifyToken(":");
var patternToken = PopToken();
if (patternToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", patternToken.Lexeme);
throw new Exception(message);
}
var options = "";
var commaToken = PopToken();
if (commaToken.Lexeme == ",")
{
VerifyString("$options");
VerifyToken(":");
var optionsToken = PopToken();
if (optionsToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", optionsToken.Lexeme);
throw new Exception(message);
}
options = optionsToken.StringValue;
}
else
{
PushToken(commaToken);
}
VerifyToken("}");
return new BsonRegularExpression(patternToken.StringValue, options);
}
private BsonValue ParseSymbolExtendedJson()
{
VerifyToken(":");
var nameToken = PopToken();
if (nameToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", nameToken.Lexeme);
throw new Exception(message);
}
VerifyToken("}");
return new BsonString(nameToken.StringValue); // will be converted to a BsonSymbol at a higher level
}
private BsonValue ParseTimestampConstructor()
{
VerifyToken("(");
int secondsSinceEpoch;
var secondsSinceEpochToken = PopToken();
if (secondsSinceEpochToken.IsNumber)
{
secondsSinceEpoch = secondsSinceEpochToken.Int32Value;
}
else
{
var message = string.Format("JSON reader expected a number but found '{0}'.", secondsSinceEpochToken.Lexeme);
throw new Exception(message);
}
VerifyToken(",");
int increment;
var incrementToken = PopToken();
if (secondsSinceEpochToken.IsNumber)
{
increment = incrementToken.Int32Value;
}
else
{
var message = string.Format("JSON reader expected a number but found '{0}'.", secondsSinceEpochToken.Lexeme);
throw new Exception(message);
}
VerifyToken(")");
return new BsonTimestamp(secondsSinceEpoch, increment);
}
private BsonValue ParseTimestampExtendedJson()
{
VerifyToken(":");
var nextToken = PopToken();
if (nextToken.Type == JsonTokenType.BeginObject)
{
return ParseTimestampExtendedJsonNewRepresentation();
}
else
{
return ParseTimestampExtendedJsonOldRepresentation(nextToken);
}
}
private BsonValue ParseTimestampExtendedJsonNewRepresentation()
{
VerifyString("t");
VerifyToken(":");
var secondsSinceEpochToken = PopToken();
int secondsSinceEpoch;
if (secondsSinceEpochToken.IsNumber)
{
secondsSinceEpoch = secondsSinceEpochToken.Int32Value;
}
else
{
var message = string.Format("JSON reader expected an integer but found '{0}'.", secondsSinceEpochToken.Lexeme);
throw new Exception(message);
}
VerifyToken(",");
VerifyString("i");
VerifyToken(":");
var incrementToken = PopToken();
int increment;
if (incrementToken.IsNumber)
{
increment = incrementToken.Int32Value;
}
else
{
var message = string.Format("JSON reader expected an integer but found '{0}'.", incrementToken.Lexeme);
throw new Exception(message);
}
VerifyToken("}");
VerifyToken("}");
return new BsonTimestamp(secondsSinceEpoch, increment);
}
private BsonValue ParseTimestampExtendedJsonOldRepresentation(JsonToken valueToken)
{
long value;
if (valueToken.Type == JsonTokenType.Int32 || valueToken.Type == JsonTokenType.Int64)
{
value = valueToken.Int64Value;
}
else if (valueToken.Type == JsonTokenType.UnquotedString && valueToken.Lexeme == "NumberLong")
{
value = ParseNumberLongConstructor().AsInt64;
}
else
{
var message = string.Format("JSON reader expected an integer but found '{0}'.", valueToken.Lexeme);
throw new Exception(message);
}
VerifyToken("}");
return new BsonTimestamp(value);
}
private BsonValue ParseUndefinedExtendedJson()
{
VerifyToken(":");
VerifyToken("true");
VerifyToken("}");
return BsonMaxKey.Value;
}
private BsonValue ParseUUIDConstructor(string uuidConstructorName)
{
VerifyToken("(");
var bytesToken = PopToken();
if (bytesToken.Type != JsonTokenType.String)
{
var message = string.Format("JSON reader expected a string but found '{0}'.", bytesToken.Lexeme);
throw new Exception(message);
}
VerifyToken(")");
var hexString = bytesToken.StringValue.Replace("{", "").Replace("}", "").Replace("-", "");
var bytes = BsonUtils.ParseHexString(hexString);
var guid = GuidConverter.FromBytes(bytes, GuidRepresentation.Standard);
GuidRepresentation guidRepresentation;
switch (uuidConstructorName)
{
case "CSUUID":
case "CSGUID":
guidRepresentation = GuidRepresentation.CSharpLegacy;
break;
case "JUUID":
case "JGUID":
guidRepresentation = GuidRepresentation.JavaLegacy;
break;
case "PYUUID":
case "PYGUID":
guidRepresentation = GuidRepresentation.PythonLegacy;
break;
case "UUID":
case "GUID":
guidRepresentation = GuidRepresentation.Standard;
break;
default:
throw new BsonInternalException("Unexpected uuidConstructorName");
}
bytes = GuidConverter.ToBytes(guid, guidRepresentation);
var subType = (guidRepresentation == GuidRepresentation.Standard) ? BsonBinarySubType.UuidStandard : BsonBinarySubType.UuidLegacy;
return new BsonBinaryData(bytes, subType, guidRepresentation);
}
private JsonToken PopToken()
{
if (_pushedToken != null)
{
var token = _pushedToken;
_pushedToken = null;
return token;
}
else
{
return JsonScanner.GetNextToken(_buffer);
}
}
private void PushToken(JsonToken token)
{
if (_pushedToken == null)
{
_pushedToken = token;
}
else
{
throw new BsonInternalException("There is already a pending token.");
}
}
private void VerifyString(string expectedString)
{
var token = PopToken();
if ((token.Type != JsonTokenType.String && token.Type != JsonTokenType.UnquotedString) || token.StringValue != expectedString)
{
var message = string.Format("JSON reader expected '{0}' but found '{1}'.", expectedString, token.StringValue);
throw new Exception(message);
}
}
private void VerifyToken(string expectedLexeme)
{
var token = PopToken();
if (token.Lexeme != expectedLexeme)
{
var message = string.Format("JSON reader expected '{0}' but found '{1}'.", expectedLexeme, token.Lexeme);
throw new Exception(message);
}
}
}
}