/examples/dynamictable/public/phpolait/JSON.php
PHP | 791 lines | 428 code | 112 blank | 251 comment | 97 complexity | 9125de729343e8358e1f8340253bfb57 MD5 | raw file
1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4/** 5* Converts to and from JSON format. 6* 7* JSON (JavaScript Object Notation) is a lightweight data-interchange 8* format. It is easy for humans to read and write. It is easy for machines 9* to parse and generate. It is based on a subset of the JavaScript 10* Programming Language, Standard ECMA-262 3rd Edition - December 1999. 11* This feature can also be found in Python. JSON is a text format that is 12* completely language independent but uses conventions that are familiar 13* to programmers of the C-family of languages, including C, C++, C#, Java, 14* JavaScript, Perl, TCL, and many others. These properties make JSON an 15* ideal data-interchange language. 16* 17* This package provides a simple encoder and decoder for JSON notation. It 18* is intended for use with client-side Javascript applications that make 19* use of HTTPRequest to perform server communication functions - data can 20* be encoded into JSON notation for use in a client-side javascript, or 21* decoded from incoming Javascript requests. JSON format is native to 22* Javascript, and can be directly eval()'ed with no further parsing 23* overhead 24* 25* All strings should be in ASCII or UTF-8 format! 26* 27* LICENSE: Redistribution and use in source and binary forms, with or 28* without modification, are permitted provided that the following 29* conditions are met: Redistributions of source code must retain the 30* above copyright notice, this list of conditions and the following 31* disclaimer. Redistributions in binary form must reproduce the above 32* copyright notice, this list of conditions and the following disclaimer 33* in the documentation and/or other materials provided with the 34* distribution. 35* 36* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 37* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 38* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 39* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 40* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 41* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 42* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 43* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 44* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 45* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 46* DAMAGE. 47* 48* @category 49* @package Services_JSON 50* @author Michal Migurski <mike-json@teczno.com> 51* @author Matt Knapp <mdknapp[at]gmail[dot]com> 52* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> 53* @copyright 2005 Michal Migurski 54* @license http://www.opensource.org/licenses/bsd-license.php 55* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 56*/ 57 58/** 59* Marker constant for Services_JSON::decode(), used to flag stack state 60*/ 61define('SERVICES_JSON_SLICE', 1); 62 63/** 64* Marker constant for Services_JSON::decode(), used to flag stack state 65*/ 66define('SERVICES_JSON_IN_STR', 2); 67 68/** 69* Marker constant for Services_JSON::decode(), used to flag stack state 70*/ 71define('SERVICES_JSON_IN_ARR', 3); 72 73/** 74* Marker constant for Services_JSON::decode(), used to flag stack state 75*/ 76define('SERVICES_JSON_IN_OBJ', 4); 77 78/** 79* Marker constant for Services_JSON::decode(), used to flag stack state 80*/ 81define('SERVICES_JSON_IN_CMT', 5); 82 83/** 84* Behavior switch for Services_JSON::decode() 85*/ 86define('SERVICES_JSON_LOOSE_TYPE', 16); 87 88/** 89* Behavior switch for Services_JSON::decode() 90*/ 91define('SERVICES_JSON_SUPPRESS_ERRORS', 32); 92 93/** 94* Converts to and from JSON format. 95* 96* Brief example of use: 97* 98* <code> 99* // create a new instance of Services_JSON 100* $json = new Services_JSON(); 101* 102* // convert a complexe value to JSON notation, and send it to the browser 103* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); 104* $output = $json->encode($value); 105* 106* print($output); 107* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] 108* 109* // accept incoming POST data, assumed to be in JSON notation 110* $input = file_get_contents('php://input', 1000000); 111* $value = $json->decode($input); 112* </code> 113*/ 114class Services_JSON 115{ 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 $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); 155 156 switch(true) { 157 case ((0x7F & $bytes) == $bytes): 158 // this case should never be reached, because we are in ASCII range 159 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 160 return chr(0x7F & $bytes); 161 162 case (0x07FF & $bytes) == $bytes: 163 // return a 2-byte UTF-8 character 164 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 165 return chr(0xC0 | (($bytes >> 6) & 0x1F)) 166 . chr(0x80 | ($bytes & 0x3F)); 167 168 case (0xFFFF & $bytes) == $bytes: 169 // return a 3-byte UTF-8 character 170 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 171 return chr(0xE0 | (($bytes >> 12) & 0x0F)) 172 . chr(0x80 | (($bytes >> 6) & 0x3F)) 173 . chr(0x80 | ($bytes & 0x3F)); 174 } 175 176 // ignoring UTF-32 for now, sorry 177 return ''; 178 } 179 180 /** 181 * convert a string from one UTF-8 char to one UTF-16 char 182 * 183 * Normally should be handled by mb_convert_encoding, but 184 * provides a slower PHP-only method for installations 185 * that lack the multibye string extension. 186 * 187 * @param string $utf8 UTF-8 character 188 * @return string UTF-16 character 189 * @access private 190 */ 191 function utf82utf16($utf8) 192 { 193 // oh please oh please oh please oh please oh please 194 if(function_exists('mb_convert_encoding')) 195 return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 196 197 switch(strlen($utf8)) { 198 case 1: 199 // this case should never be reached, because we are in ASCII range 200 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 201 return $ut8; 202 203 case 2: 204 // return a UTF-16 character from a 2-byte UTF-8 char 205 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 206 return chr(0x07 & (ord($utf8{0}) >> 2)) 207 . chr((0xC0 & (ord($utf8{0}) << 6)) 208 | (0x3F & ord($utf8{1}))); 209 210 case 3: 211 // return a UTF-16 character from a 3-byte UTF-8 char 212 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 213 return chr((0xF0 & (ord($utf8{0}) << 4)) 214 | (0x0F & (ord($utf8{1}) >> 2))) 215 . chr((0xC0 & (ord($utf8{1}) << 6)) 216 | (0x7F & ord($utf8{2}))); 217 } 218 219 // ignoring UTF-32 for now, sorry 220 return ''; 221 } 222 223 /** 224 * encodes an arbitrary variable into JSON format 225 * 226 * @param mixed $var any number, boolean, string, array, or object to be encoded. 227 * see argument 1 to Services_JSON() above for array-parsing behavior. 228 * if var is a strng, note that encode() always expects it 229 * to be in ASCII or UTF-8 format! 230 * 231 * @return mixed JSON string representation of input var or an error if a problem occurs 232 * @access public 233 */ 234 function encode($var) 235 { 236 switch (gettype($var)) { 237 case 'boolean': 238 return $var ? 'true' : 'false'; 239 240 case 'NULL': 241 return 'null'; 242 243 case 'integer': 244 return (int) $var; 245 246 case 'double': 247 case 'float': 248 return (float) $var; 249 250 case 'string': 251 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 252 $ascii = ''; 253 $strlen_var = strlen($var); 254 255 /* 256 * Iterate over every character in the string, 257 * escaping with a slash or encoding to UTF-8 where necessary 258 */ 259 for ($c = 0; $c < $strlen_var; ++$c) { 260 261 $ord_var_c = ord($var{$c}); 262 263 switch (true) { 264 case $ord_var_c == 0x08: 265 $ascii .= '\b'; 266 break; 267 case $ord_var_c == 0x09: 268 $ascii .= '\t'; 269 break; 270 case $ord_var_c == 0x0A: 271 $ascii .= '\n'; 272 break; 273 case $ord_var_c == 0x0C: 274 $ascii .= '\f'; 275 break; 276 case $ord_var_c == 0x0D: 277 $ascii .= '\r'; 278 break; 279 280 case $ord_var_c == 0x22: 281 case $ord_var_c == 0x2F: 282 case $ord_var_c == 0x5C: 283 // double quote, slash, slosh 284 $ascii .= '\\'.$var{$c}; 285 break; 286 287 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 288 // characters U-00000000 - U-0000007F (same as ASCII) 289 $ascii .= $var{$c}; 290 break; 291 292 case (($ord_var_c & 0xE0) == 0xC0): 293 // characters U-00000080 - U-000007FF, mask 110XXXXX 294 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 295 $char = pack('C*', $ord_var_c, ord($var{$c + 1})); 296 $c += 1; 297 $utf16 = $this->utf82utf16($char); 298 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 299 break; 300 301 case (($ord_var_c & 0xF0) == 0xE0): 302 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 303 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 304 $char = pack('C*', $ord_var_c, 305 ord($var{$c + 1}), 306 ord($var{$c + 2})); 307 $c += 2; 308 $utf16 = $this->utf82utf16($char); 309 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 310 break; 311 312 case (($ord_var_c & 0xF8) == 0xF0): 313 // characters U-00010000 - U-001FFFFF, mask 11110XXX 314 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 315 $char = pack('C*', $ord_var_c, 316 ord($var{$c + 1}), 317 ord($var{$c + 2}), 318 ord($var{$c + 3})); 319 $c += 3; 320 $utf16 = $this->utf82utf16($char); 321 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 322 break; 323 324 case (($ord_var_c & 0xFC) == 0xF8): 325 // characters U-00200000 - U-03FFFFFF, mask 111110XX 326 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 327 $char = pack('C*', $ord_var_c, 328 ord($var{$c + 1}), 329 ord($var{$c + 2}), 330 ord($var{$c + 3}), 331 ord($var{$c + 4})); 332 $c += 4; 333 $utf16 = $this->utf82utf16($char); 334 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 335 break; 336 337 case (($ord_var_c & 0xFE) == 0xFC): 338 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 339 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 340 $char = pack('C*', $ord_var_c, 341 ord($var{$c + 1}), 342 ord($var{$c + 2}), 343 ord($var{$c + 3}), 344 ord($var{$c + 4}), 345 ord($var{$c + 5})); 346 $c += 5; 347 $utf16 = $this->utf82utf16($char); 348 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 349 break; 350 } 351 } 352 353 return '"'.$ascii.'"'; 354 355 case 'array': 356 /* 357 * As per JSON spec if any array key is not an integer 358 * we must treat the the whole array as an object. We 359 * also try to catch a sparsely populated associative 360 * array with numeric keys here because some JS engines 361 * will create an array with empty indexes up to 362 * max_index which can cause memory issues and because 363 * the keys, which may be relevant, will be remapped 364 * otherwise. 365 * 366 * As per the ECMA and JSON specification an object may 367 * have any string as a property. Unfortunately due to 368 * a hole in the ECMA specification if the key is a 369 * ECMA reserved word or starts with a digit the 370 * parameter is only accessible using ECMAScript's 371 * bracket notation. 372 */ 373 374 // treat as a JSON object 375 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 376 $properties = array_map(array($this, 'name_value'), 377 array_keys($var), 378 array_values($var)); 379 380 foreach($properties as $property) 381 if(Services_JSON::isError($property)) 382 return $property; 383 384 return '{' . join(',', $properties) . '}'; 385 } 386 387 // treat it like a regular array 388 $elements = array_map(array($this, 'encode'), $var); 389 390 foreach($elements as $element) 391 if(Services_JSON::isError($element)) 392 return $element; 393 394 return '[' . join(',', $elements) . ']'; 395 396 case 'object': 397 $vars = get_object_vars($var); 398 399 $properties = array_map(array($this, 'name_value'), 400 array_keys($vars), 401 array_values($vars)); 402 403 foreach($properties as $property) 404 if(Services_JSON::isError($property)) 405 return $property; 406 407 return '{' . join(',', $properties) . '}'; 408 409 default: 410 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) 411 ? 'null' 412 : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); 413 } 414 } 415 416 /** 417 * array-walking function for use in generating JSON-formatted name-value pairs 418 * 419 * @param string $name name of key to use 420 * @param mixed $value reference to an array element to be encoded 421 * 422 * @return string JSON-formatted name-value pair, like '"name":value' 423 * @access private 424 */ 425 function name_value($name, $value) 426 { 427 $encoded_value = $this->encode($value); 428 429 if(Services_JSON::isError($encoded_value)) 430 return $encoded_value; 431 432 return $this->encode(strval($name)) . ':' . $encoded_value; 433 } 434 435 /** 436 * reduce a string by removing leading and trailing comments and whitespace 437 * 438 * @param $str string string value to strip of comments and whitespace 439 * 440 * @return string string value stripped of comments and whitespace 441 * @access private 442 */ 443 function reduce_string($str) 444 { 445 $str = preg_replace(array( 446 447 // eliminate single line comments in '// ...' form 448 '#^\s*//(.+)$#m', 449 450 // eliminate multi-line comments in '/* ... */' form, at start of string 451 '#^\s*/\*(.+)\*/#Us', 452 453 // eliminate multi-line comments in '/* ... */' form, at end of string 454 '#/\*(.+)\*/\s*$#Us' 455 456 ), '', $str); 457 458 // eliminate extraneous space 459 return trim($str); 460 } 461 462 /** 463 * decodes a JSON string into appropriate variable 464 * 465 * @param string $str JSON-formatted string 466 * 467 * @return mixed number, boolean, string, array, or object 468 * corresponding to given JSON input string. 469 * See argument 1 to Services_JSON() above for object-output behavior. 470 * Note that decode() always returns strings 471 * in ASCII or UTF-8 format! 472 * @access public 473 */ 474 function decode($str) 475 { 476 $str = $this->reduce_string($str); 477 478 switch (strtolower($str)) { 479 case 'true': 480 return true; 481 482 case 'false': 483 return false; 484 485 case 'null': 486 return null; 487 488 default: 489 if (is_numeric($str)) { 490 // Lookie-loo, it's a number 491 492 // This would work on its own, but I'm trying to be 493 // good about returning integers where appropriate: 494 // return (float)$str; 495 496 // Return float or int, as appropriate 497 return ((float)$str == (integer)$str) 498 ? (integer)$str 499 : (float)$str; 500 501 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { 502 // STRINGS RETURNED IN UTF-8 FORMAT 503 $delim = substr($str, 0, 1); 504 $chrs = substr($str, 1, -1); 505 $utf8 = ''; 506 $strlen_chrs = strlen($chrs); 507 508 for ($c = 0; $c < $strlen_chrs; ++$c) { 509 510 $substr_chrs_c_2 = substr($chrs, $c, 2); 511 $ord_chrs_c = ord($chrs{$c}); 512 513 switch (true) { 514 case $substr_chrs_c_2 == '\b': 515 $utf8 .= chr(0x08); 516 ++$c; 517 break; 518 case $substr_chrs_c_2 == '\t': 519 $utf8 .= chr(0x09); 520 ++$c; 521 break; 522 case $substr_chrs_c_2 == '\n': 523 $utf8 .= chr(0x0A); 524 ++$c; 525 break; 526 case $substr_chrs_c_2 == '\f': 527 $utf8 .= chr(0x0C); 528 ++$c; 529 break; 530 case $substr_chrs_c_2 == '\r': 531 $utf8 .= chr(0x0D); 532 ++$c; 533 break; 534 535 case $substr_chrs_c_2 == '\\"': 536 case $substr_chrs_c_2 == '\\\'': 537 case $substr_chrs_c_2 == '\\\\': 538 case $substr_chrs_c_2 == '\\/': 539 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || 540 ($delim == "'" && $substr_chrs_c_2 != '\\"')) { 541 $utf8 .= $chrs{++$c}; 542 } 543 break; 544 545 case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): 546 // single, escaped unicode character 547 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) 548 . chr(hexdec(substr($chrs, ($c + 4), 2))); 549 $utf8 .= $this->utf162utf8($utf16); 550 $c += 5; 551 break; 552 553 case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): 554 $utf8 .= $chrs{$c}; 555 break; 556 557 case ($ord_chrs_c & 0xE0) == 0xC0: 558 // characters U-00000080 - U-000007FF, mask 110XXXXX 559 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 560 $utf8 .= substr($chrs, $c, 2); 561 ++$c; 562 break; 563 564 case ($ord_chrs_c & 0xF0) == 0xE0: 565 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 566 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 567 $utf8 .= substr($chrs, $c, 3); 568 $c += 2; 569 break; 570 571 case ($ord_chrs_c & 0xF8) == 0xF0: 572 // characters U-00010000 - U-001FFFFF, mask 11110XXX 573 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 574 $utf8 .= substr($chrs, $c, 4); 575 $c += 3; 576 break; 577 578 case ($ord_chrs_c & 0xFC) == 0xF8: 579 // characters U-00200000 - U-03FFFFFF, mask 111110XX 580 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 581 $utf8 .= substr($chrs, $c, 5); 582 $c += 4; 583 break; 584 585 case ($ord_chrs_c & 0xFE) == 0xFC: 586 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 587 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 588 $utf8 .= substr($chrs, $c, 6); 589 $c += 5; 590 break; 591 592 } 593 594 } 595 596 return $utf8; 597 598 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { 599 // array, or object notation 600 601 if ($str{0} == '[') { 602 $stk = array(SERVICES_JSON_IN_ARR); 603 $arr = array(); 604 } else { 605 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 606 $stk = array(SERVICES_JSON_IN_OBJ); 607 $obj = array(); 608 } else { 609 $stk = array(SERVICES_JSON_IN_OBJ); 610 $obj = new stdClass(); 611 } 612 } 613 614 array_push($stk, array('what' => SERVICES_JSON_SLICE, 615 'where' => 0, 616 'delim' => false)); 617 618 $chrs = substr($str, 1, -1); 619 $chrs = $this->reduce_string($chrs); 620 621 if ($chrs == '') { 622 if (reset($stk) == SERVICES_JSON_IN_ARR) { 623 return $arr; 624 625 } else { 626 return $obj; 627 628 } 629 } 630 631 //print("\nparsing {$chrs}\n"); 632 633 $strlen_chrs = strlen($chrs); 634 635 for ($c = 0; $c <= $strlen_chrs; ++$c) { 636 637 $top = end($stk); 638 $substr_chrs_c_2 = substr($chrs, $c, 2); 639 640 if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { 641 // found a comma that is not inside a string, array, etc., 642 // OR we've reached the end of the character list 643 $slice = substr($chrs, $top['where'], ($c - $top['where'])); 644 array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); 645 //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 646 647 if (reset($stk) == SERVICES_JSON_IN_ARR) { 648 // we are in an array, so just push an element onto the stack 649 array_push($arr, $this->decode($slice)); 650 651 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 652 // we are in an object, so figure 653 // out the property name and set an 654 // element in an associative array, 655 // for now 656 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 657 // "name":value pair 658 $key = $this->decode($parts[1]); 659 $val = $this->decode($parts[2]); 660 661 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 662 $obj[$key] = $val; 663 } else { 664 $obj->$key = $val; 665 } 666 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 667 // name:value pair, where name is unquoted 668 $key = $parts[1]; 669 $val = $this->decode($parts[2]); 670 671 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 672 $obj[$key] = $val; 673 } else { 674 $obj->$key = $val; 675 } 676 } 677 678 } 679 680 } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { 681 // found a quote, and we are not inside a string 682 array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); 683 //print("Found start of string at {$c}\n"); 684 685 } elseif (($chrs{$c} == $top['delim']) && 686 ($top['what'] == SERVICES_JSON_IN_STR) && 687 (($chrs{$c - 1} != '\\') || 688 ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { 689 // found a quote, we're in a string, and it's not escaped 690 array_pop($stk); 691 //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); 692 693 } elseif (($chrs{$c} == '[') && 694 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 695 // found a left-bracket, and we are in an array, object, or slice 696 array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); 697 //print("Found start of array at {$c}\n"); 698 699 } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { 700 // found a right-bracket, and we're in an array 701 array_pop($stk); 702 //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 703 704 } elseif (($chrs{$c} == '{') && 705 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 706 // found a left-brace, and we are in an array, object, or slice 707 array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); 708 //print("Found start of object at {$c}\n"); 709 710 } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { 711 // found a right-brace, and we're in an object 712 array_pop($stk); 713 //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 714 715 } elseif (($substr_chrs_c_2 == '/*') && 716 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 717 // found a comment start, and we are in an array, object, or slice 718 array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); 719 $c++; 720 //print("Found start of comment at {$c}\n"); 721 722 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { 723 // found a comment end, and we're in one now 724 array_pop($stk); 725 $c++; 726 727 for ($i = $top['where']; $i <= $c; ++$i) 728 $chrs = substr_replace($chrs, ' ', $i, 1); 729 730 //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 731 732 } 733 734 } 735 736 if (reset($stk) == SERVICES_JSON_IN_ARR) { 737 return $arr; 738 739 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 740 return $obj; 741 742 } 743 744 } 745 } 746 } 747 748 /** 749 * @todo Ultimately, this should just call PEAR::isError() 750 */ 751 function isError($data, $code = null) 752 { 753 if (class_exists('pear')) { 754 return PEAR::isError($data, $code); 755 } elseif (is_object($data) && (get_class($data) == 'services_json_error' || 756 is_subclass_of($data, 'services_json_error'))) { 757 return true; 758 } 759 760 return false; 761 } 762} 763 764if (class_exists('pear_error')) { 765 766 class Services_JSON_Error extends PEAR_Error 767 { 768 function Services_JSON_Error($message = 'unknown error', $code = null, 769 $mode = null, $options = null, $userinfo = null) 770 { 771 parent::PEAR_Error($message, $code, $mode, $options, $userinfo); 772 } 773 } 774 775} else { 776 777 /** 778 * @todo Ultimately, this class shall be descended from PEAR_Error 779 */ 780 class Services_JSON_Error 781 { 782 function Services_JSON_Error($message = 'unknown error', $code = null, 783 $mode = null, $options = null, $userinfo = null) 784 { 785 786 } 787 } 788 789} 790 791?>