+++ /dev/null
-/*\r
- Copyright (c) 2008, Adobe Systems Incorporated\r
- All rights reserved.\r
-\r
- Redistribution and use in source and binary forms, with or without \r
- modification, are permitted provided that the following conditions are\r
- met:\r
-\r
- * Redistributions of source code must retain the above copyright notice, \r
- this list of conditions and the following disclaimer.\r
- \r
- * Redistributions in binary form must reproduce the above copyright\r
- notice, this list of conditions and the following disclaimer in the \r
- documentation and/or other materials provided with the distribution.\r
- \r
- * Neither the name of Adobe Systems Incorporated nor the names of its \r
- contributors may be used to endorse or promote products derived from \r
- this software without specific prior written permission.\r
-\r
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR \r
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
-*/\r
-\r
-package com.adobe.serialization.json {\r
-\r
- public class JSONTokenizer {\r
- \r
- /** The object that will get parsed from the JSON string */\r
- private var obj:Object;\r
- \r
- /** The JSON string to be parsed */\r
- private var jsonString:String;\r
- \r
- /** The current parsing location in the JSON string */\r
- private var loc:int;\r
- \r
- /** The current character in the JSON string during parsing */\r
- private var ch:String;\r
- \r
- /**\r
- * Constructs a new JSONDecoder to parse a JSON string \r
- * into a native object.\r
- *\r
- * @param s The JSON string to be converted\r
- * into a native object\r
- */\r
- public function JSONTokenizer( s:String ) {\r
- jsonString = s;\r
- loc = 0;\r
- \r
- // prime the pump by getting the first character\r
- nextChar();\r
- }\r
- \r
- /**\r
- * Gets the next token in the input sting and advances\r
- * the character to the next character after the token\r
- */\r
- public function getNextToken():JSONToken {\r
- var token:JSONToken = new JSONToken();\r
- \r
- // skip any whitespace / comments since the last \r
- // token was read\r
- skipIgnored();\r
- \r
- // examine the new character and see what we have...\r
- switch ( ch ) {\r
- \r
- case '{':\r
- token.type = JSONTokenType.LEFT_BRACE;\r
- token.value = '{';\r
- nextChar();\r
- break\r
- \r
- case '}':\r
- token.type = JSONTokenType.RIGHT_BRACE;\r
- token.value = '}';\r
- nextChar();\r
- break\r
- \r
- case '[':\r
- token.type = JSONTokenType.LEFT_BRACKET;\r
- token.value = '[';\r
- nextChar();\r
- break\r
- \r
- case ']':\r
- token.type = JSONTokenType.RIGHT_BRACKET;\r
- token.value = ']';\r
- nextChar();\r
- break\r
- \r
- case ',':\r
- token.type = JSONTokenType.COMMA;\r
- token.value = ',';\r
- nextChar();\r
- break\r
- \r
- case ':':\r
- token.type = JSONTokenType.COLON;\r
- token.value = ':';\r
- nextChar();\r
- break;\r
- \r
- case 't': // attempt to read true\r
- var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar();\r
- \r
- if ( possibleTrue == "true" ) {\r
- token.type = JSONTokenType.TRUE;\r
- token.value = true;\r
- nextChar();\r
- } else {\r
- parseError( "Expecting 'true' but found " + possibleTrue );\r
- }\r
- \r
- break;\r
- \r
- case 'f': // attempt to read false\r
- var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar();\r
- \r
- if ( possibleFalse == "false" ) {\r
- token.type = JSONTokenType.FALSE;\r
- token.value = false;\r
- nextChar();\r
- } else {\r
- parseError( "Expecting 'false' but found " + possibleFalse );\r
- }\r
- \r
- break;\r
- \r
- case 'n': // attempt to read null\r
- \r
- var possibleNull:String = "n" + nextChar() + nextChar() + nextChar();\r
- \r
- if ( possibleNull == "null" ) {\r
- token.type = JSONTokenType.NULL;\r
- token.value = null;\r
- nextChar();\r
- } else {\r
- parseError( "Expecting 'null' but found " + possibleNull );\r
- }\r
- \r
- break;\r
- \r
- case '"': // the start of a string\r
- token = readString();\r
- break;\r
- \r
- default: \r
- // see if we can read a number\r
- if ( isDigit( ch ) || ch == '-' ) {\r
- token = readNumber();\r
- } else if ( ch == '' ) {\r
- // check for reading past the end of the string\r
- return null;\r
- } else { \r
- // not sure what was in the input string - it's not\r
- // anything we expected\r
- parseError( "Unexpected " + ch + " encountered" );\r
- }\r
- }\r
- \r
- return token;\r
- }\r
- \r
- /**\r
- * Attempts to read a string from the input string. Places\r
- * the character location at the first character after the\r
- * string. It is assumed that ch is " before this method is called.\r
- *\r
- * @return the JSONToken with the string value if a string could\r
- * be read. Throws an error otherwise.\r
- */\r
- private function readString():JSONToken {\r
- // the token for the string we'll try to read\r
- var token:JSONToken = new JSONToken();\r
- token.type = JSONTokenType.STRING;\r
- \r
- // the string to store the string we'll try to read\r
- var string:String = "";\r
- \r
- // advance past the first "\r
- nextChar();\r
- \r
- while ( ch != '"' && ch != '' ) {\r
- \r
- // unescape the escape sequences in the string\r
- if ( ch == '\\' ) {\r
- \r
- // get the next character so we know what\r
- // to unescape\r
- nextChar();\r
- \r
- switch ( ch ) {\r
- \r
- case '"': // quotation mark\r
- string += '"';\r
- break;\r
- \r
- case '/': // solidus\r
- string += "/";\r
- break;\r
- \r
- case '\\': // reverse solidus\r
- string += '\\';\r
- break;\r
- \r
- case 'b': // bell\r
- string += '\b';\r
- break;\r
- \r
- case 'f': // form feed\r
- string += '\f';\r
- break;\r
- \r
- case 'n': // newline\r
- string += '\n';\r
- break;\r
- \r
- case 'r': // carriage return\r
- string += '\r';\r
- break;\r
- \r
- case 't': // horizontal tab\r
- string += '\t'\r
- break;\r
- \r
- case 'u':\r
- // convert a unicode escape sequence\r
- // to it's character value - expecting\r
- // 4 hex digits\r
- \r
- // save the characters as a string we'll convert to an int\r
- var hexValue:String = "";\r
- \r
- // try to find 4 hex characters\r
- for ( var i:int = 0; i < 4; i++ ) {\r
- // get the next character and determine\r
- // if it's a valid hex digit or not\r
- if ( !isHexDigit( nextChar() ) ) {\r
- parseError( " Excepted a hex digit, but found: " + ch );\r
- }\r
- // valid, add it to the value\r
- hexValue += ch;\r
- }\r
- \r
- // convert hexValue to an integer, and use that\r
- // integrer value to create a character to add\r
- // to our string.\r
- string += String.fromCharCode( parseInt( hexValue, 16 ) );\r
- \r
- break;\r
- \r
- default:\r
- // couldn't unescape the sequence, so just\r
- // pass it through\r
- string += '\\' + ch;\r
- \r
- }\r
- \r
- } else {\r
- // didn't have to unescape, so add the character to the string\r
- string += ch;\r
- \r
- }\r
- \r
- // move to the next character\r
- nextChar();\r
- \r
- }\r
- \r
- // we read past the end of the string without closing it, which\r
- // is a parse error\r
- if ( ch == '' ) {\r
- parseError( "Unterminated string literal" );\r
- }\r
- \r
- // move past the closing " in the input string\r
- nextChar();\r
- \r
- // attach to the string to the token so we can return it\r
- token.value = string;\r
- \r
- return token;\r
- }\r
- \r
- /**\r
- * Attempts to read a number from the input string. Places\r
- * the character location at the first character after the\r
- * number.\r
- * \r
- * @return The JSONToken with the number value if a number could\r
- * be read. Throws an error otherwise.\r
- */\r
- private function readNumber():JSONToken {\r
- // the token for the number we'll try to read\r
- var token:JSONToken = new JSONToken();\r
- token.type = JSONTokenType.NUMBER;\r
- \r
- // the string to accumulate the number characters\r
- // into that we'll convert to a number at the end\r
- var input:String = "";\r
- \r
- // check for a negative number\r
- if ( ch == '-' ) {\r
- input += '-';\r
- nextChar();\r
- }\r
- \r
- // the number must start with a digit\r
- if ( !isDigit( ch ) )\r
- {\r
- parseError( "Expecting a digit" );\r
- }\r
- \r
- // 0 can only be the first digit if it\r
- // is followed by a decimal point\r
- if ( ch == '0' )\r
- {\r
- input += ch;\r
- nextChar();\r
- \r
- // make sure no other digits come after 0\r
- if ( isDigit( ch ) )\r
- {\r
- parseError( "A digit cannot immediately follow 0" );\r
- }\r
-// Commented out - this should only be available when "strict" is false\r
-// // unless we have 0x which starts a hex number\\r
-// else if ( ch == 'x' )\r
-// {\r
-// // include the x in the input\r
-// input += ch;\r
-// nextChar();\r
-// \r
-// // need at least one hex digit after 0x to\r
-// // be valid\r
-// if ( isHexDigit( ch ) )\r
-// {\r
-// input += ch;\r
-// nextChar();\r
-// }\r
-// else\r
-// {\r
-// parseError( "Number in hex format require at least one hex digit after \"0x\"" ); \r
-// }\r
-// \r
-// // consume all of the hex values\r
-// while ( isHexDigit( ch ) )\r
-// {\r
-// input += ch;\r
-// nextChar();\r
-// }\r
-// }\r
- }\r
- else\r
- {\r
- // read numbers while we can\r
- while ( isDigit( ch ) ) {\r
- input += ch;\r
- nextChar();\r
- }\r
- }\r
- \r
- // check for a decimal value\r
- if ( ch == '.' ) {\r
- input += '.';\r
- nextChar();\r
- \r
- // after the decimal there has to be a digit\r
- if ( !isDigit( ch ) )\r
- {\r
- parseError( "Expecting a digit" );\r
- }\r
- \r
- // read more numbers to get the decimal value\r
- while ( isDigit( ch ) ) {\r
- input += ch;\r
- nextChar();\r
- }\r
- }\r
- \r
- // check for scientific notation\r
- if ( ch == 'e' || ch == 'E' )\r
- {\r
- input += "e"\r
- nextChar();\r
- // check for sign\r
- if ( ch == '+' || ch == '-' )\r
- {\r
- input += ch;\r
- nextChar();\r
- }\r
- \r
- // require at least one number for the exponent\r
- // in this case\r
- if ( !isDigit( ch ) )\r
- {\r
- parseError( "Scientific notation number needs exponent value" );\r
- }\r
- \r
- // read in the exponent\r
- while ( isDigit( ch ) )\r
- {\r
- input += ch;\r
- nextChar();\r
- }\r
- }\r
- \r
- // convert the string to a number value\r
- var num:Number = Number( input );\r
- \r
- if ( isFinite( num ) && !isNaN( num ) ) {\r
- token.value = num;\r
- return token;\r
- } else {\r
- parseError( "Number " + num + " is not valid!" );\r
- }\r
- return null;\r
- }\r
-\r
- /**\r
- * Reads the next character in the input\r
- * string and advances the character location.\r
- *\r
- * @return The next character in the input string, or\r
- * null if we've read past the end.\r
- */\r
- private function nextChar():String {\r
- return ch = jsonString.charAt( loc++ );\r
- }\r
- \r
- /**\r
- * Advances the character location past any\r
- * sort of white space and comments\r
- */\r
- private function skipIgnored():void\r
- {\r
- var originalLoc:int;\r
- \r
- // keep trying to skip whitespace and comments as long\r
- // as we keep advancing past the original location \r
- do\r
- {\r
- originalLoc = loc;\r
- skipWhite();\r
- skipComments();\r
- }\r
- while ( originalLoc != loc );\r
- }\r
- \r
- /**\r
- * Skips comments in the input string, either\r
- * single-line or multi-line. Advances the character\r
- * to the first position after the end of the comment.\r
- */\r
- private function skipComments():void {\r
- if ( ch == '/' ) {\r
- // Advance past the first / to find out what type of comment\r
- nextChar();\r
- switch ( ch ) {\r
- case '/': // single-line comment, read through end of line\r
- \r
- // Loop over the characters until we find\r
- // a newline or until there's no more characters left\r
- do {\r
- nextChar();\r
- } while ( ch != '\n' && ch != '' )\r
- \r
- // move past the \n\r
- nextChar();\r
- \r
- break;\r
- \r
- case '*': // multi-line comment, read until closing */\r
-\r
- // move past the opening *\r
- nextChar();\r
- \r
- // try to find a trailing */\r
- while ( true ) {\r
- if ( ch == '*' ) {\r
- // check to see if we have a closing /\r
- nextChar();\r
- if ( ch == '/') {\r
- // move past the end of the closing */\r
- nextChar();\r
- break;\r
- }\r
- } else {\r
- // move along, looking if the next character is a *\r
- nextChar();\r
- }\r
- \r
- // when we're here we've read past the end of \r
- // the string without finding a closing */, so error\r
- if ( ch == '' ) {\r
- parseError( "Multi-line comment not closed" );\r
- }\r
- }\r
-\r
- break;\r
- \r
- // Can't match a comment after a /, so it's a parsing error\r
- default:\r
- parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" );\r
- }\r
- }\r
- \r
- }\r
- \r
- \r
- /**\r
- * Skip any whitespace in the input string and advances\r
- * the character to the first character after any possible\r
- * whitespace.\r
- */\r
- private function skipWhite():void {\r
- \r
- // As long as there are spaces in the input \r
- // stream, advance the current location pointer\r
- // past them\r
- while ( isWhiteSpace( ch ) ) {\r
- nextChar();\r
- }\r
- \r
- }\r
- \r
- /**\r
- * Determines if a character is whitespace or not.\r
- *\r
- * @return True if the character passed in is a whitespace\r
- * character\r
- */\r
- private function isWhiteSpace( ch:String ):Boolean {\r
- return ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' );\r
- }\r
- \r
- /**\r
- * Determines if a character is a digit [0-9].\r
- *\r
- * @return True if the character passed in is a digit\r
- */\r
- private function isDigit( ch:String ):Boolean {\r
- return ( ch >= '0' && ch <= '9' );\r
- }\r
- \r
- /**\r
- * Determines if a character is a digit [0-9].\r
- *\r
- * @return True if the character passed in is a digit\r
- */\r
- private function isHexDigit( ch:String ):Boolean {\r
- // get the uppercase value of ch so we only have\r
- // to compare the value between 'A' and 'F'\r
- var uc:String = ch.toUpperCase();\r
- \r
- // a hex digit is a digit of A-F, inclusive ( using\r
- // our uppercase constraint )\r
- return ( isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) );\r
- }\r
- \r
- /**\r
- * Raises a parsing error with a specified message, tacking\r
- * on the error location and the original string.\r
- *\r
- * @param message The message indicating why the error occurred\r
- */\r
- public function parseError( message:String ):void {\r
- throw new JSONParseError( message, loc, jsonString );\r
- }\r
- }\r
- \r
-}\r