/library/Sopha/Json/Decoder.php
PHP | 462 lines | 286 code | 42 blank | 134 comment | 32 complexity | aeefa8e439c71b33f4b2c9d623058f15 MD5 | raw file
Possible License(s): BSD-3-Clause
- <?php
- /**
- * Sopha - A PHP 5.x Interface to CouchDB
- *
- * LICENSE
- *
- * This source file is subject to the new BSD license that is bundled
- * with this package in the file LICENSE.
- * It is also available through the world-wide-web at this URL:
- * http://prematureoptimization.org/sopha/license/new-bsd
- *
- * If you did not receive a copy of the license and are unable to
- * obtain it through the world-wide-web, please send an email
- * to license@zend.com so we can send you a copy immediately.
- *
- * @package Sopha
- * @subpackage Json
- * @version $Id$
- * @license http://prematureoptimization.org/sopha/license/new-bsd
- */
- /**
- * This code was mostly adapted from Zend_Json - a part of the Zend Framework
- * Copyright (c) 2005-2008 Zend Technologies USA Inc., licensed under the
- * New BSD License. See http://framework.zend.com for more information.
- */
- require_once 'Sopha/Json.php';
- /**
- * Decode JSON encoded string to PHP variable constructs
- *
- */
- class Sopha_Json_Decoder
- {
- /**
- * Parse tokens used to decode the JSON object. These are not
- * for public consumption, they are just used internally to the
- * class.
- */
- const EOF = 0;
- const DATUM = 1;
- const LBRACE = 2;
- const LBRACKET = 3;
- const RBRACE = 4;
- const RBRACKET = 5;
- const COMMA = 6;
- const COLON = 7;
- /**
- * Use to maintain a "pointer" to the source being decoded
- *
- * @var string
- */
- protected $_source;
- /**
- * Caches the source length
- *
- * @var int
- */
- protected $_sourceLength;
- /**
- * The offset within the souce being decoded
- *
- * @var int
- *
- */
- protected $_offset;
- /**
- * The current token being considered in the parser cycle
- *
- * @var int
- */
- protected $_token;
- /**
- * Flag indicating how objects should be decoded
- *
- * @var int
- * @access protected
- */
- protected $_decodeType;
- /**
- * Constructor
- *
- * @param string $source String source to decode
- * @param int $decodeType How objects should be decoded -- see
- * {@link Sopha_Json::TYPE_ARRAY} and {@link Sopha_Json::TYPE_OBJECT} for
- * valid values
- * @return void
- */
- protected function __construct($source, $decodeType)
- {
- // Set defaults
- $this->_source = $source;
- $this->_sourceLength = strlen($source);
- $this->_token = self::EOF;
- $this->_offset = 0;
- // Normalize and set $decodeType
- if (!in_array($decodeType, array(Sopha_Json::TYPE_ARRAY, Sopha_Json::TYPE_OBJECT)))
- {
- $decodeType = Sopha_Json::TYPE_ARRAY;
- }
- $this->_decodeType = $decodeType;
- // Set pointer at first token
- $this->_getNextToken();
- }
- /**
- * Decode a JSON source string
- *
- * Decodes a JSON encoded string. The value returned will be one of the
- * following:
- * - integer
- * - float
- * - boolean
- * - null
- * - StdClass
- * - array
- * - array of one or more of the above types
- *
- * By default, decoded objects will be returned as associative arrays; to
- * return a StdClass object instead, pass {@link Sopha_Json::TYPE_OBJECT} to
- * the $objectDecodeType parameter.
- *
- * Throws a Sopha_Json_Exception if the source string is null.
- *
- * @static
- * @access public
- * @param string $source String to be decoded
- * @param int $objectDecodeType How objects should be decoded; should be
- * either or {@link Sopha_Json::TYPE_ARRAY} or
- * {@link Sopha_Json::TYPE_OBJECT}; defaults to TYPE_ARRAY
- * @return mixed
- * @throws Sopha_Json_Exception
- */
- public static function decode($source = null, $objectDecodeType = Sopha_Json::TYPE_ARRAY)
- {
- if (null === $source) {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception('Must specify JSON encoded source for decoding');
- } elseif (!is_string($source)) {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception('Can only decode JSON encoded strings');
- }
- $decoder = new self($source, $objectDecodeType);
- return $decoder->_decodeValue();
- }
- /**
- * Recursive driving rountine for supported toplevel tops
- *
- * @return mixed
- */
- protected function _decodeValue()
- {
- switch ($this->_token) {
- case self::DATUM:
- $result = $this->_tokenValue;
- $this->_getNextToken();
- return($result);
- break;
- case self::LBRACE:
- return($this->_decodeObject());
- break;
- case self::LBRACKET:
- return($this->_decodeArray());
- break;
- default:
- return null;
- break;
- }
- }
- /**
- * Decodes an object of the form:
- * { "attribute: value, "attribute2" : value,...}
- *
- * If Sopha_Json_Encoder was used to encode the original object then
- * a special attribute called __className which specifies a class
- * name that should wrap the data contained within the encoded source.
- *
- * Decodes to either an array or StdClass object, based on the value of
- * {@link $_decodeType}. If invalid $_decodeType present, returns as an
- * array.
- *
- * @return array|StdClass
- */
- protected function _decodeObject()
- {
- $members = array();
- $tok = $this->_getNextToken();
- while ($tok && $tok != self::RBRACE) {
- if ($tok != self::DATUM || ! is_string($this->_tokenValue)) {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception('Missing key in object encoding: ' . $this->_source);
- }
- $key = $this->_tokenValue;
- $tok = $this->_getNextToken();
- if ($tok != self::COLON) {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception('Missing ":" in object encoding: ' . $this->_source);
- }
- $tok = $this->_getNextToken();
- $members[$key] = $this->_decodeValue();
- $tok = $this->_token;
- if ($tok == self::RBRACE) {
- break;
- }
- if ($tok != self::COMMA) {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception('Missing "," in object encoding: ' . $this->_source);
- }
- $tok = $this->_getNextToken();
- }
- switch ($this->_decodeType) {
- case Sopha_Json::TYPE_OBJECT:
- // Create new StdClass and populate with $members
- $result = new StdClass();
- foreach ($members as $key => $value) {
- $result->$key = $value;
- }
- break;
- case Sopha_Json::TYPE_ARRAY:
- default:
- $result = $members;
- break;
- }
- $this->_getNextToken();
- return $result;
- }
- /**
- * Decodes a JSON array format:
- * [element, element2,...,elementN]
- *
- * @return array
- */
- protected function _decodeArray()
- {
- $result = array();
- $starttok = $tok = $this->_getNextToken(); // Move past the '['
- $index = 0;
- while ($tok && $tok != self::RBRACKET) {
- $result[$index++] = $this->_decodeValue();
- $tok = $this->_token;
- if ($tok == self::RBRACKET || !$tok) {
- break;
- }
- if ($tok != self::COMMA) {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception('Missing "," in array encoding: ' . $this->_source);
- }
- $tok = $this->_getNextToken();
- }
- $this->_getNextToken();
- return($result);
- }
- /**
- * Removes whitepsace characters from the source input
- */
- protected function _eatWhitespace()
- {
- if (preg_match(
- '/([\t\b\f\n\r ])*/s',
- $this->_source,
- $matches,
- PREG_OFFSET_CAPTURE,
- $this->_offset)
- && $matches[0][1] == $this->_offset)
- {
- $this->_offset += strlen($matches[0][0]);
- }
- }
- /**
- * Retrieves the next token from the source stream
- *
- * @return int Token constant value specified in class definition
- */
- protected function _getNextToken()
- {
- $this->_token = self::EOF;
- $this->_tokenValue = null;
- $this->_eatWhitespace();
- if ($this->_offset >= $this->_sourceLength) {
- return(self::EOF);
- }
- $str = $this->_source;
- $str_length = $this->_sourceLength;
- $i = $this->_offset;
- $start = $i;
- switch ($str{$i}) {
- case '{':
- $this->_token = self::LBRACE;
- break;
- case '}':
- $this->_token = self::RBRACE;
- break;
- case '[':
- $this->_token = self::LBRACKET;
- break;
- case ']':
- $this->_token = self::RBRACKET;
- break;
- case ',':
- $this->_token = self::COMMA;
- break;
- case ':':
- $this->_token = self::COLON;
- break;
- case '"':
- $result = '';
- do {
- $i++;
- if ($i >= $str_length) {
- break;
- }
- $chr = $str{$i};
- if ($chr == '\\') {
- $i++;
- if ($i >= $str_length) {
- break;
- }
- $chr = $str{$i};
- switch ($chr) {
- case '"' :
- $result .= '"';
- break;
- case '\\':
- $result .= '\\';
- break;
- case '/' :
- $result .= '/';
- break;
- case 'b' :
- $result .= chr(8);
- break;
- case 'f' :
- $result .= chr(12);
- break;
- case 'n' :
- $result .= chr(10);
- break;
- case 'r' :
- $result .= chr(13);
- break;
- case 't' :
- $result .= chr(9);
- break;
- case '\'' :
- $result .= '\'';
- break;
- default:
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception("Illegal escape "
- . "sequence '" . $chr . "'");
- }
- } elseif ($chr == '"') {
- break;
- } else {
- $result .= $chr;
- }
- } while ($i < $str_length);
- $this->_token = self::DATUM;
- //$this->_tokenValue = substr($str, $start + 1, $i - $start - 1);
- $this->_tokenValue = $result;
- break;
- case 't':
- if (($i+ 3) < $str_length && substr($str, $start, 4) == "true") {
- $this->_token = self::DATUM;
- }
- $this->_tokenValue = true;
- $i += 3;
- break;
- case 'f':
- if (($i+ 4) < $str_length && substr($str, $start, 5) == "false") {
- $this->_token = self::DATUM;
- }
- $this->_tokenValue = false;
- $i += 4;
- break;
- case 'n':
- if (($i+ 3) < $str_length && substr($str, $start, 4) == "null") {
- $this->_token = self::DATUM;
- }
- $this->_tokenValue = NULL;
- $i += 3;
- break;
- }
- if ($this->_token != self::EOF) {
- $this->_offset = $i + 1; // Consume the last token character
- return($this->_token);
- }
- $chr = $str{$i};
- if ($chr == '-' || $chr == '.' || ($chr >= '0' && $chr <= '9')) {
- if (preg_match('/-?([0-9])*(\.[0-9]*)?((e|E)((-|\+)?)[0-9]+)?/s',
- $str, $matches, PREG_OFFSET_CAPTURE, $start) && $matches[0][1] == $start) {
- $datum = $matches[0][0];
- if (is_numeric($datum)) {
- if (preg_match('/^0\d+$/', $datum)) {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception("Octal notation not supported by JSON (value: $datum)");
- } else {
- $val = intval($datum);
- $fVal = floatval($datum);
- $this->_tokenValue = ($val == $fVal ? $val : $fVal);
- }
- } else {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception("Illegal number format: $datum");
- }
- $this->_token = self::DATUM;
- $this->_offset = $start + strlen($datum);
- }
- } else {
- require_once 'Sopha/Json/Exception.php';
- throw new Sopha_Json_Exception('Illegal Token');
- }
- return($this->_token);
- }
- }