/
githubmirror
/
sg3_utils
Обзор
Документация
Войти
/
githubmirror
/
sg3_utils
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
lib/sg_json.c
1 508 строк
46 KB
Douglas Gilbert
sg_inq: re-introduce --ata, prepare for release
16 июл 2026, 06:56
16 июл 2026, 06:56
0cf9863
Код
Авторство
О чём код?
/* * Copyright (c) 2022-2026 Douglas Gilbert. * All rights reserved. * * SPDX-License-Identifier: BSD-2-Clause */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <stdbool.h> #include <string.h> #include <ctype.h> #include "sg_pr2serr.h" #include "sg_json.h" /* Note: does not depend on sg_lib.h or its implementation */ #include "sg_json_builder.h" #define sgj_opts_ev "SG3_UTILS_JSON_OPTS" /* * #define json_serialize_mode_multiline 0 * #define json_serialize_mode_single_line 1 * #define json_serialize_mode_packed 2 * * #define json_serialize_opt_CRLF (1 << 1) * #define json_serialize_opt_pack_brackets (1 << 2) * #define json_serialize_opt_no_space_after_comma (1 << 3) * #define json_serialize_opt_no_space_after_colon (1 << 4) * #define json_serialize_opt_use_tabs (1 << 5) */ static const json_serialize_opts def_out_settings = { json_serialize_mode_multiline, /* one of serialize_mode_* */ 0, /* serialize_opt_* OR-ed together */ 4 /* indent size */ }; static int sgj_name_to_snake(const char * in, char * out, int maxlen_out); static bool sgj_parse_opts(sgj_state * jsp, const char * j_optarg) { bool bad_arg = false; bool prev_negate = false; bool negate; int k, c; for (k = 0; j_optarg[k]; ++k) { /* step over leading whitespace */ if (! isspace((uint8_t)j_optarg[k])) break; } for ( ; j_optarg[k]; ++k) { c = j_optarg[k]; negate = false; switch (c) { case '=': if (0 == k) /* should remove this, allows '-j==h' */ break; /* allow and ignore leading '=' */ bad_arg = true; if (0 == jsp->first_bad_char) jsp->first_bad_char = c; break; case '!': case '~': case '-': /* '-' is probably most practical negation symbol */ negate = true; break; case '0': case '2': jsp->pr_indent_size = 2; break; case '3': jsp->pr_indent_size = 3; break; case '4': jsp->pr_indent_size = 4; break; case '8': jsp->pr_indent_size = 8; break; case 'e': jsp->pr_exit_status = ! prev_negate; break; case 'g': jsp->pr_format = 'g'; break; case 'h': jsp->pr_hex = ! prev_negate; break; case 'k': jsp->pr_packed = ! prev_negate; break; case 'l': jsp->pr_leadin = ! prev_negate; break; case 'n': jsp->pr_name_ex = ! prev_negate; break; case 'o': jsp->pr_out_hr = ! prev_negate; break; case 'p': jsp->pr_pretty = ! prev_negate; break; case 'q': ++jsp->q_counter; break; case 's': jsp->pr_string = ! prev_negate; break; case 'v': ++jsp->verbose; break; case 'y': jsp->pr_format = 'g'; break; case 'z': ++jsp->z_counter; break; case '?': bad_arg = true; jsp->first_bad_char = '\0'; break; default: bad_arg = true; if (0 == jsp->first_bad_char) jsp->first_bad_char = c; break; } prev_negate = negate ? ! prev_negate : false; } return ! bad_arg; } char * sg_json_usage(int char_if_not_j, char * b, int blen) { int n; char short_opt = char_if_not_j ? char_if_not_j : 'j'; if ((NULL == b) || (blen < 1)) goto fini; n = sg_scnpr(b, blen, "JSON option usage:\n"); n += sg_scn3pr(b, blen, n, " --json[=JO] | -%c[=JO]\n\n", short_opt); n += sg_scn3pr(b, blen, n, " where JO is a string of one or more of:\n"); n += sg_scn3pr(b, blen, n, " 0 | 2 tab pretty output to 2 spaces\n"); n += sg_scn3pr(b, blen, n, " 4 tab pretty output to 4 spaces (def)\n"); n += sg_scn3pr(b, blen, n, " 8 tab pretty output to 8 spaces\n"); if (n >= (blen - 1)) goto fini; n += sg_scn3pr(b, blen, n, " e show 'exit_status' field\n"); n += sg_scn3pr(b, blen, n, " h additionally show 'hex' fields\n"); n += sg_scn3pr(b, blen, n, " k packed, only non-pretty printed output\n"); n += sg_scn3pr(b, blen, n, " l show lead-in fields (version and " "invocation information)\n"); n += sg_scn3pr(b, blen, n, " n show 'name_extra' information fields\n"); n += sg_scn3pr(b, blen, n, " o non-JSON output placed in 'plain_text_output' " "array in lead-in\n"); if (n >= (blen - 1)) goto fini; n += sg_scn3pr(b, blen, n, " p pretty print the JSON output\n"); n += sg_scn3pr(b, blen, n, " s show string output (usually fields named " "'meaning')\n"); n += sg_scn3pr(b, blen, n, " v make JSON output more verbose\n"); n += sg_scn3pr(b, blen, n, " - | ~ | ! toggle next letter setting\n"); sg_scn3pr(b, blen, n, "\nIn the absence of the optional JO argument, " "the following are set\non: 'elps' while the others are set " "off, and tabs are set to 4.\nBefore command line JO options " "are applied, the environment\nvariable: %s is applied (if " "present). Note that\nno space is permitted between the short " "option ('-%c') and its\nargument ('JO'). For more information " "see 'man sg3_utils_json' or\n'man sdparm_json' .\n", sgj_opts_ev, short_opt); fini: return b; } static char * sg_json_settings(sgj_state * jsp, char * b, int blen) { snprintf(b, blen, "%d%se%sh%sk%sl%sn%so%sp%ss%sv", jsp->pr_indent_size, jsp->pr_exit_status ? "" : "-", jsp->pr_hex ? "" : "-", jsp->pr_packed ? "" : "-", jsp->pr_leadin ? "" : "-", jsp->pr_name_ex ? "" : "-", jsp->pr_out_hr ? "" : "-", jsp->pr_pretty ? "" : "-", jsp->pr_string ? "" : "-", jsp->verbose ? "" : "-"); return b; } static void sgj_def_opts(sgj_state * jsp) { jsp->pr_as_json = true; jsp->pr_exit_status = true; jsp->pr_hex = false; jsp->pr_leadin = true; jsp->pr_out_hr = false; jsp->pr_name_ex = false; jsp->pr_packed = false; /* 'k' control character, needs '-p' */ jsp->pr_pretty = true; jsp->pr_string = true; jsp->pr_format = 0; jsp->first_bad_char = 0; jsp->verbose = 0; jsp->pr_indent_size = 4; } bool sgj_init_state(sgj_state * jsp, const char * j_optarg) { const char * cp; if (NULL == jsp) return false; sgj_def_opts(jsp); jsp->basep = NULL; jsp->out_hrp = NULL; jsp->userp = NULL; cp = getenv(sgj_opts_ev); if (cp) { if (! sgj_parse_opts(jsp, cp)) { pr2ws("error parsing %s environment variable, ignore\n", sgj_opts_ev); sgj_def_opts(jsp); } } return j_optarg ? sgj_parse_opts(jsp, j_optarg) : true; } sgj_opaque_p sgj_start_r(const char * util_name, const char * ver_str, int argc, char *argv[], sgj_state * jsp) { int k; json_value * jvp; json_value * jv2p = NULL; json_value * jap = NULL; if (NULL == jsp) return NULL; jvp = json_object_new(0); if (NULL == jvp) return NULL; jsp->basep = jvp; if (jsp->pr_leadin) { jap = json_array_new(0); if (NULL == jap) { json_builder_free((json_value *)jvp); return NULL; } /* assume rest of json_*_new() calls succeed */ json_array_push((json_value *)jap, json_integer_new(1)); json_array_push((json_value *)jap, json_integer_new(0)); json_object_push((json_value *)jvp, "json_format_version", (json_value *)jap); if (util_name) { jap = json_array_new(0); if (argv) { for (k = 0; k < argc; ++k) json_array_push((json_value *)jap, json_string_new(argv[k])); } jv2p = json_object_push((json_value *)jvp, "utility_invoked", json_object_new(0)); json_object_push((json_value *)jv2p, "name", json_string_new(util_name)); if (ver_str) json_object_push((json_value *)jv2p, "version_date", json_string_new(ver_str)); else json_object_push((json_value *)jv2p, "version_date", json_string_new("0.0")); json_object_push((json_value *)jv2p, "argv", jap); } if (jsp->verbose) { const char * cp = getenv(sgj_opts_ev); char b[32]; json_object_push((json_value *)jv2p, "environment_variable_name", json_string_new(sgj_opts_ev)); json_object_push((json_value *)jv2p, "environment_variable_value", json_string_new(cp ? cp : "no available")); sg_json_settings(jsp, b, sizeof(b)); json_object_push((json_value *)jv2p, "json_options", json_string_new(b)); } } else { if (jsp->pr_out_hr && util_name) jv2p = json_object_push((json_value *)jvp, "utility_invoked", json_object_new(0)); } if (jsp->pr_out_hr && jv2p) { jsp->out_hrp = json_object_push((json_value *)jv2p, "plain_text_output", json_array_new(0)); if (jsp->pr_leadin && (jsp->verbose > 3)) { char * bp = (char *)calloc(1, 4096); if (bp) { sg_json_usage(0, bp, 4096); sgj_hr_str_out(jsp, bp, strlen(bp)); free(bp); } } } return jvp; } /* Serializes JSON in-memory tree and writes it to fp (usually stdout) . * jsp and fp are assumed to be valid, non-NULL pointers. */ void sgj_js2file_estr(sgj_state * jsp, sgj_opaque_p jop, int exit_status, const char * estr, FILE * fp) { size_t len; const char * ccp; char * b; json_value * jvp = (json_value *)(jop ? jop : jsp->basep); json_serialize_opts out_settings; if (NULL == jvp) { fprintf(fp, "%s: json NULL pointers ??\n", __func__); return; } if ((NULL == jop) && jsp->pr_exit_status) { char d[80]; if (estr) ccp = estr; else { if (0 == exit_status) snprintf(d, sizeof(d), "no errors"); else snprintf(d, sizeof(d), "exit_status=%d", exit_status); ccp = d; } sgj_js_nv_istr(jsp, jop, "exit_status", exit_status, NULL, ccp); } memcpy(&out_settings, &def_out_settings, sizeof(out_settings)); if (jsp->pr_indent_size != def_out_settings.indent_size) out_settings.indent_size = jsp->pr_indent_size; if (! jsp->pr_pretty) out_settings.mode = jsp->pr_packed ? json_serialize_mode_packed : json_serialize_mode_single_line; len = json_measure_ex(jvp, out_settings); if (len < 1) return; if (jsp->verbose > 3) { #if defined(SG_LIB_WIN32) fprintf(fp, "%s: serialization length: %u bytes\n", __func__, len); #else fprintf(fp, "%s: serialization length: %zu bytes\n", __func__, len); #endif } b = (char *)calloc(1, len); if (NULL == b) { if (jsp->verbose > 3) { #if defined(SG_LIB_WIN32) pr2serr("%s: unable to get %u bytes on heap\n", __func__, len); #else pr2serr("%s: unable to get %zu bytes on heap\n", __func__, len); #endif } return; } json_serialize_ex(b, jvp, out_settings); if (jsp->verbose > 3) fprintf(fp, "json serialized:\n"); fprintf(fp, "%s\n", b); free(b); } void sgj_finish(sgj_state * jsp) { if (jsp && jsp->basep) { json_builder_free((json_value *)jsp->basep); jsp->basep = NULL; jsp->out_hrp = NULL; jsp->userp = NULL; } } void sgj_free_unattached(sgj_opaque_p jop) { if (jop) json_builder_free((json_value *)jop); } void sgj_pr_hr(sgj_state * jsp, const char * fmt, ...) { va_list args; if ((NULL == jsp) || (! jsp->pr_as_json)) { va_start(args, fmt); vfprintf(stdout, fmt, args); va_end(args); } else if (jsp->pr_out_hr) { bool step = false; size_t ln; char b[256]; static const int blen = sizeof(b); va_start(args, fmt); ln = vsnprintf(b, blen, fmt, args); if ((ln > 0) && (ln < (size_t)blen)) { char * cp; /* deal with leading, trailing and embedded newlines */ while ( true ) { cp = strrchr(b, '\n'); if (NULL == cp) break; else if (cp == b) { if ('\0' == *(cp + 1)) *cp = '\0'; else step = true; break; } else if ('\0' == *(cp + 1)) *cp = '\0'; else *cp = ';'; } /* replace any tabs with semicolons or spaces */ while ( true ) { cp = strchr(b, '\t'); if (NULL == cp) break; else if (cp == b) { if ('\0' == *(cp + 1)) *cp = '\0'; else { *cp = ' '; /* so don't find \t again and again */ step = true; } } else { if (';' == *(cp - 1)) *cp = ' '; else *cp = ';'; } } } json_array_push((json_value *)jsp->out_hrp, json_string_new(step ? b + 1 : b)); va_end(args); } else { /* do nothing, just consume arguments */ va_start(args, fmt); va_end(args); } } /* jop will 'own' returned value (if non-NULL) */ sgj_opaque_p sgj_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s) { sgj_opaque_p resp = NULL; if (jsp && jsp->pr_as_json && name_so_s) resp = json_object_push((json_value *)(jop ? jop : jsp->basep), name_so_s, json_object_new(0)); return resp; } sgj_opaque_p sgj_snake_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop, const char * conv2sname) { if (jsp && jsp->pr_as_json && conv2sname) { int olen = strlen(conv2sname); char * sname = (char *)malloc(olen + 8); sgj_opaque_p resp = NULL; int nlen; if (NULL == sname) return NULL; nlen = sgj_name_to_snake(conv2sname, sname, olen + 8); if (nlen > 0) resp = json_object_push((json_value *)(jop ? jop : jsp->basep), sname, json_object_new(0)); free(sname); return resp; } return NULL; } /* jop will 'own' returned value (if non-NULL) */ sgj_opaque_p sgj_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s) { sgj_opaque_p resp = NULL; if (jsp && jsp->pr_as_json && name_so_s) resp = json_object_push((json_value *)(jop ? jop : jsp->basep), name_so_s, json_array_new(0)); return resp; } sgj_opaque_p sgj_snake_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop, const char * conv2sname) { if (jsp && jsp->pr_as_json && conv2sname) { int olen = strlen(conv2sname); char * sname = (char *)malloc(olen + 8); sgj_opaque_p resp = NULL; int nlen; if (NULL == sname) return NULL; nlen = sgj_name_to_snake(conv2sname, sname, olen + 8); if (nlen > 0) resp = json_object_push((json_value *)(jop ? jop : jsp->basep), sname, json_array_new(0)); free(sname); return resp; } return NULL; } /* Newly created object is un-attached to jsp->basep tree */ sgj_opaque_p sgj_new_unattached_object_r(sgj_state * jsp) { return (jsp && jsp->pr_as_json) ? json_object_new(0) : NULL; } /* Newly created array is un-attached to jsp->basep tree */ sgj_opaque_p sgj_new_unattached_array_r(sgj_state * jsp) { return (jsp && jsp->pr_as_json) ? json_array_new(0) : NULL; } /* Newly created string is un-attached to jsp->basep tree */ sgj_opaque_p sgj_new_unattached_string_r(sgj_state * jsp, const char * val_i) { return (jsp && jsp->pr_as_json) ? json_string_new(val_i) : NULL; } /* Newly created string with length object is un-attached to jsp->basep * tree */ sgj_opaque_p sgj_new_unattached_str_len_r(sgj_state * jsp, const char * val_s, int val_len) { return (jsp && jsp->pr_as_json && val_s && (val_len > 0)) ? json_string_new_length(val_len, val_s) : NULL; } /* Newly created integer object is un-attached to jsp->basep tree */ sgj_opaque_p sgj_new_unattached_integer_r(sgj_state * jsp, uint64_t val_i) { return (jsp && jsp->pr_as_json) ? json_integer_new(val_i) : NULL; } /* Newly created boolean object is un-attached to jsp->basep tree */ sgj_opaque_p sgj_new_unattached_bool_r(sgj_state * jsp, bool val_b) { return (jsp && jsp->pr_as_json) ? json_boolean_new(val_b) : NULL; } /* Newly created null object is un-attached to jsp->basep tree */ sgj_opaque_p sgj_new_unattached_null_r(sgj_state * jsp) { return (jsp && jsp->pr_as_json) ? json_null_new() : NULL; } sgj_opaque_p sgj_js_nv_s(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, const char * val_s) { if (jsp && jsp->pr_as_json && val_s) { if (name_so_s) return json_object_push((json_value *)(jop ? jop : jsp->basep), name_so_s, json_string_new(val_s)); else return json_array_push((json_value *)(jop ? jop : jsp->basep), json_string_new(val_s)); } else return NULL; } sgj_opaque_p sgj_js_nv_s_len(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, const char * val_s, int val_len) { int k; if (jsp && jsp->pr_as_json && val_s && (val_len > 0)) { for (k = 0; k < val_len; ++k) { /* don't want '\0' in val_s string */ if (0 == val_s[k]) break; } if (name_so_s) return json_object_push((json_value *)(jop ? jop : jsp->basep), name_so_s, json_string_new_length(k, val_s)); else return json_array_push((json_value *)(jop ? jop : jsp->basep), json_string_new_length(k, val_s)); } else return NULL; } /* Local copy of sg_lib:sg_has_control_char() . Note that scan stops if * null char ('\0') found and false is returned. */ static bool has_control_char(const uint8_t * up, int len) { int k; uint8_t u; for (k = 0; k < len; ++k) { u = up[k]; if (0 == u) break; if ((u < 0x20) || (0x7f == u)) return true; } return false; } sgj_opaque_p sgj_js_nv_s_len_chk(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, const uint8_t * va_p, int va_len) { sgj_opaque_p res = NULL; if (va_p && (va_len > 0) && has_control_char(va_p, va_len)) { const int n = va_len * 4 + 4; char * p = (char *)malloc(n); if (p) { int k; k = sgj_conv2json_string(va_p, va_len, p, n); if (k > 0) res = sgj_js_nv_s_len(jsp, jop, name_so_s, p, k); free(p); } return res; } else return sgj_js_nv_s_len(jsp, jop, name_so_s, (const char *)va_p, va_len); } /* If name_so_s is non-NULL, then pushes that name, integer(val_i) pair into * JSON in-memory tree at position jop. If name_so_s is NULL assume jop points * to an array of integers and pushes value there. If jop is NULL then uses * jsp->basep (usually the root of the in-memory tree) in its place. If jsp * is NULL or jsp->pr_as_json is false then does nothing and returns NULL, * otherwise returns pointer to newly created object in the JSON in-memory * tree. */ sgj_opaque_p sgj_js_nv_i(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, int64_t val_i) { if (jsp && jsp->pr_as_json) { if (name_so_s) return json_object_push((json_value *)(jop ? jop : jsp->basep), name_so_s, json_integer_new(val_i)); else return json_array_push((json_value *)(jop ? jop : jsp->basep), json_integer_new(val_i)); } else return NULL; } sgj_opaque_p sgj_js_nv_b(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, bool val_b) { if (jsp && jsp->pr_as_json) { if (name_so_s) return json_object_push((json_value *)(jop ? jop : jsp->basep), name_so_s, json_boolean_new(val_b)); else return json_array_push((json_value *)(jop ? jop : jsp->basep), json_boolean_new(val_b)); } else return NULL; } /* jop will 'own' ua_jop (if returned value is non-NULL) */ sgj_opaque_p sgj_js_nv_o(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, sgj_opaque_p ua_jop /* val_o */) { if (jsp && jsp->pr_as_json && ua_jop) { if (name_so_s) return json_object_push((json_value *)(jop ? jop : jsp->basep), name_so_s, (json_value *)ua_jop); else return json_array_push((json_value *)(jop ? jop : jsp->basep), (json_value *)ua_jop); } else return NULL; } void sgj_js_nv_ihex(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, uint64_t val_i) { if ((NULL == jsp) || (NULL == name_so_s) || (! jsp->pr_as_json)) return; else if (jsp->pr_hex) { sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name_so_s); char b[64]; if (NULL == jo2p) return; sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); snprintf(b, sizeof(b), "%" PRIx64, val_i); sgj_js_nv_s(jsp, jo2p, "hex", b); } else sgj_js_nv_i(jsp, jop, name_so_s, (int64_t)val_i); } static const char * sc_mn_s = "meaning"; void sgj_js_nv_istr(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, int64_t val_i, const char * nmeaning_or_s, const char * vmeaning_or_s) { if ((NULL == jsp) || (! jsp->pr_as_json)) return; else if (vmeaning_or_s && jsp->pr_string) { sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name_so_s); if (NULL == jo2p) return; sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); sgj_js_nv_s(jsp, jo2p, (nmeaning_or_s ? nmeaning_or_s : sc_mn_s), vmeaning_or_s); } else sgj_js_nv_i(jsp, jop, name_so_s, val_i); } void sgj_js_nv_ihexstr(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, int64_t val_i, const char * nmeaning_or_s, const char * vmeaning_or_s) { bool as_str; if ((NULL == jsp) || (! jsp->pr_as_json)) return; as_str = jsp->pr_string && vmeaning_or_s; if ((! jsp->pr_hex) && (! as_str)) sgj_js_nv_i(jsp, jop, name_so_s, val_i); else { char b[64]; sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name_so_s); if (NULL == jo2p) return; sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); if (jsp->pr_hex) { snprintf(b, sizeof(b), "%" PRIx64, val_i); sgj_js_nv_s(jsp, jo2p, "hex", b); } if (as_str) sgj_js_nv_s(jsp, jo2p, (nmeaning_or_s ? nmeaning_or_s : sc_mn_s), vmeaning_or_s); } } static const char * sc_nex_s = "name_extra"; void sgj_js_nv_ihex_nex(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, int64_t val_i, bool hex_as_well, const char * nex_s) { bool as_hex, as_nex; if ((NULL == jsp) || (! jsp->pr_as_json)) return; as_hex = jsp->pr_hex && hex_as_well; as_nex = jsp->pr_name_ex && nex_s; if (! (as_hex || as_nex)) sgj_js_nv_i(jsp, jop, name_so_s, val_i); else { char b[64]; sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name_so_s); if (NULL == jo2p) return; sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); if (as_hex) { snprintf(b, sizeof(b), "%" PRIx64, val_i); sgj_js_nv_s(jsp, jo2p, "hex", b); } if (as_nex) sgj_js_nv_s(jsp, jo2p, sc_nex_s, nex_s); } } void sgj_js_nv_s_nex(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, const char * val_s, const char * nex_s) { bool as_nex; if ((NULL == jsp) || (! jsp->pr_as_json)) return; as_nex = jsp->pr_name_ex && nex_s; if ((NULL == val_s) && (! as_nex)) /* corner case: assume jop is an array */ json_array_push((json_value *)(jop ? jop : jsp->basep), json_string_new(name_so_s)); else if (NULL == val_s) sgj_js_nv_s(jsp, jop, name_so_s, nex_s); else if (! as_nex) sgj_js_nv_s(jsp, jop, name_so_s, val_s); else { sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name_so_s); if (NULL == jo2p) return; sgj_js_nv_s(jsp, jo2p, "s", val_s); sgj_js_nv_s(jsp, jo2p, sc_nex_s, nex_s); } } /* Simplified version of sg_lib::hex2str() */ static void h2str(const uint8_t * byte_arr, int num_bytes, int leadin_sp, char * bp, int blen) { int j, k, n; if (leadin_sp > 128) leadin_sp = 128; if (blen < 4) return; /* too small to play with */ if (leadin_sp >= blen - 4) leadin_sp = blen - 4; for (n = 0; n < leadin_sp; ++n) bp[n] = ' '; bp[n] = '\0'; for (k = 0; (k < num_bytes) && (n < blen); ) { j = sg_scn3pr(bp, blen, n, "%02x ", byte_arr[k]); if (j < 2) break; n += j; ++k; if ((0 == (k % 8)) && (k < num_bytes) && (n < blen)) { bp[n++] = ' '; } } j = strlen(bp); if ((j > 0) && (' ' == bp[j - 1])) bp[j - 1] = '\0'; /* chop off trailing space */ } /* Add hex byte strings irrespective of jsp->pr_hex setting. */ void sgj_js_nv_hex_bytes(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, const uint8_t * byte_arr, int num_bytes) { int blen = num_bytes * 4; char * bp; if ((NULL == jsp) || (! jsp->pr_as_json)) return; bp = (char *)calloc(1, blen + 4); if (bp) { h2str(byte_arr, num_bytes, 0, bp, blen); sgj_js_nv_s(jsp, jop, name_so_s, bp); free(bp); } } void sgj_js_nv_ihexstr_nex(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, int64_t val_i, bool hex_as_well, const char * nmeaning_or_s, const char * vmeaning_or_s, const char * nex_s) { bool as_hex, as_str, as_nex; const char * sname; if ((NULL == jsp) || (! jsp->pr_as_json)) return; as_hex = jsp->pr_hex && hex_as_well; as_str = jsp->pr_string && vmeaning_or_s; as_nex = jsp->pr_name_ex && nex_s; sname = nmeaning_or_s ? nmeaning_or_s : sc_mn_s; if (! (as_hex || as_nex || as_str)) sgj_js_nv_i(jsp, jop, name_so_s, val_i); else { char b[64]; sgj_opaque_p jo2p = sgj_named_subobject_r(jsp, jop, name_so_s); if (NULL == jo2p) return; sgj_js_nv_i(jsp, jo2p, "i", (int64_t)val_i); if (as_nex) { if (as_hex) { snprintf(b, sizeof(b), "%" PRIx64, val_i); sgj_js_nv_s(jsp, jo2p, "hex", b); } if (as_str) { sgj_js_nv_s(jsp, jo2p, sname, vmeaning_or_s); } sgj_js_nv_s(jsp, jo2p, sc_nex_s, nex_s); } else if (as_hex) { snprintf(b, sizeof(b), "%" PRIx64, val_i); sgj_js_nv_s(jsp, jo2p, "hex", b); if (as_str) sgj_js_nv_s(jsp, jo2p, sname, vmeaning_or_s); } else if (as_str) sgj_js_nv_s(jsp, jo2p, sname, vmeaning_or_s); } } /* Treat '\n' in sp as line breaks. Consumes characters from sp until either * a '\0' is found or slen is exhausted. Add each line to jsp->out_hrp JSON * array (if conditions met). Outputs to stdout. */ void sgj_hr_str_out(sgj_state * jsp, const char * sp, int slen) { char c; int k, n; const char * prev_sp = sp; const char * cur_sp = sp; if ((NULL == jsp) || (NULL == jsp->out_hrp) || (! jsp->pr_as_json) || (! jsp->pr_out_hr)) return; for (k = 0; k < slen; ++k, ++cur_sp) { c = *cur_sp; if ('\0' == c) break; else if ('\n' == c) { n = cur_sp - prev_sp; /* when name is NULL, add to array (jsp->out_hrp) */ sgj_js_nv_s_len(jsp, jsp->out_hrp, NULL, prev_sp, n); prev_sp = cur_sp + 1; } } if (prev_sp < cur_sp) { n = cur_sp - prev_sp; sgj_js_nv_s_len(jsp, jsp->out_hrp, NULL, prev_sp, n); } } char * sgj_convert2snake(const char * in_name, char * name_so_s, int max_sname_len) { sgj_name_to_snake(in_name, name_so_s, max_sname_len); return name_so_s; } bool sgj_is_snake_name(const char * in_name) { size_t k; size_t ln = strlen(in_name); char c; for (k = 0; k < ln; ++k) { c = in_name[k]; if (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z')) || (c == '_')) continue; else return false; } return true; } /* This function tries to convert the 'in' C string to "snake_case" * convention so the output 'out' only contains lower case ASCII letters, * numerals and "_" as a separator. Any leading or trailing underscores * are removed as are repeated underscores (e.g. "_Snake __ case" becomes * "snake_case"). Parentheses and the characters between them are removed. * Returns number of characters placed in 'out' excluding the trailing * NULL */ char * sgj_convert2snake_rm_parens(const char * in, char * out, int maxlen_out) { bool prev_underscore = false; bool within_paren = false; int c, k, j, inlen; if (maxlen_out < 2) { if (maxlen_out == 1) out[0] = '\0'; return out; } inlen = strlen(in); for (k = 0, j = 0; (k < inlen) && (j < maxlen_out); ++k) { c = in[k]; if (within_paren) { if (')' == c) within_paren = false; continue; } if (isalnum(c)) { out[j++] = isupper(c) ? tolower(c) : c; prev_underscore = false; } else if ('(' == c) within_paren = true; else if ((j > 0) && (! prev_underscore)) { out[j++] = '_'; prev_underscore = true; } /* else we are skipping character 'c' */ } if (j == maxlen_out) out[--j] = '\0'; else if (0 == j) { out[0] = '_'; out[1] = '\0'; return out; } /* trim of trailing underscores (might have been spaces) */ for (k = j - 1; k >= 0; --k) { if (out[k] != '_') break; } if (k < 0) k = 0; else ++k; out[k] = '\0'; return out; } static int sgj_name_to_snake(const char * in, char * out, int maxlen_out) { bool prev_underscore = false; int c, k, j, inlen; if (maxlen_out < 2) { if (maxlen_out == 1) out[0] = '\0'; return 0; } inlen = strlen(in); for (k = 0, j = 0; (k < inlen) && (j < maxlen_out); ++k) { c = in[k]; if (isalnum(c)) { out[j++] = isupper(c) ? tolower(c) : c; prev_underscore = false; } else if ((j > 0) && (! prev_underscore)) { out[j++] = '_'; prev_underscore = true; } /* else we are skipping character 'c' */ } if (j == maxlen_out) out[--j] = '\0'; /* trim of trailing underscore (can only be one) */ if (0 == j) { out[j++] = '_'; /* degenerate case: name set to '_' */ out[j] = '\0'; } else if ('_' == out[j - 1]) out[--j] = '\0'; else out[j] = '\0'; return j; } static int sgj_jtype_to_s(char * b, int blen_max, json_value * jvp, bool as_hex) { json_type jtype = jvp ? jvp->type : json_none; switch (jtype) { case json_string: return sg_scnpr(b, blen_max, "%s", jvp->u.string.ptr); case json_integer: if (as_hex) return sg_scnpr(b, blen_max, "0x%" PRIx64, jvp->u.integer); else return sg_scnpr(b, blen_max, "%" PRIi64, jvp->u.integer); case json_boolean: return sg_scnpr(b, blen_max, "%s", jvp->u.boolean ? "true" : "false"); case json_none: default: if ((blen_max > 0) && ('\0' != b[0])) b[0] = '\0'; break; } return 0; } static int sgj_haj_helper(char * b, int blen_max, const char * name, enum sgj_separator_t sep, bool use_jvp, json_value * jvp, int64_t val_instead, bool as_hex) { int n = 0; if (name) { n += sg_scn3pr(b, blen_max, n, "%s", name); switch (sep) { case SGJ_SEP_NONE: break; case SGJ_SEP_SPACE_1: n += sg_scn3pr(b, blen_max, n, " "); break; case SGJ_SEP_SPACE_2: n += sg_scn3pr(b, blen_max, n, " "); break; case SGJ_SEP_SPACE_3: n += sg_scn3pr(b, blen_max, n, " "); break; case SGJ_SEP_SPACE_4: n += sg_scn3pr(b, blen_max, n, " "); break; case SGJ_SEP_EQUAL_NO_SPACE: n += sg_scn3pr(b, blen_max, n, "="); break; case SGJ_SEP_EQUAL_1_SPACE: n += sg_scn3pr(b, blen_max, n, "= "); break; case SGJ_SEP_SPACE_EQUAL_SPACE: n += sg_scn3pr(b, blen_max, n, " = "); break; case SGJ_SEP_COLON_NO_SPACE: n += sg_scn3pr(b, blen_max, n, ":"); break; case SGJ_SEP_COLON_1_SPACE: case SGJ_SEP_COLON_1_SPACE_RM_PARENS: n += sg_scn3pr(b, blen_max, n, ": "); break; default: break; } } if (use_jvp) n += sgj_jtype_to_s(b + n, blen_max - n, jvp, as_hex); else if (as_hex) n += sg_scn3pr(b, blen_max, n, "0x%" PRIx64, val_instead); else n += sg_scn3pr(b, blen_max, n, "%" PRIi64, val_instead); return n; } /* aname will be converted to a snake name, if required. leadin_sp is * clamped at 128 . */ static void sgj_haj_xx(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * aname, enum sgj_separator_t sep, json_value * jvp, bool hex_haj, const char * val_s, const char * nex_s) { bool eaten = false; bool as_json = (jsp && jsp->pr_as_json); bool json_hex = hex_haj || (jsp && jsp->pr_hex); bool done; int n; json_type jtype = jvp ? jvp->type : json_none; char b[256]; char jname[96]; static const int blen = sizeof(b); b[0] = '\0'; /* if leadin_sp negative, snprintf() takes abs(leadin_sp) */ snprintf(b, blen, "%*s", ((abs(leadin_sp) > 128) ? 128 : leadin_sp), ""); n = strlen(b); if (NULL == aname) { if ((! as_json) || (jsp && jsp->pr_out_hr)) { sgj_jtype_to_s(b + n, blen - n, jvp, hex_haj); printf("%s\n", b); } if (NULL == jop) { if (as_json && jsp->pr_out_hr) { eaten = true; json_array_push((json_value *)jsp->out_hrp, jvp ? jvp : json_null_new()); } } else { /* assume jop points to named array */ if (as_json) { eaten = true; json_array_push((json_value *)jop, jvp ? jvp : json_null_new()); } } goto fini; } if (as_json) { int k; if (NULL == jop) jop = jsp->basep; if (SGJ_SEP_COLON_1_SPACE_RM_PARENS == sep) { jname[0] = '\0'; sgj_convert2snake_rm_parens(aname, jname, sizeof(jname)); k = strlen(jname); } else { k = sgj_name_to_snake(aname, jname, sizeof(jname)); } if (k > 0) { done = false; if (nex_s && (strlen(nex_s) > 0)) { switch (jtype) { case json_string: sgj_js_nv_s_nex(jsp, jop, jname, jvp->u.string.ptr, nex_s); done = true; break; case json_integer: sgj_js_nv_ihexstr_nex(jsp, jop, jname, jvp->u.integer, json_hex, sc_mn_s, val_s, nex_s); done = true; break; case json_boolean: sgj_js_nv_ihexstr_nex(jsp, jop, jname, jvp->u.boolean, false, sc_mn_s, val_s, nex_s); done = true; break; case json_none: default: break; } } else { switch (jtype) { case json_string: sgj_js_nv_s(jsp, jop, jname, jvp->u.string.ptr); done = true; break; case json_integer: if (json_hex) { sgj_js_nv_ihexstr(jsp, jop, jname, jvp->u.integer, sc_mn_s, val_s); done = true; } break; case json_none: default: break; } } if (! done) { eaten = true; json_object_push((json_value *)jop, jname, jvp ? jvp : json_null_new()); } } } if (jvp && ((as_json && jsp->pr_out_hr) || (! as_json))) sgj_haj_helper(b + n, blen - n, aname, sep, true, jvp, 0, hex_haj); if (as_json && jsp->pr_out_hr) json_array_push((json_value *)jsp->out_hrp, json_string_new(b)); if (! as_json) printf("%s\n", b); fini: if (jvp && (! eaten)) json_builder_free((json_value *)jvp); } void sgj_haj_vs(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, const char * val_s) { json_value * jvp; /* make json_value even if jsp->pr_as_json is false */ jvp = val_s ? json_string_new(val_s) : NULL; sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, NULL); } void sgj_haj_vs_len(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, const char * val_s, int val_len) { json_value * jvp; /* make json_value even if jsp->pr_as_json is false */ jvp = (val_s && (val_len > 0)) ? json_string_new_length(val_len, val_s) : NULL; sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, NULL); } void sgj_haj_vs_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, const char * val_s, const char * nex_s) { json_value * jvp; /* make json_value even if jsp->pr_as_json is false */ jvp = val_s ? json_string_new(val_s) : NULL; sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, nex_s); } void sgj_haj_vi(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, int64_t val_i, bool hex_haj) { json_value * jvp; jvp = json_integer_new(val_i); sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_haj, NULL, NULL); } void sgj_haj_vistr(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, int64_t val_i, bool hex_haj, const char * vmeaning_or_s) { json_value * jvp; jvp = json_integer_new(val_i); sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_haj, vmeaning_or_s, NULL); } void sgj_haj_vi_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, int64_t val_i, bool hex_haj, const char * nex_s) { json_value * jvp; jvp = json_integer_new(val_i); sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_haj, NULL, nex_s); } void sgj_haj_vistr_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, int64_t val_i, bool hex_haj, const char * vmeaning_or_s, const char * nex_s) { json_value * jvp; jvp = json_integer_new(val_i); sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, hex_haj, vmeaning_or_s, nex_s); } void sgj_haj_vb(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, bool val_b) { json_value * jvp; jvp = json_boolean_new(val_b); sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, NULL); } sgj_opaque_p sgj_haj_subo_r(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * aname, enum sgj_separator_t sep, int64_t val_i, bool hex_haj) { bool as_json = (jsp && jsp->pr_as_json); int n = 0; sgj_opaque_p jo2p; char b[256]; static const int blen = sizeof(b); if (NULL == aname) return NULL; snprintf(b, blen, "%*s", ((abs(leadin_sp) > 128) ? 128 : leadin_sp), ""); n = strlen(b); if ((! as_json) || jsp->pr_out_hr) sgj_haj_helper(b + n, blen - n, aname, sep, false, NULL, val_i, hex_haj); if (as_json && jsp->pr_out_hr) json_array_push((json_value *)jsp->out_hrp, json_string_new(b)); if (! as_json) printf("%s\n", b); if (as_json) { sgj_name_to_snake(aname, b, blen); jo2p = sgj_named_subobject_r(jsp, jop, b); if (jo2p) { sgj_js_nv_i(jsp, jo2p, "i", val_i); if (hex_haj && jsp->pr_hex) { snprintf(b, blen, "%" PRIx64, val_i); sgj_js_nv_s(jsp, jo2p, "hex", b); } } return jo2p; } return NULL; } void sgj_haj_vs_hex_bytes_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, const uint8_t * byte_arr, int num_bytes, const char * nex_s) { static const int max_fixed_len = 132; char b[132]; int blen = num_bytes * 4 + 8; char * bp = b; if (blen >= max_fixed_len) { bp = (char *)calloc(1, blen + 4); } if (bp) { json_value * jvp; if (leadin_sp < 0) leadin_sp = 0; h2str(byte_arr, num_bytes, 0 /* leadin_sp */, bp, blen); /* make json_value even if jsp->pr_as_json is false */ jvp = json_string_new(bp); sgj_haj_xx(jsp, jop, leadin_sp, name, sep, jvp, false, NULL, nex_s); if (bp != b) free(bp); } } /* Convert a byte stream that is meant to be printable ASCII or UTF-8 to * something that is allowable in a JSON string. This means treating the * ASCII control characters (i.e. < 0x20) and DEL as specials. Also '\' and * '"' need to be escaped with a preceding '\'. These C escape codes are used * in JSON: '\b', '\f', '\n', '\r' and '\t'. Other control characters, and DEL * are encoded as '\x<hh>' where <hh> is two hex digits. So the DEL and * null ACSII characters in the input will appear as '\x7f' and '\x00' * respectively in the output. The output serializer will expand those * two to '\\x7f' and '\\x00'. Note that the JSON form of '\u<hhhh>' is * _not_ used. The input is pointed to by 'cup' which is 'ulen' bytes long. * The output is written to 'op' and will not exceed 'olen_max' bytes. If * 'olen_max' is breached, this function returns -1 else it returns the * number of bytes written to 'op'. */ int sgj_conv2json_string(const uint8_t * cup, int ulen, char * op, int olen_max) { int k, j; for (k = 0, j = 0; k < ulen; k++) { uint8_t u = cup[k]; /* Treat DEL [0x7f] as non-printable, output: "\\x7f" */ if ((u >= 0x20) && (u != 0x7f)) { if (j + 1 >= olen_max) return -1; op[j++] = u; } else { uint8_t u2 = 0; switch (u) { case '"': case '\\': u2 = u; break; case '\b': u2 = 'b'; break; case '\f': u2 = 'f'; break; case '\n': u2 = 'n'; break; case '\r': u2 = 'r'; break; case '\t': u2 = 't'; break; } if (u2) { /* the escaping of these is handled by the json_builder's * output serializer. */ if (j + 1 >= olen_max) return -1; op[j++] = u; /* not using u2, only that it is != 0 */ } else { char b[8]; if (snprintf(b, sizeof(b), "\\x%02x", u) != 4 || j + 4 >= olen_max) return -1; memcpy(op + j, b, 4); j += 4; } } } return j; }