Annotation of parser3/src/classes/json.C, revision 1.59

1.1       misha       1: /** @file
                      2:        Parser: @b json parser class.
                      3: 
1.57      moko        4:        Copyright (c) 2000-2023 Art. Lebedev Studio (http://www.artlebedev.com)
                      5:        Authors: Konstantin Morshnev <moko@design.ru>
1.1       misha       6: */
                      7: 
                      8: #include "classes.h"
                      9: #include "pa_vmethod_frame.h"
                     10: 
                     11: #include "pa_request.h"
                     12: #include "pa_vbool.h"
1.59    ! moko       13: //#include "pa_varray.h"
1.1       misha      14: 
                     15: #include "pa_charset.h"
                     16: #include "pa_charsets.h"
1.29      moko       17: #include "pa_json.h"
1.1       misha      18: 
1.14      misha      19: #ifdef XML
                     20: #include "pa_vxdoc.h"
                     21: #endif
                     22: 
1.59    ! moko       23: volatile const char * IDENT_JSON_C="$Id: json.C,v 1.58 2023/10/01 15:14:44 moko Exp $";
1.17      moko       24: 
1.1       misha      25: // class
                     26: 
                     27: class MJson: public Methoded {
                     28: public:
                     29:        MJson();
                     30: };
                     31: 
                     32: // global variable
                     33: 
1.41      moko       34: DECLARE_CLASS_VAR(json, new MJson);
1.1       misha      35: 
                     36: // methods
1.53      moko       37: struct Json : public PA_Allocated {
1.59    ! moko       38:        Stack<VHashBase*> stack;
1.3       moko       39:        Stack<String*> key_stack;
1.1       misha      40: 
1.3       moko       41:        String* key;
1.1       misha      42:        Value* result;
                     43: 
1.16      misha      44:        Junction* hook_object;
                     45:        Junction* hook_array;
1.3       moko       46:        Request* request;
                     47: 
1.1       misha      48:        Charset *charset;
1.23      moko       49:        String::Language taint;
                     50: 
1.1       misha      51:        bool handle_double;
1.30      misha      52:        bool handle_int;
1.59    ! moko       53:        bool handle_array;
1.4       moko       54:        enum Distinct { D_EXCEPTION, D_FIRST, D_LAST, D_ALL } distinct;
1.3       moko       55: 
1.23      moko       56:        Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook_object(NULL), hook_array(NULL), 
1.30      misha      57:                request(NULL), charset(acharset), taint(String::L_TAINTED), handle_double(true), handle_int(true), 
1.59    ! moko       58:                handle_array(true), distinct(D_EXCEPTION){}
1.4       moko       59: 
                     60:        bool set_distinct(const String &value){
                     61:                if (value == "first") distinct = D_FIRST;
                     62:                else if (value == "last") distinct = D_LAST;
                     63:                else if (value == "all") distinct = D_ALL;
                     64:                else return false;
                     65:                return true;
                     66:        }
1.59    ! moko       67: 
        !            68:        bool set_handle_array(const String &value){
        !            69:                if (value == "array") handle_array = true;
        !            70:                else if (value == "hash") handle_array = false;
        !            71:                else return false;
        !            72:                return true;
        !            73:        }
1.1       misha      74: };
                     75: 
                     76: static void set_json_value(Json *json, Value *value){
1.59    ! moko       77:        VHashBase *top = json->stack.top_value();
1.3       moko       78:        if(json->key == NULL){
1.59    ! moko       79:                top->add(value);
1.1       misha      80:        } else {
1.4       moko       81:                switch (json->distinct){
                     82:                        case Json::D_EXCEPTION:
                     83:                                if (top->hash().put_dont_replace(*json->key, value))
                     84:                                        throw Exception(PARSER_RUNTIME, json->key, "duplicate key");
                     85:                                break;
                     86:                        case Json::D_FIRST:
                     87:                                top->hash().put_dont_replace(*json->key, value);
                     88:                                break;
                     89:                        case Json::D_LAST:
                     90:                                top->hash().put(*json->key, value);
                     91:                                break;
                     92:                        case Json::D_ALL:
                     93:                                if (top->hash().put_dont_replace(*json->key, value)){
                     94:                                        for(int i=2;;i++){
                     95:                                                String key;
                     96:                                                key << *json->key << "_" << format(i, 0);
                     97:                                                if (!top->hash().put_dont_replace(key, value)) break;
                     98:                                        }
                     99:                                }
                    100:                                break;
                    101:                }
1.3       moko      102:                json->key=NULL;
1.1       misha     103:        }
                    104: }
                    105: 
