1 package org.json;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 import java.text.ParseException;
28
29 /**
30 * A JSONTokener takes a source string and extracts characters and tokens from
31 * it. It is used by the JSONObject and JSONArray constructors to parse JSON
32 * source strings.
33 *
34 * @author JSON.org
35 * @version 1
36 */
37 public class JSONTokener {
38
39 /**
40 * The index of the next character.
41 */
42 private int myIndex;
43
44 /**
45 * The source string being tokenized.
46 */
47 private String mySource;
48
49 /**
50 * Construct a JSONTokener from a string.
51 *
52 * @param s
53 * A source string.
54 */
55 public JSONTokener(String s) {
56 myIndex = 0;
57 mySource = s;
58 }
59
60 /**
61 * Back up one character. This provides a sort of lookahead capability, so
62 * that you can test for a digit or letter before attempting to parse the
63 * next number or identifier.
64 */
65 public void back() {
66 if (myIndex > 0) {
67 myIndex -= 1;
68 }
69 }
70
71 /**
72 * Get the hex value of a character (base16).
73 *
74 * @param c
75 * A character between '0' and '9' or between 'A' and 'F' or
76 * between 'a' and 'f'.
77 * @return An int between 0 and 15, or -1 if c was not a hex digit.
78 */
79 public static int dehexchar(char c) {
80 if (c >= '0' && c <= '9') {
81 return c - '0';
82 }
83 if (c >= 'A' && c <= 'F') {
84 return c + 10 - 'A';
85 }
86 if (c >= 'a' && c <= 'f') {
87 return c + 10 - 'a';
88 }
89 return -1;
90 }
91
92 /**
93 * Determine if the source string still contains characters that next() can
94 * consume.
95 *
96 * @return true if not yet at the end of the source.
97 */
98 public boolean more() {
99 return myIndex < mySource.length();
100 }
101
102 /**
103 * Get the next character in the source string.
104 *
105 * @return The next character, or 0 if past the end of the source string.
106 */
107 public char next() {
108 char c = more() ? mySource.charAt(myIndex) : 0;
109 myIndex += 1;
110 return c;
111 }
112
113 /**
114 * Consume the next character, and check that it matches a specified
115 * character.
116 *
117 * @throws ParseException
118 * if the character does not match.
119 * @param c
120 * The character to match.
121 * @return The character.
122 */
123 public char next(char c) throws ParseException {
124 char n = next();
125 if (n != c) {
126 throw syntaxError("Expected '" + c + "' and instead saw '" + n
127 + "'.");
128 }
129 return n;
130 }
131
132 /**
133 * Get the next n characters.
134 *
135 * @exception ParseException
136 * Substring bounds error if there are not n characters
137 * remaining in the source string.
138 *
139 * @param n
140 * The number of characters to take.
141 * @return A string of n characters.
142 */
143 public String next(int n) throws ParseException {
144 int i = myIndex;
145 int j = i + n;
146 if (j >= mySource.length()) {
147 throw syntaxError("Substring bounds error");
148 }
149 myIndex += n;
150 return mySource.substring(i, j);
151 }
152
153 /**
154 * Get the next char in the string, skipping whitespace and comments
155 * (slashslash, slashstar, and hash).
156 *
157 * @throws ParseException
158 * @return A character, or 0 if there are no more characters.
159 */
160 public char nextClean() throws java.text.ParseException {
161 while (true) {
162 char c = next();
163 if (c == '/') {
164 switch (next()) {
165 case '/':
166 do {
167 c = next();
168 } while (c != '\n' && c != '\r' && c != 0);
169 break;
170 case '*':
171 while (true) {
172 c = next();
173 if (c == 0) {
174 throw syntaxError("Unclosed comment.");
175 }
176 if (c == '*') {
177 if (next() == '/') {
178 break;
179 }
180 back();
181 }
182 }
183 break;
184 default:
185 back();
186 return '/';
187 }
188 } else if (c == '#') {
189 do {
190 c = next();
191 } while (c != '\n' && c != '\r' && c != 0);
192 } else if (c == 0 || c > ' ') {
193 return c;
194 }
195 }
196 }
197
198 /**
199 * Return the characters up to the next close quote character. Backslash
200 * processing is done. The formal JSON format does not allow strings in
201 * single quotes, but an implementation is allowed to accept them.
202 *
203 * @exception ParseException
204 * Unterminated string.
205 * @param quote
206 * The quoting character, either <code>"</code> <small>(double
207 * quote)</small> or <code>'</code> <small>(single
208 * quote)</small>.
209 * @return A String.
210 */
211 public String nextString(char quote) throws ParseException {
212 char c;
213 StringBuffer sb = new StringBuffer();
214 while (true) {
215 c = next();
216 switch (c) {
217 case 0:
218 case '\n':
219 case '\r':
220 throw syntaxError("Unterminated string");
221 case '\\':
222 c = next();
223 switch (c) {
224 case 'b':
225 sb.append('\b');
226 break;
227 case 't':
228 sb.append('\t');
229 break;
230 case 'n':
231 sb.append('\n');
232 break;
233 case 'f':
234 sb.append('\f');
235 break;
236 case 'r':
237 sb.append('\r');
238 break;
239 case 'u':
240 sb.append((char) Integer.parseInt(next(4), 16));
241 break;
242 case 'x':
243 sb.append((char) Integer.parseInt(next(2), 16));
244 break;
245 default:
246 sb.append(c);
247 }
248 break;
249 default:
250 if (c == quote) {
251 return sb.toString();
252 }
253 sb.append(c);
254 }
255 }
256 }
257
258 /**
259 * Get the text up but not including the specified character or the end of
260 * line, whichever comes first.
261 *
262 * @param d
263 * A delimiter character.
264 * @return A string.
265 */
266 public String nextTo(char d) {
267 StringBuffer sb = new StringBuffer();
268 while (true) {
269 char c = next();
270 if (c == d || c == 0 || c == '\n' || c == '\r') {
271 if (c != 0) {
272 back();
273 }
274 return sb.toString().trim();
275 }
276 sb.append(c);
277 }
278 }
279
280 /**
281 * Get the text up but not including one of the specified delimeter
282 * characters or the end of line, whichever comes first.
283 *
284 * @param delimiters
285 * A set of delimiter characters.
286 * @return A string, trimmed.
287 */
288 public String nextTo(String delimiters) {
289 char c;
290 StringBuffer sb = new StringBuffer();
291 while (true) {
292 c = next();
293 if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') {
294 if (c != 0) {
295 back();
296 }
297 return sb.toString().trim();
298 }
299 sb.append(c);
300 }
301 }
302
303 /**
304 * Get the next value. The value can be a Boolean, Double, Integer,
305 * JSONArray, JSONObject, or String, or the JSONObject.NULL object.
306 *
307 * @exception ParseException
308 * The source does not conform to JSON syntax.
309 *
310 * @return An object.
311 */
312 public Object nextValue() throws ParseException {
313 char c = nextClean();
314 String s;
315
316 switch (c) {
317 case '"':
318 case '\'':
319 return nextString(c);
320 case '{':
321 back();
322 return new JSONObject(this);
323 case '[':
324 back();
325 return new JSONArray(this);
326 }
327
328
329
330
331
332
333
334
335
336
337 StringBuffer sb = new StringBuffer();
338 char b = c;
339 while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
340 sb.append(c);
341 c = next();
342 }
343 back();
344
345
346
347
348
349 s = sb.toString().trim();
350 if (s.equals("")) {
351 throw syntaxError("Missing value.");
352 }
353 if (s.equalsIgnoreCase("true")) {
354 return Boolean.TRUE;
355 }
356 if (s.equalsIgnoreCase("false")) {
357 return Boolean.FALSE;
358 }
359 if (s.equalsIgnoreCase("null")) {
360 return JSONObject.NULL;
361 }
362
363
364
365
366
367
368
369
370
371 if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
372 if (b == '0') {
373 if (s.length() > 2
374 && (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
375 try {
376 return new Integer(Integer.parseInt(s.substring(2), 16));
377 } catch (Exception e) {
378 }
379 } else {
380 try {
381 return new Integer(Integer.parseInt(s, 8));
382 } catch (Exception e) {
383 }
384 }
385 }
386 try {
387 return new Integer(s);
388 } catch (Exception e) {
389 }
390 try {
391 return new Double(s);
392 } catch (Exception e) {
393 }
394 }
395 return s;
396 }
397
398 /**
399 * Skip characters until the next character is the requested character. If
400 * the requested character is not found, no characters are skipped.
401 *
402 * @param to
403 * A character to skip to.
404 * @return The requested character, or zero if the requested character is
405 * not found.
406 */
407 public char skipTo(char to) {
408 char c;
409 int index = myIndex;
410 do {
411 c = next();
412 if (c == 0) {
413 myIndex = index;
414 return c;
415 }
416 } while (c != to);
417 back();
418 return c;
419 }
420
421 /**
422 * Skip characters until past the requested string. If it is not found, we
423 * are left at the end of the source.
424 *
425 * @param to
426 * A string to skip past.
427 */
428 public void skipPast(String to) {
429 myIndex = mySource.indexOf(to, myIndex);
430 if (myIndex < 0) {
431 myIndex = mySource.length();
432 } else {
433 myIndex += to.length();
434 }
435 }
436
437 /**
438 * Make a ParseException to signal a syntax error.
439 *
440 * @param message
441 * The error message.
442 * @return A ParseException object, suitable for throwing
443 */
444 public ParseException syntaxError(String message) {
445 return new ParseException(message + toString(), myIndex);
446 }
447
448 /**
449 * Make a printable string of this JSONTokener.
450 *
451 * @return " at character [myIndex] of [mySource]"
452 */
453 public String toString() {
454 return " at character " + myIndex + " of " + mySource;
455 }
456
457 /**
458 * Convert <code>%</code><i>hh</i> sequences to single characters, and
459 * convert plus to space.
460 *
461 * @param s
462 * A string that may contain <code>+</code> <small>(plus)</small>
463 * and <code>%</code><i>hh</i> sequences.
464 * @return The unescaped string.
465 */
466 public static String unescape(String s) {
467 int len = s.length();
468 StringBuffer b = new StringBuffer();
469 for (int i = 0; i < len; ++i) {
470 char c = s.charAt(i);
471 if (c == '+') {
472 c = ' ';
473 } else if (c == '%' && i + 2 < len) {
474 int d = dehexchar(s.charAt(i + 1));
475 int e = dehexchar(s.charAt(i + 2));
476 if (d >= 0 && e >= 0) {
477 c = (char) (d * 16 + e);
478 i += 2;
479 }
480 }
481 b.append(c);
482 }
483 return b.toString();
484 }
485 }