PageRenderTime 43ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/classphp/flourish/fJSON.php

https://github.com/jsuarez/Lexer
PHP | 711 lines | 406 code | 93 blank | 212 comment | 121 complexity | bf2ade9ec5b3eb7e2197ab191bb21f61 MD5 | raw file
  1. <?php
  2. /**
  3. * Provides encoding and decoding for JSON
  4. *
  5. * This class is a compatibility class for the
  6. * [http://php.net/json json extension] on servers with PHP 5.0 or 5.1, or
  7. * servers with the json extension compiled out.
  8. *
  9. * This class will handle JSON values that are not contained in an array or
  10. * object - such values are not valid according to the JSON spec, but the
  11. * functionality is included for compatiblity with the json extension.
  12. *
  13. * @copyright Copyright (c) 2008-2009 Will Bond
  14. * @author Will Bond [wb] <will@flourishlib.com>
  15. * @license http://flourishlib.com/license
  16. *
  17. * @package Flourish
  18. * @link http://flourishlib.com/fJSON
  19. *
  20. * @version 1.0.0b4
  21. * @changes 1.0.0b4 Fixed a bug with ::decode() where JSON objects could lose all but the first key: value pair [wb, 2009-05-06]
  22. * @changes 1.0.0b3 Updated the class to be consistent with PHP 5.2.9+ for encoding and decoding invalid data [wb, 2009-05-04]
  23. * @changes 1.0.0b2 Changed @ error suppression operator to `error_reporting()` calls [wb, 2009-01-26]
  24. * @changes 1.0.0b The initial implementation [wb, 2008-07-12]
  25. */
  26. class fJSON
  27. {
  28. // The following constants allow for nice looking callbacks to static methods
  29. const decode = 'fJSON::decode';
  30. const encode = 'fJSON::encode';
  31. const sendHeader = 'fJSON::sendHeader';
  32. /**
  33. * An abstract representation of [
  34. *
  35. * @internal
  36. *
  37. * @var integer
  38. */
  39. const J_ARRAY_OPEN = 0;
  40. /**
  41. * An abstract representation of , in a JSON array
  42. *
  43. * @internal
  44. *
  45. * @var integer
  46. */
  47. const J_ARRAY_COMMA = 1;
  48. /**
  49. * An abstract representation of ]
  50. *
  51. * @internal
  52. *
  53. * @var integer
  54. */
  55. const J_ARRAY_CLOSE = 2;
  56. /**
  57. * An abstract representation of {
  58. *
  59. * @internal
  60. *
  61. * @var integer
  62. */
  63. const J_OBJ_OPEN = 3;
  64. /**
  65. * An abstract representation of a JSON object key
  66. *
  67. * @internal
  68. *
  69. * @var integer
  70. */
  71. const J_KEY = 4;
  72. /**
  73. * An abstract representation of :
  74. *
  75. * @internal
  76. *
  77. * @var integer
  78. */
  79. const J_COLON = 5;
  80. /**
  81. * An abstract representation of , in a JSON object
  82. *
  83. * @internal
  84. *
  85. * @var integer
  86. */
  87. const J_OBJ_COMMA = 6;
  88. /**
  89. * An abstract representation of }
  90. *
  91. * @internal
  92. *
  93. * @var integer
  94. */
  95. const J_OBJ_CLOSE = 7;
  96. /**
  97. * An abstract representation of an integer
  98. *
  99. * @internal
  100. *
  101. * @var integer
  102. */
  103. const J_INTEGER = 8;
  104. /**
  105. * An abstract representation of a floating value
  106. *
  107. * @internal
  108. *
  109. * @var integer
  110. */
  111. const J_FLOAT = 9;
  112. /**
  113. * An abstract representation of a boolean true
  114. *
  115. * @internal
  116. *
  117. * @var integer
  118. */
  119. const J_TRUE = 10;
  120. /**
  121. * An abstract representation of a boolean false
  122. *
  123. * @internal
  124. *
  125. * @var integer
  126. */
  127. const J_FALSE = 11;
  128. /**
  129. * An abstract representation of null
  130. *
  131. * @internal
  132. *
  133. * @var integer
  134. */
  135. const J_NULL = 12;
  136. /**
  137. * An abstract representation of a string
  138. *
  139. * @internal
  140. *
  141. * @var integer
  142. */
  143. const J_STRING = 13;
  144. /**
  145. * An array of special characters in JSON strings
  146. *
  147. * @var array
  148. */
  149. static private $control_character_map = array(
  150. '"' => '\"', '\\' => '\\\\', '/' => '\/', "\x8" => '\b',
  151. "\xC" => '\f', "\n" => '\n', "\r" => '\r', "\t" => '\t'
  152. );
  153. /**
  154. * An array of what values are allowed after other values
  155. *
  156. * @internal
  157. *
  158. * @var array
  159. */
  160. static private $next_values = array(
  161. self::J_ARRAY_OPEN => array(
  162. self::J_ARRAY_OPEN => TRUE,
  163. self::J_ARRAY_CLOSE => TRUE,
  164. self::J_OBJ_OPEN => TRUE,
  165. self::J_INTEGER => TRUE,
  166. self::J_FLOAT => TRUE,
  167. self::J_TRUE => TRUE,
  168. self::J_FALSE => TRUE,
  169. self::J_NULL => TRUE,
  170. self::J_STRING => TRUE
  171. ),
  172. self::J_ARRAY_COMMA => array(
  173. self::J_ARRAY_OPEN => TRUE,
  174. self::J_OBJ_OPEN => TRUE,
  175. self::J_INTEGER => TRUE,
  176. self::J_FLOAT => TRUE,
  177. self::J_TRUE => TRUE,
  178. self::J_FALSE => TRUE,
  179. self::J_NULL => TRUE,
  180. self::J_STRING => TRUE
  181. ),
  182. self::J_ARRAY_CLOSE => array(
  183. self::J_ARRAY_CLOSE => TRUE,
  184. self::J_OBJ_CLOSE => TRUE,
  185. self::J_ARRAY_COMMA => TRUE,
  186. self::J_OBJ_COMMA => TRUE
  187. ),
  188. self::J_OBJ_OPEN => array(
  189. self::J_OBJ_CLOSE => TRUE,
  190. self::J_KEY => TRUE
  191. ),
  192. self::J_KEY => array(
  193. self::J_COLON => TRUE
  194. ),
  195. self::J_OBJ_COMMA => array(
  196. self::J_KEY => TRUE
  197. ),
  198. self::J_COLON => array(
  199. self::J_ARRAY_OPEN => TRUE,
  200. self::J_OBJ_OPEN => TRUE,
  201. self::J_INTEGER => TRUE,
  202. self::J_FLOAT => TRUE,
  203. self::J_TRUE => TRUE,
  204. self::J_FALSE => TRUE,
  205. self::J_NULL => TRUE,
  206. self::J_STRING => TRUE
  207. ),
  208. self::J_OBJ_CLOSE => array(
  209. self::J_ARRAY_CLOSE => TRUE,
  210. self::J_OBJ_CLOSE => TRUE,
  211. self::J_ARRAY_COMMA => TRUE,
  212. self::J_OBJ_COMMA => TRUE
  213. ),
  214. self::J_INTEGER => array(
  215. self::J_ARRAY_CLOSE => TRUE,
  216. self::J_OBJ_CLOSE => TRUE,
  217. self::J_ARRAY_COMMA => TRUE,
  218. self::J_OBJ_COMMA => TRUE
  219. ),
  220. self::J_FLOAT => array(
  221. self::J_ARRAY_CLOSE => TRUE,
  222. self::J_OBJ_CLOSE => TRUE,
  223. self::J_ARRAY_COMMA => TRUE,
  224. self::J_OBJ_COMMA => TRUE
  225. ),
  226. self::J_TRUE => array(
  227. self::J_ARRAY_CLOSE => TRUE,
  228. self::J_OBJ_CLOSE => TRUE,
  229. self::J_ARRAY_COMMA => TRUE,
  230. self::J_OBJ_COMMA => TRUE
  231. ),
  232. self::J_FALSE => array(
  233. self::J_ARRAY_CLOSE => TRUE,
  234. self::J_OBJ_CLOSE => TRUE,
  235. self::J_ARRAY_COMMA => TRUE,
  236. self::J_OBJ_COMMA => TRUE
  237. ),
  238. self::J_NULL => array(
  239. self::J_ARRAY_CLOSE => TRUE,
  240. self::J_OBJ_CLOSE => TRUE,
  241. self::J_ARRAY_COMMA => TRUE,
  242. self::J_OBJ_COMMA => TRUE
  243. ),
  244. self::J_STRING => array(
  245. self::J_ARRAY_CLOSE => TRUE,
  246. self::J_OBJ_CLOSE => TRUE,
  247. self::J_ARRAY_COMMA => TRUE,
  248. self::J_OBJ_COMMA => TRUE
  249. )
  250. );
  251. /**
  252. * Decodes a JSON string into native PHP data types
  253. *
  254. * This function is very strict about the format of JSON. If the string is
  255. * not a valid JSON string, `NULL` will be returned.
  256. *
  257. * @param string $json This should be the name of a related class
  258. * @param boolean $assoc If this is TRUE, JSON objects will be represented as an assocative array instead of a `stdClass` object
  259. * @return array|stdClass A PHP equivalent of the JSON string
  260. */
  261. static public function decode($json, $assoc=FALSE)
  262. {
  263. if (!is_string($json) && !is_numeric($json)) {
  264. return NULL;
  265. }
  266. $json = trim($json);
  267. if ($json === '') {
  268. return NULL;
  269. }
  270. // If the json is an array or object, we can rely on the php function
  271. if (function_exists('json_decode') && ($json[0] == '[' || $json[0] == '{' || version_compare(PHP_VERSION, '5.2.9', '>='))) {
  272. return json_decode($json, $assoc);
  273. }
  274. preg_match_all('~\[| # Array begin
  275. \]| # Array end
  276. {| # Object begin
  277. }| # Object end
  278. -?(?:0|[1-9]\d*) # Float
  279. (?:\.\d*(?:[eE][+\-]?\d+)?|
  280. (?:[eE][+\-]?\d+))|
  281. -?(?:0|[1-9]\d*)| # Integer
  282. true| # True
  283. false| # False
  284. null| # Null
  285. ,| # Member separator for arrays and objects
  286. :| # Value separator for objects
  287. "(?:(?:(?!\\\\u)[^\\\\"\n\b\f\r\t]+)| # String
  288. \\\\\\\\|
  289. \\\\/|
  290. \\\\"|
  291. \\\\b|
  292. \\\\f|
  293. \\\\n|
  294. \\\\r|
  295. \\\\t|
  296. \\\\u[0-9a-fA-F]{4})*"|
  297. \s+ # Whitespace
  298. ~x', $json, $matches);
  299. $matched_length = 0;
  300. $stack = array();
  301. $last = NULL;
  302. $last_key = NULL;
  303. $output = NULL;
  304. $container = NULL;
  305. if (sizeof($matches) == 1 && strlen($matches[0][0]) == strlen($json)) {
  306. $match = $matches[0][0];
  307. $stack = array();
  308. $type = self::getElementType($stack, self::J_ARRAY_OPEN, $match);
  309. $element = self::scalarize($type, $match);
  310. if ($match !== $element) {
  311. return $element;
  312. }
  313. }
  314. if ($json[0] != '[' && $json[0] != '{') {
  315. return NULL;
  316. }
  317. foreach ($matches[0] as $match) {
  318. if ($matched_length == 0) {
  319. if ($match == '[') {
  320. $output = array();
  321. $last = self::J_ARRAY_OPEN;
  322. } else {
  323. $output = ($assoc) ? array() : new stdClass();
  324. $last = self::J_OBJ_OPEN;
  325. }
  326. $stack[] = array($last, &$output);
  327. $container =& $output;
  328. $matched_length = 1;
  329. continue;
  330. }
  331. $matched_length += strlen($match);
  332. // Whitespace can be skipped over
  333. if (ctype_space($match)) {
  334. continue;
  335. }
  336. $type = self::getElementType($stack, $last, $match);
  337. // An invalid sequence will cause parsing to stop
  338. if (!isset(self::$next_values[$last][$type])) {
  339. break;
  340. }
  341. // Decode the data values
  342. $match = self::scalarize($type, $match);
  343. // If the element is not a value, record some info and continue
  344. if ($type == self::J_COLON ||
  345. $type == self::J_OBJ_COMMA ||
  346. $type == self::J_ARRAY_COMMA ||
  347. $type == self::J_KEY) {
  348. $last = $type;
  349. if ($type == self::J_KEY) {
  350. $last_key = $match;
  351. }
  352. continue;
  353. }
  354. // This flag is used to indicate if an array or object is being added and thus
  355. // if the container reference needs to be changed to the current match
  356. $ref_match = FALSE;
  357. // Closing an object or array
  358. if ($type == self::J_OBJ_CLOSE || $type == self::J_ARRAY_CLOSE) {
  359. array_pop($stack);
  360. if (sizeof($stack) == 0) {
  361. break;
  362. }
  363. $new_container = end($stack);
  364. $container =& $new_container[1];
  365. $last = $type;
  366. continue;
  367. }
  368. // Opening a new object or array requires some references to keep
  369. // track of what the current container is
  370. if ($type == self::J_OBJ_OPEN) {
  371. $match = ($assoc) ? array() : new stdClass();
  372. $ref_match = TRUE;
  373. }
  374. if ($type == self::J_ARRAY_OPEN) {
  375. $match = array();
  376. $ref_match = TRUE;
  377. }
  378. if ($ref_match) {
  379. $stack[] = array($type, &$match);
  380. $stack_end = end($stack);
  381. }
  382. // Here we assign the value. This code is kind of crazy because
  383. // we have to keep track of the current container by references
  384. // so we can traverse back down the stack as we move out of
  385. // nested arrays and objects
  386. if ($last == self::J_COLON && !$assoc) {
  387. if ($last_key == '') {
  388. $last_key = '_empty_';
  389. }
  390. if ($ref_match) {
  391. $container->$last_key =& $stack_end[1];
  392. $container =& $stack_end[1];
  393. } else {
  394. $container->$last_key = $match;
  395. }
  396. } elseif ($last == self::J_COLON) {
  397. if ($ref_match) {
  398. $container[$last_key] =& $stack_end[1];
  399. $container =& $stack_end[1];
  400. } else {
  401. $container[$last_key] = $match;
  402. }
  403. } else {
  404. if ($ref_match) {
  405. $container[] =& $stack_end[1];
  406. $container =& $stack_end[1];
  407. } else {
  408. $container[] = $match;
  409. }
  410. }
  411. if ($last == self::J_COLON) {
  412. $last_key = NULL;
  413. }
  414. $last = $type;
  415. unset($match);
  416. }
  417. if ($matched_length != strlen($json) || sizeof($stack) > 0) {
  418. return NULL;
  419. }
  420. return $output;
  421. }
  422. /**
  423. * Encodes a PHP value into a JSON string
  424. *
  425. * @param mixed $value The PHP value to encode
  426. * @return string The JSON string that is equivalent to the PHP value
  427. */
  428. static public function encode($value)
  429. {
  430. if (is_resource($value)) {
  431. return 'null';
  432. }
  433. if (function_exists('json_encode')) {
  434. return json_encode($value);
  435. }
  436. if (is_int($value)) {
  437. return (string) $value;
  438. }
  439. if (is_float($value)) {
  440. return (string) $value;
  441. }
  442. if (is_bool($value)) {
  443. return ($value) ? 'true' : 'false';
  444. }
  445. if (is_null($value)) {
  446. return 'null';
  447. }
  448. if (is_string($value)) {
  449. if (!preg_match('#^.*$#usD', $value)) {
  450. return 'null';
  451. }
  452. $char_array = fUTF8::explode($value);
  453. $output = '"';
  454. foreach ($char_array as $char) {
  455. if (isset(self::$control_character_map[$char])) {
  456. $output .= self::$control_character_map[$char];
  457. } elseif (strlen($char) < 2) {
  458. $output .= $char;
  459. } else {
  460. $output .= '\u' . substr(strtolower(fUTF8::ord($char)), 2);
  461. }
  462. }
  463. $output .= '"';
  464. return $output;
  465. }
  466. // Detect if an array is associative, which would mean it needs to be encoded as an object
  467. $is_assoc_array = FALSE;
  468. if (is_array($value) && $value) {
  469. $looking_for = 0;
  470. foreach ($value as $key => $val) {
  471. if (!is_numeric($key) || $key != $looking_for) {
  472. $is_assoc_array = TRUE;
  473. break;
  474. }
  475. $looking_for++;
  476. }
  477. }
  478. if (is_object($value) || $is_assoc_array) {
  479. $output = '{';
  480. $members = array();
  481. foreach ($value as $key => $val) {
  482. $members[] = self::encode((string) $key) . ':' . self::encode($val);
  483. }
  484. $output .= join(',', $members);
  485. $output .= '}';
  486. return $output;
  487. }
  488. if (is_array($value)) {
  489. $output = '[';
  490. $members = array();
  491. foreach ($value as $key => $val) {
  492. $members[] = self::encode($val);
  493. }
  494. $output .= join(',', $members);
  495. $output .= ']';
  496. return $output;
  497. }
  498. }
  499. /**
  500. * Determines the type of a parser JSON element
  501. *
  502. * @param array &$stack The stack of arrays/objects being parsed
  503. * @param integer $last The type of the last element parsed
  504. * @param string $element The element being detected
  505. * @return integer The element type
  506. */
  507. static private function getElementType(&$stack, $last, $element)
  508. {
  509. if ($element == '[') {
  510. return self::J_ARRAY_OPEN;
  511. }
  512. if ($element == ']') {
  513. return self::J_ARRAY_CLOSE;
  514. }
  515. if ($element == '{') {
  516. return self::J_OBJ_OPEN;
  517. }
  518. if ($element == '}') {
  519. return self::J_OBJ_CLOSE;
  520. }
  521. if (ctype_digit($element)) {
  522. return self::J_INTEGER;
  523. }
  524. if (is_numeric($element)) {
  525. return self::J_FLOAT;
  526. }
  527. if ($element == 'true') {
  528. return self::J_TRUE;
  529. }
  530. if ($element == 'false') {
  531. return self::J_FALSE;
  532. }
  533. if ($element == 'null') {
  534. return self::J_NULL;
  535. }
  536. $last_stack = end($stack);
  537. if ($element == ',' && $last_stack[0] == self::J_ARRAY_OPEN) {
  538. return self::J_ARRAY_COMMA;
  539. }
  540. if ($element == ',') {
  541. return self::J_OBJ_COMMA;
  542. }
  543. if ($element == ':') {
  544. return self::J_COLON;
  545. }
  546. if ($last == self::J_OBJ_OPEN || $last == self::J_OBJ_COMMA) {
  547. return self::J_KEY;
  548. }
  549. return self::J_STRING;
  550. }
  551. /**
  552. * Decodes a scalar value
  553. *
  554. * @param integer $type The type of the element
  555. * @param string $element The element to be converted to a scalar
  556. * @return mixed The scalar value or the original string of the element
  557. */
  558. static private function scalarize($type, $element)
  559. {
  560. if ($type == self::J_INTEGER) {
  561. $element = (integer) $element;
  562. }
  563. if ($type == self::J_FLOAT) {
  564. $element = (float) $element;
  565. }
  566. if ($type == self::J_FALSE) {
  567. $element = FALSE;
  568. }
  569. if ($type == self::J_TRUE) {
  570. $element = TRUE;
  571. }
  572. if ($type == self::J_NULL) {
  573. $element = NULL;
  574. }
  575. if ($type == self::J_STRING || $type == self::J_KEY) {
  576. $element = substr($element, 1, -1);
  577. $element = strtr($element, array_flip(self::$control_character_map));
  578. $element = preg_replace('#\\\\u([0-9a-fA-F]{4})#e', 'fUTF8::chr("U+\1")', $element);
  579. }
  580. return $element;
  581. }
  582. /**
  583. * Sets the proper `Content-Type` header for UTF-8 encoded JSON
  584. *
  585. * @return void
  586. */
  587. static public function sendHeader()
  588. {
  589. header('Content-Type: application/json; charset=utf-8');
  590. }
  591. /**
  592. * Forces use as a static class
  593. *
  594. * @return fJSON
  595. */
  596. private function __construct() { }
  597. }
  598. /**
  599. * Copyright (c) 2008-2009 Will Bond <will@flourishlib.com>
  600. *
  601. * Permission is hereby granted, free of charge, to any person obtaining a copy
  602. * of this software and associated documentation files (the "Software"), to deal
  603. * in the Software without restriction, including without limitation the rights
  604. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  605. * copies of the Software, and to permit persons to whom the Software is
  606. * furnished to do so, subject to the following conditions:
  607. *
  608. * The above copyright notice and this permission notice shall be included in
  609. * all copies or substantial portions of the Software.
  610. *
  611. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  612. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  613. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  614. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  615. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  616. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  617. * THE SOFTWARE.
  618. */