<?
/*
$Revision: 1.5 $
$Date: 2006/06/12 07:40:58 $

(c) David Mzareulyan
    david@hiero.ru


Public interface:

    string JSON::encode(mixed $value, boolean $encodeEmptyArrayToArray = true, boolean $encodeNonASCII = false)

            $value - value to encode
            $encodeEmptyArrayToArray - if true, "array()" encoded as "[]", else as "{}"
            $encodeNonASCII - if true, _all_ non-ASCII symbols encoded as "\uXXXX"

    mixed  JSON::decode(string $string, boolean $decodeObjectToArray = true)

            $string - string to decode
            $decodeObjectToArray - if true, objects "{...}" decoded to associative arrays, else to "stdClass" instances
*/

if(__FILE__ == $_SERVER["SCRIPT_FILENAME"]) highlight_file(__FILE__);

class 
JSON {
    function 
encode($value$encodeEmptyArrayToArray true$encodeNonASCII false) {
        switch(
gettype($value)) {
            case 
'boolean': return $value 'true' 'false';

            case 
'NULL':    return 'null';

            case 
'integer': return (int) $value;

            case 
'double':
            case 
'float':   return (float) $value;

            case 
'string':
                
$value str_replace(
                    array(
"\\","\"","\/","\b","\f","\n","\r","\t"), 
                    array(
"\\\\","\\\"","\\\/","\\b","\\f","\\n","\\r","\\t"), 
                    
$value);
                
$value preg_replace_callback('/[\x00-\x1F]/', array("JSON""_space2code"), $value);
                if(
$encodeNonASCII$value preg_replace_callback('{
                        [\xC0-\xDF][\x80-\xBF]{1}|
                        [\xE0-\xEF][\x80-\xBF]{2}|
                        [\xF0-\xF7][\x80-\xBF]{3}|
                        [\xF8-\xFB][\x80-\xBF]{4}|
                        [\xFC-\xFD][\x80-\xBF]{5}
                        }x'
, array("JSON""_utf2code"), $value);
                return 
'"'.$value.'"';

            case 
'array':
                if(
count($value) == 0) return $encodeEmptyArrayToArray "[]" "{}";
                if(
array_keys($value) === range(0count($value) - 1)) {
                    return 
'[' join(','array_map(array("JSON"'encode'), $value)) . ']';
                } else {
                    return 
'{'.join(','array_map(array("JSON"'_encodeNameValuePair'), array_keys($value), array_values($value))).'}';
                }

            case 
'object':
                
$vars get_object_vars($value);
                return 
'{'.join(','array_map(array("JSON"'_encodeNameValuePair'), array_keys($vars), array_values($vars))).'}';

            default:    return 
'';
        }
    }


    function 
decode($str$decodeObjectToArray true) {
        
        if (
function_exists('json_decode')) return json_decode($str$decodeObjectToArray);
        
        
$str trim($str);

        switch(
strtolower($str)) {
            case 
'true':    return true;
            case 
'false':   return false;
            case 
'null':    return null;
        }

        if(
is_numeric($str)) return ((float)$str == (int)$str) ? (int)$str : (float)$str;

        if(
preg_match('/^("|\')(.+)\1$/s'$str$m)) { // string
            
$str preg_replace_callback('/\x5C([\'"\x5C\/bfnrt]|u[0-9a-fA-F]{4})/', array("JSON""_unslash"), $m[2]);
            return 
$str;
        }
                    
        if (
preg_match('/^(\[.*\]|\{.*\})$/s'$str)) { // array or object
            
$scalarPattern '/
                    true|false|null|
                    (?:-?\d+(?:\.\d+)?(?:e[+-]?\d+)?)|
                    "(?:[^"\x5C]|\x5C[\x5C"\'\\/bfnrt])*?"|
                    \'(?:[^\'\x5C]|\x5C[\x5C"\'\\/bfnrt])*?\'
                /xis'
;
            
$saver =& new JSON__saver($helper);
            
$result preg_replace_callback($scalarPattern, array($saver"saveScalar"), $str);
            
$result preg_replace('/[^\d\[\]\{\}:,]/s'''$result);

            while(!
is_numeric($result)) {
                
$l strlen($result);
                
$result preg_replace_callback('/(\d+):(\d+)/', array($saver"savePair"), $result);
                
$result preg_replace_callback('/\[[\d,]*\]/',  array($saver"saveArray"), $result);
                
$result preg_replace_callback('/\{[\d,]*\}/',  array($saver"saveObject"), $result);
                if(
$l == strlen($result)) break;
            }
            
$result = (int)preg_replace('/\D/'''$result);
            return 
JSON::_restore($helper$result$decodeObjectToArray);
        }

        return 
"?";
    }

    
/* private functions */

    
function _space2code($a) { return sprintf("\\u%04x"ord($a[0]{0})); }
    function 
_encodeNameValuePair($name$value) { return JSON::encode(strval($name)).':'.JSON::encode($value); }

    function 
_utf2code($a) {
        
$utf    $a[0];
        
$first  ord($utf{0});
        
$len    strlen($utf);
        
$code   $first & (0xff >> $len);
        for(
$i=1$i<$len$i++) $code = ($code << 6) + (ord($utf{$i}) & 0x3f);
        return 
sprintf("\\u%04x"$code);
    }

    function 
_unslash($a) {
            
$v $a[1];
            
$repl = array('"' => '"'"'" => "'"'\\' => '\\''/' => '/'
                          
'b' => "\x08"'f' => "\x0C"'n' => "\x0A"'r' => "\x0D"'t' => "\x09");
            if(isset(
$repl[$v{0}])) return $repl[$v{0}];
            if(
strtolower($v{0}) == "u") return JSON::code2utf(hexdec(substr($v1)));
            return 
"?"
        }

    function 
code2utf($x) {
        if(
$x 1<<8)  return   chr($x);
        if(
$x 1<<12) return   chr(0xC0 + (($x>>6) & 0x1F)).
                                
chr(0x80 + ($x 0x3F));
        if(
$x 1<<17) return   chr(0xE0 + (($x>>12) & 0x0F)).
                                
chr(0x80 + (($x>>6) & 0x3F)).
                                
chr(0x80 + ($x 0x3F));
        if(
$x 1<<22) return   chr(0xF0 + (($x>>18) & 0x07)).
                                
chr(0x80 + (($x>>12) & 0x3F)).
                                
chr(0x80 + (($x>>6) & 0x3F)).
                                
chr(0x80 + ($x 0x3F));
        return 
'?';
    }

    function 
_restore(&$helper$index$decodeObjectToArray) {
        if(
$index >= count($helper)) return null;
        
$q $helper[$index];
        switch(
get_class($q)) {
            case 
"json__scalar":    return $q->value;
            case 
"json__array":
                
$res = array();
                foreach(
$q->value as $item$res[] = JSON::_restore($helper$item$decodeObjectToArray);
                return 
$res;
            case 
"json__object":
                
$res $decodeObjectToArray ? array() : new stdClass();
                foreach(
$q->value as $item) {
                    
$pq $helper[$item];
                    if(!
is_a($pq"JSON__pair")) continue;
                    
$k $pq->key$v JSON::_restore($helper$pq->value$decodeObjectToArray);
                    if(
is_array($res)) $res[$k] = $v; else $res->$k $v;
                }
                return 
$res;
            default: return 
null;
        }
    }
}

/* private classes */

class JSON__scalar { function JSON__scalar($v) { $this->value $v; } }
class 
JSON__array  { function JSON__array($v)  { $this->value $v; } }
class 
JSON__object { function JSON__object($v) { $this->value $v; } }
class 
JSON__pair   { function JSON__pair($k$v) { $this->key $k$this->value $v; } }

class 
JSON__saver {
    function 
JSON__saver(&$helper) { $this->store =& $helper; }
    function 
saveScalar($a) {
        
$this->store[] = new JSON__scalar(JSON::decode($a[0]));
        return (
count($this->store)-1);
    }
    function 
saveArray($a) {
        
$v substr($a[0],1,-1);
        
$this->store[] = new JSON__array($v explode(","$v) : array());
        return (
count($this->store)-1);
    }
    function 
saveObject($a) {
        
$v substr($a[0],1,-1);
        
$this->store[] = new JSON__object($v explode(","$v) : array());
        return (
count($this->store)-1);
    }
    function 
savePair($a) {
        
$this->store[] = new JSON__pair($this->store[$a[1]]->value$a[2]);
        return (
count($this->store)-1);
    }
}

?>