PageRenderTime 66ms CodeModel.GetById 39ms RepoModel.GetById 1ms app.codeStats 0ms

/included/json.php

https://github.com/agnesrambaud/yacs
PHP | 373 lines | 277 code | 8 blank | 88 comment | 1 complexity | 62bf8d10ad526df4d90bc91130ea11cc MD5 | raw file
  1. <?php
  2. /**
  3. * Converts to and from JSON format.
  4. *
  5. * JSON (JavaScript Object Notation) is a lightweight data-interchange
  6. * format. It is easy for humans to read and write. It is easy for machines
  7. * to parse and generate. It is based on a subset of the JavaScript
  8. * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
  9. * This feature can also be found in Python. JSON is a text format that is
  10. * completely language independent but uses conventions that are familiar
  11. * to programmers of the C-family of languages, including C, C++, C#, Java,
  12. * JavaScript, Perl, TCL, and many others. These properties make JSON an
  13. * ideal data-interchange language.
  14. *
  15. * This package provides a simple encoder and decoder for JSON notation. It
  16. * is intended for use with client-side Javascript applications that make
  17. * use of HTTPRequest to perform server communication functions - data can
  18. * be encoded into JSON notation for use in a client-side javascript, or
  19. * decoded from incoming Javascript requests. JSON format is native to
  20. * Javascript, and can be directly eval()'ed with no further parsing
  21. * overhead
  22. *
  23. * All strings should be in ASCII or UTF-8 format!
  24. *
  25. * PHP versions 4 and 5
  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
  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. * @reference
  55. * @license http://www.freebsd.org/copyright/freebsd-license.html
  56. * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
  57. */
  58. /**
  59. * Marker constant for JSON::decode(), used to flag stack state
  60. */
  61. define('JSON_SLICE', 1);
  62. /**
  63. * Marker constant for JSON::decode(), used to flag stack state
  64. */
  65. define('JSON_IN_STR', 2);
  66. /**
  67. * Marker constant for JSON::decode(), used to flag stack state
  68. */
  69. define('JSON_IN_ARR', 4);
  70. /**
  71. * Marker constant for JSON::decode(), used to flag stack state
  72. */
  73. define('JSON_IN_OBJ', 8);
  74. /**
  75. * Marker constant for JSON::decode(), used to flag stack state
  76. */
  77. define('JSON_IN_CMT', 16);
  78. /**
  79. * Behavior switch for JSON::decode()
  80. */
  81. define('JSON_LOOSE_TYPE', 10);
  82. /**
  83. * Behavior switch for JSON::decode()
  84. */
  85. define('JSON_STRICT_TYPE', 11);
  86. /**
  87. * encodes an arbitrary variable into JSON format
  88. *
  89. * @param mixed $var any number, boolean, string, array, or object to be encoded.
  90. * see argument 1 to JSON() above for array-parsing behavior.
  91. * if var is a strng, note that encode() always expects it
  92. * to be in ASCII or UTF-8 format!
  93. *
  94. * @return string JSON string representation of input var
  95. * @access public
  96. */
  97. function json_encode2($var) {
  98. switch (gettype($var)) {
  99. case 'boolean':
  100. return $var ? 'true' : 'false';
  101. case 'NULL':
  102. return 'null';
  103. case 'integer':
  104. return sprintf('%d', $var);
  105. case 'double':
  106. case 'float':
  107. return sprintf('%f', $var);
  108. case 'string':
  109. $var = str_replace("\n", "\\n", $var);
  110. $var = str_replace("\r", "\\r", $var);
  111. $var = str_replace("\"", "\\\"", $var);
  112. return ("\"$var\"");
  113. case 'array':
  114. /*
  115. * As per JSON spec if any array key is not an integer
  116. * we must treat the the whole array as an object. We
  117. * also try to catch a sparsely populated associative
  118. * array with numeric keys here because some JS engines
  119. * will create an array with empty indexes up to
  120. * max_index which can cause memory issues and because
  121. * the keys, which may be relevant, will be remapped
  122. * otherwise.
  123. *
  124. * As per the ECMA and JSON specification an object may
  125. * have any string as a property. Unfortunately due to
  126. * a hole in the ECMA specification if the key is a
  127. * ECMA reserved word or starts with a digit the
  128. * parameter is only accessible using ECMAScript's
  129. * bracket notation.
  130. */
  131. // treat as a JSON object
  132. if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
  133. return (sprintf('{%s}', join(',', array_map('_name_value', array_keys($var), array_values($var)))));
  134. }
  135. // treat it like a regular array
  136. return (sprintf('[%s]', join(',', array_map('json_encode2', $var))));
  137. case 'object':
  138. $vars = get_object_vars($var);
  139. return (sprintf('{%s}', join(',', array_map('_name_value', array_keys($vars), array_values($vars)))));
  140. default:
  141. return ("");
  142. }
  143. }
  144. /** function _name_value
  145. * array-walking function for use in generating JSON-formatted name-value pairs
  146. *
  147. * @param string $name name of key to use
  148. * @param mixed $value reference to an array element to be encoded
  149. *
  150. * @return string JSON-formatted name-value pair, like '"name":value'
  151. * @access private
  152. */
  153. function _name_value($name, $value) {
  154. return (sprintf("%s:%s", json_encode2(strval($name)), json_encode2($value)));
  155. }
  156. /**
  157. * reduce a string by removing leading and trailing comments and whitespace
  158. *
  159. * @param $str string string value to strip of comments and whitespace
  160. *
  161. * @return string string value stripped of comments and whitespace
  162. * @access private
  163. */
  164. function _reduce_string($str) {
  165. $str = preg_replace(array(
  166. // eliminate single line comments in '// ...' form
  167. '#^\s*//(.+)$#m',
  168. // eliminate multi-line comments in '/* ... */' form, at start of string
  169. '#^\s*/\*(.+)\*/#Us',
  170. // eliminate multi-line comments in '/* ... */' form, at end of string
  171. '#/\*(.+)\*/\s*$#Us'
  172. ), '', $str);
  173. // eliminate extraneous space
  174. return (trim($str));
  175. }
  176. /**
  177. * decodes a JSON string into appropriate variable
  178. *
  179. * @param string $str JSON-formatted string
  180. *
  181. * @return mixed number, boolean, string, array, or object
  182. * corresponding to given JSON input string.
  183. * See argument 1 to JSON() above for object-output behavior.
  184. * Note that decode() always returns strings
  185. * in ASCII or UTF-8 format!
  186. * @access public
  187. */
  188. function json_decode2($str, $always_true=TRUE) {
  189. $str = _reduce_string($str);
  190. switch (strtolower($str)) {
  191. case 'true':
  192. return true;
  193. case 'false':
  194. return false;
  195. case 'null':
  196. return null;
  197. default:
  198. if (is_numeric($str)) {
  199. // Lookie-loo, it's a number
  200. // This would work on its own, but I'm trying to be
  201. // good about returning integers where appropriate:
  202. // return (float)$str;
  203. // Return float or int, as appropriate
  204. return ((float)$str == (integer)$str)
  205. ? (integer)$str
  206. : (float)$str;
  207. } elseif (preg_match('/^("|\').+("|\')$/s', $str, $m) && $m[1] == $m[2]) {
  208. // STRINGS RETURNED IN UTF-8 FORMAT
  209. $delim = substr($str, 0, 1);
  210. $chrs = substr($str, 1, -1);
  211. $utf8 = '';
  212. $strlen_chrs = strlen($chrs);
  213. for ($c = 0; $c < $strlen_chrs; ++$c) {
  214. $substr_chrs_c_2 = substr($chrs, $c, 2);
  215. $ord_chrs_c = ord($chrs{$c});
  216. switch ($substr_chrs_c_2) {
  217. case '\b': $utf8 .= chr(0x08); $c+=1; break;
  218. case '\t': $utf8 .= chr(0x09); $c+=1; break;
  219. case '\n': $utf8 .= chr(0x0A); $c+=1; break;
  220. case '\f': $utf8 .= chr(0x0C); $c+=1; break;
  221. case '\r': $utf8 .= chr(0x0D); $c+=1; break;
  222. case '\\"':
  223. case '\\\'':
  224. case '\\\\':
  225. case '\\/':
  226. if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
  227. ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
  228. $utf8 .= $chrs{++$c};
  229. }
  230. break;
  231. default:
  232. if (preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6))) {
  233. // single, escaped unicode character
  234. $utf16 = chr(hexdec(substr($chrs, ($c+2), 2)))
  235. . chr(hexdec(substr($chrs, ($c+4), 2)));
  236. $utf8 .= mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
  237. $c+=5;
  238. } elseif(($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F)) {
  239. $utf8 .= $chrs{$c};
  240. } elseif(($ord_chrs_c & 0xE0) == 0xC0) {
  241. // characters U-00000080 - U-000007FF, mask 110XXXXX
  242. //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  243. $utf8 .= substr($chrs, $c, 2); $c += 1;
  244. } elseif(($ord_chrs_c & 0xF0) == 0xE0) {
  245. // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  246. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  247. $utf8 .= substr($chrs, $c, 3); $c += 2;
  248. } elseif(($ord_chrs_c & 0xF8) == 0xF0) {
  249. // characters U-00010000 - U-001FFFFF, mask 11110XXX
  250. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  251. $utf8 .= substr($chrs, $c, 4); $c += 3;
  252. } elseif(($ord_chrs_c & 0xFC) == 0xF8) {
  253. // characters U-00200000 - U-03FFFFFF, mask 111110XX
  254. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  255. $utf8 .= substr($chrs, $c, 5); $c += 4;
  256. } elseif(($ord_chrs_c & 0xFE) == 0xFC) {
  257. // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  258. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  259. $utf8 .= substr($chrs, $c, 6); $c += 5;
  260. }
  261. break;
  262. }
  263. }
  264. return ($utf8);
  265. } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
  266. // array, or object notation
  267. if ($str{0} == '[') {
  268. $stk = array(JSON_IN_ARR);
  269. $arr = array();
  270. } else {
  271. $stk = array(JSON_IN_OBJ);
  272. $obj = array();
  273. }
  274. array_push($stk, array('what' => JSON_SLICE, 'where' => 0, 'delim' => false));
  275. $chrs = substr($str, 1, -1);
  276. $chrs = _reduce_string($chrs);
  277. if ($chrs == '') {
  278. if (reset($stk) == JSON_IN_ARR)
  279. return ($arr);
  280. else
  281. return ($obj);
  282. }
  283. $strlen_chrs = strlen($chrs);
  284. for ($c = 0; $c <= $strlen_chrs; ++$c) {
  285. $top = end($stk);
  286. $substr_chrs_c_2 = substr($chrs, $c, 2);
  287. if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == JSON_SLICE))) {
  288. // found a comma that is not inside a string, array, etc.,
  289. // OR we've reached the end of the character list
  290. $slice = substr($chrs, $top['where'], ($c - $top['where']));
  291. array_push($stk, array('what' => JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
  292. //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
  293. if (reset($stk) == JSON_IN_ARR) {
  294. // we are in an array, so just push an element onto the stack
  295. array_push($arr, json_decode2($slice));
  296. } elseif (reset($stk) == JSON_IN_OBJ) {
  297. // we are in an object, so figure
  298. // out the property name and set an
  299. // element in an associative array,
  300. // for now
  301. if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
  302. // "name":value pair
  303. $key = json_decode2($parts[1]);
  304. $val = json_decode2($parts[2]);
  305. $obj[$key] = $val;
  306. } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
  307. // name:value pair, where name is unquoted
  308. $key = $parts[1];
  309. $val = json_decode2($parts[2]);
  310. $obj[$key] = $val;
  311. }
  312. }
  313. } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) &&
  314. in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) {
  315. // found a quote, and we are not inside a string
  316. array_push($stk, array('what' => JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
  317. //print("Found start of string at {$c}\n");
  318. } elseif (($chrs{$c} == $top['delim']) &&
  319. ($top['what'] == JSON_IN_STR) &&
  320. (($chrs{$c - 1} != "\\") ||
  321. ($chrs{$c - 1} == "\\" && $chrs{$c - 2} == "\\"))) {
  322. // found a quote, we're in a string, and it's not escaped
  323. array_pop($stk);
  324. } elseif (($chrs{$c} == '[') &&
  325. in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) {
  326. // found a left-bracket, and we are in an array, object, or slice
  327. array_push($stk, array('what' => JSON_IN_ARR, 'where' => $c, 'delim' => false));
  328. } elseif (($chrs{$c} == ']') && ($top['what'] == JSON_IN_ARR)) {
  329. // found a right-bracket, and we're in an array
  330. array_pop($stk);
  331. } elseif (($chrs{$c} == '{') &&
  332. in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) {
  333. // found a left-brace, and we are in an array, object, or slice
  334. array_push($stk, array('what' => JSON_IN_OBJ, 'where' => $c, 'delim' => false));
  335. } elseif (($chrs{$c} == '}') && ($top['what'] == JSON_IN_OBJ)) {
  336. // found a right-brace, and we're in an object
  337. array_pop($stk);
  338. } elseif (($substr_chrs_c_2 == '/*') &&
  339. in_array($top['what'], array(JSON_SLICE, JSON_IN_ARR, JSON_IN_OBJ))) {
  340. // found a comment start, and we are in an array, object, or slice
  341. array_push($stk, array('what' => JSON_IN_CMT, 'where' => $c, 'delim' => false));
  342. $c++;
  343. } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == JSON_IN_CMT)) {
  344. // found a comment end, and we're in one now
  345. array_pop($stk);
  346. $c++;
  347. for ($i = $top['where']; $i <= $c; ++$i)
  348. $chrs = substr_replace($chrs, ' ', $i, 1);
  349. }
  350. }
  351. if (reset($stk) == JSON_IN_ARR)
  352. return ($arr);
  353. elseif (reset($stk) == JSON_IN_OBJ)
  354. return ($obj);
  355. }
  356. }
  357. }
  358. ?>