/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren This file is part of CLIXON. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 3 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL, and not to allow others to use your version of this file under the terms of Apache License version 2, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** * JSON support functions. * JSON syntax is according to: * http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf */ #include #include #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clixon */ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_xml.h" #include "clixon_json.h" #include "clixon_json_parse.h" #define JSON_INDENT 2 /* maybe we should set this programmatically? */ /* Let xml2json_cbuf_vec() return json array: [a,b]. ALternative is to create a pseudo-object and return that: {top:{a,b}} */ #define VEC_ARRAY 1 enum array_element_type{ NO_ARRAY=0, FIRST_ARRAY, MIDDLE_ARRAY, LAST_ARRAY, BODY_ARRAY }; enum childtype{ NULL_CHILD=0, /* eg no children */ BODY_CHILD, /* eg one child which is a body, eg 1 */ ANY_CHILD, /* eg or */ }; /*! x is element and has exactly one child which in turn has none * Clone from clixon_xml_map.c */ static enum childtype childtype(cxobj *x) { cxobj *xc1; /* the only child of x */ if (xml_type(x) != CX_ELMNT) return -1; /* n/a */ if (xml_child_nr(x) == 0) return NULL_CHILD; if (xml_child_nr(x) > 1) return ANY_CHILD; xc1 = xml_child_i(x, 0); /* From here exactly one child */ if (xml_child_nr(xc1) == 0 && xml_type(xc1)==CX_BODY) return BODY_CHILD; else return ANY_CHILD; } static char* childtype2str(enum childtype lt) { switch(lt){ case NULL_CHILD: return "null"; break; case BODY_CHILD: return "body"; break; case ANY_CHILD: return "any"; break; } return ""; } static char* arraytype2str(enum array_element_type lt) { switch(lt){ case NO_ARRAY: return "no"; break; case FIRST_ARRAY: return "first"; break; case MIDDLE_ARRAY: return "middle"; break; case LAST_ARRAY: return "last"; break; case BODY_ARRAY: return "body"; break; } return ""; } static enum array_element_type array_eval(cxobj *xprev, cxobj *x, cxobj *xnext) { enum array_element_type array = NO_ARRAY; int eqprev=0; int eqnext=0; if (xml_type(x)!=CX_ELMNT){ array=BODY_ARRAY; goto done; } if (xnext && xml_type(xnext)==CX_ELMNT && strcmp(xml_name(x),xml_name(xnext))==0) eqnext++; if (xprev && xml_type(xprev)==CX_ELMNT && strcmp(xml_name(x),xml_name(xprev))==0) eqprev++; if (eqprev && eqnext) array = MIDDLE_ARRAY; else if (eqprev) array = LAST_ARRAY; else if (eqnext) array = FIRST_ARRAY; else array = NO_ARRAY; done: return array; } char * json_escape(char *str) { int i, j; char *snew; j = 0; for (i=0;i |1 | | | | | | | | json: |\ta:null |\ta: |\ta:{\n | | | | |\n} | +---------+--------------+--------------+--------------+ |first |11 |..a>1 | | | | | | | | json: |\tnull |\t |\t{a | | |\n\t] |\n\t] |\n\t}\t] | +---------+--------------+--------------+--------------+ */ static int xml2json1_cbuf(cbuf *cb, cxobj *x, enum array_element_type arraytype, int level, int pretty, int flat) { int retval = -1; int i; cxobj *xc; enum childtype childt; childt = childtype(x); if (pretty==2) cprintf(cb, "#%s_array, %s_child ", arraytype2str(arraytype), childtype2str(childt)); switch(arraytype){ case BODY_ARRAY:{ char *str; if ((str = json_escape(xml_value(x))) == NULL) goto done; cprintf(cb, "\"%s\"", str); free(str); break; } case NO_ARRAY: if (!flat) cprintf(cb, "%*s\"%s\": ", pretty?(level*JSON_INDENT):0, "", xml_name(x)); switch (childt){ case NULL_CHILD: cprintf(cb, "null"); break; case BODY_CHILD: break; case ANY_CHILD: cprintf(cb, "{%s", pretty?"\n":""); break; default: break; } break; case FIRST_ARRAY: cprintf(cb, "%*s\"%s\": ", pretty?(level*JSON_INDENT):0, "", xml_name(x)); level++; cprintf(cb, "[%s%*s", pretty?"\n":"", pretty?(level*JSON_INDENT):0, ""); switch (childt){ case NULL_CHILD: cprintf(cb, "null"); break; case BODY_CHILD: break; case ANY_CHILD: cprintf(cb, "{%s", pretty?"\n":""); break; default: break; } break; case MIDDLE_ARRAY: case LAST_ARRAY: level++; cprintf(cb, "%*s", pretty?(level*JSON_INDENT):0, ""); switch (childt){ case NULL_CHILD: cprintf(cb, "null"); break; case BODY_CHILD: break; case ANY_CHILD: cprintf(cb, "{ %s", pretty?"\n":""); break; default: break; } break; default: break; } for (i=0; i --> --> {"b" : null,"c" : null} * @see xml2json1_cbuf */ int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty) { int retval = -1; int level = 0; int i; cxobj *xp = NULL; cxobj *xc; if ((xp = xml_new("", NULL)) == NULL) goto done; for (i=0; i --> --> {"b" : null,"c" : null} * @see xml2json1_cbuf */ int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty) { int retval = 1; cbuf *cb = NULL; if ((cb = cbuf_new()) ==NULL){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (xml2json_cbuf_vec(cb, vec, veclen, pretty) < 0) goto done; fprintf(f, "%s", cbuf_get(cb)); retval = 0; done: if (cb) cbuf_free(cb); return retval; } /*! Parse a string containing JSON and return an XML tree * @param[in] str Input string containing JSON * @param[in] name Log string, typically filename * @param[out] xt XML top of tree typically w/o children on entry (but created) */ static int json_parse(char *str, const char *name, cxobj *xt) { int retval = -1; struct clicon_json_yacc_arg jy = {0,}; // clicon_debug(1, "%s", __FUNCTION__); jy.jy_parse_string = str; jy.jy_name = name; jy.jy_linenum = 1; jy.jy_current = xt; if (json_scan_init(&jy) < 0) goto done; if (json_parse_init(&jy) < 0) goto done; if (clixon_json_parseparse(&jy) != 0) { /* yacc returns 1 on error */ clicon_log(LOG_NOTICE, "JSON error: %s on line %d", name, jy.jy_linenum); if (clicon_errno == 0) clicon_err(OE_XML, 0, "JSON parser error with no error code (should not happen)"); goto done; } retval = 0; done: // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); json_parse_exit(&jy); json_scan_exit(&jy); return retval; } /*! Parse string containing JSON and return an XML tree * * @param[in] str String containing JSON * @param[out] xt On success a top of XML parse tree is created with name 'top' * @retval 0 OK * @retval -1 Error with clicon_err called * * @code * cxobj *cx = NULL; * if (json_parse_str(str, &cx) < 0) * err; * xml_free(cx); * @endcode * @note you need to free the xml parse tree after use, using xml_free() */ int json_parse_str(char *str, cxobj **xt) { if ((*xt = xml_new("top", NULL)) == NULL) return -1; return json_parse(str, "", *xt); }