/** * @fileoverview Json parser 처리를 위한 함수. */ if ( !JsNamespace.exist("Eco.Json") ) { /** * @namespace * @name Eco.Json * @memberof! */ JsNamespace.declare("Eco.Json", { _Token_Type : { UNKNOWN : -1, COMMA : 0, LEFT_BRACE : 1, RIGHT_BRACE : 2, LEFT_BRACKET : 3, RIGHT_BRACKET : 4, COLON : 6, TRUE : 7, FALSE : 8, NULL : 9, STRING : 10, NUMBER : 11, NAN : 12 }, /** * JSON 문자열을 객체로 변환한 결과값을 반환한다. * @function decode * @param {string} s JSON String * @param {boolean=} strict JSON 문자열을 decoding 할때 엄격히 표준을 따라야 하는지 여부. * @return {object} 결과 object * @memberOf Eco.Json */ decode : ( typeof JSON == "undefined" ) ? function(s, strict) { if(Eco.isEmpty(s)) return; var pThis = Eco.Json; pThis._jsonString = s; pThis._strict = strict; pThis._loc = 0; pThis._ch = null; // prime the pump by getting the first character pThis._nextChar(); pThis._nextToken(); pThis._value = pThis._parseValue(); // Make sure the input stream is empty if ( strict && pThis._nextToken() != null ) { pThis._parseError( "Unexpected characters left in input stream" ); } return pThis._value; } : function(s) { if(Eco.isEmpty(s)) return; return JSON.parse(s); }, /** * Returns the next token from the tokenzier reading * the JSON string * @private */ _nextToken : function() { var pThis = Eco.Json; return pThis._token = pThis.getNextToken(); }, /** * Attempt to parse an array. * @private */ _parseArray : function() { var a = []; var token; var pThis = Eco.Json; var strict = pThis._strict; token = pThis._nextToken(); var tokenType = pThis._Token_Type; if ( token.type == tokenType.RIGHT_BRACKET ) { // we're done reading the array, so return it return a; } // in non-strict mode an empty array is also a comma // followed by a right bracket else if ( !strict && token.type == tokenType.COMMA ) { // move past the comma token = pThis._nextToken(); // check to see if we're reached the end of the array if ( token.type == tokenType.RIGHT_BRACKET ) { return a; } else { pThis._parseError( "Leading commas are not supported. Expecting ']' but found " + token.value ); } } // deal with elements of the array, and use an "infinite" // loop because we could have any amount of elements while ( true ) { // read in the value and add it to the array a.push( pThis._parseValue() ); // after the value there should be a ] or a , token = pThis._nextToken(); if ( token.type == tokenType.RIGHT_BRACKET ) { // we're done reading the array, so return it return a; } else if ( token.type == tokenType.COMMA ) { // move past the comma and read another value token = pThis._nextToken(); // Allow arrays to have a comma after the last element // if the decoder is not in strict mode if ( !strict ) { // Reached ",]" as the end of the array, so return it if ( token.type == tokenType.RIGHT_BRACKET ) { return a; } } } else { pThis._parseError( "Expecting ] or , but found " + token.value ); } } return null; }, /** * Attempt to parse an object. * @private */ _parseObject : function() { var pThis = Eco.Json; // create the object internally that we're going to // attempt to parse from the tokenizer var o = {}; var strict = pThis._strict; // store the string part of an object member so // that we can assign it a value in the object var key; // grab the next token from the tokenizer var token = pThis._nextToken(); var tokenType = pThis._Token_Type; // check to see if we have an empty object if ( token.type == tokenType.RIGHT_BRACE ) { // we're done reading the object, so return it return o; } // in non-strict mode an empty object is also a comma // followed by a right bracket else if ( !strict && token.type == tokenType.COMMA ) { // move past the comma token = pThis._nextToken(); // check to see if we're reached the end of the object if ( token.type == tokenType.RIGHT_BRACE ) { return o; } else { pThis._parseError( "Leading commas are not supported. Expecting '}' but found " + token.value ); } } // deal with members of the object, and use an "infinite" // loop because we could have any amount of members while ( true ) { if ( token.type == tokenType.STRING ) { // the string value we read is the key for the object key = String( token.value ); // move past the string to see what's next token = pThis._nextToken(); // after the string there should be a : if ( token.type == tokenType.COLON ) { // move past the : and read/assign a value for the key token = pThis._nextToken(); o[key] = pThis._parseValue(); // move past the value to see what's next token = pThis._nextToken(); // after the value there's either a } or a , if ( token.type == tokenType.RIGHT_BRACE ) { // we're done reading the object, so return it return o; } else if ( token.type == tokenType.COMMA ) { // skip past the comma and read another member token = pThis._nextToken(); // Allow objects to have a comma after the last member // if the decoder is not in strict mode if ( !strict ) { // Reached ",}" as the end of the object, so return it if ( token.type == tokenType.RIGHT_BRACE ) { return o; } } } else { pThis._parseError( "Expecting } or , but found " + token.value ); } } else { pThis._parseError( "Expecting : but found " + token.value ); } } else { pThis._parseError( "Expecting string but found " + token.value ); } } return null; }, _parseValue : function() { var pThis = Eco.Json; var strict = pThis._strict; var token = pThis._token; // Catch errors when the input stream ends abruptly if ( token == null ) { pThis._parseError( "Unexpected end of input" ); } var tokenType = pThis._Token_Type; switch ( token.type ) { case tokenType.LEFT_BRACE: return pThis._parseObject(); case tokenType.LEFT_BRACKET: return pThis._parseArray(); case tokenType.STRING: case tokenType.NUMBER: case tokenType.TRUE: case tokenType.FALSE: case tokenType.NULL: return token.value; case tokenType.NAN: if ( !strict ) { return token.value; } else { pThis._parseError( "Unexpected " + token.value ); } default: pThis._parseError( "Unexpected " + token.value ); } return null; }, /** * Object를 문자열로 변환한다. * @function encode * @param {object} value 문자열로 변환할 Object. * @return {object} 변환된 문자열 * @memberOf Eco.Json */ encode : ( typeof JSON == "undefined" ) ? function( value ) { return Eco.Json._convertToString( value ); } : function( value, replacer, space ) { // 참고 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify return JSON.stringify( value, replacer, space ); }, _convertToString : function( value ) { var pThis = Eco.Json; // determine what value is and convert it based on it's type if ( Eco.isString(value) ) { // escape the string so it's formatted correctly return pThis._escapeString( value ); } else if ( Eco.isNumber(value) ) { // only encode numbers that finate return isFinite( value ) ? value.toString() : "null"; } else if ( Eco.isBoolean(value) ) { // convert boolean to string easily return value ? "true" : "false"; } else if ( Eco.isArray(value) ) { // call the helper method to convert an array return pThis._arrayToString( value ); } else if ( Eco.isXComponent(value) ) { return value.name; } //else if ( value && MiscUtil.isObject(value) ) else if ( value ) { // call the helper method to convert an object return pThis._objectToString( value ); } return "null"; }, _escapeString : function( str ) { // create a string to store the string's jsonstring value var s = ""; // current character in the string we're processing var ch; // store the length in a local variable to reduce lookups var len = str.length; // loop over all of the characters in the string for ( var i = 0; i < len; i++ ) { // examine the character to determine if we have to escape it ch = str.charAt( i ); switch ( ch ) { case '"': // quotation mark s += "\\\""; break; //case '/': // solidus // s += "\\/"; // break; case '\\': // reverse solidus s += "\\\\"; break; case '\b': // bell s += "\\b"; break; case '\f': // form feed s += "\\f"; break; case '\n': // newline s += "\\n"; break; case '\r': // carriage return s += "\\r"; break; case '\t': // horizontal tab s += "\\t"; break; default: // everything else // check for a control character and escape as unicode if ( ch < ' ' ) { // get the hex digit(s) of the character (either 1 or 2 digits) var hexCode = ch.charCodeAt( 0 ).toString( 16 ); // ensure that there are 4 digits by adjusting // the # of zeros accordingly. var zeroPad = hexCode.length == 2 ? "00" : "000"; // create the unicode escape sequence with 4 hex digits s += "\\u" + zeroPad + hexCode; } else { // no need to do any special encoding, just pass-through s += ch; } }// end switch }// end for loop return "\"" + s + "\""; }, _arrayToString : function( a ) { var pThis = Eco.Json; // create a string to store the array's jsonstring value var s = ""; // loop over the elements in the array and add their converted // values to the string for ( var i = 0; i < a.length; i++ ) { // when the length is 0 we're adding the first element so // no comma is necessary if ( s.length > 0 ) { // we've already added an element, so add the comma separator s += "," } // convert the value to a string s += pThis._convertToString( a[i] ); } // KNOWN ISSUE: In ActionScript, Arrays can also be associative // objects and you can put anything in them, ie: // myArray["foo"] = "bar"; // // These properties aren't picked up in the for loop above because // the properties don't correspond to indexes. However, we're // sort of out luck because the JSON specification doesn't allow // these types of array properties. // // So, if the array was also used as an associative object, there // may be some values in the array that don't get properly encoded. // // A possible solution is to instead encode the Array as an Object // but then it won't get decoded correctly (and won't be an // Array instance) // close the array and return it's string value return "[" + s + "]"; }, _objectToString : function( o ) { // create a string to store the object's jsonstring value var s = ""; var pThis = Eco.Json; //trace("_objectToString o="+o); if ( Eco.isObject(o) ) { // the value of o[key] in the loop below - store this // as a variable so we don't have to keep looking up o[key] // when testing for valid values to convert var value; // loop over the keys in the object and add their converted // values to the string for ( var key in o ) { if ( o.hasOwnProperty(key) ) { // assign value to a variable for quick lookup value = o[key]; // don't add function's to the JSON string if ( Eco.isFunction(value) ) { // skip this key and try another continue; } // when the length is 0 we're adding the first item so // no comma is necessary if ( s.length > 0 ) { // we've already added an item, so add the comma separator s += "," } s += pThis._escapeString( key ) + ":" + pThis._convertToString( value ); } } } //else if ( is xpcomp ) //{ //} else // o is a ufo class instance { /* // Loop over all of the variables and accessors in the class and // serialize them along with their values. for each ( var v:XML in classInfo..*.( name() == "variable" || name() == "accessor" ) ) { // When the length is 0 we're adding the first item so // no comma is necessary if ( s.length > 0 ) { // We've already added an item, so add the comma separator s += "," } s += escapeString( v.@name.toString() ) + ":" + convertToString( o[ v.@name ] ); } */ var value; var propName; var props = o._properties; var getter; //trace("props="+props + ", typeof=" + (typeof(props))); trace("json.js > _objectToString > ■ start 테스트를 위한 임의설정 "); if(props === undefined) { trace("json.js > _objectToString > s="+s); return "{" + s + "}"; } //return "{" + s + "}"; for (var i=0,len=props.length; i 0 ) { // we've already added an item, so add the comma separator s += "," } s += pThis._escapeString( propName ) + ":" + pThis._convertToString( value ); } } return "{" + s + "}"; }, setToken : function(type, value) { Eco.Json._token = { type : type, value : value }; // zoo // var token = this._token; // token.type = type; // token.value = value; }, getToken : function() { return Eco.Json._token; }, getNextToken : function() { var pThis = Eco.Json; // skip any whitespace / comments since the last // token was read pThis.skipIgnored(); var tokenType = pThis._Token_Type; var ch = pThis._ch; // examine the new character and see what we have... switch ( ch ) { case '{': pThis.setToken(tokenType.LEFT_BRACE, '{'); pThis._nextChar(); break; case '}': pThis.setToken(tokenType.RIGHT_BRACE, '}'); pThis._nextChar(); break; case '[': pThis.setToken(tokenType.LEFT_BRACKET, '['); pThis._nextChar(); break; case ']': pThis.setToken(tokenType.RIGHT_BRACKET, ']'); pThis._nextChar(); break; case ',': pThis.setToken(tokenType.COMMA, ','); pThis._nextChar(); break; case ':': pThis.setToken(tokenType.COLON, ':'); pThis._nextChar(); break; case 't': // attempt to read true var possibleTrue = "t" + pThis._nextChar() + pThis._nextChar() + pThis._nextChar(); if ( possibleTrue == "true" ) { pThis.setToken(tokenType.TRUE, true); pThis._nextChar(); } else { pThis._parseError( "Expecting 'true' but found " + possibleTrue ); } break; case 'f': // attempt to read false var possibleFalse = "f" + pThis._nextChar() + pThis._nextChar() + pThis._nextChar() + pThis._nextChar(); if ( possibleFalse == "false" ) { pThis.setToken(tokenType.FALSE, false); pThis._nextChar(); } else { pThis._parseError( "Expecting 'false' but found " + possibleFalse ); } break; case 'n': // attempt to read null var possibleNull = "n" + pThis._nextChar() + pThis._nextChar() + pThis._nextChar(); if ( possibleNull == "null" ) { pThis.setToken(tokenType.NULL, null); pThis._nextChar(); } else { pThis._parseError( "Expecting 'null' but found " + possibleNull ); } break; case 'N': // attempt to read NaN var possibleNaN = "N" + pThis._nextChar() + pThis._nextChar(); if ( possibleNaN == "NaN" ) { pThis.setToken(tokenType.NAN, NaN); pThis._nextChar(); } else { pThis._parseError( "Expecting 'NaN' but found " + possibleNaN ); } break; case '"': // the start of a string pThis._readString(); break; default: // see if we can read a number if ( pThis._isDigit( ch ) || ch == '-' ) { pThis._readNumber(); } else if ( ch == '' ) { // check for reading past the end of the string return null; } else { // not sure what was in the input string - it's not // anything we expected pThis._parseError( "Unexpected " + ch + " encountered" ); } } return pThis._token; }, _readString : function() { var pThis = Eco.Json; // the string to store the string we'll try to read var string = ""; // advance past the first " var ch = pThis._nextChar(); while ( ch != '"' && ch != '' ) { // unescape the escape sequences in the string if ( ch == '\\' ) { // get the next character so we know what // to unescape ch = pThis._nextChar(); switch ( ch ) { case '"': // quotation mark string += '"'; break; case '/': // solidus string += "/"; break; case '\\': // reverse solidus string += '\\'; break; case 'b': // bell string += '\b'; break; case 'f': // form feed string += '\f'; break; case 'n': // newline string += '\n'; break; case 'r': // carriage return string += '\r'; break; case 't': // horizontal tab string += '\t' break; case 'u': // convert a unicode escape sequence // to it's character value - expecting // 4 hex digits // save the characters as a string we'll convert to an int var hexValue = ""; // try to find 4 hex characters for ( var i = 0; i < 4; i++ ) { // get the next character and determine // if it's a valid hex digit or not if ( !pThis._isHexDigit( ch = pThis._nextChar() ) ) { pThis._parseError( " Excepted a hex digit, but found: " + ch ); } // valid, add it to the value hexValue += ch; } // convert hexValue to an integer, and use that // integrer value to create a character to add // to our string. string += String.fromCharCode( parseInt( hexValue, 16 ) ); break; default: // couldn't unescape the sequence, so just // pass it through string += '\\' + ch; } } else { // didn't have to unescape, so add the character to the string string += ch; } // move to the next character ch = pThis._nextChar(); } // we read past the end of the string without closing it, which // is a parse error if ( ch == '' ) { pThis._parseError( "Unterminated string literal" ); } // move past the closing " in the input string pThis._nextChar(); pThis.setToken(pThis._Token_Type.STRING, string); }, _readNumber : function() { var pThis = Eco.Json; // the string to accumulate the number characters // into that we'll convert to a number at the end var input = ""; var ch = pThis._ch; var strict = pThis._strict; // check for a negative number if ( ch == '-' ) { input += '-'; ch = pThis._nextChar(); } // the number must start with a digit if ( !pThis._isDigit( ch ) ) { pThis._parseError( "Expecting a digit" ); } // 0 can only be the first digit if it // is followed by a decimal point if ( ch == '0' ) { input += ch; ch = pThis._nextChar(); // make sure no other digits come after 0 if ( pThis._isDigit( ch ) ) { pThis._parseError( "A digit cannot immediately follow 0" ); } // unless we have 0x which starts a hex number, but this // doesn't match JSON spec so check for not strict mode. else if ( !strict && ch == 'x' ) { // include the x in the input input += ch; ch = pThis._nextChar(); // need at least one hex digit after 0x to // be valid if ( pThis._isHexDigit( ch ) ) { input += ch; ch = pThis._nextChar(); } else { pThis._parseError( "Number in hex format require at least one hex digit after \"0x\"" ); } // consume all of the hex values while ( pThis._isHexDigit( ch ) ) { input += ch; ch = pThis._nextChar(); } } } else { // read numbers while we can while ( pThis._isDigit( ch ) ) { input += ch; ch = pThis._nextChar(); } } // check for a decimal value if ( ch == '.' ) { input += '.'; ch = pThis._nextChar(); // after the decimal there has to be a digit if ( !pThis._isDigit( ch ) ) { pThis._parseError( "Expecting a digit" ); } // read more numbers to get the decimal value while ( pThis._isDigit( ch ) ) { input += ch; ch = pThis._nextChar(); } } // check for scientific notation if ( ch == 'e' || ch == 'E' ) { input += "e" ch = pThis._nextChar(); // check for sign if ( ch == '+' || ch == '-' ) { input += ch; ch = pThis._nextChar(); } // require at least one number for the exponent // in this case if ( !pThis._isDigit( ch ) ) { pThis._parseError( "Scientific notation number needs exponent value" ); } // read in the exponent while ( pThis._isDigit( ch ) ) { input += ch; ch = pThis._nextChar(); } } // convert the string to a number value var num = Number( input ); if ( isFinite( num ) && !isNaN( num ) ) { // the token for the number that we've read pThis.setToken(pThis._Token_Type.NUMBER, num); return; } else { pThis._parseError( "Number " + num + " is not valid!" ); } pThis._token = null; }, _nextChar : function() { var pThis = Eco.Json; return pThis._ch = pThis._jsonString.charAt( pThis._loc++ ); }, skipIgnored : function() { var originalLoc; var pThis = Eco.Json; // keep trying to skip whitespace and comments as long // as we keep advancing past the original location do { originalLoc = pThis._loc; pThis._skipWhite(); pThis._skipComments(); } while ( originalLoc != pThis._loc ); }, _skipComments : function() { var pThis = Eco.Json; var ch = pThis._ch; if ( ch == '/' ) { // Advance past the first / to find out what type of comment ch = pThis._nextChar(); switch ( ch ) { case '/': // single-line comment, read through end of line // Loop over the characters until we find // a newline or until there's no more characters left do { ch = pThis._nextChar(); } while ( ch != '\n' && ch != '' ) // move past the \n ch = pThis._nextChar(); break; case '*': // multi-line comment, read until closing */ // move past the opening * ch = pThis._nextChar(); // try to find a trailing */ while ( true ) { if ( ch == '*' ) { // check to see if we have a closing / ch = pThis._nextChar(); if ( ch == '/') { // move past the end of the closing */ ch = pThis._nextChar(); break; } } else { // move along, looking if the next character is a * ch = pThis._nextChar(); } // when we're here we've read past the end of // the string without finding a closing */, so error if ( ch == '' ) { pThis._parseError( "Multi-line comment not closed" ); } } break; // Can't match a comment after a /, so it's a parsing error default: pThis._parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" ); } } }, _skipWhite : function() { // As long as there are spaces in the input // stream, advance the current location pointer // past them var pThis = Eco.Json; var ch = pThis._ch; while ( pThis._isWhiteSpace( ch ) ) { ch = pThis._nextChar(); } }, _isWhiteSpace : function( ch ) { return ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' ); }, _isDigit : function( ch ) { return ( ch >= '0' && ch <= '9' ); }, _isHexDigit : function( ch ) { // get the uppercase value of ch so we only have // to compare the value between 'A' and 'F' var uc = ch.toUpperCase(); // a hex digit is a digit of A-F, inclusive ( using // our uppercase constraint ) return ( Eco.Json._isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) ); }, _parseError : function( msg ) { //throw new Error(3000, msg); Eco.Logger.error({"message":msg, "stack":true}); } }); }