Changeset 11877 for trunk/wp-includes/class-json.php
- Timestamp:
- 08/25/2009 08:11:07 AM (17 years ago)
- File:
-
- 1 edited
-
trunk/wp-includes/class-json.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/wp-includes/class-json.php
r11875 r11877 46 46 * 47 47 * @category 48 * @package Services_JSON49 * @author Michal Migurski <mike-json@teczno.com>50 * @author Matt Knapp <mdknapp[at]gmail[dot]com>51 * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>52 * @copyright 2005 Michal Migurski53 * @version CVS: $Id: JSON.php,v 1.3 2009/05/22 23:51:00 alan_k Exp $54 * @license http://www.opensource.org/licenses/bsd-license.php55 * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=19848 * @package Services_JSON 49 * @author Michal Migurski <mike-json@teczno.com> 50 * @author Matt Knapp <mdknapp[at]gmail[dot]com> 51 * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> 52 * @copyright 2005 Michal Migurski 53 * @version CVS: $Id: JSON.php,v 1.3 2009/05/22 23:51:00 alan_k Exp $ 54 * @license http://www.opensource.org/licenses/bsd-license.php 55 * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 56 56 */ 57 57 … … 59 59 * Marker constant for Services_JSON::decode(), used to flag stack state 60 60 */ 61 define('SERVICES_JSON_SLICE', 1);61 define('SERVICES_JSON_SLICE', 1); 62 62 63 63 /** … … 114 114 class Services_JSON 115 115 { 116 /**117 * constructs a new JSON instance118 *119 * @param int $useobject behavior flags; combine with boolean-OR120 *121 *possible values:122 *- SERVICES_JSON_LOOSE_TYPE: loose typing.123 *"{...}" syntax creates associative arrays124 *instead of objects in decode().125 *- SERVICES_JSON_SUPPRESS_ERRORS: error suppression.126 *Values which can't be encoded (e.g. resources)127 *appear as NULL instead of throwing errors.128 *By default, a deeply-nested resource will129 *bubble up with an error, so all return values130 *from encode() should be checked with isError()131 */132 function Services_JSON($use = 0)133 {134 $this->use = $use;135 }136 137 /**138 * convert a string from one UTF-16 char to one UTF-8 char139 *140 * Normally should be handled by mb_convert_encoding, but141 * provides a slower PHP-only method for installations142 * that lack the multibye string extension.143 *144 * @paramstring $utf16 UTF-16 character145 * @returnstring UTF-8 character146 * @accessprivate147 */148 function utf162utf8($utf16)149 {150 // oh please oh please oh please oh please oh please151 if(function_exists('mb_convert_encoding')) {152 return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');153 }154 155 $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});156 157 switch(true) {158 case ((0x7F & $bytes) == $bytes):159 // this case should never be reached, because we are in ASCII range160 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8161 return chr(0x7F & $bytes);162 163 case (0x07FF & $bytes) == $bytes:164 // return a 2-byte UTF-8 character165 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8166 return chr(0xC0 | (($bytes >> 6) & 0x1F))167 . chr(0x80 | ($bytes & 0x3F));168 169 case (0xFFFF & $bytes) == $bytes:170 // return a 3-byte UTF-8 character171 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8172 return chr(0xE0 | (($bytes >> 12) & 0x0F))173 . chr(0x80 | (($bytes >> 6) & 0x3F))174 . chr(0x80 | ($bytes & 0x3F));175 }176 177 // ignoring UTF-32 for now, sorry178 return '';179 }180 181 /**182 * convert a string from one UTF-8 char to one UTF-16 char183 *184 * Normally should be handled by mb_convert_encoding, but185 * provides a slower PHP-only method for installations186 * that lack the multibye string extension.187 *188 * @param string $utf8UTF-8 character189 * @returnstring UTF-16 character190 * @accessprivate191 */192 function utf82utf16($utf8)193 {194 // oh please oh please oh please oh please oh please195 if(function_exists('mb_convert_encoding')) {196 return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');197 }198 199 switch(strlen($utf8)) {200 case 1:201 // this case should never be reached, because we are in ASCII range202 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8203 return $utf8;204 205 case 2:206 // return a UTF-16 character from a 2-byte UTF-8 char207 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8208 return chr(0x07 & (ord($utf8{0}) >> 2))209 . chr((0xC0 & (ord($utf8{0}) << 6))210 | (0x3F & ord($utf8{1})));211 212 case 3:213 // return a UTF-16 character from a 3-byte UTF-8 char214 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8215 return chr((0xF0 & (ord($utf8{0}) << 4))216 | (0x0F & (ord($utf8{1}) >> 2)))217 . chr((0xC0 & (ord($utf8{1}) << 6))218 | (0x7F & ord($utf8{2})));219 }220 221 // ignoring UTF-32 for now, sorry222 return '';223 }224 225 /**226 * encodes an arbitrary variable into JSON format (and sends JSON Header)227 *228 * @param mixed $varany number, boolean, string, array, or object to be encoded.229 *see argument 1 to Services_JSON() above for array-parsing behavior.230 *if var is a strng, note that encode() always expects it231 *to be in ASCII or UTF-8 format!232 *233 * @return mixedJSON string representation of input var or an error if a problem occurs234 * @accesspublic235 */236 function encode($var)237 {238 header('Content-type: application/x-javascript');239 return $this->_encode($var);240 }241 /**242 * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)243 *244 * @param mixed $varany number, boolean, string, array, or object to be encoded.245 *see argument 1 to Services_JSON() above for array-parsing behavior.246 *if var is a strng, note that encode() always expects it247 *to be in ASCII or UTF-8 format!248 *249 * @return mixedJSON string representation of input var or an error if a problem occurs250 * @accesspublic251 */252 function encodeUnsafe($var)253 {254 return $this->_encode($var);255 }256 /**257 * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 258 *259 * @param mixed $varany number, boolean, string, array, or object to be encoded.260 *see argument 1 to Services_JSON() above for array-parsing behavior.261 *if var is a strng, note that encode() always expects it262 *to be in ASCII or UTF-8 format!263 *264 * @return mixedJSON string representation of input var or an error if a problem occurs265 * @accesspublic266 */267 function _encode($var) 268 {269 270 switch (gettype($var)) {271 case 'boolean':272 return $var ? 'true' : 'false';273 274 case 'NULL':275 return 'null';276 277 case 'integer':278 return (int) $var;279 280 case 'double':281 case 'float':282 return (float) $var;283 284 case 'string':285 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT286 $ascii = '';287 $strlen_var = strlen($var);288 289 /*290 * Iterate over every character in the string,291 * escaping with a slash or encoding to UTF-8 where necessary292 */293 for ($c = 0; $c < $strlen_var; ++$c) {294 295 $ord_var_c = ord($var{$c});296 297 switch (true) {298 case $ord_var_c == 0x08:299 $ascii .= '\b';300 break;301 case $ord_var_c == 0x09:302 $ascii .= '\t';303 break;304 case $ord_var_c == 0x0A:305 $ascii .= '\n';306 break;307 case $ord_var_c == 0x0C:308 $ascii .= '\f';309 break;310 case $ord_var_c == 0x0D:311 $ascii .= '\r';312 break;313 314 case $ord_var_c == 0x22:315 case $ord_var_c == 0x2F:316 case $ord_var_c == 0x5C:317 // double quote, slash, slosh318 $ascii .= '\\'.$var{$c};319 break;320 321 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):322 // characters U-00000000 - U-0000007F (same as ASCII)323 $ascii .= $var{$c};324 break;325 326 case (($ord_var_c & 0xE0) == 0xC0):327 // characters U-00000080 - U-000007FF, mask 110XXXXX328 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8329 if ($c+1 >= $strlen_var) {330 $c += 1;331 $ascii .= '?';332 break;333 }334 335 $char = pack('C*', $ord_var_c, ord($var{$c + 1}));336 $c += 1;337 $utf16 = $this->utf82utf16($char);338 $ascii .= sprintf('\u%04s', bin2hex($utf16));339 break;340 341 case (($ord_var_c & 0xF0) == 0xE0):342 if ($c+2 >= $strlen_var) {343 $c += 2;344 $ascii .= '?';345 break;346 }347 // characters U-00000800 - U-0000FFFF, mask 1110XXXX348 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8349 $char = pack('C*', $ord_var_c,350 @ord($var{$c + 1}),351 @ord($var{$c + 2}));352 $c += 2;353 $utf16 = $this->utf82utf16($char);354 $ascii .= sprintf('\u%04s', bin2hex($utf16));355 break;356 357 case (($ord_var_c & 0xF8) == 0xF0):358 if ($c+3 >= $strlen_var) {359 $c += 3;360 $ascii .= '?';361 break;362 }363 // characters U-00010000 - U-001FFFFF, mask 11110XXX364 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8365 $char = pack('C*', $ord_var_c,366 ord($var{$c + 1}),367 ord($var{$c + 2}),368 ord($var{$c + 3}));369 $c += 3;370 $utf16 = $this->utf82utf16($char);371 $ascii .= sprintf('\u%04s', bin2hex($utf16));372 break;373 374 case (($ord_var_c & 0xFC) == 0xF8):375 // characters U-00200000 - U-03FFFFFF, mask 111110XX376 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8377 if ($c+4 >= $strlen_var) {378 $c += 4;379 $ascii .= '?';380 break;381 }382 $char = pack('C*', $ord_var_c,383 ord($var{$c + 1}),384 ord($var{$c + 2}),385 ord($var{$c + 3}),386 ord($var{$c + 4}));387 $c += 4;388 $utf16 = $this->utf82utf16($char);389 $ascii .= sprintf('\u%04s', bin2hex($utf16));390 break;391 392 case (($ord_var_c & 0xFE) == 0xFC):393 if ($c+5 >= $strlen_var) {394 $c += 5;395 $ascii .= '?';396 break;397 }398 // characters U-04000000 - U-7FFFFFFF, mask 1111110X399 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8400 $char = pack('C*', $ord_var_c,401 ord($var{$c + 1}),402 ord($var{$c + 2}),403 ord($var{$c + 3}),404 ord($var{$c + 4}),405 ord($var{$c + 5}));406 $c += 5;407 $utf16 = $this->utf82utf16($char);408 $ascii .= sprintf('\u%04s', bin2hex($utf16));409 break;410 }411 }412 return '"'.$ascii.'"';413 414 case 'array':415 /*416 * As per JSON spec if any array key is not an integer417 * we must treat the the whole array as an object. We418 * also try to catch a sparsely populated associative419 * array with numeric keys here because some JS engines420 * will create an array with empty indexes up to421 * max_index which can cause memory issues and because422 * the keys, which may be relevant, will be remapped423 * otherwise.424 *425 * As per the ECMA and JSON specification an object may426 * have any string as a property. Unfortunately due to427 * a hole in the ECMA specification if the key is a428 * ECMA reserved word or starts with a digit the429 * parameter is only accessible using ECMAScript's430 * bracket notation.431 */432 433 // treat as a JSON object434 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {435 $properties = array_map(array($this, 'name_value'),436 array_keys($var),437 array_values($var));438 439 foreach($properties as $property) {440 if(Services_JSON::isError($property)) {441 return $property;442 }443 }444 445 return '{' . join(',', $properties) . '}';446 }447 448 // treat it like a regular array449 $elements = array_map(array($this, '_encode'), $var);450 451 foreach($elements as $element) {452 if(Services_JSON::isError($element)) {453 return $element;454 }455 }456 457 return '[' . join(',', $elements) . ']';458 459 case 'object':460 $vars = get_object_vars($var);461 462 $properties = array_map(array($this, 'name_value'),463 array_keys($vars),464 array_values($vars));465 466 foreach($properties as $property) {467 if(Services_JSON::isError($property)) {468 return $property;469 }470 }471 472 return '{' . join(',', $properties) . '}';473 474 default:475 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)476 ? 'null'477 : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");478 }479 }480 481 /**482 * array-walking function for use in generating JSON-formatted name-value pairs483 *484 * @param string $namename of key to use485 * @param mixed$value reference to an array element to be encoded486 *487 * @returnstring JSON-formatted name-value pair, like '"name":value'488 * @accessprivate489 */490 function name_value($name, $value)491 {492 $encoded_value = $this->_encode($value);493 494 if(Services_JSON::isError($encoded_value)) {495 return $encoded_value;496 }497 498 return $this->_encode(strval($name)) . ':' . $encoded_value;499 }500 501 /**502 * reduce a string by removing leading and trailing comments and whitespace503 *504 * @param $str stringstring value to strip of comments and whitespace505 *506 * @returnstring string value stripped of comments and whitespace507 * @accessprivate508 */509 function reduce_string($str)510 {511 $str = preg_replace(array(512 513 // eliminate single line comments in '// ...' form514 '#^\s*//(.+)$#m',515 516 // eliminate multi-line comments in '/* ... */' form, at start of string517 '#^\s*/\*(.+)\*/#Us',518 519 // eliminate multi-line comments in '/* ... */' form, at end of string520 '#/\*(.+)\*/\s*$#Us'521 522 ), '', $str);523 524 // eliminate extraneous space525 return trim($str);526 }527 528 /**529 * decodes a JSON string into appropriate variable530 *531 * @param string $strJSON-formatted string532 *533 * @return mixednumber, boolean, string, array, or object534 *corresponding to given JSON input string.535 *See argument 1 to Services_JSON() above for object-output behavior.536 *Note that decode() always returns strings537 *in ASCII or UTF-8 format!538 * @accesspublic539 */540 function decode($str)541 {542 $str = $this->reduce_string($str);543 544 switch (strtolower($str)) {545 case 'true':546 return true;547 548 case 'false':549 return false;550 551 case 'null':552 return null;553 554 default:555 $m = array();556 557 if (is_numeric($str)) {558 // Lookie-loo, it's a number559 560 // This would work on its own, but I'm trying to be561 // good about returning integers where appropriate:562 // return (float)$str;563 564 // Return float or int, as appropriate565 return ((float)$str == (integer)$str)566 ? (integer)$str567 : (float)$str;568 569 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {570 // STRINGS RETURNED IN UTF-8 FORMAT571 $delim = substr($str, 0, 1);572 $chrs = substr($str, 1, -1);573 $utf8 = '';574 $strlen_chrs = strlen($chrs);575 576 for ($c = 0; $c < $strlen_chrs; ++$c) {577 578 $substr_chrs_c_2 = substr($chrs, $c, 2);579 $ord_chrs_c = ord($chrs{$c});580 581 switch (true) {582 case $substr_chrs_c_2 == '\b':583 $utf8 .= chr(0x08);584 ++$c;585 break;586 case $substr_chrs_c_2 == '\t':587 $utf8 .= chr(0x09);588 ++$c;589 break;590 case $substr_chrs_c_2 == '\n':591 $utf8 .= chr(0x0A);592 ++$c;593 break;594 case $substr_chrs_c_2 == '\f':595 $utf8 .= chr(0x0C);596 ++$c;597 break;598 case $substr_chrs_c_2 == '\r':599 $utf8 .= chr(0x0D);600 ++$c;601 break;602 603 case $substr_chrs_c_2 == '\\"':604 case $substr_chrs_c_2 == '\\\'':605 case $substr_chrs_c_2 == '\\\\':606 case $substr_chrs_c_2 == '\\/':607 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||608 ($delim == "'" && $substr_chrs_c_2 != '\\"')) {609 $utf8 .= $chrs{++$c};610 }611 break;612 613 case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):614 // single, escaped unicode character615 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))616 . chr(hexdec(substr($chrs, ($c + 4), 2)));617 $utf8 .= $this->utf162utf8($utf16);618 $c += 5;619 break;620 621 case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):622 $utf8 .= $chrs{$c};623 break;624 625 case ($ord_chrs_c & 0xE0) == 0xC0:626 // characters U-00000080 - U-000007FF, mask 110XXXXX627 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8628 $utf8 .= substr($chrs, $c, 2);629 ++$c;630 break;631 632 case ($ord_chrs_c & 0xF0) == 0xE0:633 // characters U-00000800 - U-0000FFFF, mask 1110XXXX634 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8635 $utf8 .= substr($chrs, $c, 3);636 $c += 2;637 break;638 639 case ($ord_chrs_c & 0xF8) == 0xF0:640 // characters U-00010000 - U-001FFFFF, mask 11110XXX641 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8642 $utf8 .= substr($chrs, $c, 4);643 $c += 3;644 break;645 646 case ($ord_chrs_c & 0xFC) == 0xF8:647 // characters U-00200000 - U-03FFFFFF, mask 111110XX648 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8649 $utf8 .= substr($chrs, $c, 5);650 $c += 4;651 break;652 653 case ($ord_chrs_c & 0xFE) == 0xFC:654 // characters U-04000000 - U-7FFFFFFF, mask 1111110X655 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8656 $utf8 .= substr($chrs, $c, 6);657 $c += 5;658 break;659 660 }661 662 }663 664 return $utf8;665 666 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {667 // array, or object notation668 669 if ($str{0} == '[') {670 $stk = array(SERVICES_JSON_IN_ARR);671 $arr = array();672 } else {673 if ($this->use & SERVICES_JSON_LOOSE_TYPE) {674 $stk = array(SERVICES_JSON_IN_OBJ);675 $obj = array();676 } else {677 $stk = array(SERVICES_JSON_IN_OBJ);678 $obj = new stdClass();679 }680 }681 682 array_push($stk, array('what' => SERVICES_JSON_SLICE,683 'where' => 0,684 'delim' => false));685 686 $chrs = substr($str, 1, -1);687 $chrs = $this->reduce_string($chrs);688 689 if ($chrs == '') {690 if (reset($stk) == SERVICES_JSON_IN_ARR) {691 return $arr;692 693 } else {694 return $obj;695 696 }697 }698 699 //print("\nparsing {$chrs}\n");700 701 $strlen_chrs = strlen($chrs);702 703 for ($c = 0; $c <= $strlen_chrs; ++$c) {704 705 $top = end($stk);706 $substr_chrs_c_2 = substr($chrs, $c, 2);707 708 if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {709 // found a comma that is not inside a string, array, etc.,710 // OR we've reached the end of the character list711 $slice = substr($chrs, $top['where'], ($c - $top['where']));712 array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));713 //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");714 715 if (reset($stk) == SERVICES_JSON_IN_ARR) {716 // we are in an array, so just push an element onto the stack717 array_push($arr, $this->decode($slice));718 719 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {720 // we are in an object, so figure721 // out the property name and set an722 // element in an associative array,723 // for now724 $parts = array();725 726 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {727 // "name":value pair728 $key = $this->decode($parts[1]);729 $val = $this->decode($parts[2]);730 731 if ($this->use & SERVICES_JSON_LOOSE_TYPE) {732 $obj[$key] = $val;733 } else {734 $obj->$key = $val;735 }736 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {737 // name:value pair, where name is unquoted738 $key = $parts[1];739 $val = $this->decode($parts[2]);740 741 if ($this->use & SERVICES_JSON_LOOSE_TYPE) {742 $obj[$key] = $val;743 } else {744 $obj->$key = $val;745 }746 }747 748 }749 750 } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {751 // found a quote, and we are not inside a string752 array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));753 //print("Found start of string at {$c}\n");754 755 } elseif (($chrs{$c} == $top['delim']) &&756 ($top['what'] == SERVICES_JSON_IN_STR) &&757 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {758 // found a quote, we're in a string, and it's not escaped759 // we know that it's not escaped becase there is _not_ an760 // odd number of backslashes at the end of the string so far761 array_pop($stk);762 //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");763 764 } elseif (($chrs{$c} == '[') &&765 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {766 // found a left-bracket, and we are in an array, object, or slice767 array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));768 //print("Found start of array at {$c}\n");769 770 } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {771 // found a right-bracket, and we're in an array772 array_pop($stk);773 //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");774 775 } elseif (($chrs{$c} == '{') &&776 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {777 // found a left-brace, and we are in an array, object, or slice778 array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));779 //print("Found start of object at {$c}\n");780 781 } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {782 // found a right-brace, and we're in an object783 array_pop($stk);784 //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");785 786 } elseif (($substr_chrs_c_2 == '/*') &&787 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {788 // found a comment start, and we are in an array, object, or slice789 array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));790 $c++;791 //print("Found start of comment at {$c}\n");792 793 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {794 // found a comment end, and we're in one now795 array_pop($stk);796 $c++;797 798 for ($i = $top['where']; $i <= $c; ++$i)799 $chrs = substr_replace($chrs, ' ', $i, 1);800 801 //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");802 803 }804 805 }806 807 if (reset($stk) == SERVICES_JSON_IN_ARR) {808 return $arr;809 810 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {811 return $obj;812 813 }814 815 }816 }817 }818 819 /**820 * @todo Ultimately, this should just call PEAR::isError()821 */822 function isError($data, $code = null)823 {824 if (class_exists('pear')) {825 return PEAR::isError($data, $code);826 } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||827 is_subclass_of($data, 'services_json_error'))) {828 return true;829 }830 831 return false;832 }116 /** 117 * constructs a new JSON instance 118 * 119 * @param int $use object behavior flags; combine with boolean-OR 120 * 121 * possible values: 122 * - SERVICES_JSON_LOOSE_TYPE: loose typing. 123 * "{...}" syntax creates associative arrays 124 * instead of objects in decode(). 125 * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. 126 * Values which can't be encoded (e.g. resources) 127 * appear as NULL instead of throwing errors. 128 * By default, a deeply-nested resource will 129 * bubble up with an error, so all return values 130 * from encode() should be checked with isError() 131 */ 132 function Services_JSON($use = 0) 133 { 134 $this->use = $use; 135 } 136 137 /** 138 * convert a string from one UTF-16 char to one UTF-8 char 139 * 140 * Normally should be handled by mb_convert_encoding, but 141 * provides a slower PHP-only method for installations 142 * that lack the multibye string extension. 143 * 144 * @param string $utf16 UTF-16 character 145 * @return string UTF-8 character 146 * @access private 147 */ 148 function utf162utf8($utf16) 149 { 150 // oh please oh please oh please oh please oh please 151 if(function_exists('mb_convert_encoding')) { 152 return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); 153 } 154 155 $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); 156 157 switch(true) { 158 case ((0x7F & $bytes) == $bytes): 159 // this case should never be reached, because we are in ASCII range 160 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 161 return chr(0x7F & $bytes); 162 163 case (0x07FF & $bytes) == $bytes: 164 // return a 2-byte UTF-8 character 165 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 166 return chr(0xC0 | (($bytes >> 6) & 0x1F)) 167 . chr(0x80 | ($bytes & 0x3F)); 168 169 case (0xFFFF & $bytes) == $bytes: 170 // return a 3-byte UTF-8 character 171 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 172 return chr(0xE0 | (($bytes >> 12) & 0x0F)) 173 . chr(0x80 | (($bytes >> 6) & 0x3F)) 174 . chr(0x80 | ($bytes & 0x3F)); 175 } 176 177 // ignoring UTF-32 for now, sorry 178 return ''; 179 } 180 181 /** 182 * convert a string from one UTF-8 char to one UTF-16 char 183 * 184 * Normally should be handled by mb_convert_encoding, but 185 * provides a slower PHP-only method for installations 186 * that lack the multibye string extension. 187 * 188 * @param string $utf8 UTF-8 character 189 * @return string UTF-16 character 190 * @access private 191 */ 192 function utf82utf16($utf8) 193 { 194 // oh please oh please oh please oh please oh please 195 if(function_exists('mb_convert_encoding')) { 196 return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 197 } 198 199 switch(strlen($utf8)) { 200 case 1: 201 // this case should never be reached, because we are in ASCII range 202 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 203 return $utf8; 204 205 case 2: 206 // return a UTF-16 character from a 2-byte UTF-8 char 207 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 208 return chr(0x07 & (ord($utf8{0}) >> 2)) 209 . chr((0xC0 & (ord($utf8{0}) << 6)) 210 | (0x3F & ord($utf8{1}))); 211 212 case 3: 213 // return a UTF-16 character from a 3-byte UTF-8 char 214 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 215 return chr((0xF0 & (ord($utf8{0}) << 4)) 216 | (0x0F & (ord($utf8{1}) >> 2))) 217 . chr((0xC0 & (ord($utf8{1}) << 6)) 218 | (0x7F & ord($utf8{2}))); 219 } 220 221 // ignoring UTF-32 for now, sorry 222 return ''; 223 } 224 225 /** 226 * encodes an arbitrary variable into JSON format (and sends JSON Header) 227 * 228 * @param mixed $var any number, boolean, string, array, or object to be encoded. 229 * see argument 1 to Services_JSON() above for array-parsing behavior. 230 * if var is a strng, note that encode() always expects it 231 * to be in ASCII or UTF-8 format! 232 * 233 * @return mixed JSON string representation of input var or an error if a problem occurs 234 * @access public 235 */ 236 function encode($var) 237 { 238 header('Content-type: application/x-javascript'); 239 return $this->_encode($var); 240 } 241 /** 242 * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!) 243 * 244 * @param mixed $var any number, boolean, string, array, or object to be encoded. 245 * see argument 1 to Services_JSON() above for array-parsing behavior. 246 * if var is a strng, note that encode() always expects it 247 * to be in ASCII or UTF-8 format! 248 * 249 * @return mixed JSON string representation of input var or an error if a problem occurs 250 * @access public 251 */ 252 function encodeUnsafe($var) 253 { 254 return $this->_encode($var); 255 } 256 /** 257 * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 258 * 259 * @param mixed $var any number, boolean, string, array, or object to be encoded. 260 * see argument 1 to Services_JSON() above for array-parsing behavior. 261 * if var is a strng, note that encode() always expects it 262 * to be in ASCII or UTF-8 format! 263 * 264 * @return mixed JSON string representation of input var or an error if a problem occurs 265 * @access public 266 */ 267 function _encode($var) 268 { 269 270 switch (gettype($var)) { 271 case 'boolean': 272 return $var ? 'true' : 'false'; 273 274 case 'NULL': 275 return 'null'; 276 277 case 'integer': 278 return (int) $var; 279 280 case 'double': 281 case 'float': 282 return (float) $var; 283 284 case 'string': 285 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 286 $ascii = ''; 287 $strlen_var = strlen($var); 288 289 /* 290 * Iterate over every character in the string, 291 * escaping with a slash or encoding to UTF-8 where necessary 292 */ 293 for ($c = 0; $c < $strlen_var; ++$c) { 294 295 $ord_var_c = ord($var{$c}); 296 297 switch (true) { 298 case $ord_var_c == 0x08: 299 $ascii .= '\b'; 300 break; 301 case $ord_var_c == 0x09: 302 $ascii .= '\t'; 303 break; 304 case $ord_var_c == 0x0A: 305 $ascii .= '\n'; 306 break; 307 case $ord_var_c == 0x0C: 308 $ascii .= '\f'; 309 break; 310 case $ord_var_c == 0x0D: 311 $ascii .= '\r'; 312 break; 313 314 case $ord_var_c == 0x22: 315 case $ord_var_c == 0x2F: 316 case $ord_var_c == 0x5C: 317 // double quote, slash, slosh 318 $ascii .= '\\'.$var{$c}; 319 break; 320 321 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 322 // characters U-00000000 - U-0000007F (same as ASCII) 323 $ascii .= $var{$c}; 324 break; 325 326 case (($ord_var_c & 0xE0) == 0xC0): 327 // characters U-00000080 - U-000007FF, mask 110XXXXX 328 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 329 if ($c+1 >= $strlen_var) { 330 $c += 1; 331 $ascii .= '?'; 332 break; 333 } 334 335 $char = pack('C*', $ord_var_c, ord($var{$c + 1})); 336 $c += 1; 337 $utf16 = $this->utf82utf16($char); 338 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 339 break; 340 341 case (($ord_var_c & 0xF0) == 0xE0): 342 if ($c+2 >= $strlen_var) { 343 $c += 2; 344 $ascii .= '?'; 345 break; 346 } 347 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 348 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 349 $char = pack('C*', $ord_var_c, 350 @ord($var{$c + 1}), 351 @ord($var{$c + 2})); 352 $c += 2; 353 $utf16 = $this->utf82utf16($char); 354 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 355 break; 356 357 case (($ord_var_c & 0xF8) == 0xF0): 358 if ($c+3 >= $strlen_var) { 359 $c += 3; 360 $ascii .= '?'; 361 break; 362 } 363 // characters U-00010000 - U-001FFFFF, mask 11110XXX 364 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 365 $char = pack('C*', $ord_var_c, 366 ord($var{$c + 1}), 367 ord($var{$c + 2}), 368 ord($var{$c + 3})); 369 $c += 3; 370 $utf16 = $this->utf82utf16($char); 371 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 372 break; 373 374 case (($ord_var_c & 0xFC) == 0xF8): 375 // characters U-00200000 - U-03FFFFFF, mask 111110XX 376 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 377 if ($c+4 >= $strlen_var) { 378 $c += 4; 379 $ascii .= '?'; 380 break; 381 } 382 $char = pack('C*', $ord_var_c, 383 ord($var{$c + 1}), 384 ord($var{$c + 2}), 385 ord($var{$c + 3}), 386 ord($var{$c + 4})); 387 $c += 4; 388 $utf16 = $this->utf82utf16($char); 389 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 390 break; 391 392 case (($ord_var_c & 0xFE) == 0xFC): 393 if ($c+5 >= $strlen_var) { 394 $c += 5; 395 $ascii .= '?'; 396 break; 397 } 398 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 399 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 400 $char = pack('C*', $ord_var_c, 401 ord($var{$c + 1}), 402 ord($var{$c + 2}), 403 ord($var{$c + 3}), 404 ord($var{$c + 4}), 405 ord($var{$c + 5})); 406 $c += 5; 407 $utf16 = $this->utf82utf16($char); 408 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 409 break; 410 } 411 } 412 return '"'.$ascii.'"'; 413 414 case 'array': 415 /* 416 * As per JSON spec if any array key is not an integer 417 * we must treat the the whole array as an object. We 418 * also try to catch a sparsely populated associative 419 * array with numeric keys here because some JS engines 420 * will create an array with empty indexes up to 421 * max_index which can cause memory issues and because 422 * the keys, which may be relevant, will be remapped 423 * otherwise. 424 * 425 * As per the ECMA and JSON specification an object may 426 * have any string as a property. Unfortunately due to 427 * a hole in the ECMA specification if the key is a 428 * ECMA reserved word or starts with a digit the 429 * parameter is only accessible using ECMAScript's 430 * bracket notation. 431 */ 432 433 // treat as a JSON object 434 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 435 $properties = array_map(array($this, 'name_value'), 436 array_keys($var), 437 array_values($var)); 438 439 foreach($properties as $property) { 440 if(Services_JSON::isError($property)) { 441 return $property; 442 } 443 } 444 445 return '{' . join(',', $properties) . '}'; 446 } 447 448 // treat it like a regular array 449 $elements = array_map(array($this, '_encode'), $var); 450 451 foreach($elements as $element) { 452 if(Services_JSON::isError($element)) { 453 return $element; 454 } 455 } 456 457 return '[' . join(',', $elements) . ']'; 458 459 case 'object': 460 $vars = get_object_vars($var); 461 462 $properties = array_map(array($this, 'name_value'), 463 array_keys($vars), 464 array_values($vars)); 465 466 foreach($properties as $property) { 467 if(Services_JSON::isError($property)) { 468 return $property; 469 } 470 } 471 472 return '{' . join(',', $properties) . '}'; 473 474 default: 475 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) 476 ? 'null' 477 : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); 478 } 479 } 480 481 /** 482 * array-walking function for use in generating JSON-formatted name-value pairs 483 * 484 * @param string $name name of key to use 485 * @param mixed $value reference to an array element to be encoded 486 * 487 * @return string JSON-formatted name-value pair, like '"name":value' 488 * @access private 489 */ 490 function name_value($name, $value) 491 { 492 $encoded_value = $this->_encode($value); 493 494 if(Services_JSON::isError($encoded_value)) { 495 return $encoded_value; 496 } 497 498 return $this->_encode(strval($name)) . ':' . $encoded_value; 499 } 500 501 /** 502 * reduce a string by removing leading and trailing comments and whitespace 503 * 504 * @param $str string string value to strip of comments and whitespace 505 * 506 * @return string string value stripped of comments and whitespace 507 * @access private 508 */ 509 function reduce_string($str) 510 { 511 $str = preg_replace(array( 512 513 // eliminate single line comments in '// ...' form 514 '#^\s*//(.+)$#m', 515 516 // eliminate multi-line comments in '/* ... */' form, at start of string 517 '#^\s*/\*(.+)\*/#Us', 518 519 // eliminate multi-line comments in '/* ... */' form, at end of string 520 '#/\*(.+)\*/\s*$#Us' 521 522 ), '', $str); 523 524 // eliminate extraneous space 525 return trim($str); 526 } 527 528 /** 529 * decodes a JSON string into appropriate variable 530 * 531 * @param string $str JSON-formatted string 532 * 533 * @return mixed number, boolean, string, array, or object 534 * corresponding to given JSON input string. 535 * See argument 1 to Services_JSON() above for object-output behavior. 536 * Note that decode() always returns strings 537 * in ASCII or UTF-8 format! 538 * @access public 539 */ 540 function decode($str) 541 { 542 $str = $this->reduce_string($str); 543 544 switch (strtolower($str)) { 545 case 'true': 546 return true; 547 548 case 'false': 549 return false; 550 551 case 'null': 552 return null; 553 554 default: 555 $m = array(); 556 557 if (is_numeric($str)) { 558 // Lookie-loo, it's a number 559 560 // This would work on its own, but I'm trying to be 561 // good about returning integers where appropriate: 562 // return (float)$str; 563 564 // Return float or int, as appropriate 565 return ((float)$str == (integer)$str) 566 ? (integer)$str 567 : (float)$str; 568 569 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { 570 // STRINGS RETURNED IN UTF-8 FORMAT 571 $delim = substr($str, 0, 1); 572 $chrs = substr($str, 1, -1); 573 $utf8 = ''; 574 $strlen_chrs = strlen($chrs); 575 576 for ($c = 0; $c < $strlen_chrs; ++$c) { 577 578 $substr_chrs_c_2 = substr($chrs, $c, 2); 579 $ord_chrs_c = ord($chrs{$c}); 580 581 switch (true) { 582 case $substr_chrs_c_2 == '\b': 583 $utf8 .= chr(0x08); 584 ++$c; 585 break; 586 case $substr_chrs_c_2 == '\t': 587 $utf8 .= chr(0x09); 588 ++$c; 589 break; 590 case $substr_chrs_c_2 == '\n': 591 $utf8 .= chr(0x0A); 592 ++$c; 593 break; 594 case $substr_chrs_c_2 == '\f': 595 $utf8 .= chr(0x0C); 596 ++$c; 597 break; 598 case $substr_chrs_c_2 == '\r': 599 $utf8 .= chr(0x0D); 600 ++$c; 601 break; 602 603 case $substr_chrs_c_2 == '\\"': 604 case $substr_chrs_c_2 == '\\\'': 605 case $substr_chrs_c_2 == '\\\\': 606 case $substr_chrs_c_2 == '\\/': 607 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || 608 ($delim == "'" && $substr_chrs_c_2 != '\\"')) { 609 $utf8 .= $chrs{++$c}; 610 } 611 break; 612 613 case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): 614 // single, escaped unicode character 615 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) 616 . chr(hexdec(substr($chrs, ($c + 4), 2))); 617 $utf8 .= $this->utf162utf8($utf16); 618 $c += 5; 619 break; 620 621 case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): 622 $utf8 .= $chrs{$c}; 623 break; 624 625 case ($ord_chrs_c & 0xE0) == 0xC0: 626 // characters U-00000080 - U-000007FF, mask 110XXXXX 627 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 628 $utf8 .= substr($chrs, $c, 2); 629 ++$c; 630 break; 631 632 case ($ord_chrs_c & 0xF0) == 0xE0: 633 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 634 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 635 $utf8 .= substr($chrs, $c, 3); 636 $c += 2; 637 break; 638 639 case ($ord_chrs_c & 0xF8) == 0xF0: 640 // characters U-00010000 - U-001FFFFF, mask 11110XXX 641 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 642 $utf8 .= substr($chrs, $c, 4); 643 $c += 3; 644 break; 645 646 case ($ord_chrs_c & 0xFC) == 0xF8: 647 // characters U-00200000 - U-03FFFFFF, mask 111110XX 648 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 649 $utf8 .= substr($chrs, $c, 5); 650 $c += 4; 651 break; 652 653 case ($ord_chrs_c & 0xFE) == 0xFC: 654 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 655 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 656 $utf8 .= substr($chrs, $c, 6); 657 $c += 5; 658 break; 659 660 } 661 662 } 663 664 return $utf8; 665 666 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { 667 // array, or object notation 668 669 if ($str{0} == '[') { 670 $stk = array(SERVICES_JSON_IN_ARR); 671 $arr = array(); 672 } else { 673 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 674 $stk = array(SERVICES_JSON_IN_OBJ); 675 $obj = array(); 676 } else { 677 $stk = array(SERVICES_JSON_IN_OBJ); 678 $obj = new stdClass(); 679 } 680 } 681 682 array_push($stk, array('what' => SERVICES_JSON_SLICE, 683 'where' => 0, 684 'delim' => false)); 685 686 $chrs = substr($str, 1, -1); 687 $chrs = $this->reduce_string($chrs); 688 689 if ($chrs == '') { 690 if (reset($stk) == SERVICES_JSON_IN_ARR) { 691 return $arr; 692 693 } else { 694 return $obj; 695 696 } 697 } 698 699 //print("\nparsing {$chrs}\n"); 700 701 $strlen_chrs = strlen($chrs); 702 703 for ($c = 0; $c <= $strlen_chrs; ++$c) { 704 705 $top = end($stk); 706 $substr_chrs_c_2 = substr($chrs, $c, 2); 707 708 if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { 709 // found a comma that is not inside a string, array, etc., 710 // OR we've reached the end of the character list 711 $slice = substr($chrs, $top['where'], ($c - $top['where'])); 712 array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); 713 //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 714 715 if (reset($stk) == SERVICES_JSON_IN_ARR) { 716 // we are in an array, so just push an element onto the stack 717 array_push($arr, $this->decode($slice)); 718 719 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 720 // we are in an object, so figure 721 // out the property name and set an 722 // element in an associative array, 723 // for now 724 $parts = array(); 725 726 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 727 // "name":value pair 728 $key = $this->decode($parts[1]); 729 $val = $this->decode($parts[2]); 730 731 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 732 $obj[$key] = $val; 733 } else { 734 $obj->$key = $val; 735 } 736 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 737 // name:value pair, where name is unquoted 738 $key = $parts[1]; 739 $val = $this->decode($parts[2]); 740 741 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 742 $obj[$key] = $val; 743 } else { 744 $obj->$key = $val; 745 } 746 } 747 748 } 749 750 } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { 751 // found a quote, and we are not inside a string 752 array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); 753 //print("Found start of string at {$c}\n"); 754 755 } elseif (($chrs{$c} == $top['delim']) && 756 ($top['what'] == SERVICES_JSON_IN_STR) && 757 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { 758 // found a quote, we're in a string, and it's not escaped 759 // we know that it's not escaped becase there is _not_ an 760 // odd number of backslashes at the end of the string so far 761 array_pop($stk); 762 //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); 763 764 } elseif (($chrs{$c} == '[') && 765 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 766 // found a left-bracket, and we are in an array, object, or slice 767 array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); 768 //print("Found start of array at {$c}\n"); 769 770 } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { 771 // found a right-bracket, and we're in an array 772 array_pop($stk); 773 //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 774 775 } elseif (($chrs{$c} == '{') && 776 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 777 // found a left-brace, and we are in an array, object, or slice 778 array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); 779 //print("Found start of object at {$c}\n"); 780 781 } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { 782 // found a right-brace, and we're in an object 783 array_pop($stk); 784 //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 785 786 } elseif (($substr_chrs_c_2 == '/*') && 787 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 788 // found a comment start, and we are in an array, object, or slice 789 array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); 790 $c++; 791 //print("Found start of comment at {$c}\n"); 792 793 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { 794 // found a comment end, and we're in one now 795 array_pop($stk); 796 $c++; 797 798 for ($i = $top['where']; $i <= $c; ++$i) 799 $chrs = substr_replace($chrs, ' ', $i, 1); 800 801 //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 802 803 } 804 805 } 806 807 if (reset($stk) == SERVICES_JSON_IN_ARR) { 808 return $arr; 809 810 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 811 return $obj; 812 813 } 814 815 } 816 } 817 } 818 819 /** 820 * @todo Ultimately, this should just call PEAR::isError() 821 */ 822 function isError($data, $code = null) 823 { 824 if (class_exists('pear')) { 825 return PEAR::isError($data, $code); 826 } elseif (is_object($data) && (get_class($data) == 'services_json_error' || 827 is_subclass_of($data, 'services_json_error'))) { 828 return true; 829 } 830 831 return false; 832 } 833 833 } 834 834 835 835 if (class_exists('PEAR_Error')) { 836 836 837 class Services_JSON_Error extends PEAR_Error838 {839 function Services_JSON_Error($message = 'unknown error', $code = null,840 $mode = null, $options = null, $userinfo = null)841 {842 parent::PEAR_Error($message, $code, $mode, $options, $userinfo);843 }844 }837 class Services_JSON_Error extends PEAR_Error 838 { 839 function Services_JSON_Error($message = 'unknown error', $code = null, 840 $mode = null, $options = null, $userinfo = null) 841 { 842 parent::PEAR_Error($message, $code, $mode, $options, $userinfo); 843 } 844 } 845 845 846 846 } else { 847 847 848 /**849 * @todo Ultimately, this class shall be descended from PEAR_Error850 */851 class Services_JSON_Error852 {853 function Services_JSON_Error($message = 'unknown error', $code = null,854 $mode = null, $options = null, $userinfo = null)855 {856 857 }858 }848 /** 849 * @todo Ultimately, this class shall be descended from PEAR_Error 850 */ 851 class Services_JSON_Error 852 { 853 function Services_JSON_Error($message = 'unknown error', $code = null, 854 $mode = null, $options = null, $userinfo = null) 855 { 856 857 } 858 } 859 859 860 860 } 861
Note: See TracChangeset
for help on using the changeset viewer.