source: trunk/plugins/ImageManager/Classes/JSON.php @ 1258

Last change on this file since 1258 was 1192, checked in by gogo, 10 years ago

Discussion in ticket #1478

Creating Sub Classes

Addition of method Xinha.extend. This method provides a means for more "classical" sub classing within javascript. Short story,

    var Vehicle = function() { }
    Vehicle.prototype.horn = function() { alert('Toot'); }
    var Car = function()
	{
		Car.parentConstructor.call(this); // If you want to call it.
	}
    Xinha.extend(Car, Vehicle);
    Car.prototype.horn = function()
	{
		alert('Toot');
		Car.superClass.horn.call(this);  // If you want to call it.
	}

remember the "call" method of javascript is .call(<on-this-object>, arg, arg, arg ..) and that you can also use the "apply" method to pass in arguments as an array which is .apply(<on this object>, [arg, arg, arg]), and that the arguments to your method are in the "arguments" array.

Dialog Modification

Split out the setting of the localizer from the translation of HTML in source:trunk/modules/Dialogs/XinhaDialog.js by introducing a new method Xinha.Dialog::setLocalizer(), Xinha.Dialog::translateHtml() remains compatible, the localizer is optional to it.

.htaccess Security

Further additions to the .htaccess files for the demo_images in ExtendedFileManager? and ImageManager?. I think we should give consideration to just deleting these folders totally, over the last year I've had a number of instances of people coming to me with these folders filled with various malware.

File-Picker on arbitrary fields outside Xinha (ExtendedFileManager?)

Addition of a source:trunk/plugins/ExtendedFileManager/file-picker.js hijacking of the ExtendedFileManager? in the same manner as the existing source:trunk/plugins/ImageManager/image-picker.js within ImageManager?. I'm putting this n there incase somebody finds it useful, but it may need some work as I don't use it myself any more. I am likely to come up with a way to replace both ExtendedFileManager? and ImageManager? with the "Mootools FileManager", this is a very nice file manager with a similar dialog "look" to our new dialogs, and the very VERY important bonus of it being easy to upload multiple files at once with a progress indicator (using a hidden flash component to do the hard work).

ImageManager? to use hspace and vspace attributes instead of margin.

The addition of a config option to ImageManager? "UseHSpaceVSpace" which swaps out the "margin" settings for the hspace and vspace attributes. The reason for this apparent "old fashioned-ness" is that margin is less reliably honoured when the HTML is put into an email.

YouTube and Flickr added to the ImageManager?

The addition of additional data sources (aka backends or choosers) to ImageManager?, specifically "YouTube" and "Flickr".

When one or both is enabled the user can use a selector in the image manager popup to choose from images on the local server, or search for videos on YouTube, or images on Flickr.

YouTube is enabled by setting $IMConfig['YouTube'] = TRUE; when the user selects a video the large format video still is inserted into the Xinha area, with extra information on the query string.

Flickr is enabled by setting $IMConfig['Flickr']['Key'] = 'your flickr api key here';, when the user selects an image, the image is inserted into the Xinha area.

For videos especially there needs to be some extra processing to turn that into a video when the end user sees this HTML, this is done by the "smart-image.js" script in combination with the (included) swfobject. In short, on the page WHERE YOU USE THE HTML (not where you are running Xinha to edit it) you will put this

   <script type="text/javascript" src="/path/to/xinha/plugins/ImageManager/smart-image.js"></script>
   <body onload="SmartImages.replace_all();">

it will replace the still image with the video (provided javascript is on).

Smart-image will also add a little hover attribution to Flickr sourced images (ie, hover over the image an attribution link appears). If you are going to use the Flickr source, then you must make sure you are legally permitted to do so, for one, your site can not be commercial. I've provided you with the tools, just try not to shoot yourself.

New Dialog Types

Added a new Dialog type "DetachedDialog?". This "faked" dialog extends the Xinha.Dialog and is a "drop in" replacement for it, the difference is the DetachedDialog? is not associated to any instance of Xinha. No Xinha needs to be instantiated for a "plugin" to use a DetachedDialog?. Where this is useful is in leveraging off plugins to provide functionality outside of Xinha, see link-picker.js below (in Linker).

