Source for file JSON.php

Documentation is available at JSON.php

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

Documentation generated on Wed, 09 Feb 2011 09:01:47 +0700 by phpDocumentor 1.4.2