diff --git a/src/json/print.cpp b/src/json/print.cpp index f747cdf..b77760d 100644 --- a/src/json/print.cpp +++ b/src/json/print.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include + #define epicsExportSharedSymbols #include #include @@ -16,28 +19,84 @@ namespace pvd = epics::pvData; namespace { +using namespace pvd::yajl; + +void yg(yajl_gen_status sts) { + const char *msg = "<\?\?\?>"; + switch(sts) { + case yajl_gen_status_ok: + case yajl_gen_generation_complete: + return; +#define CASE(STS) case STS: msg = #STS; break + CASE(yajl_gen_keys_must_be_strings); + CASE(yajl_gen_in_error_state); + CASE(yajl_gen_no_buf); + CASE(yajl_gen_invalid_number); + CASE(yajl_max_depth_exceeded); +#ifdef EPICS_YAJL_VERSION + CASE(yajl_gen_invalid_string); +#endif +#undef CASE + } + throw std::runtime_error(msg); +} + +static +void stream_printer(void * ctx, + const char * str, + size_arg len) +{ + std::ostream *strm = (std::ostream*)ctx; + strm->write(str, len); +} struct args { - std::ostream& strm; + yajl_gen handle; const pvd::JSONPrintOptions& opts; - unsigned indent; + std::string indent; args(std::ostream& strm, const pvd::JSONPrintOptions& opts) - :strm(strm) - ,opts(opts) - ,indent(opts.indent) - {} + :opts(opts) + ,indent(opts.indent, ' ') + { +#ifndef EPICS_YAJL_VERSION + yajl_gen_config conf; + conf.beautify = opts.multiLine; + conf.indentString = indent.c_str(); + if(!(handle = yajl_gen_alloc2(stream_printer, NULL, NULL, &strm))) + throw std::bad_alloc(); - void doIntent() { - if(!opts.multiLine) return; - strm.put('\n'); - unsigned i=indent; - while(i--) strm.put(' '); + if(opts.json5) { + static bool warned; + if(!warned) { + warned = true; + errlogPrintf("Warning: Ignoring request to print JSON5. Update Base >= 7.0.5"); + } + } +#else + if(!(handle = yajl_gen_alloc(NULL))) + throw std::bad_alloc(); + if(opts.multiLine) { + yajl_gen_config(handle, yajl_gen_beautify, 1); + yajl_gen_config(handle, yajl_gen_indent_string, indent.c_str()); + } else { + yajl_gen_config(handle, yajl_gen_beautify, 0); + } + yajl_gen_config(handle, yajl_gen_json5, (int)opts.json5); + yajl_gen_config(handle, yajl_gen_print_callback, stream_printer, &strm); +#endif + } + ~args() { + yajl_gen_free(handle); } }; +void yg_string(yajl_gen handle, const std::string& s) { + yg(yajl_gen_string(handle, (const unsigned char*)s.c_str(), s.size())); +} + void show_field(args& A, const pvd::PVField* fld, const pvd::BitSet *mask); void show_struct(args& A, const pvd::PVStructure* fld, const pvd::BitSet *mask) @@ -47,26 +106,17 @@ void show_struct(args& A, const pvd::PVStructure* fld, const pvd::BitSet *mask) const pvd::StringArray& names = type->getFieldNames(); - A.strm.put('{'); - A.indent++; + yg(yajl_gen_map_open(A.handle)); - bool first = true; for(size_t i=0, N=names.size(); iget(children[i]->getFieldOffset())) continue; - if(first) - first = false; - else - A.strm.put(','); - A.doIntent(); - A.strm<<'\"'<(fld); - if(scalar->getScalar()->getScalarType()==pvd::pvString) { - A.strm<<'\"'<getAs()<<'\"'; - } else { - A.strm<getAs(); + switch(scalar->getScalar()->getScalarType()) { + case pvd::pvString: yg_string(A.handle, scalar->getAs()); break; + case pvd::pvBoolean: yg(yajl_gen_bool(A.handle, scalar->getAs())); break; + case pvd::pvDouble: + case pvd::pvFloat: yg(yajl_gen_double(A.handle, scalar->getAs())); break; + // case pvd::pvULong: // can't always be exactly represented... + default: + yg(yajl_gen_integer(A.handle, scalar->getAs())); break; } } return; case pvd::scalarArray: { const pvd::PVScalarArray *scalar=static_cast(fld); - const bool isstring = scalar->getScalarArray()->getElementType()==pvd::pvString; pvd::shared_vector arr; scalar->getAs(arr); - pvd::shared_vector sarr(pvd::shared_vector_convert(arr)); + yg(yajl_gen_array_open(A.handle)); - A.strm.put('['); - for(size_t i=0, N=sarr.size(); i sarr(pvd::shared_vector_convert(arr)); + for(size_t i=0, N=sarr.size(); i sarr(pvd::shared_vector_convert(arr)); + for(size_t i=0, N=sarr.size(); i sarr(pvd::shared_vector_convert(arr)); + for(size_t i=0, N=sarr.size(); i sarr(pvd::shared_vector_convert(arr)); + for(size_t i=0, N=sarr.size(); i(fld)->view()); - A.strm.put('['); - A.indent++; + yg(yajl_gen_array_open(A.handle)); for(size_t i=0, N=arr.size(); iget()); if(!C) { - A.strm<<"null"; + yg(yajl_gen_null(A.handle)); } else { show_field(A, C.get(), 0); } @@ -145,29 +214,23 @@ void show_field(args& A, const pvd::PVField* fld, const pvd::BitSet *mask) case pvd::unionArray: { const pvd::PVUnionArray *U=static_cast(fld); pvd::PVUnionArray::const_svector arr(U->view()); - A.strm.put('['); - A.indent++; + + yg(yajl_gen_array_open(A.handle)); for(size_t i=0, N=arr.size(); i(val, "any", "4.2"); - testFieldEqual(val, "almost", "hello"); + testFieldEqual(val, "almost", "long string /with\\ several \" and ' characters\x1f\xe2\x9c\x85"); } void testroundtrip() @@ -255,19 +255,19 @@ void testroundtrip() round2 = strm.str(); } - testEqual(round2, "{\"scalar\": 42," - "\"ivec\": [1,2,3]," - "\"svec\": [\"one\",\"two\"]," - "\"sub\": {" - "\"x\": {" - "\"y\": 43" + testEqual(round2, "{\"scalar\":42," + "\"ivec\":[1,2,3]," + "\"svec\":[\"one\",\"two\"]," + "\"sub\":{" + "\"x\":{" + "\"y\":43" "}}," - "\"extra\": 0," - "\"sarr\": [{\"a\": 5,\"b\": 6}," - "{\"a\": 7,\"b\": 8}," - "{\"a\": 9,\"b\": 10}]," - "\"any\": \"4.2\"," - "\"almost\": \"hello\"" + "\"extra\":0," + "\"sarr\":[{\"a\":5,\"b\":6}," + "{\"a\":7,\"b\":8}," + "{\"a\":9,\"b\":10}]," + "\"any\":\"4.2\"," + "\"almost\":\"long string /with\\\\ several \\\" and ' characters\\u001F\xe2\x9c\x85\"" "}"); }