AJAX core library: possible breaking change, readystatechange functions are now called with the XHR instance as the first parameter, to allow requests to run in parallel. This means much better stability but may break some applets (compatibility hack is included)
<?php
/**
* $Id: JSON.php 40 2007-06-18 11:43:15Z spocke $
*
* @package MCManager.utils
* @author Moxiecode
* @copyright Copyright © 2007, Moxiecode Systems AB, All rights reserved.
*/
define('JSON_BOOL', 1);
define('JSON_INT', 2);
define('JSON_STR', 3);
define('JSON_FLOAT', 4);
define('JSON_NULL', 5);
define('JSON_START_OBJ', 6);
define('JSON_END_OBJ', 7);
define('JSON_START_ARRAY', 8);
define('JSON_END_ARRAY', 9);
define('JSON_KEY', 10);
define('JSON_SKIP', 11);
define('JSON_IN_ARRAY', 30);
define('JSON_IN_OBJECT', 40);
define('JSON_IN_BETWEEN', 50);
class Moxiecode_JSONReader {
var $_data, $_len, $_pos;
var $_value, $_token;
var $_location, $_lastLocations;
var $_needProp;
function Moxiecode_JSONReader($data) {
$this->_data = $data;
$this->_len = strlen($data);
$this->_pos = -1;
$this->_location = JSON_IN_BETWEEN;
$this->_lastLocations = array();
$this->_needProp = false;
}
function getToken() {
return $this->_token;
}
function getLocation() {
return $this->_location;
}
function getTokenName() {
switch ($this->_token) {
case JSON_BOOL:
return 'JSON_BOOL';
case JSON_INT:
return 'JSON_INT';
case JSON_STR:
return 'JSON_STR';
case JSON_FLOAT:
return 'JSON_FLOAT';
case JSON_NULL:
return 'JSON_NULL';
case JSON_START_OBJ:
return 'JSON_START_OBJ';
case JSON_END_OBJ:
return 'JSON_END_OBJ';
case JSON_START_ARRAY:
return 'JSON_START_ARRAY';
case JSON_END_ARRAY:
return 'JSON_END_ARRAY';
case JSON_KEY:
return 'JSON_KEY';
}
return 'UNKNOWN';
}
function getValue() {
return $this->_value;
}
function readToken() {
$chr = $this->read();
if ($chr != null) {
switch ($chr) {
case '[':
$this->_lastLocation[] = $this->_location;
$this->_location = JSON_IN_ARRAY;
$this->_token = JSON_START_ARRAY;
$this->_value = null;
$this->readAway();
return true;
case ']':
$this->_location = array_pop($this->_lastLocation);
$this->_token = JSON_END_ARRAY;
$this->_value = null;
$this->readAway();
if ($this->_location == JSON_IN_OBJECT)
$this->_needProp = true;
return true;
case '{':
$this->_lastLocation[] = $this->_location;
$this->_location = JSON_IN_OBJECT;
$this->_needProp = true;
$this->_token = JSON_START_OBJ;
$this->_value = null;
$this->readAway();
return true;
case '}':
$this->_location = array_pop($this->_lastLocation);
$this->_token = JSON_END_OBJ;
$this->_value = null;
$this->readAway();
if ($this->_location == JSON_IN_OBJECT)
$this->_needProp = true;
return true;
// String
case '"':
case '\'':
return $this->_readString($chr);
// Null
case 'n':
return $this->_readNull();
// Bool
case 't':
case 'f':
return $this->_readBool($chr);
default:
// Is number
if (is_numeric($chr) || $chr == '-' || $chr == '.')
return $this->_readNumber($chr);
return true;
}
}
return false;
}
function _readBool($chr) {
$this->_token = JSON_BOOL;
$this->_value = $chr == 't';
if ($chr == 't')
$this->skip(3); // rue
else
$this->skip(4); // alse
$this->readAway();
if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
$this->_needProp = true;
return true;
}
function _readNull() {
$this->_token = JSON_NULL;
$this->_value = null;
$this->skip(3); // ull
$this->readAway();
if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
$this->_needProp = true;
return true;
}
function _readString($quote) {
$output = "";
$this->_token = JSON_STR;
$endString = false;
while (($chr = $this->peek()) != -1) {
switch ($chr) {
case '\\':
// Read away slash
$this->read();
// Read escape code
$chr = $this->read();
switch ($chr) {
case 't':
$output .= "\t";
break;
case 'b':
$output .= "\b";
break;
case 'f':
$output .= "\f";
break;
case 'r':
$output .= "\r";
break;
case 'n':
$output .= "\n";
break;
case 'u':
$output .= $this->_int2utf8(hexdec($this->read(4)));
break;
default:
$output .= $chr;
break;
}
break;
case '\'':
case '"':
if ($chr == $quote)
$endString = true;
$chr = $this->read();
if ($chr != -1 && $chr != $quote)
$output .= $chr;
break;
default:
$output .= $this->read();
}
// String terminated
if ($endString)
break;
}
$this->readAway();
$this->_value = $output;
// Needed a property
if ($this->_needProp) {
$this->_token = JSON_KEY;
$this->_needProp = false;
return true;
}
if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
$this->_needProp = true;
return true;
}
function _int2utf8($int) {
$int = intval($int);
switch ($int) {
case 0:
return chr(0);
case ($int & 0x7F):
return chr($int);
case ($int & 0x7FF):
return chr(0xC0 | (($int >> 6) & 0x1F)) . chr(0x80 | ($int & 0x3F));
case ($int & 0xFFFF):
return chr(0xE0 | (($int >> 12) & 0x0F)) . chr(0x80 | (($int >> 6) & 0x3F)) . chr (0x80 | ($int & 0x3F));
case ($int & 0x1FFFFF):
return chr(0xF0 | ($int >> 18)) . chr(0x80 | (($int >> 12) & 0x3F)) . chr(0x80 | (($int >> 6) & 0x3F)) . chr(0x80 | ($int & 0x3F));
}
}
function _readNumber($start) {
$value = "";
$isFloat = false;
$this->_token = JSON_INT;
$value .= $start;
while (($chr = $this->peek()) != -1) {
if (is_numeric($chr) || $chr == '-' || $chr == '.') {
if ($chr == '.')
$isFloat = true;
$value .= $this->read();
} else
break;
}
$this->readAway();
if ($isFloat) {
$this->_token = JSON_FLOAT;
$this->_value = floatval($value);
} else
$this->_value = intval($value);
if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
$this->_needProp = true;
return true;
}
function readAway() {
while (($chr = $this->peek()) != null) {
if ($chr != ':' && $chr != ',' && $chr != ' ')
return;
$this->read();
}
}
function read($len = 1) {
if ($this->_pos < $this->_len) {
if ($len > 1) {
$str = substr($this->_data, $this->_pos + 1, $len);
$this->_pos += $len;
return $str;
} else
return $this->_data[++$this->_pos];
}
return null;
}
function skip($len) {
$this->_pos += $len;
}
function peek() {
if ($this->_pos < $this->_len)
return $this->_data[$this->_pos + 1];
return null;
}
}
/**
* This class handles JSON stuff.
*
* @package MCManager.utils
*/
class Moxiecode_JSON {
function Moxiecode_JSON() {
}
function decode($input) {
$reader = new Moxiecode_JSONReader($input);
return $this->readValue($reader);
}
function readValue(&$reader) {
$this->data = array();
$this->parents = array();
$this->cur =& $this->data;
$key = null;
$loc = JSON_IN_ARRAY;
while ($reader->readToken()) {
switch ($reader->getToken()) {
case JSON_STR:
case JSON_INT:
case JSON_BOOL:
case JSON_FLOAT:
case JSON_NULL:
switch ($reader->getLocation()) {
case JSON_IN_OBJECT:
$this->cur[$key] = $reader->getValue();
break;
case JSON_IN_ARRAY:
$this->cur[] = $reader->getValue();
break;
default:
return $reader->getValue();
}
break;
case JSON_KEY:
$key = $reader->getValue();
break;
case JSON_START_OBJ:
case JSON_START_ARRAY:
if ($loc == JSON_IN_OBJECT)
$this->addArray($key);
else
$this->addArray(null);
$cur =& $obj;
$loc = $reader->getLocation();
break;
case JSON_END_OBJ:
case JSON_END_ARRAY:
$loc = $reader->getLocation();
if (count($this->parents) > 0) {
$this->cur =& $this->parents[count($this->parents) - 1];
array_pop($this->parents);
}
break;
}
}
return $this->data[0];
}
// This method was needed since PHP is crapy and doesn't have pointers/references
function addArray($key) {
$this->parents[] =& $this->cur;
$ar = array();
if ($key)
$this->cur[$key] =& $ar;
else
$this->cur[] =& $ar;
$this->cur =& $ar;
}
function getDelim($index, &$reader) {
switch ($reader->getLocation()) {
case JSON_IN_ARRAY:
case JSON_IN_OBJECT:
if ($index > 0)
return ",";
break;
}
return "";
}
function encode($input) {
switch (gettype($input)) {
case 'boolean':
return $input ? 'true' : 'false';
case 'integer':
return (int) $input;
case 'float':
case 'double':
return (float) $input;
case 'NULL':
return 'null';
case 'string':
return $this->encodeString($input);
case 'array':
return $this->_encodeArray($input);
case 'object':
return $this->_encodeArray(get_object_vars($input));
}
return '';
}
function encodeString($input) {
// Needs to be escaped
if (preg_match('/[^a-zA-Z0-9]/', $input)) {
$output = '';
for ($i=0; $i<strlen($input); $i++) {
switch ($input[$i]) {
case "\b":
$output .= "\\b";
break;
case "\t":
$output .= "\\t";
break;
case "\f":
$output .= "\\f";
break;
case "\r":
$output .= "\\r";
break;
case "\n":
$output .= "\\n";
break;
case '\\':
$output .= "\\\\";
break;
case '\'':
$output .= "\\'";
break;
case '"':
$output .= '\"';
break;
default:
$byte = ord($input[$i]);
if (($byte & 0xE0) == 0xC0) {
$char = pack('C*', $byte, ord($input[$i + 1]));
$i += 1;
$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
} if (($byte & 0xF0) == 0xE0) {
$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2]));
$i += 2;
$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
} if (($byte & 0xF8) == 0xF0) {
$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3])));
$i += 3;
$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
} if (($byte & 0xFC) == 0xF8) {
$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3]), ord($input[$i + 4])));
$i += 4;
$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
} if (($byte & 0xFE) == 0xFC) {
$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2], ord($input[$i + 3]), ord($input[$i + 4]), ord($input[$i + 5])));
$i += 5;
$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
} else if ($byte < 128)
$output .= $input[$i];
}
}
return '"' . $output . '"';
}
return '"' . $input . '"';
}
function _utf82utf16($utf8) {
if (function_exists('mb_convert_encoding'))
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
switch (strlen($utf8)) {
case 1:
return $utf8;
case 2:
return chr(0x07 & (ord($utf8[0]) >> 2)) . chr((0xC0 & (ord($utf8[0]) << 6)) | (0x3F & ord($utf8[1])));
case 3:
return chr((0xF0 & (ord($utf8[0]) << 4)) | (0x0F & (ord($utf8[1]) >> 2))) . chr((0xC0 & (ord($utf8[1]) << 6)) | (0x7F & ord($utf8[2])));
}
return '';
}
function _encodeArray($input) {
$output = '';
$isIndexed = true;
$keys = array_keys($input);
for ($i=0; $i<count($keys); $i++) {
if (!is_int($keys[$i])) {
$output .= $this->encodeString($keys[$i]) . ':' . $this->encode($input[$keys[$i]]);
$isIndexed = false;
} else
$output .= $this->encode($input[$keys[$i]]);
if ($i != count($keys) - 1)
$output .= ',';
}
return $isIndexed ? '[' . $output . ']' : '{' . $output . '}';
}
}
?>