cvs: pear /HTML_AJAX/AJAX Action.php Helper.php JSON.php Response.php Server.php
| From: | David Coallier | Date: | Thu, 09 Feb 2006 14:48:08 +0000 |
| Subject: | cvs: pear /HTML_AJAX/AJAX Action.php Helper.php JSON.php Response.php Server.php | ||
| Groups: | php.pear.cvs | ||
| Request: | Send a blank email to pear-cvs+get-38076@lists.php.net to get a copy of this message | ||
davidc Thu Feb 9 14:48:08 2006 UTC
Added files:
/pear/HTML_AJAX/AJAX Action.php Response.php
Modified files:
/pear/HTML_AJAX/AJAX Helper.php JSON.php Server.php
Log:
- View http://websvn.bluga.net/ for more infos about the
commits,
- Fixed 6569
http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/Helper.php?r1=1.2&r2=1.3&diff_format=u Index: pear/HTML_AJAX/AJAX/Helper.php diff -u pear/HTML_AJAX/AJAX/Helper.php:1.2 pear/HTML_AJAX/AJAX/Helper.php:1.3 --- pear/HTML_AJAX/AJAX/Helper.php:1.2 Thu Nov 3 13:54:37 2005 +++ pear/HTML_AJAX/AJAX/Helper.php Thu Feb 9 14:48:08 2006 @@ -33,7 +33,12 @@ * * @var array */ - var $jsLibraries = array('Util','Main','Request','HttpClient','Dispatcher','Behavior','Loading','JSON'); + var $jsLibraries = array('Util','Main','Request','HttpClient','Dispatcher','Behavior','Loading','JSON','iframe'); + + /** + * Remote class stubs to include + */ + var $stubs = array(); /** * Include all needed libraries, stubs, and set defaultServer @@ -42,8 +47,26 @@ */ function setupAJAX() { - $libs = implode(',',$this->jsLibraries); - $ret = "<script type='text/javascript' src='{$this->serverUrl}?client={$libs}'></script>\n"; + $libs = array(0=>array()); + foreach($this->jsLibraries as $library) { + if (is_array($library)) { + $libs[] = implode(',',$library); + } + else { + $libs[0][] = $library; + } + } + $libs[0] = implode(',',$libs[0]); + + $ret = ''; + foreach($libs as $list) { + $ret .= "<script type='text/javascript' src='{$this->serverUrl}?client={$list}'></script>\n"; + } + + if (count($this->stubs) > 0) { + $stubs = implode(',',$this->stubs); + $ret .= "<script type='text/javascript' src='{$this->serverUrl}?stub={$stubs}'></script>\n"; + } $ret .= $this->encloseInScript('HTML_AJAX.defaultServerUrl = '.$this->escape($this->serverUrl)); return $ret; } @@ -56,7 +79,7 @@ * @param string $style style tag of the loading div */ function loadingMessage($body, $class = 'HTML_AJAX_Loading', - $style = 'position: absolute; top: 0; right: 0; backgroundColor: red; width: 80px; padding: 4px; display: none') + $style = 'position: absolute; top: 0; right: 0; background-color: red; width: 80px; padding: 4px; display: none') { return "<div id='HTML_AJAX_LOADING' class='{$class}' style=\"{$style}\">{$body}</div>\n"; } http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/JSON.php?r1=1.9&r2=1.10&diff_format=u Index: pear/HTML_AJAX/AJAX/JSON.php diff -u pear/HTML_AJAX/AJAX/JSON.php:1.9 pear/HTML_AJAX/AJAX/JSON.php:1.10 --- pear/HTML_AJAX/AJAX/JSON.php:1.9 Thu Nov 3 13:54:37 2005 +++ pear/HTML_AJAX/AJAX/JSON.php Thu Feb 9 14:48:08 2006 @@ -1,437 +1,796 @@ <?php - // +----------------------------------------------------------------------+ - // | PHP version 4 | - // +----------------------------------------------------------------------+ - // | Copyright (c) 2005 Michal Migurski | - // +----------------------------------------------------------------------+ - // | This source file is subject to version 3.0 of the PHP license, | - // | that is bundled with this package in the file LICENSE, and is | - // | available through the world-wide-web at the following url: | - // | http://www.php.net/license/3_0.txt. | - // | If you did not receive a copy of the PHP license and are unable to | - // | obtain it through the world-wide-web, please send a note to | - // | license@php.net so we can mail you a copy immediately. | - // +----------------------------------------------------------------------+ - // | Author: Michal Migurski, mike-json[at]teczno[dot]com | - // | with contributions from: | - // | Matt Knapp, mdknapp[at]gmail[dot]com | - // | Brett Stimmerman, brettstimmerman[at]gmail[dot]com | - // +----------------------------------------------------------------------+ - // - // $Id: JSON.php,v 1.9 2005/11/03 13:54:37 davidc Exp $ - /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ - - define('HTML_AJAX_JSON_SLICE', 1); - define('HTML_AJAX_JSON_IN_STR', 2); - define('HTML_AJAX_JSON_IN_ARR', 4); - define('HTML_AJAX_JSON_IN_OBJ', 8); - define('HTML_AJAX_JSON_IN_CMT', 16); - define('HTML_AJAX_JSON_LOOSE_TYPE', 10); - define('HTML_AJAX_JSON_STRICT_TYPE', 11); - - /** HTML_AJAX_JSON - * Conversion to and from HTML_AJAX_JSON format. - * See http://json.org for details. +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ +/** + * This is an embedded version of HTML_AJAX_JSON since it has yet to have a PEAR release + * it has been renamed to HTML_AJAX_JSON so no problems will be caused by an eventual release + * Feel free to report bugs against it to HTML_AJAX + */ + +/** + * Converts to and from JSON format. + * + * JSON (JavaScript Object Notation) is a lightweight data-interchange + * format. It is easy for humans to read and write. It is easy for machines + * to parse and generate. It is based on a subset of the JavaScript + * Programming Language, Standard ECMA-262 3rd Edition - December 1999. + * This feature can also be found in Python. JSON is a text format that is + * completely language independent but uses conventions that are familiar + * to programmers of the C-family of languages, including C, C++, C#, Java, + * JavaScript, Perl, TCL, and many others. These properties make JSON an + * ideal data-interchange language. + * + * This package provides a simple encoder and decoder for JSON notation. It + * is intended for use with client-side Javascript applications that make + * use of HTTPRequest to perform server communication functions - data can + * be encoded into JSON notation for use in a client-side javascript, or + * decoded from incoming Javascript requests. JSON format is native to + * Javascript, and can be directly eval()'ed with no further parsing + * overhead + * + * All strings should be in ASCII or UTF-8 format! + * + * LICENSE: Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: Redistributions of source code must retain the + * above copyright notice, this list of conditions and the following + * disclaimer. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * @category + * @package HTML_AJAX_JSON + * @author Michal Migurski <mike-json@teczno.com> + * @author Matt Knapp <mdknapp[at]gmail[dot]com> + * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> + * @copyright 2005 Michal Migurski + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for HTML_AJAX_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for HTML_AJAX_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * <code> + * // create a new instance of HTML_AJAX_JSON + * $json = new HTML_AJAX_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * </code> + */ +class HTML_AJAX_JSON +{ + /** + * constructs a new JSON instance * - * note all strings should be in ASCII or UTF-8 format! + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() */ - class HTML_AJAX_JSON + function HTML_AJAX_JSON($use = 0) { - /** function HTML_AJAX_JSON - * constructor - * - * @param use int object behavior: when encoding or decoding, - * be loose or strict about object/array usage - * - * possible values: - * HTML_AJAX_JSON_STRICT_TYPE - strict typing, default - * "{...}" syntax creates objects in decode - * HTML_AJAX_JSON_LOOSE_TYPE - loose typing - * "{...}" syntax creates associative arrays in decode - */ - function HTML_AJAX_JSON($use=HTML_AJAX_JSON_STRICT_TYPE) - { - $this->use = $use; + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); } - /** function encode - * encode an arbitrary variable into HTML_AJAX_JSON format - * - * @param var mixed any number, boolean, string, array, or object to be encoded. - * see argument 1 to HTML_AJAX_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return string HTML_AJAX_JSON string representation of input var - */ - function encode($var) - { - switch(gettype($var)) { - case 'boolean': - return $var ? 'true' : 'false'; + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $ut8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to HTML_AJAX_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; - case 'NULL': - return 'null'; + case 'double': + case 'float': + return (float) $var; - case 'integer': - return sprintf('%d', $var); + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { - case 'double': - case 'float': - return sprintf('%f', $var); + $ord_var_c = ord($var{$c}); - case 'string': // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT - $ascii = ''; - $strlen_var = strlen($var); - - for($c = 0; $c < $strlen_var; $c++) { - - $ord_var_c = ord($var{$c}); - - if($ord_var_c == 0x08) { + switch (true) { + case $ord_var_c == 0x08: $ascii .= '\b'; - - } elseif($ord_var_c == 0x09) { + break; + case $ord_var_c == 0x09: $ascii .= '\t'; - - } elseif($ord_var_c == 0x0A) { + break; + case $ord_var_c == 0x0A: $ascii .= '\n'; - - } elseif($ord_var_c == 0x0C) { + break; + case $ord_var_c == 0x0C: $ascii .= '\f'; - - } elseif($ord_var_c == 0x0D) { + break; + case $ord_var_c == 0x0D: $ascii .= '\r'; - - } elseif(($ord_var_c == 0x22) || ($ord_var_c == 0x2F) || ($ord_var_c == 0x5C)) { - $ascii .= '\\'.$var{$c}; // double quote, slash, slosh - - } elseif(($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)) { + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): // characters U-00000000 - U-0000007F (same as ASCII) - $ascii .= $var{$c}; // most normal ASCII chars - - } elseif(($ord_var_c & 0xE0) == 0xC0) { - // characters U-00000080 - U-000007FF, mask 110XXXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1})); $c+=1; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } elseif(($ord_var_c & 0xF0) == 0xE0) { - // characters U-00000800 - U-0000FFFF, mask 1110XXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2})); $c+=2; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } elseif(($ord_var_c & 0xF8) == 0xF0) { - // characters U-00010000 - U-001FFFFF, mask 11110XXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3})); $c+=3; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } elseif(($ord_var_c & 0xFC) == 0xF8) { - // characters U-00200000 - U-03FFFFFF, mask 111110XX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3}), ord($var{$c+4})); $c+=4; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } elseif(($ord_var_c & 0xFE) == 0xFC) { - // characters U-04000000 - U-7FFFFFFF, mask 1111110X, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3}), ord($var{$c+4}), ord($var{$c+5})); $c+=5; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) + if(HTML_AJAX_JSON::isError($property)) + return $property; - return sprintf('"%s"', $ascii); - - case 'array': - // As per HTML_AJAX_JSON spec if any array key is not an integer we must treat the the whole array as an object. - // We also try to catch a sparsely populated associative array with numeric keys here because some JS - // engines will create an array with empty indexes up to max_index which can cause memory issues - // and because the keys, which may be relevant, will be remapped otherwise. - // - // As per the ECMA and HTML_AJAX_JSON specification an object may have any string as a property. Unfortunately due to a - // hole in the ECMA specification if the key is a ECMA reserved word or starts with a digit the parameter is only - // accessible using ECMAScript's bracket notation. - - // treat as a HTML_AJAX_JSON object - if(is_array($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) - return sprintf('{%s}', join(',', array_map(array($this, 'name_value'), array_keys($var), array_values($var)))); - - // treat it like a regular array - return sprintf('[%s]', join(',', array_map(array($this, 'encode'), $var))); - - case 'object': - $vars = get_object_vars($var); - return sprintf('{%s}', join(',', array_map(array($this, 'name_value'), array_keys($vars), array_values($vars)))); - - default: - return ''; - } - } - - /** function enc - * alias for encode() - */ - function enc($var) - { - return $this->encode($var); - } - - /** function name_value - * array-walking function for use in generating HTML_AJAX_JSON-formatted name-value pairs - * - * @param name string name of key to use - * @param value mixed reference to an array element to be encoded - * - * @return string HTML_AJAX_JSON-formatted name-value pair, like '"name":value' - */ - function name_value($name, $value) - { - return (sprintf("%s:%s", $this->encode(strval($name)), $this->encode($value))); - } + return '{' . join(',', $properties) . '}'; + } - /** function reduce_string - * reduce a string by removing leading and trailing comments and whitespace - * - * @param str string string value to strip of comments and whitespace - * - * @return string string value stripped of comments and whitespace - */ - function reduce_string($str) - { - $str = preg_replace('#^\s*//(.+)$#m', '', $str); // eliminate single line comments in '// ...' form - $str = preg_replace('#^\s*/\*(.+)\*/#Us', '', $str); // eliminate multi-line comments in '/* ... */' form, at start of string - $str = preg_replace('#/\*(.+)\*/\s*$#Us', '', $str); // eliminate multi-line comments in '/* ... */' form, at end of string - $str = trim($str); // eliminate extraneous space - - return $str; - } + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) + if(HTML_AJAX_JSON::isError($element)) + return $element; + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); - /** function decode - * decode a HTML_AJAX_JSON string into appropriate variable - * - * @param str string HTML_AJAX_JSON-formatted string - * - * @return mixed number, boolean, string, array, or object - * corresponding to given HTML_AJAX_JSON input string. - * see argument 1 to HTML_AJAX_JSON() above for object-output behavior. - * note that decode() always returns strings - * in ASCII or UTF-8 format! - */ - function decode($str) - { - $str = $this->reduce_string($str); - - switch(strtolower($str)) { - case 'true': - return true; - - case 'false': - return false; - - case 'null': - return null; - - default: - if(is_numeric($str)) { // Lookie-loo, it's a number - // return (float)$str; // This would work on its own, but I'm trying to be good about returning integers where appropriate - return ((float)$str == (integer)$str) - ? (integer)$str - : (float)$str; - - } elseif(preg_match('/^".+"$/s', $str) || preg_match('/^\'.+\'$/s', $str)) { // STRINGS RETURNED IN UTF-8 FORMAT - $delim = substr($str, 0, 1); - $chrs = substr($str, 1, -1); - $utf8 = ''; - $strlen_chrs = strlen($chrs); - - for($c = 0; $c < $strlen_chrs; $c++) { - - $substr_chrs_c_2 = substr($chrs, $c, 2); - $ord_chrs_c = ord($chrs{$c}); + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) + if(HTML_AJAX_JSON::isError($property)) + return $property; + + return '{' . join(',', $properties) . '}'; - if($substr_chrs_c_2 == '\b') { - $utf8 .= chr(0x08); $c+=1; - - } elseif($substr_chrs_c_2 == '\t') { - $utf8 .= chr(0x09); $c+=1; - - } elseif($substr_chrs_c_2 == '\n') { - $utf8 .= chr(0x0A); $c+=1; - - } elseif($substr_chrs_c_2 == '\f') { - $utf8 .= chr(0x0C); $c+=1; + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new HTML_AJAX_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } - } elseif($substr_chrs_c_2 == '\r') { - $utf8 .= chr(0x0D); $c+=1; + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(HTML_AJAX_JSON::isError($encoded_value)) + return $encoded_value; - } elseif(($delim == '"') && (($substr_chrs_c_2 == '\\"') || ($substr_chrs_c_2 == '\\\\') || ($substr_chrs_c_2 == '\\/'))) { - $utf8 .= $chrs{++$c}; + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', - } elseif(($delim == "'") && (($substr_chrs_c_2 == '\\\'') || ($substr_chrs_c_2 == '\\\\') || ($substr_chrs_c_2 == '\\/'))) { - $utf8 .= $chrs{++$c}; + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', - } elseif(preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6))) { // single, escaped unicode character - $utf16 = chr(hexdec(substr($chrs, ($c+2), 2))) . chr(hexdec(substr($chrs, ($c+4), 2))); - $utf8 .= mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); - $c+=5; + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' - } elseif(($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F)) { - $utf8 .= $chrs{$c}; + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to HTML_AJAX_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); - } elseif(($ord_chrs_c & 0xE0) == 0xC0) { - // characters U-00000080 - U-000007FF, mask 110XXXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 2); $c += 1; - - } elseif(($ord_chrs_c & 0xF0) == 0xE0) { - // characters U-00000800 - U-0000FFFF, mask 1110XXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 3); $c += 2; - - } elseif(($ord_chrs_c & 0xF8) == 0xF0) { - // characters U-00010000 - U-001FFFFF, mask 11110XXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 4); $c += 3; - - } elseif(($ord_chrs_c & 0xFC) == 0xF8) { - // characters U-00200000 - U-03FFFFFF, mask 111110XX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 5); $c += 4; - - } elseif(($ord_chrs_c & 0xFE) == 0xFC) { - // characters U-04000000 - U-7FFFFFFF, mask 1111110X, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 6); $c += 5; + switch (strtolower($str)) { + case 'true': + return true; - } + case 'false': + return false; + + case 'null': + return null; + + default: + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + } - - return $utf8; + + } - } elseif(preg_match('/^\[.*\]$/s', $str) || preg_match('/^{.*}$/s', $str)) { // array, or object notation - - if($str{0} == '[') { - $stk = array(HTML_AJAX_JSON_IN_ARR); - $arr = array(); + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); } else { - if($this->use == HTML_AJAX_JSON_LOOSE_TYPE) { - $stk = array(HTML_AJAX_JSON_IN_OBJ); - $obj = array(); - } else { - $stk = array(HTML_AJAX_JSON_IN_OBJ); - $obj = new stdClass(); - } + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); } - - array_push($stk, array('what' => HTML_AJAX_JSON_SLICE, 'where' => 0, 'delim' => false)); - $chrs = substr($str, 1, -1); - $chrs = $this->reduce_string($chrs); - - if($chrs == '') { - if(reset($stk) == HTML_AJAX_JSON_IN_ARR) { - return $arr; + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); - } else { - return $obj; + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; - } } + } - //print("\nparsing {$chrs}\n"); - - $strlen_chrs = strlen($chrs); - - for($c = 0; $c <= $strlen_chrs; $c++) { - - $top = end($stk); - $substr_chrs_c_2 = substr($chrs, $c, 2); - - if(($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == HTML_AJAX_JSON_SLICE))) { // found a comma that is not inside a string, array, etc., OR we've reached the end of the character list - $slice = substr($chrs, $top['where'], ($c - $top['where'])); - array_push($stk, array('what' => HTML_AJAX_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); - //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - if(reset($stk) == HTML_AJAX_JSON_IN_ARR) { // we are in an array, so just push an element onto the stack - array_push($arr, $this->decode($slice)); - - } elseif(reset($stk) == HTML_AJAX_JSON_IN_OBJ) { // we are in an object, so figure out the property name and set an element in an associative array, for now - if(preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { // "name":value pair - $key = $this->decode($parts[1]); - $val = $this->decode($parts[2]); - - if($this->use == HTML_AJAX_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } elseif(preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { // name:value pair, where name is unquoted - $key = $parts[1]; - $val = $this->decode($parts[2]); - - if($this->use == HTML_AJAX_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; } - } - - } elseif((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != HTML_AJAX_JSON_IN_STR)) { // found a quote, and we are not inside a string - array_push($stk, array('what' => HTML_AJAX_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); - //print("Found start of string at {$c}\n"); - - } elseif(($chrs{$c} == $top['delim']) && ($top['what'] == HTML_AJAX_JSON_IN_STR) && (($chrs{$c - 1} != "\\") || ($chrs{$c - 1} == "\\" && $chrs{$c - 2} == "\\"))) { // found a quote, we're in a string, and it's not escaped - array_pop($stk); - //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); - - } elseif(($chrs{$c} == '[') && in_array($top['what'], array(HTML_AJAX_JSON_SLICE, HTML_AJAX_JSON_IN_ARR, HTML_AJAX_JSON_IN_OBJ))) { // found a left-bracket, and we are in an array, object, or slice - array_push($stk, array('what' => HTML_AJAX_JSON_IN_ARR, 'where' => $c, 'delim' => false)); - //print("Found start of array at {$c}\n"); - - } elseif(($chrs{$c} == ']') && ($top['what'] == HTML_AJAX_JSON_IN_ARR)) { // found a right-bracket, and we're in an array - array_pop($stk); - //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif(($chrs{$c} == '{') && in_array($top['what'], array(HTML_AJAX_JSON_SLICE, HTML_AJAX_JSON_IN_ARR, HTML_AJAX_JSON_IN_OBJ))) { // found a left-brace, and we are in an array, object, or slice - array_push($stk, array('what' => HTML_AJAX_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); - //print("Found start of object at {$c}\n"); - - } elseif(($chrs{$c} == '}') && ($top['what'] == HTML_AJAX_JSON_IN_OBJ)) { // found a right-brace, and we're in an object - array_pop($stk); - //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif(($substr_chrs_c_2 == '/*') && in_array($top['what'], array(HTML_AJAX_JSON_SLICE, HTML_AJAX_JSON_IN_ARR, HTML_AJAX_JSON_IN_OBJ))) { // found a comment start, and we are in an array, object, or slice - array_push($stk, array('what' => HTML_AJAX_JSON_IN_CMT, 'where' => $c, 'delim' => false)); - $c++; - //print("Found start of comment at {$c}\n"); - - } elseif(($substr_chrs_c_2 == '*/') && ($top['what'] == HTML_AJAX_JSON_IN_CMT)) { // found a comment end, and we're in one now - array_pop($stk); - $c++; - - for($i = $top['where']; $i <= $c; $i++) - $chrs = substr_replace($chrs, ' ', $i, 1); - - //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - + } - - } - - if(reset($stk) == HTML_AJAX_JSON_IN_ARR) { - return $arr; - - } elseif(reset($stk) == HTML_AJAX_JSON_IN_OBJ) { - return $obj; - + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + (($chrs{$c - 1} != '\\') || + ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { + // found a quote, we're in a string, and it's not escaped + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + } } - } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } } - - /** function dec - * alias for decode() - */ - function dec($var) + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('pear_error')) { + + class HTML_AJAX_JSON_Error extends PEAR_Error + { + function HTML_AJAX_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) { - return $this->decode($var); + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class HTML_AJAX_JSON_Error + { + function HTML_AJAX_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + } } + +} + ?> http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/Server.php?r1=1.9&r2=1.10&diff_format=u Index: pear/HTML_AJAX/AJAX/Server.php diff -u pear/HTML_AJAX/AJAX/Server.php:1.9 pear/HTML_AJAX/AJAX/Server.php:1.10 --- pear/HTML_AJAX/AJAX/Server.php:1.9 Thu Nov 3 13:54:37 2005 +++ pear/HTML_AJAX/AJAX/Server.php Thu Feb 9 14:48:08 2006 @@ -104,15 +104,20 @@ 'all' => 'HTML_AJAX.js', 'html_ajax' => 'HTML_AJAX.js', 'html_ajax_lite'=> 'HTML_AJAX_lite.js', - 'json' => 'JSON.js', + 'json' => 'serializer/JSON.js', 'request' => 'Request.js', - 'main' => 'Main.js', + 'main' => array('Main.js','clientPool.js'), 'httpclient' => 'HttpClient.js', 'dispatcher' => 'Dispatcher.js', 'util' => 'util.js', 'loading' => 'Loading.js', - 'phpserializer' => 'serialize.js', - 'behavior' => array('behavior.js','cssQuery-p.js') + 'phpserializer' => 'serializer/phpSerializer.js', + 'urlserializer' => 'serializer/UrlSerializer.js', + 'haserializer' => 'serializer/haSerializer.js', + 'priorityqueue' => 'priorityQueue.js', + 'clientpool' => 'clientPool.js', + 'iframe' => 'IframeXHR.js', + 'behavior' => array('behavior/behavior.js','behavior/cssQuery-p.js') ); /** @@ -134,14 +139,22 @@ /** * Constructor creates the HTML_AJAX instance - * - * @todo: verify that PHP_SELF always does what we want */ function HTML_AJAX_Server() { $this->ajax =& new HTML_AJAX(); - $this->ajax->serverUrl = $_SERVER['PHP_SELF']; + // parameters for HTML::AJAX + $parameters = array('stub', 'client'); + + // keep in the query string all the parameters that don't belong to AJAX + // we remove all string like "parameter=something&". Final '&' can also + // be '&' (to be sure) and is optional. '=something' is optional too. + $querystring = preg_replace('/(' . join('|', $parameters) . ')(?:=[^&]*(?:&(?:amp;)?|$))?/', '', $_SERVER['QUERY_STRING']); + + // call the server with this query string + $this->ajax->serverUrl = htmlentities($_SERVER['PHP_SELF']) .'?'. htmlentities($querystring); + $methods = get_class_methods($this); foreach($methods as $method) { if (preg_match('/^init([a-zA-Z0-9_]+)$/',$method,$match)) { @@ -153,15 +166,18 @@ /** * Handle a client request, either generating a client or having HTML_AJAX handle the request * - * @return string generated client or ajax response + * @return boolean true if request was handled, false otherwise */ function handleRequest() { if ($this->options == true) { $this->_loadOptions(); } + //basically a hook for iframe but allows processing of data earlier + $this->ajax->populatePayload(); if (!isset($_GET['c']) && (count($this->options['client']) > 0 || count($this->options['stub']) > 0) ) { - return $this->generateClient(); + $this->generateClient(); + return true; } else { $this->_init($this->_cleanIdentifier($_GET['c'])); return $this->ajax->handleRequest(); @@ -200,10 +216,11 @@ * @param object $instance an external class with initClassName methods */ function registerInitObject(&$instance) { + $instance->server =& $this; $methods = get_class_methods($instance); foreach($methods as $method) { if (preg_match('/^init([a-zA-Z0-9_]+)$/',$method,$match)) { - $this->_initLookup[$match[1]] = array(&$instance,$method); + $this->_initLookup[strtolower($match[1])] = array(&$instance,$method); } } } @@ -216,6 +233,7 @@ function generateClient() { $headers = array(); + ob_start(); // create a list list of js files were going to need to output @@ -349,12 +367,14 @@ // were outputting content, add our length header and send the output $length = ob_get_length(); - if ($length > 0) { + $output = ob_get_contents(); + ob_end_clean(); + if ($length > 0 && $this->ajax->_sendContentLength()) { $headers['Content-Length'] = $length; } $headers['Content-Type'] = 'text/javascript; charset=utf-8'; - call_user_func($this->ajax->_callbacks['headers'],$headers); - ob_end_flush(); + call_user_func($this->ajax->_callbacks['headers'], $headers); + echo($output); } /** @@ -383,7 +403,12 @@ function clientJsLocation() { if (!$this->clientJsLocation) { - return '@data-dir@'.DIRECTORY_SEPARATOR.'HTML_AJAX'.DIRECTORY_SEPARATOR.'js'.DIRECTORY_SEPARATOR; + $path = '@data-dir@'.DIRECTORY_SEPARATOR.'HTML_AJAX'.DIRECTORY_SEPARATOR.'js'.DIRECTORY_SEPARATOR; + if(strpos($path, '@'.'data-dir@') === 0) + { + $path = realpath(dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'js').DIRECTORY_SEPARATOR; + } + return $path; } else { return $this->clientJsLocation; } http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/Action.php?view=markup&rev=1.1 Index: pear/HTML_AJAX/AJAX/Action.php +++ pear/HTML_AJAX/AJAX/Action.php <?php /** * OO AJAX Implementation for PHP * * @category HTML * @package AJAX * @author Elizabeth Smith <auroraeosrose@gmail.com> * @copyright 2005 Elizabeth Smith * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version Release: @package_version@ */ /** * Require the response class and json serializer */ require_once 'HTML/AJAX/Response.php'; require_once 'HTML/AJAX/Serializer/JSON.php'; /** * Helper Class for creating information that can be properly serialized and used by * the haaction serializer that eliminates the need for php users to write javascript * for dealing with the information returned by an ajax method - instead the javascript * is basically created for them */ class HTML_AJAX_Action extends HTML_AJAX_Response { /** * about * @var $contentType type */ var $contentType = 'application/html_ajax_action'; /** * about * @var $_actions type */ var $_actions; /** * public function prependAttr() * * adds $data to the end of the attribute in the item identified by $id ($id can also be a class) */ function prependAttr($id, $attribute, $data = NULL) { if(!is_null($data)) { $attribute = array($attribute => $data); } $this->_actions[] = array( 'action' => 'prepend', 'id' => $id, 'attributes' => $attribute, 'data' => $data, ); return; } /** * public function appendAttr() * * adds $data to the beginning of the attribute in the item identified by $id ($id can also be a class) */ function appendAttr($id, $attribute, $data = NULL) { if(!is_null($data)) { $attribute = array($attribute => $data); } $this->_actions[] = array( 'action' => 'append', 'id' => $id, 'attributes' => $attribute, ); return; } /** * public function assignAttr() * * assigns $data to the value of the attribute in the item identified by $id ($id can also be a class) * if attribute already exists, the entire value of the attribute will be replaced */ function assignAttr($id, $attribute, $data = NULL) { if(!is_null($data)) { $attribute = array($attribute => $data); } $this->_actions[] = array( 'action' => 'assign', 'id' => $id, 'attributes' => $attribute, ); return; } /** * public function clearAttr() * * removes an attr from the item identified by $id ($id can also be a class) */ function clearAttr($id, $attribute) { if(!is_array($attribute)) { $attribute = array($attribute); } $this->_actions[] = array( 'action' => 'clear', 'id' => $id, 'attributes' => $attribute, ); return; } /** * public function createNode() * * higher level dom manipulation - creates a new node to insert into the dom * type can be append, prepend, or insert - for insert id should be the id of * the element you're inserting before, for append or prepend should be the id * of the element you're inserting into */ function createNode($id, $tag, $attribute, $type = 'append') { $types = array('append', 'prepend', 'insertBefore', 'insertAfter'); if(!in_array($type, $types)) { $type = 'append'; } settype($attribute, 'array'); $this->_actions[] = array( 'action' => 'create', 'id' => $id, 'tag' => $tag, 'attributes' => $attribute, 'type' => $type, ); return; } /** * public function replaceNode() * * higher level dom manipulation - replaces one node with another * This can be used to replace a div with a form for inline editing * use innerHtml attribute to change inside text */ function replaceNode($id, $tag, $attribute) { $this->_actions[] = array( 'action' => 'replace', 'id' => $id, 'tag' => $tag, 'attributes' => settype($attribute, 'array'), ); return; } /** * public function removeNode() * * higher level dom manipulation - deletes node from the dom */ function removeNode($id) { $this->_actions[] = array( 'action' => 'remove', 'id' => $id, ); return; } /** * public function insertScript() * * adds straight javascript */ function insertScript($data) { $this->_actions[] = array( 'action' => 'script', 'data' => $data, ); return; } /** * public function insertAlert() * * adds javascript alert */ function insertAlert($data) { $this->_actions[] = array( 'action' => 'alert', 'data' => $data, ); return; } /** * public function getPayload() * * something */ function getPayload() { $serializer = new HTML_AJAX_Serializer_JSON(); return $serializer->serialize($this->_actions); } } ?> http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/Response.php?view=markup&rev=1.1 Index: pear/HTML_AJAX/AJAX/Response.php +++ pear/HTML_AJAX/AJAX/Response.php <?php /** * OO AJAX Implementation for PHP * * @category HTML * @package AJAX * @author Elizabeth Smith <auroraeosrose@gmail.com> * @copyright 2005 Elizabeth Smith * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version Release: @package_version@ */ /** * Require the main AJAX library */ require_once 'HTML/AJAX.php'; /** * Josh says we need this, I don't know why yet */ class HTML_AJAX_Response { /** * about * @var $contentType type */ var $contentType = 'text/plain'; /** * about * @var $payload type */ var $payload = ''; /** * public function getContentType() * * gets the right content type? */ function getContentType() { return $this->contentType; } /** * public function getPayload() * * something */ function getPayload() { return $this->payload; } } ?>
http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/Helper.php?r1=1.2&r2=1.3&diff_format=u Index: pear/HTML_AJAX/AJAX/Helper.php diff -u pear/HTML_AJAX/AJAX/Helper.php:1.2 pear/HTML_AJAX/AJAX/Helper.php:1.3 --- pear/HTML_AJAX/AJAX/Helper.php:1.2 Thu Nov 3 13:54:37 2005 +++ pear/HTML_AJAX/AJAX/Helper.php Thu Feb 9 14:48:08 2006 @@ -33,7 +33,12 @@ * * @var array */ - var $jsLibraries = array('Util','Main','Request','HttpClient','Dispatcher','Behavior','Loading','JSON'); + var $jsLibraries = array('Util','Main','Request','HttpClient','Dispatcher','Behavior','Loading','JSON','iframe'); + + /** + * Remote class stubs to include + */ + var $stubs = array(); /** * Include all needed libraries, stubs, and set defaultServer @@ -42,8 +47,26 @@ */ function setupAJAX() { - $libs = implode(',',$this->jsLibraries); - $ret = "<script type='text/javascript' src='{$this->serverUrl}?client={$libs}'></script>\n"; + $libs = array(0=>array()); + foreach($this->jsLibraries as $library) { + if (is_array($library)) { + $libs[] = implode(',',$library); + } + else { + $libs[0][] = $library; + } + } + $libs[0] = implode(',',$libs[0]); + + $ret = ''; + foreach($libs as $list) { + $ret .= "<script type='text/javascript' src='{$this->serverUrl}?client={$list}'></script>\n"; + } + + if (count($this->stubs) > 0) { + $stubs = implode(',',$this->stubs); + $ret .= "<script type='text/javascript' src='{$this->serverUrl}?stub={$stubs}'></script>\n"; + } $ret .= $this->encloseInScript('HTML_AJAX.defaultServerUrl = '.$this->escape($this->serverUrl)); return $ret; } @@ -56,7 +79,7 @@ * @param string $style style tag of the loading div */ function loadingMessage($body, $class = 'HTML_AJAX_Loading', - $style = 'position: absolute; top: 0; right: 0; backgroundColor: red; width: 80px; padding: 4px; display: none') + $style = 'position: absolute; top: 0; right: 0; background-color: red; width: 80px; padding: 4px; display: none') { return "<div id='HTML_AJAX_LOADING' class='{$class}' style=\"{$style}\">{$body}</div>\n"; } http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/JSON.php?r1=1.9&r2=1.10&diff_format=u Index: pear/HTML_AJAX/AJAX/JSON.php diff -u pear/HTML_AJAX/AJAX/JSON.php:1.9 pear/HTML_AJAX/AJAX/JSON.php:1.10 --- pear/HTML_AJAX/AJAX/JSON.php:1.9 Thu Nov 3 13:54:37 2005 +++ pear/HTML_AJAX/AJAX/JSON.php Thu Feb 9 14:48:08 2006 @@ -1,437 +1,796 @@ <?php - // +----------------------------------------------------------------------+ - // | PHP version 4 | - // +----------------------------------------------------------------------+ - // | Copyright (c) 2005 Michal Migurski | - // +----------------------------------------------------------------------+ - // | This source file is subject to version 3.0 of the PHP license, | - // | that is bundled with this package in the file LICENSE, and is | - // | available through the world-wide-web at the following url: | - // | http://www.php.net/license/3_0.txt. | - // | If you did not receive a copy of the PHP license and are unable to | - // | obtain it through the world-wide-web, please send a note to | - // | license@php.net so we can mail you a copy immediately. | - // +----------------------------------------------------------------------+ - // | Author: Michal Migurski, mike-json[at]teczno[dot]com | - // | with contributions from: | - // | Matt Knapp, mdknapp[at]gmail[dot]com | - // | Brett Stimmerman, brettstimmerman[at]gmail[dot]com | - // +----------------------------------------------------------------------+ - // - // $Id: JSON.php,v 1.9 2005/11/03 13:54:37 davidc Exp $ - /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ - - define('HTML_AJAX_JSON_SLICE', 1); - define('HTML_AJAX_JSON_IN_STR', 2); - define('HTML_AJAX_JSON_IN_ARR', 4); - define('HTML_AJAX_JSON_IN_OBJ', 8); - define('HTML_AJAX_JSON_IN_CMT', 16); - define('HTML_AJAX_JSON_LOOSE_TYPE', 10); - define('HTML_AJAX_JSON_STRICT_TYPE', 11); - - /** HTML_AJAX_JSON - * Conversion to and from HTML_AJAX_JSON format. - * See http://json.org for details. +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ +/** + * This is an embedded version of HTML_AJAX_JSON since it has yet to have a PEAR release + * it has been renamed to HTML_AJAX_JSON so no problems will be caused by an eventual release + * Feel free to report bugs against it to HTML_AJAX + */ + +/** + * Converts to and from JSON format. + * + * JSON (JavaScript Object Notation) is a lightweight data-interchange + * format. It is easy for humans to read and write. It is easy for machines + * to parse and generate. It is based on a subset of the JavaScript + * Programming Language, Standard ECMA-262 3rd Edition - December 1999. + * This feature can also be found in Python. JSON is a text format that is + * completely language independent but uses conventions that are familiar + * to programmers of the C-family of languages, including C, C++, C#, Java, + * JavaScript, Perl, TCL, and many others. These properties make JSON an + * ideal data-interchange language. + * + * This package provides a simple encoder and decoder for JSON notation. It + * is intended for use with client-side Javascript applications that make + * use of HTTPRequest to perform server communication functions - data can + * be encoded into JSON notation for use in a client-side javascript, or + * decoded from incoming Javascript requests. JSON format is native to + * Javascript, and can be directly eval()'ed with no further parsing + * overhead + * + * All strings should be in ASCII or UTF-8 format! + * + * LICENSE: Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: Redistributions of source code must retain the + * above copyright notice, this list of conditions and the following + * disclaimer. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * @category + * @package HTML_AJAX_JSON + * @author Michal Migurski <mike-json@teczno.com> + * @author Matt Knapp <mdknapp[at]gmail[dot]com> + * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> + * @copyright 2005 Michal Migurski + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for HTML_AJAX_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for HTML_AJAX_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for HTML_AJAX_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * <code> + * // create a new instance of HTML_AJAX_JSON + * $json = new HTML_AJAX_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * </code> + */ +class HTML_AJAX_JSON +{ + /** + * constructs a new JSON instance * - * note all strings should be in ASCII or UTF-8 format! + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() */ - class HTML_AJAX_JSON + function HTML_AJAX_JSON($use = 0) { - /** function HTML_AJAX_JSON - * constructor - * - * @param use int object behavior: when encoding or decoding, - * be loose or strict about object/array usage - * - * possible values: - * HTML_AJAX_JSON_STRICT_TYPE - strict typing, default - * "{...}" syntax creates objects in decode - * HTML_AJAX_JSON_LOOSE_TYPE - loose typing - * "{...}" syntax creates associative arrays in decode - */ - function HTML_AJAX_JSON($use=HTML_AJAX_JSON_STRICT_TYPE) - { - $this->use = $use; + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); } - /** function encode - * encode an arbitrary variable into HTML_AJAX_JSON format - * - * @param var mixed any number, boolean, string, array, or object to be encoded. - * see argument 1 to HTML_AJAX_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return string HTML_AJAX_JSON string representation of input var - */ - function encode($var) - { - switch(gettype($var)) { - case 'boolean': - return $var ? 'true' : 'false'; + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $ut8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to HTML_AJAX_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; - case 'NULL': - return 'null'; + case 'double': + case 'float': + return (float) $var; - case 'integer': - return sprintf('%d', $var); + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { - case 'double': - case 'float': - return sprintf('%f', $var); + $ord_var_c = ord($var{$c}); - case 'string': // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT - $ascii = ''; - $strlen_var = strlen($var); - - for($c = 0; $c < $strlen_var; $c++) { - - $ord_var_c = ord($var{$c}); - - if($ord_var_c == 0x08) { + switch (true) { + case $ord_var_c == 0x08: $ascii .= '\b'; - - } elseif($ord_var_c == 0x09) { + break; + case $ord_var_c == 0x09: $ascii .= '\t'; - - } elseif($ord_var_c == 0x0A) { + break; + case $ord_var_c == 0x0A: $ascii .= '\n'; - - } elseif($ord_var_c == 0x0C) { + break; + case $ord_var_c == 0x0C: $ascii .= '\f'; - - } elseif($ord_var_c == 0x0D) { + break; + case $ord_var_c == 0x0D: $ascii .= '\r'; - - } elseif(($ord_var_c == 0x22) || ($ord_var_c == 0x2F) || ($ord_var_c == 0x5C)) { - $ascii .= '\\'.$var{$c}; // double quote, slash, slosh - - } elseif(($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)) { + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): // characters U-00000000 - U-0000007F (same as ASCII) - $ascii .= $var{$c}; // most normal ASCII chars - - } elseif(($ord_var_c & 0xE0) == 0xC0) { - // characters U-00000080 - U-000007FF, mask 110XXXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1})); $c+=1; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } elseif(($ord_var_c & 0xF0) == 0xE0) { - // characters U-00000800 - U-0000FFFF, mask 1110XXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2})); $c+=2; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } elseif(($ord_var_c & 0xF8) == 0xF0) { - // characters U-00010000 - U-001FFFFF, mask 11110XXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3})); $c+=3; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } elseif(($ord_var_c & 0xFC) == 0xF8) { - // characters U-00200000 - U-03FFFFFF, mask 111110XX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3}), ord($var{$c+4})); $c+=4; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } elseif(($ord_var_c & 0xFE) == 0xFC) { - // characters U-04000000 - U-7FFFFFFF, mask 1111110X, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c+1}), ord($var{$c+2}), ord($var{$c+3}), ord($var{$c+4}), ord($var{$c+5})); $c+=5; - $ascii .= sprintf('\u%04s', bin2hex(mb_convert_encoding($char, 'UTF-16', 'UTF-8'))); - - } + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) + if(HTML_AJAX_JSON::isError($property)) + return $property; - return sprintf('"%s"', $ascii); - - case 'array': - // As per HTML_AJAX_JSON spec if any array key is not an integer we must treat the the whole array as an object. - // We also try to catch a sparsely populated associative array with numeric keys here because some JS - // engines will create an array with empty indexes up to max_index which can cause memory issues - // and because the keys, which may be relevant, will be remapped otherwise. - // - // As per the ECMA and HTML_AJAX_JSON specification an object may have any string as a property. Unfortunately due to a - // hole in the ECMA specification if the key is a ECMA reserved word or starts with a digit the parameter is only - // accessible using ECMAScript's bracket notation. - - // treat as a HTML_AJAX_JSON object - if(is_array($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) - return sprintf('{%s}', join(',', array_map(array($this, 'name_value'), array_keys($var), array_values($var)))); - - // treat it like a regular array - return sprintf('[%s]', join(',', array_map(array($this, 'encode'), $var))); - - case 'object': - $vars = get_object_vars($var); - return sprintf('{%s}', join(',', array_map(array($this, 'name_value'), array_keys($vars), array_values($vars)))); - - default: - return ''; - } - } - - /** function enc - * alias for encode() - */ - function enc($var) - { - return $this->encode($var); - } - - /** function name_value - * array-walking function for use in generating HTML_AJAX_JSON-formatted name-value pairs - * - * @param name string name of key to use - * @param value mixed reference to an array element to be encoded - * - * @return string HTML_AJAX_JSON-formatted name-value pair, like '"name":value' - */ - function name_value($name, $value) - { - return (sprintf("%s:%s", $this->encode(strval($name)), $this->encode($value))); - } + return '{' . join(',', $properties) . '}'; + } - /** function reduce_string - * reduce a string by removing leading and trailing comments and whitespace - * - * @param str string string value to strip of comments and whitespace - * - * @return string string value stripped of comments and whitespace - */ - function reduce_string($str) - { - $str = preg_replace('#^\s*//(.+)$#m', '', $str); // eliminate single line comments in '// ...' form - $str = preg_replace('#^\s*/\*(.+)\*/#Us', '', $str); // eliminate multi-line comments in '/* ... */' form, at start of string - $str = preg_replace('#/\*(.+)\*/\s*$#Us', '', $str); // eliminate multi-line comments in '/* ... */' form, at end of string - $str = trim($str); // eliminate extraneous space - - return $str; - } + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) + if(HTML_AJAX_JSON::isError($element)) + return $element; + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); - /** function decode - * decode a HTML_AJAX_JSON string into appropriate variable - * - * @param str string HTML_AJAX_JSON-formatted string - * - * @return mixed number, boolean, string, array, or object - * corresponding to given HTML_AJAX_JSON input string. - * see argument 1 to HTML_AJAX_JSON() above for object-output behavior. - * note that decode() always returns strings - * in ASCII or UTF-8 format! - */ - function decode($str) - { - $str = $this->reduce_string($str); - - switch(strtolower($str)) { - case 'true': - return true; - - case 'false': - return false; - - case 'null': - return null; - - default: - if(is_numeric($str)) { // Lookie-loo, it's a number - // return (float)$str; // This would work on its own, but I'm trying to be good about returning integers where appropriate - return ((float)$str == (integer)$str) - ? (integer)$str - : (float)$str; - - } elseif(preg_match('/^".+"$/s', $str) || preg_match('/^\'.+\'$/s', $str)) { // STRINGS RETURNED IN UTF-8 FORMAT - $delim = substr($str, 0, 1); - $chrs = substr($str, 1, -1); - $utf8 = ''; - $strlen_chrs = strlen($chrs); - - for($c = 0; $c < $strlen_chrs; $c++) { - - $substr_chrs_c_2 = substr($chrs, $c, 2); - $ord_chrs_c = ord($chrs{$c}); + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) + if(HTML_AJAX_JSON::isError($property)) + return $property; + + return '{' . join(',', $properties) . '}'; - if($substr_chrs_c_2 == '\b') { - $utf8 .= chr(0x08); $c+=1; - - } elseif($substr_chrs_c_2 == '\t') { - $utf8 .= chr(0x09); $c+=1; - - } elseif($substr_chrs_c_2 == '\n') { - $utf8 .= chr(0x0A); $c+=1; - - } elseif($substr_chrs_c_2 == '\f') { - $utf8 .= chr(0x0C); $c+=1; + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new HTML_AJAX_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } - } elseif($substr_chrs_c_2 == '\r') { - $utf8 .= chr(0x0D); $c+=1; + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(HTML_AJAX_JSON::isError($encoded_value)) + return $encoded_value; - } elseif(($delim == '"') && (($substr_chrs_c_2 == '\\"') || ($substr_chrs_c_2 == '\\\\') || ($substr_chrs_c_2 == '\\/'))) { - $utf8 .= $chrs{++$c}; + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', - } elseif(($delim == "'") && (($substr_chrs_c_2 == '\\\'') || ($substr_chrs_c_2 == '\\\\') || ($substr_chrs_c_2 == '\\/'))) { - $utf8 .= $chrs{++$c}; + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', - } elseif(preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6))) { // single, escaped unicode character - $utf16 = chr(hexdec(substr($chrs, ($c+2), 2))) . chr(hexdec(substr($chrs, ($c+4), 2))); - $utf8 .= mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); - $c+=5; + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' - } elseif(($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F)) { - $utf8 .= $chrs{$c}; + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to HTML_AJAX_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); - } elseif(($ord_chrs_c & 0xE0) == 0xC0) { - // characters U-00000080 - U-000007FF, mask 110XXXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 2); $c += 1; - - } elseif(($ord_chrs_c & 0xF0) == 0xE0) { - // characters U-00000800 - U-0000FFFF, mask 1110XXXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 3); $c += 2; - - } elseif(($ord_chrs_c & 0xF8) == 0xF0) { - // characters U-00010000 - U-001FFFFF, mask 11110XXX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 4); $c += 3; - - } elseif(($ord_chrs_c & 0xFC) == 0xF8) { - // characters U-00200000 - U-03FFFFFF, mask 111110XX, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 5); $c += 4; - - } elseif(($ord_chrs_c & 0xFE) == 0xFC) { - // characters U-04000000 - U-7FFFFFFF, mask 1111110X, see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 6); $c += 5; + switch (strtolower($str)) { + case 'true': + return true; - } + case 'false': + return false; + + case 'null': + return null; + + default: + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + } - - return $utf8; + + } - } elseif(preg_match('/^\[.*\]$/s', $str) || preg_match('/^{.*}$/s', $str)) { // array, or object notation - - if($str{0} == '[') { - $stk = array(HTML_AJAX_JSON_IN_ARR); - $arr = array(); + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); } else { - if($this->use == HTML_AJAX_JSON_LOOSE_TYPE) { - $stk = array(HTML_AJAX_JSON_IN_OBJ); - $obj = array(); - } else { - $stk = array(HTML_AJAX_JSON_IN_OBJ); - $obj = new stdClass(); - } + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); } - - array_push($stk, array('what' => HTML_AJAX_JSON_SLICE, 'where' => 0, 'delim' => false)); - $chrs = substr($str, 1, -1); - $chrs = $this->reduce_string($chrs); - - if($chrs == '') { - if(reset($stk) == HTML_AJAX_JSON_IN_ARR) { - return $arr; + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); - } else { - return $obj; + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; - } } + } - //print("\nparsing {$chrs}\n"); - - $strlen_chrs = strlen($chrs); - - for($c = 0; $c <= $strlen_chrs; $c++) { - - $top = end($stk); - $substr_chrs_c_2 = substr($chrs, $c, 2); - - if(($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == HTML_AJAX_JSON_SLICE))) { // found a comma that is not inside a string, array, etc., OR we've reached the end of the character list - $slice = substr($chrs, $top['where'], ($c - $top['where'])); - array_push($stk, array('what' => HTML_AJAX_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); - //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - if(reset($stk) == HTML_AJAX_JSON_IN_ARR) { // we are in an array, so just push an element onto the stack - array_push($arr, $this->decode($slice)); - - } elseif(reset($stk) == HTML_AJAX_JSON_IN_OBJ) { // we are in an object, so figure out the property name and set an element in an associative array, for now - if(preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { // "name":value pair - $key = $this->decode($parts[1]); - $val = $this->decode($parts[2]); - - if($this->use == HTML_AJAX_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } elseif(preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { // name:value pair, where name is unquoted - $key = $parts[1]; - $val = $this->decode($parts[2]); - - if($this->use == HTML_AJAX_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; } - } - - } elseif((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != HTML_AJAX_JSON_IN_STR)) { // found a quote, and we are not inside a string - array_push($stk, array('what' => HTML_AJAX_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); - //print("Found start of string at {$c}\n"); - - } elseif(($chrs{$c} == $top['delim']) && ($top['what'] == HTML_AJAX_JSON_IN_STR) && (($chrs{$c - 1} != "\\") || ($chrs{$c - 1} == "\\" && $chrs{$c - 2} == "\\"))) { // found a quote, we're in a string, and it's not escaped - array_pop($stk); - //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); - - } elseif(($chrs{$c} == '[') && in_array($top['what'], array(HTML_AJAX_JSON_SLICE, HTML_AJAX_JSON_IN_ARR, HTML_AJAX_JSON_IN_OBJ))) { // found a left-bracket, and we are in an array, object, or slice - array_push($stk, array('what' => HTML_AJAX_JSON_IN_ARR, 'where' => $c, 'delim' => false)); - //print("Found start of array at {$c}\n"); - - } elseif(($chrs{$c} == ']') && ($top['what'] == HTML_AJAX_JSON_IN_ARR)) { // found a right-bracket, and we're in an array - array_pop($stk); - //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif(($chrs{$c} == '{') && in_array($top['what'], array(HTML_AJAX_JSON_SLICE, HTML_AJAX_JSON_IN_ARR, HTML_AJAX_JSON_IN_OBJ))) { // found a left-brace, and we are in an array, object, or slice - array_push($stk, array('what' => HTML_AJAX_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); - //print("Found start of object at {$c}\n"); - - } elseif(($chrs{$c} == '}') && ($top['what'] == HTML_AJAX_JSON_IN_OBJ)) { // found a right-brace, and we're in an object - array_pop($stk); - //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif(($substr_chrs_c_2 == '/*') && in_array($top['what'], array(HTML_AJAX_JSON_SLICE, HTML_AJAX_JSON_IN_ARR, HTML_AJAX_JSON_IN_OBJ))) { // found a comment start, and we are in an array, object, or slice - array_push($stk, array('what' => HTML_AJAX_JSON_IN_CMT, 'where' => $c, 'delim' => false)); - $c++; - //print("Found start of comment at {$c}\n"); - - } elseif(($substr_chrs_c_2 == '*/') && ($top['what'] == HTML_AJAX_JSON_IN_CMT)) { // found a comment end, and we're in one now - array_pop($stk); - $c++; - - for($i = $top['where']; $i <= $c; $i++) - $chrs = substr_replace($chrs, ' ', $i, 1); - - //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - + } - - } - - if(reset($stk) == HTML_AJAX_JSON_IN_ARR) { - return $arr; - - } elseif(reset($stk) == HTML_AJAX_JSON_IN_OBJ) { - return $obj; - + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + (($chrs{$c - 1} != '\\') || + ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { + // found a quote, we're in a string, and it's not escaped + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + } } - } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } } - - /** function dec - * alias for decode() - */ - function dec($var) + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('pear_error')) { + + class HTML_AJAX_JSON_Error extends PEAR_Error + { + function HTML_AJAX_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) { - return $this->decode($var); + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class HTML_AJAX_JSON_Error + { + function HTML_AJAX_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + } } + +} + ?> http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/Server.php?r1=1.9&r2=1.10&diff_format=u Index: pear/HTML_AJAX/AJAX/Server.php diff -u pear/HTML_AJAX/AJAX/Server.php:1.9 pear/HTML_AJAX/AJAX/Server.php:1.10 --- pear/HTML_AJAX/AJAX/Server.php:1.9 Thu Nov 3 13:54:37 2005 +++ pear/HTML_AJAX/AJAX/Server.php Thu Feb 9 14:48:08 2006 @@ -104,15 +104,20 @@ 'all' => 'HTML_AJAX.js', 'html_ajax' => 'HTML_AJAX.js', 'html_ajax_lite'=> 'HTML_AJAX_lite.js', - 'json' => 'JSON.js', + 'json' => 'serializer/JSON.js', 'request' => 'Request.js', - 'main' => 'Main.js', + 'main' => array('Main.js','clientPool.js'), 'httpclient' => 'HttpClient.js', 'dispatcher' => 'Dispatcher.js', 'util' => 'util.js', 'loading' => 'Loading.js', - 'phpserializer' => 'serialize.js', - 'behavior' => array('behavior.js','cssQuery-p.js') + 'phpserializer' => 'serializer/phpSerializer.js', + 'urlserializer' => 'serializer/UrlSerializer.js', + 'haserializer' => 'serializer/haSerializer.js', + 'priorityqueue' => 'priorityQueue.js', + 'clientpool' => 'clientPool.js', + 'iframe' => 'IframeXHR.js', + 'behavior' => array('behavior/behavior.js','behavior/cssQuery-p.js') ); /** @@ -134,14 +139,22 @@ /** * Constructor creates the HTML_AJAX instance - * - * @todo: verify that PHP_SELF always does what we want */ function HTML_AJAX_Server() { $this->ajax =& new HTML_AJAX(); - $this->ajax->serverUrl = $_SERVER['PHP_SELF']; + // parameters for HTML::AJAX + $parameters = array('stub', 'client'); + + // keep in the query string all the parameters that don't belong to AJAX + // we remove all string like "parameter=something&". Final '&' can also + // be '&' (to be sure) and is optional. '=something' is optional too. + $querystring = preg_replace('/(' . join('|', $parameters) . ')(?:=[^&]*(?:&(?:amp;)?|$))?/', '', $_SERVER['QUERY_STRING']); + + // call the server with this query string + $this->ajax->serverUrl = htmlentities($_SERVER['PHP_SELF']) .'?'. htmlentities($querystring); + $methods = get_class_methods($this); foreach($methods as $method) { if (preg_match('/^init([a-zA-Z0-9_]+)$/',$method,$match)) { @@ -153,15 +166,18 @@ /** * Handle a client request, either generating a client or having HTML_AJAX handle the request * - * @return string generated client or ajax response + * @return boolean true if request was handled, false otherwise */ function handleRequest() { if ($this->options == true) { $this->_loadOptions(); } + //basically a hook for iframe but allows processing of data earlier + $this->ajax->populatePayload(); if (!isset($_GET['c']) && (count($this->options['client']) > 0 || count($this->options['stub']) > 0) ) { - return $this->generateClient(); + $this->generateClient(); + return true; } else { $this->_init($this->_cleanIdentifier($_GET['c'])); return $this->ajax->handleRequest(); @@ -200,10 +216,11 @@ * @param object $instance an external class with initClassName methods */ function registerInitObject(&$instance) { + $instance->server =& $this; $methods = get_class_methods($instance); foreach($methods as $method) { if (preg_match('/^init([a-zA-Z0-9_]+)$/',$method,$match)) { - $this->_initLookup[$match[1]] = array(&$instance,$method); + $this->_initLookup[strtolower($match[1])] = array(&$instance,$method); } } } @@ -216,6 +233,7 @@ function generateClient() { $headers = array(); + ob_start(); // create a list list of js files were going to need to output @@ -349,12 +367,14 @@ // were outputting content, add our length header and send the output $length = ob_get_length(); - if ($length > 0) { + $output = ob_get_contents(); + ob_end_clean(); + if ($length > 0 && $this->ajax->_sendContentLength()) { $headers['Content-Length'] = $length; } $headers['Content-Type'] = 'text/javascript; charset=utf-8'; - call_user_func($this->ajax->_callbacks['headers'],$headers); - ob_end_flush(); + call_user_func($this->ajax->_callbacks['headers'], $headers); + echo($output); } /** @@ -383,7 +403,12 @@ function clientJsLocation() { if (!$this->clientJsLocation) { - return '@data-dir@'.DIRECTORY_SEPARATOR.'HTML_AJAX'.DIRECTORY_SEPARATOR.'js'.DIRECTORY_SEPARATOR; + $path = '@data-dir@'.DIRECTORY_SEPARATOR.'HTML_AJAX'.DIRECTORY_SEPARATOR.'js'.DIRECTORY_SEPARATOR; + if(strpos($path, '@'.'data-dir@') === 0) + { + $path = realpath(dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'js').DIRECTORY_SEPARATOR; + } + return $path; } else { return $this->clientJsLocation; } http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/Action.php?view=markup&rev=1.1 Index: pear/HTML_AJAX/AJAX/Action.php +++ pear/HTML_AJAX/AJAX/Action.php <?php /** * OO AJAX Implementation for PHP * * @category HTML * @package AJAX * @author Elizabeth Smith <auroraeosrose@gmail.com> * @copyright 2005 Elizabeth Smith * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version Release: @package_version@ */ /** * Require the response class and json serializer */ require_once 'HTML/AJAX/Response.php'; require_once 'HTML/AJAX/Serializer/JSON.php'; /** * Helper Class for creating information that can be properly serialized and used by * the haaction serializer that eliminates the need for php users to write javascript * for dealing with the information returned by an ajax method - instead the javascript * is basically created for them */ class HTML_AJAX_Action extends HTML_AJAX_Response { /** * about * @var $contentType type */ var $contentType = 'application/html_ajax_action'; /** * about * @var $_actions type */ var $_actions; /** * public function prependAttr() * * adds $data to the end of the attribute in the item identified by $id ($id can also be a class) */ function prependAttr($id, $attribute, $data = NULL) { if(!is_null($data)) { $attribute = array($attribute => $data); } $this->_actions[] = array( 'action' => 'prepend', 'id' => $id, 'attributes' => $attribute, 'data' => $data, ); return; } /** * public function appendAttr() * * adds $data to the beginning of the attribute in the item identified by $id ($id can also be a class) */ function appendAttr($id, $attribute, $data = NULL) { if(!is_null($data)) { $attribute = array($attribute => $data); } $this->_actions[] = array( 'action' => 'append', 'id' => $id, 'attributes' => $attribute, ); return; } /** * public function assignAttr() * * assigns $data to the value of the attribute in the item identified by $id ($id can also be a class) * if attribute already exists, the entire value of the attribute will be replaced */ function assignAttr($id, $attribute, $data = NULL) { if(!is_null($data)) { $attribute = array($attribute => $data); } $this->_actions[] = array( 'action' => 'assign', 'id' => $id, 'attributes' => $attribute, ); return; } /** * public function clearAttr() * * removes an attr from the item identified by $id ($id can also be a class) */ function clearAttr($id, $attribute) { if(!is_array($attribute)) { $attribute = array($attribute); } $this->_actions[] = array( 'action' => 'clear', 'id' => $id, 'attributes' => $attribute, ); return; } /** * public function createNode() * * higher level dom manipulation - creates a new node to insert into the dom * type can be append, prepend, or insert - for insert id should be the id of * the element you're inserting before, for append or prepend should be the id * of the element you're inserting into */ function createNode($id, $tag, $attribute, $type = 'append') { $types = array('append', 'prepend', 'insertBefore', 'insertAfter'); if(!in_array($type, $types)) { $type = 'append'; } settype($attribute, 'array'); $this->_actions[] = array( 'action' => 'create', 'id' => $id, 'tag' => $tag, 'attributes' => $attribute, 'type' => $type, ); return; } /** * public function replaceNode() * * higher level dom manipulation - replaces one node with another * This can be used to replace a div with a form for inline editing * use innerHtml attribute to change inside text */ function replaceNode($id, $tag, $attribute) { $this->_actions[] = array( 'action' => 'replace', 'id' => $id, 'tag' => $tag, 'attributes' => settype($attribute, 'array'), ); return; } /** * public function removeNode() * * higher level dom manipulation - deletes node from the dom */ function removeNode($id) { $this->_actions[] = array( 'action' => 'remove', 'id' => $id, ); return; } /** * public function insertScript() * * adds straight javascript */ function insertScript($data) { $this->_actions[] = array( 'action' => 'script', 'data' => $data, ); return; } /** * public function insertAlert() * * adds javascript alert */ function insertAlert($data) { $this->_actions[] = array( 'action' => 'alert', 'data' => $data, ); return; } /** * public function getPayload() * * something */ function getPayload() { $serializer = new HTML_AJAX_Serializer_JSON(); return $serializer->serialize($this->_actions); } } ?> http://cvs.php.net/viewcvs.cgi/pear/HTML_AJAX/AJAX/Response.php?view=markup&rev=1.1 Index: pear/HTML_AJAX/AJAX/Response.php +++ pear/HTML_AJAX/AJAX/Response.php <?php /** * OO AJAX Implementation for PHP * * @category HTML * @package AJAX * @author Elizabeth Smith <auroraeosrose@gmail.com> * @copyright 2005 Elizabeth Smith * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @version Release: @package_version@ */ /** * Require the main AJAX library */ require_once 'HTML/AJAX.php'; /** * Josh says we need this, I don't know why yet */ class HTML_AJAX_Response { /** * about * @var $contentType type */ var $contentType = 'text/plain'; /** * about * @var $payload type */ var $payload = ''; /** * public function getContentType() * * gets the right content type? */ function getContentType() { return $this->contentType; } /** * public function getPayload() * * something */ function getPayload() { return $this->payload; } } ?>