1.25      moko      106: String* json_string(Json *json, const char *value, uint32_t length){
1.3       moko      107:        String::C result = json->charset !=NULL ? 
1.44      moko      108:                Charset::transcode(String::C(value, length), pa_UTF8_charset, *json->charset) :
1.25      moko      109:                String::C(pa_strdup(value, length), length);
1.39      moko      110:        return new String(result, json->taint);
1.1       misha     111: }
                    112: 
1.3       moko      113: static Value *json_hook(Request &r, Junction *hook, String* key, Value* value){
1.10      moko      114:        Value *params[]={new VString(key ? *key : String::Empty), value};
1.49      moko      115:        METHOD_FRAME_ACTION(*hook->method, r.method_frame, hook->self, {
                    116:                frame.store_params(params, 2);
                    117:                r.call(frame);
                    118:                return &frame.result();
                    119:        });
1.1       misha     120: }
                    121: 
1.25      moko      122: static int json_callback(Json *json, int type, const char *value, uint32_t length)
1.1       misha     123: {
                    124:        switch(type) {
1.25      moko      125:                case JSON_OBJECT_BEGIN:{
1.4       moko      126:                        VHash *v = new VHash();
1.16      misha     127:                        if (json->hook_object){
1.1       misha     128:                                json->key_stack.push(json->key);
1.16      misha     129:                                json->key=NULL;
1.1       misha     130:                        } else {
                    131:                                if (json->stack.count()) set_json_value(json, v);
                    132:                        }
                    133:                        json->stack.push(v);
                    134:                        break;
                    135:                }
1.25      moko      136:                case JSON_OBJECT_END:{
1.16      misha     137:                        if (json->hook_object){
1.3       moko      138:                                String* key = json->key_stack.pop();
1.16      misha     139:                                json->result = json_hook(*json->request, json->hook_object, key, json->stack.pop());
1.1       misha     140:                                
                    141:                                if (json->stack.count()){
                    142:                                        json->key = key;
                    143:                                        set_json_value(json, json->result);
                    144:                                }
                    145:                        } else {
                    146:                                json->result = json->stack.pop();
                    147:                        }
                    148:                        break;
                    149:                }
1.25      moko      150:                case JSON_ARRAY_BEGIN:{
1.16      misha     151:                        if (json->hook_array){
                    152:                                json->key_stack.push(json->key);
                    153:                                json->key=NULL;
1.59    ! moko      154:                                json->stack.push(new VHash);
1.16      misha     155:                        } else {
1.59    ! moko      156:                                VHashBase *v = new VHash;
        !           157: //                             VHashBase *v = json->handle_array ? (VHashBase *)new VArray : (VHashBase *)new VHash;
1.16      misha     158:                                if (json->stack.count()) set_json_value(json, v);
1.59    ! moko      159:                                json->stack.push(v);
1.16      misha     160:                        }
1.1       misha     161:                        break;
                    162:                }
1.25      moko      163:                case JSON_ARRAY_END:
1.12      moko      164:                        // libjson supports array at top level, we too
1.16      misha     165:                        if (json->hook_array){
                    166:                                String* key = json->key_stack.pop();
                    167:                                json->result = json_hook(*json->request, json->hook_array, key, json->stack.pop());
                    168:                                
                    169:                                if (json->stack.count()){
                    170:                                        json->key = key;
                    171:                                        set_json_value(json, json->result);
                    172:                                }
                    173:                        } else {
                    174:                                json->result = json->stack.pop();
                    175:                        }
1.1       misha     176:                        break;
1.25      moko      177:                case JSON_KEY:
                    178:                        json->key = json_string(json, value, length);
1.16      misha     179:                        break;
1.25      moko      180:                case JSON_INT:
1.30      misha     181:                        if (json->handle_int){
                    182:                                set_json_value(json, new VDouble( json_string(json, value, length)->as_double() ));
                    183:                        } else {
                    184:                                // JSON_STRING
                    185:                                set_json_value(json, new VString(*json_string(json, value, length)));
                    186:                        }
1.1       misha     187:                        break;
1.25      moko      188:                case JSON_FLOAT:
1.1       misha     189:                        if (json->handle_double){
1.25      moko      190:                                set_json_value(json, new VDouble( json_string(json, value, length)->as_double() ));
1.1       misha     191:                                break;
1.25      moko      192:                        } // else is JSON_STRING
                    193:                case JSON_STRING:
                    194:                        set_json_value(json, new VString(*json_string(json, value, length)));
1.1       misha     195:                        break;
1.25      moko      196:                case JSON_NULL:
1.18      moko      197:                        set_json_value(json, VVoid::get());
1.1       misha     198:                        break;
1.25      moko      199:                case JSON_TRUE:
1.1       misha     200:                        set_json_value(json, &VBool::get(true));
                    201:                        break;
1.25      moko      202:                case JSON_FALSE:
1.1       misha     203:                        set_json_value(json, &VBool::get(false));
1.25      moko      204:                        break;
1.1       misha     205:        }
1.25      moko      206:        return 0;
1.1       misha     207: }
                    208: 
