source: Dev/trunk/src/client/dojox/grid/tests/support/json.php

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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