root / docs / www / colonyscout / internal / includes / uploadify / com / adobe / serialization / json / JSONTokenizer.as @ f59acf11
History | View | Annotate | Download (14.6 KB)
1 |
/* |
---|---|
2 |
Copyright (c) 2008, Adobe Systems Incorporated |
3 |
All rights reserved. |
4 |
|
5 |
Redistribution and use in source and binary forms, with or without |
6 |
modification, are permitted provided that the following conditions are |
7 |
met: |
8 |
|
9 |
* Redistributions of source code must retain the above copyright notice, |
10 |
this list of conditions and the following disclaimer. |
11 |
|
12 |
* Redistributions in binary form must reproduce the above copyright |
13 |
notice, this list of conditions and the following disclaimer in the |
14 |
documentation and/or other materials provided with the distribution. |
15 |
|
16 |
* Neither the name of Adobe Systems Incorporated nor the names of its |
17 |
contributors may be used to endorse or promote products derived from |
18 |
this software without specific prior written permission. |
19 |
|
20 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
21 |
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
22 |
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
23 |
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
24 |
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
25 |
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
26 |
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
27 |
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
28 |
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
29 |
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 |
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 |
*/ |
32 |
|
33 |
package com.adobe.serialization.json { |
34 |
|
35 |
public class JSONTokenizer { |
36 |
|
37 |
/** The object that will get parsed from the JSON string */ |
38 |
private var obj:Object; |
39 |
|
40 |
/** The JSON string to be parsed */ |
41 |
private var jsonString:String; |
42 |
|
43 |
/** The current parsing location in the JSON string */ |
44 |
private var loc:int; |
45 |
|
46 |
/** The current character in the JSON string during parsing */ |
47 |
private var ch:String; |
48 |
|
49 |
/** |
50 |
* Constructs a new JSONDecoder to parse a JSON string |
51 |
* into a native object. |
52 |
* |
53 |
* @param s The JSON string to be converted |
54 |
* into a native object |
55 |
*/ |
56 |
public function JSONTokenizer( s:String ) { |
57 |
jsonString = s; |
58 |
loc = 0; |
59 |
|
60 |
// prime the pump by getting the first character |
61 |
nextChar(); |
62 |
} |
63 |
|
64 |
/** |
65 |
* Gets the next token in the input sting and advances |
66 |
* the character to the next character after the token |
67 |
*/ |
68 |
public function getNextToken():JSONToken { |
69 |
var token:JSONToken = new JSONToken(); |
70 |
|
71 |
// skip any whitespace / comments since the last |
72 |
// token was read |
73 |
skipIgnored(); |
74 |
|
75 |
// examine the new character and see what we have... |
76 |
switch ( ch ) { |
77 |
|
78 |
case '{': |
79 |
token.type = JSONTokenType.LEFT_BRACE; |
80 |
token.value = '{'; |
81 |
nextChar(); |
82 |
break |
83 |
|
84 |
case '}': |
85 |
token.type = JSONTokenType.RIGHT_BRACE; |
86 |
token.value = '}'; |
87 |
nextChar(); |
88 |
break |
89 |
|
90 |
case '[': |
91 |
token.type = JSONTokenType.LEFT_BRACKET; |
92 |
token.value = '['; |
93 |
nextChar(); |
94 |
break |
95 |
|
96 |
case ']': |
97 |
token.type = JSONTokenType.RIGHT_BRACKET; |
98 |
token.value = ']'; |
99 |
nextChar(); |
100 |
break |
101 |
|
102 |
case ',': |
103 |
token.type = JSONTokenType.COMMA; |
104 |
token.value = ','; |
105 |
nextChar(); |
106 |
break |
107 |
|
108 |
case ':': |
109 |
token.type = JSONTokenType.COLON; |
110 |
token.value = ':'; |
111 |
nextChar(); |
112 |
break; |
113 |
|
114 |
case 't': // attempt to read true |
115 |
var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar(); |
116 |
|
117 |
if ( possibleTrue == "true" ) { |
118 |
token.type = JSONTokenType.TRUE; |
119 |
token.value = true; |
120 |
nextChar(); |
121 |
} else { |
122 |
parseError( "Expecting 'true' but found " + possibleTrue ); |
123 |
} |
124 |
|
125 |
break; |
126 |
|
127 |
case 'f': // attempt to read false |
128 |
var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar(); |
129 |
|
130 |
if ( possibleFalse == "false" ) { |
131 |
token.type = JSONTokenType.FALSE; |
132 |
token.value = false; |
133 |
nextChar(); |
134 |
} else { |
135 |
parseError( "Expecting 'false' but found " + possibleFalse ); |
136 |
} |
137 |
|
138 |
break; |
139 |
|
140 |
case 'n': // attempt to read null |
141 |
|
142 |
var possibleNull:String = "n" + nextChar() + nextChar() + nextChar(); |
143 |
|
144 |
if ( possibleNull == "null" ) { |
145 |
token.type = JSONTokenType.NULL; |
146 |
token.value = null; |
147 |
nextChar(); |
148 |
} else { |
149 |
parseError( "Expecting 'null' but found " + possibleNull ); |
150 |
} |
151 |
|
152 |
break; |
153 |
|
154 |
case '"': // the start of a string |
155 |
token = readString(); |
156 |
break; |
157 |
|
158 |
default: |
159 |
// see if we can read a number |
160 |
if ( isDigit( ch ) || ch == '-' ) { |
161 |
token = readNumber(); |
162 |
} else if ( ch == '' ) { |
163 |
// check for reading past the end of the string |
164 |
return null; |
165 |
} else { |
166 |
// not sure what was in the input string - it's not |
167 |
// anything we expected |
168 |
parseError( "Unexpected " + ch + " encountered" ); |
169 |
} |
170 |
} |
171 |
|
172 |
return token; |
173 |
} |
174 |
|
175 |
/** |
176 |
* Attempts to read a string from the input string. Places |
177 |
* the character location at the first character after the |
178 |
* string. It is assumed that ch is " before this method is called. |
179 |
* |
180 |
* @return the JSONToken with the string value if a string could |
181 |
* be read. Throws an error otherwise. |
182 |
*/ |
183 |
private function readString():JSONToken { |
184 |
// the token for the string we'll try to read |
185 |
var token:JSONToken = new JSONToken(); |
186 |
token.type = JSONTokenType.STRING; |
187 |
|
188 |
// the string to store the string we'll try to read |
189 |
var string:String = ""; |
190 |
|
191 |
// advance past the first " |
192 |
nextChar(); |
193 |
|
194 |
while ( ch != '"' && ch != '' ) { |
195 |
|
196 |
// unescape the escape sequences in the string |
197 |
if ( ch == '\\' ) { |
198 |
|
199 |
// get the next character so we know what |
200 |
// to unescape |
201 |
nextChar(); |
202 |
|
203 |
switch ( ch ) { |
204 |
|
205 |
case '"': // quotation mark |
206 |
string += '"'; |
207 |
break; |
208 |
|
209 |
case '/': // solidus |
210 |
string += "/"; |
211 |
break; |
212 |
|
213 |
case '\\': // reverse solidus |
214 |
string += '\\'; |
215 |
break; |
216 |
|
217 |
case 'b': // bell |
218 |
string += '\b'; |
219 |
break; |
220 |
|
221 |
case 'f': // form feed |
222 |
string += '\f'; |
223 |
break; |
224 |
|
225 |
case 'n': // newline |
226 |
string += '\n'; |
227 |
break; |
228 |
|
229 |
case 'r': // carriage return |
230 |
string += '\r'; |
231 |
break; |
232 |
|
233 |
case 't': // horizontal tab |
234 |
string += '\t' |
235 |
break; |
236 |
|
237 |
case 'u': |
238 |
// convert a unicode escape sequence |
239 |
// to it's character value - expecting |
240 |
// 4 hex digits |
241 |
|
242 |
// save the characters as a string we'll convert to an int |
243 |
var hexValue:String = ""; |
244 |
|
245 |
// try to find 4 hex characters |
246 |
for ( var i:int = 0; i < 4; i++ ) { |
247 |
// get the next character and determine |
248 |
// if it's a valid hex digit or not |
249 |
if ( !isHexDigit( nextChar() ) ) { |
250 |
parseError( " Excepted a hex digit, but found: " + ch ); |
251 |
} |
252 |
// valid, add it to the value |
253 |
hexValue += ch; |
254 |
} |
255 |
|
256 |
// convert hexValue to an integer, and use that |
257 |
// integrer value to create a character to add |
258 |
// to our string. |
259 |
string += String.fromCharCode( parseInt( hexValue, 16 ) ); |
260 |
|
261 |
break; |
262 |
|
263 |
default: |
264 |
// couldn't unescape the sequence, so just |
265 |
// pass it through |
266 |
string += '\\' + ch; |
267 |
|
268 |
} |
269 |
|
270 |
} else { |
271 |
// didn't have to unescape, so add the character to the string |
272 |
string += ch; |
273 |
|
274 |
} |
275 |
|
276 |
// move to the next character |
277 |
nextChar(); |
278 |
|
279 |
} |
280 |
|
281 |
// we read past the end of the string without closing it, which |
282 |
// is a parse error |
283 |
if ( ch == '' ) { |
284 |
parseError( "Unterminated string literal" ); |
285 |
} |
286 |
|
287 |
// move past the closing " in the input string |
288 |
nextChar(); |
289 |
|
290 |
// attach to the string to the token so we can return it |
291 |
token.value = string; |
292 |
|
293 |
return token; |
294 |
} |
295 |
|
296 |
/** |
297 |
* Attempts to read a number from the input string. Places |
298 |
* the character location at the first character after the |
299 |
* number. |
300 |
* |
301 |
* @return The JSONToken with the number value if a number could |
302 |
* be read. Throws an error otherwise. |
303 |
*/ |
304 |
private function readNumber():JSONToken { |
305 |
// the token for the number we'll try to read |
306 |
var token:JSONToken = new JSONToken(); |
307 |
token.type = JSONTokenType.NUMBER; |
308 |
|
309 |
// the string to accumulate the number characters |
310 |
// into that we'll convert to a number at the end |
311 |
var input:String = ""; |
312 |
|
313 |
// check for a negative number |
314 |
if ( ch == '-' ) { |
315 |
input += '-'; |
316 |
nextChar(); |
317 |
} |
318 |
|
319 |
// the number must start with a digit |
320 |
if ( !isDigit( ch ) ) |
321 |
{ |
322 |
parseError( "Expecting a digit" ); |
323 |
} |
324 |
|
325 |
// 0 can only be the first digit if it |
326 |
// is followed by a decimal point |
327 |
if ( ch == '0' ) |
328 |
{ |
329 |
input += ch; |
330 |
nextChar(); |
331 |
|
332 |
// make sure no other digits come after 0 |
333 |
if ( isDigit( ch ) ) |
334 |
{ |
335 |
parseError( "A digit cannot immediately follow 0" ); |
336 |
} |
337 |
// Commented out - this should only be available when "strict" is false |
338 |
// // unless we have 0x which starts a hex number\ |
339 |
// else if ( ch == 'x' ) |
340 |
// { |
341 |
// // include the x in the input |
342 |
// input += ch; |
343 |
// nextChar(); |
344 |
// |
345 |
// // need at least one hex digit after 0x to |
346 |
// // be valid |
347 |
// if ( isHexDigit( ch ) ) |
348 |
// { |
349 |
// input += ch; |
350 |
// nextChar(); |
351 |
// } |
352 |
// else |
353 |
// { |
354 |
// parseError( "Number in hex format require at least one hex digit after \"0x\"" ); |
355 |
// } |
356 |
// |
357 |
// // consume all of the hex values |
358 |
// while ( isHexDigit( ch ) ) |
359 |
// { |
360 |
// input += ch; |
361 |
// nextChar(); |
362 |
// } |
363 |
// } |
364 |
} |
365 |
else |
366 |
{ |
367 |
// read numbers while we can |
368 |
while ( isDigit( ch ) ) { |
369 |
input += ch; |
370 |
nextChar(); |
371 |
} |
372 |
} |
373 |
|
374 |
// check for a decimal value |
375 |
if ( ch == '.' ) { |
376 |
input += '.'; |
377 |
nextChar(); |
378 |
|
379 |
// after the decimal there has to be a digit |
380 |
if ( !isDigit( ch ) ) |
381 |
{ |
382 |
parseError( "Expecting a digit" ); |
383 |
} |
384 |
|
385 |
// read more numbers to get the decimal value |
386 |
while ( isDigit( ch ) ) { |
387 |
input += ch; |
388 |
nextChar(); |
389 |
} |
390 |
} |
391 |
|
392 |
// check for scientific notation |
393 |
if ( ch == 'e' || ch == 'E' ) |
394 |
{ |
395 |
input += "e" |
396 |
nextChar(); |
397 |
// check for sign |
398 |
if ( ch == '+' || ch == '-' ) |
399 |
{ |
400 |
input += ch; |
401 |
nextChar(); |
402 |
} |
403 |
|
404 |
// require at least one number for the exponent |
405 |
// in this case |
406 |
if ( !isDigit( ch ) ) |
407 |
{ |
408 |
parseError( "Scientific notation number needs exponent value" ); |
409 |
} |
410 |
|
411 |
// read in the exponent |
412 |
while ( isDigit( ch ) ) |
413 |
{ |
414 |
input += ch; |
415 |
nextChar(); |
416 |
} |
417 |
} |
418 |
|
419 |
// convert the string to a number value |
420 |
var num:Number = Number( input ); |
421 |
|
422 |
if ( isFinite( num ) && !isNaN( num ) ) { |
423 |
token.value = num; |
424 |
return token; |
425 |
} else { |
426 |
parseError( "Number " + num + " is not valid!" ); |
427 |
} |
428 |
return null; |
429 |
} |
430 |
|
431 |
/** |
432 |
* Reads the next character in the input |
433 |
* string and advances the character location. |
434 |
* |
435 |
* @return The next character in the input string, or |
436 |
* null if we've read past the end. |
437 |
*/ |
438 |
private function nextChar():String { |
439 |
return ch = jsonString.charAt( loc++ ); |
440 |
} |
441 |
|
442 |
/** |
443 |
* Advances the character location past any |
444 |
* sort of white space and comments |
445 |
*/ |
446 |
private function skipIgnored():void |
447 |
{ |
448 |
var originalLoc:int; |
449 |
|
450 |
// keep trying to skip whitespace and comments as long |
451 |
// as we keep advancing past the original location |
452 |
do |
453 |
{ |
454 |
originalLoc = loc; |
455 |
skipWhite(); |
456 |
skipComments(); |
457 |
} |
458 |
while ( originalLoc != loc ); |
459 |
} |
460 |
|
461 |
/** |
462 |
* Skips comments in the input string, either |
463 |
* single-line or multi-line. Advances the character |
464 |
* to the first position after the end of the comment. |
465 |
*/ |
466 |
private function skipComments():void { |
467 |
if ( ch == '/' ) { |
468 |
// Advance past the first / to find out what type of comment |
469 |
nextChar(); |
470 |
switch ( ch ) { |
471 |
case '/': // single-line comment, read through end of line |
472 |
|
473 |
// Loop over the characters until we find |
474 |
// a newline or until there's no more characters left |
475 |
do { |
476 |
nextChar(); |
477 |
} while ( ch != '\n' && ch != '' ) |
478 |
|
479 |
// move past the \n |
480 |
nextChar(); |
481 |
|
482 |
break; |
483 |
|
484 |
case '*': // multi-line comment, read until closing */ |
485 |
|
486 |
// move past the opening * |
487 |
nextChar(); |
488 |
|
489 |
// try to find a trailing */ |
490 |
while ( true ) { |
491 |
if ( ch == '*' ) { |
492 |
// check to see if we have a closing / |
493 |
nextChar(); |
494 |
if ( ch == '/') { |
495 |
// move past the end of the closing */ |
496 |
nextChar(); |
497 |
break; |
498 |
} |
499 |
} else { |
500 |
// move along, looking if the next character is a * |
501 |
nextChar(); |
502 |
} |
503 |
|
504 |
// when we're here we've read past the end of |
505 |
// the string without finding a closing */, so error |
506 |
if ( ch == '' ) { |
507 |
parseError( "Multi-line comment not closed" ); |
508 |
} |
509 |
} |
510 |
|
511 |
break; |
512 |
|
513 |
// Can't match a comment after a /, so it's a parsing error |
514 |
default: |
515 |
parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" ); |
516 |
} |
517 |
} |
518 |
|
519 |
} |
520 |
|
521 |
|
522 |
/** |
523 |
* Skip any whitespace in the input string and advances |
524 |
* the character to the first character after any possible |
525 |
* whitespace. |
526 |
*/ |
527 |
private function skipWhite():void { |
528 |
|
529 |
// As long as there are spaces in the input |
530 |
// stream, advance the current location pointer |
531 |
// past them |
532 |
while ( isWhiteSpace( ch ) ) { |
533 |
nextChar(); |
534 |
} |
535 |
|
536 |
} |
537 |
|
538 |
/** |
539 |
* Determines if a character is whitespace or not. |
540 |
* |
541 |
* @return True if the character passed in is a whitespace |
542 |
* character |
543 |
*/ |
544 |
private function isWhiteSpace( ch:String ):Boolean { |
545 |
return ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' ); |
546 |
} |
547 |
|
548 |
/** |
549 |
* Determines if a character is a digit [0-9]. |
550 |
* |
551 |
* @return True if the character passed in is a digit |
552 |
*/ |
553 |
private function isDigit( ch:String ):Boolean { |
554 |
return ( ch >= '0' && ch <= '9' ); |
555 |
} |
556 |
|
557 |
/** |
558 |
* Determines if a character is a digit [0-9]. |
559 |
* |
560 |
* @return True if the character passed in is a digit |
561 |
*/ |
562 |
private function isHexDigit( ch:String ):Boolean { |
563 |
// get the uppercase value of ch so we only have |
564 |
// to compare the value between 'A' and 'F' |
565 |
var uc:String = ch.toUpperCase(); |
566 |
|
567 |
// a hex digit is a digit of A-F, inclusive ( using |
568 |
// our uppercase constraint ) |
569 |
return ( isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) ); |
570 |
} |
571 |
|
572 |
/** |
573 |
* Raises a parsing error with a specified message, tacking |
574 |
* on the error location and the original string. |
575 |
* |
576 |
* @param message The message indicating why the error occurred |
577 |
*/ |
578 |
public function parseError( message:String ):void { |
579 |
throw new JSONParseError( message, loc, jsonString ); |
580 |
} |
581 |
} |
582 |
|
583 |
} |