1.5       moko      209: static const char* json_error_message(int error_code){
                    210:        static const char* error_messages[] = {
1.1       misha     211:                NULL,
1.25      moko      212:                "out of memory",
                    213:                "bad character",
                    214:                "stack empty",
                    215:                "pop unexpected mode",
                    216:                "nesting limit",
                    217:                "data limit",
                    218:                "comment not allowed by config",
1.35      moko      219:                "unexpected character",
1.25      moko      220:                "missing unicode low surrogate",
                    221:                "unexpected unicode low surrogate",
                    222:                "error comma out of structure",
                    223:                "error in a callback"
1.1       misha     224:        };
                    225:        return error_messages[error_code];
                    226: }
                    227: 
1.23      moko      228: extern String::Language get_untaint_lang(const String& lang_name);
                    229: 
1.35      moko      230: #define SOURCE_MAX_LEN 60
                    231: 
                    232: void json_exception_with_source(Request& r, const char* msg, const char* json, int offset){
                    233:        int i;
                    234: 
                    235:        int line=0;
                    236:        int start=0;
                    237:        int end=strlen(json);
                    238: 
                    239:        if(offset>end)
                    240:                offset=end;
                    241: 
                    242:        for(i = 0; i < offset; i++){
                    243:                if(json[i]=='\n'){
                    244:                        line++;
                    245:                }
                    246:        }
                    247: 
                    248:        if(offset > SOURCE_MAX_LEN/2)
                    249:                start = offset - SOURCE_MAX_LEN/2;
                    250: 
                    251:        for(i = offset-1; i>=start; i--){
                    252:                if(json[i]=='\n'){
                    253:                        start=i+1;
                    254:                        break;
                    255:                }
                    256:        }
                    257: 
                    258:        if(start+SOURCE_MAX_LEN < end)
                    259:                end=start+SOURCE_MAX_LEN;
                    260: 
                    261:        for(i = offset+1; i<end; i++){
                    262:                if(json[i]=='\n'){
                    263:                        end=i;
                    264:                        break;
                    265:                }
                    266:        }
                    267: 
                    268:        char *source = pa_strdup(json+start, end-start);
                    269:        int source_offset = offset-start;
                    270: 
                    271:        if(source[source_offset]=='\n')
                    272:                source[source_offset]=' ';
                    273: 
                    274:        for(i = 0; i < source_offset; i++){
                    275:                if(source[i]=='\t'){
                    276:                        source[i]=' ';
                    277:                }
                    278:        }
                    279: 
                    280:        if(r.charsets.source().isUTF8()){
                    281:                source=(char *)fixUTF8(source);
                    282:                if(source_offset>0){
                    283:                        String s_source(pa_strdup(source,source_offset));
                    284:                        source_offset=s_source.length(r.charsets.source());
                    285:                }
                    286:        }
                    287: 
                    288:        throw Exception("json.parse", 0, "%s at line %d\n%s\n%*s", msg, line+1, source, source_offset+1, "^");
                    289: }
                    290: 
