OSDN Git Service

PHP Notice対応
[trpgtools-onweb/AjaxChat.git] / lib / Jsphon / Decoder.php
1 <?php
2 //
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 // +--------------------------------------------------------------------+
16 //
17 //
18
19 require_once(dirname(__FILE__) .'/Decoder/Tokenizer.php');
20
21 /**
22  * Converts JSON-formatted string to appropriate PHP variable
23  * 
24  * example:
25  * <code>
26  * //create a new instance of Jsphon_Decoder
27  * $json =& new Jsphon_Decoder();
28  * 
29  * //convert JSON-formatted string to PHP variable
30  * $value = '["foo","bar",{"hoge":[1,2]}]';
31  * $var = $json->decode($value);
32  * 
33  * print_r($var);
34  * //array('foo', 'bar', array('hoge' => array(1,2)))
35  * </code>
36  * 
37  * @author Hawk
38  */
39 class Jsphon_Decoder
40 {
41     var $_mbstring;
42
43     var $_decodeOverUCS2;
44
45     var $_internalError;
46
47     var $_transTable = array(
48         '\b' => "\x08",
49         '\t' => "\x09",
50         '\n' => "\x0A",
51         '\f' => "\x0C",
52         '\r' => "\x0D",
53         '\"' => "\x22",
54         '\/' => "\x2F",
55          '\\\\' => "\x5C"
56         );
57
58     var $_allUESreg = '/\\\u([a-fA-F0-9]{4})/';
59
60     var $_utf16surUESreg = '/\\\u(D[89AB][A-F0-9]{2})\\\u(D[C-F][A-F0-9]{2})/i';
61
62     /**
63      * construct a new Jsphon_Decoder instance.
64      * 
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.
68      */
69     function Jsphon_Decoder($decodeOverUCS2=false)
70     {
71         $this->_decodeOverUCS2 = $decodeOverUCS2;
72         $this->_mbstring = extension_loaded('mbstring');
73     }
74
75     /**
76      * decodes a JSON string into appropriate variable
77      * 
78      * @param  String    $json
79      * @return mixed
80      */
81     function decode($json)
82     {
83         $tknz = new Jsphon_Decoder_Tokenizer($json,
84                                              array(&$this, 'handleInternalError'));
85         $this->_internalError = false;
86
87         $result = null;
88         if($tknz->nextToken()) {
89             $result = $this->_decodeJSValue($tknz);
90         }
91         
92         $this->_internalError = false;
93         return $result;
94     }
95
96     /**
97      * 
98      * @param  Object $tknz
99      * @return mixed
100      */
101     function _decodeJSValue(&$tknz)
102     {
103         switch($tknz->getToken()) {
104           case JSPHON_TOKEN_DATUM:
105             if(is_string($r = $tknz->getTokenValue())) {
106                 $r = $this->decodeString($r);
107             }
108             return $r;
109             
110           case JSPHON_TOKEN_LBRACKET:
111             return $this->_decodeArray($tknz);
112
113           case JSPHON_TOKEN_LBRACE:
114             return $this->_decodeObject($tknz);
115
116           default:
117             $this->_error("syntax error: Expecting '{', '[' or DAUM.");
118             return null;
119         }
120     }
121
122     /**
123      * Decodes a JSON array format:
124      *  [element, element2,...,elementN]
125      * 
126      * @param  Object $tknz
127      * @return array
128      */
129     function _decodeArray(&$tknz)
130     {
131         $ret = array();
132         if(!($token = $tknz->nextToken())) {
133             return null;
134             
135         } elseif($token == JSPHON_TOKEN_RBRACKET) {
136             return $ret;
137         }
138
139         //if false, break
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()
146             );
147
148         if($this->_internalError) {
149             return null;
150             
151         } elseif($token == JSPHON_TOKEN_RBRACKET) {
152             return $ret;
153         } else {
154             $this->_error("Missing ',' or ']' in array encoding.");
155             return null;
156         }
157     }
158
159     /**
160      * Decodes an object of the form:
161      *  { "attribute: value, "attribute2" : value,...}
162      * 
163      * _decodeObject() always converts a JSON-object to an associative array,
164      * because you can't access empty property in PHP5.
165      * <code>
166      * // { "":"foo" }
167      * $obj->{""} = "foo"; //Fatal error: Cannot access empty property
168      * </code>
169      * 
170      * @param  Object $tknz
171      * @return array
172      */
173     function _decodeObject(&$tknz)
174     {
175         $ret = array();
176         if(!($token = $tknz->nextToken())) {
177             return null;
178             
179         } elseif($token == JSPHON_TOKEN_RBRACE) {
180             return $ret;
181         }
182
183         //if false, break
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()
196             );
197
198         if($this->_internalError) {
199             return null;
200             
201         } elseif($token == JSPHON_TOKEN_RBRACE) {
202             return $ret;
203         } else {
204             $this->_error("Missing ',' or '}' in object encoding.");
205             return null;
206         }
207     }
208
209     function _checkObjectKey($key)
210     {
211         if(!is_string($key)) {
212             $this->_error("Object's key must be a string, but is ". gettype($key));
213             return false;
214         }
215         return true;
216     }
217
218     function _checkKeyValueSep($token)
219     {
220         if($token != JSPHON_TOKEN_COLON) {
221             $this->_error("Missing ':' in object encoding.");
222             return false;
223         }
224         return true;
225     }
226     
227
228     /**
229      * 
230      * 
231      * @param  String    $encoded
232      * @return String
233      */
234     function decodeString($encoded)
235     {
236         $ret = strtr($encoded, $this->_transTable);
237         
238         if($this->_decodeOverUCS2) {
239             return $this->_decodeUESOverUCS2($ret);
240         } else {
241             //_decodeUESWithoutMbstring() fastar than _decodeUES()...
242             return $this->_decodeUESWithoutMbstring($ret);
243         }
244         
245         /*
246         if(!$this->_mbstring) {
247             return $this->_decodeUESWithoutMbstring($ret);
248         } elseif($this->_decodeOverUCS2) {
249             return $this->_decodeUESOverUCS2($ret);
250         } else {
251             return $this->_decodeUES($ret);
252         }
253         */
254     }
255
256     function _decodeUES($str)
257     {
258         $transTable = array();
259
260         if(!preg_match_all($this->_allUESreg, $str, $matches)) {
261             return $str;
262         }
263
264         $codepoints = $matches[1];
265         foreach($matches[0] as $i => $escSeq) {
266             if(isset($transTable[$escSeq])) {
267                 continue;
268             }
269             $codepoint = hexdec($codepoints[$i]);
270             $transTable[$escSeq] = (0xD800 <= $codepoint && $codepoint <= 0xDFFF) ? ""
271             : mb_convert_encoding(pack('n', $codepoint), 'UTF-8', 'UCS-2');
272         }
273         return strtr($str, $transTable);
274     }
275     
276     function _decodeUESOverUCS2($str)
277     {
278         $transTable = array();
279
280         if(!preg_match_all($this->_allUESreg, $str, $u16)) {
281             return $str;
282         }
283         
284         if(preg_match_all($this->_utf16surUESreg, $str, $u16sur)) {
285             $sur1st = $u16sur[1];
286             $sur2nd = $u16sur[2];
287             
288             foreach($u16sur[0] as $i => $escSeq) {
289                 if(isset($transTable[$escSeq])) {
290                     continue;
291                 }
292                 $transTable[$escSeq] = mb_convert_encoding(
293                     pack('n2', hexdec($sur1st[$i]), hexdec($sur2nd[$i])), 'UTF-8', 'UTF-16BE');
294             }
295         }
296
297         $codepoints = $u16[1];
298         foreach($u16[0] as $i => $escSeq) {
299             if(isset($transTable[$escSeq])) {
300                 continue;
301             }
302             $codepoint = hexdec($codepoints[$i]);
303             $transTable[$escSeq] = (0xD800 <= $codepoint && $codepoint <= 0xDFFF) ? ""
304             : mb_convert_encoding(pack('n', $codepoint), 'UTF-8', 'UCS-2');
305         }
306         return strtr($str, $transTable);
307     }
308
309     function _decodeUESWithoutMbstring($str)
310     {
311         $transTable = array();
312
313         if(!preg_match_all($this->_allUESreg, $str, $matches)) {
314             return $str;
315         }
316
317         $codepoints = $matches[1];
318         foreach($matches[0] as $i => $escSeq) {
319             if(isset($transTable[$escSeq])) {
320                 continue;
321             }
322             
323             $utf8char = "";
324
325             $cp = hexdec($codepoints[$i]);
326             switch(true) {
327               case ($cp < 0x80):
328                 $utf8char = chr($cp);
329                 break;
330                 
331               case (0xD800 <= $cp && $cp <= 0xDFFF):
332                 break;
333
334               case ($cp < 0x800):
335                 $utf8char = chr($cp >> 6 & 0x1F | 0xC0) . chr($cp & 0x3F | 0x80);
336                 break;
337                 
338               case ($cp < 0x10000):
339                 $utf8char = chr($cp >> 12 & 0xF | 0xE0) .
340                 chr($cp >> 6 & 0x3F | 0x80) . chr($cp & 0x3F | 0x80);
341                 break;
342
343               default:
344             }
345             $transTable[$escSeq] = $utf8char;
346         }
347         return strtr($str, $transTable);
348     }
349     
350
351     /**
352      * a simple wrapper for PEAR_ErrorStack::push.
353      * 
354      * @param  String    $message
355      * @param  array     $param
356      * @param  string    $level
357      * @param  int       $code
358      * @return String
359      */
360     function _error($message,
361                     $param= array(),
362                     $level='error',
363                     $code=JSPHON_ERROR_DECODE_SYNTAX)
364     {
365         $e = Jsphon_Error::push(
366             $code, $level, $param, $message, false, debug_backtrace());
367         $this->_internalError = true;
368         return $e;
369     }
370     
371     function handleInternalError($err)
372     {
373         $this->_internalError = true;
374     }
375 }
376
377 ?>