#region License
// Copyright (c) 2007 James Newton-King
//
// 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.
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Globalization;
using Newtonsoft.Json.Utilities;
namespace Newtonsoft.Json{
/// <summary>
/// Represents a reader that provides fast, non-cached, forward-only access to serialized Json data.
/// </summary>
public class JsonTextReader : JsonReader, IJsonLineInfo
{
private readonly TextReader _reader;
private readonly StringBuffer _buffer;
private char? _lastChar;
private int _currentLinePosition;
private int _currentLineNumber;
private bool _end;
/// <summary>
/// Initializes a new instance of the <see cref="JsonReader"/> class with the specified <see cref="TextReader"/>.
/// </summary>
/// <param name="reader">The <c>TextReader</c> containing the XML data to read.</param>
public JsonTextReader(TextReader reader)
{
if (reader == null)
throw new ArgumentNullException("reader");
_reader = reader;
_buffer = new StringBuffer(4096);
_currentLineNumber = 1;
}
private void ParseString(char quote)
{
ReadStringIntoBuffer(quote);
string text = _buffer.ToString();
_buffer.Position = 0;
if (text.StartsWith("/Date(", StringComparison.Ordinal) && text.EndsWith(")/", StringComparison.Ordinal))
{
ParseDate(text);
}
else
{
SetToken(JsonToken.String, text);
QuoteChar = quote;
}
}
private void ReadStringIntoBuffer(char quote)
{
while (true)
{
char currentChar = MoveNext();
switch (currentChar)
{
case '\0':
if (_end)
throw CreateJsonReaderException("Unterminated string. Expected delimiter: {0}. Line {1}, position {2}.", quote, _currentLineNumber, _currentLinePosition);
_buffer.Append('\0');
break;
case '\\':
if ((currentChar = MoveNext()) != '\0' || !_end)
{
switch (currentChar)
{
case 'b':
_buffer.Append('\b');
break;
case 't':
_buffer.Append('\t');
break;
case 'n':
_buffer.Append('\n');
break;
case 'f':
_buffer.Append('\f');
break;
case 'r':
_buffer.Append('\r');
break;
case '\\':
_buffer.Append('\\');
break;
case '"':
case '\'':
case '/':
_buffer.Append(currentChar);
break;
case 'u':
char[] hexValues = new char[4];
for (int i = 0; i < hexValues.Length; i++)
{
if ((currentChar = MoveNext()) != '\0' || !_end)
hexValues[i] = currentChar;
else
throw CreateJsonReaderException("Unexpected end while parsing unicode character. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
char hexChar = Convert.ToChar(int.Parse(new string(hexValues), NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo));
_buffer.Append(hexChar);
break;
default:
throw CreateJsonReaderException("Bad JSON escape sequence: {0}. Line {1}, position {2}.", @"\" + currentChar, _currentLineNumber, _currentLinePosition);
}
}
else
{
throw CreateJsonReaderException("Unterminated string. Expected delimiter: {0}. Line {1}, position {2}.", quote, _currentLineNumber, _currentLinePosition);
}
break;
case '"':
case '\'':
if (currentChar == quote)
{
return;
}
else
{
_buffer.Append(currentChar);
}
break;
default:
_buffer.Append(currentChar);
break;
}
}
}
private JsonReaderException CreateJsonReaderException(string format, params object[] args)
{
string message = format.FormatWith(CultureInfo.InvariantCulture, args);
return new JsonReaderException(message, null, _currentLineNumber, _currentLinePosition);
}
private void ParseDate(string text)
{
string value = text.Substring(6, text.Length - 8);
DateTimeKind kind = DateTimeKind.Utc;
int index = value.IndexOf('+', 1);
if (index == -1)
index = value.IndexOf('-', 1);
if (index != -1)
{
kind = DateTimeKind.Local;
value = value.Substring(0, index);
}
long javaScriptTicks = long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
DateTime utcDateTime = JsonConvert.ConvertJavaScriptTicksToDateTime(javaScriptTicks);
DateTime dateTime;
switch (kind)
{
case DateTimeKind.Unspecified:
dateTime = DateTime.SpecifyKind(utcDateTime.ToLocalTime(), DateTimeKind.Unspecified);
break;
case DateTimeKind.Local:
dateTime = utcDateTime.ToLocalTime();
break;
default:
dateTime = utcDateTime;
break;
}
SetToken(JsonToken.Date, dateTime);
}
private const int LineFeedValue = StringUtils.LineFeed;
private const int CarriageReturnValue = StringUtils.CarriageReturn;
private char MoveNext()
{
int value = _reader.Read();
switch (value)
{
case -1:
_end = true;
return '\0';
case CarriageReturnValue:
if (_reader.Peek() == LineFeedValue)
_reader.Read();
_currentLineNumber++;
_currentLinePosition = 0;
break;
case LineFeedValue:
_currentLineNumber++;
_currentLinePosition = 0;
break;
default:
_currentLinePosition++;
break;
}
return (char)value;
}
private bool HasNext()
{
return (_reader.Peek() != -1);
}
private int PeekNext()
{
return _reader.Peek();
}
/// <summary>
/// Reads the next JSON token from the stream.
/// </summary>
/// <returns>
/// true if the next token was read successfully; false if there are no more tokens to read.
/// </returns>
public override bool Read()
{
while (true)
{
char currentChar;
if (_lastChar != null)
{
currentChar = _lastChar.Value;
_lastChar = null;
}
else
{
currentChar = MoveNext();
}
if (currentChar == '\0' && _end)
return false;
switch (CurrentState)
{
case State.Start:
case State.Property:
case State.Array:
case State.ArrayStart:
case State.Constructor:
case State.ConstructorStart:
return ParseValue(currentChar);
case State.Complete:
break;
case State.Object:
case State.ObjectStart:
return ParseObject(currentChar);
case State.PostValue:
// returns true if it hits
// end of object or array
if (ParsePostValue(currentChar))
return true;
break;
case State.Closed:
break;
case State.Error:
break;
default:
throw CreateJsonReaderException("Unexpected state: {0}. Line {1}, position {2}.", CurrentState, _currentLineNumber, _currentLinePosition);
}
}
}
/// <summary>
/// Reads the next JSON token from the stream as a <see cref="T:Byte[]"/>.
/// </summary>
/// <returns>
/// A <see cref="T:Byte[]"/> or a null reference if the next JSON token is null.
/// </returns>
public override byte[] ReadAsBytes()
{
while (true)
{
char currentChar;
if (_lastChar != null)
{
currentChar = _lastChar.Value;
_lastChar = null;
}
else
{
currentChar = MoveNext();
}
if (currentChar == '\0' && _end)
throw CreateJsonReaderException("Unexpected end when reading bytes: Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
switch (CurrentState)
{
case State.PostValue:
case State.Start:
case State.Property:
case State.Array:
case State.ArrayStart:
case State.Constructor:
case State.ConstructorStart:
do
{
switch (currentChar)
{
case '"':
case '\'':
ReadStringIntoBuffer(currentChar);
byte[] data;
if (_buffer.Position == 0)
{
data = new byte[0];
}
else
{
data = Convert.FromBase64CharArray(_buffer.GetInternalBuffer(), 0, _buffer.Position);
_buffer.Position = 0;
}
SetToken(JsonToken.Bytes, data);
return data;
case 'n':
ParseNull();
return null;
case ' ':
case StringUtils.Tab:
case StringUtils.LineFeed:
case StringUtils.CarriageReturn:
// eat
break;
case ',':
SetStateBasedOnCurrent();
break;
default:
if (char.IsWhiteSpace(currentChar))
{
// eat
}
else
{
throw CreateJsonReaderException("Unexpected character encountered while parsing value: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
}
break;
}
} while ((currentChar = MoveNext()) != '\0' || !_end);
break;
default:
throw CreateJsonReaderException("Unexpected state: {0}. Line {1}, position {2}.", CurrentState, _currentLineNumber, _currentLinePosition);
}
}
}
private bool ParsePostValue(char currentChar)
{
do
{
switch (currentChar)
{
case '}':
SetToken(JsonToken.EndObject);
return true;
case ']':
SetToken(JsonToken.EndArray);
return true;
case ')':
SetToken(JsonToken.EndConstructor);
return true;
case '/':
ParseComment();
return true;
case ',':
// finished parsing
SetStateBasedOnCurrent();
return false;
case ' ':
case StringUtils.Tab:
case StringUtils.LineFeed:
case StringUtils.CarriageReturn:
// eat
break;
default:
if (char.IsWhiteSpace(currentChar))
{
// eat
}
else
{
throw CreateJsonReaderException("After parsing a value an unexpected character was encountered: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
}
break;
}
} while ((currentChar = MoveNext()) != '\0' || !_end);
return false;
}
private bool ParseObject(char currentChar)
{
do
{
switch (currentChar)
{
case '}':
SetToken(JsonToken.EndObject);
return true;
case '/':
ParseComment();
return true;
case ' ':
case StringUtils.Tab:
case StringUtils.LineFeed:
case StringUtils.CarriageReturn:
// eat
break;
default:
if (char.IsWhiteSpace(currentChar))
{
// eat
}
else
{
return ParseProperty(currentChar);
}
break;
}
} while ((currentChar = MoveNext()) != '\0' || !_end);
return false;
}
private bool ParseProperty(char firstChar)
{
char currentChar = firstChar;
char quoteChar;
if (ValidIdentifierChar(currentChar))
{
quoteChar = '\0';
currentChar = ParseUnquotedProperty(currentChar);
}
else if (currentChar == '"' || currentChar == '\'')
{
quoteChar = currentChar;
ReadStringIntoBuffer(quoteChar);
currentChar = MoveNext();
}
else
{
throw CreateJsonReaderException("Invalid property identifier character: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
}
if (currentChar != ':')
{
currentChar = MoveNext();
// finished property. skip any whitespace and move to colon
EatWhitespace(currentChar, false, out currentChar);
if (currentChar != ':')
throw CreateJsonReaderException("Invalid character after parsing property name. Expected ':' but got: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
}
SetToken(JsonToken.PropertyName, _buffer.ToString());
QuoteChar = quoteChar;
_buffer.Position = 0;
return true;
}
private bool ValidIdentifierChar(char value)
{
return (char.IsLetterOrDigit(value) || value == '_' || value == '$');
}
private char ParseUnquotedProperty(char firstChar)
{
// parse unquoted property name until whitespace or colon
_buffer.Append(firstChar);
char currentChar;
while ((currentChar = MoveNext()) != '\0' || !_end)
{
if (char.IsWhiteSpace(currentChar) || currentChar == ':')
{
return currentChar;
}
else if (ValidIdentifierChar(currentChar))
{
_buffer.Append(currentChar);
}
else
{
throw CreateJsonReaderException("Invalid JavaScript property identifier character: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
}
}
throw CreateJsonReaderException("Unexpected end when parsing unquoted property name. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
private bool ParseValue(char currentChar)
{
do
{
switch (currentChar)
{
case '"':
case '\'':
ParseString(currentChar);
return true;
case 't':
ParseTrue();
return true;
case 'f':
ParseFalse();
return true;
case 'n':
if (HasNext())
{
char next = (char)PeekNext();
if (next == 'u')
ParseNull();
else if (next == 'e')
ParseConstructor();
else
throw CreateJsonReaderException("Unexpected character encountered while parsing value: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
}
else
{
throw CreateJsonReaderException("Unexpected end. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
return true;
case 'N':
ParseNumberNaN();
return true;
case 'I':
ParseNumberPositiveInfinity();
return true;
case '-':
if (PeekNext() == 'I')
ParseNumberNegativeInfinity();
else
ParseNumber(currentChar);
return true;
case '/':
ParseComment();
return true;
case 'u':
ParseUndefined();
return true;
case '{':
SetToken(JsonToken.StartObject);
return true;
case '[':
SetToken(JsonToken.StartArray);
return true;
case '}':
SetToken(JsonToken.EndObject);
return true;
case ']':
SetToken(JsonToken.EndArray);
return true;
case ',':
SetToken(JsonToken.Undefined);
return true;
case ')':
SetToken(JsonToken.EndConstructor);
return true;
case ' ':
case StringUtils.Tab:
case StringUtils.LineFeed:
case StringUtils.CarriageReturn:
// eat
break;
default:
if (char.IsWhiteSpace(currentChar))
{
// eat
}
else if (char.IsNumber(currentChar) || currentChar == '-' || currentChar == '.')
{
ParseNumber(currentChar);
return true;
}
else
{
throw CreateJsonReaderException("Unexpected character encountered while parsing value: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
}
break;
}
} while ((currentChar = MoveNext()) != '\0' || !_end);
return false;
}
private bool EatWhitespace(char initialChar, bool oneOrMore, out char finalChar)
{
bool whitespace = false;
char currentChar = initialChar;
while (currentChar == ' ' || char.IsWhiteSpace(currentChar))
{
whitespace = true;
currentChar = MoveNext();
}
finalChar = currentChar;
return (!oneOrMore || whitespace);
}
private void ParseConstructor()
{
if (MatchValue('n', "new", true))
{
char currentChar = MoveNext();
if (EatWhitespace(currentChar, true, out currentChar))
{
while (char.IsLetter(currentChar))
{
_buffer.Append(currentChar);
currentChar = MoveNext();
}
EatWhitespace(currentChar, false, out currentChar);
if (currentChar != '(')
throw CreateJsonReaderException("Unexpected character while parsing constructor: {0}. Line {1}, position {2}.", currentChar, _currentLineNumber, _currentLinePosition);
string constructorName = _buffer.ToString();
_buffer.Position = 0;
SetToken(JsonToken.StartConstructor, constructorName);
}
}
}
private void ParseNumber(char firstChar)
{
char currentChar = firstChar;
bool nonBase10 = (firstChar == '0');
// parse until seperator character or end
bool end = false;
do
{
if (IsSeperator(currentChar))
{
end = true;
_lastChar = currentChar;
}
else
{
_buffer.Append(currentChar);
}
} while (!end && ((currentChar = MoveNext()) != '\0' || !_end));
string number = _buffer.ToString();
object numberValue;
JsonToken numberType;
if (number.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1
|| number.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1)
{
numberValue = Convert.ToDouble(number, CultureInfo.InvariantCulture);
numberType = JsonToken.Float;
}
else if (nonBase10)
{
numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? Convert.ToInt64(number, 16)
: Convert.ToInt64(number, 8);
numberType = JsonToken.Integer;
}
else
{
try
{
numberValue = Convert.ToInt64(number, CultureInfo.InvariantCulture);
}
catch (OverflowException ex)
{
throw new JsonReaderException("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, number), ex);
}
numberType = JsonToken.Integer;
}
_buffer.Position = 0;
SetToken(numberType, numberValue);
}
private void ParseComment()
{
// should have already parsed / character before reaching this method
char currentChar = MoveNext();
if (currentChar == '*')
{
while ((currentChar = MoveNext()) != '\0' || !_end)
{
if (currentChar == '*')
{
if ((currentChar = MoveNext()) != '\0' || !_end)
{
if (currentChar == '/')
{
break;
}
else
{
_buffer.Append('*');
_buffer.Append(currentChar);
}
}
}
else
{
_buffer.Append(currentChar);
}
}
}
else
{
throw CreateJsonReaderException("Error parsing comment. Expected: *. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
SetToken(JsonToken.Comment, _buffer.ToString());
_buffer.Position = 0;
}
private bool MatchValue(char firstChar, string value)
{
char currentChar = firstChar;
int i = 0;
do
{
if (currentChar != value[i])
{
break;
}
i++;
}
while (i < value.Length && ((currentChar = MoveNext()) != '\0' || !_end));
return (i == value.Length);
}
private bool MatchValue(char firstChar, string value, bool noTrailingNonSeperatorCharacters)
{
// will match value and then move to the next character, checking that it is a seperator character
bool match = MatchValue(firstChar, value);
if (!noTrailingNonSeperatorCharacters)
{
return match;
}
else
{
int c = PeekNext();
char next = (c != -1) ? (char) c : '\0';
bool matchAndNoTrainingNonSeperatorCharacters = (match && (next == '\0' || IsSeperator(next)));
return matchAndNoTrainingNonSeperatorCharacters;
}
}
private bool IsSeperator(char c)
{
switch (c)
{
case '}':
case ']':
case ',':
return true;
case '/':
// check next character to see if start of a comment
return (HasNext() && PeekNext() == '*');
case ')':
if (CurrentState == State.Constructor || CurrentState == State.ConstructorStart)
return true;
break;
case ' ':
case StringUtils.Tab:
case StringUtils.LineFeed:
case StringUtils.CarriageReturn:
return true;
default:
if (char.IsWhiteSpace(c))
return true;
break;
}
return false;
}
private void ParseTrue()
{
// check characters equal 'true'
// and that it is followed by either a seperator character
// or the text ends
if (MatchValue('t', JsonConvert.True, true))
{
SetToken(JsonToken.Boolean, true);
}
else
{
throw CreateJsonReaderException("Error parsing boolean value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
}
private void ParseNull()
{
if (MatchValue('n', JsonConvert.Null, true))
{
SetToken(JsonToken.Null);
}
else
{
throw CreateJsonReaderException("Error parsing null value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
}
private void ParseUndefined()
{
if (MatchValue('u', JsonConvert.Undefined, true))
{
SetToken(JsonToken.Undefined);
}
else
{
throw CreateJsonReaderException("Error parsing undefined value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
}
private void ParseFalse()
{
if (MatchValue('f', JsonConvert.False, true))
{
SetToken(JsonToken.Boolean, false);
}
else
{
throw CreateJsonReaderException("Error parsing boolean value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
}
private void ParseNumberNegativeInfinity()
{
if (MatchValue('-', JsonConvert.NegativeInfinity, true))
{
SetToken(JsonToken.Float, double.NegativeInfinity);
}
else
{
throw CreateJsonReaderException("Error parsing negative infinity value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
}
private void ParseNumberPositiveInfinity()
{
if (MatchValue('I', JsonConvert.PositiveInfinity, true))
{
SetToken(JsonToken.Float, double.PositiveInfinity);
}
else
{
throw CreateJsonReaderException("Error parsing positive infinity value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
}
private void ParseNumberNaN()
{
if (MatchValue('N', JsonConvert.NaN, true))
{
SetToken(JsonToken.Float, double.NaN);
}
else
{
throw CreateJsonReaderException("Error parsing NaN value. Line {0}, position {1}.", _currentLineNumber, _currentLinePosition);
}
}
/// <summary>
/// Changes the state to closed.
/// </summary>
public override void Close()
{
base.Close();
if (_reader != null)
_reader.Close();
if (_buffer != null)
_buffer.Clear();
}
/// <summary>
/// Gets a value indicating whether the class can return line information.
/// </summary>
/// <returns>
/// <c>true</c> if LineNumber and LinePosition can be provided; otherwise, <c>false</c>.
/// </returns>
public bool HasLineInfo()
{
return true;
}
/// <summary>
/// Gets the current line number.
/// </summary>
/// <value>
/// The current line number or 0 if no line information is available (for example, HasLineInfo returns false).
/// </value>
public int LineNumber
{
get
{
if (CurrentState == State.Start)
return 0;
return _currentLineNumber;
}
}
/// <summary>
/// Gets the current line position.
/// </summary>
/// <value>
/// The current line position or 0 if no line information is available (for example, HasLineInfo returns false).
/// </value>
public int LinePosition
{
get { return _currentLinePosition; }
}
}
}
|