MediaWiki  REL1_19
Services_JSON.php
Go to the documentation of this file.
00001 <?php
00002 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
00003 
00062 define('SERVICES_JSON_SLICE',   1);
00063 
00067 define('SERVICES_JSON_IN_STR',  2);
00068 
00072 define('SERVICES_JSON_IN_ARR',  3);
00073 
00077 define('SERVICES_JSON_IN_OBJ',  4);
00078 
00082 define('SERVICES_JSON_IN_CMT', 5);
00083 
00087 define('SERVICES_JSON_LOOSE_TYPE', 16);
00088 
00092 define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
00093 
00117 class Services_JSON
00118 {
00135         function __construct($use = 0)
00136         {
00137                 $this->use = $use;
00138         }
00139 
00140         private static $mHavePear = null;
00146         private static function pearInstalled() {
00147                 if ( self::$mHavePear === null ) {
00148                         self::$mHavePear = class_exists( 'pear' );
00149                 }
00150                 return self::$mHavePear;
00151         }
00152 
00164         function utf162utf8($utf16)
00165         {
00166                 // oh please oh please oh please oh please oh please
00167                 if(function_exists('mb_convert_encoding')) {
00168                         return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
00169                 }
00170 
00171                 $bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
00172 
00173                 switch(true) {
00174                         case ((0x7F & $bytes) == $bytes):
00175                                 // this case should never be reached, because we are in ASCII range
00176                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00177                                 return chr(0x7F & $bytes);
00178 
00179                         case (0x07FF & $bytes) == $bytes:
00180                                 // return a 2-byte UTF-8 character
00181                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00182                                 return chr(0xC0 | (($bytes >> 6) & 0x1F))
00183                                          . chr(0x80 | ($bytes & 0x3F));
00184 
00185                         case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16[2])) == 0xDC:
00186                                 // return a 4-byte UTF-8 character
00187                                 $char = ((($bytes & 0x03FF) << 10)
00188                                            | ((ord($utf16[2]) & 0x03) << 8)
00189                                            | ord($utf16[3]));
00190                                 $char += 0x10000;
00191                                 return chr(0xF0 | (($char >> 18) & 0x07))
00192                                          . chr(0x80 | (($char >> 12) & 0x3F))
00193                                          . chr(0x80 | (($char >> 6) & 0x3F))
00194                                          . chr(0x80 | ($char & 0x3F));
00195 
00196                         case (0xFFFF & $bytes) == $bytes:
00197                                 // return a 3-byte UTF-8 character
00198                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00199                                 return chr(0xE0 | (($bytes >> 12) & 0x0F))
00200                                          . chr(0x80 | (($bytes >> 6) & 0x3F))
00201                                          . chr(0x80 | ($bytes & 0x3F));
00202                 }
00203 
00204                 // ignoring UTF-32 for now, sorry
00205                 return '';
00206         }
00207 
00219         function utf82utf16($utf8)
00220         {
00221                 // oh please oh please oh please oh please oh please
00222                 if(function_exists('mb_convert_encoding')) {
00223                         return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
00224                 }
00225 
00226                 switch(strlen($utf8)) {
00227                         case 1:
00228                                 // this case should never be reached, because we are in ASCII range
00229                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00230                                 return $utf8;
00231 
00232                         case 2:
00233                                 // return a UTF-16 character from a 2-byte UTF-8 char
00234                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00235                                 return chr(0x07 & (ord($utf8[0]) >> 2))
00236                                          . chr((0xC0 & (ord($utf8[0]) << 6))
00237                                                  | (0x3F & ord($utf8[1])));
00238 
00239                         case 3:
00240                                 // return a UTF-16 character from a 3-byte UTF-8 char
00241                                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00242                                 return chr((0xF0 & (ord($utf8[0]) << 4))
00243                                                  | (0x0F & (ord($utf8[1]) >> 2)))
00244                                          . chr((0xC0 & (ord($utf8[1]) << 6))
00245                                                  | (0x7F & ord($utf8[2])));
00246 
00247                         case 4:
00248                                 // return a UTF-16 surrogate pair from a 4-byte UTF-8 char
00249                                 if(ord($utf8[0]) > 0xF4) return ''; # invalid
00250                                 $char = ((0x1C0000 & (ord($utf8[0]) << 18))
00251                                            | (0x03F000 & (ord($utf8[1]) << 12))
00252                                            | (0x000FC0 & (ord($utf8[2]) << 6))
00253                                            | (0x00003F & ord($utf8[3])));
00254                                 if($char > 0x10FFFF) return ''; # invalid
00255                                 $char -= 0x10000;
00256                                 return chr(0xD8 | (($char >> 18) & 0x03))
00257                                          . chr(($char >> 10) & 0xFF)
00258                                          . chr(0xDC | (($char >> 8) & 0x03))
00259                                          . chr($char & 0xFF);
00260                 }
00261 
00262                 // ignoring UTF-32 for now, sorry
00263                 return '';
00264         }
00265 
00278         function encode($var, $pretty=false)
00279         {
00280                 $this->indent = 0;
00281                 $this->pretty = $pretty;
00282                 $this->nameValSeparator = $pretty ? ': ' : ':';
00283                 return $this->encode2($var);
00284         }
00285 
00297         function encode2($var)
00298         {
00299                 if ($this->pretty) {
00300                         $close = "\n" . str_repeat("\t", $this->indent);
00301                         $open = $close . "\t";
00302                         $mid = ',' . $open;
00303                 }
00304                 else {
00305                         $open = $close = '';
00306                         $mid = ',';
00307                 }
00308 
00309                 switch (gettype($var)) {
00310                         case 'boolean':
00311                                 return $var ? 'true' : 'false';
00312 
00313                         case 'NULL':
00314                                 return 'null';
00315 
00316                         case 'integer':
00317                                 return (int) $var;
00318 
00319                         case 'double':
00320                         case 'float':
00321                                 return (float) $var;
00322 
00323                         case 'string':
00324                                 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
00325                                 $ascii = '';
00326                                 $strlen_var = strlen($var);
00327 
00328                            /*
00329                                 * Iterate over every character in the string,
00330                                 * escaping with a slash or encoding to UTF-8 where necessary
00331                                 */
00332                                 for ($c = 0; $c < $strlen_var; ++$c) {
00333 
00334                                         $ord_var_c = ord($var[$c]);
00335 
00336                                         switch (true) {
00337                                                 case $ord_var_c == 0x08:
00338                                                         $ascii .= '\b';
00339                                                         break;
00340                                                 case $ord_var_c == 0x09:
00341                                                         $ascii .= '\t';
00342                                                         break;
00343                                                 case $ord_var_c == 0x0A:
00344                                                         $ascii .= '\n';
00345                                                         break;
00346                                                 case $ord_var_c == 0x0C:
00347                                                         $ascii .= '\f';
00348                                                         break;
00349                                                 case $ord_var_c == 0x0D:
00350                                                         $ascii .= '\r';
00351                                                         break;
00352 
00353                                                 case $ord_var_c == 0x22:
00354                                                 case $ord_var_c == 0x2F:
00355                                                 case $ord_var_c == 0x5C:
00356                                                         // double quote, slash, slosh
00357                                                         $ascii .= '\\'.$var[$c];
00358                                                         break;
00359 
00360                                                 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
00361                                                         // characters U-00000000 - U-0000007F (same as ASCII)
00362                                                         $ascii .= $var[$c];
00363                                                         break;
00364 
00365                                                 case (($ord_var_c & 0xE0) == 0xC0):
00366                                                         // characters U-00000080 - U-000007FF, mask 110XXXXX
00367                                                         // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00368                                                         $char = pack('C*', $ord_var_c, ord($var[$c + 1]));
00369                                                         $c += 1;
00370                                                         $utf16 = $this->utf82utf16($char);
00371                                                         $ascii .= sprintf('\u%04s', bin2hex($utf16));
00372                                                         break;
00373 
00374                                                 case (($ord_var_c & 0xF0) == 0xE0):
00375                                                         // characters U-00000800 - U-0000FFFF, mask 1110XXXX
00376                                                         // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00377                                                         $char = pack('C*', $ord_var_c,
00378                                                                         ord($var[$c + 1]),
00379                                                                         ord($var[$c + 2]));
00380                                                         $c += 2;
00381                                                         $utf16 = $this->utf82utf16($char);
00382                                                         $ascii .= sprintf('\u%04s', bin2hex($utf16));
00383                                                         break;
00384 
00385                                                 case (($ord_var_c & 0xF8) == 0xF0):
00386                                                         // characters U-00010000 - U-001FFFFF, mask 11110XXX
00387                                                         // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00388                                                         // These will always return a surrogate pair
00389                                                         $char = pack('C*', $ord_var_c,
00390                                                                         ord($var[$c + 1]),
00391                                                                         ord($var[$c + 2]),
00392                                                                         ord($var[$c + 3]));
00393                                                         $c += 3;
00394                                                         $utf16 = $this->utf82utf16($char);
00395                                                         if($utf16 == '') {
00396                                                                 $ascii .= '\ufffd';
00397                                                         } else {
00398                                                                 $utf16 = str_split($utf16, 2);
00399                                                                 $ascii .= sprintf('\u%04s\u%04s', bin2hex($utf16[0]), bin2hex($utf16[1]));
00400                                                         }
00401                                                         break;
00402                                         }
00403                                 }
00404 
00405                                 return '"'.$ascii.'"';
00406 
00407                         case 'array':
00408                            /*
00409                                 * As per JSON spec if any array key is not an integer
00410                                 * we must treat the the whole array as an object. We
00411                                 * also try to catch a sparsely populated associative
00412                                 * array with numeric keys here because some JS engines
00413                                 * will create an array with empty indexes up to
00414                                 * max_index which can cause memory issues and because
00415                                 * the keys, which may be relevant, will be remapped
00416                                 * otherwise.
00417                                 *
00418                                 * As per the ECMA and JSON specification an object may
00419                                 * have any string as a property. Unfortunately due to
00420                                 * a hole in the ECMA specification if the key is a
00421                                 * ECMA reserved word or starts with a digit the
00422                                 * parameter is only accessible using ECMAScript's
00423                                 * bracket notation.
00424                                 */
00425 
00426                                 // treat as a JSON object
00427                                 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
00428                                         $this->indent++;
00429                                         $properties = array_map(array($this, 'name_value'),
00430                                                                 array_keys($var),
00431                                                                 array_values($var));
00432                                         $this->indent--;
00433 
00434                                         foreach($properties as $property) {
00435                                                 if($this->isError($property)) {
00436                                                         return $property;
00437                                                 }
00438                                         }
00439 
00440                                         return '{' . $open . join($mid, $properties) . $close . '}';
00441                                 }
00442 
00443                                 // treat it like a regular array
00444                                 $this->indent++;
00445                                 $elements = array_map(array($this, 'encode2'), $var);
00446                                 $this->indent--;
00447 
00448                                 foreach($elements as $element) {
00449                                         if($this->isError($element)) {
00450                                                 return $element;
00451                                         }
00452                                 }
00453 
00454                                 return '[' . $open . join($mid, $elements) . $close . ']';
00455 
00456                         case 'object':
00457                                 $vars = get_object_vars($var);
00458 
00459                                 $this->indent++;
00460                                 $properties = array_map(array($this, 'name_value'),
00461                                                         array_keys($vars),
00462                                                         array_values($vars));
00463                                 $this->indent--;
00464 
00465                                 foreach($properties as $property) {
00466                                         if($this->isError($property)) {
00467                                                 return $property;
00468                                         }
00469                                 }
00470 
00471                                 return '{' . $open . join($mid, $properties) . $close . '}';
00472 
00473                         default:
00474                                 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
00475                                         ? 'null'
00476                                         : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
00477                 }
00478         }
00479 
00489         function name_value($name, $value)
00490         {
00491                 $encoded_value = $this->encode2($value);
00492 
00493                 if($this->isError($encoded_value)) {
00494                         return $encoded_value;
00495                 }
00496 
00497                 return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value;
00498         }
00499 
00508         function reduce_string($str)
00509         {
00510                 $str = preg_replace(array(
00511 
00512                                 // eliminate single line comments in '// ...' form
00513                                 '#^\s*//(.+)$#m',
00514 
00515                                 // eliminate multi-line comments in '/* ... */' form, at start of string
00516                                 '#^\s*/\*(.+)\*/#Us',
00517 
00518                                 // eliminate multi-line comments in '/* ... */' form, at end of string
00519                                 '#/\*(.+)\*/\s*$#Us'
00520 
00521                         ), '', $str);
00522 
00523                 // eliminate extraneous space
00524                 return trim($str);
00525         }
00526 
00539         function decode($str)
00540         {
00541                 $str = $this->reduce_string($str);
00542 
00543                 switch (strtolower($str)) {
00544                         case 'true':
00545                                 return true;
00546 
00547                         case 'false':
00548                                 return false;
00549 
00550                         case 'null':
00551                                 return null;
00552 
00553                         default:
00554                                 $m = array();
00555 
00556                                 if (is_numeric($str)) {
00557                                         // Lookie-loo, it's a number
00558 
00559                                         // This would work on its own, but I'm trying to be
00560                                         // good about returning integers where appropriate:
00561                                         // return (float)$str;
00562 
00563                                         // Return float or int, as appropriate
00564                                         return ((float)$str == (integer)$str)
00565                                                 ? (integer)$str
00566                                                 : (float)$str;
00567 
00568                                 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
00569                                         // STRINGS RETURNED IN UTF-8 FORMAT
00570                                         $delim = substr($str, 0, 1);
00571                                         $chrs = substr($str, 1, -1);
00572                                         $utf8 = '';
00573                                         $strlen_chrs = strlen($chrs);
00574 
00575                                         for ($c = 0; $c < $strlen_chrs; ++$c) {
00576 
00577                                                 $substr_chrs_c_2 = substr($chrs, $c, 2);
00578                                                 $ord_chrs_c = ord($chrs[$c]);
00579 
00580                                                 switch (true) {
00581                                                         case $substr_chrs_c_2 == '\b':
00582                                                                 $utf8 .= chr(0x08);
00583                                                                 ++$c;
00584                                                                 break;
00585                                                         case $substr_chrs_c_2 == '\t':
00586                                                                 $utf8 .= chr(0x09);
00587                                                                 ++$c;
00588                                                                 break;
00589                                                         case $substr_chrs_c_2 == '\n':
00590                                                                 $utf8 .= chr(0x0A);
00591                                                                 ++$c;
00592                                                                 break;
00593                                                         case $substr_chrs_c_2 == '\f':
00594                                                                 $utf8 .= chr(0x0C);
00595                                                                 ++$c;
00596                                                                 break;
00597                                                         case $substr_chrs_c_2 == '\r':
00598                                                                 $utf8 .= chr(0x0D);
00599                                                                 ++$c;
00600                                                                 break;
00601 
00602                                                         case $substr_chrs_c_2 == '\\"':
00603                                                         case $substr_chrs_c_2 == '\\\'':
00604                                                         case $substr_chrs_c_2 == '\\\\':
00605                                                         case $substr_chrs_c_2 == '\\/':
00606                                                                 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
00607                                                                    ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
00608                                                                         $utf8 .= $chrs[++$c];
00609                                                                 }
00610                                                                 break;
00611 
00612                                                         case preg_match('/\\\uD[89AB][0-9A-F]{2}\\\uD[C-F][0-9A-F]{2}/i', substr($chrs, $c, 12)):
00613                                                                 // escaped unicode surrogate pair
00614                                                                 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
00615                                                                            . chr(hexdec(substr($chrs, ($c + 4), 2)))
00616                                                                            . chr(hexdec(substr($chrs, ($c + 8), 2)))
00617                                                                            . chr(hexdec(substr($chrs, ($c + 10), 2)));
00618                                                                 $utf8 .= $this->utf162utf8($utf16);
00619                                                                 $c += 11;
00620                                                                 break;
00621 
00622                                                         case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
00623                                                                 // single, escaped unicode character
00624                                                                 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
00625                                                                            . chr(hexdec(substr($chrs, ($c + 4), 2)));
00626                                                                 $utf8 .= $this->utf162utf8($utf16);
00627                                                                 $c += 5;
00628                                                                 break;
00629 
00630                                                         case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
00631                                                                 $utf8 .= $chrs[$c];
00632                                                                 break;
00633 
00634                                                         case ($ord_chrs_c & 0xE0) == 0xC0:
00635                                                                 // characters U-00000080 - U-000007FF, mask 110XXXXX
00636                                                                 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00637                                                                 $utf8 .= substr($chrs, $c, 2);
00638                                                                 ++$c;
00639                                                                 break;
00640 
00641                                                         case ($ord_chrs_c & 0xF0) == 0xE0:
00642                                                                 // characters U-00000800 - U-0000FFFF, mask 1110XXXX
00643                                                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00644                                                                 $utf8 .= substr($chrs, $c, 3);
00645                                                                 $c += 2;
00646                                                                 break;
00647 
00648                                                         case ($ord_chrs_c & 0xF8) == 0xF0:
00649                                                                 // characters U-00010000 - U-001FFFFF, mask 11110XXX
00650                                                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00651                                                                 $utf8 .= substr($chrs, $c, 4);
00652                                                                 $c += 3;
00653                                                                 break;
00654 
00655                                                         case ($ord_chrs_c & 0xFC) == 0xF8:
00656                                                                 // characters U-00200000 - U-03FFFFFF, mask 111110XX
00657                                                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00658                                                                 $utf8 .= substr($chrs, $c, 5);
00659                                                                 $c += 4;
00660                                                                 break;
00661 
00662                                                         case ($ord_chrs_c & 0xFE) == 0xFC:
00663                                                                 // characters U-04000000 - U-7FFFFFFF, mask 1111110X
00664                                                                 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
00665                                                                 $utf8 .= substr($chrs, $c, 6);
00666                                                                 $c += 5;
00667                                                                 break;
00668 
00669                                                 }
00670 
00671                                         }
00672 
00673                                         return $utf8;
00674 
00675                                 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
00676                                         // array, or object notation
00677 
00678                                         if ($str[0] == '[') {
00679                                                 $stk = array(SERVICES_JSON_IN_ARR);
00680                                                 $arr = array();
00681                                         } else {
00682                                                 if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
00683                                                         $stk = array(SERVICES_JSON_IN_OBJ);
00684                                                         $obj = array();
00685                                                 } else {
00686                                                         $stk = array(SERVICES_JSON_IN_OBJ);
00687                                                         $obj = new stdClass();
00688                                                 }
00689                                         }
00690 
00691                                         array_push($stk, array( 'what'  => SERVICES_JSON_SLICE,
00692                                                                 'where' => 0,
00693                                                                 'delim' => false));
00694 
00695                                         $chrs = substr($str, 1, -1);
00696                                         $chrs = $this->reduce_string($chrs);
00697 
00698                                         if ($chrs == '') {
00699                                                 if (reset($stk) == SERVICES_JSON_IN_ARR) {
00700                                                         return $arr;
00701 
00702                                                 } else {
00703                                                         return $obj;
00704 
00705                                                 }
00706                                         }
00707 
00708                                         //print("\nparsing {$chrs}\n");
00709 
00710                                         $strlen_chrs = strlen($chrs);
00711 
00712                                         for ($c = 0; $c <= $strlen_chrs; ++$c) {
00713 
00714                                                 $top = end($stk);
00715                                                 $substr_chrs_c_2 = substr($chrs, $c, 2);
00716 
00717                                                 if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
00718                                                         // found a comma that is not inside a string, array, etc.,
00719                                                         // OR we've reached the end of the character list
00720                                                         $slice = substr($chrs, $top['where'], ($c - $top['where']));
00721                                                         array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
00722                                                         //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
00723 
00724                                                         if (reset($stk) == SERVICES_JSON_IN_ARR) {
00725                                                                 // we are in an array, so just push an element onto the stack
00726                                                                 array_push($arr, $this->decode($slice));
00727 
00728                                                         } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
00729                                                                 // we are in an object, so figure
00730                                                                 // out the property name and set an
00731                                                                 // element in an associative array,
00732                                                                 // for now
00733                                                                 $parts = array();
00734 
00735                                                                 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
00736                                                                         // "name":value pair
00737                                                                         $key = $this->decode($parts[1]);
00738                                                                         $val = $this->decode($parts[2]);
00739 
00740                                                                         if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
00741                                                                                 $obj[$key] = $val;
00742                                                                         } else {
00743                                                                                 $obj->$key = $val;
00744                                                                         }
00745                                                                 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
00746                                                                         // name:value pair, where name is unquoted
00747                                                                         $key = $parts[1];
00748                                                                         $val = $this->decode($parts[2]);
00749 
00750                                                                         if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
00751                                                                                 $obj[$key] = $val;
00752                                                                         } else {
00753                                                                                 $obj->$key = $val;
00754                                                                         }
00755                                                                 }
00756 
00757                                                         }
00758 
00759                                                 } elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
00760                                                         // found a quote, and we are not inside a string
00761                                                         array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
00762                                                         //print("Found start of string at {$c}\n");
00763 
00764                                                 } elseif (($chrs[$c] == $top['delim']) &&
00765                                                                  ($top['what'] == SERVICES_JSON_IN_STR) &&
00766                                                                  (($chrs[$c - 1] != '\\') ||
00767                                                                  ($chrs[$c - 1] == '\\' && $chrs[$c - 2] == '\\'))) {
00768                                                         // found a quote, we're in a string, and it's not escaped
00769                                                         array_pop($stk);
00770                                                         //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
00771 
00772                                                 } elseif (($chrs[$c] == '[') &&
00773                                                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
00774                                                         // found a left-bracket, and we are in an array, object, or slice
00775                                                         array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
00776                                                         //print("Found start of array at {$c}\n");
00777 
00778                                                 } elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
00779                                                         // found a right-bracket, and we're in an array
00780                                                         array_pop($stk);
00781                                                         //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
00782 
00783                                                 } elseif (($chrs[$c] == '{') &&
00784                                                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
00785                                                         // found a left-brace, and we are in an array, object, or slice
00786                                                         array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
00787                                                         //print("Found start of object at {$c}\n");
00788 
00789                                                 } elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
00790                                                         // found a right-brace, and we're in an object
00791                                                         array_pop($stk);
00792                                                         //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
00793 
00794                                                 } elseif (($substr_chrs_c_2 == '/*') &&
00795                                                                  in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
00796                                                         // found a comment start, and we are in an array, object, or slice
00797                                                         array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
00798                                                         $c++;
00799                                                         //print("Found start of comment at {$c}\n");
00800 
00801                                                 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
00802                                                         // found a comment end, and we're in one now
00803                                                         array_pop($stk);
00804                                                         $c++;
00805 
00806                                                         for ($i = $top['where']; $i <= $c; ++$i)
00807                                                                 $chrs = substr_replace($chrs, ' ', $i, 1);
00808 
00809                                                         //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
00810 
00811                                                 }
00812 
00813                                         }
00814 
00815                                         if (reset($stk) == SERVICES_JSON_IN_ARR) {
00816                                                 return $arr;
00817 
00818                                         } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
00819                                                 return $obj;
00820 
00821                                         }
00822 
00823                                 }
00824                 }
00825         }
00826 
00830         function isError($data, $code = null)
00831         {
00832                 if ( self::pearInstalled() ) {
00833                         //avoid some strict warnings on PEAR isError check (looks like http://pear.php.net/bugs/bug.php?id=9950 has been around for some time)
00834                         return @PEAR::isError($data, $code);
00835                 } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
00836                                 is_subclass_of($data, 'services_json_error'))) {
00837                         return true;
00838                 }
00839 
00840                 return false;
00841         }
00842 }
00843 
00844 
00845 // Hide the PEAR_Error variant from Doxygen
00847 if (class_exists('PEAR_Error')) {
00848 
00852         class Services_JSON_Error extends PEAR_Error
00853         {
00854                 function Services_JSON_Error($message = 'unknown error', $code = null,
00855                                                 $mode = null, $options = null, $userinfo = null)
00856                 {
00857                         parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
00858                 }
00859         }
00860 
00861 } else {
00863 
00868         class Services_JSON_Error
00869         {
00870                 function Services_JSON_Error($message = 'unknown error', $code = null,
00871                                                 $mode = null, $options = null, $userinfo = null)
00872                 {
00873                         $this->message = $message;
00874                 }
00875 
00876                 function __toString()
00877                 {
00878                         return $this->message;
00879                 }
00880         }
00881 }