PageRenderTime 23ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Sopha/Json/Decoder.php

http://github.com/shevron/sopha
PHP | 462 lines | 286 code | 42 blank | 134 comment | 32 complexity | aeefa8e439c71b33f4b2c9d623058f15 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Sopha - A PHP 5.x Interface to CouchDB
  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.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://prematureoptimization.org/sopha/license/new-bsd
  11. *
  12. * If you did not receive a copy of the license and are unable to
  13. * obtain it through the world-wide-web, please send an email
  14. * to license@zend.com so we can send you a copy immediately.
  15. *
  16. * @package Sopha
  17. * @subpackage Json
  18. * @version $Id$
  19. * @license http://prematureoptimization.org/sopha/license/new-bsd
  20. */
  21. /**
  22. * This code was mostly adapted from Zend_Json - a part of the Zend Framework
  23. * Copyright (c) 2005-2008 Zend Technologies USA Inc., licensed under the
  24. * New BSD License. See http://framework.zend.com for more information.
  25. */
  26. require_once 'Sopha/Json.php';
  27. /**
  28. * Decode JSON encoded string to PHP variable constructs
  29. *
  30. */
  31. class Sopha_Json_Decoder
  32. {
  33. /**
  34. * Parse tokens used to decode the JSON object. These are not
  35. * for public consumption, they are just used internally to the
  36. * class.
  37. */
  38. const EOF = 0;
  39. const DATUM = 1;
  40. const LBRACE = 2;
  41. const LBRACKET = 3;
  42. const RBRACE = 4;
  43. const RBRACKET = 5;
  44. const COMMA = 6;
  45. const COLON = 7;
  46. /**
  47. * Use to maintain a "pointer" to the source being decoded
  48. *
  49. * @var string
  50. */
  51. protected $_source;
  52. /**
  53. * Caches the source length
  54. *
  55. * @var int
  56. */
  57. protected $_sourceLength;
  58. /**
  59. * The offset within the souce being decoded
  60. *
  61. * @var int
  62. *
  63. */
  64. protected $_offset;
  65. /**
  66. * The current token being considered in the parser cycle
  67. *
  68. * @var int
  69. */
  70. protected $_token;
  71. /**
  72. * Flag indicating how objects should be decoded
  73. *
  74. * @var int
  75. * @access protected
  76. */
  77. protected $_decodeType;
  78. /**
  79. * Constructor
  80. *
  81. * @param string $source String source to decode
  82. * @param int $decodeType How objects should be decoded -- see
  83. * {@link Sopha_Json::TYPE_ARRAY} and {@link Sopha_Json::TYPE_OBJECT} for
  84. * valid values
  85. * @return void
  86. */
  87. protected function __construct($source, $decodeType)
  88. {
  89. // Set defaults
  90. $this->_source = $source;
  91. $this->_sourceLength = strlen($source);
  92. $this->_token = self::EOF;
  93. $this->_offset = 0;
  94. // Normalize and set $decodeType
  95. if (!in_array($decodeType, array(Sopha_Json::TYPE_ARRAY, Sopha_Json::TYPE_OBJECT)))
  96. {
  97. $decodeType = Sopha_Json::TYPE_ARRAY;
  98. }
  99. $this->_decodeType = $decodeType;
  100. // Set pointer at first token
  101. $this->_getNextToken();
  102. }
  103. /**
  104. * Decode a JSON source string
  105. *
  106. * Decodes a JSON encoded string. The value returned will be one of the
  107. * following:
  108. * - integer
  109. * - float
  110. * - boolean
  111. * - null
  112. * - StdClass
  113. * - array
  114. * - array of one or more of the above types
  115. *
  116. * By default, decoded objects will be returned as associative arrays; to
  117. * return a StdClass object instead, pass {@link Sopha_Json::TYPE_OBJECT} to
  118. * the $objectDecodeType parameter.
  119. *
  120. * Throws a Sopha_Json_Exception if the source string is null.
  121. *
  122. * @static
  123. * @access public
  124. * @param string $source String to be decoded
  125. * @param int $objectDecodeType How objects should be decoded; should be
  126. * either or {@link Sopha_Json::TYPE_ARRAY} or
  127. * {@link Sopha_Json::TYPE_OBJECT}; defaults to TYPE_ARRAY
  128. * @return mixed
  129. * @throws Sopha_Json_Exception
  130. */
  131. public static function decode($source = null, $objectDecodeType = Sopha_Json::TYPE_ARRAY)
  132. {
  133. if (null === $source) {
  134. require_once 'Sopha/Json/Exception.php';
  135. throw new Sopha_Json_Exception('Must specify JSON encoded source for decoding');
  136. } elseif (!is_string($source)) {
  137. require_once 'Sopha/Json/Exception.php';
  138. throw new Sopha_Json_Exception('Can only decode JSON encoded strings');
  139. }
  140. $decoder = new self($source, $objectDecodeType);
  141. return $decoder->_decodeValue();
  142. }
  143. /**
  144. * Recursive driving rountine for supported toplevel tops
  145. *
  146. * @return mixed
  147. */
  148. protected function _decodeValue()
  149. {
  150. switch ($this->_token) {
  151. case self::DATUM:
  152. $result = $this->_tokenValue;
  153. $this->_getNextToken();
  154. return($result);
  155. break;
  156. case self::LBRACE:
  157. return($this->_decodeObject());
  158. break;
  159. case self::LBRACKET:
  160. return($this->_decodeArray());
  161. break;
  162. default:
  163. return null;
  164. break;
  165. }
  166. }
  167. /**
  168. * Decodes an object of the form:
  169. * { "attribute: value, "attribute2" : value,...}
  170. *
  171. * If Sopha_Json_Encoder was used to encode the original object then
  172. * a special attribute called __className which specifies a class
  173. * name that should wrap the data contained within the encoded source.
  174. *
  175. * Decodes to either an array or StdClass object, based on the value of
  176. * {@link $_decodeType}. If invalid $_decodeType present, returns as an
  177. * array.
  178. *
  179. * @return array|StdClass
  180. */
  181. protected function _decodeObject()
  182. {
  183. $members = array();
  184. $tok = $this->_getNextToken();
  185. while ($tok && $tok != self::RBRACE) {
  186. if ($tok != self::DATUM || ! is_string($this->_tokenValue)) {
  187. require_once 'Sopha/Json/Exception.php';
  188. throw new Sopha_Json_Exception('Missing key in object encoding: ' . $this->_source);
  189. }
  190. $key = $this->_tokenValue;
  191. $tok = $this->_getNextToken();
  192. if ($tok != self::COLON) {
  193. require_once 'Sopha/Json/Exception.php';
  194. throw new Sopha_Json_Exception('Missing ":" in object encoding: ' . $this->_source);
  195. }
  196. $tok = $this->_getNextToken();
  197. $members[$key] = $this->_decodeValue();
  198. $tok = $this->_token;
  199. if ($tok == self::RBRACE) {
  200. break;
  201. }
  202. if ($tok != self::COMMA) {
  203. require_once 'Sopha/Json/Exception.php';
  204. throw new Sopha_Json_Exception('Missing "," in object encoding: ' . $this->_source);
  205. }
  206. $tok = $this->_getNextToken();
  207. }
  208. switch ($this->_decodeType) {
  209. case Sopha_Json::TYPE_OBJECT:
  210. // Create new StdClass and populate with $members
  211. $result = new StdClass();
  212. foreach ($members as $key => $value) {
  213. $result->$key = $value;
  214. }
  215. break;
  216. case Sopha_Json::TYPE_ARRAY:
  217. default:
  218. $result = $members;
  219. break;
  220. }
  221. $this->_getNextToken();
  222. return $result;
  223. }
  224. /**
  225. * Decodes a JSON array format:
  226. * [element, element2,...,elementN]
  227. *
  228. * @return array
  229. */
  230. protected function _decodeArray()
  231. {
  232. $result = array();
  233. $starttok = $tok = $this->_getNextToken(); // Move past the '['
  234. $index = 0;
  235. while ($tok && $tok != self::RBRACKET) {
  236. $result[$index++] = $this->_decodeValue();
  237. $tok = $this->_token;
  238. if ($tok == self::RBRACKET || !$tok) {
  239. break;
  240. }
  241. if ($tok != self::COMMA) {
  242. require_once 'Sopha/Json/Exception.php';
  243. throw new Sopha_Json_Exception('Missing "," in array encoding: ' . $this->_source);
  244. }
  245. $tok = $this->_getNextToken();
  246. }
  247. $this->_getNextToken();
  248. return($result);
  249. }
  250. /**
  251. * Removes whitepsace characters from the source input
  252. */
  253. protected function _eatWhitespace()
  254. {
  255. if (preg_match(
  256. '/([\t\b\f\n\r ])*/s',
  257. $this->_source,
  258. $matches,
  259. PREG_OFFSET_CAPTURE,
  260. $this->_offset)
  261. && $matches[0][1] == $this->_offset)
  262. {
  263. $this->_offset += strlen($matches[0][0]);
  264. }
  265. }
  266. /**
  267. * Retrieves the next token from the source stream
  268. *
  269. * @return int Token constant value specified in class definition
  270. */
  271. protected function _getNextToken()
  272. {
  273. $this->_token = self::EOF;
  274. $this->_tokenValue = null;
  275. $this->_eatWhitespace();
  276. if ($this->_offset >= $this->_sourceLength) {
  277. return(self::EOF);
  278. }
  279. $str = $this->_source;
  280. $str_length = $this->_sourceLength;
  281. $i = $this->_offset;
  282. $start = $i;
  283. switch ($str{$i}) {
  284. case '{':
  285. $this->_token = self::LBRACE;
  286. break;
  287. case '}':
  288. $this->_token = self::RBRACE;
  289. break;
  290. case '[':
  291. $this->_token = self::LBRACKET;
  292. break;
  293. case ']':
  294. $this->_token = self::RBRACKET;
  295. break;
  296. case ',':
  297. $this->_token = self::COMMA;
  298. break;
  299. case ':':
  300. $this->_token = self::COLON;
  301. break;
  302. case '"':
  303. $result = '';
  304. do {
  305. $i++;
  306. if ($i >= $str_length) {
  307. break;
  308. }
  309. $chr = $str{$i};
  310. if ($chr == '\\') {
  311. $i++;
  312. if ($i >= $str_length) {
  313. break;
  314. }
  315. $chr = $str{$i};
  316. switch ($chr) {
  317. case '"' :
  318. $result .= '"';
  319. break;
  320. case '\\':
  321. $result .= '\\';
  322. break;
  323. case '/' :
  324. $result .= '/';
  325. break;
  326. case 'b' :
  327. $result .= chr(8);
  328. break;
  329. case 'f' :
  330. $result .= chr(12);
  331. break;
  332. case 'n' :
  333. $result .= chr(10);
  334. break;
  335. case 'r' :
  336. $result .= chr(13);
  337. break;
  338. case 't' :
  339. $result .= chr(9);
  340. break;
  341. case '\'' :
  342. $result .= '\'';
  343. break;
  344. default:
  345. require_once 'Sopha/Json/Exception.php';
  346. throw new Sopha_Json_Exception("Illegal escape "
  347. . "sequence '" . $chr . "'");
  348. }
  349. } elseif ($chr == '"') {
  350. break;
  351. } else {
  352. $result .= $chr;
  353. }
  354. } while ($i < $str_length);
  355. $this->_token = self::DATUM;
  356. //$this->_tokenValue = substr($str, $start + 1, $i - $start - 1);
  357. $this->_tokenValue = $result;
  358. break;
  359. case 't':
  360. if (($i+ 3) < $str_length && substr($str, $start, 4) == "true") {
  361. $this->_token = self::DATUM;
  362. }
  363. $this->_tokenValue = true;
  364. $i += 3;
  365. break;
  366. case 'f':
  367. if (($i+ 4) < $str_length && substr($str, $start, 5) == "false") {
  368. $this->_token = self::DATUM;
  369. }
  370. $this->_tokenValue = false;
  371. $i += 4;
  372. break;
  373. case 'n':
  374. if (($i+ 3) < $str_length && substr($str, $start, 4) == "null") {
  375. $this->_token = self::DATUM;
  376. }
  377. $this->_tokenValue = NULL;
  378. $i += 3;
  379. break;
  380. }
  381. if ($this->_token != self::EOF) {
  382. $this->_offset = $i + 1; // Consume the last token character
  383. return($this->_token);
  384. }
  385. $chr = $str{$i};
  386. if ($chr == '-' || $chr == '.' || ($chr >= '0' && $chr <= '9')) {
  387. if (preg_match('/-?([0-9])*(\.[0-9]*)?((e|E)((-|\+)?)[0-9]+)?/s',
  388. $str, $matches, PREG_OFFSET_CAPTURE, $start) && $matches[0][1] == $start) {
  389. $datum = $matches[0][0];
  390. if (is_numeric($datum)) {
  391. if (preg_match('/^0\d+$/', $datum)) {
  392. require_once 'Sopha/Json/Exception.php';
  393. throw new Sopha_Json_Exception("Octal notation not supported by JSON (value: $datum)");
  394. } else {
  395. $val = intval($datum);
  396. $fVal = floatval($datum);
  397. $this->_tokenValue = ($val == $fVal ? $val : $fVal);
  398. }
  399. } else {
  400. require_once 'Sopha/Json/Exception.php';
  401. throw new Sopha_Json_Exception("Illegal number format: $datum");
  402. }
  403. $this->_token = self::DATUM;
  404. $this->_offset = $start + strlen($datum);
  405. }
  406. } else {
  407. require_once 'Sopha/Json/Exception.php';
  408. throw new Sopha_Json_Exception('Illegal Token');
  409. }
  410. return($this->_token);
  411. }
  412. }