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(); } } }