JSONTokener.java revision adc854b798c1cfe3bfd4c27d68d5cee38ca617da
1package org.json;
2
3/*
4Copyright (c) 2002 JSON.org
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16The Software shall be used for Good, not Evil.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24SOFTWARE.
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 */
34public 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>&nbsp;<small>(double quote)</small> or
206     *      <code>'</code>&nbsp;<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        StringBuilder sb = new StringBuilder();
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        StringBuilder sb = new StringBuilder();
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        StringBuilder sb = new StringBuilder();
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         * Handle unquoted text. This could be the values true, false, or
327         * null, or it can be a number. An implementation (such as this one)
328         * is allowed to also accept non-standard forms.
329         *
330         * Accumulate characters until we reach the end of the text or a
331         * formatting character.
332         */
333
334        StringBuilder sb = new StringBuilder();
335        char b = c;
336        while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
337            sb.append(c);
338            c = next();
339        }
340        back();
341
342        /*
343         * If it is true, false, or null, return the proper value.
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         * If it might be a number, try converting it. We support the 0- and 0x-
362         * conventions. If a number cannot be produced, then the value will just
363         * be a string. Note that the 0-, 0x-, plus, and implied string
364         * conventions are non-standard. A JSON parser is free to accept
365         * non-JSON forms as long as it accepts all correct JSON forms.
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 new Integer(Integer.parseInt(s.substring(2),
374                                16));
375                    } catch (Exception e) {
376                        /* Ignore the error */
377                    }
378                } else {
379                    try {
380                        return new Integer(Integer.parseInt(s, 8));
381                    } catch (Exception e) {
382                        /* Ignore the error */
383                    }
384                }
385            }
386            try {
387                return new Integer(s);
388            } catch (Exception e) {
389                try {
390                    return new Long(s);
391                } catch (Exception f) {
392                    try {
393                        return new Double(s);
394                    }  catch (Exception g) {
395                        return s;
396                    }
397                }
398            }
399        }
400        return s;
401    }
402
403
404    /**
405     * Skip characters until the next character is the requested character.
406     * If the requested character is not found, no characters are skipped.
407     * @param to A character to skip to.
408     * @return The requested character, or zero if the requested character
409     * is not found.
410     */
411    public char skipTo(char to) {
412        char c;
413        int index = this.myIndex;
414        do {
415            c = next();
416            if (c == 0) {
417                this.myIndex = index;
418                return c;
419            }
420        } while (c != to);
421        back();
422        return c;
423    }
424
425
426    /**
427     * Skip characters until past the requested string.
428     * If it is not found, we are left at the end of the source.
429     * @param to A string to skip past.
430     */
431    public void skipPast(String to) {
432        this.myIndex = this.mySource.indexOf(to, this.myIndex);
433        if (this.myIndex < 0) {
434            this.myIndex = this.mySource.length();
435        } else {
436            this.myIndex += to.length();
437        }
438    }
439
440
441    /**
442     * Make a JSONException to signal a syntax error.
443     *
444     * @param message The error message.
445     * @return  A JSONException object, suitable for throwing
446     */
447    public JSONException syntaxError(String message) {
448        return new JSONException(message + toString());
449    }
450
451
452    /**
453     * Make a printable string of this JSONTokener.
454     *
455     * @return " at character [this.myIndex] of [this.mySource]"
456     */
457    public String toString() {
458        return " at character " + this.myIndex + " of " + this.mySource;
459    }
460}