1.1       misha     291: static void _parse(Request& r, MethodParams& params) {
1.3       moko      292:        const String& json_string=params.as_string(0, "json must be string");
                    293: 
                    294:        Json json(r.charsets.source().isUTF8() ? NULL : &(r.charsets.source()));
1.1       misha     295: 
1.25      moko      296:        json_config config = {
                    297:                0,              // buffer_initial_size
1.26      moko      298:                128,            // max_nesting
1.25      moko      299:                0,              // max_data
                    300:                1,              // allow_c_comments
                    301:                1,              // allow_yaml_comments
                    302:                pa_malloc,
                    303:                pa_realloc,
                    304:                pa_free
                    305:        };
1.1       misha     306: 
                    307:        if(params.count() == 2)
                    308:                if(HashStringValue* options=params.as_hash(1)) {
                    309:                        int valid_options=0;
                    310:                        if(Value* value=options->get("depth")) {
1.46      moko      311:                                config.max_nesting=r.process(*value).as_int();
1.1       misha     312:                                valid_options++;
                    313:                        }
                    314:                        if(Value* value=options->get("double")) {
1.46      moko      315:                                json.handle_double=r.process(*value).as_bool();
1.4       moko      316:                                valid_options++;
                    317:                        }
1.30      misha     318:                        if(Value* value=options->get("int")) {
1.46      moko      319:                                json.handle_int=r.process(*value).as_bool();
1.30      misha     320:                                valid_options++;
                    321:                        }
1.4       moko      322:                        if(Value* value=options->get("distinct")) {
                    323:                                const String& sdistinct=value->as_string();
                    324:                                if (!json.set_distinct(sdistinct))
                    325:                                        throw Exception(PARSER_RUNTIME, &sdistinct, "must be 'first', 'last' or 'all'");
1.1       misha     326:                                valid_options++;
                    327:                        }
1.23      moko      328:                        if(Value* value=options->get("taint")) {
                    329:                                json.taint=get_untaint_lang(value->as_string());
                    330:                                valid_options++;
                    331:                        }
1.1       misha     332:                        if(Value* value=options->get("object")) {
1.16      misha     333:                                json.hook_object=value->get_junction();
1.3       moko      334:                                json.request=&r;
1.49      moko      335:                                if (!json.hook_object || !json.hook_object->method || !json.hook_object->method->params_names || !(json.hook_object->method->params_count == 2))
1.1       misha     336:                                        throw Exception(PARSER_RUNTIME, 0, "$.object must be parser method with 2 parameters");
                    337:                                valid_options++;
                    338:                        }
1.16      misha     339:                        if(Value* value=options->get("array")) {
1.59    ! moko      340:                                if(value->get_string()){
        !           341:                                        const String& sarray=value->as_string();
        !           342:                                        if (!json.set_handle_array(sarray))
        !           343:                                                throw Exception(PARSER_RUNTIME, &sarray, "$.array must be parser method with 2 parameters or 'array' or 'hash'");
        !           344:                                } else {
        !           345:                                        json.hook_array=value->get_junction();
        !           346:                                        json.request=&r;
        !           347:                                        if (!json.hook_array || !json.hook_array->method || !json.hook_array->method->params_names || !(json.hook_array->method->params_count == 2))
        !           348:                                                throw Exception(PARSER_RUNTIME, 0, "$.array must be parser method with 2 parameters or 'array' or 'hash'");
        !           349:                                }
1.16      misha     350:                                valid_options++;
                    351:                        }
1.1       misha     352:                        if(valid_options!=options->count())
                    353:                                throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                    354:                }
                    355: 
1.28      moko      356:        const String::Body json_body = json_string.cstr_to_string_body_untaint(String::L_JSON, r.connection(false), &r.charsets);
1.44      moko      357:        const char *json_cstr = json.charset != NULL ? Charset::transcode(json_body, *json.charset, pa_UTF8_charset).cstr() : json_body.cstr();
1.1       misha     358: 
1.25      moko      359:        json_parser parser;
                    360:        if(int result = json_parser_init(&parser, &config, (json_parser_callback)&json_callback, &json))
                    361:                throw Exception("json.parse", 0, "%s", json_error_message(result));
                    362: 
1.36      moko      363:        if(!*json_cstr)
                    364:                throw Exception("json.parse", 0, "empty string is not valid json");
                    365: 
                    366:        const char *first_quote=strchr(json_cstr,'"');
                    367:        if(first_quote && first_quote>json_cstr && *(--first_quote) == '\\')
                    368:                json_exception_with_source(r, "illegal quote escape, json may be tainted", json_cstr, first_quote-json_cstr);
                    369: 
1.25      moko      370:        uint32_t processed;
                    371:        if(int result = json_parser_string(&parser, json_cstr, strlen(json_cstr), &processed))
1.35      moko      372:                json_exception_with_source(r, json_error_message(result), json_cstr, processed);
1.3       moko      373: 
1.25      moko      374:        if (!json_parser_is_done(&parser))
1.35      moko      375:                json_exception_with_source(r, "unexpected end of json data", json_cstr, processed);
                    376: 
1.25      moko      377:        json_parser_free(&parser);
1.1       misha     378: 
1.48      moko      379:        if (json.result) r.write(*json.result);
1.1       misha     380: }
                    381: 
