| // +--------------------------------------------------------------------+ // // require_once(dirname(__FILE__) .'/Decoder/Tokenizer.php'); /** * Converts JSON-formatted string to appropriate PHP variable * * example: * * //create a new instance of Jsphon_Decoder * $json =& new Jsphon_Decoder(); * * //convert JSON-formatted string to PHP variable * $value = '["foo","bar",{"hoge":[1,2]}]'; * $var = $json->decode($value); * * print_r($var); * //array('foo', 'bar', array('hoge' => array(1,2))) * * * @author Hawk */ class Jsphon_Decoder { var $_mbstring; var $_decodeOverUCS2; var $_internalError; var $_transTable = array( '\b' => "\x08", '\t' => "\x09", '\n' => "\x0A", '\f' => "\x0C", '\r' => "\x0D", '\"' => "\x22", '\/' => "\x2F", '\\\\' => "\x5C" ); var $_allUESreg = '/\\\u([a-fA-F0-9]{4})/'; var $_utf16surUESreg = '/\\\u(D[89AB][A-F0-9]{2})\\\u(D[C-F][A-F0-9]{2})/i'; /** * construct a new Jsphon_Decoder instance. * * @param bool $decodeOverUCS2 If true, decodeString() converts the whole * Unicode escape sequences (\uXXXX) including surrogate pairs * to corresponding characters in UTF-8. */ function Jsphon_Decoder($decodeOverUCS2=false) { $this->_decodeOverUCS2 = $decodeOverUCS2; $this->_mbstring = extension_loaded('mbstring'); } /** * decodes a JSON string into appropriate variable * * @param String $json * @return mixed */ function decode($json) { $tknz = new Jsphon_Decoder_Tokenizer($json, array(&$this, 'handleInternalError')); $this->_internalError = false; $result = null; if($tknz->nextToken()) { $result = $this->_decodeJSValue($tknz); } $this->_internalError = false; return $result; } /** * * @param Object $tknz * @return mixed */ function _decodeJSValue(&$tknz) { switch($tknz->getToken()) { case JSPHON_TOKEN_DATUM: if(is_string($r = $tknz->getTokenValue())) { $r = $this->decodeString($r); } return $r; case JSPHON_TOKEN_LBRACKET: return $this->_decodeArray($tknz); case JSPHON_TOKEN_LBRACE: return $this->_decodeObject($tknz); default: $this->_error("syntax error: Expecting '{', '[' or DAUM."); return null; } } /** * Decodes a JSON array format: * [element, element2,...,elementN] * * @param Object $tknz * @return array */ function _decodeArray(&$tknz) { $ret = array(); if(!($token = $tknz->nextToken())) { return null; } elseif($token == JSPHON_TOKEN_RBRACKET) { return $ret; } //if false, break while( ($value = $this->_decodeJSValue($tknz) or true) and !$this->_internalError and ($ret[] = $value or true) and $token = $tknz->nextToken() and $token == JSPHON_TOKEN_COMMA and $token = $tknz->nextToken() ); if($this->_internalError) { return null; } elseif($token == JSPHON_TOKEN_RBRACKET) { return $ret; } else { $this->_error("Missing ',' or ']' in array encoding."); return null; } } /** * Decodes an object of the form: * { "attribute: value, "attribute2" : value,...} * * _decodeObject() always converts a JSON-object to an associative array, * because you can't access empty property in PHP5. * * // { "":"foo" } * $obj->{""} = "foo"; //Fatal error: Cannot access empty property * * * @param Object $tknz * @return array */ function _decodeObject(&$tknz) { $ret = array(); if(!($token = $tknz->nextToken())) { return null; } elseif($token == JSPHON_TOKEN_RBRACE) { return $ret; } //if false, break while( ($key = $this->_decodeJSValue($tknz) or true) and !$this->_internalError and $this->_checkObjectKey($key) and $token = $tknz->nextToken() and $this->_checkKeyValueSep($token) and $token = $tknz->nextToken() and ($value = $this->_decodeJSValue($tknz) or true) and !$this->_internalError and ($ret[$key] = $value or true) and $token = $tknz->nextToken() and $token == JSPHON_TOKEN_COMMA and $token = $tknz->nextToken() ); if($this->_internalError) { return null; } elseif($token == JSPHON_TOKEN_RBRACE) { return $ret; } else { $this->_error("Missing ',' or '}' in object encoding."); return null; } } function _checkObjectKey($key) { if(!is_string($key)) { $this->_error("Object's key must be a string, but is ". gettype($key)); return false; } return true; } function _checkKeyValueSep($token) { if($token != JSPHON_TOKEN_COLON) { $this->_error("Missing ':' in object encoding."); return false; } return true; } /** * * * @param String $encoded * @return String */ function decodeString($encoded) { $ret = strtr($encoded, $this->_transTable); if($this->_decodeOverUCS2) { return $this->_decodeUESOverUCS2($ret); } else { //_decodeUESWithoutMbstring() fastar than _decodeUES()... return $this->_decodeUESWithoutMbstring($ret); } /* if(!$this->_mbstring) { return $this->_decodeUESWithoutMbstring($ret); } elseif($this->_decodeOverUCS2) { return $this->_decodeUESOverUCS2($ret); } else { return $this->_decodeUES($ret); } */ } function _decodeUES($str) { $transTable = array(); if(!preg_match_all($this->_allUESreg, $str, $matches)) { return $str; } $codepoints = $matches[1]; foreach($matches[0] as $i => $escSeq) { if(isset($transTable[$escSeq])) { continue; } $codepoint = hexdec($codepoints[$i]); $transTable[$escSeq] = (0xD800 <= $codepoint && $codepoint <= 0xDFFF) ? "" : mb_convert_encoding(pack('n', $codepoint), 'UTF-8', 'UCS-2'); } return strtr($str, $transTable); } function _decodeUESOverUCS2($str) { $transTable = array(); if(!preg_match_all($this->_allUESreg, $str, $u16)) { return $str; } if(preg_match_all($this->_utf16surUESreg, $str, $u16sur)) { $sur1st = $u16sur[1]; $sur2nd = $u16sur[2]; foreach($u16sur[0] as $i => $escSeq) { if(isset($transTable[$escSeq])) { continue; } $transTable[$escSeq] = mb_convert_encoding( pack('n2', hexdec($sur1st[$i]), hexdec($sur2nd[$i])), 'UTF-8', 'UTF-16BE'); } } $codepoints = $u16[1]; foreach($u16[0] as $i => $escSeq) { if(isset($transTable[$escSeq])) { continue; } $codepoint = hexdec($codepoints[$i]); $transTable[$escSeq] = (0xD800 <= $codepoint && $codepoint <= 0xDFFF) ? "" : mb_convert_encoding(pack('n', $codepoint), 'UTF-8', 'UCS-2'); } return strtr($str, $transTable); } function _decodeUESWithoutMbstring($str) { $transTable = array(); if(!preg_match_all($this->_allUESreg, $str, $matches)) { return $str; } $codepoints = $matches[1]; foreach($matches[0] as $i => $escSeq) { if(isset($transTable[$escSeq])) { continue; } $utf8char = ""; $cp = hexdec($codepoints[$i]); switch(true) { case ($cp < 0x80): $utf8char = chr($cp); break; case (0xD800 <= $cp && $cp <= 0xDFFF): break; case ($cp < 0x800): $utf8char = chr($cp >> 6 & 0x1F | 0xC0) . chr($cp & 0x3F | 0x80); break; case ($cp < 0x10000): $utf8char = chr($cp >> 12 & 0xF | 0xE0) . chr($cp >> 6 & 0x3F | 0x80) . chr($cp & 0x3F | 0x80); break; default: } $transTable[$escSeq] = $utf8char; } return strtr($str, $transTable); } /** * a simple wrapper for PEAR_ErrorStack::push. * * @param String $message * @param array $param * @param string $level * @param int $code * @return String */ function _error($message, $param= array(), $level='error', $code=JSPHON_ERROR_DECODE_SYNTAX) { $e = Jsphon_Error::push( $code, $level, $param, $message, false, debug_backtrace()); $this->_internalError = true; return $e; } function handleInternalError($err) { $this->_internalError = true; } } ?>