web/lib/Zend/Json.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Json
       
    17  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    19  * @version    $Id: Json.php 20615 2010-01-25 19:54:12Z matthew $
       
    20  */
       
    21 
       
    22 /**
       
    23  * Zend_Json_Expr.
       
    24  *
       
    25  * @see Zend_Json_Expr
       
    26  */
       
    27 require_once 'Zend/Json/Expr.php';
       
    28 
       
    29 
       
    30 /**
       
    31  * Class for encoding to and decoding from JSON.
       
    32  *
       
    33  * @category   Zend
       
    34  * @package    Zend_Json
       
    35  * @uses       Zend_Json_Expr
       
    36  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    37  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    38  */
       
    39 class Zend_Json
       
    40 {
       
    41     /**
       
    42      * How objects should be encoded -- arrays or as StdClass. TYPE_ARRAY is 1
       
    43      * so that it is a boolean true value, allowing it to be used with
       
    44      * ext/json's functions.
       
    45      */
       
    46     const TYPE_ARRAY  = 1;
       
    47     const TYPE_OBJECT = 0;
       
    48 
       
    49      /**
       
    50       * To check the allowed nesting depth of the XML tree during xml2json conversion.
       
    51       *
       
    52       * @var int
       
    53       */
       
    54     public static $maxRecursionDepthAllowed=25;
       
    55 
       
    56     /**
       
    57      * @var bool
       
    58      */
       
    59     public static $useBuiltinEncoderDecoder = false;
       
    60 
       
    61     /**
       
    62      * Decodes the given $encodedValue string which is
       
    63      * encoded in the JSON format
       
    64      *
       
    65      * Uses ext/json's json_decode if available.
       
    66      *
       
    67      * @param string $encodedValue Encoded in JSON format
       
    68      * @param int $objectDecodeType Optional; flag indicating how to decode
       
    69      * objects. See {@link Zend_Json_Decoder::decode()} for details.
       
    70      * @return mixed
       
    71      */
       
    72     public static function decode($encodedValue, $objectDecodeType = Zend_Json::TYPE_ARRAY)
       
    73     {
       
    74         $encodedValue = (string) $encodedValue;
       
    75         if (function_exists('json_decode') && self::$useBuiltinEncoderDecoder !== true) {
       
    76             $decode = json_decode($encodedValue, $objectDecodeType);
       
    77 
       
    78             // php < 5.3
       
    79             if (!function_exists('json_last_error')) {
       
    80                 if ($decode === $encodedValue) {
       
    81                     require_once 'Zend/Json/Exception.php';
       
    82                     throw new Zend_Json_Exception('Decoding failed');
       
    83                 }
       
    84             // php >= 5.3
       
    85             } elseif (($jsonLastErr = json_last_error()) != JSON_ERROR_NONE) {
       
    86                 require_once 'Zend/Json/Exception.php';
       
    87                 switch ($jsonLastErr) {
       
    88                     case JSON_ERROR_DEPTH:
       
    89                         throw new Zend_Json_Exception('Decoding failed: Maximum stack depth exceeded');
       
    90                     case JSON_ERROR_CTRL_CHAR:
       
    91                         throw new Zend_Json_Exception('Decoding failed: Unexpected control character found');
       
    92                     case JSON_ERROR_SYNTAX:
       
    93                         throw new Zend_Json_Exception('Decoding failed: Syntax error');
       
    94                     default:
       
    95                         throw new Zend_Json_Exception('Decoding failed');
       
    96                 }
       
    97             }
       
    98 
       
    99             return $decode;
       
   100         }
       
   101 
       
   102         require_once 'Zend/Json/Decoder.php';
       
   103         return Zend_Json_Decoder::decode($encodedValue, $objectDecodeType);
       
   104     }
       
   105 
       
   106     /**
       
   107      * Encode the mixed $valueToEncode into the JSON format
       
   108      *
       
   109      * Encodes using ext/json's json_encode() if available.
       
   110      *
       
   111      * NOTE: Object should not contain cycles; the JSON format
       
   112      * does not allow object reference.
       
   113      *
       
   114      * NOTE: Only public variables will be encoded
       
   115      *
       
   116      * NOTE: Encoding native javascript expressions are possible using Zend_Json_Expr.
       
   117      *       You can enable this by setting $options['enableJsonExprFinder'] = true
       
   118      *
       
   119      * @see Zend_Json_Expr
       
   120      *
       
   121      * @param  mixed $valueToEncode
       
   122      * @param  boolean $cycleCheck Optional; whether or not to check for object recursion; off by default
       
   123      * @param  array $options Additional options used during encoding
       
   124      * @return string JSON encoded object
       
   125      */
       
   126     public static function encode($valueToEncode, $cycleCheck = false, $options = array())
       
   127     {
       
   128         if (is_object($valueToEncode) && method_exists($valueToEncode, 'toJson')) {
       
   129             return $valueToEncode->toJson();
       
   130         }
       
   131 
       
   132         // Pre-encoding look for Zend_Json_Expr objects and replacing by tmp ids
       
   133         $javascriptExpressions = array();
       
   134         if(isset($options['enableJsonExprFinder'])
       
   135            && ($options['enableJsonExprFinder'] == true)
       
   136         ) {
       
   137             /**
       
   138              * @see Zend_Json_Encoder
       
   139              */
       
   140             require_once "Zend/Json/Encoder.php";
       
   141             $valueToEncode = self::_recursiveJsonExprFinder($valueToEncode, $javascriptExpressions);
       
   142         }
       
   143 
       
   144         // Encoding
       
   145         if (function_exists('json_encode') && self::$useBuiltinEncoderDecoder !== true) {
       
   146             $encodedResult = json_encode($valueToEncode);
       
   147         } else {
       
   148             require_once 'Zend/Json/Encoder.php';
       
   149             $encodedResult = Zend_Json_Encoder::encode($valueToEncode, $cycleCheck, $options);
       
   150         }
       
   151 
       
   152         //only do post-proccessing to revert back the Zend_Json_Expr if any.
       
   153         if (count($javascriptExpressions) > 0) {
       
   154             $count = count($javascriptExpressions);
       
   155             for($i = 0; $i < $count; $i++) {
       
   156                 $magicKey = $javascriptExpressions[$i]['magicKey'];
       
   157                 $value    = $javascriptExpressions[$i]['value'];
       
   158 
       
   159                 $encodedResult = str_replace(
       
   160                     //instead of replacing "key:magicKey", we replace directly magicKey by value because "key" never changes.
       
   161                     '"' . $magicKey . '"',
       
   162                     $value,
       
   163                     $encodedResult
       
   164                 );
       
   165             }
       
   166         }
       
   167 
       
   168          return $encodedResult;
       
   169     }
       
   170 
       
   171     /**
       
   172      * Check & Replace Zend_Json_Expr for tmp ids in the valueToEncode
       
   173      *
       
   174      * Check if the value is a Zend_Json_Expr, and if replace its value
       
   175      * with a magic key and save the javascript expression in an array.
       
   176      *
       
   177      * NOTE this method is recursive.
       
   178      *
       
   179      * NOTE: This method is used internally by the encode method.
       
   180      *
       
   181      * @see encode
       
   182      * @param mixed $valueToCheck a string - object property to be encoded
       
   183      * @return void
       
   184      */
       
   185     protected static function _recursiveJsonExprFinder(
       
   186         &$value, array &$javascriptExpressions, $currentKey = null
       
   187     ) {
       
   188          if ($value instanceof Zend_Json_Expr) {
       
   189             // TODO: Optimize with ascii keys, if performance is bad
       
   190             $magicKey = "____" . $currentKey . "_" . (count($javascriptExpressions));
       
   191             $javascriptExpressions[] = array(
       
   192 
       
   193                 //if currentKey is integer, encodeUnicodeString call is not required.
       
   194                 "magicKey" => (is_int($currentKey)) ? $magicKey : Zend_Json_Encoder::encodeUnicodeString($magicKey),
       
   195                 "value"    => $value->__toString(),
       
   196             );
       
   197             $value = $magicKey;
       
   198         } elseif (is_array($value)) {
       
   199             foreach ($value as $k => $v) {
       
   200                 $value[$k] = self::_recursiveJsonExprFinder($value[$k], $javascriptExpressions, $k);
       
   201             }
       
   202         } elseif (is_object($value)) {
       
   203             foreach ($value as $k => $v) {
       
   204                 $value->$k = self::_recursiveJsonExprFinder($value->$k, $javascriptExpressions, $k);
       
   205             }
       
   206         }
       
   207         return $value;
       
   208     }
       
   209 
       
   210     /**
       
   211      * fromXml - Converts XML to JSON
       
   212      *
       
   213      * Converts a XML formatted string into a JSON formatted string.
       
   214      * The value returned will be a string in JSON format.
       
   215      *
       
   216      * The caller of this function needs to provide only the first parameter,
       
   217      * which is an XML formatted String. The second parameter is optional, which
       
   218      * lets the user to select if the XML attributes in the input XML string
       
   219      * should be included or ignored in xml2json conversion.
       
   220      *
       
   221      * This function converts the XML formatted string into a PHP array by
       
   222      * calling a recursive (protected static) function in this class. Then, it
       
   223      * converts that PHP array into JSON by calling the "encode" static funcion.
       
   224      *
       
   225      * Throws a Zend_Json_Exception if the input not a XML formatted string.
       
   226      * NOTE: Encoding native javascript expressions via Zend_Json_Expr is not possible.
       
   227      *
       
   228      * @static
       
   229      * @access public
       
   230      * @param string $xmlStringContents XML String to be converted
       
   231      * @param boolean $ignoreXmlAttributes Include or exclude XML attributes in
       
   232      * the xml2json conversion process.
       
   233      * @return mixed - JSON formatted string on success
       
   234      * @throws Zend_Json_Exception
       
   235      */
       
   236     public static function fromXml ($xmlStringContents, $ignoreXmlAttributes=true) {
       
   237         // Load the XML formatted string into a Simple XML Element object.
       
   238         $simpleXmlElementObject = simplexml_load_string($xmlStringContents);
       
   239 
       
   240         // If it is not a valid XML content, throw an exception.
       
   241         if ($simpleXmlElementObject == null) {
       
   242             require_once 'Zend/Json/Exception.php';
       
   243             throw new Zend_Json_Exception('Function fromXml was called with an invalid XML formatted string.');
       
   244         } // End of if ($simpleXmlElementObject == null)
       
   245 
       
   246         $resultArray = null;
       
   247 
       
   248         // Call the recursive function to convert the XML into a PHP array.
       
   249         $resultArray = self::_processXml($simpleXmlElementObject, $ignoreXmlAttributes);
       
   250 
       
   251         // Convert the PHP array to JSON using Zend_Json encode method.
       
   252         // It is just that simple.
       
   253         $jsonStringOutput = self::encode($resultArray);
       
   254         return($jsonStringOutput);
       
   255     } // End of function fromXml.
       
   256 
       
   257     /**
       
   258      * _processXml - Contains the logic for xml2json
       
   259      *
       
   260      * The logic in this function is a recursive one.
       
   261      *
       
   262      * The main caller of this function (i.e. fromXml) needs to provide
       
   263      * only the first two parameters i.e. the SimpleXMLElement object and
       
   264      * the flag for ignoring or not ignoring XML attributes. The third parameter
       
   265      * will be used internally within this function during the recursive calls.
       
   266      *
       
   267      * This function converts the SimpleXMLElement object into a PHP array by
       
   268      * calling a recursive (protected static) function in this class. Once all
       
   269      * the XML elements are stored in the PHP array, it is returned to the caller.
       
   270      *
       
   271      * Throws a Zend_Json_Exception if the XML tree is deeper than the allowed limit.
       
   272      *
       
   273      * @static
       
   274      * @access protected
       
   275      * @param SimpleXMLElement $simpleXmlElementObject XML element to be converted
       
   276      * @param boolean $ignoreXmlAttributes Include or exclude XML attributes in
       
   277      * the xml2json conversion process.
       
   278      * @param int $recursionDepth Current recursion depth of this function
       
   279      * @return mixed - On success, a PHP associative array of traversed XML elements
       
   280      * @throws Zend_Json_Exception
       
   281      */
       
   282     protected static function _processXml ($simpleXmlElementObject, $ignoreXmlAttributes, $recursionDepth=0) {
       
   283         // Keep an eye on how deeply we are involved in recursion.
       
   284         if ($recursionDepth > self::$maxRecursionDepthAllowed) {
       
   285             // XML tree is too deep. Exit now by throwing an exception.
       
   286             require_once 'Zend/Json/Exception.php';
       
   287             throw new Zend_Json_Exception(
       
   288                 "Function _processXml exceeded the allowed recursion depth of " .
       
   289                 self::$maxRecursionDepthAllowed);
       
   290         } // End of if ($recursionDepth > self::$maxRecursionDepthAllowed)
       
   291 
       
   292         if ($recursionDepth == 0) {
       
   293             // Store the original SimpleXmlElementObject sent by the caller.
       
   294             // We will need it at the very end when we return from here for good.
       
   295             $callerProvidedSimpleXmlElementObject = $simpleXmlElementObject;
       
   296         } // End of if ($recursionDepth == 0)
       
   297 
       
   298         if ($simpleXmlElementObject instanceof SimpleXMLElement) {
       
   299             // Get a copy of the simpleXmlElementObject
       
   300             $copyOfSimpleXmlElementObject = $simpleXmlElementObject;
       
   301             // Get the object variables in the SimpleXmlElement object for us to iterate.
       
   302             $simpleXmlElementObject = get_object_vars($simpleXmlElementObject);
       
   303         } // End of if (get_class($simpleXmlElementObject) == "SimpleXMLElement")
       
   304 
       
   305         // It needs to be an array of object variables.
       
   306         if (is_array($simpleXmlElementObject)) {
       
   307             // Initialize a result array.
       
   308             $resultArray = array();
       
   309             // Is the input array size 0? Then, we reached the rare CDATA text if any.
       
   310             if (count($simpleXmlElementObject) <= 0) {
       
   311                 // Let us return the lonely CDATA. It could even be
       
   312                 // an empty element or just filled with whitespaces.
       
   313                 return (trim(strval($copyOfSimpleXmlElementObject)));
       
   314             } // End of if (count($simpleXmlElementObject) <= 0)
       
   315 
       
   316             // Let us walk through the child elements now.
       
   317             foreach($simpleXmlElementObject as $key=>$value) {
       
   318                 // Check if we need to ignore the XML attributes.
       
   319                 // If yes, you can skip processing the XML attributes.
       
   320                 // Otherwise, add the XML attributes to the result array.
       
   321                 if(($ignoreXmlAttributes == true) && (is_string($key)) && ($key == "@attributes")) {
       
   322                     continue;
       
   323                 } // End of if(($ignoreXmlAttributes == true) && ($key == "@attributes"))
       
   324 
       
   325                 // Let us recursively process the current XML element we just visited.
       
   326                 // Increase the recursion depth by one.
       
   327                 $recursionDepth++;
       
   328                 $resultArray[$key] = self::_processXml ($value, $ignoreXmlAttributes, $recursionDepth);
       
   329 
       
   330                 // Decrease the recursion depth by one.
       
   331                 $recursionDepth--;
       
   332             } // End of foreach($simpleXmlElementObject as $key=>$value) {
       
   333 
       
   334             if ($recursionDepth == 0) {
       
   335                 // That is it. We are heading to the exit now.
       
   336                 // Set the XML root element name as the root [top-level] key of
       
   337                 // the associative array that we are going to return to the original
       
   338                 // caller of this recursive function.
       
   339                 $tempArray = $resultArray;
       
   340                 $resultArray = array();
       
   341                 $resultArray[$callerProvidedSimpleXmlElementObject->getName()] = $tempArray;
       
   342             } // End of if ($recursionDepth == 0)
       
   343 
       
   344             return($resultArray);
       
   345         } else {
       
   346             // We are now looking at either the XML attribute text or
       
   347             // the text between the XML tags.
       
   348 
       
   349             // In order to allow Zend_Json_Expr from xml, we check if the node
       
   350             // matchs the pattern that try to detect if it is a new Zend_Json_Expr
       
   351             // if it matches, we return a new Zend_Json_Expr instead of a text node
       
   352             $pattern = '/^[\s]*new Zend_Json_Expr[\s]*\([\s]*[\"\']{1}(.*)[\"\']{1}[\s]*\)[\s]*$/';
       
   353             $matchings = array();
       
   354             $match = preg_match ($pattern, $simpleXmlElementObject, $matchings);
       
   355             if ($match) {
       
   356                 return new Zend_Json_Expr($matchings[1]);
       
   357             } else {
       
   358                 return (trim(strval($simpleXmlElementObject)));
       
   359             }
       
   360 
       
   361         } // End of if (is_array($simpleXmlElementObject))
       
   362     } // End of function _processXml.
       
   363     
       
   364     /**
       
   365      * Pretty-print JSON string
       
   366      * 
       
   367      * Use 'indent' option to select indentation string - by default it's a tab
       
   368      * 
       
   369      * @param string $json Original JSON string
       
   370      * @param array $options Encoding options
       
   371      * @return string
       
   372      */
       
   373     public static function prettyPrint($json, $options = array())
       
   374     {
       
   375         $tokens = preg_split('|([\{\}\]\[,])|', $json, -1, PREG_SPLIT_DELIM_CAPTURE);
       
   376         $result = "";
       
   377         $indent = 0;
       
   378         
       
   379         $ind = "\t";
       
   380         if(isset($options['indent'])) {
       
   381             $ind = $options['indent'];
       
   382         }
       
   383         
       
   384         foreach($tokens as $token) {
       
   385             if($token == "") continue;
       
   386             
       
   387             $prefix = str_repeat($ind, $indent);
       
   388             if($token == "{" || $token == "[") {
       
   389                 $indent++;
       
   390                 if($result != "" && $result[strlen($result)-1] == "\n") {
       
   391                     $result .= $prefix;
       
   392                 }
       
   393                 $result .= "$token\n";
       
   394             } else if($token == "}" || $token == "]") {
       
   395                 $indent--;
       
   396                 $prefix = str_repeat($ind, $indent);
       
   397                 $result .= "\n$prefix$token";                
       
   398             } else if($token == ",") {
       
   399                 $result .= "$token\n";
       
   400             } else {
       
   401                 $result .= $prefix.$token;
       
   402             }
       
   403         }
       
   404         return $result;
       
   405    }
       
   406 }