人人商城

JSON.php 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. <?php
  2. class Services_JSON
  3. {
  4. /**
  5. * constructs a new JSON instance
  6. *
  7. * @param int $use object behavior flags; combine with boolean-OR
  8. *
  9. * possible values:
  10. * - SERVICES_JSON_LOOSE_TYPE: loose typing.
  11. * "{...}" syntax creates associative arrays
  12. * instead of objects in decode().
  13. * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
  14. * Values which can't be encoded (e.g. resources)
  15. * appear as NULL instead of throwing errors.
  16. * By default, a deeply-nested resource will
  17. * bubble up with an error, so all return values
  18. * from encode() should be checked with isError()
  19. */
  20. public function Services_JSON($use = 0)
  21. {
  22. $this->use = $use;
  23. }
  24. /**
  25. * convert a string from one UTF-16 char to one UTF-8 char
  26. *
  27. * Normally should be handled by mb_convert_encoding, but
  28. * provides a slower PHP-only method for installations
  29. * that lack the multibye string extension.
  30. *
  31. * @param string $utf16 UTF-16 character
  32. * @return string UTF-8 character
  33. * @access private
  34. */
  35. public function utf162utf8($utf16)
  36. {
  37. if (function_exists('mb_convert_encoding')) {
  38. return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
  39. }
  40. $bytes = ord($utf16[0]) << 8 | ord($utf16[1]);
  41. switch (true) {
  42. case (127 & $bytes) == $bytes:
  43. return chr(127 & $bytes);
  44. case (2047 & $bytes) == $bytes:
  45. return chr(192 | $bytes >> 6 & 31) . chr(128 | $bytes & 63);
  46. case (65535 & $bytes) == $bytes:
  47. return chr(224 | $bytes >> 12 & 15) . chr(128 | $bytes >> 6 & 63) . chr(128 | $bytes & 63);
  48. }
  49. return '';
  50. }
  51. /**
  52. * convert a string from one UTF-8 char to one UTF-16 char
  53. *
  54. * Normally should be handled by mb_convert_encoding, but
  55. * provides a slower PHP-only method for installations
  56. * that lack the multibye string extension.
  57. *
  58. * @param string $utf8 UTF-8 character
  59. * @return string UTF-16 character
  60. * @access private
  61. */
  62. public function utf82utf16($utf8)
  63. {
  64. if (function_exists('mb_convert_encoding')) {
  65. return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
  66. }
  67. switch (strlen($utf8)) {
  68. case 1:
  69. return $utf8;
  70. case 2:
  71. return chr(7 & ord($utf8[0]) >> 2) . chr(192 & ord($utf8[0]) << 6 | 63 & ord($utf8[1]));
  72. case 3:
  73. return chr(240 & ord($utf8[0]) << 4 | 15 & ord($utf8[1]) >> 2) . chr(192 & ord($utf8[1]) << 6 | 127 & ord($utf8[2]));
  74. }
  75. return '';
  76. }
  77. /**
  78. * encodes an arbitrary variable into JSON format
  79. *
  80. * @param mixed $var any number, boolean, string, array, or object to be encoded.
  81. * see argument 1 to Services_JSON() above for array-parsing behavior.
  82. * if var is a strng, note that encode() always expects it
  83. * to be in ASCII or UTF-8 format!
  84. *
  85. * @return mixed JSON string representation of input var or an error if a problem occurs
  86. * @access public
  87. */
  88. public function encode($var)
  89. {
  90. switch (gettype($var)) {
  91. case 'boolean':
  92. return $var ? 'true' : 'false';
  93. case 'NULL':
  94. return 'null';
  95. case 'integer':
  96. return (int) $var;
  97. case 'double':
  98. case 'float':
  99. return (double) $var;
  100. case 'string':
  101. $ascii = '';
  102. $strlen_var = strlen($var);
  103. $c = 0;
  104. while ($c < $strlen_var) {
  105. $ord_var_c = ord($var[$c]);
  106. switch (true) {
  107. case $ord_var_c == 8:
  108. $ascii .= '\\b';
  109. break;
  110. case $ord_var_c == 9:
  111. $ascii .= '\\t';
  112. break;
  113. case $ord_var_c == 10:
  114. $ascii .= '\\n';
  115. break;
  116. case $ord_var_c == 12:
  117. $ascii .= '\\f';
  118. break;
  119. case $ord_var_c == 13:
  120. $ascii .= '\\r';
  121. break;
  122. case $ord_var_c == 34:
  123. case $ord_var_c == 47:
  124. case $ord_var_c == 92:
  125. $ascii .= '\\' . $var[$c];
  126. break;
  127. case 32 <= $ord_var_c && $ord_var_c <= 127:
  128. $ascii .= $var[$c];
  129. break;
  130. case ($ord_var_c & 224) == 192:
  131. $char = pack('C*', $ord_var_c, ord($var[$c + 1]));
  132. $c += 1;
  133. $utf16 = $this->utf82utf16($char);
  134. $ascii .= sprintf('\\u%04s', bin2hex($utf16));
  135. break;
  136. case ($ord_var_c & 240) == 224:
  137. $char = pack('C*', $ord_var_c, ord($var[$c + 1]), ord($var[$c + 2]));
  138. $c += 2;
  139. $utf16 = $this->utf82utf16($char);
  140. $ascii .= sprintf('\\u%04s', bin2hex($utf16));
  141. break;
  142. case ($ord_var_c & 248) == 240:
  143. $char = pack('C*', $ord_var_c, ord($var[$c + 1]), ord($var[$c + 2]), ord($var[$c + 3]));
  144. $c += 3;
  145. $utf16 = $this->utf82utf16($char);
  146. $ascii .= sprintf('\\u%04s', bin2hex($utf16));
  147. break;
  148. case ($ord_var_c & 252) == 248:
  149. $char = pack('C*', $ord_var_c, ord($var[$c + 1]), ord($var[$c + 2]), ord($var[$c + 3]), ord($var[$c + 4]));
  150. $c += 4;
  151. $utf16 = $this->utf82utf16($char);
  152. $ascii .= sprintf('\\u%04s', bin2hex($utf16));
  153. break;
  154. case ($ord_var_c & 254) == 252:
  155. $char = pack('C*', $ord_var_c, ord($var[$c + 1]), ord($var[$c + 2]), ord($var[$c + 3]), ord($var[$c + 4]), ord($var[$c + 5]));
  156. $c += 5;
  157. $utf16 = $this->utf82utf16($char);
  158. $ascii .= sprintf('\\u%04s', bin2hex($utf16));
  159. break;
  160. }
  161. ++$c;
  162. }
  163. return '"' . $ascii . '"';
  164. case 'array':
  165. if (is_array($var) && count($var) && array_keys($var) !== range(0, sizeof($var) - 1)) {
  166. $properties = array_map(array($this, 'name_value'), array_keys($var), array_values($var));
  167. foreach ($properties as $property) {
  168. if (Services_JSON::isError($property)) {
  169. return $property;
  170. }
  171. }
  172. return '{' . join(',', $properties) . '}';
  173. }
  174. $elements = array_map(array($this, 'encode'), $var);
  175. foreach ($elements as $element) {
  176. if (Services_JSON::isError($element)) {
  177. return $element;
  178. }
  179. }
  180. return '[' . join(',', $elements) . ']';
  181. case 'object':
  182. $vars = get_object_vars($var);
  183. $properties = array_map(array($this, 'name_value'), array_keys($vars), array_values($vars));
  184. foreach ($properties as $property) {
  185. if (Services_JSON::isError($property)) {
  186. return $property;
  187. }
  188. }
  189. return '{' . join(',', $properties) . '}';
  190. default:
  191. return $this->use & SERVICES_JSON_SUPPRESS_ERRORS ? 'null' : new Services_JSON_Error(gettype($var) . ' can not be encoded as JSON string');
  192. }
  193. }
  194. /**
  195. * array-walking function for use in generating JSON-formatted name-value pairs
  196. *
  197. * @param string $name name of key to use
  198. * @param mixed $value reference to an array element to be encoded
  199. *
  200. * @return string JSON-formatted name-value pair, like '"name":value'
  201. * @access private
  202. */
  203. public function name_value($name, $value)
  204. {
  205. $encoded_value = $this->encode($value);
  206. if (Services_JSON::isError($encoded_value)) {
  207. return $encoded_value;
  208. }
  209. return $this->encode(strval($name)) . ':' . $encoded_value;
  210. }
  211. /**
  212. * reduce a string by removing leading and trailing comments and whitespace
  213. *
  214. * @param $str string string value to strip of comments and whitespace
  215. *
  216. * @return string string value stripped of comments and whitespace
  217. * @access private
  218. */
  219. public function reduce_string($str)
  220. {
  221. $str = preg_replace(array('#^\\s*//(.+)$#m', '#^\\s*/\\*(.+)\\*/#Us', '#/\\*(.+)\\*/\\s*$#Us'), '', $str);
  222. return trim($str);
  223. }
  224. /**
  225. * decodes a JSON string into appropriate variable
  226. *
  227. * @param string $str JSON-formatted string
  228. *
  229. * @return mixed number, boolean, string, array, or object
  230. * corresponding to given JSON input string.
  231. * See argument 1 to Services_JSON() above for object-output behavior.
  232. * Note that decode() always returns strings
  233. * in ASCII or UTF-8 format!
  234. * @access public
  235. */
  236. public function decode($str)
  237. {
  238. $str = $this->reduce_string($str);
  239. switch (strtolower($str)) {
  240. case 'true':
  241. return true;
  242. case 'false':
  243. return false;
  244. case 'null':
  245. return NULL;
  246. default:
  247. $m = array();
  248. if (is_numeric($str)) {
  249. return (double) $str == (int) $str ? (int) $str : (double) $str;
  250. }
  251. if (preg_match('/^("|\').*(\\1)$/s', $str, $m) && $m[1] == $m[2]) {
  252. $delim = substr($str, 0, 1);
  253. $chrs = substr($str, 1, -1);
  254. $utf8 = '';
  255. $strlen_chrs = strlen($chrs);
  256. $c = 0;
  257. while ($c < $strlen_chrs) {
  258. $substr_chrs_c_2 = substr($chrs, $c, 2);
  259. $ord_chrs_c = ord($chrs[$c]);
  260. switch (true) {
  261. case $substr_chrs_c_2 == '\\b':
  262. $utf8 .= chr(8);
  263. ++$c;
  264. break;
  265. case $substr_chrs_c_2 == '\\t':
  266. $utf8 .= chr(9);
  267. ++$c;
  268. break;
  269. case $substr_chrs_c_2 == '\\n':
  270. $utf8 .= chr(10);
  271. ++$c;
  272. break;
  273. case $substr_chrs_c_2 == '\\f':
  274. $utf8 .= chr(12);
  275. ++$c;
  276. break;
  277. case $substr_chrs_c_2 == '\\r':
  278. $utf8 .= chr(13);
  279. ++$c;
  280. break;
  281. case $substr_chrs_c_2 == '\\"':
  282. case $substr_chrs_c_2 == '\\\'':
  283. case $substr_chrs_c_2 == '\\\\':
  284. case $substr_chrs_c_2 == '\\/':
  285. if ($delim == '"' && $substr_chrs_c_2 != '\\\'' || $delim == '\'' && $substr_chrs_c_2 != '\\"') {
  286. $utf8 .= $chrs[++$c];
  287. }
  288. break;
  289. case preg_match('/\\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
  290. $utf16 = chr(hexdec(substr($chrs, $c + 2, 2))) . chr(hexdec(substr($chrs, $c + 4, 2)));
  291. $utf8 .= $this->utf162utf8($utf16);
  292. $c += 5;
  293. break;
  294. case 32 <= $ord_chrs_c && $ord_chrs_c <= 127:
  295. $utf8 .= $chrs[$c];
  296. break;
  297. case ($ord_chrs_c & 224) == 192:
  298. $utf8 .= substr($chrs, $c, 2);
  299. ++$c;
  300. break;
  301. case ($ord_chrs_c & 240) == 224:
  302. $utf8 .= substr($chrs, $c, 3);
  303. $c += 2;
  304. break;
  305. case ($ord_chrs_c & 248) == 240:
  306. $utf8 .= substr($chrs, $c, 4);
  307. $c += 3;
  308. break;
  309. case ($ord_chrs_c & 252) == 248:
  310. $utf8 .= substr($chrs, $c, 5);
  311. $c += 4;
  312. break;
  313. case ($ord_chrs_c & 254) == 252:
  314. $utf8 .= substr($chrs, $c, 6);
  315. $c += 5;
  316. break;
  317. }
  318. ++$c;
  319. }
  320. return $utf8;
  321. }
  322. if (preg_match('/^\\[.*\\]$/s', $str) || preg_match('/^\\{.*\\}$/s', $str)) {
  323. if ($str[0] == '[') {
  324. $stk = array(SERVICES_JSON_IN_ARR);
  325. $arr = array();
  326. }
  327. else if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  328. $stk = array(SERVICES_JSON_IN_OBJ);
  329. $obj = array();
  330. }
  331. else {
  332. $stk = array(SERVICES_JSON_IN_OBJ);
  333. $obj = new stdClass();
  334. }
  335. array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => 0, 'delim' => false));
  336. $chrs = substr($str, 1, -1);
  337. $chrs = $this->reduce_string($chrs);
  338. if ($chrs == '') {
  339. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  340. return $arr;
  341. }
  342. return $obj;
  343. }
  344. $strlen_chrs = strlen($chrs);
  345. $c = 0;
  346. while ($c <= $strlen_chrs) {
  347. $top = end($stk);
  348. $substr_chrs_c_2 = substr($chrs, $c, 2);
  349. if ($c == $strlen_chrs || $chrs[$c] == ',' && $top['what'] == SERVICES_JSON_SLICE) {
  350. $slice = substr($chrs, $top['where'], $c - $top['where']);
  351. array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => $c + 1, 'delim' => false));
  352. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  353. array_push($arr, $this->decode($slice));
  354. }
  355. else {
  356. if (reset($stk) == SERVICES_JSON_IN_OBJ) {
  357. $parts = array();
  358. if (preg_match('/^\\s*(["\'].*[^\\\\]["\'])\\s*:\\s*(\\S.*),?$/Uis', $slice, $parts)) {
  359. $key = $this->decode($parts[1]);
  360. $val = $this->decode($parts[2]);
  361. if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  362. $obj[$key] = $val;
  363. }
  364. else {
  365. $obj->$key = $val;
  366. }
  367. }
  368. else {
  369. if (preg_match('/^\\s*(\\w+)\\s*:\\s*(\\S.*),?$/Uis', $slice, $parts)) {
  370. $key = $parts[1];
  371. $val = $this->decode($parts[2]);
  372. if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
  373. $obj[$key] = $val;
  374. }
  375. else {
  376. $obj->$key = $val;
  377. }
  378. }
  379. }
  380. }
  381. }
  382. }
  383. else {
  384. if (($chrs[$c] == '"' || $chrs[$c] == '\'') && $top['what'] != SERVICES_JSON_IN_STR) {
  385. array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
  386. }
  387. else {
  388. if ($chrs[$c] == $top['delim'] && $top['what'] == SERVICES_JSON_IN_STR && ($chrs[$c - 1] != '\\' || $chrs[$c - 1] == '\\' && $chrs[$c - 2] == '\\')) {
  389. array_pop($stk);
  390. }
  391. else {
  392. if ($chrs[$c] == '[' && in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  393. array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
  394. }
  395. else {
  396. if ($chrs[$c] == ']' && $top['what'] == SERVICES_JSON_IN_ARR) {
  397. array_pop($stk);
  398. }
  399. else {
  400. if ($chrs[$c] == '{' && in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  401. array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
  402. }
  403. else {
  404. if ($chrs[$c] == '}' && $top['what'] == SERVICES_JSON_IN_OBJ) {
  405. array_pop($stk);
  406. }
  407. else {
  408. if ($substr_chrs_c_2 == '/*' && in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
  409. array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
  410. ++$c;
  411. }
  412. else {
  413. if ($substr_chrs_c_2 == '*/' && $top['what'] == SERVICES_JSON_IN_CMT) {
  414. array_pop($stk);
  415. ++$c;
  416. $i = $top['where'];
  417. while ($i <= $c) {
  418. $chrs = substr_replace($chrs, ' ', $i, 1);
  419. ++$i;
  420. }
  421. }
  422. }
  423. }
  424. }
  425. }
  426. }
  427. }
  428. }
  429. }
  430. ++$c;
  431. }
  432. if (reset($stk) == SERVICES_JSON_IN_ARR) {
  433. return $arr;
  434. }
  435. if (reset($stk) == SERVICES_JSON_IN_OBJ) {
  436. return $obj;
  437. }
  438. }
  439. }
  440. }
  441. /**
  442. * @todo Ultimately, this should just call PEAR::isError()
  443. */
  444. public function isError($data, $code = NULL)
  445. {
  446. if (class_exists('pear')) {
  447. return PEAR::isError($data, $code);
  448. }
  449. if (is_object($data) && (get_class($data) == 'services_json_error' || is_subclass_of($data, 'services_json_error'))) {
  450. return true;
  451. }
  452. return false;
  453. }
  454. }
  455. define('SERVICES_JSON_SLICE', 1);
  456. define('SERVICES_JSON_IN_STR', 2);
  457. define('SERVICES_JSON_IN_ARR', 3);
  458. define('SERVICES_JSON_IN_OBJ', 4);
  459. define('SERVICES_JSON_IN_CMT', 5);
  460. define('SERVICES_JSON_LOOSE_TYPE', 16);
  461. define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
  462. if (class_exists('PEAR_Error')) {
  463. class Services_JSON_Error extends PEAR_Error
  464. {
  465. public function Services_JSON_Error($message = 'unknown error', $code = NULL, $mode = NULL, $options = NULL, $userinfo = NULL)
  466. {
  467. parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
  468. }
  469. }
  470. return 1;
  471. }
  472. class Services_JSON_Error
  473. {
  474. public function Services_JSON_Error($message = 'unknown error', $code = NULL, $mode = NULL, $options = NULL, $userinfo = NULL)
  475. {
  476. }
  477. }
  478. ?>