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