Documentation TYPO3 par Ameos |
00001 <?php 00002 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 00003 00062 define('SERVICES_JSON_SLICE', 1); 00063 00067 define('SERVICES_JSON_IN_STR', 2); 00068 00072 define('SERVICES_JSON_IN_ARR', 3); 00073 00077 define('SERVICES_JSON_IN_OBJ', 4); 00078 00082 define('SERVICES_JSON_IN_CMT', 5); 00083 00087 define('SERVICES_JSON_LOOSE_TYPE', 16); 00088 00092 define('SERVICES_JSON_SUPPRESS_ERRORS', 32); 00093 00115 class Services_JSON 00116 { 00133 function Services_JSON($use = 0) 00134 { 00135 $this->use = $use; 00136 } 00137 00149 function utf162utf8($utf16) 00150 { 00151 // oh please oh please oh please oh please oh please 00152 if(function_exists('mb_convert_encoding')) { 00153 return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); 00154 } 00155 00156 $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); 00157 00158 switch(true) { 00159 case ((0x7F & $bytes) == $bytes): 00160 // this case should never be reached, because we are in ASCII range 00161 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00162 return chr(0x7F & $bytes); 00163 00164 case (0x07FF & $bytes) == $bytes: 00165 // return a 2-byte UTF-8 character 00166 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00167 return chr(0xC0 | (($bytes >> 6) & 0x1F)) 00168 . chr(0x80 | ($bytes & 0x3F)); 00169 00170 case (0xFFFF & $bytes) == $bytes: 00171 // return a 3-byte UTF-8 character 00172 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00173 return chr(0xE0 | (($bytes >> 12) & 0x0F)) 00174 . chr(0x80 | (($bytes >> 6) & 0x3F)) 00175 . chr(0x80 | ($bytes & 0x3F)); 00176 } 00177 00178 // ignoring UTF-32 for now, sorry 00179 return ''; 00180 } 00181 00193 function utf82utf16($utf8) 00194 { 00195 // oh please oh please oh please oh please oh please 00196 if(function_exists('mb_convert_encoding')) { 00197 return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 00198 } 00199 00200 switch(strlen($utf8)) { 00201 case 1: 00202 // this case should never be reached, because we are in ASCII range 00203 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00204 return $utf8; 00205 00206 case 2: 00207 // return a UTF-16 character from a 2-byte UTF-8 char 00208 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00209 return chr(0x07 & (ord($utf8{0}) >> 2)) 00210 . chr((0xC0 & (ord($utf8{0}) << 6)) 00211 | (0x3F & ord($utf8{1}))); 00212 00213 case 3: 00214 // return a UTF-16 character from a 3-byte UTF-8 char 00215 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00216 return chr((0xF0 & (ord($utf8{0}) << 4)) 00217 | (0x0F & (ord($utf8{1}) >> 2))) 00218 . chr((0xC0 & (ord($utf8{1}) << 6)) 00219 | (0x7F & ord($utf8{2}))); 00220 } 00221 00222 // ignoring UTF-32 for now, sorry 00223 return ''; 00224 } 00225 00237 function encode($var) 00238 { 00239 switch (gettype($var)) { 00240 case 'boolean': 00241 return $var ? 'true' : 'false'; 00242 00243 case 'NULL': 00244 return 'null'; 00245 00246 case 'integer': 00247 return (int) $var; 00248 00249 case 'double': 00250 case 'float': 00251 return (float) $var; 00252 00253 case 'string': 00254 // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 00255 $ascii = ''; 00256 $strlen_var = strlen($var); 00257 00258 /* 00259 * Iterate over every character in the string, 00260 * escaping with a slash or encoding to UTF-8 where necessary 00261 */ 00262 for ($c = 0; $c < $strlen_var; ++$c) { 00263 00264 $ord_var_c = ord($var{$c}); 00265 00266 switch (true) { 00267 case $ord_var_c == 0x08: 00268 $ascii .= '\b'; 00269 break; 00270 case $ord_var_c == 0x09: 00271 $ascii .= '\t'; 00272 break; 00273 case $ord_var_c == 0x0A: 00274 $ascii .= '\n'; 00275 break; 00276 case $ord_var_c == 0x0C: 00277 $ascii .= '\f'; 00278 break; 00279 case $ord_var_c == 0x0D: 00280 $ascii .= '\r'; 00281 break; 00282 00283 case $ord_var_c == 0x22: 00284 case $ord_var_c == 0x2F: 00285 case $ord_var_c == 0x5C: 00286 // double quote, slash, slosh 00287 $ascii .= '\\'.$var{$c}; 00288 break; 00289 00290 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 00291 // characters U-00000000 - U-0000007F (same as ASCII) 00292 $ascii .= $var{$c}; 00293 break; 00294 00295 case (($ord_var_c & 0xE0) == 0xC0): 00296 // characters U-00000080 - U-000007FF, mask 110XXXXX 00297 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00298 $char = pack('C*', $ord_var_c, ord($var{$c + 1})); 00299 $c += 1; 00300 $utf16 = $this->utf82utf16($char); 00301 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 00302 break; 00303 00304 case (($ord_var_c & 0xF0) == 0xE0): 00305 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 00306 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00307 $char = pack('C*', $ord_var_c, 00308 ord($var{$c + 1}), 00309 ord($var{$c + 2})); 00310 $c += 2; 00311 $utf16 = $this->utf82utf16($char); 00312 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 00313 break; 00314 00315 case (($ord_var_c & 0xF8) == 0xF0): 00316 // characters U-00010000 - U-001FFFFF, mask 11110XXX 00317 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00318 $char = pack('C*', $ord_var_c, 00319 ord($var{$c + 1}), 00320 ord($var{$c + 2}), 00321 ord($var{$c + 3})); 00322 $c += 3; 00323 $utf16 = $this->utf82utf16($char); 00324 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 00325 break; 00326 00327 case (($ord_var_c & 0xFC) == 0xF8): 00328 // characters U-00200000 - U-03FFFFFF, mask 111110XX 00329 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00330 $char = pack('C*', $ord_var_c, 00331 ord($var{$c + 1}), 00332 ord($var{$c + 2}), 00333 ord($var{$c + 3}), 00334 ord($var{$c + 4})); 00335 $c += 4; 00336 $utf16 = $this->utf82utf16($char); 00337 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 00338 break; 00339 00340 case (($ord_var_c & 0xFE) == 0xFC): 00341 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 00342 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00343 $char = pack('C*', $ord_var_c, 00344 ord($var{$c + 1}), 00345 ord($var{$c + 2}), 00346 ord($var{$c + 3}), 00347 ord($var{$c + 4}), 00348 ord($var{$c + 5})); 00349 $c += 5; 00350 $utf16 = $this->utf82utf16($char); 00351 $ascii .= sprintf('\u%04s', bin2hex($utf16)); 00352 break; 00353 } 00354 } 00355 00356 return '"'.$ascii.'"'; 00357 00358 case 'array': 00359 /* 00360 * As per JSON spec if any array key is not an integer 00361 * we must treat the the whole array as an object. We 00362 * also try to catch a sparsely populated associative 00363 * array with numeric keys here because some JS engines 00364 * will create an array with empty indexes up to 00365 * max_index which can cause memory issues and because 00366 * the keys, which may be relevant, will be remapped 00367 * otherwise. 00368 * 00369 * As per the ECMA and JSON specification an object may 00370 * have any string as a property. Unfortunately due to 00371 * a hole in the ECMA specification if the key is a 00372 * ECMA reserved word or starts with a digit the 00373 * parameter is only accessible using ECMAScript's 00374 * bracket notation. 00375 */ 00376 00377 // treat as a JSON object 00378 if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 00379 $properties = array_map(array($this, 'name_value'), 00380 array_keys($var), 00381 array_values($var)); 00382 00383 foreach($properties as $property) { 00384 if(Services_JSON::isError($property)) { 00385 return $property; 00386 } 00387 } 00388 00389 return '{' . join(',', $properties) . '}'; 00390 } 00391 00392 // treat it like a regular array 00393 $elements = array_map(array($this, 'encode'), $var); 00394 00395 foreach($elements as $element) { 00396 if(Services_JSON::isError($element)) { 00397 return $element; 00398 } 00399 } 00400 00401 return '[' . join(',', $elements) . ']'; 00402 00403 case 'object': 00404 $vars = get_object_vars($var); 00405 00406 $properties = array_map(array($this, 'name_value'), 00407 array_keys($vars), 00408 array_values($vars)); 00409 00410 foreach($properties as $property) { 00411 if(Services_JSON::isError($property)) { 00412 return $property; 00413 } 00414 } 00415 00416 return '{' . join(',', $properties) . '}'; 00417 00418 default: 00419 return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) 00420 ? 'null' 00421 : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); 00422 } 00423 } 00424 00434 function name_value($name, $value) 00435 { 00436 $encoded_value = $this->encode($value); 00437 00438 if(Services_JSON::isError($encoded_value)) { 00439 return $encoded_value; 00440 } 00441 00442 return $this->encode(strval($name)) . ':' . $encoded_value; 00443 } 00444 00453 function reduce_string($str) 00454 { 00455 $str = preg_replace(array( 00456 00457 // eliminate single line comments in '// ...' form 00458 '#^\s*//(.+)$#m', 00459 00460 // eliminate multi-line comments in '/* ... */' form, at start of string 00461 '#^\s*/\*(.+)\*/#Us', 00462 00463 // eliminate multi-line comments in '/* ... */' form, at end of string 00464 '#/\*(.+)\*/\s*$#Us' 00465 00466 ), '', $str); 00467 00468 // eliminate extraneous space 00469 return trim($str); 00470 } 00471 00484 function decode($str) 00485 { 00486 $str = $this->reduce_string($str); 00487 00488 switch (strtolower($str)) { 00489 case 'true': 00490 return true; 00491 00492 case 'false': 00493 return false; 00494 00495 case 'null': 00496 return null; 00497 00498 default: 00499 $m = array(); 00500 00501 if (is_numeric($str)) { 00502 // Lookie-loo, it's a number 00503 00504 // This would work on its own, but I'm trying to be 00505 // good about returning integers where appropriate: 00506 // return (float)$str; 00507 00508 // Return float or int, as appropriate 00509 return ((float)$str == (integer)$str) 00510 ? (integer)$str 00511 : (float)$str; 00512 00513 } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { 00514 // STRINGS RETURNED IN UTF-8 FORMAT 00515 $delim = substr($str, 0, 1); 00516 $chrs = substr($str, 1, -1); 00517 $utf8 = ''; 00518 $strlen_chrs = strlen($chrs); 00519 00520 for ($c = 0; $c < $strlen_chrs; ++$c) { 00521 00522 $substr_chrs_c_2 = substr($chrs, $c, 2); 00523 $ord_chrs_c = ord($chrs{$c}); 00524 00525 switch (true) { 00526 case $substr_chrs_c_2 == '\b': 00527 $utf8 .= chr(0x08); 00528 ++$c; 00529 break; 00530 case $substr_chrs_c_2 == '\t': 00531 $utf8 .= chr(0x09); 00532 ++$c; 00533 break; 00534 case $substr_chrs_c_2 == '\n': 00535 $utf8 .= chr(0x0A); 00536 ++$c; 00537 break; 00538 case $substr_chrs_c_2 == '\f': 00539 $utf8 .= chr(0x0C); 00540 ++$c; 00541 break; 00542 case $substr_chrs_c_2 == '\r': 00543 $utf8 .= chr(0x0D); 00544 ++$c; 00545 break; 00546 00547 case $substr_chrs_c_2 == '\\"': 00548 case $substr_chrs_c_2 == '\\\'': 00549 case $substr_chrs_c_2 == '\\\\': 00550 case $substr_chrs_c_2 == '\\/': 00551 if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || 00552 ($delim == "'" && $substr_chrs_c_2 != '\\"')) { 00553 $utf8 .= $chrs{++$c}; 00554 } 00555 break; 00556 00557 case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): 00558 // single, escaped unicode character 00559 $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) 00560 . chr(hexdec(substr($chrs, ($c + 4), 2))); 00561 $utf8 .= $this->utf162utf8($utf16); 00562 $c += 5; 00563 break; 00564 00565 case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): 00566 $utf8 .= $chrs{$c}; 00567 break; 00568 00569 case ($ord_chrs_c & 0xE0) == 0xC0: 00570 // characters U-00000080 - U-000007FF, mask 110XXXXX 00571 //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00572 $utf8 .= substr($chrs, $c, 2); 00573 ++$c; 00574 break; 00575 00576 case ($ord_chrs_c & 0xF0) == 0xE0: 00577 // characters U-00000800 - U-0000FFFF, mask 1110XXXX 00578 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00579 $utf8 .= substr($chrs, $c, 3); 00580 $c += 2; 00581 break; 00582 00583 case ($ord_chrs_c & 0xF8) == 0xF0: 00584 // characters U-00010000 - U-001FFFFF, mask 11110XXX 00585 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00586 $utf8 .= substr($chrs, $c, 4); 00587 $c += 3; 00588 break; 00589 00590 case ($ord_chrs_c & 0xFC) == 0xF8: 00591 // characters U-00200000 - U-03FFFFFF, mask 111110XX 00592 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00593 $utf8 .= substr($chrs, $c, 5); 00594 $c += 4; 00595 break; 00596 00597 case ($ord_chrs_c & 0xFE) == 0xFC: 00598 // characters U-04000000 - U-7FFFFFFF, mask 1111110X 00599 // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 00600 $utf8 .= substr($chrs, $c, 6); 00601 $c += 5; 00602 break; 00603 00604 } 00605 00606 } 00607 00608 return $utf8; 00609 00610 } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { 00611 // array, or object notation 00612 00613 if ($str{0} == '[') { 00614 $stk = array(SERVICES_JSON_IN_ARR); 00615 $arr = array(); 00616 } else { 00617 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 00618 $stk = array(SERVICES_JSON_IN_OBJ); 00619 $obj = array(); 00620 } else { 00621 $stk = array(SERVICES_JSON_IN_OBJ); 00622 $obj = new stdClass(); 00623 } 00624 } 00625 00626 array_push($stk, array('what' => SERVICES_JSON_SLICE, 00627 'where' => 0, 00628 'delim' => false)); 00629 00630 $chrs = substr($str, 1, -1); 00631 $chrs = $this->reduce_string($chrs); 00632 00633 if ($chrs == '') { 00634 if (reset($stk) == SERVICES_JSON_IN_ARR) { 00635 return $arr; 00636 00637 } else { 00638 return $obj; 00639 00640 } 00641 } 00642 00643 //print("\nparsing {$chrs}\n"); 00644 00645 $strlen_chrs = strlen($chrs); 00646 00647 for ($c = 0; $c <= $strlen_chrs; ++$c) { 00648 00649 $top = end($stk); 00650 $substr_chrs_c_2 = substr($chrs, $c, 2); 00651 00652 if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { 00653 // found a comma that is not inside a string, array, etc., 00654 // OR we've reached the end of the character list 00655 $slice = substr($chrs, $top['where'], ($c - $top['where'])); 00656 array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); 00657 //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 00658 00659 if (reset($stk) == SERVICES_JSON_IN_ARR) { 00660 // we are in an array, so just push an element onto the stack 00661 array_push($arr, $this->decode($slice)); 00662 00663 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 00664 // we are in an object, so figure 00665 // out the property name and set an 00666 // element in an associative array, 00667 // for now 00668 $parts = array(); 00669 00670 if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 00671 // "name":value pair 00672 $key = $this->decode($parts[1]); 00673 $val = $this->decode($parts[2]); 00674 00675 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 00676 $obj[$key] = $val; 00677 } else { 00678 $obj->$key = $val; 00679 } 00680 } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { 00681 // name:value pair, where name is unquoted 00682 $key = $parts[1]; 00683 $val = $this->decode($parts[2]); 00684 00685 if ($this->use & SERVICES_JSON_LOOSE_TYPE) { 00686 $obj[$key] = $val; 00687 } else { 00688 $obj->$key = $val; 00689 } 00690 } 00691 00692 } 00693 00694 } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { 00695 // found a quote, and we are not inside a string 00696 array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); 00697 //print("Found start of string at {$c}\n"); 00698 00699 } elseif (($chrs{$c} == $top['delim']) && 00700 ($top['what'] == SERVICES_JSON_IN_STR) && 00701 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { 00702 // found a quote, we're in a string, and it's not escaped 00703 // we know that it's not escaped becase there is _not_ an 00704 // odd number of backslashes at the end of the string so far 00705 array_pop($stk); 00706 //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); 00707 00708 } elseif (($chrs{$c} == '[') && 00709 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 00710 // found a left-bracket, and we are in an array, object, or slice 00711 array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); 00712 //print("Found start of array at {$c}\n"); 00713 00714 } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { 00715 // found a right-bracket, and we're in an array 00716 array_pop($stk); 00717 //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 00718 00719 } elseif (($chrs{$c} == '{') && 00720 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 00721 // found a left-brace, and we are in an array, object, or slice 00722 array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); 00723 //print("Found start of object at {$c}\n"); 00724 00725 } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { 00726 // found a right-brace, and we're in an object 00727 array_pop($stk); 00728 //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 00729 00730 } elseif (($substr_chrs_c_2 == '/*') && 00731 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { 00732 // found a comment start, and we are in an array, object, or slice 00733 array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); 00734 $c++; 00735 //print("Found start of comment at {$c}\n"); 00736 00737 } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { 00738 // found a comment end, and we're in one now 00739 array_pop($stk); 00740 $c++; 00741 00742 for ($i = $top['where']; $i <= $c; ++$i) 00743 $chrs = substr_replace($chrs, ' ', $i, 1); 00744 00745 //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); 00746 00747 } 00748 00749 } 00750 00751 if (reset($stk) == SERVICES_JSON_IN_ARR) { 00752 return $arr; 00753 00754 } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { 00755 return $obj; 00756 00757 } 00758 00759 } 00760 } 00761 } 00762 00766 function isError($data, $code = null) 00767 { 00768 if (class_exists('pear')) { 00769 return PEAR::isError($data, $code); 00770 } elseif (is_object($data) && (get_class($data) == 'services_json_error' || 00771 is_subclass_of($data, 'services_json_error'))) { 00772 return true; 00773 } 00774 00775 return false; 00776 } 00777 } 00778 00779 if (class_exists('PEAR_Error')) { 00780 00781 class Services_JSON_Error extends PEAR_Error 00782 { 00783 function Services_JSON_Error($message = 'unknown error', $code = null, 00784 $mode = null, $options = null, $userinfo = null) 00785 { 00786 parent::PEAR_Error($message, $code, $mode, $options, $userinfo); 00787 } 00788 } 00789 00790 } else { 00791 00795 class Services_JSON_Error 00796 { 00797 function Services_JSON_Error($message = 'unknown error', $code = null, 00798 $mode = null, $options = null, $userinfo = null) 00799 { 00800 00801 } 00802 } 00803 00804 } 00805 00806 ?>