--- parser3/src/classes/json.C 2010/09/16 23:35:38 1.6 +++ parser3/src/classes/json.C 2012/05/27 22:33:59 1.19 @@ -1,11 +1,9 @@ /** @file Parser: @b json parser class. - Copyright (c) 2010 ArtLebedev Group (http://www.artlebedev.com) + Copyright (c) 2000-2012 Art. Lebedev Studio (http://www.artlebedev.com) */ -static const char * const IDENT_RESPONSE_C="$Date: 2010/09/16 23:35:38 $"; - #include "classes.h" #include "pa_vmethod_frame.h" @@ -16,13 +14,17 @@ static const char * const IDENT_RESPONSE #include "pa_charsets.h" #include "JSON_parser.h" +#ifdef XML +#include "pa_vxdoc.h" +#endif + +volatile const char * IDENT_JSON_C="$Id: json.C,v 1.19 2012/05/27 22:33:59 misha Exp $"; + // class class MJson: public Methoded { public: MJson(); -public: // Methoded - bool used_directly() { return true; } }; // global variable @@ -37,14 +39,15 @@ struct Json { String* key; Value* result; - Junction* hook; + Junction* hook_object; + Junction* hook_array; Request* request; Charset *charset; bool handle_double; enum Distinct { D_EXCEPTION, D_FIRST, D_LAST, D_ALL } distinct; - Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook(NULL), request(NULL), charset(acharset), handle_double(true), distinct(D_EXCEPTION){} + Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook_object(NULL), hook_array(NULL), request(NULL), charset(acharset), handle_double(true), distinct(D_EXCEPTION){} bool set_distinct(const String &value){ if (value == "first") distinct = D_FIRST; @@ -94,7 +97,7 @@ String* json_string(Json *json, const JS static Value *json_hook(Request &r, Junction *hook, String* key, Value* value){ VMethodFrame frame(*hook->method, r.method_frame, hook->self); - Value *params[]={new VString(*key), value}; + Value *params[]={new VString(key ? *key : String::Empty), value}; frame.store_params(params, 2); r.execute_method(frame); @@ -107,8 +110,9 @@ static int json_callback(Json *json, int switch(type) { case JSON_T_OBJECT_BEGIN:{ VHash *v = new VHash(); - if (json->hook){ + if (json->hook_object){ json->key_stack.push(json->key); + json->key=NULL; } else { if (json->stack.count()) set_json_value(json, v); } @@ -116,9 +120,9 @@ static int json_callback(Json *json, int break; } case JSON_T_OBJECT_END:{ - if (json->hook){ + if (json->hook_object){ String* key = json->key_stack.pop(); - json->result = json_hook(*json->request, json->hook, key, json->stack.pop()); + json->result = json_hook(*json->request, json->hook_object, key, json->stack.pop()); if (json->stack.count()){ json->key = key; @@ -131,16 +135,32 @@ static int json_callback(Json *json, int } case JSON_T_ARRAY_BEGIN:{ VHash *v = new VHash(); - set_json_value(json, v); + if (json->hook_array){ + json->key_stack.push(json->key); + json->key=NULL; + } else { + if (json->stack.count()) set_json_value(json, v); + } json->stack.push(v); break; } case JSON_T_ARRAY_END: - json->stack.pop(); + // libjson supports array at top level, we too + if (json->hook_array){ + String* key = json->key_stack.pop(); + json->result = json_hook(*json->request, json->hook_array, key, json->stack.pop()); + + if (json->stack.count()){ + json->key = key; + set_json_value(json, json->result); + } + } else { + json->result = json->stack.pop(); + } break; case JSON_T_KEY: json->key = json_string(json, value); - break; + break; case JSON_T_INTEGER: set_json_value(json, new VInt((int)value->vu.integer_value)); break; @@ -153,7 +173,7 @@ static int json_callback(Json *json, int set_json_value(json, new VString(*json_string(json, value))); break; case JSON_T_NULL: - set_json_value(json, new VVoid()); + set_json_value(json, VVoid::get()); break; case JSON_T_TRUE: set_json_value(json, &VBool::get(true)); @@ -214,12 +234,19 @@ static void _parse(Request& r, MethodPar valid_options++; } if(Value* value=options->get("object")) { - json.hook=value->get_junction(); + json.hook_object=value->get_junction(); json.request=&r; - if (!json.hook || !json.hook->method || !json.hook->method->params_names || !(json.hook->method->params_names->count() == 2)) + if (!json.hook_object || !json.hook_object->method || !json.hook_object->method->params_names || !(json.hook_object->method->params_names->count() == 2)) throw Exception(PARSER_RUNTIME, 0, "$.object must be parser method with 2 parameters"); valid_options++; } + if(Value* value=options->get("array")) { + json.hook_array=value->get_junction(); + json.request=&r; + if (!json.hook_array || !json.hook_array->method || !json.hook_array->method->params_names || !(json.hook_array->method->params_names->count() == 2)) + throw Exception(PARSER_RUNTIME, 0, "$.array must be parser method with 2 parameters"); + valid_options++; + } if(valid_options!=options->count()) throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); } @@ -244,40 +271,82 @@ static void _parse(Request& r, MethodPar if (json.result) r.write_no_lang(*json.result); } -const String& value_json_string(Value& v, Json_options* options); +char *get_indent(uint level){ + static char* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={}; + if (!cache[level]){ + char *result = static_cast(pa_gc_malloc_atomic(level+1)); + memset(result, '\t', level); + result[level]='\0'; + return cache[level]=result; + } + return cache[level]; +} + +const String& value_json_string(String::Body key, Value& v, Json_options* options); const String& hash_json_string(HashStringValue &hash, Json_options* options) { if(!hash.count()) - return *new String("{}"); + return *new String("{}", String::L_AS_IS); + + uint level = options->r->json_string_recoursion_go_down(); + + String& result = *new String("{\n", String::L_AS_IS); + + if (options->indent){ + + String *delim=NULL; + options->indent=get_indent(level); + for(HashStringValue::Iterator i(hash); i; i.next() ){ + if (delim){ + result << *delim; + } else { + result << options->indent << "\""; + delim = new String(",\n", String::L_AS_IS); *delim << options->indent << "\""; + } + result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), options); + } + result << "\n" << (options->indent=get_indent(level-1)) << "}"; - options->r->json_string_recoursion_go_down(); + } else { + + bool need_delim=false; + for(HashStringValue::Iterator i(hash); i; i.next() ){ + result << (need_delim ? ",\n\"" : "\""); + result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), options); + need_delim=true; + } + result << "\n}"; - String& result = *new String("{"); - bool need_delim=false; - for(HashStringValue::Iterator i(hash); i; i.next() ){ - result << (need_delim ? ",\"" : "\""); - result << String(i.key(), String::L_JSON) << "\":" << value_json_string(*i.value(), options); - need_delim=true; } - result << "}"; options->r->json_string_recoursion_go_up(); return result; } -const String& value_json_string(Value& v, Json_options* options) { - if(options && options->methods) - if(Value* method=options->methods->get(v.type())){ +static bool based_on( + HashStringValue::key_type key, + HashStringValue::value_type /*value*/, + Value* v) { + return v->is(key.cstr()); +} + +const String& value_json_string(String::Body key, Value& v, Json_options* options) { + if(options && options->methods) { + Value* method=options->methods->get(v.type()); + if(!method) + method=options->methods->first_that(based_on, &v); + if(method) { Junction* junction=method->get_junction(); VMethodFrame frame(*junction->method, options->r->method_frame, junction->self); - Value *params[]={&v, (options->params) ? options->params : VVoid::get()}; - frame.store_params(params, 2); + Value *params[]={new VString(*new String(key, String::L_JSON)), &v, (options->params) ? options->params : VVoid::get()}; + frame.store_params(params, 3); options->r->execute_method(frame); return frame.result().as_string(); } + } if(HashStringValue* hash=v.get_hash()) return hash_json_string(*hash, options); @@ -293,6 +362,7 @@ static void _string(Request& r, MethodPa json.params=params.get(1); HashStringValue* methods=new HashStringValue(); int valid_options=0; + HashStringValue* vvalue; for(HashStringValue::Iterator i(*options); i; i.next() ){ String::Body key=i.key(); Value* value=i.value(); @@ -304,19 +374,27 @@ static void _string(Request& r, MethodPa if(!json.set_date_format(svalue)) throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string' or 'unix-timestamp'"); valid_options++; + } else if(key == "indent"){ + json.indent=r.process_to_value(*value).as_bool() ? "":NULL; + valid_options++; } else if(key == "table" && value->is_string()){ const String& svalue=value->as_string(); if(!json.set_table_format(svalue)) - throw Exception(PARSER_RUNTIME, &svalue, "must be 'array' or 'object'"); + throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'"); valid_options++; } else if(key == "file" && value->is_string()){ const String& svalue=value->as_string(); if(!json.set_file_format(svalue)) - throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64' or 'text'"); + throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'"); + valid_options++; +#ifdef XML + } else if(key == "xdoc" && (vvalue = value->get_hash())){ + json.xdoc_options=new XDocOutputOptions(r, vvalue); valid_options++; +#endif } else if(Junction* junction=value->get_junction()){ - if(!junction->method || !junction->method->params_names || junction->method->params_names->count() != 2) - throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 2 parameters", key); + if(!junction->method || !junction->method->params_names || junction->method->params_names->count() != 3) + throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", key.cstr()); methods->put(key, value); valid_options++; } @@ -327,7 +405,9 @@ static void _string(Request& r, MethodPa json.methods=methods; } - r.write_pass_lang(value_json_string(params[0], &json)); + const String& result_string=value_json_string(String::Body(), params[0], &json); + String::Body result_body=result_string.cstr_to_string_body_untaint(String::L_JSON, 0, &r.charsets); + r.write_pass_lang(*new String(result_body, String::L_AS_IS)); } // constructor