1.26      moko      382: const uint ANTI_ENDLESS_JSON_STRING_RECOURSION=128;
                    383: 
1.8       moko      384: char *get_indent(uint level){
                    385:        static char* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={};
                    386:        if (!cache[level]){
1.56      moko      387:                char *result = static_cast<char*>(pa_malloc_atomic(level+1));
1.8       moko      388:                memset(result, '\t', level);
1.9       moko      389:                result[level]='\0';
1.8       moko      390:                return cache[level]=result;
                    391:        }
                    392:        return cache[level];
                    393: }
                    394: 
1.56      moko      395: String *get_delim(uint level){
                    396:        static String* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={};
                    397: 
                    398:        if (!cache[level]){
                    399:                char *result = static_cast<char*>(pa_malloc_atomic(level+2+1+1));
                    400:                result[0]=',';
                    401:                result[1]='\n';
                    402:                memset(result+2, '\t', level);
                    403:                result[level+2]='"';
                    404:                result[level+3]='\0';
                    405:                return cache[level] = new String(result, String::L_AS_IS);
                    406:        }
                    407:        return cache[level];
                    408: }
                    409: 
1.59    ! moko      410: String *get_array_delim(uint level){
        !           411:        static String* cache[ANTI_ENDLESS_JSON_STRING_RECOURSION]={};
        !           412: 
        !           413:        if (!cache[level]){
        !           414:                char *result = static_cast<char*>(pa_malloc_atomic(level+2+1));
        !           415:                result[0]=',';
        !           416:                result[1]='\n';
        !           417:                memset(result+2, '\t', level);
        !           418:                result[level+2]='\0';
        !           419:                return cache[level] = new String(result, String::L_AS_IS);
        !           420:        }
        !           421:        return cache[level];
        !           422: }
        !           423: 
1.26      moko      424: class Json_string_recoursion {
                    425:        Json_options& foptions;
                    426: public:
                    427:        Json_string_recoursion(Json_options& aoptions) : foptions(aoptions) {
                    428:                if(++foptions.json_string_recoursion==ANTI_ENDLESS_JSON_STRING_RECOURSION)
                    429:                        throw Exception(PARSER_RUNTIME, 0, "call canceled - endless json recursion detected");
                    430:        }
                    431:        ~Json_string_recoursion() {
                    432:                if(foptions.json_string_recoursion)
                    433:                        foptions.json_string_recoursion--;
                    434:        }
                    435: };
                    436: 
