using System;
using System.Text;
using System.Globalization;
namespace Nii.JSON
{
///
///
/// A JSONTokener takes a source string and extracts characters and tokens from
/// it. It is used by the JSONObject and JSONArray constructors to parse
/// JSON source strings.
///
///
/// Public Domain 2002 JSON.org
/// @author JSON.org
/// @version 0.1
///
/// Ported to C# by Are Bjolseth, teleplan.no
///
///
/// - Implement Custom exceptions
/// - Add unit testing
/// - Add log4net
///
///
///
public class JSONTokener
{
/// The index of the next character.
private int myIndex;
/// The source string being tokenized.
private string mySource;
///
/// Construct a JSONTokener from a string.
///
/// A source string.
public JSONTokener(string s)
{
myIndex = 0;
mySource = s;
}
///
/// Back up one character. This provides a sort of lookahead capability,
/// so that you can test for a digit or letter before attempting to parse
/// the next number or identifier.
///
public void back()
{
if (myIndex > 0)
myIndex -= 1;
}
///
/// Get the hex value of a character (base16).
///
///
/// A character between '0' and '9' or between 'A' and 'F' or
/// between 'a' and 'f'.
///
/// An int between 0 and 15, or -1 if c was not a hex digit.
public static int dehexchar(char c)
{
if (c >= '0' && c <= '9')
{
return c - '0';
}
if (c >= 'A' && c <= 'F')
{
return c + 10 - 'A';
}
if (c >= 'a' && c <= 'f')
{
return c + 10 - 'a';
}
return -1;
}
///
/// Determine if the source string still contains characters that next() can consume.
///
/// true if not yet at the end of the source.
public bool more()
{
return myIndex < mySource.Length;
}
///
/// Get the next character in the source string.
///
/// The next character, or 0 if past the end of the source string.
public char next()
{
char c = more() ? mySource[myIndex] : (char)0;
myIndex +=1;
return c;
}
///
/// Consume the next character, and check that it matches a specified character
///
/// The character to match.
/// The character.
public char next(char c)
{
char n = next();
if (n != c)
{
string msg = "Expected '" + c + "' and instead saw '" + n + "'.";
throw (new Exception(msg));
}
return n;
}
///
/// Get the next n characters.
///
/// The number of characters to take.
/// A string of n characters.
public string next(int n)
{
int i = myIndex;
int j = i + n;
if (j >= mySource.Length)
{
string msg = "Substring bounds error";
throw (new Exception(msg));
}
myIndex += n;
return mySource.Substring(i,j);
}
///
/// Get the next char in the string, skipping whitespace
/// and comments (slashslash and slashstar).
///
/// A character, or 0 if there are no more characters.
public char nextClean()
{
while (true)
{
char c = next();
if (c == '/')
{
switch (next())
{
case '/':
do
{
c = next();
} while (c != '\n' && c != '\r' && c != 0);
break;
case '*':
while (true)
{
c = next();
if (c == 0)
{
throw (new Exception("Unclosed comment."));
}
if (c == '*')
{
if (next() == '/')
{
break;
}
back();
}
}
break;
default:
back();
return '/';
}
}
else if (c == 0 || c > ' ')
{
return c;
}
}
}
///
/// Return the characters up to the next close quote character.
/// Backslash processing is done. The formal JSON format does not
/// allow strings in single quotes, but an implementation is allowed to
/// accept them.
///
/// The quoting character, either " or '
/// A String.
public string nextString(char quote)
{
char c;
StringBuilder sb = new StringBuilder();
while (true)
{
c = next();
if ((c == 0x00) || (c == 0x0A) || (c == 0x0D))
{
throw (new Exception("Unterminated string"));
}
// CTRL chars
if (c == '\\')
{
c = next();
switch (c)
{
case 'b': //Backspace
sb.Append('\b');
break;
case 't': //Horizontal tab
sb.Append('\t');
break;
case 'n': //newline
sb.Append('\n');
break;
case 'f': //Form feed
sb.Append('\f');
break;
case 'r': // Carriage return
sb.Append('\r');
break;
case 'u':
//sb.append((char)Integer.parseInt(next(4), 16)); // 16 == radix, ie. hex
int iascii = int.Parse(next(4),System.Globalization.NumberStyles.HexNumber);
sb.Append((char)iascii);
break;
default:
sb.Append(c);
break;
}
}
else
{
if (c == quote)
{
return sb.ToString();
}
sb.Append(c);
}
}//END-while
}
///
/// Get the text up but not including the specified character or the
/// end of line, whichever comes first.
///
/// A delimiter character.
/// A string.
public string nextTo(char d)
{
StringBuilder sb = new StringBuilder();
while (true)
{
char c = next();
if (c == d || c == (char)0 || c == '\n' || c == '\r')
{
if (c != (char)0)
{
back();
}
return sb.ToString().Trim();
}
sb.Append(c);
}
}
///
/// Get the text up but not including one of the specified delimeter
/// characters or the end of line, which ever comes first.
///
/// A set of delimiter characters.
/// A string, trimmed.
public string nextTo(string delimiters)
{
char c;
StringBuilder sb = new StringBuilder();
while (true)
{
c = next();
if ((delimiters.IndexOf(c) >= 0) || (c == (char)0 ) || (c == '\n') || (c == '\r'))
{
if (c != (char)0)
{
back();
}
return sb.ToString().Trim();
}
sb.Append(c);
}
}
///
/// Get the next value as object. The value can be a Boolean, Double, Integer,
/// JSONArray, JSONObject, or String, or the JSONObject.NULL object.
///
/// An object.
public object nextObject()
{
char c = nextClean();
string s;
if (c == '"' || c == '\'')
{
return nextString(c);
}
// Object
if (c == '{')
{
back();
return new JSONObject(this);
}
// JSON Array
if (c == '[')
{
back();
return new JSONArray(this);
}
StringBuilder sb = new StringBuilder();
char b = c;
while (c >= ' ' && c != ':' && c != ',' && c != ']' && c != '}' && c != '/')
{
sb.Append(c);
c = next();
}
back();
s = sb.ToString().Trim();
if (s == "true")
return bool.Parse("true");
if (s == "false")
return bool.Parse("false");
if (s == "null")
return JSONObject.NULL;
if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+')
{
try
{
return Convert.ToInt32(s);
}
catch (Exception e)
{
string msg = e.Message;
}
try
{
return Convert.ToDouble(s, NumberFormatInfo.InvariantInfo);
}
catch (Exception e)
{
string msg = e.Message;
}
}
if (s == "")
{
throw (new Exception("Missing value"));
}
return s;
}
///
/// Skip characters until the next character is the requested character.
/// If the requested character is not found, no characters are skipped.
///
/// A character to skip to.
///
/// The requested character, or zero if the requested character is not found.
///
public char skipTo(char to)
{
char c;
int i = myIndex;
do
{
c = next();
if (c == (char)0)
{
myIndex = i;
return c;
}
}while (c != to);
back();
return c;
}
///
/// Skip characters until past the requested string.
/// If it is not found, we are left at the end of the source.
///
/// A string to skip past.
public void skipPast(string to)
{
myIndex = mySource.IndexOf(to, myIndex);
if (myIndex < 0)
{
myIndex = mySource.Length;
}
else
{
myIndex += to.Length;
}
}
// TODO implement exception SyntaxError
///
/// Make a printable string of this JSONTokener.
///
/// " at character [myIndex] of [mySource]"
public override string ToString()
{
return " at charachter " + myIndex + " of " + mySource;
}
///
/// Unescape the source text. Convert %hh sequences to single characters,
/// and convert plus to space. There are Web transport systems that insist on
/// doing unnecessary URL encoding. This provides a way to undo it.
///
public void unescape()
{
mySource = unescape(mySource);
}
///
/// Convert %hh sequences to single characters, and convert plus to space.
///
/// A string that may contain plus and %hh sequences.
/// The unescaped string.
public static string unescape(string s)
{
int len = s.Length;
StringBuilder sb = new StringBuilder();
for (int i=0; i < len; i++)
{
char c = s[i];
if (c == '+')
{
c = ' ';
}
else if (c == '%' && (i + 2 < len))
{
int d = dehexchar(s[i+1]);
int e = dehexchar(s[i+2]);
if (d >= 0 && e >= 0)
{
c = (char)(d*16 + e);
i += 2;
}
}
sb.Append(c);
}
return sb.ToString();
}
}
}