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', 4); 72 73/** 74 * Marker constant for Services_JSON::decode(), used to flag stack state 75 */ 76define('SERVICES_JSON_IN_OBJ', 8); 77 78/** 79 * Marker constant for Services_JSON::decode(), used to flag stack state 80 */ 81define('SERVICES_JSON_IN_CMT', 16); 82 83/** 84 * Behavior switch for Services_JSON::decode() 85 */ 86define('SERVICES_JSON_LOOSE_TYPE', 10); 87 88/** 89 * Behavior switch for Services_JSON::decode() 90 */ 91define('SERVICES_JSON_STRICT_TYPE', 11); 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: when encoding or decoding, 120 * be loose or strict about object/array usage 121 * 122 * possible values: 123 * - SERVICES_JSON_STRICT_TYPE: strict typing, default. 124 * "{...}" syntax creates objects in decode(). 125 * - SERVICES_JSON_LOOSE_TYPE: loose typing. 126 * "{...}" syntax creates associative arrays in decode(). 127 */ 128 function Services_JSON($use = SERVICES_JSON_STRICT_TYPE) 129 { 130 $this->use = $use; 131 } 132 133 /** 134 * convert a string from one UTF-16 char to one UTF-8 char 135 * 136 * Normally should be handled by mb_convert_encoding, but 137 * provides a slower PHP-only method for installations 138 * that lack the multibye string extension. 139 * 140 * @param string $utf16 UTF-16 character 141 * @return string UTF-8 character 142 * @access private 143 */ 144 function utf162utf8($utf16) 145 { 146 // oh please oh please oh please oh please oh please 147 if(function_exists('mb_convert_encoding')) 148 return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); 149 150 $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); 151 152 switch(true) { 153 case ((0x7F & $bytes) == $bytes): 154 // this case should never be reached, because we are in ASCII range 155 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 156 return chr(0x7F & $bytes); 157 158 case (0x07FF & $bytes) == $bytes: 159 // return a 2-byte UTF-8 character 160 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 161 return chr(0xC0 | (($bytes >> 6) & 0x1F)) 162 . chr(0x80 | ($bytes & 0x3F)); 163 164 case (0xFFFF & $bytes) == $bytes: 165 // return a 3-byte UTF-8 character 166 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 167 return chr(0xE0 | (($bytes >> 12) & 0x0F)) 168 . chr(0x80 | (($bytes >> 6) & 0x3F)) 169 . chr(0x80 | ($bytes & 0x3F)); 170 } 171 172 // ignoring UTF-32 for now, sorry 173 return ''; 174 } 175 176 /** 177 * convert a string from one UTF-8 char to one UTF-16 char 178 * 179 * Normally should be handled by mb_convert_encoding, but 180 * provides a slower PHP-only method for installations 181 * that lack the multibye string extension. 182 * 183 * @param string $utf8 UTF-8 character 184 * @return string UTF-16 character 185 * @access private 186 */ 187 function utf82utf16($utf8) 188 { 189 // oh please oh please oh please oh please oh please 190 if(function_exists('mb_convert_encoding')) 191 return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 192 193 switch(strlen($utf8)) { 194 case 1: 195 // this case should never be reached, because we are in ASCII range 196 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 197 return $utf8; 198 199 case 2: 200 // return a UTF-16 character from a 2-byte UTF-8 char 201 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 202 return chr(0x07 & (ord($utf8{0}) >> 2)) 203 . chr((0xC0 & (ord($utf8{0}) << 6)) 204 | (0x3F & ord($utf8{1}))); 205 206 case 3: 207 // return a UTF-16 character from a 3-byte UTF-8 char 208 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 209 return chr((0xF0 & (ord($utf8{0}) << 4)) 210 | (0x0F & (ord($utf8{1}) >> 2))) 211 . chr((0xC0 & (ord($utf8{1}) << 6)) 212 | (0x7F & ord($utf8{2}))); 213 } 214 215 // ignoring UTF-32 for now, sorry 216 return ''; 217 } 218 219 /** 220 * encodes an arbitrary variable into JSON format 221 * 222 * @param mixed $var any number, boolean, string, array, or object to be encoded. 223 * see argument 1 to Services_JSON() above for array-parsing behavior. 224 * if var is a strng, note that encode() always expects it 225 * to be in ASCII or UTF-8 format! 226 * 227 * @return string JSON string representation of input var 228 * @access public 229 */ 230 function encode($var) 231 { 232 switch (gettype($var)) { 233 case 'boolean': 234 return $var ? 'true' : 'false'; 235 236 case 'NULL': 237 return 'null'; 238 239 case 'integer': 240 return (int) $var; 241 242 case 'double': 243 case 'float': 244 return (float) $var; 245 246 case 'string': 247 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 248 $ascii = ''; 249 $strlen_var = strlen($var); 250 251 /* 252 * Iterate over every character in the string, 253 * escaping with a slash or encoding to UTF-8 where necessary 254 */ 255 for ($c = 0; $c < $strlen_var; ++$c) { 256 257 $ord_var_c = ord($var{$c}); 258 259 switch (true) { 260 case $ord_var_c == 0x08: 261 $ascii .= '\b'; 262 break; 263 case $ord_var_c == 0x09: 264 $ascii .= '\t'; 265 break; 266 case $ord_var_c == 0x0A: 267 $ascii .= '\n'; 268 break; 269 case $ord_var_c == 0x0C: 270 $ascii .= '\f'; 271 break; 272 case $ord_var_c == 0x0D: 273 $ascii .= '\r'; 274 break; 275 276 case $ord_var_c == 0x22: 277 case $ord_var_c == 0x2F: 278 case $ord_var_c == 0x5C: 279 // double quote, slash, slosh 280 $ascii .= '\\'.$var{$c}; 281 break; 282 283 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 284 // characters U-00000000 - U-0000007F (same as ASCII) 285 $ascii .= $var{$c}; 286 break; 287 288 case (($ord_var_c & 0xE0) == 0xC0): 289 // characters U-00000080 - U-000007FF, mask 110XXXXX 290 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 291 $char = pack('C*', $ord_var_c, ord($var{$c + 1})); 292 $c += 1; 293 $utf16 = $this->utf82utf16($char); 294 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 295 break; 296 297 case (($ord_var_c & 0xF0) == 0xE0): 298 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 299 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 300 $char = pack('C*', $ord_var_c, 301 ord($var{$c + 1}), 302 ord($var{$c + 2})); 303 $c += 2; 304 $utf16 = $this->utf82utf16($char); 305 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 306 break; 307 308 case (($ord_var_c & 0xF8) == 0xF0): 309 // characters U-00010000 - U-001FFFFF, mask 11110XXX 310 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 311 $char = pack('C*', $ord_var_c, 312 ord($var{$c + 1}), 313 ord($var{$c + 2}), 314 ord($var{$c + 3})); 315 $c += 3; 316 $utf16 = $this->utf82utf16($char); 317 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 318 break; 319 320 case (($ord_var_c & 0xFC) == 0xF8): 321 // characters U-00200000 - U-03FFFFFF, mask 111110XX 322 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 323 $char = pack('C*', $ord_var_c, 324 ord($var{$c + 1}), 325 ord($var{$c + 2}), 326 ord($var{$c + 3}), 327 ord($var{$c + 4})); 328 $c += 4; 329 $utf16 = $this->utf82utf16($char); 330 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 331 break; 332 333 case (($ord_var_c & 0xFE) == 0xFC): 334 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 335 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 336 $char = pack('C*', $ord_var_c, 337 ord($var{$c + 1}), 338 ord($var{$c + 2}), 339 ord($var{$c + 3}), 340 ord($var{$c + 4}), 341 ord($var{$c + 5})); 342 $c += 5; 343 $utf16 = $this->utf82utf16($char); 344 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 345 break; 346 } 347 } 348 349 return '"'.$ascii.'"'; 350 351 case 'array': 352 /* 353 * As per JSON spec if any array key is not an integer 354 * we must treat the the whole array as an object. We 355 * also try to catch a sparsely populated associative 356 * array with numeric keys here because some JS engines 357 * will create an array with empty indexes up to 358 * max_index which can cause memory issues and because 359 * the keys, which may be relevant, will be remapped 360 * otherwise. 361 * 362 * As per the ECMA and JSON specification an object may 363 * have any string as a property. Unfortunately due to 364 * a hole in the ECMA specification if the key is a 365 * ECMA reserved word or starts with a digit the 366 * parameter is only accessible using ECMAScript's 367 * bracket notation. 368 */ 369 370 // treat as a JSON object 371 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 372 return '{' . 373 join(',', array_map(array($this, 'name_value'), 374 array_keys($var), 375 array_values($var))) 376 . '}'; 377 } 378 379 // treat it like a regular array 380 return '[' . join(',', array_map(array($this, 'encode'), $var)) . ']'; 381 382 case 'object': 383 $vars = get_object_vars($var); 384 return '{' . 385 join(',', array_map(array($this, 'name_value'), 386 array_keys($vars), 387 array_values($vars))) 388 . '}'; 389 390 default: 391 return ''; 392 } 393 } 394 395 /** 396 * array-walking function for use in generating JSON-formatted name-value pairs 397 * 398 * @param string $name name of key to use 399 * @param mixed $value reference to an array element to be encoded 400 * 401 * @return string JSON-formatted name-value pair, like '"name":value' 402 * @access private 403 */ 404 function name_value($name, $value) 405 { 406 return $this->encode(strval($name)) . ':' . $this->encode($value); 407 } 408 409 /** 410 * reduce a string by removing leading and trailing comments and whitespace 411 * 412 * @param $str string string value to strip of comments and whitespace 413 * 414 * @return string string value stripped of comments and whitespace 415 * @access private 416 */ 417 function reduce_string($str) 418 { 419 $str = preg_replace(array( 420 421 // eliminate single line comments in '// ...' form 422 '#^\s*//(.+)$#m', 423 424 // eliminate multi-line comments in '/* ... */' form, at start of string 425 '#^\s*/\*(.+)\*/#Us', 426 427 // eliminate multi-line comments in '/* ... */' form, at end of string 428 '#/\*(.+)\*/\s*$#Us' 429 430 ), '', $str); 431 432 // eliminate extraneous space 433 return trim($str); 434 } 435 436 /** 437 * decodes a JSON string into appropriate variable 438 * 439 * @param string $str JSON-formatted string 440 * 441 * @return mixed number, boolean, string, array, or object 442 * corresponding to given JSON input string. 443 * See argument 1 to Services_JSON() above for object-output behavior. 444 * Note that decode() always returns strings 445 * in ASCII or UTF-8 format! 446 * @access public 447 */ 448 function decode($str) 449 { 450 $str = $this->reduce_string($str); 451 452 switch (strtolower($str)) { 453 case 'true': 454 return true; 455 456 case 'false': 457 return false; 458 459 case 'null': 460 return null; 461 462 default: 463 if (is_numeric($str)) { 464 // Lookie-loo, it's a number 465 466 // This would work on its own, but I'm trying to be 467 // good about returning integers where appropriate: 468 // return (float)$str; 469 470 // Return float or int, as appropriate 471 return ((float)$str == (integer)$str) 472 ? (integer)$str 473 : (float)$str; 474 475 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { 476 // STRINGS RETURNED IN UTF-8 FORMAT 477 $delim = substr($str, 0, 1); 478 $chrs = substr($str, 1, -1); 479 $utf8 = ''; 480 $strlen_chrs = strlen($chrs); 481 482 for ($c = 0; $c < $strlen_chrs; ++$c) { 483 484 $substr_chrs_c_2 = substr($chrs, $c, 2); 485 $ord_chrs_c = ord($chrs{$c}); 486 487 switch (true) { 488 case $substr_chrs_c_2 == '\b': 489 $utf8 .= chr(0x08); 490 ++$c; 491 break; 492 case $substr_chrs_c_2 == '\t': 493 $utf8 .= chr(0x09); 494 ++$c; 495 break; 496 case $substr_chrs_c_2 == '\n': 497 $utf8 .= chr(0x0A); 498 ++$c; 499 break; 500 case $substr_chrs_c_2 == '\f': 501 $utf8 .= chr(0x0C); 502 ++$c; 503 break; 504 case $substr_chrs_c_2 == '\r': 505 $utf8 .= chr(0x0D); 506 ++$c; 507 break; 508 509 case $substr_chrs_c_2 == '\\"': 510 case $substr_chrs_c_2 == '\\\'': 511 case $substr_chrs_c_2 == '\\\\': 512 case $substr_chrs_c_2 == '\\/': 513 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || 514 ($delim == "'" && $substr_chrs_c_2 != '\\"')) { 515 $utf8 .= $chrs{++$c}; 516 } 517 break; 518 519 case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): 520 // single, escaped unicode character 521 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) 522 . chr(hexdec(substr($chrs, ($c + 4), 2))); 523 $utf8 .= $this->utf162utf8($utf16); 524 $c += 5; 525 break; 526 527 case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): 528 $utf8 .= $chrs{$c}; 529 break; 530 531 case ($ord_chrs_c & 0xE0) == 0xC0: 532 // characters U-00000080 - U-000007FF, mask 110XXXXX 533 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 534 $utf8 .= substr($chrs, $c, 2); 535 ++$c; 536 break; 537 538 case ($ord_chrs_c & 0xF0) == 0xE0: 539 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 540 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 541 $utf8 .= substr($chrs, $c, 3); 542 $c += 2; 543 break; 544 545 case ($ord_chrs_c & 0xF8) == 0xF0: 546 // characters U-00010000 - U-001FFFFF, mask 11110XXX 547 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 548 $utf8 .= substr($chrs, $c, 4); 549 $c += 3; 550 break; 551 552 case ($ord_chrs_c & 0xFC) == 0xF8: 553 // characters U-00200000 - U-03FFFFFF, mask 111110XX 554 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 555 $utf8 .= substr($chrs, $c, 5); 556 $c += 4; 557 break; 558 559 case ($ord_chrs_c & 0xFE) == 0xFC: 560 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 561 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 562 $utf8 .= substr($chrs, $c, 6); 563 $c += 5; 564 break; 565 566 } 567 568 } 569 570 return $utf8; 571 572 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { 573 // array, or object notation 574 575 if ($str{0} == '[') { 576 $stk = array(SERVICES_JSON_IN_ARR); 577 $arr = array(); 578 } else { 579 if ($this->use == SERVICES_JSON_LOOSE_TYPE) { 580 $stk = array(SERVICES_JSON_IN_OBJ); 581 $obj = array(); 582 } else { 583 $stk = array(SERVICES_JSON_IN_OBJ); 584 $obj = new stdClass(); 585 } 586 } 587 588 array_push($stk, array('what' => SERVICES_JSON_SLICE, 589 'where' => 0, 590 'delim' => false)); 591 592 $chrs = substr($str, 1, -1); 593 $chrs = $this->reduce_string($chrs); 594 595 if ($chrs == '') { 596 if (reset($stk) == SERVICES_JSON_IN_ARR) { 597 return $arr; 598 599 } else { 600 return $obj; 601 602 } 603 } 604 605 //print("\nparsing {$chrs}\n"); 606 607 $strlen_chrs = strlen($chrs); 608 609 for ($c = 0; $c <= $strlen_chrs; ++$c) { 610 611 $top = end($stk); 612 $substr_chrs_c_2 = substr($chrs, $c, 2); 613 614 if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { 615 // found a comma that is not inside a string, array, etc., 616 // OR we've reached the end of the character list 617 $slice = substr($chrs, $top['where'], ($c - $top['where'])); 618 array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); 619 //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 620 621 if (reset($stk) == SERVICES_JSON_IN_ARR) { 622 // we are in an array, so just push an element onto the stack 623 array_push($arr, $this->decode($slice)); 624 625 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 626 // we are in an object, so figure 627 // out the property name and set an 628 // element in an associative array, 629 // for now 630 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 631 // "name":value pair 632 $key = $this->decode($parts[1]); 633 $val = $this->decode($parts[2]); 634 635 if ($this->use == SERVICES_JSON_LOOSE_TYPE) { 636 $obj[$key] = $val; 637 } else { 638 $obj->$key = $val; 639 } 640 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 641 // name:value pair, where name is unquoted 642 $key = $parts[1]; 643 $val = $this->decode($parts[2]); 644 645 if ($this->use == SERVICES_JSON_LOOSE_TYPE) { 646 $obj[$key] = $val; 647 } else { 648 $obj->$key = $val; 649 } 650 } 651 652 } 653 654 } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { 655 // found a quote, and we are not inside a string 656 array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); 657 //print("Found start of string at {$c}\n"); 658 659 } elseif (($chrs{$c} == $top['delim']) && 660 ($top['what'] == SERVICES_JSON_IN_STR) && 661 (($chrs{$c - 1} != '\\') || 662 ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { 663 // found a quote, we're in a string, and it's not escaped 664 array_pop($stk); 665 //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); 666 667 } elseif (($chrs{$c} == '[') && 668 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 669 // found a left-bracket, and we are in an array, object, or slice 670 array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); 671 //print("Found start of array at {$c}\n"); 672 673 } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { 674 // found a right-bracket, and we're in an array 675 array_pop($stk); 676 //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 677 678 } elseif (($chrs{$c} == '{') && 679 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 680 // found a left-brace, and we are in an array, object, or slice 681 array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); 682 //print("Found start of object at {$c}\n"); 683 684 } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { 685 // found a right-brace, and we're in an object 686 array_pop($stk); 687 //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 688 689 } elseif (($substr_chrs_c_2 == '/*') && 690 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 691 // found a comment start, and we are in an array, object, or slice 692 array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); 693 $c++; 694 //print("Found start of comment at {$c}\n"); 695 696 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { 697 // found a comment end, and we're in one now 698 array_pop($stk); 699 $c++; 700 701 for ($i = $top['where']; $i <= $c; ++$i) 702 $chrs = substr_replace($chrs, ' ', $i, 1); 703 704 //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 705 706 } 707 708 } 709 710 if (reset($stk) == SERVICES_JSON_IN_ARR) { 711 return $arr; 712 713 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 714 return $obj; 715 716 } 717 718 } 719 } 720 } 721 722} 723 724?>