00001 <?php
00002
00003
00061 define('SERVICES_JSON_SLICE', 1);
00062
00066 define('SERVICES_JSON_IN_STR', 2);
00067
00071 define('SERVICES_JSON_IN_ARR', 3);
00072
00076 define('SERVICES_JSON_IN_OBJ', 4);
00077
00081 define('SERVICES_JSON_IN_CMT', 5);
00082
00086 define('SERVICES_JSON_LOOSE_TYPE', 16);
00087
00091 define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
00092
00116 class Services_JSON
00117 {
00134 function Services_JSON($use = 0)
00135 {
00136 $this->use = $use;
00137 }
00138
00139 private static $mHavePear = null;
00145 private static function pearInstalled() {
00146 if ( self::$mHavePear === null ) {
00147 self::$mHavePear = class_exists( 'pear' );
00148 }
00149 return self::$mHavePear;
00150 }
00151
00163 function utf162utf8($utf16)
00164 {
00165
00166 if(function_exists('mb_convert_encoding')) {
00167 return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
00168 }
00169
00170 $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
00171
00172 switch(true) {
00173 case ((0x7F & $bytes) == $bytes):
00174
00175
00176 return chr(0x7F & $bytes);
00177
00178 case (0x07FF & $bytes) == $bytes:
00179
00180
00181 return chr(0xC0 | (($bytes >> 6) & 0x1F))
00182 . chr(0x80 | ($bytes & 0x3F));
00183
00184 case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16{2})) == 0xDC:
00185
00186 $char = ((($bytes & 0x03FF) << 10)
00187 | ((ord($utf16{2}) & 0x03) << 8)
00188 | ord($utf16{3}));
00189 $char += 0x10000;
00190 return chr(0xF0 | (($char >> 18) & 0x07))
00191 . chr(0x80 | (($char >> 12) & 0x3F))
00192 . chr(0x80 | (($char >> 6) & 0x3F))
00193 . chr(0x80 | ($char & 0x3F));
00194
00195 case (0xFFFF & $bytes) == $bytes:
00196
00197
00198 return chr(0xE0 | (($bytes >> 12) & 0x0F))
00199 . chr(0x80 | (($bytes >> 6) & 0x3F))
00200 . chr(0x80 | ($bytes & 0x3F));
00201 }
00202
00203
00204 return '';
00205 }
00206
00218 function utf82utf16($utf8)
00219 {
00220
00221 if(function_exists('mb_convert_encoding')) {
00222 return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
00223 }
00224
00225 switch(strlen($utf8)) {
00226 case 1:
00227
00228
00229 return $utf8;
00230
00231 case 2:
00232
00233
00234 return chr(0x07 & (ord($utf8{0}) >> 2))
00235 . chr((0xC0 & (ord($utf8{0}) << 6))
00236 | (0x3F & ord($utf8{1})));
00237
00238 case 3:
00239
00240
00241 return chr((0xF0 & (ord($utf8{0}) << 4))
00242 | (0x0F & (ord($utf8{1}) >> 2)))
00243 . chr((0xC0 & (ord($utf8{1}) << 6))
00244 | (0x7F & ord($utf8{2})));
00245
00246 case 4:
00247
00248 if(ord($utf8{0}) > 0xF4) return ''; # invalid
00249 $char = ((0x1C0000 & (ord($utf8{0}) << 18))
00250 | (0x03F000 & (ord($utf8{1}) << 12))
00251 | (0x000FC0 & (ord($utf8{2}) << 6))
00252 | (0x00003F & ord($utf8{3})));
00253 if($char > 0x10FFFF) return ''; # invalid
00254 $char -= 0x10000;
00255 return chr(0xD8 | (($char >> 18) & 0x03))
00256 . chr(($char >> 10) & 0xFF)
00257 . chr(0xDC | (($char >> 8) & 0x03))
00258 . chr($char & 0xFF);
00259 }
00260
00261
00262 return '';
00263 }
00264
00277 function encode($var, $pretty=false)
00278 {
00279 $this->indent = 0;
00280 $this->pretty = $pretty;
00281 $this->nameValSeparator = $pretty ? ': ' : ':';
00282 return $this->encode2($var);
00283 }
00284
00296 function encode2($var)
00297 {
00298 if ($this->pretty) {
00299 $close = "\n" . str_repeat("\t", $this->indent);
00300 $open = $close . "\t";
00301 $mid = ',' . $open;
00302 }
00303 else {
00304 $open = $close = '';
00305 $mid = ',';
00306 }
00307
00308 switch (gettype($var)) {
00309 case 'boolean':
00310 return $var ? 'true' : 'false';
00311
00312 case 'NULL':
00313 return 'null';
00314
00315 case 'integer':
00316 return (int) $var;
00317
00318 case 'double':
00319 case 'float':
00320 return (float) $var;
00321
00322 case 'string':
00323
00324 $ascii = '';
00325 $strlen_var = strlen($var);
00326
00327
00328
00329
00330
00331 for ($c = 0; $c < $strlen_var; ++$c) {
00332
00333 $ord_var_c = ord($var{$c});
00334
00335 switch (true) {
00336 case $ord_var_c == 0x08:
00337 $ascii .= '\b';
00338 break;
00339 case $ord_var_c == 0x09:
00340 $ascii .= '\t';
00341 break;
00342 case $ord_var_c == 0x0A:
00343 $ascii .= '\n';
00344 break;
00345 case $ord_var_c == 0x0C:
00346 $ascii .= '\f';
00347 break;
00348 case $ord_var_c == 0x0D:
00349 $ascii .= '\r';
00350 break;
00351
00352 case $ord_var_c == 0x22:
00353 case $ord_var_c == 0x2F:
00354 case $ord_var_c == 0x5C:
00355
00356 $ascii .= '\\'.$var{$c};
00357 break;
00358
00359 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
00360
00361 $ascii .= $var{$c};
00362 break;
00363
00364 case (($ord_var_c & 0xE0) == 0xC0):
00365
00366
00367 $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
00368 $c += 1;
00369 $utf16 = $this->utf82utf16($char);
00370 $ascii .= sprintf('\u%04s', bin2hex($utf16));
00371 break;
00372
00373 case (($ord_var_c & 0xF0) == 0xE0):
00374
00375
00376 $char = pack('C*', $ord_var_c,
00377 ord($var{$c + 1}),
00378 ord($var{$c + 2}));
00379 $c += 2;
00380 $utf16 = $this->utf82utf16($char);
00381 $ascii .= sprintf('\u%04s', bin2hex($utf16));
00382 break;
00383
00384 case (($ord_var_c & 0xF8) == 0xF0):
00385
00386
00387
00388 $char = pack('C*', $ord_var_c,
00389 ord($var{$c + 1}),
00390 ord($var{$c + 2}),
00391 ord($var{$c + 3}));
00392 $c += 3;
00393 $utf16 = $this->utf82utf16($char);
00394 if($utf16 == '') {
00395 $ascii .= '\ufffd';
00396 } else {
00397 $utf16 = str_split($utf16, 2);
00398 $ascii .= sprintf('\u%04s\u%04s', bin2hex($utf16[0]), bin2hex($utf16[1]));
00399 }
00400 break;
00401 }
00402 }
00403
00404 return '"'.$ascii.'"';
00405
00406 case 'array':
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420
00421
00422
00423
00424
00425
00426 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
00427 $this->indent++;
00428 $properties = array_map(array($this, 'name_value'),
00429 array_keys($var),
00430 array_values($var));
00431 $this->indent--;
00432
00433 foreach($properties as $property) {
00434 if(Services_JSON::isError($property)) {
00435 return $property;
00436 }
00437 }
00438
00439 return '{' . $open . join($mid, $properties) . $close . '}';
00440 }
00441
00442
00443 $this->indent++;
00444 $elements = array_map(array($this, 'encode2'), $var);
00445 $this->indent--;
00446
00447 foreach($elements as $element) {
00448 if(Services_JSON::isError($element)) {
00449 return $element;
00450 }
00451 }
00452
00453 return '[' . $open . join($mid, $elements) . $close . ']';
00454
00455 case 'object':
00456 $vars = get_object_vars($var);
00457
00458 $this->indent++;
00459 $properties = array_map(array($this, 'name_value'),
00460 array_keys($vars),
00461 array_values($vars));
00462 $this->indent--;
00463
00464 foreach($properties as $property) {
00465 if(Services_JSON::isError($property)) {
00466 return $property;
00467 }
00468 }
00469
00470 return '{' . $open . join($mid, $properties) . $close . '}';
00471
00472 default:
00473 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
00474 ? 'null'
00475 : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
00476 }
00477 }
00478
00488 function name_value($name, $value)
00489 {
00490 $encoded_value = $this->encode2($value);
00491
00492 if(Services_JSON::isError($encoded_value)) {
00493 return $encoded_value;
00494 }
00495
00496 return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value;
00497 }
00498
00507 function reduce_string($str)
00508 {
00509 $str = preg_replace(array(
00510
00511
00512 '#^\s*//(.+)$#m',
00513
00514
00515 '#^\s*/\*(.+)\*/#Us',
00516
00517
00518 '#/\*(.+)\*/\s*$#Us'
00519
00520 ), '', $str);
00521
00522
00523 return trim($str);
00524 }
00525
00538 function decode($str)
00539 {
00540 $str = $this->reduce_string($str);
00541
00542 switch (strtolower($str)) {
00543 case 'true':
00544 return true;
00545
00546 case 'false':
00547 return false;
00548
00549 case 'null':
00550 return null;
00551
00552 default:
00553 $m = array();
00554
00555 if (is_numeric($str)) {
00556
00557
00558
00559
00560
00561
00562
00563 return ((float)$str == (integer)$str)
00564 ? (integer)$str
00565 : (float)$str;
00566
00567 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
00568
00569 $delim = substr($str, 0, 1);
00570 $chrs = substr($str, 1, -1);
00571 $utf8 = '';
00572 $strlen_chrs = strlen($chrs);
00573
00574 for ($c = 0; $c < $strlen_chrs; ++$c) {
00575
00576 $substr_chrs_c_2 = substr($chrs, $c, 2);
00577 $ord_chrs_c = ord($chrs{$c});
00578
00579 switch (true) {
00580 case $substr_chrs_c_2 == '\b':
00581 $utf8 .= chr(0x08);
00582 ++$c;
00583 break;
00584 case $substr_chrs_c_2 == '\t':
00585 $utf8 .= chr(0x09);
00586 ++$c;
00587 break;
00588 case $substr_chrs_c_2 == '\n':
00589 $utf8 .= chr(0x0A);
00590 ++$c;
00591 break;
00592 case $substr_chrs_c_2 == '\f':
00593 $utf8 .= chr(0x0C);
00594 ++$c;
00595 break;
00596 case $substr_chrs_c_2 == '\r':
00597 $utf8 .= chr(0x0D);
00598 ++$c;
00599 break;
00600
00601 case $substr_chrs_c_2 == '\\"':
00602 case $substr_chrs_c_2 == '\\\'':
00603 case $substr_chrs_c_2 == '\\\\':
00604 case $substr_chrs_c_2 == '\\/':
00605 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
00606 ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
00607 $utf8 .= $chrs{++$c};
00608 }
00609 break;
00610
00611 case preg_match('/\\\uD[89AB][0-9A-F]{2}\\\uD[C-F][0-9A-F]{2}/i', substr($chrs, $c, 12)):
00612
00613 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
00614 . chr(hexdec(substr($chrs, ($c + 4), 2)))
00615 . chr(hexdec(substr($chrs, ($c + 8), 2)))
00616 . chr(hexdec(substr($chrs, ($c + 10), 2)));
00617 $utf8 .= $this->utf162utf8($utf16);
00618 $c += 11;
00619 break;
00620
00621 case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
00622
00623 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
00624 . chr(hexdec(substr($chrs, ($c + 4), 2)));
00625 $utf8 .= $this->utf162utf8($utf16);
00626 $c += 5;
00627 break;
00628
00629 case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
00630 $utf8 .= $chrs{$c};
00631 break;
00632
00633 case ($ord_chrs_c & 0xE0) == 0xC0:
00634
00635
00636 $utf8 .= substr($chrs, $c, 2);
00637 ++$c;
00638 break;
00639
00640 case ($ord_chrs_c & 0xF0) == 0xE0:
00641
00642
00643 $utf8 .= substr($chrs, $c, 3);
00644 $c += 2;
00645 break;
00646
00647 case ($ord_chrs_c & 0xF8) == 0xF0:
00648
00649
00650 $utf8 .= substr($chrs, $c, 4);
00651 $c += 3;
00652 break;
00653
00654 case ($ord_chrs_c & 0xFC) == 0xF8:
00655
00656
00657 $utf8 .= substr($chrs, $c, 5);
00658 $c += 4;
00659 break;
00660
00661 case ($ord_chrs_c & 0xFE) == 0xFC:
00662
00663
00664 $utf8 .= substr($chrs, $c, 6);
00665 $c += 5;
00666 break;
00667
00668 }
00669
00670 }
00671
00672 return $utf8;
00673
00674 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
00675
00676
00677 if ($str{0} == '[') {
00678 $stk = array(SERVICES_JSON_IN_ARR);
00679 $arr = array();
00680 } else {
00681 if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
00682 $stk = array(SERVICES_JSON_IN_OBJ);
00683 $obj = array();
00684 } else {
00685 $stk = array(SERVICES_JSON_IN_OBJ);
00686 $obj = new stdClass();
00687 }
00688 }
00689
00690 array_push($stk, array( 'what' => SERVICES_JSON_SLICE,
00691 'where' => 0,
00692 'delim' => false));
00693
00694 $chrs = substr($str, 1, -1);
00695 $chrs = $this->reduce_string($chrs);
00696
00697 if ($chrs == '') {
00698 if (reset($stk) == SERVICES_JSON_IN_ARR) {
00699 return $arr;
00700
00701 } else {
00702 return $obj;
00703
00704 }
00705 }
00706
00707
00708
00709 $strlen_chrs = strlen($chrs);
00710
00711 for ($c = 0; $c <= $strlen_chrs; ++$c) {
00712
00713 $top = end($stk);
00714 $substr_chrs_c_2 = substr($chrs, $c, 2);
00715
00716 if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
00717
00718
00719 $slice = substr($chrs, $top['where'], ($c - $top['where']));
00720 array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
00721
00722
00723 if (reset($stk) == SERVICES_JSON_IN_ARR) {
00724
00725 array_push($arr, $this->decode($slice));
00726
00727 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
00728
00729
00730
00731
00732 $parts = array();
00733
00734 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
00735
00736 $key = $this->decode($parts[1]);
00737 $val = $this->decode($parts[2]);
00738
00739 if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
00740 $obj[$key] = $val;
00741 } else {
00742 $obj->$key = $val;
00743 }
00744 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
00745
00746 $key = $parts[1];
00747 $val = $this->decode($parts[2]);
00748
00749 if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
00750 $obj[$key] = $val;
00751 } else {
00752 $obj->$key = $val;
00753 }
00754 }
00755
00756 }
00757
00758 } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
00759
00760 array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
00761
00762
00763 } elseif (($chrs{$c} == $top['delim']) &&
00764 ($top['what'] == SERVICES_JSON_IN_STR) &&
00765 (($chrs{$c - 1} != '\\') ||
00766 ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) {
00767
00768 array_pop($stk);
00769
00770
00771 } elseif (($chrs{$c} == '[') &&
00772 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
00773
00774 array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
00775
00776
00777 } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
00778
00779 array_pop($stk);
00780
00781
00782 } elseif (($chrs{$c} == '{') &&
00783 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
00784
00785 array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
00786
00787
00788 } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
00789
00790 array_pop($stk);
00791
00792
00793 } elseif (($substr_chrs_c_2 == '/*') &&
00794 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
00795
00796 array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
00797 $c++;
00798
00799
00800 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
00801
00802 array_pop($stk);
00803 $c++;
00804
00805 for ($i = $top['where']; $i <= $c; ++$i)
00806 $chrs = substr_replace($chrs, ' ', $i, 1);
00807
00808
00809
00810 }
00811
00812 }
00813
00814 if (reset($stk) == SERVICES_JSON_IN_ARR) {
00815 return $arr;
00816
00817 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
00818 return $obj;
00819
00820 }
00821
00822 }
00823 }
00824 }
00825
00829 function isError($data, $code = null)
00830 {
00831 if ( self::pearInstalled() ) {
00832
00833 return @PEAR::isError($data, $code);
00834 } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
00835 is_subclass_of($data, 'services_json_error'))) {
00836 return true;
00837 }
00838
00839 return false;
00840 }
00841 }
00842
00843
00844
00846 if (class_exists('PEAR_Error')) {
00847
00851 class Services_JSON_Error extends PEAR_Error
00852 {
00853 function Services_JSON_Error($message = 'unknown error', $code = null,
00854 $mode = null, $options = null, $userinfo = null)
00855 {
00856 parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
00857 }
00858 }
00859
00860 } else {
00862
00867 class Services_JSON_Error
00868 {
00869 function Services_JSON_Error($message = 'unknown error', $code = null,
00870 $mode = null, $options = null, $userinfo = null)
00871 {
00872
00873 }
00874 }
00875 }