miniblog

Unnamed repository; edit this file 'description' to name the repository.
git clone http://git.code.weiherhei.de/miniblog.git
Log | Files | Refs

json_unnessacary.php (34014B)


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