PageRenderTime 210ms CodeModel.GetById 101ms app.highlight 52ms RepoModel.GetById 48ms app.codeStats 0ms

/wp-includes/class-json.php

https://bitbucket.org/aqge/deptandashboard
PHP | 863 lines | 474 code | 113 blank | 276 comment | 98 complexity | c7811e668b6a4b4bd89afc3f227146af MD5 | raw file
  1<?php
  2if ( !class_exists( 'Services_JSON' ) ) :
  3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4/**
  5 * Converts to and from JSON format.
  6 *
  7 * JSON (JavaScript Object Notation) is a lightweight data-interchange
  8 * format. It is easy for humans to read and write. It is easy for machines
  9 * to parse and generate. It is based on a subset of the JavaScript
 10 * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
 11 * This feature can also be found in  Python. JSON is a text format that is
 12 * completely language independent but uses conventions that are familiar
 13 * to programmers of the C-family of languages, including C, C++, C#, Java,
 14 * JavaScript, Perl, TCL, and many others. These properties make JSON an
 15 * ideal data-interchange language.
 16 *
 17 * This package provides a simple encoder and decoder for JSON notation. It
 18 * is intended for use with client-side Javascript applications that make
 19 * use of HTTPRequest to perform server communication functions - data can
 20 * be encoded into JSON notation for use in a client-side javascript, or
 21 * decoded from incoming Javascript requests. JSON format is native to
 22 * Javascript, and can be directly eval()'ed with no further parsing
 23 * overhead
 24 *
 25 * All strings should be in ASCII or UTF-8 format!
 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		Services_JSON
 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 * @version     CVS: $Id: JSON.php 288200 2009-09-09 15:41:29Z alan_k $
 55 * @license		http://www.opensource.org/licenses/bsd-license.php
 56 * @link		http://pear.php.net/pepr/pepr-proposal-show.php?id=198
 57 */
 58
 59/**
 60 * Marker constant for Services_JSON::decode(), used to flag stack state
 61 */
 62define('SERVICES_JSON_SLICE', 1);
 63
 64/**
 65 * Marker constant for Services_JSON::decode(), used to flag stack state
 66 */
 67define('SERVICES_JSON_IN_STR',  2);
 68
 69/**
 70 * Marker constant for Services_JSON::decode(), used to flag stack state
 71 */
 72define('SERVICES_JSON_IN_ARR',  3);
 73
 74/**
 75 * Marker constant for Services_JSON::decode(), used to flag stack state
 76 */
 77define('SERVICES_JSON_IN_OBJ',  4);
 78
 79/**
 80 * Marker constant for Services_JSON::decode(), used to flag stack state
 81 */
 82define('SERVICES_JSON_IN_CMT', 5);
 83
 84/**
 85 * Behavior switch for Services_JSON::decode()
 86 */
 87define('SERVICES_JSON_LOOSE_TYPE', 16);
 88
 89/**
 90 * Behavior switch for Services_JSON::decode()
 91 */
 92define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
 93
 94/**
 95 * Converts to and from JSON format.
 96 *
 97 * Brief example of use:
 98 *
 99 * <code>
100 * // create a new instance of Services_JSON
101 * $json = new Services_JSON();
102 *
103 * // convert a complexe value to JSON notation, and send it to the browser
104 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
105 * $output = $json->encode($value);
106 *
107 * print($output);
108 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
109 *
110 * // accept incoming POST data, assumed to be in JSON notation
111 * $input = file_get_contents('php://input', 1000000);
112 * $value = $json->decode($input);
113 * </code>
114 */
115class Services_JSON
116{
117 /**
118	* constructs a new JSON instance
119	*
120	* @param int $use object behavior flags; combine with boolean-OR
121	*
122	*						possible values:
123	*						- SERVICES_JSON_LOOSE_TYPE:  loose typing.
124	*								"{...}" syntax creates associative arrays
125	*								instead of objects in decode().
126	*						- SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
127	*								Values which can't be encoded (e.g. resources)
128	*								appear as NULL instead of throwing errors.
129	*								By default, a deeply-nested resource will
130	*								bubble up with an error, so all return values
131	*								from encode() should be checked with isError()
132	*/
133	function Services_JSON($use = 0)
134	{
135		$this->use = $use;
136	}
137
138 /**
139	* convert a string from one UTF-16 char to one UTF-8 char
140	*
141	* Normally should be handled by mb_convert_encoding, but
142	* provides a slower PHP-only method for installations
143	* that lack the multibye string extension.
144	*
145	* @param	string  $utf16  UTF-16 character
146	* @return string  UTF-8 character
147	* @access private
148	*/
149	function utf162utf8($utf16)
150	{
151		// oh please oh please oh please oh please oh please
152		if(function_exists('mb_convert_encoding')) {
153			return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
154		}
155
156		$bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
157
158		switch(true) {
159			case ((0x7F & $bytes) == $bytes):
160				// this case should never be reached, because we are in ASCII range
161				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
162				return chr(0x7F & $bytes);
163
164			case (0x07FF & $bytes) == $bytes:
165				// return a 2-byte UTF-8 character
166				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
167				return chr(0xC0 | (($bytes >> 6) & 0x1F))
168					. chr(0x80 | ($bytes & 0x3F));
169
170			case (0xFFFF & $bytes) == $bytes:
171				// return a 3-byte UTF-8 character
172				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
173				return chr(0xE0 | (($bytes >> 12) & 0x0F))
174					. chr(0x80 | (($bytes >> 6) & 0x3F))
175					. chr(0x80 | ($bytes & 0x3F));
176		}
177
178		// ignoring UTF-32 for now, sorry
179		return '';
180	}
181
182 /**
183	* convert a string from one UTF-8 char to one UTF-16 char
184	*
185	* Normally should be handled by mb_convert_encoding, but
186	* provides a slower PHP-only method for installations
187	* that lack the multibye string extension.
188	*
189	* @param	string  $utf8 UTF-8 character
190	* @return string  UTF-16 character
191	* @access private
192	*/
193	function utf82utf16($utf8)
194	{
195		// oh please oh please oh please oh please oh please
196		if(function_exists('mb_convert_encoding')) {
197			return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
198		}
199
200		switch(strlen($utf8)) {
201			case 1:
202				// this case should never be reached, because we are in ASCII range
203				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
204				return $utf8;
205
206			case 2:
207				// return a UTF-16 character from a 2-byte UTF-8 char
208				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
209				return chr(0x07 & (ord($utf8[0]) >> 2))
210					. chr((0xC0 & (ord($utf8[0]) << 6))
211						| (0x3F & ord($utf8[1])));
212
213			case 3:
214				// return a UTF-16 character from a 3-byte UTF-8 char
215				// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
216				return chr((0xF0 & (ord($utf8[0]) << 4))
217						| (0x0F & (ord($utf8[1]) >> 2)))
218					. chr((0xC0 & (ord($utf8[1]) << 6))
219						| (0x7F & ord($utf8[2])));
220		}
221
222		// ignoring UTF-32 for now, sorry
223		return '';
224	}
225
226 /**
227	* encodes an arbitrary variable into JSON format (and sends JSON Header)
228	*
229	* @param	mixed $var	any number, boolean, string, array, or object to be encoded.
230	*						see argument 1 to Services_JSON() above for array-parsing behavior.
231	*						if var is a strng, note that encode() always expects it
232	*						to be in ASCII or UTF-8 format!
233	*
234	* @return mixed JSON string representation of input var or an error if a problem occurs
235	* @access public
236	*/
237	function encode($var)
238	{
239		header('Content-type: application/json');
240		return $this->_encode($var);
241	}
242	/**
243	* encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)
244	*
245	* @param	mixed $var	any number, boolean, string, array, or object to be encoded.
246	*						see argument 1 to Services_JSON() above for array-parsing behavior.
247	*						if var is a strng, note that encode() always expects it
248	*						to be in ASCII or UTF-8 format!
249	*
250	* @return mixed JSON string representation of input var or an error if a problem occurs
251	* @access public
252	*/
253	function encodeUnsafe($var)
254	{
255		return $this->_encode($var);
256	}
257	/**
258	* PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format
259	*
260	* @param	mixed $var	any number, boolean, string, array, or object to be encoded.
261	*						see argument 1 to Services_JSON() above for array-parsing behavior.
262	*						if var is a strng, note that encode() always expects it
263	*						to be in ASCII or UTF-8 format!
264	*
265	* @return mixed JSON string representation of input var or an error if a problem occurs
266	* @access public
267	*/
268	function _encode($var)
269	{
270
271		switch (gettype($var)) {
272			case 'boolean':
273				return $var ? 'true' : 'false';
274
275			case 'NULL':
276				return 'null';
277
278			case 'integer':
279				return (int) $var;
280
281			case 'double':
282			case 'float':
283				return (float) $var;
284
285			case 'string':
286				// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
287				$ascii = '';
288				$strlen_var = strlen($var);
289
290			/*
291				* Iterate over every character in the string,
292				* escaping with a slash or encoding to UTF-8 where necessary
293				*/
294				for ($c = 0; $c < $strlen_var; ++$c) {
295
296					$ord_var_c = ord($var[$c]);
297
298					switch (true) {
299						case $ord_var_c == 0x08:
300							$ascii .= '\b';
301							break;
302						case $ord_var_c == 0x09:
303							$ascii .= '\t';
304							break;
305						case $ord_var_c == 0x0A:
306							$ascii .= '\n';
307							break;
308						case $ord_var_c == 0x0C:
309							$ascii .= '\f';
310							break;
311						case $ord_var_c == 0x0D:
312							$ascii .= '\r';
313							break;
314
315						case $ord_var_c == 0x22:
316						case $ord_var_c == 0x2F:
317						case $ord_var_c == 0x5C:
318							// double quote, slash, slosh
319							$ascii .= '\\'.$var[$c];
320							break;
321
322						case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
323							// characters U-00000000 - U-0000007F (same as ASCII)
324							$ascii .= $var[$c];
325							break;
326
327						case (($ord_var_c & 0xE0) == 0xC0):
328							// characters U-00000080 - U-000007FF, mask 110XXXXX
329							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
330							if ($c+1 >= $strlen_var) {
331								$c += 1;
332								$ascii .= '?';
333								break;
334							}
335
336							$char = pack('C*', $ord_var_c, ord($var[$c + 1]));
337							$c += 1;
338							$utf16 = $this->utf82utf16($char);
339							$ascii .= sprintf('\u%04s', bin2hex($utf16));
340							break;
341
342						case (($ord_var_c & 0xF0) == 0xE0):
343							if ($c+2 >= $strlen_var) {
344								$c += 2;
345								$ascii .= '?';
346								break;
347							}
348							// characters U-00000800 - U-0000FFFF, mask 1110XXXX
349							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
350							$char = pack('C*', $ord_var_c,
351										@ord($var[$c + 1]),
352										@ord($var[$c + 2]));
353							$c += 2;
354							$utf16 = $this->utf82utf16($char);
355							$ascii .= sprintf('\u%04s', bin2hex($utf16));
356							break;
357
358						case (($ord_var_c & 0xF8) == 0xF0):
359							if ($c+3 >= $strlen_var) {
360								$c += 3;
361								$ascii .= '?';
362								break;
363							}
364							// characters U-00010000 - U-001FFFFF, mask 11110XXX
365							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
366							$char = pack('C*', $ord_var_c,
367										ord($var[$c + 1]),
368										ord($var[$c + 2]),
369										ord($var[$c + 3]));
370							$c += 3;
371							$utf16 = $this->utf82utf16($char);
372							$ascii .= sprintf('\u%04s', bin2hex($utf16));
373							break;
374
375						case (($ord_var_c & 0xFC) == 0xF8):
376							// characters U-00200000 - U-03FFFFFF, mask 111110XX
377							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
378							if ($c+4 >= $strlen_var) {
379								$c += 4;
380								$ascii .= '?';
381								break;
382							}
383							$char = pack('C*', $ord_var_c,
384										ord($var[$c + 1]),
385										ord($var[$c + 2]),
386										ord($var[$c + 3]),
387										ord($var[$c + 4]));
388							$c += 4;
389							$utf16 = $this->utf82utf16($char);
390							$ascii .= sprintf('\u%04s', bin2hex($utf16));
391							break;
392
393						case (($ord_var_c & 0xFE) == 0xFC):
394						if ($c+5 >= $strlen_var) {
395								$c += 5;
396								$ascii .= '?';
397								break;
398							}
399							// characters U-04000000 - U-7FFFFFFF, mask 1111110X
400							// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
401							$char = pack('C*', $ord_var_c,
402										ord($var[$c + 1]),
403										ord($var[$c + 2]),
404										ord($var[$c + 3]),
405										ord($var[$c + 4]),
406										ord($var[$c + 5]));
407							$c += 5;
408							$utf16 = $this->utf82utf16($char);
409							$ascii .= sprintf('\u%04s', bin2hex($utf16));
410							break;
411					}
412				}
413				return  '"'.$ascii.'"';
414
415			case 'array':
416			/*
417				* As per JSON spec if any array key is not an integer
418				* we must treat the the whole array as an object. We
419				* also try to catch a sparsely populated associative
420				* array with numeric keys here because some JS engines
421				* will create an array with empty indexes up to
422				* max_index which can cause memory issues and because
423				* the keys, which may be relevant, will be remapped
424				* otherwise.
425				*
426				* As per the ECMA and JSON specification an object may
427				* have any string as a property. Unfortunately due to
428				* a hole in the ECMA specification if the key is a
429				* ECMA reserved word or starts with a digit the
430				* parameter is only accessible using ECMAScript's
431				* bracket notation.
432				*/
433
434				// treat as a JSON object
435				if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
436					$properties = array_map(array($this, 'name_value'),
437											array_keys($var),
438											array_values($var));
439
440					foreach($properties as $property) {
441						if(Services_JSON::isError($property)) {
442							return $property;
443						}
444					}
445
446					return '{' . join(',', $properties) . '}';
447				}
448
449				// treat it like a regular array
450				$elements = array_map(array($this, '_encode'), $var);
451
452				foreach($elements as $element) {
453					if(Services_JSON::isError($element)) {
454						return $element;
455					}
456				}
457
458				return '[' . join(',', $elements) . ']';
459
460			case 'object':
461				$vars = get_object_vars($var);
462
463				$properties = array_map(array($this, 'name_value'),
464										array_keys($vars),
465										array_values($vars));
466
467				foreach($properties as $property) {
468					if(Services_JSON::isError($property)) {
469						return $property;
470					}
471				}
472
473				return '{' . join(',', $properties) . '}';
474
475			default:
476				return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
477					? 'null'
478					: new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
479		}
480	}
481
482 /**
483	* array-walking function for use in generating JSON-formatted name-value pairs
484	*
485	* @param	string  $name name of key to use
486	* @param	mixed $value  reference to an array element to be encoded
487	*
488	* @return string  JSON-formatted name-value pair, like '"name":value'
489	* @access private
490	*/
491	function name_value($name, $value)
492	{
493		$encoded_value = $this->_encode($value);
494
495		if(Services_JSON::isError($encoded_value)) {
496			return $encoded_value;
497		}
498
499		return $this->_encode(strval($name)) . ':' . $encoded_value;
500	}
501
502 /**
503	* reduce a string by removing leading and trailing comments and whitespace
504	*
505	* @param	$str	string	string value to strip of comments and whitespace
506	*
507	* @return string  string value stripped of comments and whitespace
508	* @access private
509	*/
510	function reduce_string($str)
511	{
512		$str = preg_replace(array(
513
514				// eliminate single line comments in '// ...' form
515				'#^\s*//(.+)$#m',
516
517				// eliminate multi-line comments in '/* ... */' form, at start of string
518				'#^\s*/\*(.+)\*/#Us',
519
520				// eliminate multi-line comments in '/* ... */' form, at end of string
521				'#/\*(.+)\*/\s*$#Us'
522
523			), '', $str);
524
525		// eliminate extraneous space
526		return trim($str);
527	}
528
529 /**
530	* decodes a JSON string into appropriate variable
531	*
532	* @param	string  $str	JSON-formatted string
533	*
534	* @return mixed number, boolean, string, array, or object
535	*				corresponding to given JSON input string.
536	*				See argument 1 to Services_JSON() above for object-output behavior.
537	*				Note that decode() always returns strings
538	*				in ASCII or UTF-8 format!
539	* @access public
540	*/
541	function decode($str)
542	{
543		$str = $this->reduce_string($str);
544
545		switch (strtolower($str)) {
546			case 'true':
547				return true;
548
549			case 'false':
550				return false;
551
552			case 'null':
553				return null;
554
555			default:
556				$m = array();
557
558				if (is_numeric($str)) {
559					// Lookie-loo, it's a number
560
561					// This would work on its own, but I'm trying to be
562					// good about returning integers where appropriate:
563					// return (float)$str;
564
565					// Return float or int, as appropriate
566					return ((float)$str == (integer)$str)
567						? (integer)$str
568						: (float)$str;
569
570				} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
571					// STRINGS RETURNED IN UTF-8 FORMAT
572					$delim = substr($str, 0, 1);
573					$chrs = substr($str, 1, -1);
574					$utf8 = '';
575					$strlen_chrs = strlen($chrs);
576
577					for ($c = 0; $c < $strlen_chrs; ++$c) {
578
579						$substr_chrs_c_2 = substr($chrs, $c, 2);
580						$ord_chrs_c = ord($chrs[$c]);
581
582						switch (true) {
583							case $substr_chrs_c_2 == '\b':
584								$utf8 .= chr(0x08);
585								++$c;
586								break;
587							case $substr_chrs_c_2 == '\t':
588								$utf8 .= chr(0x09);
589								++$c;
590								break;
591							case $substr_chrs_c_2 == '\n':
592								$utf8 .= chr(0x0A);
593								++$c;
594								break;
595							case $substr_chrs_c_2 == '\f':
596								$utf8 .= chr(0x0C);
597								++$c;
598								break;
599							case $substr_chrs_c_2 == '\r':
600								$utf8 .= chr(0x0D);
601								++$c;
602								break;
603
604							case $substr_chrs_c_2 == '\\"':
605							case $substr_chrs_c_2 == '\\\'':
606							case $substr_chrs_c_2 == '\\\\':
607							case $substr_chrs_c_2 == '\\/':
608								if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
609								($delim == "'" && $substr_chrs_c_2 != '\\"')) {
610									$utf8 .= $chrs[++$c];
611								}
612								break;
613
614							case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
615								// single, escaped unicode character
616								$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
617									. chr(hexdec(substr($chrs, ($c + 4), 2)));
618								$utf8 .= $this->utf162utf8($utf16);
619								$c += 5;
620								break;
621
622							case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
623								$utf8 .= $chrs[$c];
624								break;
625
626							case ($ord_chrs_c & 0xE0) == 0xC0:
627								// characters U-00000080 - U-000007FF, mask 110XXXXX
628								//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
629								$utf8 .= substr($chrs, $c, 2);
630								++$c;
631								break;
632
633							case ($ord_chrs_c & 0xF0) == 0xE0:
634								// characters U-00000800 - U-0000FFFF, mask 1110XXXX
635								// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
636								$utf8 .= substr($chrs, $c, 3);
637								$c += 2;
638								break;
639
640							case ($ord_chrs_c & 0xF8) == 0xF0:
641								// characters U-00010000 - U-001FFFFF, mask 11110XXX
642								// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
643								$utf8 .= substr($chrs, $c, 4);
644								$c += 3;
645								break;
646
647							case ($ord_chrs_c & 0xFC) == 0xF8:
648								// characters U-00200000 - U-03FFFFFF, mask 111110XX
649								// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
650								$utf8 .= substr($chrs, $c, 5);
651								$c += 4;
652								break;
653
654							case ($ord_chrs_c & 0xFE) == 0xFC:
655								// characters U-04000000 - U-7FFFFFFF, mask 1111110X
656								// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
657								$utf8 .= substr($chrs, $c, 6);
658								$c += 5;
659								break;
660
661						}
662
663					}
664
665					return $utf8;
666
667				} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
668					// array, or object notation
669
670					if ($str[0] == '[') {
671						$stk = array(SERVICES_JSON_IN_ARR);
672						$arr = array();
673					} else {
674						if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
675							$stk = array(SERVICES_JSON_IN_OBJ);
676							$obj = array();
677						} else {
678							$stk = array(SERVICES_JSON_IN_OBJ);
679							$obj = new stdClass();
680						}
681					}
682
683					array_push($stk, array('what'  => SERVICES_JSON_SLICE,
684										'where' => 0,
685										'delim' => false));
686
687					$chrs = substr($str, 1, -1);
688					$chrs = $this->reduce_string($chrs);
689
690					if ($chrs == '') {
691						if (reset($stk) == SERVICES_JSON_IN_ARR) {
692							return $arr;
693
694						} else {
695							return $obj;
696
697						}
698					}
699
700					//print("\nparsing {$chrs}\n");
701
702					$strlen_chrs = strlen($chrs);
703
704					for ($c = 0; $c <= $strlen_chrs; ++$c) {
705
706						$top = end($stk);
707						$substr_chrs_c_2 = substr($chrs, $c, 2);
708
709						if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
710							// found a comma that is not inside a string, array, etc.,
711							// OR we've reached the end of the character list
712							$slice = substr($chrs, $top['where'], ($c - $top['where']));
713							array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
714							//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
715
716							if (reset($stk) == SERVICES_JSON_IN_ARR) {
717								// we are in an array, so just push an element onto the stack
718								array_push($arr, $this->decode($slice));
719
720							} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
721								// we are in an object, so figure
722								// out the property name and set an
723								// element in an associative array,
724								// for now
725								$parts = array();
726
727								if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
728									// "name":value pair
729									$key = $this->decode($parts[1]);
730									$val = $this->decode($parts[2]);
731
732									if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
733										$obj[$key] = $val;
734									} else {
735										$obj->$key = $val;
736									}
737								} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
738									// name:value pair, where name is unquoted
739									$key = $parts[1];
740									$val = $this->decode($parts[2]);
741
742									if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
743										$obj[$key] = $val;
744									} else {
745										$obj->$key = $val;
746									}
747								}
748
749							}
750
751						} elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
752							// found a quote, and we are not inside a string
753							array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
754							//print("Found start of string at {$c}\n");
755
756						} elseif (($chrs[$c] == $top['delim']) &&
757								($top['what'] == SERVICES_JSON_IN_STR) &&
758								((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
759							// found a quote, we're in a string, and it's not escaped
760							// we know that it's not escaped becase there is _not_ an
761							// odd number of backslashes at the end of the string so far
762							array_pop($stk);
763							//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
764
765						} elseif (($chrs[$c] == '[') &&
766								in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
767							// found a left-bracket, and we are in an array, object, or slice
768							array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
769							//print("Found start of array at {$c}\n");
770
771						} elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
772							// found a right-bracket, and we're in an array
773							array_pop($stk);
774							//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
775
776						} elseif (($chrs[$c] == '{') &&
777								in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
778							// found a left-brace, and we are in an array, object, or slice
779							array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
780							//print("Found start of object at {$c}\n");
781
782						} elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
783							// found a right-brace, and we're in an object
784							array_pop($stk);
785							//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
786
787						} elseif (($substr_chrs_c_2 == '/*') &&
788								in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
789							// found a comment start, and we are in an array, object, or slice
790							array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
791							$c++;
792							//print("Found start of comment at {$c}\n");
793
794						} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
795							// found a comment end, and we're in one now
796							array_pop($stk);
797							$c++;
798
799							for ($i = $top['where']; $i <= $c; ++$i)
800								$chrs = substr_replace($chrs, ' ', $i, 1);
801
802							//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
803
804						}
805
806					}
807
808					if (reset($stk) == SERVICES_JSON_IN_ARR) {
809						return $arr;
810
811					} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
812						return $obj;
813
814					}
815
816				}
817		}
818	}
819
820	/**
821	* @todo Ultimately, this should just call PEAR::isError()
822	*/
823	function isError($data, $code = null)
824	{
825		if (class_exists('pear')) {
826			return PEAR::isError($data, $code);
827		} elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
828								is_subclass_of($data, 'services_json_error'))) {
829			return true;
830		}
831
832		return false;
833	}
834}
835
836if (class_exists('PEAR_Error')) {
837
838	class Services_JSON_Error extends PEAR_Error
839	{
840		function Services_JSON_Error($message = 'unknown error', $code = null,
841									$mode = null, $options = null, $userinfo = null)
842		{
843			parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
844		}
845	}
846
847} else {
848
849	/**
850	* @todo Ultimately, this class shall be descended from PEAR_Error
851	*/
852	class Services_JSON_Error
853	{
854		function Services_JSON_Error($message = 'unknown error', $code = null,
855									$mode = null, $options = null, $userinfo = null)
856		{
857
858		}
859	}
860
861}
862endif;
863?>