root / src / org / json / JSONTokener.java @ 4bf05cad
History | View | Annotate | Download (11.2 kB)
1 |
package org.json; |
---|---|
2 |
|
3 |
import java.io.BufferedReader; |
4 |
import java.io.IOException; |
5 |
import java.io.Reader; |
6 |
import java.io.StringReader; |
7 |
|
8 |
/*
|
9 |
Copyright (c) 2002 JSON.org
|
10 |
|
11 |
Permission is hereby granted, free of charge, to any person obtaining a copy
|
12 |
of this software and associated documentation files (the "Software"), to deal
|
13 |
in the Software without restriction, including without limitation the rights
|
14 |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
15 |
copies of the Software, and to permit persons to whom the Software is
|
16 |
furnished to do so, subject to the following conditions:
|
17 |
|
18 |
The above copyright notice and this permission notice shall be included in all
|
19 |
copies or substantial portions of the Software.
|
20 |
|
21 |
The Software shall be used for Good, not Evil.
|
22 |
|
23 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
24 |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
25 |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
26 |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
27 |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
28 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
29 |
SOFTWARE.
|
30 |
*/
|
31 |
|
32 |
/**
|
33 |
* A JSONTokener takes a source string and extracts characters and tokens from
|
34 |
* it. It is used by the JSONObject and JSONArray constructors to parse
|
35 |
* JSON source strings.
|
36 |
* @author JSON.org
|
37 |
* @version 2008-09-18
|
38 |
*/
|
39 |
public class JSONTokener { |
40 |
|
41 |
private int index; |
42 |
private Reader reader; |
43 |
private char lastChar; |
44 |
private boolean useLastChar; |
45 |
|
46 |
|
47 |
/**
|
48 |
* Construct a JSONTokener from a string.
|
49 |
*
|
50 |
* @param aReader A reader.
|
51 |
*/
|
52 |
public JSONTokener(Reader aReader) { |
53 |
reader = aReader.markSupported() ? |
54 |
aReader : new BufferedReader(aReader); |
55 |
useLastChar = false;
|
56 |
index = 0;
|
57 |
} |
58 |
|
59 |
|
60 |
/**
|
61 |
* Construct a JSONTokener from a string.
|
62 |
*
|
63 |
* @param s A source string.
|
64 |
*/
|
65 |
public JSONTokener(String s) { |
66 |
this(new StringReader(s)); |
67 |
} |
68 |
|
69 |
|
70 |
/**
|
71 |
* Back up one character. This provides a sort of lookahead capability,
|
72 |
* so that you can test for a digit or letter before attempting to parse
|
73 |
* the next number or identifier.
|
74 |
*/
|
75 |
public void back() throws JSONException { |
76 |
if (useLastChar || index <= 0) |
77 |
throw new JSONException("Stepping back two steps is not supported"); |
78 |
index -= 1;
|
79 |
useLastChar = true;
|
80 |
} |
81 |
|
82 |
|
83 |
|
84 |
/**
|
85 |
* Get the hex value of a character (base16).
|
86 |
* @param c A character between '0' and '9' or between 'A' and 'F' or
|
87 |
* between 'a' and 'f'.
|
88 |
* @return An int between 0 and 15, or -1 if c was not a hex digit.
|
89 |
*/
|
90 |
public static int dehexchar(char c) { |
91 |
if (c >= '0' && c <= '9') |
92 |
return c - '0'; |
93 |
if (c >= 'A' && c <= 'F') |
94 |
return c - ('A' - 10); |
95 |
if (c >= 'a' && c <= 'f') |
96 |
return c - ('a' - 10); |
97 |
return -1; |
98 |
} |
99 |
|
100 |
|
101 |
/**
|
102 |
* Determine if the source string still contains characters that next()
|
103 |
* can consume.
|
104 |
* @return true if not yet at the end of the source.
|
105 |
*/
|
106 |
public boolean more() throws JSONException { |
107 |
char nextChar = next();
|
108 |
if (nextChar == 0) |
109 |
return false; |
110 |
back(); |
111 |
return true; |
112 |
} |
113 |
|
114 |
|
115 |
/**
|
116 |
* Get the next character in the source string.
|
117 |
*
|
118 |
* @return The next character, or 0 if past the end of the source string.
|
119 |
*/
|
120 |
public char next() throws JSONException { |
121 |
if (useLastChar) {
|
122 |
useLastChar = false;
|
123 |
if (lastChar != 0) |
124 |
index += 1;
|
125 |
return lastChar;
|
126 |
} |
127 |
int c;
|
128 |
try {
|
129 |
c = reader.read(); |
130 |
} catch (IOException exc) { |
131 |
throw new JSONException(exc); |
132 |
} |
133 |
|
134 |
if (c <= 0) { // End of stream |
135 |
lastChar = 0;
|
136 |
return 0; |
137 |
} |
138 |
index += 1;
|
139 |
lastChar = (char) c;
|
140 |
return lastChar;
|
141 |
} |
142 |
|
143 |
|
144 |
/**
|
145 |
* Consume the next character, and check that it matches a specified
|
146 |
* character.
|
147 |
* @param c The character to match.
|
148 |
* @return The character.
|
149 |
* @throws JSONException if the character does not match.
|
150 |
*/
|
151 |
public char next(char c) throws JSONException { |
152 |
char n = next();
|
153 |
if (n != c)
|
154 |
throw syntaxError("Expected '" + c + "' and instead saw '" + |
155 |
n + "'");
|
156 |
return n;
|
157 |
} |
158 |
|
159 |
|
160 |
/**
|
161 |
* Get the next n characters.
|
162 |
*
|
163 |
* @param n The number of characters to take.
|
164 |
* @return A string of n characters.
|
165 |
* @throws JSONException
|
166 |
* Substring bounds error if there are not
|
167 |
* n characters remaining in the source string.
|
168 |
*/
|
169 |
public String next(int n) throws JSONException { |
170 |
if (n == 0) |
171 |
return ""; |
172 |
|
173 |
char[] buffer = new char[n]; |
174 |
int pos = 0; |
175 |
|
176 |
if (useLastChar) {
|
177 |
useLastChar = false;
|
178 |
buffer[0] = lastChar;
|
179 |
pos = 1;
|
180 |
} |
181 |
|
182 |
try {
|
183 |
int len;
|
184 |
while (pos < n && (len = reader.read(buffer, pos, n - pos)) != -1) |
185 |
pos += len; |
186 |
} catch (IOException exc) { |
187 |
throw new JSONException(exc); |
188 |
} |
189 |
index += pos; |
190 |
|
191 |
if (pos < n)
|
192 |
throw syntaxError("Substring bounds error"); |
193 |
|
194 |
lastChar = buffer[n - 1];
|
195 |
return new String(buffer); |
196 |
} |
197 |
|
198 |
|
199 |
/**
|
200 |
* Get the next char in the string, skipping whitespace.
|
201 |
* @throws JSONException
|
202 |
* @return A character, or 0 if there are no more characters.
|
203 |
*/
|
204 |
public char nextClean() throws JSONException { |
205 |
for (;;) {
|
206 |
char c = next();
|
207 |
if (c == 0 || c > ' ') |
208 |
return c;
|
209 |
} |
210 |
} |
211 |
|
212 |
|
213 |
/**
|
214 |
* Return the characters up to the next close quote character.
|
215 |
* Backslash processing is done. The formal JSON format does not
|
216 |
* allow strings in single quotes, but an implementation is allowed to
|
217 |
* accept them.
|
218 |
* @param quote The quoting character, either
|
219 |
* <code>"</code> <small>(double quote)</small> or
|
220 |
* <code>'</code> <small>(single quote)</small>.
|
221 |
* @return A String.
|
222 |
* @throws JSONException Unterminated string.
|
223 |
*/
|
224 |
public String nextString(char quote) throws JSONException { |
225 |
char c;
|
226 |
StringBuffer sb = new StringBuffer(); |
227 |
for (;;) {
|
228 |
c = next(); |
229 |
switch (c) {
|
230 |
case 0: |
231 |
case '\n': |
232 |
case '\r': |
233 |
throw syntaxError("Unterminated string"); |
234 |
case '\\': |
235 |
c = next(); |
236 |
switch (c) {
|
237 |
case 'b': |
238 |
sb.append('\b');
|
239 |
break;
|
240 |
case 't': |
241 |
sb.append('\t');
|
242 |
break;
|
243 |
case 'n': |
244 |
sb.append('\n');
|
245 |
break;
|
246 |
case 'f': |
247 |
sb.append('\f');
|
248 |
break;
|
249 |
case 'r': |
250 |
sb.append('\r');
|
251 |
break;
|
252 |
case 'u': |
253 |
sb.append((char)Integer.parseInt(next(4), 16)); |
254 |
break;
|
255 |
case 'x' : |
256 |
sb.append((char) Integer.parseInt(next(2), 16)); |
257 |
break;
|
258 |
default:
|
259 |
sb.append(c); |
260 |
} |
261 |
break;
|
262 |
default:
|
263 |
if (c == quote)
|
264 |
return sb.toString();
|
265 |
sb.append(c); |
266 |
} |
267 |
} |
268 |
} |
269 |
|
270 |
|
271 |
/**
|
272 |
* Get the text up but not including the specified character or the
|
273 |
* end of line, whichever comes first.
|
274 |
* @param d A delimiter character.
|
275 |
* @return A string.
|
276 |
*/
|
277 |
public String nextTo(char d) throws JSONException { |
278 |
StringBuffer sb = new StringBuffer(); |
279 |
for (;;) {
|
280 |
char c = next();
|
281 |
if (c == d || c == 0 || c == '\n' || c == '\r') { |
282 |
if (c != 0) |
283 |
back(); |
284 |
return sb.toString().trim();
|
285 |
} |
286 |
sb.append(c); |
287 |
} |
288 |
} |
289 |
|
290 |
|
291 |
/**
|
292 |
* Get the text up but not including one of the specified delimiter
|
293 |
* characters or the end of line, whichever comes first.
|
294 |
* @param delimiters A set of delimiter characters.
|
295 |
* @return A string, trimmed.
|
296 |
*/
|
297 |
public String nextTo(String delimiters) throws JSONException { |
298 |
char c;
|
299 |
StringBuffer sb = new StringBuffer(); |
300 |
for (;;) {
|
301 |
c = next(); |
302 |
if (delimiters.indexOf(c) >= 0 || c == 0 || |
303 |
c == '\n' || c == '\r') { |
304 |
if (c != 0) |
305 |
back(); |
306 |
return sb.toString().trim();
|
307 |
} |
308 |
sb.append(c); |
309 |
} |
310 |
} |
311 |
|
312 |
|
313 |
/**
|
314 |
* Get the next value. The value can be a Boolean, Double, Integer,
|
315 |
* JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
|
316 |
* @throws JSONException If syntax error.
|
317 |
*
|
318 |
* @return An object.
|
319 |
*/
|
320 |
public Object nextValue() throws JSONException { |
321 |
char c = nextClean();
|
322 |
String s;
|
323 |
|
324 |
switch (c) {
|
325 |
case '"': |
326 |
case '\'': |
327 |
return nextString(c);
|
328 |
case '{': |
329 |
back(); |
330 |
return new JSONObject(this); |
331 |
case '[': |
332 |
case '(': |
333 |
back(); |
334 |
return new JSONArray(this); |
335 |
} |
336 |
|
337 |
/*
|
338 |
* Handle unquoted text. This could be the values true, false, or
|
339 |
* null, or it can be a number. An implementation (such as this one)
|
340 |
* is allowed to also accept non-standard forms.
|
341 |
*
|
342 |
* Accumulate characters until we reach the end of the text or a
|
343 |
* formatting character.
|
344 |
*/
|
345 |
|
346 |
StringBuffer sb = new StringBuffer(); |
347 |
while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { |
348 |
sb.append(c); |
349 |
c = next(); |
350 |
} |
351 |
back(); |
352 |
|
353 |
s = sb.toString().trim(); |
354 |
if (s.equals("")) |
355 |
throw syntaxError("Missing value"); |
356 |
return JSONObject.stringToValue(s);
|
357 |
} |
358 |
|
359 |
|
360 |
/**
|
361 |
* Skip characters until the next character is the requested character.
|
362 |
* If the requested character is not found, no characters are skipped.
|
363 |
* @param to A character to skip to.
|
364 |
* @return The requested character, or zero if the requested character
|
365 |
* is not found.
|
366 |
*/
|
367 |
public char skipTo(char to) throws JSONException { |
368 |
char c;
|
369 |
try {
|
370 |
int startIndex = index;
|
371 |
reader.mark(Integer.MAX_VALUE);
|
372 |
do {
|
373 |
c = next(); |
374 |
if (c == 0) { |
375 |
reader.reset(); |
376 |
index = startIndex; |
377 |
return c;
|
378 |
} |
379 |
} while (c != to);
|
380 |
} catch (IOException exc) { |
381 |
throw new JSONException(exc); |
382 |
} |
383 |
|
384 |
back(); |
385 |
return c;
|
386 |
} |
387 |
|
388 |
/**
|
389 |
* Make a JSONException to signal a syntax error.
|
390 |
*
|
391 |
* @param message The error message.
|
392 |
* @return A JSONException object, suitable for throwing
|
393 |
*/
|
394 |
public JSONException syntaxError(String message) { |
395 |
return new JSONException(message + toString()); |
396 |
} |
397 |
|
398 |
|
399 |
/**
|
400 |
* Make a printable string of this JSONTokener.
|
401 |
*
|
402 |
* @return " at character [this.index]"
|
403 |
*/
|
404 |
@Override
|
405 |
public String toString() { |
406 |
return " at character " + index; |
407 |
} |
408 |
} |