Source for file Services_JSON.phpclass

Documentation is available at Services_JSON.phpclass

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

Documentation generated on Thu, 08 Jan 2009 17:48:29 +0100 by phpDocumentor 1.4.0a2