1.21      moko      437: const String& value_json_string(String::Body key, Value& v, Json_options& options);
1.6       misha     438: 
1.37      moko      439: const String* Json_options::hash_json_string(HashStringValue *hash) {
                    440:        if(!hash || !hash->count())
1.21      moko      441:                return new String("{}", String::L_AS_IS);
1.8       moko      442: 
1.26      moko      443:        Json_string_recoursion go_down(*this);
1.8       moko      444: 
                    445:        String& result = *new String("{\n", String::L_AS_IS);
                    446: 
1.21      moko      447:        if (indent){
1.8       moko      448: 
                    449:                String *delim=NULL;
1.26      moko      450:                indent=get_indent(json_string_recoursion);
1.37      moko      451:                for(HashStringValue::Iterator i(*hash); i; i.next() ){
1.8       moko      452:                        if (delim){
                    453:                                result << *delim;
                    454:                        } else {
1.21      moko      455:                                result << indent << "\"";
1.56      moko      456:                                delim = get_delim(json_string_recoursion);
1.8       moko      457:                        }
1.21      moko      458:                        result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8       moko      459:                }
1.26      moko      460:                result << "\n" << (indent=get_indent(json_string_recoursion-1)) << "}";
1.6       misha     461: 
1.8       moko      462:        } else {
                    463: 
                    464:                bool need_delim=false;
1.37      moko      465:                for(HashStringValue::Iterator i(*hash); i; i.next() ){
1.8       moko      466:                        result << (need_delim ? ",\n\"" : "\"");
1.21      moko      467:                        result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8       moko      468:                        need_delim=true;
                    469:                }
                    470:                result << "\n}";
1.6       misha     471: 
                    472:        }
                    473: 
1.21      moko      474:        return &result;
1.6       misha     475: }
                    476: 
1.59    ! moko      477: const String* Json_options::array_json_string(ArrayValue *array) {
        !           478:        if(!array || !array->count())
        !           479:                return new String("[]", String::L_AS_IS);
        !           480: 
        !           481:        Json_string_recoursion go_down(*this);
        !           482: 
        !           483:        String& result = *new String("[\n", String::L_AS_IS);
        !           484: 
        !           485:        if (indent){
        !           486: 
        !           487:                String *delim=NULL;
        !           488:                indent=get_indent(json_string_recoursion);
        !           489:                for(ArrayValue::Iterator i(*array); i; i.next() ){
        !           490:                        if (delim){
        !           491:                                result << *delim;
        !           492:                        } else {
        !           493:                                result << indent;
        !           494:                                delim = get_array_delim(json_string_recoursion);
        !           495:                        }
        !           496:                        result << value_json_string(i.key(), i.value() ? *i.value() : *VVoid::get(), *this);
        !           497:                }
        !           498:                result << "\n" << (indent=get_indent(json_string_recoursion-1)) << "]";
        !           499: 
        !           500:        } else {
        !           501: 
        !           502:                bool need_delim=false;
        !           503:                for(ArrayValue::Iterator i(*array); i; i.next() ){
        !           504:                        if(need_delim) result << ",\n";
        !           505:                        result << value_json_string(i.key(), i.value() ? *i.value() : *VVoid::get(), *this);
        !           506:                        need_delim=true;
        !           507:                }
        !           508:                result << "\n]";
        !           509: 
        !           510:        }
        !           511: 
        !           512:        return &result;
        !           513: }
        !           514: 
        !           515: const String* Json_options::array_compact_json_string(ArrayValue *array) {
        !           516:        if(!array || !array->count())
        !           517:                return new String("[]", String::L_AS_IS);
        !           518: 
        !           519:        Json_string_recoursion go_down(*this);
        !           520: 
        !           521:        String& result = *new String("[\n", String::L_AS_IS);
        !           522: 
        !           523:        if (indent){
        !           524: 
        !           525:                String *delim=NULL;
        !           526:                indent=get_indent(json_string_recoursion);
        !           527:                for(ArrayValue::Iterator i(*array); i; i.next() ){
        !           528:                        if (i.value()){
        !           529:                                if (delim){
        !           530:                                        result << *delim;
        !           531:                                } else {
        !           532:                                        result << indent;
        !           533:                                        delim = get_array_delim(json_string_recoursion);
        !           534:                                }
        !           535:                                result << value_json_string(i.key(), *i.value(), *this);
        !           536:                        }
        !           537:                }
        !           538:                result << "\n" << (indent=get_indent(json_string_recoursion-1)) << "]";
        !           539: 
        !           540:        } else {
        !           541: 
        !           542:                bool need_delim=false;
        !           543:                for(ArrayValue::Iterator i(*array); i; i.next() ){
        !           544:                        if (i.value()){
        !           545:                                if(need_delim) result << ",\n";
        !           546:                                result << value_json_string(i.key(), *i.value(), *this);
        !           547:                                need_delim=true;
        !           548:                        }
        !           549:                }
        !           550:                result << "\n]";
        !           551: 
        !           552:        }
        !           553: 
        !           554:        return &result;
        !           555: }
        !           556: 
