From 44304381a1fc3303ffff3d605728ba68ad1b2cb6 Mon Sep 17 00:00:00 2001 From: Yossi Kantor Date: Wed, 3 Apr 2013 15:35:50 +0300 Subject: Eina: JSON parser --- src/Makefile_Eina.am | 9 +- src/benchmarks/eina/Makefile.am | 1 + src/benchmarks/eina/eina_bench.c | 1 + src/benchmarks/eina/eina_bench.h | 1 + src/benchmarks/eina/eina_bench_json.c | 59 ++ src/examples/eina/eina_json_01.c | 59 ++ src/examples/eina/eina_json_02.c | 68 ++ src/examples/eina/eina_json_03.c | 100 +++ src/lib/eina/Eina.h | 3 +- src/lib/eina/eina_json.c | 1294 +++++++++++++++++++++++++++++++++ src/lib/eina/eina_json.h | 899 +++++++++++++++++++++++ src/tests/eina/eina_suite.c | 1 + src/tests/eina/eina_suite.h | 1 + src/tests/eina/eina_test_json.c | 613 ++++++++++++++++ 14 files changed, 3105 insertions(+), 4 deletions(-) create mode 100644 src/benchmarks/eina/eina_bench_json.c create mode 100644 src/examples/eina/eina_json_01.c create mode 100644 src/examples/eina/eina_json_02.c create mode 100644 src/examples/eina/eina_json_03.c create mode 100644 src/lib/eina/eina_json.c create mode 100644 src/lib/eina/eina_json.h create mode 100644 src/tests/eina/eina_test_json.c diff --git a/src/Makefile_Eina.am b/src/Makefile_Eina.am index 7e40749d1f..72b2ea5726 100644 --- a/src/Makefile_Eina.am +++ b/src/Makefile_Eina.am @@ -80,7 +80,8 @@ lib/eina/eina_inline_lock_barrier.x \ lib/eina/eina_tmpstr.h \ lib/eina/eina_alloca.h \ lib/eina/eina_cow.h \ -lib/eina/eina_inline_unicode.x +lib/eina/eina_inline_unicode.x \ +lib/eina/eina_json.h # Will be back for developper after 1.2. # lib/eina/eina_model.h @@ -147,7 +148,8 @@ lib/eina/eina_value.c \ lib/eina/eina_xattr.c \ lib/eina/eina_share_common.h \ lib/eina/eina_private.h \ -lib/eina/eina_strbuf_common.h +lib/eina/eina_strbuf_common.h \ +lib/eina/eina_json.c # Will be back for developper after 1.2 # lib/eina/eina_model.c \ @@ -267,7 +269,8 @@ tests/eina/eina_test_simple_xml_parser.c \ tests/eina/eina_test_value.c \ tests/eina/eina_test_cow.c \ tests/eina/eina_test_barrier.c \ -tests/eina/eina_test_tmpstr.c +tests/eina/eina_test_tmpstr.c \ +tests/eina/eina_test_json.c # tests/eina/eina_test_model.c tests_eina_eina_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \ diff --git a/src/benchmarks/eina/Makefile.am b/src/benchmarks/eina/Makefile.am index 63cf39f8d0..3c8ae681a3 100644 --- a/src/benchmarks/eina/Makefile.am +++ b/src/benchmarks/eina/Makefile.am @@ -24,6 +24,7 @@ eina_bench_mempool.c \ eina_bench_stringshare_e17.c \ eina_bench_array.c \ eina_bench_rectangle_pool.c \ +eina_bench_json.c \ ecore_list.c \ ecore_strings.c \ ecore_hash.c \ diff --git a/src/benchmarks/eina/eina_bench.c b/src/benchmarks/eina/eina_bench.c index 5bd35f819b..5bb98d99e9 100644 --- a/src/benchmarks/eina/eina_bench.c +++ b/src/benchmarks/eina/eina_bench.c @@ -36,6 +36,7 @@ struct _Eina_Benchmark_Case static const Eina_Benchmark_Case etc[] = { { "Hash", eina_bench_hash }, + /* { "JSON", eina_bench_json }, */ /* { "Array vs List vs Inlist", eina_bench_array }, */ /* { "Stringshare", eina_bench_stringshare }, */ /* { "Convert", eina_bench_convert }, */ diff --git a/src/benchmarks/eina/eina_bench.h b/src/benchmarks/eina/eina_bench.h index d575822b54..6c8b12526e 100644 --- a/src/benchmarks/eina/eina_bench.h +++ b/src/benchmarks/eina/eina_bench.h @@ -29,6 +29,7 @@ void eina_bench_sort(Eina_Benchmark *bench); void eina_bench_mempool(Eina_Benchmark *bench); void eina_bench_rectangle_pool(Eina_Benchmark *bench); void eina_bench_quadtree(Eina_Benchmark *bench); +void eina_bench_json(Eina_Benchmark *bench); /* Specific benchmark. */ void eina_bench_e17(void); diff --git a/src/benchmarks/eina/eina_bench_json.c b/src/benchmarks/eina/eina_bench_json.c new file mode 100644 index 0000000000..e18eab8f7e --- /dev/null +++ b/src/benchmarks/eina/eina_bench_json.c @@ -0,0 +1,59 @@ +/* EINA - EFL data type library + * Copyright (C) 2013 Yossi Kantor + Cedric Bail + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#ifdef EINA_BENCH_HAVE_GLIB +# include +#endif + +#include "Evas_Data.h" +#include "Ecore_Data.h" + +#include "eina_json.h" +#include "eina_main.h" +#include "eina_bench.h" + +static void +eina_bench_json_parse(int request) +{ + int i; + eina_init(); + + Eina_Json_Context *jctx = eina_json_context_dom_new(); + eina_json_context_parse(jctx, "{"); + for (i = 0; i < request; i++) + eina_json_context_parse(jctx, "\"Bench\":[\"Json\", 34.5, true, null],"); + eina_json_context_parse(jctx, "\"End\":null }"); + + eina_shutdown(); +} + +void eina_bench_json(Eina_Benchmark *bench) +{ + eina_benchmark_register(bench, "json", + EINA_BENCHMARK(eina_bench_json_parse), + 100, 100000, 500); +} diff --git a/src/examples/eina/eina_json_01.c b/src/examples/eina/eina_json_01.c new file mode 100644 index 0000000000..036fbae5f8 --- /dev/null +++ b/src/examples/eina/eina_json_01.c @@ -0,0 +1,59 @@ +//Compile with: +//gcc -g eina_json_01.c -o eina_json_01 `pkg-config --cflags --libs eina` + +// This example demonstrates a simple use of json context parsing of a complete string with example +// of a result's diagnostics and output. + +#include "Eina.h" + +#define DOERROR(x,args...) { printf(x,## args); return 1; } +#define BUFFSIZE 100 + +char *json_err_name[] = {"No error","Lexical error","Syntax Error","Input Past End"}; +char my_json[] = +"\ +{\n\ +\"String\":\"MyJSON\",\n \ +\"Array\":[1,2,3,true,5,false,\"\",{},null,[78,\"Hello\"],\"World\"],\n \ +\"Object\": { \"Subobj\":{ \"Sub1\":null,\"Sub2\":56} }\n \ +}\ +"; + +int +main(int argc,void **argv) +{ + eina_init(); + + // Its a DOM parsing. When its done without errors, we'll have a json tree to take. + Eina_Json_Context *ctx = eina_json_context_dom_new(); + + //Now parse + eina_json_context_parse(ctx, my_json); + + //Analize and report results + Eina_Json_Error jsnerr = eina_json_context_error_get(ctx); + if (jsnerr) + { + printf ("Parsing failed\n"); + printf ("Error %d:%d %s\n", eina_json_context_line_get(ctx), + eina_json_context_column_get(ctx), + json_err_name[jsnerr]); + } + else if (eina_json_context_completed_get(ctx)) + { + // If parsing was successful - take the json tree and print it + printf ("Successfully parsed\n"); + Eina_Json_Value *jsnval = eina_json_context_dom_tree_take(ctx); + char *json_output = eina_json_format_string_get(jsnval, EINA_JSON_FORMAT_BASIC); + eina_json_value_free(jsnval); + printf("%s\n", json_output); + free(json_output); + } + else + printf ("Parsing was not completed\n"); + + eina_json_context_free(ctx); + eina_shutdown(); + + return 0; +} diff --git a/src/examples/eina/eina_json_02.c b/src/examples/eina/eina_json_02.c new file mode 100644 index 0000000000..546a219393 --- /dev/null +++ b/src/examples/eina/eina_json_02.c @@ -0,0 +1,68 @@ +//Compile with: +//gcc -g eina_json_02.c -o eina_json_02 `pkg-config --cflags --libs eina` + +// This example reads and parses json text gradually from file by BUFFSIZE chunks. +// Usage: eina_json_02 jsonfile + +#include "Eina.h" + +#define DOERROR(x,args...) { printf(x,## args); return 1; } +#define BUFFSIZE 100 + +char *json_err_name[]={"No error","Lexical error","Syntax Error","Input Past End"}; +char rbuff[BUFFSIZE] = {0}; + +int +main(int argc, void **argv) +{ + size_t rd; + + eina_init(); + + if (argc < 2) DOERROR("Usage: eina_json_02 filename \n"); + + FILE* fp = fopen(argv[1], "r"); + if (!fp) DOERROR("Error openning file file %s\n", (char*)argv[1]); + + // Its a DOM parsing. When its done without errors, we'll have a json tree to take. + Eina_Json_Context *ctx = eina_json_context_dom_new(); + + // Read and parse json file by BUFFSIZE chunks + while (eina_json_context_unfinished_get(ctx)) + { + rd = fread(rbuff, 1, BUFFSIZE,fp); + eina_json_context_parse_n(ctx, rbuff, rd); + + //End of file reached + if (rd != BUFFSIZE) break; + } + + //Report results + Eina_Json_Error jsnerr = eina_json_context_error_get(ctx); + if (jsnerr) + { + printf ("Parsing failed\n"); + printf ("Error %d:%d %s\n",eina_json_context_line_get(ctx), + eina_json_context_column_get(ctx), + json_err_name[jsnerr]); + } + else if (eina_json_context_completed_get(ctx)) + { + // If parsing was successful - take the json tree and print it + printf ("File %s was successfully parsed \n\n", (char*)argv[1]); + Eina_Json_Value *jsnval = eina_json_context_dom_tree_take(ctx); + char *json_output = eina_json_format_string_get(jsnval, EINA_JSON_FORMAT_BASIC); + eina_json_value_free(jsnval); + printf("%s\n", json_output); + free(json_output); + } + else + printf ("Parsing was not completed - file is incomplete\n"); + + eina_json_context_free(ctx); + fclose(fp); + + eina_shutdown(); + + return 0; +} diff --git a/src/examples/eina/eina_json_03.c b/src/examples/eina/eina_json_03.c new file mode 100644 index 0000000000..9b5a0e113f --- /dev/null +++ b/src/examples/eina/eina_json_03.c @@ -0,0 +1,100 @@ +//Compile with: +//gcc -g eina_json_03.c -o eina_json_03 `pkg-config --cflags --libs eina` + +// This example demonstrates a simple use of a json context SAX parsing +// with callback function which prints indented json objects + +#include "Eina.h" + +#define DOERROR(x,args...) { printf(x,## args); return 1; } +#define BUFFSIZE 100 + +typedef struct +{ + Eina_Strbuf *text; + unsigned long parent_idx; +} Sax_Parser_Data; + +char *json_err_name[]={"No error","Lexical error","Syntax Error","Input Past End"}; + +char *json_type_string[]={"NULL","NUMBER","STRING","BOOLEAN","PAIR","OBJECT","ARRAY"}; + +char my_json[] = +"{\n\ +\"String\":\"MyJSON\",\n \ +\"Array\":[1,2,3,true,5,false,\"\",{},null,[78,\"Hello\"],\"World\"],\n \ +\"Object\": { \"Subobj\":{ \"Sub1\":null,\"Sub2\":56} }\n \ +}"; + +void * +sax_parser_cb(Eina_Json_Type type, void *parent, const char *text, void *data) +{ + Sax_Parser_Data *saxdata = (Sax_Parser_Data *)data; + const char* strval = NULL; + + switch(type) + { + case EINA_JSON_TYPE_NUMBER: + case EINA_JSON_TYPE_STRING: + case EINA_JSON_TYPE_PAIR: + strval = text; + break; + case EINA_JSON_TYPE_BOOLEAN: + strval = (*text == 't') ? "true" : "false"; + break; + } + + saxdata->parent_idx++; + eina_strbuf_append_printf(saxdata->text, + "(%p) PARENT(%p) TYPE:%s", + (void*)saxdata->parent_idx, + parent, + json_type_string[type]); + + if (strval) eina_strbuf_append_printf(saxdata->text, " = \"%s\"", strval); + eina_strbuf_append_char(saxdata->text, '\n'); + + return (void*)saxdata->parent_idx; +} + +int +main(int argc,void **argv) +{ + Sax_Parser_Data mysax; + + eina_init(); + + mysax.text = eina_strbuf_new(); + mysax.parent_idx = 1; + + // Its a SAX parsing. We initilize it with our our callback function + // and the data to be used by the callback. + Eina_Json_Context *ctx = eina_json_context_sax_new(sax_parser_cb, &mysax); + + //Now parse + eina_json_context_parse(ctx, my_json); + + //Analize and report results + Eina_Json_Error jsnerr = eina_json_context_error_get(ctx); + if (jsnerr) + { + printf ("Parsing failed\n"); + printf ("Error %d:%d %s\n", eina_json_context_line_get(ctx), + eina_json_context_column_get(ctx), + json_err_name[jsnerr]); + } + else if (eina_json_context_completed_get(ctx)) + { + // If parsing was successful - print the text that our callback produced. + printf ("File was successfully parsed\n\n"); + printf("%s\n", eina_strbuf_string_get(mysax.text)); + } + else + printf ("Parsing was not completed\n"); + + eina_strbuf_free(mysax.text); + eina_json_context_free(ctx); + + eina_shutdown(); + return 0; +} diff --git a/src/lib/eina/Eina.h b/src/lib/eina/Eina.h index e22d6e2060..37a9d5a7c0 100644 --- a/src/lib/eina/Eina.h +++ b/src/lib/eina/Eina.h @@ -203,7 +203,7 @@ * * Eina tools aims to help application development, providing ways to * make it safer, log errors, manage memory more efficiently and more. - * + * */ #ifdef _WIN32 @@ -263,6 +263,7 @@ extern "C" { #include "eina_xattr.h" #include "eina_value.h" #include "eina_cow.h" +#include "eina_json.h" #ifdef __cplusplus } diff --git a/src/lib/eina/eina_json.c b/src/lib/eina/eina_json.c new file mode 100644 index 0000000000..4288249082 --- /dev/null +++ b/src/lib/eina/eina_json.c @@ -0,0 +1,1294 @@ +/* EINA - EFL data type library + * Copyright (C) 2013 Yossi Kantor + * Cedric Bail + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#include "eina_json.h" +#include +#include + +/*============================================================================* + * Local * + *============================================================================*/ + +#define _RETURN_SW(st, retr) { Stm_Switch _sw; _sw.state = st;\ + _sw.retrans = retr; return _sw; } + +#define _CASE_DIGIT case '0':case '1':case '2':\ + case '3':case '4':case '5':\ + case '6':case '7':case '8':\ + case '9' + +#define _ISDIGIT(a) (a >= '0' && a <= '9') + +#define JSON_GLUE_BUFF_STEP 64 + +static Eina_Json_Value *_eina_json_type_new(Eina_Json_Type type); + +typedef struct _Stm_State Stm_State; +typedef struct _Stm_Machine Stm_Machine; +typedef struct _Stm_switch Stm_Switch; +typedef unsigned int Stm_Val; + +typedef Stm_Switch(*Stm_State_Cb)(Stm_State *state, Stm_Val token, void* data); + +struct _Stm_switch +{ + Stm_State *state; + Eina_Bool retrans; +}; + +struct _Stm_State +{ + Stm_State_Cb state_cb; + void *param_ptr1; + int param_int1; +}; + +struct _Stm_Machine +{ + Stm_State *initial_state; + Stm_State *current_state; + void *data; +}; + +struct _lex_keyword_param +{ + char* str; + int len; + Stm_Val token; +}; + +struct _Eina_Json_Value +{ + EINA_INLIST; + Eina_Json_Type type; + Eina_Json_Value *parent; + union + { + double number; + Eina_Strbuf *string; + Eina_Bool boolean; + Eina_Inlist *lst; + struct _Pair + { + Eina_Strbuf *name; + Eina_Json_Value *val; + } pair; + }; +}; + +struct _Eina_Json_Context +{ + Stm_Machine lex_machine; + Stm_Machine syntax_machine; + + Eina_Json_Error latest_error; + + Eina_Json_Value *jobj; + + Eina_Array *jstack; + void *jparent; + Eina_Json_Type jparent_type; + Eina_Json_Parser_Cb parser_cb; + void *cb_data; + + const char *str; + const char *head; + + Eina_Bool glue_on; + char *glue_buf; + unsigned glue_buf_size; + unsigned glue_len; + + unsigned line; + unsigned col; +}; + +enum _Eina_Json_Token +{ + JSON_STRING = 1, + JSON_NUMBER, + JSON_TRUE, + JSON_FALSE, + JSON_NULL, + JSON_OBJ_OPEN = '{', + JSON_OBJ_CLOSE = '}', + JSON_ARR_OPEN = '[', + JSON_ARR_CLOSE = ']', + JSON_COMMA = ',', + JSON_COLON = ':' +}; + +// Syntax and Lexical parsing state's callbacks forward declaration +static Stm_Switch _syntx_entry_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _syntx_value_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _syntx_new_object_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _syntx_object_name_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _syntx_object_colon_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _syntx_object_next_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _syntx_array_next_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _syntx_end_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _syntx_unexpected_cb(Stm_State *state, Stm_Val token, void *data); + +static Stm_Switch _lex_initial_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_int_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_frac_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_digit_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_exp_sign_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_exp_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_string_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_string_esc_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_keyword_cb(Stm_State *state, Stm_Val token, void *data); +static Stm_Switch _lex_unexpected_cb(Stm_State *state, Stm_Val token, void *data); + +// Syntax states +static Stm_State syntx_entry = { _syntx_entry_cb }; +static Stm_State syntx_end = { _syntx_end_cb }; +static Stm_State syntx_new_object = { _syntx_new_object_cb }; +static Stm_State syntx_value = { _syntx_value_cb }; +static Stm_State syntx_object_name = { _syntx_object_name_cb }; +static Stm_State syntx_object_colon = { _syntx_object_colon_cb }; +static Stm_State syntx_next_object = { _syntx_object_next_cb }; +static Stm_State syntx_next_array = { _syntx_array_next_cb }; +static Stm_State syntx_unexpected = { _syntx_unexpected_cb }; +static const Stm_Switch switch_syntax_unexpected = { &syntx_unexpected, EINA_TRUE }; + +// Lexical states +static struct _lex_keyword_param lex_keyword_true = {"true", 4, JSON_TRUE}; +static struct _lex_keyword_param lex_keyword_false = {"false", 5, JSON_FALSE}; +static struct _lex_keyword_param lex_keyword_null = {"null", 4, JSON_NULL}; + +static Stm_State lex_initial = {_lex_initial_cb }; +static Stm_State lex_int = { _lex_int_cb }; +static Stm_State lex_int_entry = {_lex_digit_cb, (void*)&lex_int }; +static Stm_State lex_fraction = {_lex_frac_cb }; +static Stm_State lex_fraction_entry = {_lex_digit_cb, (void*)&lex_fraction }; +static Stm_State lex_exp_sign = { _lex_exp_sign_cb }; +static Stm_State lex_exp = { _lex_exp_cb }; +static Stm_State lex_exp_entry = {_lex_digit_cb, (void*)&lex_exp }; +static Stm_State lex_string = {_lex_string_cb }; +static Stm_State lex_string_esc = {_lex_string_esc_cb }; +static Stm_State lex_unexpected = { _lex_unexpected_cb }; +static Stm_State lex_true = {_lex_keyword_cb, (void*)&lex_keyword_true }; +static Stm_State lex_false = {_lex_keyword_cb, (void*)&lex_keyword_false }; +static Stm_State lex_null = {_lex_keyword_cb, (void*)&lex_keyword_null }; +static const Stm_Switch switch_lex_unexpected = { &lex_unexpected, EINA_TRUE }; + + +void * +_eina_json_parser_dom_cb(Eina_Json_Type type, void *parent, const char *text, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + Eina_Json_Value *newjval = NULL; + + switch(type) + { + case EINA_JSON_TYPE_NULL: + newjval = eina_json_null_new(); + break; + case EINA_JSON_TYPE_NUMBER: + newjval = eina_json_number_new(atof(text)); + break; + case EINA_JSON_TYPE_STRING: + newjval = eina_json_string_new(text); + break; + case EINA_JSON_TYPE_BOOLEAN: + newjval = eina_json_boolean_new((*text == 't') ? EINA_TRUE : EINA_FALSE ); + break; + case EINA_JSON_TYPE_PAIR: + newjval = _eina_json_type_new(EINA_JSON_TYPE_PAIR); + if (newjval) + { + newjval->pair.name = eina_strbuf_new(); + eina_strbuf_append(newjval->pair.name, text); + } + break; + case EINA_JSON_TYPE_OBJECT: + newjval = eina_json_object_new(); + break; + case EINA_JSON_TYPE_ARRAY: + newjval = eina_json_array_new(); + break; + } + + if (!newjval) return NULL; + + if (parent) + { + Eina_Json_Value *jcontainer = (Eina_Json_Value*)parent; + switch(jcontainer->type) + { + case EINA_JSON_TYPE_PAIR: + jcontainer->pair.val = newjval; + break; + case EINA_JSON_TYPE_OBJECT: + jcontainer->lst = eina_inlist_append(jcontainer->lst, + EINA_INLIST_GET(newjval)); + break; + case EINA_JSON_TYPE_ARRAY: + eina_json_array_append(jcontainer, newjval); + break; + default: + break; + } + } + else + { + jsctx->jobj = newjval; + } + return newjval; +} + +void +_state_machine_feed(Stm_Machine *machine, Stm_Val token) +{ + while (machine->current_state) + { + Stm_Switch sw = + machine->current_state->state_cb + (machine->current_state, token, machine->data); + + machine->current_state = sw.state; + if (!sw.retrans) break; + } +} + +inline static void +_syntax_token_process(Stm_Val token, Eina_Json_Context *data) +{ + _state_machine_feed(&(data->syntax_machine), token); +} + +static Stm_Switch +_syntx_entry_cb(Stm_State *state EINA_UNUSED, Stm_Val token, void *data EINA_UNUSED) +{ + switch (token) + { + case JSON_OBJ_OPEN: + case JSON_ARR_OPEN: + _RETURN_SW(&syntx_value, EINA_TRUE); + } + return switch_syntax_unexpected; +} + +static Stm_Switch +_syntx_value_cb(Stm_State *state EINA_UNUSED, Stm_Val token, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + Stm_State* nextstate = NULL; + Eina_Json_Type jtype; + Eina_Bool call_cb = EINA_TRUE; + void* valptr = NULL; + + switch (token) + { + case JSON_OBJ_CLOSE: + case JSON_ARR_CLOSE: + if (eina_array_count(jsctx->jstack) < 1) + _RETURN_SW(&syntx_end, EINA_FALSE); + jsctx->jparent = eina_array_pop(jsctx->jstack); + jsctx->jparent_type = (Eina_Json_Type)eina_array_pop(jsctx->jstack); + call_cb = EINA_FALSE; + break; + + case JSON_OBJ_OPEN: + nextstate = &syntx_new_object; + jtype = EINA_JSON_TYPE_OBJECT; + break; + + case JSON_ARR_OPEN: + nextstate = &syntx_value; + jtype = EINA_JSON_TYPE_ARRAY; + break; + + case JSON_NUMBER: jtype = EINA_JSON_TYPE_NUMBER; break; + case JSON_STRING: jtype = EINA_JSON_TYPE_STRING; break; + case JSON_TRUE: case JSON_FALSE: jtype = EINA_JSON_TYPE_BOOLEAN; break; + case JSON_NULL: jtype = EINA_JSON_TYPE_NULL; break; + + default: + return switch_syntax_unexpected; + } + + if (call_cb) + { + valptr = jsctx->parser_cb(jtype, jsctx->jparent, jsctx->glue_buf, + jsctx->cb_data); + + if (!valptr) + return switch_syntax_unexpected; + } + + if (nextstate) + { + if (jsctx->jparent) + { + eina_array_push(jsctx->jstack, (void*)jsctx->jparent_type); + eina_array_push(jsctx->jstack, jsctx->jparent); + } + jsctx->jparent = valptr; + jsctx->jparent_type = jtype; + } else + { + if (jsctx->jparent_type == EINA_JSON_TYPE_PAIR) + { + jsctx->jparent = eina_array_pop(jsctx->jstack); + jsctx->jparent_type = (Eina_Json_Type)eina_array_pop(jsctx->jstack); + } + nextstate = (jsctx->jparent_type == EINA_JSON_TYPE_OBJECT) ? + &syntx_next_object : + &syntx_next_array; + } + + _RETURN_SW(nextstate, EINA_FALSE); +} + +static Stm_Switch +_syntx_new_object_cb(Stm_State *state EINA_UNUSED, Stm_Val token, void *data EINA_UNUSED) +{ + switch (token) + { + case JSON_OBJ_CLOSE: + _RETURN_SW(&syntx_value, EINA_TRUE); + case JSON_STRING: + _RETURN_SW(&syntx_object_name, EINA_TRUE); + } + return switch_syntax_unexpected; +} + +static +Stm_Switch _syntx_object_name_cb(Stm_State *state EINA_UNUSED, Stm_Val token, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + + if (token == JSON_STRING) + { + eina_array_push(jsctx->jstack, (void*)jsctx->jparent_type); + eina_array_push(jsctx->jstack, jsctx->jparent); + + jsctx->jparent_type = EINA_JSON_TYPE_PAIR; + jsctx->jparent = jsctx->parser_cb(EINA_JSON_TYPE_PAIR, jsctx->jparent, + jsctx->glue_buf, jsctx->cb_data); + if (jsctx->jparent) _RETURN_SW(&syntx_object_colon, EINA_FALSE); + } + return switch_syntax_unexpected; +} + +static Stm_Switch +_syntx_object_colon_cb(Stm_State *state EINA_UNUSED, Stm_Val token, void *data EINA_UNUSED) +{ + if (token == JSON_COLON) + _RETURN_SW(&syntx_value, EINA_FALSE); + return switch_syntax_unexpected; +} + +static Stm_Switch +_syntx_object_next_cb(Stm_State *state EINA_UNUSED, Stm_Val token, void *data EINA_UNUSED) +{ + switch (token) + { + case JSON_COMMA: + _RETURN_SW(&syntx_object_name, EINA_FALSE); + case JSON_OBJ_CLOSE: + _RETURN_SW(&syntx_value, EINA_TRUE); + } + return switch_syntax_unexpected; +} + +static Stm_Switch +_syntx_array_next_cb(Stm_State *state EINA_UNUSED, Stm_Val token, void *data EINA_UNUSED) +{ + switch (token) + { + case JSON_COMMA: + _RETURN_SW(&syntx_value, EINA_FALSE); + case JSON_ARR_CLOSE: + _RETURN_SW(&syntx_value, EINA_TRUE); + } + return switch_syntax_unexpected; +} + +static Stm_Switch +_syntx_end_cb(Stm_State *state EINA_UNUSED, Stm_Val token EINA_UNUSED, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + jsctx->latest_error = EINA_JSON_ERROR_PAST_END; + _RETURN_SW(NULL, EINA_FALSE); +} + +static Stm_Switch +_syntx_unexpected_cb(Stm_State *state EINA_UNUSED, Stm_Val token EINA_UNUSED, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + if (jsctx->latest_error == EINA_JSON_ERROR_NONE) + jsctx->latest_error = EINA_JSON_ERROR_SYNTAX_TOKEN; + _RETURN_SW(NULL, EINA_FALSE); +} + +Stm_Switch +_lex_initial_cb(Stm_State *state, Stm_Val token, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + + if (jsctx->glue_on) + { + jsctx->glue_buf[0]='\0'; + jsctx->glue_len = 0; + jsctx->glue_on = EINA_FALSE; + } + + switch (token) + { + case '}': case '{': case ',': + case ':': case '[': case ']': + _syntax_token_process(token, data); + _RETURN_SW (state, EINA_FALSE); + + case '\n': + jsctx->line ++; + jsctx->col = 0; + case '\r': case '\t': case ' ': + _RETURN_SW (state, EINA_FALSE); + + case '\"': + _RETURN_SW(&lex_string, EINA_FALSE); + + _CASE_DIGIT: + _RETURN_SW(&lex_int_entry, EINA_TRUE); + case '-': + jsctx->glue_on = EINA_TRUE; + _RETURN_SW(&lex_int_entry, EINA_FALSE); + + case 't': + _RETURN_SW(&lex_true, EINA_TRUE); + case 'f': + _RETURN_SW(&lex_false, EINA_TRUE); + case 'n': + _RETURN_SW(&lex_null, EINA_TRUE); + } + return switch_lex_unexpected; +} + +static Stm_Switch +_lex_digit_cb(Stm_State *state, Stm_Val token, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + jsctx->glue_on = EINA_TRUE; + + Stm_State* next_state = (Stm_State*)(state->param_ptr1); + if (_ISDIGIT(token)) + _RETURN_SW(next_state, EINA_FALSE); + + return switch_lex_unexpected; +} + +static Stm_Switch +_lex_int_cb(Stm_State *state, Stm_Val token, void *data) +{ + switch (token) + { + _CASE_DIGIT: + _RETURN_SW(state, EINA_FALSE); + case 'e': + case 'E': + _RETURN_SW(&lex_exp_sign, EINA_FALSE); + case '.': + _RETURN_SW(&lex_fraction_entry, EINA_FALSE); + } + _syntax_token_process(JSON_NUMBER, data); + _RETURN_SW(&lex_initial, EINA_TRUE); +} + +static Stm_Switch +_lex_frac_cb(Stm_State *state, Stm_Val token, void *data) +{ + switch (token) + { + _CASE_DIGIT: + _RETURN_SW(state, EINA_FALSE); + case 'e': + case 'E': + _RETURN_SW(&lex_exp_sign, EINA_FALSE); + } + _syntax_token_process(JSON_NUMBER, data); + _RETURN_SW (&lex_initial, EINA_TRUE); +} + +static Stm_Switch +_lex_exp_sign_cb(Stm_State *state EINA_UNUSED, Stm_Val token, void *data EINA_UNUSED) +{ + switch (token) + { + _CASE_DIGIT: + _RETURN_SW (&lex_exp_entry, EINA_TRUE); + case '-': + case '+': + _RETURN_SW (&lex_exp_entry, EINA_FALSE); + } + return switch_lex_unexpected; +} + +static Stm_Switch +_lex_exp_cb(Stm_State *state, Stm_Val token, void *data) +{ + switch (token) + { + _CASE_DIGIT: + _RETURN_SW(state, EINA_FALSE); + } + _syntax_token_process(JSON_NUMBER, data); + _RETURN_SW(&lex_initial, EINA_TRUE); +} + +static Stm_Switch +_lex_string_cb(Stm_State *state, Stm_Val token, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + jsctx->glue_on = EINA_TRUE; + + switch (token) + { + case '\\': + _RETURN_SW (&lex_string_esc, EINA_FALSE); + case '\"': + _syntax_token_process(JSON_STRING, data); + _RETURN_SW (&lex_initial, EINA_FALSE); + } + _RETURN_SW(state, EINA_FALSE); +} + +static Stm_Switch +_lex_string_esc_cb(Stm_State *state EINA_UNUSED, Stm_Val token EINA_UNUSED, void *data EINA_UNUSED) +{ + _RETURN_SW(&lex_string, EINA_FALSE); +} + +static Stm_Switch +_lex_keyword_cb(Stm_State *state, Stm_Val token, void *data) +{ + struct _lex_keyword_param *param = state->param_ptr1; + + char* cmpstr = param->str; + unsigned maxlen = param->len - 1; + + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + + jsctx->glue_on = EINA_TRUE; + + if ((jsctx->glue_len > maxlen) || (cmpstr[jsctx->glue_len] != (char)token)) + return switch_lex_unexpected; + + if (jsctx->glue_len == maxlen) + { + _syntax_token_process(param->token, data); + _RETURN_SW(&lex_initial, EINA_FALSE); + } + _RETURN_SW(state, EINA_FALSE); +} + +static Stm_Switch +_lex_unexpected_cb(Stm_State *state EINA_UNUSED, Stm_Val token EINA_UNUSED, void *data) +{ + Eina_Json_Context *jsctx = (Eina_Json_Context *)data; + if (jsctx->latest_error == EINA_JSON_ERROR_NONE) + jsctx->latest_error = EINA_JSON_ERROR_LEX_TOKEN; + _RETURN_SW(NULL, EINA_FALSE); +} + +static Eina_Json_Context * +_eina_json_context_new(Eina_Json_Parser_Cb cb, void *data) +{ + Eina_Json_Context *ret = calloc(1, sizeof(Eina_Json_Context)); + + if (ret) + { + ret->jstack = eina_array_new(10); + + ret->glue_buf_size = JSON_GLUE_BUFF_STEP; + ret->glue_buf = malloc(ret->glue_buf_size); + + ret->parser_cb = (cb) ? cb : _eina_json_parser_dom_cb; + ret->cb_data = (cb) ? data : ret; + + if (!(ret->jstack && ret->glue_buf)) + { + if (ret->jstack) free(ret->jstack); + if (ret->glue_buf) free(ret->glue_buf); + free(ret); + return NULL; + } + + eina_json_context_reset(ret); + } + return ret; +} + +static Eina_Bool +_eina_json_context_parse(Eina_Json_Context *ctx, const char *text, unsigned text_len) +{ + unsigned lencount = (text_len) ? text_len : 1; + + ctx->head = ctx->str = text; + + if (ctx->latest_error) + return EINA_FALSE; + + while (*ctx->head && lencount) + { + _state_machine_feed(&(ctx->lex_machine), *ctx->head); + + if (ctx->glue_on) + { + if (ctx->glue_buf_size - ctx->glue_len < 2) + { + ctx->glue_buf_size += JSON_GLUE_BUFF_STEP; + ctx->glue_buf = realloc(ctx->glue_buf, ctx->glue_buf_size); + } + ctx->glue_buf[ctx->glue_len++] = *ctx->head; + ctx->glue_buf[ctx->glue_len] = '\0'; + } + + if (ctx->latest_error) + return EINA_FALSE; + + ctx->head++; + ctx->col++; + + if (text_len) lencount--; + } + return EINA_TRUE; +} + +static Eina_Json_Value * +_eina_json_parse(const char *text, unsigned size) +{ + Eina_Json_Value *ret = NULL; + Eina_Json_Context *ctx = eina_json_context_dom_new(); + _eina_json_context_parse(ctx, text, size); + if (eina_json_context_completed_get(ctx)) + ret = eina_json_context_dom_tree_take(ctx); + eina_json_context_free(ctx); + return ret; +} + +static Eina_Json_Value * +_eina_json_type_new(Eina_Json_Type type) +{ + Eina_Json_Value *ret = calloc(1, sizeof(Eina_Json_Value)); + if (ret) + ret->type = type; + return ret; +} + +static unsigned +_eina_json_gen_count_get(Eina_Json_Value *obj) +{ + return eina_inlist_count(obj->lst); +} + +static Eina_Json_Value * +_eina_json_gen_nth_get(Eina_Json_Value *obj, unsigned idx) +{ + unsigned count = 0; + Eina_Inlist *l = NULL; + for (l = obj->lst; l; l = l->next) + if ((count++) == idx) break; + return EINA_INLIST_CONTAINER_GET(l, Eina_Json_Value); +} + +static Eina_Json_Value * +_eina_json_gen_append(Eina_Json_Value *obj, Eina_Json_Value *objadd) +{ + if (objadd->parent) + { + EINA_LOG_ERR("Can't append json object %p. " + "Its already belongs to another json object %p.\n", + objadd, objadd->parent); + return NULL; + } + obj->lst = eina_inlist_append(obj->lst, EINA_INLIST_GET(objadd)); + objadd->parent = obj; + return objadd; +} + +static Eina_Json_Value * +_eina_json_gen_insert(Eina_Json_Value *obj, unsigned ind, Eina_Json_Value *jsnval) +{ + Eina_Json_Value *o = _eina_json_gen_nth_get(obj, ind); + if (!o && ind) return NULL; + + if (jsnval->parent) + { + EINA_LOG_ERR("Can't insert json object %p. " + "Its already belongs to another json object %p.\n", + jsnval, jsnval->parent); + return NULL; + } + + obj->lst = eina_inlist_prepend_relative(obj->lst, + EINA_INLIST_GET(jsnval), + EINA_INLIST_GET(o)); + jsnval->parent = obj; + return jsnval; +} + +static Eina_Bool +_eina_json_gen_nth_remove(Eina_Json_Value *obj, unsigned ind) +{ + Eina_Json_Value *o = _eina_json_gen_nth_get(obj, ind); + if (!o) return EINA_FALSE; + obj->lst = eina_inlist_remove(obj->lst, EINA_INLIST_GET(o)); + o->parent = NULL; + eina_json_value_free(o); + return EINA_TRUE; +} + +static inline Eina_Iterator * +_eina_json_gen_iterator_new(Eina_Json_Value *obj) +{ + return eina_inlist_iterator_new(obj->lst); +} + +static void +_eina_json_delim_print(Eina_Json_Value *jsnval, Eina_Strbuf *jtext, + unsigned ident0, unsigned identV, Eina_Bool objbreak) +{ + int newident; + Eina_Json_Value *jval; + Eina_Inlist *l = NULL; + + switch (jsnval->type) + { + case EINA_JSON_TYPE_NULL: + eina_strbuf_append(jtext, "null"); + break; + + case EINA_JSON_TYPE_NUMBER: + if (ceil(jsnval->number) == jsnval->number) + eina_strbuf_append_printf(jtext, "%ld", (long)(jsnval->number)); + else + eina_strbuf_append_printf(jtext, "%.2f", jsnval->number); + break; + + case EINA_JSON_TYPE_STRING: + eina_strbuf_append_printf(jtext, "\"%s\"", + eina_strbuf_string_get(jsnval->string)); + break; + + case EINA_JSON_TYPE_BOOLEAN: + eina_strbuf_append(jtext, (jsnval->boolean) ? "true" : "false"); + break; + + case EINA_JSON_TYPE_PAIR: + eina_strbuf_append_printf(jtext, "\"%s\"", + eina_strbuf_string_get(jsnval->pair.name)); + eina_strbuf_append(jtext, ":"); + if (objbreak) + if (jsnval->pair.val->type == EINA_JSON_TYPE_OBJECT) + eina_strbuf_append_printf(jtext, "\n%*s", ident0, " "); + _eina_json_delim_print(jsnval->pair.val, jtext, ident0, identV, objbreak); + break; + + case EINA_JSON_TYPE_ARRAY: + eina_strbuf_append_char(jtext, '['); + + for (l = jsnval->lst; l; l = l->next) + { + jval = EINA_INLIST_CONTAINER_GET(l, Eina_Json_Value); + _eina_json_delim_print(jval, jtext, ident0, identV, objbreak); + if (l->next) eina_strbuf_append(jtext, (objbreak) ? ", " : ","); + } + eina_strbuf_append_char(jtext, ']'); + break; + + case EINA_JSON_TYPE_OBJECT: + if (!jsnval->lst) + { + eina_strbuf_append(jtext, "{}"); + break; + } + newident = ident0 + identV; + eina_strbuf_append_char(jtext, '{'); + + for (l = jsnval->lst; l;l = l->next) + { + if (objbreak) + eina_strbuf_append_printf(jtext, "\n%*s", newident, " "); + jval = EINA_INLIST_CONTAINER_GET(l, Eina_Json_Value); + _eina_json_delim_print(jval, jtext, newident, identV, objbreak); + if (l->next) eina_strbuf_append(jtext, ","); + } + if (objbreak) + eina_strbuf_append_printf(jtext, "\n%*s", ident0, " "); + + eina_strbuf_append_char(jtext, '}'); + break; + } +} + +/*============================================================================* + * JSON Parser API * + *============================================================================*/ + +EAPI void +eina_json_context_reset(Eina_Json_Context* ctx) +{ + Stm_Machine * lexm = &(ctx->lex_machine); + Stm_Machine * stxm = &(ctx->syntax_machine); + + lexm->current_state = lexm->initial_state = &lex_initial; + stxm->current_state = stxm->initial_state = &syntx_entry; + stxm->data = lexm->data = ctx; + + ctx->latest_error = EINA_JSON_ERROR_NONE; + + ctx->glue_len = 0; + ctx->glue_buf[0] = '\0'; + ctx->glue_on = EINA_FALSE; + + eina_array_clean(ctx->jstack); + eina_json_value_free(ctx->jobj); + ctx->jobj = NULL; + ctx->jparent=NULL; + + ctx->str = ctx->head = NULL; + ctx->line = ctx->col = 1; +} + +EAPI Eina_Json_Context * +eina_json_context_dom_new() +{ + return _eina_json_context_new(NULL, NULL); +} + +EAPI Eina_Json_Context * +eina_json_context_sax_new(Eina_Json_Parser_Cb cb, void* data) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(cb, NULL); + return _eina_json_context_new(cb, data); +} + +EAPI void +eina_json_context_free(Eina_Json_Context* ctx) +{ + if (!ctx) return; + + eina_json_context_reset(ctx); + free(ctx->glue_buf); + eina_array_free(ctx->jstack); + free(ctx); +} + +EAPI inline unsigned +eina_json_context_line_get(Eina_Json_Context* ctx) +{ + return ctx->line; +} + +EAPI inline unsigned +eina_json_context_column_get(Eina_Json_Context* ctx) +{ + return ctx->col; +} + +EAPI inline Eina_Json_Error +eina_json_context_error_get(Eina_Json_Context* ctx) +{ + return ctx->latest_error; +} + +EAPI inline Eina_Bool +eina_json_context_completed_get(Eina_Json_Context* ctx) +{ + return (ctx->syntax_machine.current_state == &syntx_end); +} + +EAPI Eina_Bool +eina_json_context_unfinished_get(Eina_Json_Context* ctx) +{ + return (Eina_Bool)(!eina_json_context_completed_get(ctx) && + !eina_json_context_error_get(ctx)); +} + +EAPI Eina_Json_Value * +eina_json_context_dom_tree_take(Eina_Json_Context* ctx) +{ + if (!eina_json_context_completed_get(ctx)) + { + EINA_LOG_ERR("Taking json tree from erroneous or uncompleted json context"); + return NULL; + } + if (!ctx->jobj) + { + EINA_LOG_ERR("Json tree already taken for this json context"); + return NULL; + } + Eina_Json_Value* ret = ctx->jobj; + ctx->jobj = NULL; + return ret; +} + +EAPI inline Eina_Json_Value * +eina_json_parse(const char *text) +{ + return _eina_json_parse(text, 0); +} + +EAPI Eina_Json_Value * +eina_json_parse_n(const char *text, unsigned text_len) +{ + if (!text_len) return NULL; + return _eina_json_parse(text, text_len); +} + +EAPI Eina_Bool +eina_json_context_parse(Eina_Json_Context *ctx, const char *text) +{ + return _eina_json_context_parse(ctx, text, 0); +} + +EAPI Eina_Bool +eina_json_context_parse_n(Eina_Json_Context *ctx, const char *text, unsigned text_len) +{ + if (!text_len) return !!(ctx->latest_error); + return _eina_json_context_parse(ctx, text, text_len); +} + +/*============================================================================* + * JSON Value manipulation API * + *============================================================================*/ + +EAPI Eina_Json_Value * +eina_json_number_new(double num) +{ + Eina_Json_Value *ret = _eina_json_type_new(EINA_JSON_TYPE_NUMBER); + if (ret) ret->number = num; + return ret; +} + +EAPI Eina_Json_Value * +eina_json_string_new(const char* string) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(string, NULL); + Eina_Json_Value *ret = _eina_json_type_new(EINA_JSON_TYPE_STRING); + if (ret) + { + ret->string = eina_strbuf_new(); + eina_strbuf_append(ret->string, string); + } + return ret; +} + +EAPI Eina_Json_Value * +eina_json_boolean_new(Eina_Bool boolval) +{ + Eina_Json_Value *ret = _eina_json_type_new(EINA_JSON_TYPE_BOOLEAN); + if (ret) ret->boolean = boolval; + return ret; +} + +EAPI inline Eina_Json_Value * +eina_json_null_new() +{ + return _eina_json_type_new(EINA_JSON_TYPE_NULL); +} + +EAPI Eina_Json_Value * +eina_json_object_new() +{ + return _eina_json_type_new(EINA_JSON_TYPE_OBJECT); +} + +EAPI Eina_Json_Value * +eina_json_array_new() +{ + return _eina_json_type_new(EINA_JSON_TYPE_ARRAY); +} + +EAPI void +eina_json_value_free(Eina_Json_Value *jsnval) +{ + if (!jsnval) return; + + if (jsnval->parent) + { + EINA_LOG_ERR("Can't free json object %p. It belongs to %p.\n", + jsnval, jsnval->parent); + return; + } + + switch (jsnval->type) + { + case EINA_JSON_TYPE_STRING: + eina_strbuf_free(jsnval->string); + break; + case EINA_JSON_TYPE_PAIR: + eina_strbuf_free(jsnval->pair.name); + if (jsnval->pair.val) jsnval->pair.val->parent = NULL; + eina_json_value_free(jsnval->pair.val); + break; + case EINA_JSON_TYPE_OBJECT: + case EINA_JSON_TYPE_ARRAY: + while (jsnval->lst) + { + Eina_Json_Value *aux = EINA_INLIST_CONTAINER_GET(jsnval->lst, + Eina_Json_Value); + jsnval->lst = eina_inlist_remove(jsnval->lst, jsnval->lst); + aux->parent = NULL; + eina_json_value_free(aux); + } + default: + break; + } + free(jsnval); +} + +EAPI inline +Eina_Json_Type eina_json_type_get(Eina_Json_Value *jsnval) +{ + return jsnval->type; +} + +EAPI Eina_Bool +eina_json_number_set(Eina_Json_Value *jsnval, double num) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type != EINA_JSON_TYPE_NUMBER), EINA_FALSE); + jsnval->number = num; + return EINA_TRUE; +} + +EAPI Eina_Bool +eina_json_string_set(Eina_Json_Value *jsnval, const char* string) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type != EINA_JSON_TYPE_STRING), EINA_FALSE); + eina_strbuf_reset(jsnval->string); + eina_strbuf_append(jsnval->string, string); + return EINA_TRUE; +} + +EAPI Eina_Bool +eina_json_boolean_set(Eina_Json_Value *jsnval, Eina_Bool boolval) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type != EINA_JSON_TYPE_BOOLEAN), EINA_FALSE); + jsnval->boolean = boolval; + return EINA_TRUE; +} + +EAPI double +eina_json_number_get(Eina_Json_Value *jsnval) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type != EINA_JSON_TYPE_NUMBER), 0.0); + return jsnval->number; +} + +EAPI const char * +eina_json_string_get(Eina_Json_Value *jsnval) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type != EINA_JSON_TYPE_STRING), NULL); + return eina_strbuf_string_get(jsnval->string); +} + +EAPI Eina_Bool +eina_json_boolean_get(Eina_Json_Value *jsnval) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type != EINA_JSON_TYPE_BOOLEAN), EINA_FALSE); + return jsnval->boolean; +} + +EAPI const char * +eina_json_pair_name_get(Eina_Json_Value *obj) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_PAIR), NULL); + return eina_strbuf_string_get(obj->pair.name); +} + +EAPI Eina_Json_Value * +eina_json_pair_value_get(Eina_Json_Value *obj) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_PAIR), NULL); + return obj->pair.val; +} + +EAPI unsigned +eina_json_object_count_get(Eina_Json_Value *obj) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_OBJECT), 0); + return _eina_json_gen_count_get(obj); +} + +EAPI Eina_Json_Value * +eina_json_object_nth_get(Eina_Json_Value *obj, unsigned idx) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_OBJECT), NULL); + return _eina_json_gen_nth_get(obj, idx); +} + +EAPI Eina_Json_Value * +eina_json_object_append(Eina_Json_Value *obj, const char* keyname, Eina_Json_Value *jsnval) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_OBJECT), NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type == EINA_JSON_TYPE_PAIR), NULL); + + if (jsnval->parent) + { + EINA_LOG_ERR("Can't append json object %p.Its already " + "belongs to another json object %p.\n", + jsnval, jsnval->parent); + return NULL; + } + + Eina_Json_Value *pr = _eina_json_type_new(EINA_JSON_TYPE_PAIR); + pr->pair.name = eina_strbuf_new(); + eina_strbuf_append(pr->pair.name, keyname); + pr->pair.val = jsnval; + + Eina_Json_Value *ins = _eina_json_gen_append(obj, pr); + return (ins) ? pr : NULL; +} + +EAPI Eina_Json_Value * +eina_json_object_insert(Eina_Json_Value *obj, unsigned idx, const char* keyname, Eina_Json_Value *jsnval) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_OBJECT), NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type == EINA_JSON_TYPE_PAIR), NULL); + + Eina_Json_Value *o = _eina_json_gen_nth_get(obj, idx); + if (!o && idx) return NULL; + + if (jsnval->parent) + { + EINA_LOG_ERR("Can't insert json object %p.Its already " + "belongs to another json object %p.\n", + jsnval, jsnval->parent); + return NULL; + } + + Eina_Json_Value *pr = _eina_json_type_new(EINA_JSON_TYPE_PAIR); + pr->pair.name = eina_strbuf_new(); + eina_strbuf_append(pr->pair.name, keyname); + pr->pair.val = jsnval; + jsnval->parent = pr; + + Eina_Json_Value *ins = _eina_json_gen_insert(obj, idx, pr); + return (ins) ? pr : NULL; +} + +EAPI Eina_Bool +eina_json_object_nth_remove(Eina_Json_Value *obj, unsigned idx) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_OBJECT), EINA_FALSE); + return _eina_json_gen_nth_remove(obj, idx); +} + +EAPI Eina_Iterator * +eina_json_object_iterator_new(Eina_Json_Value *obj) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_OBJECT), NULL); + return _eina_json_gen_iterator_new(obj); +} + +EAPI Eina_Json_Value * +eina_json_object_value_get_internal(Eina_Json_Value *obj, ...) +{ + char* jkey; + va_list vl; + Eina_Json_Value *jval; + Eina_Inlist *l; + + EINA_SAFETY_ON_TRUE_RETURN_VAL((obj->type != EINA_JSON_TYPE_OBJECT), NULL); + + jval = obj; + va_start(vl, obj); + while ((jkey = va_arg(vl, char*))) + { + if (jval->type != EINA_JSON_TYPE_OBJECT) return NULL; + for (l = jval->lst; l; l = l->next) + { + jval = EINA_INLIST_CONTAINER_GET(l, Eina_Json_Value); + if (!strcmp(eina_strbuf_string_get(jval->pair.name), jkey)) break; + jval = NULL; + } + if (!jval) break; + jval = jval->pair.val; + } + va_end(vl); + + if (jval == obj) jval = NULL; + + return jval; +} + +EAPI unsigned +eina_json_array_count_get(Eina_Json_Value *arr) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((arr->type != EINA_JSON_TYPE_ARRAY), 0); + return _eina_json_gen_count_get(arr); +} + +EAPI Eina_Json_Value * +eina_json_array_nth_get(Eina_Json_Value *arr, unsigned idx) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((arr->type != EINA_JSON_TYPE_ARRAY), NULL); + return _eina_json_gen_nth_get(arr, idx); +} + +EAPI Eina_Json_Value * +eina_json_array_append(Eina_Json_Value *arr, Eina_Json_Value *jsnval) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((arr->type != EINA_JSON_TYPE_ARRAY), NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type == EINA_JSON_TYPE_PAIR), NULL); + return _eina_json_gen_append(arr, jsnval); +} + +EAPI Eina_Json_Value * +eina_json_array_insert(Eina_Json_Value *arr, unsigned idx, Eina_Json_Value *jsnval) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((arr->type != EINA_JSON_TYPE_ARRAY), NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL((jsnval->type == EINA_JSON_TYPE_PAIR), NULL); + return _eina_json_gen_insert(arr, idx, jsnval); +} + +EAPI Eina_Bool +eina_json_array_nth_remove(Eina_Json_Value *arr, unsigned idx) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((arr->type != EINA_JSON_TYPE_ARRAY), EINA_FALSE); + return _eina_json_gen_nth_remove(arr, idx); +} + +EAPI Eina_Iterator * +eina_json_array_iterator_new(Eina_Json_Value *arr) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((arr->type != EINA_JSON_TYPE_ARRAY), NULL); + return _eina_json_gen_iterator_new(arr); +} + +EAPI char * +eina_json_format_string_get(Eina_Json_Value *jsnval, Eina_Json_Format format) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((format > EINA_JSON_FORMAT_BASIC), NULL); + + int ident0 = 0; + int identV = (format) ? 2 : 0; + int objbreak = (format) ? EINA_TRUE : EINA_FALSE; + + Eina_Strbuf *jtext = eina_strbuf_new(); + _eina_json_delim_print(jsnval, jtext, ident0, identV, objbreak); + char *ret = eina_strbuf_string_steal(jtext); + eina_strbuf_free(jtext); + return ret; +} diff --git a/src/lib/eina/eina_json.h b/src/lib/eina/eina_json.h new file mode 100644 index 0000000000..aa9fe2a250 --- /dev/null +++ b/src/lib/eina/eina_json.h @@ -0,0 +1,899 @@ +/* EINA - EFL data type library + * Copyright (C) 2013 Yossi Kantor + * Cedric Bail + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#ifndef EINA_JSON_H_ +#define EINA_JSON_H_ + +#include +#include + +#include "eina_config.h" + +#include "eina_types.h" +#include "eina_iterator.h" +#include "eina_inlist.h" +#include "eina_array.h" +#include "eina_strbuf.h" +#include "eina_safety_checks.h" + +/** + * @page eina_json_example_01 + * @include eina_json_01.c + * @example eina_json_01.c + */ + + /** + * @page eina_json_example_02 + * @include eina_json_02.c + * @example eina_json_02.c + */ + + /** + * @page eina_json_example_03 + * @include eina_json_03.c + * @example eina_json_03.c + */ + +/** + * @page eina_json_dom_example_page @b Dom @b Parsing + * @dontinclude eina_json_01.c + * @n + * DOM parsing is a method of parsing the whole text document into a convinient + * data structure. In our case we'll have a @c Eina_Json_Value* pointer to a + * root json object after we done. + * + * So, we define a json string @c my_json + * @skip my_json + * @until ; + * + * Next we initilize parse context instance @c ctx + * @skipline Eina_Json_Context + * + * and now we can parse @c my_json string + * @skipline eina_json_context_parse + * + * Lets see if error occured + * @skipline Eina_Json_Error + * + * Since no error occurred @ref eina_json_context_error_get() will return + * EINA_JSON_ERROR_NONE. In our case the parsing was completed successfully so + * @ref eina_json_context_completed_get() will return true and the next code + * will be executed + * @skip eina_json_context_completed_get + * @until } + * + * Since DOM parsing was successful we can now use the function + * @ref eina_json_context_dom_tree_take in order to get the pointer to a root + * object. Once we "took" the tree, we explicitly own it and its our + * responsibility to free it with @ref eina_json_value_free(). We can + * now traverse, and fully manipulate the tree pointed by @c jsnval with + * @ref Eina_Json_Tree_Group "Eina_Json_Value API". In this example we + * just print the tree to screen. + * + * And after we done we free the @c ctx instance + * @skipline eina_json_context_free(ctx); + * + * You can see the full source code + * @ref eina_json_example_01 "here". + */ + +/** + * @page eina_json_sax_example_page @b Sax @b Parsing + * @dontinclude eina_json_03.c + * @n + * Unlike DOM parser, SAX parser doesn't parses the text document into a + * defined data structure, but instead notifies via callback function about each + * element and node it encounters. In general,API-wise it works pretty much the + * same as the @ref eina_json_dom_example_page "DOM" , the main difference that + * you don't get to @ref eina_json_context_dom_tree_take at the end of successful + * parsing. What your callback function produces is what you get. + * + * First we define a @c struct to use it as data structure for the callback + * @skip typedef + * @until Sax_Parser_Data + * + * Again, we define a json string @c my_json + * @skip my_json + * @until ; + * + * Now we define the callback function ( @ref Eina_Json_Parser_Cb ) + * @skip void + * @until { + * + * The callback function takes 4 parameters: + * @li @c type - the type of json object that was currently parsed + * @li @c parent - a unique parent's id (usually pointer) representing + * the parent of this object. If NULL then this is a root object. + * (Explained further below). + * @li @c text - a text data of the object. Depends on @c type as follows: + * @par + * @c type = @c EINA_JSON_TYPE_STRING @c text holds the string @n + * @c type = @c EINA_JSON_TYPE_PAIR @c text holds the name of key value @n + * @c type = @c EINA_JSON_TYPE_NUMBER @c text holds the string of + * a number (always legal) @n + * @c type = @c EINA_JSON_TYPE_BOOLEAN first letter of @c text + * (@c *text) @c 'f' = false, @c 't' = true @n + * + * @li @c data - data that is passed to the callback + * + * The return value of this callback function is a void* parent id. So as + * you might have guessed, the @c parent parameter that you receive in the + * callback is the one that was returned from that callback earlier. If you + * parsing a json array with 2 numbers @a '[2,3]' it will look like that: + * You get first call to callback identifying array with parameters + * @c type = @c EINA_JSON_TYPE_ARRAY and @c parent = Some previous Id... . + * Your callback allocates the array in memory with address 0x001EDFC, + * (you save this address your private @c data) and return it. Next + * call for first element in the array which is number 2, + * with parameters: @c type = @c EINA_JSON_TYPE_NUMBER + * and @c parent = 0x001EDFC (right,from before) @c text = "2". The + * following call for a second element will be @c type = + @c EINA_JSON_TYPE_NUMBER and @c parent = 0x001EDFC + * @c text = "3". @n + * While its makes most sense that @ parent id will be a (void*) pointer, + * it doesn't necessary have to be one. As long as you have means to + * identify parents for their elements and common language between your + * callback and the parser stack its all good. Another thing about + * the return value, is that its only meaningful when processing + * parent types: + * EINA_JSON_TYPE_PAIR/EINA_JSON_TYPE_ARRAY/EINA_JSON_TYPE_OBJECT. + * Any other types can return any value (like (void*)1) except NULL. + * Returning NULL from the callback function will signal the parser + * that and error had occurred and @c EINA_JSON_ERROR_SYNTAX_TOKEN will + * be raised and the parser will stop. + + * Now for the rest of our callback function: + * @until return + * @until } + + * As you can see, our callback basically creates a string inside + * @c data->text which represents the elements it parses and the parent + * id is an increment of data->parent_idx. + + * Now for the main function. First we define a variable to be passed as + * @c *data to our callback. + * @until Sax_Parser_Data + + * Then we initialize it. + * @skip eina + * @until parent_idx + * + * Now create a SAX parse context instance and pass it our callback + * function and @c sax_parser_cb and reference to @c mysax as data. + * @skipline Eina_Json_Context + * + * Now we parse the the json text: + * @skipline eina_json_context_parse + * + * Upon successful completion we print our string in @c mysax + * @skip eina_json_context_completed_get + * @until } + * + * Not to forget to free the context + * @skipline eina_json_context_free + * + * You can see the full source code + * @ref eina_json_example_02 "here". + */ + +/** + * @defgroup Eina_Json_Group Json + * Json format parser and data structure + * + * This module provides a complete set of tools for a Json format. + * It offers json @ref eina_json_sax_example_page "SAX" and + * @ref eina_json_dom_example_page "DOM" parser and json tree + * creation, traversal, manipulation and textual output abilities. + * + * @b Parsing @n + * While still offering a plain run-of-a-mill @ref eina_json_parse "eina_json_parse(" + * @ref eina_json_parse_n "_n )" which takes a @a complete json text,parses it and + * then returns a json tree on success or NULL on failure,the main feature of this + * module is the context parsing function(s) @ref eina_json_context_parse + * "eina_json_context_parse(" @ref eina_json_context_parse_n "_n )". Context parsing is + * using a @ref Eina_Json_Context instance to parse a single json root object. + * The instance is serving as a state keeper of this parsing and this gives us + * extended abilities to: + * @li Stream parsing - parsing a single json root object not necessarily in one piece + * but as a series of sequential parts. + * @li Configuration for a specific parsing. + * @li Extended diagnostics and probing for a state of a parsing. + * @li Easy to add any future API and properties for @ref Eina_Json_Context. + * + * A small example. Lets say we have a small json we want to parse: + * @code + * { + * "Json1":"Hello", + * "Json2":"World", + * } + * @endcode + * + * We create new @c Eina_Json_Context instance @c called ctx + * @code + * Eina_Json_Context *ctx = eina_json_context_dom_new(); + * @endcode + * (Note: We choose @ref eina_json_context_dom_new for this example, + * but we could've initialized @c ctx with + * @ref eina_json_context_sax_new just as well.) + * + * Then we can parse this json as whole: + * @code + * eina_json_context_parse(ctx, "{\"Json1\":\"Hello\",\"Json2\":\"World\"}"); + * @endcode + * + * Or as a stream of sequential parts: + * @code + * eina_json_context_parse(ctx, "{\"Json1\":\"H"); + * eina_json_context_parse(ctx, "ell"); + * eina_json_context_parse(ctx, "o\",\"Json2\":\"World\"}"); + * @endcode + * + * (The examples above parses null-terminated strings. + * If it isn't your case, use @ref eina_json_context_parse_n + * with a length of a string) + * + * At any time, @c ctx can be probed for its status. Test If parsing was + * completed successfully, an error occurred or neither (waits further input) + * and so on with @ref eina_json_context_error_get, + * @ref eina_json_context_completed_get etc. This @ref eina_json_example_02 + * "example" shows the use of sequential parsing when reading json from file + * and parsing it in X-sized chunks. + * + * Please refer to these examples for detailed explanation: + * @li @ref eina_json_dom_example_page "DOM parsing" + * @li @ref eina_json_sax_example_page "SAX parsing" + */ + +/** + * @addtogroup Eina_Tools_Group Tools + * + * @{ + */ + +/** + * @defgroup Eina_Json_Group Json + * + * @{ + */ + +typedef struct _Eina_Json_Context Eina_Json_Context; +typedef struct _Eina_Json_Value Eina_Json_Value; +typedef struct _Eina_Json_Output_Options Eina_Json_Output_Options; + +/** + * @typedef Eina_Json_Format + * Determinates how a json tree will be converted to text + */ +typedef enum _Eina_Json_Format +{ + EINA_JSON_FORMAT_PACKED,/**< Prints json in one line no spaces between delimiters */ + EINA_JSON_FORMAT_BASIC/**< Nice, delimitered and indented human readable format */ +} Eina_Json_Format; + +typedef enum _Eina_Json_Type +{ + EINA_JSON_TYPE_NULL, + EINA_JSON_TYPE_NUMBER, + EINA_JSON_TYPE_STRING, + EINA_JSON_TYPE_BOOLEAN, + EINA_JSON_TYPE_PAIR, + EINA_JSON_TYPE_OBJECT, + EINA_JSON_TYPE_ARRAY +} Eina_Json_Type; + +/** + * @typedef Eina_Json_Error + * Json parser possible error states + */ +typedef enum _Eina_Json_Error +{ + EINA_JSON_ERROR_NONE,/**< No error */ + EINA_JSON_ERROR_LEX_TOKEN,/**< Error in lexical analysis */ + EINA_JSON_ERROR_SYNTAX_TOKEN,/**< Error in syntax analysis */ + EINA_JSON_ERROR_PAST_END/**< Input was entered to the parser after its already successfully parsed a json root */ +} Eina_Json_Error; + +typedef void*(*Eina_Json_Parser_Cb)(Eina_Json_Type type, void *parent, const char *text, void*data); + +/** + * @section Eina_Json_Parser_Group "Json Parser API" + * + * @{ + */ + + +/** + * @brief Create new json DOM parser context + * + * @return Newly created json parser context on success or @c NULL on failure + * + */ +EAPI Eina_Json_Context *eina_json_context_dom_new() EINA_WARN_UNUSED_RESULT; + + +/** + * @brief @brief Create new json DOM parser context. + * + * @param cb The SAX parser @ref eina_json_sax_example_page "callback". + * Must not be NULL + * @param data The data to be passed to the callback (@p cb) + * @return Newly created json parser context on success or @c NULL on failure + * + */ +EAPI Eina_Json_Context *eina_json_context_sax_new(Eina_Json_Parser_Cb cb, void *data) EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Reset the parser context to its initial state. + * + * @param ctx The json parser context to be reset + * + * Resets the paser context to the state it was when it was just created + * with @c eina_json_context_dom_new or @c eina_json_context_sax_new and + * makes it ready to parse a new json text. + * Use this function when you want to recycle an already existing json + * context instead of allocating a new one. + */ +EAPI void eina_json_context_reset(Eina_Json_Context *ctx); + + +/** + * @brief Free the jason parser context. + * + * @param ctx The json parser context to be freed + * + * Will free the allocated context with and all of its resourses + * no matter on what parsing stage it is. + */ +EAPI void eina_json_context_free(Eina_Json_Context *ctx); + + +/** + * @brief Returns the current line in a json text document being parsed + * + * @param ctx The json parser context with which the text is parsed + * @return Line number + * + * Returns the current location inside a parced text line-wise. + * As any text document the line count starts from 1. + */ +EAPI unsigned eina_json_context_line_get(Eina_Json_Context *ctx); + + +/** + * @brief Returns the current column in a json text document being parsed + * + * @param ctx The json parser context with which the text is parsed + * @return Column number + * + * Returns the current location inside a parced text column-wise. + * As any text document the column count starts from 1. + */ +EAPI unsigned eina_json_context_column_get(Eina_Json_Context *ctx); + + +/** + * @brief Returns the error state of parse context + * + * @param ctx The json parser context to be probed for error state. + * @return Error type of @ref Eina_Json_Error. + * + * Probe the json parser context for error. If last @c eina_json_context_parse(_n) + * resulted in an error, it will be returned by this function. If no error occured + * @c EINA_JSON_ERROR_NONE will be returned. + * + */ +EAPI Eina_Json_Error eina_json_context_error_get(Eina_Json_Context *ctx); + + +/** + * @brief Returns whether a context parsing was succesefully completed + * + * @param ctx The json parser context to be probed for complete state. + * @return @c EINA_TRUE if completed, @c EINA_FALSE is not. + * + * Returns true when a single json root was parsed without errors. + * @note Any input of non blank characters after parsing was completed + * will result in an @c EINA_ERROR_PAST_END error. + * + */ +EAPI Eina_Bool eina_json_context_completed_get(Eina_Json_Context *ctx); + + +/** + * @brief Returns whether a context parsing awaits further input to complete. + * + * @param ctx The json parser context to be probed for unfinished state. + * @return @c EINA_TRUE if awaits further input or @c EINA_FALSE if not. + * + * Basically, this function return true when context parser in neither completed + * and neither errorneous. Usefull in read loops. + * + */ +EAPI Eina_Bool eina_json_context_unfinished_get(Eina_Json_Context *ctx); + + +/** + * @brief "Takes" parsed json DOM tree from parsing context and returns it. + * + * @param ctx The sucessefully parsed DOM parser context. + * @return Json tree on sucess or @c NULL on error. + * + * Once a DOM context parsing was succesefully completed, use this function to + * obtain its json tree. After the tree is returned and recived into your variable, + * it will be unreferenced by the json parse context. It will be your responsibility to + * free it with @ref eina_json_value_free once you "took" it. This function will return + * @c NULL and will issue an appropriate error message if the @p ctx is not a dom context, + * parseing is incomplete or errorneous, or if the tree was already "taken". + * + */ +EAPI Eina_Json_Value *eina_json_context_dom_tree_take(Eina_Json_Context *ctx) EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Parse json text into a a json tree. + * + * @param text Null terminated string of complete json root object + * @return A json tree on sucesses, @c NULL otherwise + * + * A simple json parsing function. On sucess returns a newly allocated json tree. + * If parsing fails for whatsoverver reason, @c NULL will be returned. + * @note Its your resonsibility to free the returned tree with + * @ref eina_json_value_free + * + */ +EAPI Eina_Json_Value *eina_json_parse(const char *text); + + +/** + * @brief Parse json text of a maximum specified length into a a json tree. + * + * @param text String of complete json root object + * @param text_len The length of @p text + * @return A json tree on sucesses, @c NULL otherwise + * + * This function is same as @ref eina_json_parse. Use it when you have strings + * of specified length and not necceserelly null terminated. If however a + * null-terminating character will occur earlier than @p text_len, it will be + * the end of string. + * + * @note Its your resonsibility to free the returned tree with + * @ref eina_json_value_free + * + */ +EAPI Eina_Json_Value *eina_json_parse_n(const char *text, unsigned text_len); + + +/** + * @brief Parse json text using a parse context + * + * @param ctx Json parsing context + * @param text Null terminated string of complete or sequantial part of json root object + * @return @c EINA_FALSE if error occured during parsing, otherwise @c EINA_TRUE + * + * This function is the core of this module and its usage is documented in the genearal + * documentation and examples. + * + */ +EAPI Eina_Bool eina_json_context_parse(Eina_Json_Context *ctx, const char *text); + + +/** + * @brief Parse json text of a maximum specified length using a parse context + * + * @param ctx Json parsing context + * @param text String of complete or sequantial part of json root object + * @return @c EINA_FALSE if error occured during parsing, otherwise @c EINA_TRUE + * + * This function is same as @ref eina_json_context_parse .Use it when you have strings + * of specified length and not necceserelly null terminated. If however a + * null-terminating character will occur earlier than @p text_len, it will be + * the end of string. + * + */ +EAPI Eina_Bool eina_json_context_parse_n(Eina_Json_Context *ctx, const char *text, unsigned text_len); + +/** + * @brief Turn json tree into json text + * + * @param jsnval Json tree + * @param format A text format you wish your json text to be. See @ref Eina_Json_Format + * @return Null terminated string of json text + * + * Takes a json value object and returns a null terminated string reperesenting the text + * in a format specified by @p format. Its your responsibility to free the string. + * + */ +EAPI char * eina_json_format_string_get(Eina_Json_Value *jsnval, Eina_Json_Format format) EINA_WARN_UNUSED_RESULT; + +/** + * @} + */ + +/** + * @section Eina_Json_Tree_Group "Json Value API" + * + * @{ + * + */ + +/** + * @brief Creates json number value object + * + * @param num Number to initilize json value object with. + * @return Newly allocated json object of type @c EINA_JSON_TYPE_NUMBER + * + */ +EAPI Eina_Json_Value *eina_json_number_new(double num) EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Creates json string value object + * + * @param string String to initilize json value object with. + * @return Newly allocated json object of type @c EINA_JSON_TYPE_STRING + * + */ +EAPI Eina_Json_Value *eina_json_string_new(const char *string) EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Creates json boolean value object + * + * @param boolval Boolean value to initilize json value object with. + * @return Newly allocated json object of type @c EINA_JSON_TYPE_BOOLEAN + * + */ +EAPI Eina_Json_Value *eina_json_boolean_new(Eina_Bool boolval) EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Creates json null value object + * + * @return Newly allocated json object of type @c EINA_JSON_TYPE_NULL + * + */ +EAPI Eina_Json_Value *eina_json_null_new() EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Creates json object container value object + * + * @return Newly allocated json object of type @c EINA_JSON_TYPE_OBJECT + * + */ +EAPI Eina_Json_Value *eina_json_object_new() EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Creates json array value object + * + * @return Newly allocated json object of type @c EINA_JSON_TYPE_ARRAY + * + */ +EAPI Eina_Json_Value *eina_json_array_new() EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Free json value object + * + * Frees allocated json value and its childern (if any) recursevly. + * @warning If a json object is a child of another object, freeing will fail + * and appropriate message will be logged. + * + */ +EAPI void eina_json_value_free(Eina_Json_Value *jsnval); + + +/** + * @brief Get the type of json value object + * + * @param jsnval Json value object + * @return @p jsnval type + * + */ +EAPI Eina_Json_Type eina_json_type_get(Eina_Json_Value *jsnval); + + +/** + * @brief Get number value of a json number value object + * + * @param jsnval Json value object of type @c EINA_JSON_TYPE_NUMBER + * @return Number value of a @p jsnval + * + */ +EAPI double eina_json_number_get(Eina_Json_Value *jsnval); + + +/** + * @brief Get string value of a json string value object + * + * @param jsnval Json value object of type @c EINA_JSON_TYPE_STRING + * @return String value of a @p jsnval + * + */ +EAPI const char *eina_json_string_get(Eina_Json_Value *jsnval); + + +/** + * @brief Get boolean value of a json boolean value object + * + * @param jsnval Json value object of type @c EINA_JSON_TYPE_BOOLEAN + * @return Boolean value of a @p jsnval + * + */ +EAPI Eina_Bool eina_json_boolean_get(Eina_Json_Value *jsnval); + + +/** + * @brief Get key name part of a key-value pair + * + * @param jsnval Json value object of type @c EINA_JSON_TYPE_PAIR + * @return Key name string + * + */ +EAPI const char *eina_json_pair_name_get(Eina_Json_Value *jsnval); + + +/** + * @brief Get value part of a key-value pair + * + * @param jsnval Json value object of type @c EINA_JSON_TYPE_PAIR + * @return Json value object reperesenting the value part of key-value pair + * + */ +EAPI Eina_Json_Value *eina_json_pair_value_get(Eina_Json_Value *jsnval); + + +/** + * @brief Set number value to a json number value object + * + * @param num Json value object of type @c EINA_JSON_TYPE_NUMBER + * @param num Number to set json value object to. + * @return @c EINA_TRUE if setting sucesseded, otherwise EINA_FALSE + * + */ +EAPI Eina_Bool eina_json_number_set(Eina_Json_Value *jsnval, double num); + + +/** + * @brief Set string value to a json string value object + * + * @param num Json value object of type @c EINA_JSON_TYPE_STRING + * @param string String to set json value object to. + * @return @c EINA_TRUE if setting sucesseded, otherwise EINA_FALSE + * + */ +EAPI Eina_Bool eina_json_string_set(Eina_Json_Value *jsnval, const char* string); + + +/** + * @brief Set boolean value to a json string value object + * + * @param num Json value object of type @c EINA_JSON_TYPE_BOOLEAN + * @param boolval Boolean value to set json value object to. + * @return @c EINA_TRUE if setting sucesseded, otherwise EINA_FALSE + * + */ +EAPI Eina_Bool eina_json_boolean_set(Eina_Json_Value *jsnval, Eina_Bool boolval); + + +/** + * @brief Get number of key-value pairs inside a json object + * + * @param obj Json value object of type @c EINA_JSON_TYPE_OBJECT + * @return Number of elements (key-value pairs) inside @p obj + * + */ +EAPI unsigned eina_json_object_count_get(Eina_Json_Value *obj); + + +/** + * @brief Get the nth element of json object + * + * @param obj Json value object of type @c EINA_JSON_TYPE_OBJECT + * @param idx Index inside the @p obj + * @return Jason value object of @c EINA_JSON_TYPE_PAIR or @c NULL if @p idx + * out of bounds or other possible error + * + */ +EAPI Eina_Json_Value *eina_json_object_nth_get(Eina_Json_Value *obj, unsigned idx); + + +/** + * @brief Append new key-value pair to the end of json object + * + * @param obj Json value object of type @c EINA_JSON_TYPE_OBJECT + * @param keyname Name of key in key-value pair + * @param jsnval Json value object as a value part of key-value pair. Must NOT be a @c EINA_JSON_TYPE_PAIR + * @return Reference of newly appended key-value pair inside @p obj, or @c NULL on failure + * + * @warning Once json value is appended or inserted into another json object or array its + * fully belongs to it. It can be freed only by that container and cannot be appended or + * inserted to any other. Doing so will triger a log message and error value will be returned. + */ +EAPI Eina_Json_Value *eina_json_object_append(Eina_Json_Value *obj, const char *keyname, Eina_Json_Value *jsnval); + + +/** + * @brief Insert new key-value pair at nth place inside the json object + * + * @param obj Json value object of type @c EINA_JSON_TYPE_OBJECT + * @param idx Index inside the @p obj to insert the element at + * @param keyname Name of key in key-value pair + * @param jsnval Json value object as a value part of key-value pair. Must NOT be + * a @c EINA_JSON_TYPE_PAIR + * @return Reference of newly inserted key-value pair inside @p obj, or @c NULL on failure + * + * @note Insertion at index 0 on empty object will append the pair. Insertion when index is out of bounds + * will return NULL. + + * @warning Once json value is appended or inserted into another json object or array its + * fully belongs to it. It can be freed only by that container and cannot be appended or + * inserted to any other. Doing so will triger a log message and error value will be returned. + * + */ +EAPI Eina_Json_Value *eina_json_object_insert(Eina_Json_Value *obj, unsigned idx, const char *keyname, Eina_Json_Value *jsnval); + +/** + * @brief Remove and free a key-value pair at nth place from the json object + * + * @param obj Json value object of type @c EINA_JSON_TYPE_OBJECT + * @param idx Index inside the @p obj to remove the element at. + * @return @c EINA_TRUE if removal sucedeed, @c EINA_FALSE otherwise + * + * The remove element will be freed with @ref eina_json_value_free - thus all of its + * children will be freed too recursevly. + * + */ +EAPI Eina_Bool eina_json_object_nth_remove(Eina_Json_Value *obj, unsigned idx); + + +/** + * @brief Get @ref Eina_Iterator to iterate over objects key-value elements. + * + * @param obj Json value object of type @c EINA_JSON_TYPE_OBJECT + * @return Json obect's @ref Eina_Iterator + * + * This is the fastest way to iterate over the jason object's elements. Use it + * like any other Eina containers itterator, dont forgewt to free it in the end. + * All elements of object are of @c EINA_JSON_TYPE_PAIR type + * + */ +EAPI Eina_Iterator *eina_json_object_iterator_new(Eina_Json_Value *obj) EINA_WARN_UNUSED_RESULT; + + +/** + * @brief Get value assosiated with a given key in json object recursively + * + * @param obj Json value object of type @c EINA_JSON_TYPE_OBJECT + * @param ... List of char* key names leading to the desired object. Must end with @c NULL + * @return Json value object if found, @c NULL otherwise. + * + * Finds the corresponding value in a key-value pair inside json object. This a recursive + * function so you can specify a number of keys and traverse into the children objects as well. + * If a key occurs more than once in the same object, only its first occurrence will be returned. + * + */ +EAPI Eina_Json_Value *eina_json_object_value_get_internal(Eina_Json_Value *obj, ...); + + +/** + * @def eina_json_object_value_get + * A convenience wrapper around eina_json_object_value_get_internal() + * No @c NULL required at the end of paramenters + * @see eina_json_object_value_get_internal + */ +#define eina_json_object_value_get(obj, args...) eina_json_object_value_get_internal(obj, ## args, NULL) + + +/** + * @brief Get number of elements inside a json array + * + * @param arr Json value object of type @c EINA_JSON_TYPE_ARRAY + * @return Number of elements inside @p arr + * + */ +EAPI unsigned eina_json_array_count_get(Eina_Json_Value *arr); + + +/** + * @brief Get the nth element of json array + * + * @param arr Json value object of type @c EINA_JSON_TYPE_ARRAY + * @param idx Index inside the @p arr + * @return Json value object or @c NULL if @p idx out of bounds or other possible error + * + */ +EAPI Eina_Json_Value *eina_json_array_nth_get(Eina_Json_Value *arr, unsigned idx); + + +/** + * @brief Append new element to the end of json array + * + * @param arr Json value object of type @c EINA_JSON_TYPE_ARRAY + * @param jsnval Json value object to be appended. Must NOT be a @c EINA_JSON_TYPE_PAIR + * @return Reference of newly appended elemnt inside @p arr or @c NULL on failure + * + * @warning Once json value is appended or inserted into another json object or array its + * fully belongs to it. It can be freed only by that container and cannot be appended or + * inserted to any other. Doing so will triger a log message and error value will be returned. + */ +EAPI Eina_Json_Value *eina_json_array_append(Eina_Json_Value *arr, Eina_Json_Value *jsnval); + + +/** + * @brief Insert new element at nth place inside the json array + * + * @param arr Json value object of type @c EINA_JSON_TYPE_ARRAY + * @param idx Index inside the @p arr to insert the element at + * @param jsnval Json value object to be inserted. Must NOT be a @c EINA_JSON_TYPE_PAIR + * @return Reference of newly inserted element @p arr or @c NULL on failure + * + * @note Insertion at index 0 on empty array will append the element. Insertion when index is out of bounds + * will return NULL. + + * @warning Once json value is appended or inserted into another json object or array its + * fully belongs to it. It can be freed only by that container and cannot be appended or + * inserted to any other. Doing so will triger a log message and error value will be returned. + * + */ +EAPI Eina_Json_Value *eina_json_array_insert(Eina_Json_Value *arr, unsigned idx, Eina_Json_Value *jsnval); + + +/** + * @brief Remove and free an element at nth place from the json object + * + * @param arr Json value object of type @c EINA_JSON_TYPE_ARRAY + * @param idx Index inside the @p arr to remove the element at. + * @return @c EINA_TRUE if removal sucedeed, @c EINA_FALSE otherwise + * + * The remove element will be freed with @ref eina_json_value_free - thus all of its + * children will be freed too, recursevly. + * + */ +EAPI Eina_Bool eina_json_array_nth_remove(Eina_Json_Value *arr, unsigned idx); + + +/** + * @brief Get @ref Eina_Iterator to iterate over array elements. + * + * @param arr Json value object of type @c EINA_JSON_TYPE_ARRAY + * @return Json obect's @ref Eina_Iterator + * + * This is the fastest way to iterate over the jason array's elements. Use it + * like any other Eina containers itterator, dont forget to free it in the end. + * + */ +EAPI Eina_Iterator *eina_json_array_iterator_new(Eina_Json_Value *arr) EINA_WARN_UNUSED_RESULT; + + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +#endif diff --git a/src/tests/eina/eina_suite.c b/src/tests/eina/eina_suite.c index 1577b3aebb..12a14f2de0 100644 --- a/src/tests/eina/eina_suite.c +++ b/src/tests/eina/eina_suite.c @@ -73,6 +73,7 @@ static const Eina_Test_Case etc[] = { // { "Model", eina_test_model }, { "Barrier", eina_test_barrier }, { "Tmp String", eina_test_tmpstr }, + { "JSON", eina_test_json }, { NULL, NULL } }; diff --git a/src/tests/eina/eina_suite.h b/src/tests/eina/eina_suite.h index 85a32cf556..76b3f29818 100644 --- a/src/tests/eina/eina_suite.h +++ b/src/tests/eina/eina_suite.h @@ -60,5 +60,6 @@ void eina_test_model(TCase *tc); void eina_test_cow(TCase *tc); void eina_test_barrier(TCase *tc); void eina_test_tmpstr(TCase *tc); +void eina_test_json(TCase *tc); #endif /* EINA_SUITE_H_ */ diff --git a/src/tests/eina/eina_test_json.c b/src/tests/eina/eina_test_json.c new file mode 100644 index 0000000000..815de2603c --- /dev/null +++ b/src/tests/eina/eina_test_json.c @@ -0,0 +1,613 @@ +/* EINA - EFL data type library + * Copyright (C) 2013 Yossi Kantor + Cedric Bail + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; + * if not, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include "eina_suite.h" +#include "Eina.h" + +typedef struct +{ + unsigned line; + unsigned col; +} TextPos; + +typedef struct +{ + Eina_Strbuf *text; + unsigned long parent_idx; +} Sax_Parser_Data; + +static const char *json_type_string[] = {"NL","NR","STR","BN","PAR","OBJ","ARR"}; + +static const char jstr_array_before[] = +"{\ +\"Array1\":[0,1,2,3,4,5,6],\n\ +\"Array2\":[56,\"He\",true,\"Hello\",false,null],\n\ +\"Array3\":[\"\",\"He\",true,\"Hello\",false,null],\n\ +\"Array4\":[]\ +}"; + +static const char jstr_array_after[] = +"{\"Array1\":[0,1,2,3,4,5,6],\"Array2\":[57,56,156,\"new\",\ +false,\"Bye\",true,null],\"Array3\":[],\"Array4\":[1]}"; + +static const char jstr_object_before[] = +"{\ +\"Object1\":{\"0\":0,\"1\":1,\"2\":2,\"3\":3,\"4\":4,\"5\":5,\"6\":6},\n\ +\"Object2\":{\"Num1\":56,\"Str1\":\"Str1\",\"Bool1\":true,\"Str2\":\"Hello\",\"Null\":null},\n\ +\"Object3\":{\"Num1\":56,\"Bool1\":true,\"String1\":\"String\",\"Null\":null},\ +\"Object4\":{}\ +}"; + +static const char jstr_object_after[] = +"{\"Object1\":{\"0\":0,\"1\":1,\"2\":2,\"3\":3,\"4\":4,\"5\":5,\"6\":6},\ +\"Object2\":{\"Num3\":57,\"Num2\":56,\"Num1\":156,\"Str3\":\"new\",\"Bool1\":false,\ +\"Str2\":\"HelloStr2\",\"Null\":null},\"Object3\":{},\"Object4\":{\"NumberOne\":1},\ +\"Object5\":true}"; + +static const char jstr_object_tree[] = +"{ \"Obj1\":{ \"Obj1_1\":11, \"Obj1_2\":12 },\"Obj2\":2 }"; + +static const TextPos pos_full = {32,2}; +static const char jstr_full[] = +"{\n\ + \"Type1\":\"John\",\n\ + \"Type2\":\"Smith\",\n\ + \"Type3\":25,\n\ + \"Type4\":null,\n\ + \"Type5\":true,\n\ + \"Type6\":false,\n\ + \"Type7\":\n\ + {\n\ + \"Type1\":\"John\",\n\ + \"Type2\":\"Smith\",\n\ + \"Type3\":25,\n\ + \"Type4\":null,\n\ + \"Type5\":true,\n\ + \"Type6\":false\n\ + },\n\ + \"Type8\":[\"John\",\"Smith\",\" Escaped \\\" \",25,null,true,false],\n\ + \"TypeNum\":[0,-1,1,2.0,3.45,-4.67e2,5e-1,6e3,5e+1,-5.6e+2],\n\ + \"TypeMix\":\n\ + [\n\ + 67,null,[],{\"Hello\":[true]},false,\"Bye\",\n\ + {\n\ + \"Type21\":\"John\",\n\ + \"Type22\":\"Smith\",\n\ + \"Type23\":{}\n\ + },\n\ + {\n\ + \"type\":\"fax\",\n\ + \"number\":\"646 555-4567\"\n\ + }\n\ + ]\n\ +}"; + +static const char jstr_double_root[] = +"{\n\ + \"Type1\":\"John\",\n\ + \"Type2\":\"Smith\",\n\ + \"Type3\":25,\n\ + \"Type4\":null,\n\ + \"Type5\":true,\n\ + \"Type6\":false,\n\ + \"Type7\":\n\ + {\n\ + \"Type1\":\"John\",\n\ + \"Type2\":\"Smith\",\n\ + \"Type3\":25,\n\ + \"Type4\":null,\n\ + \"Type5\":true,\n\ + \"Type6\":false\n\ + },\n\ + \"Type8\":[\"John\",\"Smith\",\" Escaped \\\" \",25,null,true,false],\n\ + \"TypeNum\":[0,-1,1,2.0,3.45,-4.67e2,5e-1,6e3,5e+1,-5.6e+2],\n\ + \"TypeMix\":\n\ + [\n\ + 67,null,[],{\"Hello\":[true]},false,\"Bye\",\n\ + {\n\ + \"Type21\":\"John\",\n\ + \"Type22\":\"Smith\",\n\ + \"Type23\":{}\n\ + },\n\ + {\n\ + \"type\":\"fax\",\n\ + \"number\":\"646 555-4567\"\n\ + }\n\ + ]\n\ +} \n\ +{ \"DoubleRoot\":null }\n\ +"; + +static const TextPos pos_lex_error = {4,13}; +char jstr_lex_error[] = +"{\n\ + \"Type1\":\"John\",\n\ + \"Type2\":\"Smith\",\n\ + \"Type3\":25a,\n\ + \"Type4\":null,\n\ + \"Type5\":true,\n\ +}"; + +static const TextPos pos_syntax_error = {3,17}; +char jstr_syntax_error[] = +"{\n\ + \"Type1\":\"John\",\n\ + \"Type2\" \"Smith\",\n\ + \"Type3\":25,\n\ + \"Type4\":null,\n\ + \"Type5\":true,\n\ +}"; + +static const TextPos pos_incomplete = {6,15}; +char jstr_incomplete[]= +"{\n\ + \"Type1\":\"John\",\n\ + \"Type2\":\"Smith\",\n\ + \"Type3\":25,\n\ + \"Type4\":null,\n\ + \"Type5\":true"; + + +static const char jstr_full_packed[] = +"\ +{\"Type1\":\"John\",\"Type2\":\"Smith\",\"Type3\":25,\"Type4\":null,\ +\"Type5\":true,\"Type6\":false,\"Type7\":{\"Type1\":\"John\",\ +\"Type2\":\"Smith\",\"Type3\":25,\"Type4\":null,\"Type5\":true,\ +\"Type6\":false},\"Type8\":[\"John\",\"Smith\",\ +\" Escaped \\\" \",25,null,true,false],\ +\"TypeNum\":[0,-1,1,2,3.45,-467,0.50,6000,50,-560],\ +\"TypeMix\":[67,null,[],{\"Hello\":[true]},false,\"Bye\",\ +{\"Type21\":\"John\",\"Type22\":\"Smith\",\"Type23\":{}},\ +{\"type\":\"fax\",\"number\":\"646 555-4567\"}]}\ +"; + +static const char jstr_sax_result[] = +"\ +(0x2)PR((nil)):OBJ(0x3)PR(0x2):PAR(\"Type1\")(0x4)PR(0x3):STR(\"John\")\ +(0x5)PR(0x2):PAR(\"Type2\")(0x6)PR(0x5):STR(\"Smith\")(0x7)PR(0x2):\ +PAR(\"Type3\")(0x8)PR(0x7):NR(\"25\")(0x9)PR(0x2):PAR(\"Type4\")(0xa)PR\ +(0x9):NL(0xb)PR(0x2):PAR(\"Type5\")(0xc)PR(0xb):BN(\"true\")(0xd)PR(0x2):\ +PAR(\"Type6\")(0xe)PR(0xd):BN(\"false\")(0xf)PR(0x2):PAR(\"Type7\")\ +(0x10)PR(0xf):OBJ(0x11)PR(0x10):PAR(\"Type1\")(0x12)PR(0x11):\ +STR(\"John\")(0x13)PR(0x10):PAR(\"Type2\")(0x14)PR(0x13):STR(\"Smith\")\ +(0x15)PR(0x10):PAR(\"Type3\")(0x16)PR(0x15):NR(\"25\")(0x17)PR(0x10):\ +PAR(\"Type4\")(0x18)PR(0x17):NL(0x19)PR(0x10):PAR(\"Type5\")(0x1a)PR(0x19)\ +:BN(\"true\")(0x1b)PR(0x10):PAR(\"Type6\")(0x1c)PR(0x1b):BN(\"false\")\ +(0x1d)PR(0x2):PAR(\"Type8\")(0x1e)PR(0x1d):ARR(0x1f)PR(0x1e):STR(\"John\")\ +(0x20)PR(0x1e):STR(\"Smith\")(0x21)PR(0x1e):STR(\" Escaped \\\" \")\ +(0x22)PR(0x1e):NR(\"25\")(0x23)PR(0x1e):NL(0x24)PR(0x1e):BN(\"true\")\ +(0x25)PR(0x1e):BN(\"false\")(0x26)PR(0x2):PAR(\"TypeNum\")(0x27)PR(0x26)\ +:ARR(0x28)PR(0x27):NR(\"0\")(0x29)PR(0x27):NR(\"-1\")(0x2a)PR(0x27):\ +NR(\"1\")(0x2b)PR(0x27):NR(\"2.0\")(0x2c)PR(0x27):NR(\"3.45\")(0x2d)\ +PR(0x27):NR(\"-4.67e2\")(0x2e)PR(0x27):NR(\"5e-1\")(0x2f)PR(0x27):\ +NR(\"6e3\")(0x30)PR(0x27):NR(\"5e+1\")(0x31)PR(0x27):NR(\"-5.6e+2\")\ +(0x32)PR(0x2):PAR(\"TypeMix\")(0x33)PR(0x32):ARR(0x34)PR(0x33):\ +NR(\"67\")(0x35)PR(0x33):NL(0x36)PR(0x33):ARR(0x37)PR(0x33):OBJ(0x38)\ +PR(0x37):PAR(\"Hello\")(0x39)PR(0x38):ARR(0x3a)PR(0x39):BN(\"true\")\ +(0x3b)PR(0x33):BN(\"false\")(0x3c)PR(0x33):STR(\"Bye\")(0x3d)PR(0x33):\ +OBJ(0x3e)PR(0x3d):PAR(\"Type21\")(0x3f)PR(0x3e):STR(\"John\")(0x40)\ +PR(0x3d):PAR(\"Type22\")(0x41)PR(0x40):STR(\"Smith\")(0x42)PR(0x3d)\ +:PAR(\"Type23\")(0x43)PR(0x42):OBJ(0x44)PR(0x33):OBJ(0x45)PR(0x44)\ +:PAR(\"type\")(0x46)PR(0x45):STR(\"fax\")(0x47)PR(0x44):PAR(\"number\")\ +(0x48)PR(0x47):STR(\"646 555-4567\")\ +"; + +static void * +sax_parser_cb(Eina_Json_Type type, void *parent, const char *text, void *data) +{ + Sax_Parser_Data *saxdata = (Sax_Parser_Data *)data; + const char* strval = NULL; + + switch(type) + { + case EINA_JSON_TYPE_NUMBER: + case EINA_JSON_TYPE_STRING: + case EINA_JSON_TYPE_PAIR: + strval = text; + break; + case EINA_JSON_TYPE_BOOLEAN: + strval = (*text == 't') ? "true" : "false"; + break; + default: + break; + } + + saxdata->parent_idx++; + eina_strbuf_append_printf(saxdata->text, + "(%p)PR(%p):%s", + (void*)saxdata->parent_idx, + parent, + json_type_string[type]); + + if (strval) eina_strbuf_append_printf(saxdata->text, "(\"%s\")", strval); + + return (void*)saxdata->parent_idx; +} + +START_TEST(eina_json_parse_test) +{ + eina_init(); + + Eina_Json_Value* jval = eina_json_parse(jstr_full); + fail_unless(jval != NULL); + char* jval_str = eina_json_format_string_get(jval, EINA_JSON_FORMAT_PACKED); + fail_if(strcmp(jstr_full_packed, jval_str)); + free(jval_str); + eina_json_value_free(jval); + + fail_if(eina_json_parse(jstr_lex_error)); + fail_if(eina_json_parse(jstr_syntax_error)); + fail_if(eina_json_parse(jstr_incomplete)); + fail_if(eina_json_parse(jstr_double_root)); + + int json_full_len = strlen(jstr_full); + + jval = eina_json_parse_n(jstr_double_root, json_full_len); + fail_unless(jval != NULL); + jval_str = eina_json_format_string_get(jval, EINA_JSON_FORMAT_PACKED); + fail_if(strcmp(jstr_full_packed, jval_str)); + free(jval_str); + eina_json_value_free(jval); + + jval = eina_json_parse_n(jstr_full, 0xFFFF); + fail_unless(jval != NULL); + jval_str = eina_json_format_string_get(jval, EINA_JSON_FORMAT_PACKED); + fail_if(strcmp(jstr_full_packed, jval_str)); + free(jval_str); + eina_json_value_free(jval); + + fail_if(eina_json_parse_n(jstr_full, json_full_len / 2)); + fail_if(eina_json_parse_n(jstr_full, 0)); + + eina_shutdown(); +} +END_TEST + +START_TEST(eina_json_context_dom_parse_test) +{ + eina_init(); + + Eina_Bool res; + Eina_Json_Context *ctx = eina_json_context_dom_new(); + fail_if(eina_json_context_dom_tree_take(ctx)); + res = eina_json_context_parse(ctx, jstr_full); + fail_unless(res); + fail_unless(eina_json_context_completed_get(ctx)); + fail_if(eina_json_context_unfinished_get(ctx)); + fail_if(eina_json_context_line_get(ctx) != pos_full.line); + fail_if(eina_json_context_column_get(ctx) != pos_full.col); + fail_unless(eina_json_context_error_get(ctx) == EINA_JSON_ERROR_NONE); + + res = eina_json_context_parse(ctx, "{"); + fail_if(res); + fail_if(eina_json_context_completed_get(ctx)); + fail_if(eina_json_context_dom_tree_take(ctx)); + fail_if(eina_json_context_unfinished_get(ctx)); + fail_if(eina_json_context_line_get(ctx) != pos_full.line); + fail_if(eina_json_context_column_get(ctx) != pos_full.col); + fail_unless(eina_json_context_error_get(ctx) == EINA_JSON_ERROR_PAST_END); + + eina_json_context_reset(ctx); + + res = eina_json_context_parse(ctx, jstr_incomplete); + fail_unless(res); + fail_if(eina_json_context_completed_get(ctx)); + fail_if(eina_json_context_line_get(ctx) != pos_incomplete.line); + fail_if(eina_json_context_column_get(ctx) != pos_incomplete.col); + fail_unless(eina_json_context_error_get(ctx) == EINA_JSON_ERROR_NONE); + + res = eina_json_context_parse(ctx, "}"); + fail_unless(res); + fail_unless(eina_json_context_completed_get(ctx)); + fail_if(eina_json_context_line_get(ctx) != pos_incomplete.line); + fail_if(eina_json_context_column_get(ctx) != pos_incomplete.col+1); + fail_unless(eina_json_context_error_get(ctx) == EINA_JSON_ERROR_NONE); + + eina_json_context_reset(ctx); + + res = eina_json_context_parse(ctx, jstr_lex_error); + fail_if(res); + fail_if(eina_json_context_completed_get(ctx)); + fail_if(eina_json_context_line_get(ctx) != pos_lex_error.line); + fail_if(eina_json_context_column_get(ctx) != pos_lex_error.col); + fail_unless(eina_json_context_error_get(ctx) == EINA_JSON_ERROR_LEX_TOKEN); + + eina_json_context_reset(ctx); + + res = eina_json_context_parse(ctx, jstr_syntax_error); + fail_if(res); + fail_if(eina_json_context_completed_get(ctx)); + fail_if(eina_json_context_line_get(ctx) != pos_syntax_error.line); + fail_if(eina_json_context_column_get(ctx) != pos_syntax_error.col); + fail_unless(eina_json_context_error_get(ctx) == EINA_JSON_ERROR_SYNTAX_TOKEN); + + eina_json_context_reset(ctx); + + int len = strlen(jstr_double_root); + const int bsize = 3; + int head = 0; + + while (eina_json_context_unfinished_get(ctx)) + { + eina_json_context_parse_n(ctx, &jstr_double_root[head], bsize); + head += bsize; + fail_if(len-head < bsize); + } + + Eina_Json_Value *jval = eina_json_context_dom_tree_take(ctx); + fail_unless(jval != NULL); + fail_if(eina_json_context_dom_tree_take(ctx)); + char *jval_str = eina_json_format_string_get(jval, EINA_JSON_FORMAT_PACKED); + fail_if(strcmp(jstr_full_packed, jval_str)); + free(jval_str); + eina_json_value_free(jval); + + eina_json_context_free(ctx); + eina_shutdown(); +} +END_TEST + +START_TEST(eina_json_context_sax_parse_test) +{ + eina_init(); + + Sax_Parser_Data testsax; + + testsax.text = eina_strbuf_new(); + testsax.parent_idx = 1; + + Eina_Json_Context *ctx = eina_json_context_sax_new(sax_parser_cb, &testsax); + + Eina_Bool res = eina_json_context_parse(ctx, jstr_full); + fail_unless(res); + fail_unless(eina_json_context_completed_get(ctx)); + fail_if(eina_json_context_dom_tree_take(ctx)); + + fail_if(strcmp(eina_strbuf_string_get(testsax.text), jstr_sax_result)); + + eina_strbuf_free(testsax.text); + eina_json_context_free(ctx); + eina_shutdown(); +} +END_TEST + +START_TEST(eina_json_object_test) +{ + eina_init(); + + Eina_Json_Value * jobj = eina_json_parse(jstr_object_before); + fail_if(!jobj); + + Eina_Json_Value * tmp; + + Eina_Json_Value *obj1 = eina_json_pair_value_get(eina_json_object_nth_get(jobj, 0)); + Eina_Json_Value *obj2 = eina_json_pair_value_get(eina_json_object_nth_get(jobj, 1)); + Eina_Json_Value *obj3 = eina_json_pair_value_get(eina_json_object_nth_get(jobj, 2)); + Eina_Json_Value *obj4 = eina_json_pair_value_get(eina_json_object_nth_get(jobj, 3)); + + fail_if(eina_json_object_nth_get(obj4, 0)); + + tmp = eina_json_number_new(1); + fail_if(eina_json_object_insert(obj4, 1, "tmp", tmp)); + eina_json_value_free(tmp); + + fail_unless(eina_json_object_insert(obj4, 0, "NumberOne", + eina_json_number_new(1)) != NULL); + + fail_unless(eina_json_object_nth_get(obj4, 0) != NULL ); + + tmp = eina_json_number_new(1); + fail_if(eina_json_object_insert(obj4, 1, "tmp", tmp)); + eina_json_value_free(tmp); + + fail_if(eina_json_object_nth_get(obj3, 11)); + fail_if(eina_json_object_nth_remove(obj3, 11)); + fail_if(eina_json_object_nth_remove(obj3, 7)); + fail_if(eina_json_object_count_get(obj3) != 4); + while(eina_json_object_count_get(obj3)) + fail_unless(eina_json_object_nth_remove(obj3, 0)); + + fail_if(eina_json_object_nth_remove(obj3, 0)); + fail_if(eina_json_object_nth_get(obj3, 0)); + + tmp = eina_json_pair_value_get(eina_json_object_nth_get(obj2, 4)); + fail_unless(eina_json_type_get(tmp) == EINA_JSON_TYPE_NULL); + + fail_unless(eina_json_object_nth_remove(obj2, 1)); + + tmp = eina_json_pair_value_get(eina_json_object_nth_get(obj2, 3)); + fail_unless(eina_json_type_get(tmp) == EINA_JSON_TYPE_NULL); + + tmp = eina_json_pair_value_get(eina_json_object_nth_get(obj2, 1)); + fail_unless(eina_json_boolean_set(tmp, !eina_json_boolean_get(tmp))); + + tmp = eina_json_pair_value_get(eina_json_object_nth_get(obj2, 0)); + fail_unless(eina_json_number_set(tmp, eina_json_number_get(tmp) + 100)); + + tmp = eina_json_object_nth_get(obj2, 2); + Eina_Strbuf *joinstr = eina_strbuf_new(); + eina_strbuf_append(joinstr, + eina_json_string_get(eina_json_pair_value_get(tmp))); + + eina_strbuf_append(joinstr, eina_json_pair_name_get(tmp)); + fail_unless(eina_json_string_set(eina_json_pair_value_get(tmp), + eina_strbuf_string_get(joinstr))); + + eina_strbuf_free(joinstr); + + fail_unless(eina_json_object_insert(obj2, 0, "Num2", + eina_json_number_new(56)) != NULL ); + fail_unless(eina_json_object_insert(obj2, 0, "Num3", + eina_json_number_new(57)) != NULL); + fail_unless(eina_json_object_insert(obj2, 3, "Str3", + eina_json_string_new("new")) != NULL); + + tmp = eina_json_string_new("fail"); + fail_if(eina_json_object_insert(obj2, 10, "fail", tmp)); + eina_json_value_free(tmp); + + int serial; + fail_unless(eina_json_object_count_get(obj1) == 7); + for (serial=0;serial<(int)eina_json_object_count_get(obj1);serial++) + { + tmp = eina_json_object_nth_get(obj1, serial); + fail_unless(eina_json_number_get(eina_json_pair_value_get(tmp)) == serial); + fail_unless((int)atof(eina_json_pair_name_get(tmp)) == serial); + } + + serial = 0; + Eina_Iterator *it = eina_json_object_iterator_new(obj1); + Eina_Json_Value * data; + EINA_ITERATOR_FOREACH(it, data) + { + fail_unless(eina_json_number_get(eina_json_pair_value_get(data)) == serial); + fail_unless((int)atof(eina_json_pair_name_get(data)) == serial); + serial++; + } + eina_iterator_free(it); + + Eina_Json_Value* japd; + japd = eina_json_object_append(jobj, "Object5", eina_json_boolean_new(EINA_TRUE)); + fail_unless(eina_json_type_get(japd) == EINA_JSON_TYPE_PAIR); + fail_unless(eina_json_boolean_get(eina_json_pair_value_get(japd))); + + char* fstr = eina_json_format_string_get(jobj, EINA_JSON_FORMAT_PACKED); + fail_if(strcmp(fstr, jstr_object_after)); + free(fstr); + + Eina_Json_Value *treeobj = eina_json_parse(jstr_object_tree); + fail_if(eina_json_object_value_get(treeobj)); + fail_if(eina_json_object_value_get(treeobj, "Obj")); + fail_if(eina_json_object_value_get(treeobj, "Obj1", "Obj")); + tmp = eina_json_object_value_get(treeobj, "Obj1", "Obj1_2"); + fail_unless((tmp != NULL)); + fail_unless((eina_json_number_get(tmp) == 12)); + tmp = eina_json_object_value_get(treeobj, "Obj2" ); + fail_unless((tmp != NULL)); + fail_unless((eina_json_number_get(tmp) == 2)); + tmp = eina_json_null_new(); + eina_json_object_insert(treeobj, 0, "Ent", tmp); + fail_if(eina_json_object_insert(jobj, 0, "Ent", tmp)); + fail_if(eina_json_object_append(jobj, "Ent", tmp)); + fail_if(eina_json_string_new(NULL)); + eina_json_value_free(treeobj); + + eina_json_value_free(jobj); + eina_shutdown(); +} +END_TEST + +START_TEST(eina_json_array_test) +{ + eina_init(); + + Eina_Json_Value * jobj = eina_json_parse(jstr_array_before); + fail_if(!jobj); + + Eina_Json_Value * tmp; + + Eina_Json_Value *arr1 = eina_json_pair_value_get(eina_json_object_nth_get(jobj, 0)); + Eina_Json_Value *arr2 = eina_json_pair_value_get(eina_json_object_nth_get(jobj, 1)); + Eina_Json_Value *arr3 = eina_json_pair_value_get(eina_json_object_nth_get(jobj, 2)); + Eina_Json_Value *arr4 = eina_json_pair_value_get(eina_json_object_nth_get(jobj, 3)); + + fail_if(eina_json_array_nth_get(arr4, 0)); + + tmp = eina_json_number_new(1); + fail_if(eina_json_array_insert(arr4, 1, tmp)); + eina_json_value_free(tmp); + + fail_unless(eina_json_array_insert(arr4, 0, eina_json_number_new(1)) != NULL); + fail_unless(eina_json_array_nth_get(arr4, 0) != NULL); + + tmp = eina_json_number_new(1); + fail_if(eina_json_array_insert(arr4, 1, tmp)); + eina_json_value_free(tmp); + + fail_if(eina_json_array_nth_get(arr3, 11)); + fail_if(eina_json_array_nth_remove(arr3, 11)); + fail_if(eina_json_array_nth_remove(arr3, 7)); + fail_if(eina_json_array_count_get(arr3)!=6); + while(eina_json_array_count_get(arr3)) + fail_unless(eina_json_array_nth_remove(arr3, 0)); + + fail_if(eina_json_array_nth_remove(arr3, 0)); + fail_if(eina_json_array_nth_get(arr3, 0)); + + fail_if(eina_json_type_get(eina_json_array_nth_get(arr2, 5)) != EINA_JSON_TYPE_NULL); + fail_unless(eina_json_array_nth_remove(arr2, 1)); + fail_if(eina_json_type_get(eina_json_array_nth_get(arr2, 4)) != EINA_JSON_TYPE_NULL); + + tmp = eina_json_array_nth_get(arr2, 1); + fail_unless(eina_json_boolean_set(tmp, !eina_json_boolean_get(tmp))); + + tmp = eina_json_array_nth_get(arr2, 3); + fail_unless(eina_json_boolean_set(tmp, !eina_json_boolean_get(tmp))); + + tmp = eina_json_array_nth_get(arr2, 0); + fail_unless(eina_json_number_set(tmp, eina_json_number_get(tmp)+100)); + + tmp = eina_json_array_nth_get(arr2, 2); + fail_if(strcmp(eina_json_string_get(tmp), "Hello")); + fail_unless(eina_json_string_set(tmp, "Bye")); + + fail_unless(eina_json_array_insert(arr2, 0, eina_json_number_new(56)) != NULL); + fail_unless(eina_json_array_insert(arr2, 0, eina_json_number_new(57)) != NULL); + fail_unless(eina_json_array_insert(arr2, 3, eina_json_string_new("new")) != NULL); + + tmp = eina_json_string_new("fail"); + fail_if(eina_json_array_insert(arr2, 10, tmp)); + eina_json_value_free(tmp); + + int serial; + fail_unless(eina_json_array_count_get(arr1) == 7); + for (serial=0;serial<(int)eina_json_array_count_get(arr1);serial++) + fail_if(eina_json_number_get(eina_json_array_nth_get(arr1, serial)) != serial); + + serial = 0; + Eina_Iterator *it = eina_json_array_iterator_new(arr1); + Eina_Json_Value * data; + EINA_ITERATOR_FOREACH(it, data) + fail_if(eina_json_number_get(data) != serial++); + + eina_iterator_free(it); + char* fstr = eina_json_format_string_get(jobj, EINA_JSON_FORMAT_PACKED); + fail_if(strcmp(fstr, jstr_array_after)); + free(fstr); + + eina_json_value_free(jobj); + eina_shutdown(); +} +END_TEST + +void +eina_test_json(TCase *tc) +{ + tcase_add_test(tc, eina_json_parse_test); + tcase_add_test(tc, eina_json_context_dom_parse_test); + tcase_add_test(tc, eina_json_context_sax_parse_test); + tcase_add_test(tc, eina_json_object_test); + tcase_add_test(tc, eina_json_array_test); +} -- cgit v1.2.1