Also added a new Dialog type "DivDialog?". Similar to the DetachedDialog?, except the dialog HTML is written directly into an html element supplied to the dialog. The use of this is similar to the above, providing a means for getting a plugin "away" from Xinha to provide it's services for other things. This Dialog may need some work since it was written before the new Xinha.Dialog was created, in brief test it worked mostly. Worth keeping around as it's a pretty simple example of how a new Dialog type can be constructed by extending the existing one.

Added a "Link Picker" to leverage the Linker plugin for providing a "Browse" button next to normal input fields in order to select a link which is written into the field.

This is the initial usage of the DetachedDialog?, the basic usage of this is described in the comments in that file source:trunk/plugins/Linker/link-picker.js

Hid all dotfiles from the Linker scanner, the linker shouldn't be showing "hidden" files.

CSS fix to dTree in linker, just to make sure it's styles were not getting clobbered.

Stylist Duplicate Stylesheet Fix

Stop the Stylist from possibly adding a duplicate stylesheet into pageStyleSheets, this was creating a subtle problem in certain circumstances.

New Plugin: WysiwygWrap?

Added a new plugin WysiwygWrap, basically this wraps the content of your Xinha with some specific elements you tell it, when in Wysiwyg mode, and strips them out again when you go back to code (or submit). The idea is to make it so that, combined with an appropriate pageStyleSheet(s) you can more easily simulate in Xinha what it will "look like" when that HTML is "published" wherever it's getting published.

It takes a simple configuration of xinha.config.WysiwygWrap = { elements: [ list of the ancestor elements to insert, in order top down ] }
Example:

  xinha.config.WysiwygWrap = { elements: ['div#outer', 'div.inner'] }

will produce this in your Xinha Wysiwyg mode html

	<div id="outer">
		<div class="inner">
			** Original Xinha Content For Editing Here **
		</div>
	</div>

so your pageStyleSheet would just make nice CSS for those outer and inner to make the html in the Wysiwyg mode look closer to how it would look "on the site".

In practice, I'm not sure this works that well, it seemed a good idea at the time, but it can be a bit fragile.

