Services_JSON : [ class tree ] [ index ] [ all elements ]

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

Documentation generated on Fri, 08 Aug 2008 15:56:04 -0500 by phpDocumentor 1.4.0