[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
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 * @author Michal Migurski <mike-json@teczno.com> 49 * @author Matt Knapp <mdknapp[at]gmail[dot]com> 50 * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> 51 * @copyright 2005 Michal Migurski 52 * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ 53 * @license http://www.opensource.org/licenses/bsd-license.php 54 * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 55 */ 56 57 // for DokuWiki 58 if(!defined('DOKU_INC')) die('meh.'); 59 60 /** 61 * Default decoding 62 */ 63 define('JSON_STRICT_TYPE', 0); 64 65 /** 66 * Marker constant for JSON::decode(), used to flag stack state 67 */ 68 define('JSON_SLICE', 1); 69 70 /** 71 * Marker constant for JSON::decode(), used to flag stack state 72 */ 73 define('JSON_IN_STR', 2); 74 75 /** 76 * Marker constant for JSON::decode(), used to flag stack state 77 */ 78 define('JSON_IN_ARR', 3); 79 80 /** 81 * Marker constant for JSON::decode(), used to flag stack state 82 */ 83 define('JSON_IN_OBJ', 4); 84 85 /** 86 * Marker constant for JSON::decode(), used to flag stack state 87 */ 88 define('JSON_IN_CMT', 5); 89 90 /** 91 * Behavior switch for JSON::decode() 92 */ 93 define('JSON_LOOSE_TYPE', 16); 94 95 /** 96 * Behavior switch for JSON::decode() 97 */ 98 define('JSON_SUPPRESS_ERRORS', 32); 99 100 /** 101 * Converts to and from JSON format. 102 * 103 * Brief example of use: 104 * 105 * <code> 106 * // create a new instance of JSON 107 * $json = new JSON(); 108 * 109 * // convert a complexe value to JSON notation, and send it to the browser 110 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); 111 * $output = $json->encode($value); 112 * 113 * print($output); 114 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] 115 * 116 * // accept incoming POST data, assumed to be in JSON notation 117 * $input = file_get_contents('php://input', 1000000); 118 * $value = $json->decode($input); 119 * </code> 120 */ 121 class JSON 122 { 123 /** 124 * Disables the use of PHP5's native json_decode() 125 * 126 * You shouldn't change this usually because the native function is much 127 * faster. However, this non-native will also parse slightly broken JSON 128 * which might be handy when talking to a non-conform endpoint 129 */ 130 public $skipnative = false; 131 132 /** 133 * constructs a new JSON instance 134 * 135 * @param int $use object behavior flags; combine with boolean-OR 136 * 137 * possible values: 138 * - JSON_LOOSE_TYPE: loose typing. 139 * "{...}" syntax creates associative arrays 140 * instead of objects in decode(). 141 * - JSON_SUPPRESS_ERRORS: error suppression. 142 * Values which can't be encoded (e.g. resources) 143 * appear as NULL instead of throwing errors. 144 * By default, a deeply-nested resource will 145 * bubble up with an error, so all return values 146 * from encode() should be checked with isError() 147 */ 148 function __construct($use = 0) 149 { 150 $this->use = $use; 151 } 152 153 /** 154 * convert a string from one UTF-16 char to one UTF-8 char 155 * 156 * Normally should be handled by mb_convert_encoding, but 157 * provides a slower PHP-only method for installations 158 * that lack the multibye string extension. 159 * 160 * @param string $utf16 UTF-16 character 161 * @return string UTF-8 character 162 * @access private 163 */ 164 function utf162utf8($utf16) 165 { 166 // oh please oh please oh please oh please oh please 167 if(function_exists('mb_convert_encoding')) { 168 return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); 169 } 170 171 $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); 172 173 switch(true) { 174 case ((0x7F & $bytes) == $bytes): 175 // this case should never be reached, because we are in ASCII range 176 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 177 return chr(0x7F & $bytes); 178 179 case (0x07FF & $bytes) == $bytes: 180 // return a 2-byte UTF-8 character 181 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 182 return chr(0xC0 | (($bytes >> 6) & 0x1F)) 183 . chr(0x80 | ($bytes & 0x3F)); 184 185 case (0xFFFF & $bytes) == $bytes: 186 // return a 3-byte UTF-8 character 187 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 188 return chr(0xE0 | (($bytes >> 12) & 0x0F)) 189 . chr(0x80 | (($bytes >> 6) & 0x3F)) 190 . chr(0x80 | ($bytes & 0x3F)); 191 } 192 193 // ignoring UTF-32 for now, sorry 194 return ''; 195 } 196 197 /** 198 * convert a string from one UTF-8 char to one UTF-16 char 199 * 200 * Normally should be handled by mb_convert_encoding, but 201 * provides a slower PHP-only method for installations 202 * that lack the multibye string extension. 203 * 204 * @param string $utf8 UTF-8 character 205 * @return string UTF-16 character 206 * @access private 207 */ 208 function utf82utf16($utf8) 209 { 210 // oh please oh please oh please oh please oh please 211 if(function_exists('mb_convert_encoding')) { 212 return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 213 } 214 215 switch(strlen($utf8)) { 216 case 1: 217 // this case should never be reached, because we are in ASCII range 218 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 219 return $utf8; 220 221 case 2: 222 // return a UTF-16 character from a 2-byte UTF-8 char 223 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 224 return chr(0x07 & (ord($utf8{0}) >> 2)) 225 . chr((0xC0 & (ord($utf8{0}) << 6)) 226 | (0x3F & ord($utf8{1}))); 227 228 case 3: 229 // return a UTF-16 character from a 3-byte UTF-8 char 230 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 231 return chr((0xF0 & (ord($utf8{0}) << 4)) 232 | (0x0F & (ord($utf8{1}) >> 2))) 233 . chr((0xC0 & (ord($utf8{1}) << 6)) 234 | (0x7F & ord($utf8{2}))); 235 } 236 237 // ignoring UTF-32 for now, sorry 238 return ''; 239 } 240 241 /** 242 * encodes an arbitrary variable into JSON format 243 * 244 * @param mixed $var any number, boolean, string, array, or object to be encoded. 245 * see argument 1 to 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 encode($var) 253 { 254 if (!$this->skipnative && function_exists('json_encode')){ 255 return json_encode($var); 256 } 257 258 switch (gettype($var)) { 259 case 'boolean': 260 return $var ? 'true' : 'false'; 261 262 case 'NULL': 263 return 'null'; 264 265 case 'integer': 266 return (int) $var; 267 268 case 'double': 269 case 'float': 270 return (float) $var; 271 272 case 'string': 273 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 274 $ascii = ''; 275 $strlen_var = strlen($var); 276 277 /* 278 * Iterate over every character in the string, 279 * escaping with a slash or encoding to UTF-8 where necessary 280 */ 281 for ($c = 0; $c < $strlen_var; ++$c) { 282 283 $ord_var_c = ord($var{$c}); 284 285 switch (true) { 286 case $ord_var_c == 0x08: 287 $ascii .= '\b'; 288 break; 289 case $ord_var_c == 0x09: 290 $ascii .= '\t'; 291 break; 292 case $ord_var_c == 0x0A: 293 $ascii .= '\n'; 294 break; 295 case $ord_var_c == 0x0C: 296 $ascii .= '\f'; 297 break; 298 case $ord_var_c == 0x0D: 299 $ascii .= '\r'; 300 break; 301 302 case $ord_var_c == 0x22: 303 case $ord_var_c == 0x2F: 304 case $ord_var_c == 0x5C: 305 // double quote, slash, slosh 306 $ascii .= '\\'.$var{$c}; 307 break; 308 309 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 310 // characters U-00000000 - U-0000007F (same as ASCII) 311 $ascii .= $var{$c}; 312 break; 313 314 case (($ord_var_c & 0xE0) == 0xC0): 315 // characters U-00000080 - U-000007FF, mask 110XXXXX 316 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 317 $char = pack('C*', $ord_var_c, ord($var{$c + 1})); 318 $c += 1; 319 $utf16 = $this->utf82utf16($char); 320 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 321 break; 322 323 case (($ord_var_c & 0xF0) == 0xE0): 324 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 325 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 326 $char = pack('C*', $ord_var_c, 327 ord($var{$c + 1}), 328 ord($var{$c + 2})); 329 $c += 2; 330 $utf16 = $this->utf82utf16($char); 331 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 332 break; 333 334 case (($ord_var_c & 0xF8) == 0xF0): 335 // characters U-00010000 - U-001FFFFF, mask 11110XXX 336 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 337 $char = pack('C*', $ord_var_c, 338 ord($var{$c + 1}), 339 ord($var{$c + 2}), 340 ord($var{$c + 3})); 341 $c += 3; 342 $utf16 = $this->utf82utf16($char); 343 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 344 break; 345 346 case (($ord_var_c & 0xFC) == 0xF8): 347 // characters U-00200000 - U-03FFFFFF, mask 111110XX 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 ord($var{$c + 3}), 353 ord($var{$c + 4})); 354 $c += 4; 355 $utf16 = $this->utf82utf16($char); 356 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 357 break; 358 359 case (($ord_var_c & 0xFE) == 0xFC): 360 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 361 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 362 $char = pack('C*', $ord_var_c, 363 ord($var{$c + 1}), 364 ord($var{$c + 2}), 365 ord($var{$c + 3}), 366 ord($var{$c + 4}), 367 ord($var{$c + 5})); 368 $c += 5; 369 $utf16 = $this->utf82utf16($char); 370 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 371 break; 372 } 373 } 374 375 return '"'.$ascii.'"'; 376 377 case 'array': 378 /* 379 * As per JSON spec if any array key is not an integer 380 * we must treat the the whole array as an object. We 381 * also try to catch a sparsely populated associative 382 * array with numeric keys here because some JS engines 383 * will create an array with empty indexes up to 384 * max_index which can cause memory issues and because 385 * the keys, which may be relevant, will be remapped 386 * otherwise. 387 * 388 * As per the ECMA and JSON specification an object may 389 * have any string as a property. Unfortunately due to 390 * a hole in the ECMA specification if the key is a 391 * ECMA reserved word or starts with a digit the 392 * parameter is only accessible using ECMAScript's 393 * bracket notation. 394 */ 395 396 // treat as a JSON object 397 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 398 $properties = array_map(array($this, 'name_value'), 399 array_keys($var), 400 array_values($var)); 401 402 foreach($properties as $property) { 403 if(JSON::isError($property)) { 404 return $property; 405 } 406 } 407 408 return '{' . join(',', $properties) . '}'; 409 } 410 411 // treat it like a regular array 412 $elements = array_map(array($this, 'encode'), $var); 413 414 foreach($elements as $element) { 415 if(JSON::isError($element)) { 416 return $element; 417 } 418 } 419 420 return '[' . join(',', $elements) . ']'; 421 422 case 'object': 423 $vars = get_object_vars($var); 424 425 $properties = array_map(array($this, 'name_value'), 426 array_keys($vars), 427 array_values($vars)); 428 429 foreach($properties as $property) { 430 if(JSON::isError($property)) { 431 return $property; 432 } 433 } 434 435 return '{' . join(',', $properties) . '}'; 436 437 default: 438 return ($this->use & JSON_SUPPRESS_ERRORS) 439 ? 'null' 440 : new JSON_Error(gettype($var)." can not be encoded as JSON string"); 441 } 442 } 443 444 /** 445 * array-walking function for use in generating JSON-formatted name-value pairs 446 * 447 * @param string $name name of key to use 448 * @param mixed $value reference to an array element to be encoded 449 * 450 * @return string JSON-formatted name-value pair, like '"name":value' 451 * @access private 452 */ 453 function name_value($name, $value) 454 { 455 $encoded_value = $this->encode($value); 456 457 if(JSON::isError($encoded_value)) { 458 return $encoded_value; 459 } 460 461 return $this->encode(strval($name)) . ':' . $encoded_value; 462 } 463 464 /** 465 * reduce a string by removing leading and trailing comments and whitespace 466 * 467 * @param $str string string value to strip of comments and whitespace 468 * 469 * @return string string value stripped of comments and whitespace 470 * @access private 471 */ 472 function reduce_string($str) 473 { 474 $str = preg_replace(array( 475 476 // eliminate single line comments in '// ...' form 477 '#^\s*//(.+)$#m', 478 479 // eliminate multi-line comments in '/* ... */' form, at start of string 480 '#^\s*/\*(.+)\*/#Us', 481 482 // eliminate multi-line comments in '/* ... */' form, at end of string 483 '#/\*(.+)\*/\s*$#Us' 484 485 ), '', $str); 486 487 // eliminate extraneous space 488 return trim($str); 489 } 490 491 /** 492 * decodes a JSON string into appropriate variable 493 * 494 * @param string $str JSON-formatted string 495 * 496 * @return mixed number, boolean, string, array, or object 497 * corresponding to given JSON input string. 498 * See argument 1 to JSON() above for object-output behavior. 499 * Note that decode() always returns strings 500 * in ASCII or UTF-8 format! 501 * @access public 502 */ 503 function decode($str) 504 { 505 if (!$this->skipnative && function_exists('json_decode')){ 506 return json_decode($str,($this->use == JSON_LOOSE_TYPE)); 507 } 508 509 $str = $this->reduce_string($str); 510 511 switch (strtolower($str)) { 512 case 'true': 513 return true; 514 515 case 'false': 516 return false; 517 518 case 'null': 519 return null; 520 521 default: 522 $m = array(); 523 524 if (is_numeric($str)) { 525 // Lookie-loo, it's a number 526 527 // This would work on its own, but I'm trying to be 528 // good about returning integers where appropriate: 529 // return (float)$str; 530 531 // Return float or int, as appropriate 532 return ((float)$str == (integer)$str) 533 ? (integer)$str 534 : (float)$str; 535 536 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { 537 // STRINGS RETURNED IN UTF-8 FORMAT 538 $delim = substr($str, 0, 1); 539 $chrs = substr($str, 1, -1); 540 $utf8 = ''; 541 $strlen_chrs = strlen($chrs); 542 543 for ($c = 0; $c < $strlen_chrs; ++$c) { 544 545 $substr_chrs_c_2 = substr($chrs, $c, 2); 546 $ord_chrs_c = ord($chrs{$c}); 547 548 switch (true) { 549 case $substr_chrs_c_2 == '\b': 550 $utf8 .= chr(0x08); 551 ++$c; 552 break; 553 case $substr_chrs_c_2 == '\t': 554 $utf8 .= chr(0x09); 555 ++$c; 556 break; 557 case $substr_chrs_c_2 == '\n': 558 $utf8 .= chr(0x0A); 559 ++$c; 560 break; 561 case $substr_chrs_c_2 == '\f': 562 $utf8 .= chr(0x0C); 563 ++$c; 564 break; 565 case $substr_chrs_c_2 == '\r': 566 $utf8 .= chr(0x0D); 567 ++$c; 568 break; 569 570 case $substr_chrs_c_2 == '\\"': 571 case $substr_chrs_c_2 == '\\\'': 572 case $substr_chrs_c_2 == '\\\\': 573 case $substr_chrs_c_2 == '\\/': 574 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || 575 ($delim == "'" && $substr_chrs_c_2 != '\\"')) { 576 $utf8 .= $chrs{++$c}; 577 } 578 break; 579 580 case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): 581 // single, escaped unicode character 582 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) 583 . chr(hexdec(substr($chrs, ($c + 4), 2))); 584 $utf8 .= $this->utf162utf8($utf16); 585 $c += 5; 586 break; 587 588 case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): 589 $utf8 .= $chrs{$c}; 590 break; 591 592 case ($ord_chrs_c & 0xE0) == 0xC0: 593 // characters U-00000080 - U-000007FF, mask 110XXXXX 594 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 595 $utf8 .= substr($chrs, $c, 2); 596 ++$c; 597 break; 598 599 case ($ord_chrs_c & 0xF0) == 0xE0: 600 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 601 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 602 $utf8 .= substr($chrs, $c, 3); 603 $c += 2; 604 break; 605 606 case ($ord_chrs_c & 0xF8) == 0xF0: 607 // characters U-00010000 - U-001FFFFF, mask 11110XXX 608 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 609 $utf8 .= substr($chrs, $c, 4); 610 $c += 3; 611 break; 612 613 case ($ord_chrs_c & 0xFC) == 0xF8: 614 // characters U-00200000 - U-03FFFFFF, mask 111110XX 615 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 616 $utf8 .= substr($chrs, $c, 5); 617 $c += 4; 618 break; 619 620 case ($ord_chrs_c & 0xFE) == 0xFC: 621 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 622 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 623 $utf8 .= substr($chrs, $c, 6); 624 $c += 5; 625 break; 626 627 } 628 629 } 630 631 return $utf8; 632 633 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { 634 // array, or object notation 635 636 if ($str{0} == '[') { 637 $stk = array(JSON_IN_ARR); 638 $arr = array(); 639 } else { 640 if ($this->use & JSON_LOOSE_TYPE) { 641 $stk = array(JSON_IN_OBJ); 642 $obj = array(); 643 } else { 644 $stk = array(JSON_IN_OBJ); 645 $obj = new stdClass(); 646 } 647 } 648 649 array_push($stk, array('what' => JSON_SLICE, 650 'where' => 0, 651 'delim' => false)); 652 653 $chrs = substr($str, 1, -1); 654 $chrs = $this->reduce_string($chrs); 655 656 if ($chrs == '') { 657 if (reset($stk) == JSON_IN_ARR) { 658 return $arr; 659 660 } else { 661 return $obj; 662 663 } 664 } 665 666 //print("\nparsing {$chrs}\n"); 667 668 $strlen_chrs = strlen($chrs); 669 670 for ($c = 0; $c <= $strlen_chrs; ++$c) { 671 672 $top = end($stk); 673 $substr_chrs_c_2 = substr($chrs, $c, 2); 674 675 if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == JSON_SLICE))) { 676 // found a comma that is not inside a string, array, etc., 677 // OR we've reached the end of the character list 678 $slice = substr($chrs, $top['where'], ($c - $top['where'])); 679 array_push($stk, array('what' => JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); 680 //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 681 682 if (reset($stk) == JSON_IN_ARR) { 683 // we are in an array, so just push an element onto the stack 684 array_push($arr, $this->decode($slice)); 685 686 } elseif (reset($stk) == JSON_IN_OBJ) { 687 // we are in an object, so figure 688 // out the property name and set an 689 // element in an associative array, 690 // for now 691 $parts = array(); 692 693 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 694 // "name":value pair 695 $key = $this->decode($parts[1]); 696 $val = $this->decode($parts[2]); 697 698 if ($this->use & JSON_LOOSE_TYPE) { 699 $obj[$key] = $val; 700 } else { 701 $obj->$key = $val; 702 } 703 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 704 // name:value pair, where name is unquoted 705 $key = $parts[1]; 706 $val = $this->decode($parts[2]); 707 708 if ($this->use & JSON_LOOSE_TYPE) { 709 $obj[$key] = $val; 710 } else { 711 $obj->$key = $val; 712 } 713 } 714 715 } 716 717 } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != JSON_IN_STR)) { 718 // found a quote, and we are not inside a string 719 array_push($stk, array('what' => JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); 720 //print("Found start of string at {$c}\n"); 721 722 } elseif (($chrs{$c} == $top['delim']) && 723 ($top['what'] == JSON_IN_STR) && 724 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { 725 // found a quote, we're in a string, and it's not escaped 726 // we know that it's not escaped becase there is _not_ an 727 // odd number of backslashes at the end of the string so far 728 array_pop($stk); 729 //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); 730 731 } elseif (($chrs{$c} == '[') && 732 in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { 733 // found a left-bracket, and we are in an array, object, or slice 734 array_push($stk, array('what' => JSON_IN_ARR, 'where' => $c, 'delim' => false)); 735 //print("Found start of array at {$c}\n"); 736 737 } elseif (($chrs{$c} == ']') && ($top['what'] == JSON_IN_ARR)) { 738 // found a right-bracket, and we're in an array 739 array_pop($stk); 740 //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 741 742 } elseif (($chrs{$c} == '{') && 743 in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { 744 // found a left-brace, and we are in an array, object, or slice 745 array_push($stk, array('what' => JSON_IN_OBJ, 'where' => $c, 'delim' => false)); 746 //print("Found start of object at {$c}\n"); 747 748 } elseif (($chrs{$c} == '}') && ($top['what'] == JSON_IN_OBJ)) { 749 // found a right-brace, and we're in an object 750 array_pop($stk); 751 //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 752 753 } elseif (($substr_chrs_c_2 == '/*') && 754 in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) { 755 // found a comment start, and we are in an array, object, or slice 756 array_push($stk, array('what' => JSON_IN_CMT, 'where' => $c, 'delim' => false)); 757 $c++; 758 //print("Found start of comment at {$c}\n"); 759 760 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == JSON_IN_CMT)) { 761 // found a comment end, and we're in one now 762 array_pop($stk); 763 $c++; 764 765 for ($i = $top['where']; $i <= $c; ++$i) 766 $chrs = substr_replace($chrs, ' ', $i, 1); 767 768 //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 769 770 } 771 772 } 773 774 if (reset($stk) == JSON_IN_ARR) { 775 return $arr; 776 777 } elseif (reset($stk) == JSON_IN_OBJ) { 778 return $obj; 779 780 } 781 782 } 783 } 784 } 785 786 /** 787 * @todo Ultimately, this should just call PEAR::isError() 788 */ 789 function isError($data, $code = null) 790 { 791 if (class_exists('pear')) { 792 return PEAR::isError($data, $code); 793 } elseif (is_object($data) && (get_class($data) == 'JSON_error' || 794 is_subclass_of($data, 'JSON_error'))) { 795 return true; 796 } 797 798 return false; 799 } 800 } 801 802 if (class_exists('PEAR_Error')) { 803 804 class JSON_Error extends PEAR_Error 805 { 806 function __construct($message = 'unknown error', $code = null, 807 $mode = null, $options = null, $userinfo = null) 808 { 809 parent::__construct($message, $code, $mode, $options, $userinfo); 810 } 811 } 812 813 } else { 814 815 /** 816 * @todo Ultimately, this class shall be descended from PEAR_Error 817 */ 818 class JSON_Error 819 { 820 function __construct($message = 'unknown error', $code = null, 821 $mode = null, $options = null, $userinfo = null) 822 { 823 824 } 825 } 826 827 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body