File size: 33.2 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 * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
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
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        switch (gettype($var)) {
240            case 'boolean':
241                return $var ? 'true' : 'false';
242
243            case 'NULL':
244                return 'null';
245
246            case 'integer':
247                return (int) $var;
248
249            case 'double':
250            case 'float':
251                return (float) $var;
252
253            case 'string':
254                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
255                $ascii = '';
256                $strlen_var = strlen($var);
257
258               /*
259                * Iterate over every character in the string,
260                * escaping with a slash or encoding to UTF-8 where necessary
261                */
262                for ($c = 0; $c < $strlen_var; ++$c) {
263
264                    $ord_var_c = ord($var{$c});
265
266                    switch (true) {
267                        case $ord_var_c == 0x08:
268                            $ascii .= '\b';
269                            break;
270                        case $ord_var_c == 0x09:
271                            $ascii .= '\t';
272                            break;
273                        case $ord_var_c == 0x0A:
274                            $ascii .= '\n';
275                            break;
276                        case $ord_var_c == 0x0C:
277                            $ascii .= '\f';
278                            break;
279                        case $ord_var_c == 0x0D:
280                            $ascii .= '\r';
281                            break;
282
283                        case $ord_var_c == 0x22:
284                        case $ord_var_c == 0x2F:
285                        case $ord_var_c == 0x5C:
286                            // double quote, slash, slosh
287                            $ascii .= '\\'.$var{$c};
288                            break;
289
290                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
291                            // characters U-00000000 - U-0000007F (same as ASCII)
292                            $ascii .= $var{$c};
293                            break;
294
295                        case (($ord_var_c & 0xE0) == 0xC0):
296                            // characters U-00000080 - U-000007FF, mask 110XXXXX
297                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
298                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
299                            $c += 1;
300                            $utf16 = $this->utf82utf16($char);
301                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
302                            break;
303
304                        case (($ord_var_c & 0xF0) == 0xE0):
305                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
306                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
307                            $char = pack('C*', $ord_var_c,
308                                         ord($var{$c + 1}),
309                                         ord($var{$c + 2}));
310                            $c += 2;
311                            $utf16 = $this->utf82utf16($char);
312                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
313                            break;
314
315                        case (($ord_var_c & 0xF8) == 0xF0):
316                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
317                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
318                            $char = pack('C*', $ord_var_c,
319                                         ord($var{$c + 1}),
320                                         ord($var{$c + 2}),
321                                         ord($var{$c + 3}));
322                            $c += 3;
323                            $utf16 = $this->utf82utf16($char);
324                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
325                            break;
326
327                        case (($ord_var_c & 0xFC) == 0xF8):
328                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
329                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
330                            $char = pack('C*', $ord_var_c,
331                                         ord($var{$c + 1}),
332                                         ord($var{$c + 2}),
333                                         ord($var{$c + 3}),
334                                         ord($var{$c + 4}));
335                            $c += 4;
336                            $utf16 = $this->utf82utf16($char);
337                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
338                            break;
339
340                        case (($ord_var_c & 0xFE) == 0xFC):
341                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
342                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
343                            $char = pack('C*', $ord_var_c,
344                                         ord($var{$c + 1}),
345                                         ord($var{$c + 2}),
346                                         ord($var{$c + 3}),
347                                         ord($var{$c + 4}),
348                                         ord($var{$c + 5}));
349                            $c += 5;
350                            $utf16 = $this->utf82utf16($char);
351                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
352                            break;
353                    }
354                }
355
356                return '"'.$ascii.'"';
357
358            case 'array':
359               /*
360                * As per JSON spec if any array key is not an integer
361                * we must treat the the whole array as an object. We
362                * also try to catch a sparsely populated associative
363                * array with numeric keys here because some JS engines
364                * will create an array with empty indexes up to
365                * max_index which can cause memory issues and because
366                * the keys, which may be relevant, will be remapped
367                * otherwise.
368                *
369                * As per the ECMA and JSON specification an object may
370                * have any string as a property. Unfortunately due to
371                * a hole in the ECMA specification if the key is a
372                * ECMA reserved word or starts with a digit the
373                * parameter is only accessible using ECMAScript's
374                * bracket notation.
375                */
376
377                // treat as a JSON object
378                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
379                    $properties = array_map(array($this, 'name_value'),
380                                            array_keys($var),
381                                            array_values($var));
382
383                    foreach($properties as $property) {
384                        if(Services_JSON::isError($property)) {
385                            return $property;
386                        }
387                    }
388
389                    return '{' . join(',', $properties) . '}';
390                }
391
392                // treat it like a regular array
393                $elements = array_map(array($this, 'encode'), $var);
394
395                foreach($elements as $element) {
396                    if(Services_JSON::isError($element)) {
397                        return $element;
398                    }
399                }
400
401                return '[' . join(',', $elements) . ']';
402
403            case 'object':
404                $vars = get_object_vars($var);
405
406                $properties = array_map(array($this, 'name_value'),
407                                        array_keys($vars),
408                                        array_values($vars));
409
410                foreach($properties as $property) {
411                    if(Services_JSON::isError($property)) {
412                        return $property;
413                    }
414                }
415
416                return '{' . join(',', $properties) . '}';
417
418            default:
419                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
420                    ? 'null'
421                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
422        }
423    }
424
425   /**
426    * array-walking function for use in generating JSON-formatted name-value pairs
427    *
428    * @param    string  $name   name of key to use
429    * @param    mixed   $value  reference to an array element to be encoded
430    *
431    * @return   string  JSON-formatted name-value pair, like '"name":value'
432    * @access   private
433    */
434    function name_value($name, $value)
435    {
436        $encoded_value = $this->encode($value);
437
438        if(Services_JSON::isError($encoded_value)) {
439            return $encoded_value;
440        }
441
442        return $this->encode(strval($name)) . ':' . $encoded_value;
443    }
444
445   /**
446    * reduce a string by removing leading and trailing comments and whitespace
447    *
448    * @param    $str    string      string value to strip of comments and whitespace
449    *
450    * @return   string  string value stripped of comments and whitespace
451    * @access   private
452    */
453    function reduce_string($str)
454    {
455        $str = preg_replace(array(
456
457                // eliminate single line comments in '// ...' form
458                '#^\s*//(.+)$#m',
459
460                // eliminate multi-line comments in '/* ... */' form, at start of string
461                '#^\s*/\*(.+)\*/#Us',
462
463                // eliminate multi-line comments in '/* ... */' form, at end of string
464                '#/\*(.+)\*/\s*$#Us'
465
466            ), '', $str);
467
468        // eliminate extraneous space
469        return trim($str);
470    }
471
472   /**
473    * decodes a JSON string into appropriate variable
474    *
475    * @param    string  $str    JSON-formatted string
476    *
477    * @return   mixed   number, boolean, string, array, or object
478    *                   corresponding to given JSON input string.
479    *                   See argument 1 to Services_JSON() above for object-output behavior.
480    *                   Note that decode() always returns strings
481    *                   in ASCII or UTF-8 format!
482    * @access   public
483    */
484    function decode($str)
485    {
486        $str = $this->reduce_string($str);
487
488        switch (strtolower($str)) {
489            case 'true':
490                return true;
491
492            case 'false':
493                return false;
494
495            case 'null':
496                return null;
497
498            default:
499                $m = array();
500
501                if (is_numeric($str)) {
502                    // Lookie-loo, it's a number
503
504                    // This would work on its own, but I'm trying to be
505                    // good about returning integers where appropriate:
506                    // return (float)$str;
507
508                    // Return float or int, as appropriate
509                    return ((float)$str == (integer)$str)
510                        ? (integer)$str
511                        : (float)$str;
512
513                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
514                    // STRINGS RETURNED IN UTF-8 FORMAT
515                    $delim = substr($str, 0, 1);
516                    $chrs = substr($str, 1, -1);
517                    $utf8 = '';
518                    $strlen_chrs = strlen($chrs);
519
520                    for ($c = 0; $c < $strlen_chrs; ++$c) {
521
522                        $substr_chrs_c_2 = substr($chrs, $c, 2);
523                        $ord_chrs_c = ord($chrs{$c});
524
525                        switch (true) {
526                            case $substr_chrs_c_2 == '\b':
527                                $utf8 .= chr(0x08);
528                                ++$c;
529                                break;
530                            case $substr_chrs_c_2 == '\t':
531                                $utf8 .= chr(0x09);
532                                ++$c;
533                                break;
534                            case $substr_chrs_c_2 == '\n':
535                                $utf8 .= chr(0x0A);
536                                ++$c;
537                                break;
538                            case $substr_chrs_c_2 == '\f':
539                                $utf8 .= chr(0x0C);
540                                ++$c;
541                                break;
542                            case $substr_chrs_c_2 == '\r':
543                                $utf8 .= chr(0x0D);
544                                ++$c;
545                                break;
546
547                            case $substr_chrs_c_2 == '\\"':
548                            case $substr_chrs_c_2 == '\\\'':
549                            case $substr_chrs_c_2 == '\\\\':
550                            case $substr_chrs_c_2 == '\\/':
551                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
552                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
553                                    $utf8 .= $chrs{++$c};
554                                }
555                                break;
556
557                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
558                                // single, escaped unicode character
559                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
560                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
561                                $utf8 .= $this->utf162utf8($utf16);
562                                $c += 5;
563                                break;
564
565                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
566                                $utf8 .= $chrs{$c};
567                                break;
568
569                            case ($ord_chrs_c & 0xE0) == 0xC0:
570                                // characters U-00000080 - U-000007FF, mask 110XXXXX
571                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
572                                $utf8 .= substr($chrs, $c, 2);
573                                ++$c;
574                                break;
575
576                            case ($ord_chrs_c & 0xF0) == 0xE0:
577                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
578                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
579                                $utf8 .= substr($chrs, $c, 3);
580                                $c += 2;
581                                break;
582
583                            case ($ord_chrs_c & 0xF8) == 0xF0:
584                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
585                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
586                                $utf8 .= substr($chrs, $c, 4);
587                                $c += 3;
588                                break;
589
590                            case ($ord_chrs_c & 0xFC) == 0xF8:
591                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
592                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
593                                $utf8 .= substr($chrs, $c, 5);
594                                $c += 4;
595                                break;
596
597                            case ($ord_chrs_c & 0xFE) == 0xFC:
598                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
599                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
600                                $utf8 .= substr($chrs, $c, 6);
601                                $c += 5;
602                                break;
603
604                        }
605
606                    }
607
608                    return $utf8;
609
610                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
611                    // array, or object notation
612
613                    if ($str{0} == '[') {
614                        $stk = array(SERVICES_JSON_IN_ARR);
615                        $arr = array();
616                    } else {
617                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
618                            $stk = array(SERVICES_JSON_IN_OBJ);
619                            $obj = array();
620                        } else {
621                            $stk = array(SERVICES_JSON_IN_OBJ);
622                            $obj = new stdClass();
623                        }
624                    }
625
626                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
627                                           'where' => 0,
628                                           'delim' => false));
629
630                    $chrs = substr($str, 1, -1);
631                    $chrs = $this->reduce_string($chrs);
632
633                    if ($chrs == '') {
634                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
635                            return $arr;
636
637                        } else {
638                            return $obj;
639
640                        }
641                    }
642
643                    //print("\nparsing {$chrs}\n");
644
645                    $strlen_chrs = strlen($chrs);
646
647                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
648
649                        $top = end($stk);
650                        $substr_chrs_c_2 = substr($chrs, $c, 2);
651
652                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
653                            // found a comma that is not inside a string, array, etc.,
654                            // OR we've reached the end of the character list
655                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
656                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
657                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
658
659                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
660                                // we are in an array, so just push an element onto the stack
661                                array_push($arr, $this->decode($slice));
662
663                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
664                                // we are in an object, so figure
665                                // out the property name and set an
666                                // element in an associative array,
667                                // for now
668                                $parts = array();
669                               
670                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
671                                    // "name":value pair
672                                    $key = $this->decode($parts[1]);
673                                    $val = $this->decode($parts[2]);
674
675                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
676                                        $obj[$key] = $val;
677                                    } else {
678                                        $obj->$key = $val;
679                                    }
680                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
681                                    // name:value pair, where name is unquoted
682                                    $key = $parts[1];
683                                    $val = $this->decode($parts[2]);
684
685                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
686                                        $obj[$key] = $val;
687                                    } else {
688                                        $obj->$key = $val;
689                                    }
690                                }
691
692                            }
693
694                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
695                            // found a quote, and we are not inside a string
696                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
697                            //print("Found start of string at {$c}\n");
698
699                        } elseif (($chrs{$c} == $top['delim']) &&
700                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
701                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
702                            // found a quote, we're in a string, and it's not escaped
703                            // we know that it's not escaped becase there is _not_ an
704                            // odd number of backslashes at the end of the string so far
705                            array_pop($stk);
706                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
707
708                        } elseif (($chrs{$c} == '[') &&
709                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
710                            // found a left-bracket, and we are in an array, object, or slice
711                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
712                            //print("Found start of array at {$c}\n");
713
714                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
715                            // found a right-bracket, and we're in an array
716                            array_pop($stk);
717                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (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-brace, and we are in an array, object, or slice
722                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
723                            //print("Found start of object at {$c}\n");
724
725                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
726                            // found a right-brace, and we're in an object
727                            array_pop($stk);
728                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
729
730                        } elseif (($substr_chrs_c_2 == '/*') &&
731                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
732                            // found a comment start, and we are in an array, object, or slice
733                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
734                            $c++;
735                            //print("Found start of comment at {$c}\n");
736
737                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
738                            // found a comment end, and we're in one now
739                            array_pop($stk);
740                            $c++;
741
742                            for ($i = $top['where']; $i <= $c; ++$i)
743                                $chrs = substr_replace($chrs, ' ', $i, 1);
744
745                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
746
747                        }
748
749                    }
750
751                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
752                        return $arr;
753
754                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
755                        return $obj;
756
757                    }
758
759                }
760        }
761    }
762
763    /**
764     * @todo Ultimately, this should just call PEAR::isError()
765     */
766    function isError($data, $code = null)
767    {
768        if (class_exists('pear')) {
769            return PEAR::isError($data, $code);
770        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
771                                 is_subclass_of($data, 'services_json_error'))) {
772            return true;
773        }
774
775        return false;
776    }
777}
778
779if (class_exists('PEAR_Error')) {
780
781    class Services_JSON_Error extends PEAR_Error
782    {
783        function Services_JSON_Error($message = 'unknown error', $code = null,
784                                     $mode = null, $options = null, $userinfo = null)
785        {
786            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
787        }
788    }
789
790} else {
791
792    /**
793     * @todo Ultimately, this class shall be descended from PEAR_Error
794     */
795    class Services_JSON_Error
796    {
797        function Services_JSON_Error($message = 'unknown error', $code = null,
798                                     $mode = null, $options = null, $userinfo = null)
799        {
800
801        }
802    }
803
804}
805   
806?>
Note: See TracBrowser for help on using the repository browser.