3 // +--------------------------------------------------------------------+
4 // | PHP version 4 and 5 |
5 // +--------------------------------------------------------------------+
6 // | Copyright (c) 2006 Hawk |
7 // +--------------------------------------------------------------------+
8 // | This source file is subject to version 3.00 of the PHP License, |
9 // | that is available at http://www.php.net/license/3_0.txt. |
10 // | If you did not receive a copy of the PHP license and are unable to |
11 // | obtain it through the world-wide-web, please send a note to |
12 // | license@php.net so we can mail you a copy immediately. |
13 // +--------------------------------------------------------------------+
14 // | Authors: Hawk <scholar@hawklab.jp> |
15 // +--------------------------------------------------------------------+
19 require_once(dirname(__FILE__) .'/Decoder/Tokenizer.php');
22 * Converts JSON-formatted string to appropriate PHP variable
26 * //create a new instance of Jsphon_Decoder
27 * $json =& new Jsphon_Decoder();
29 * //convert JSON-formatted string to PHP variable
30 * $value = '["foo","bar",{"hoge":[1,2]}]';
31 * $var = $json->decode($value);
34 * //array('foo', 'bar', array('hoge' => array(1,2)))
47 var $_transTable = array(
58 var $_allUESreg = '/\\\u([a-fA-F0-9]{4})/';
60 var $_utf16surUESreg = '/\\\u(D[89AB][A-F0-9]{2})\\\u(D[C-F][A-F0-9]{2})/i';
63 * construct a new Jsphon_Decoder instance.
65 * @param bool $decodeOverUCS2 If true, decodeString() converts the whole
66 * Unicode escape sequences (\uXXXX) including surrogate pairs
67 * to corresponding characters in UTF-8.
69 function Jsphon_Decoder($decodeOverUCS2=false)
71 $this->_decodeOverUCS2 = $decodeOverUCS2;
72 $this->_mbstring = extension_loaded('mbstring');
76 * decodes a JSON string into appropriate variable
81 function decode($json)
83 $tknz = new Jsphon_Decoder_Tokenizer($json,
84 array(&$this, 'handleInternalError'));
85 $this->_internalError = false;
88 if($tknz->nextToken()) {
89 $result = $this->_decodeJSValue($tknz);
92 $this->_internalError = false;
101 function _decodeJSValue(&$tknz)
103 switch($tknz->getToken()) {
104 case JSPHON_TOKEN_DATUM:
105 if(is_string($r = $tknz->getTokenValue())) {
106 $r = $this->decodeString($r);
110 case JSPHON_TOKEN_LBRACKET:
111 return $this->_decodeArray($tknz);
113 case JSPHON_TOKEN_LBRACE:
114 return $this->_decodeObject($tknz);
117 $this->_error("syntax error: Expecting '{', '[' or DAUM.");
123 * Decodes a JSON array format:
124 * [element, element2,...,elementN]
126 * @param Object $tknz
129 function _decodeArray(&$tknz)
132 if(!($token = $tknz->nextToken())) {
135 } elseif($token == JSPHON_TOKEN_RBRACKET) {
140 while( ($value = $this->_decodeJSValue($tknz) or true)
141 and !$this->_internalError
142 and ($ret[] = $value or true)
143 and $token = $tknz->nextToken()
144 and $token == JSPHON_TOKEN_COMMA
145 and $token = $tknz->nextToken()
148 if($this->_internalError) {
151 } elseif($token == JSPHON_TOKEN_RBRACKET) {
154 $this->_error("Missing ',' or ']' in array encoding.");
160 * Decodes an object of the form:
161 * { "attribute: value, "attribute2" : value,...}
163 * _decodeObject() always converts a JSON-object to an associative array,
164 * because you can't access empty property in PHP5.
167 * $obj->{""} = "foo"; //Fatal error: Cannot access empty property
170 * @param Object $tknz
173 function _decodeObject(&$tknz)
176 if(!($token = $tknz->nextToken())) {
179 } elseif($token == JSPHON_TOKEN_RBRACE) {
184 while( ($key = $this->_decodeJSValue($tknz) or true)
185 and !$this->_internalError
186 and $this->_checkObjectKey($key)
187 and $token = $tknz->nextToken()
188 and $this->_checkKeyValueSep($token)
189 and $token = $tknz->nextToken()
190 and ($value = $this->_decodeJSValue($tknz) or true)
191 and !$this->_internalError
192 and ($ret[$key] = $value or true)
193 and $token = $tknz->nextToken()
194 and $token == JSPHON_TOKEN_COMMA
195 and $token = $tknz->nextToken()
198 if($this->_internalError) {
201 } elseif($token == JSPHON_TOKEN_RBRACE) {
204 $this->_error("Missing ',' or '}' in object encoding.");
209 function _checkObjectKey($key)
211 if(!is_string($key)) {
212 $this->_error("Object's key must be a string, but is ". gettype($key));
218 function _checkKeyValueSep($token)
220 if($token != JSPHON_TOKEN_COLON) {
221 $this->_error("Missing ':' in object encoding.");
231 * @param String $encoded
234 function decodeString($encoded)
236 $ret = strtr($encoded, $this->_transTable);
238 if($this->_decodeOverUCS2) {
239 return $this->_decodeUESOverUCS2($ret);
241 //_decodeUESWithoutMbstring() fastar than _decodeUES()...
242 return $this->_decodeUESWithoutMbstring($ret);
246 if(!$this->_mbstring) {
247 return $this->_decodeUESWithoutMbstring($ret);
248 } elseif($this->_decodeOverUCS2) {
249 return $this->_decodeUESOverUCS2($ret);
251 return $this->_decodeUES($ret);
256 function _decodeUES($str)
258 $transTable = array();
260 if(!preg_match_all($this->_allUESreg, $str, $matches)) {
264 $codepoints = $matches[1];
265 foreach($matches[0] as $i => $escSeq) {
266 if(isset($transTable[$escSeq])) {
269 $codepoint = hexdec($codepoints[$i]);
270 $transTable[$escSeq] = (0xD800 <= $codepoint && $codepoint <= 0xDFFF) ? ""
271 : mb_convert_encoding(pack('n', $codepoint), 'UTF-8', 'UCS-2');
273 return strtr($str, $transTable);
276 function _decodeUESOverUCS2($str)
278 $transTable = array();
280 if(!preg_match_all($this->_allUESreg, $str, $u16)) {
284 if(preg_match_all($this->_utf16surUESreg, $str, $u16sur)) {
285 $sur1st = $u16sur[1];
286 $sur2nd = $u16sur[2];
288 foreach($u16sur[0] as $i => $escSeq) {
289 if(isset($transTable[$escSeq])) {
292 $transTable[$escSeq] = mb_convert_encoding(
293 pack('n2', hexdec($sur1st[$i]), hexdec($sur2nd[$i])), 'UTF-8', 'UTF-16BE');
297 $codepoints = $u16[1];
298 foreach($u16[0] as $i => $escSeq) {
299 if(isset($transTable[$escSeq])) {
302 $codepoint = hexdec($codepoints[$i]);
303 $transTable[$escSeq] = (0xD800 <= $codepoint && $codepoint <= 0xDFFF) ? ""
304 : mb_convert_encoding(pack('n', $codepoint), 'UTF-8', 'UCS-2');
306 return strtr($str, $transTable);
309 function _decodeUESWithoutMbstring($str)
311 $transTable = array();
313 if(!preg_match_all($this->_allUESreg, $str, $matches)) {
317 $codepoints = $matches[1];
318 foreach($matches[0] as $i => $escSeq) {
319 if(isset($transTable[$escSeq])) {
325 $cp = hexdec($codepoints[$i]);
328 $utf8char = chr($cp);
331 case (0xD800 <= $cp && $cp <= 0xDFFF):
335 $utf8char = chr($cp >> 6 & 0x1F | 0xC0) . chr($cp & 0x3F | 0x80);
338 case ($cp < 0x10000):
339 $utf8char = chr($cp >> 12 & 0xF | 0xE0) .
340 chr($cp >> 6 & 0x3F | 0x80) . chr($cp & 0x3F | 0x80);
345 $transTable[$escSeq] = $utf8char;
347 return strtr($str, $transTable);
352 * a simple wrapper for PEAR_ErrorStack::push.
354 * @param String $message
355 * @param array $param
356 * @param string $level
360 function _error($message,
363 $code=JSPHON_ERROR_DECODE_SYNTAX)
365 $e = Jsphon_Error::push(
366 $code, $level, $param, $message, false, debug_backtrace());
367 $this->_internalError = true;
371 function handleInternalError($err)
373 $this->_internalError = true;