1.21      moko      557: static bool based_on(HashStringValue::key_type key, HashStringValue::value_type /*value*/, Value* v) {
1.15      misha     558:        return v->is(key.cstr());
                    559: }
1.26      moko      560: 
1.21      moko      561: const String& value_json_string(String::Body key, Value& v, Json_options& options) {
                    562:        if(options.methods) {
                    563:                Value* method=options.methods->get(v.type());
                    564:                if(!method){
                    565:                        method=options.methods->first_that<Value*>(based_on, &v);
1.31      misha     566:                        options.methods->put(v.type(), method ? method : VVoid::get());
1.21      moko      567:                }
                    568:                if(method && !method->is_void()) {
1.6       misha     569:                        Junction* junction=method->get_junction();
1.26      moko      570:                        HashStringValue* params_hash=options.params && options.indent ? options.params->get_hash() : NULL;
1.27      moko      571:                        Temp_hash_value<HashStringValue, Value*> indent(params_hash, "indent", new VString(*new String(options.indent, String::L_AS_IS)));
1.26      moko      572: 
1.21      moko      573:                        Value *params[]={new VString(*new String(key, String::L_JSON)), &v, options.params ? options.params : VVoid::get()};
1.6       misha     574: 
1.49      moko      575:                        METHOD_FRAME_ACTION(*junction->method, options.r->method_frame, junction->self, {
                    576:                                frame.store_params(params, 3);
                    577:                                options.r->call(frame);
                    578:                                return frame.result().as_string();
                    579:                        });
1.6       misha     580:                }
1.15      misha     581:        }
1.6       misha     582: 
1.21      moko      583:        options.key=key;
1.6       misha     584:        return *v.get_json_string(options);
                    585: }
                    586: 
                    587: static void _string(Request& r, MethodParams& params) {
                    588:        Json_options json(&r);
                    589: 
                    590:        if(params.count() == 2)
                    591:                if(HashStringValue* options=params.as_hash(1)) {
1.47      moko      592:                        json.params=&params[1];
1.6       misha     593:                        HashStringValue* methods=new HashStringValue();
                    594:                        int valid_options=0;
1.14      misha     595:                        HashStringValue* vvalue;
1.6       misha     596:                        for(HashStringValue::Iterator i(*options); i; i.next() ){
                    597:                                String::Body key=i.key();
                    598:                                Value* value=i.value();
                    599:                                if(key == "skip-unknown"){
1.46      moko      600:                                        json.skip_unknown=r.process(*value).as_bool();
1.6       misha     601:                                        valid_options++;
1.50      moko      602:                                } else if(key == "one-line"){
                    603:                                        json.one_line=r.process(*value).as_bool();
                    604:                                        valid_options++;
1.6       misha     605:                                } else if(key == "date" && value->is_string()){
                    606:                                        const String& svalue=value->as_string();
                    607:                                        if(!json.set_date_format(svalue))
1.38      moko      608:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string', 'iso-string' or 'unix-timestamp'");
1.6       misha     609:                                        valid_options++;
1.8       moko      610:                                } else if(key == "indent"){
1.26      moko      611:                                        if(value->is_string()){
                    612:                                                json.indent=value->as_string().cstr();
                    613:                                                json.json_string_recoursion=strlen(json.indent);
1.46      moko      614:                                        } else json.indent=r.process(*value).as_bool() ? "" : NULL;
1.8       moko      615:                                        valid_options++;
1.6       misha     616:                                } else if(key == "table" && value->is_string()){
                    617:                                        const String& svalue=value->as_string();
                    618:                                        if(!json.set_table_format(svalue))
1.13      moko      619:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'");
1.6       misha     620:                                        valid_options++;
1.59    ! moko      621:                                } else if(key == "array" && value->is_string()){
        !           622:                                        const String& svalue=value->as_string();
        !           623:                                        if(!json.set_array_format(svalue))
        !           624:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'");
        !           625:                                        valid_options++;
1.6       misha     626:                                } else if(key == "file" && value->is_string()){
                    627:                                        const String& svalue=value->as_string();
                    628:                                        if(!json.set_file_format(svalue))
1.19      misha     629:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'");
1.6       misha     630:                                        valid_options++;
1.32      misha     631:                                } else if(key == "void" && value->is_string()){
                    632:                                        const String& svalue=value->as_string();
                    633:                                        if(!json.set_void_format(svalue))
                    634:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'string' or 'null'");
                    635:                                        valid_options++;
1.14      misha     636: #ifdef XML
                    637:                                } else if(key == "xdoc" && (vvalue = value->get_hash())){
1.24      moko      638:                                        json.xdoc_options=new XDocOutputOptions();
                    639:                                        json.xdoc_options->append(r, vvalue);
1.14      misha     640:                                        valid_options++;
                    641: #endif
1.6       misha     642:                                } else if(Junction* junction=value->get_junction()){
1.49      moko      643:                                        if(!junction->method || !junction->method->params_names || junction->method->params_count != 3)
1.13      moko      644:                                                throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", key.cstr());
1.6       misha     645:                                        methods->put(key, value);
                    646:                                        valid_options++;
                    647:                                }
                    648:                        }
1.22      moko      649: 
1.6       misha     650:                        if(valid_options!=options->count())
                    651:                                throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.22      moko      652: 
                    653:                        // special handling for $._default 
1.43      moko      654:                        if(VHashBase* vhash=static_cast<VHashBase*>(params[1].as(VHASH_TYPE)))
1.22      moko      655:                                if(Value* value=vhash->get_default()) {
1.34      misha     656:                                        if(!value->is_string()){
1.43      moko      657:                                                Junction* junction=value->get_junction();
1.49      moko      658:                                                if(!junction || !junction->method || !junction->method->params_names || junction->method->params_count != 3)
1.42      moko      659:                                                        throw Exception(PARSER_RUNTIME, 0, "$._default must be string or parser method with 3 parameters");
1.34      misha     660:                                        }
1.22      moko      661:                                        json.default_method=value;
                    662:                                }
                    663: 
1.6       misha     664:                        if(methods->count())
                    665:                                json.methods=methods;
                    666:                }
1.14      misha     667: 
1.46      moko      668:        const String& result_string=value_json_string(String::Body(), r.process(params[0]), json);
1.28      moko      669:        String::Body result_body=result_string.cstr_to_string_body_untaint(String::L_JSON, r.connection(false), &r.charsets);
1.50      moko      670:        if(json.one_line){
                    671:                char *result=result_body.cstrm();
                    672:                for(char *c=result;*c;c++)
                    673:                        if(*c=='\n')
                    674:                                *c=' ';
                    675:                result_body=result;
                    676:        }
1.48      moko      677:        r.write(*new String(result_body, String::L_AS_IS));
1.50      moko      678: }
1.6       misha     679: 
1.1       misha     680: // constructor
                    681: 
                    682: MJson::MJson(): Methoded("json") {
                    683:        add_native_method("parse", Method::CT_STATIC, _parse, 1, 2);
1.6       misha     684: 
1.58      moko      685:        add_native_method("string", Method::CT_STATIC, _string, 1, 2);
1.1       misha     686: }

E-mail: