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; } /// /// 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 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 == '+') { int intResult; if (Int32.TryParse(s, out intResult)) return intResult; double doubleResult; if (Double.TryParse(s, out doubleResult)) return doubleResult; } if (s == "") { throw (new Exception("Missing value")); } return s; } /// /// 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(); } } }