Source for file json.classe.php

Documentation is available at json.classe.php

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

Documentation generated on Sun, 09 Mar 2008 23:52:18 -0300 by phpDocumentor 1.4.0

SourceForge.net Logo Support This Project