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