json print/parse from/to PVStructure
This commit is contained in:
@@ -767,6 +767,7 @@ INPUT = ../src/pv \
|
||||
./mainpage.h \
|
||||
../src/copy/pv \
|
||||
../src/misc/pv \
|
||||
../src/json/pv \
|
||||
./release_notes.h
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
|
||||
@@ -11,6 +11,7 @@ include $(PVDATA_SRC)/factory/Makefile
|
||||
include $(PVDATA_SRC)/property/Makefile
|
||||
include $(PVDATA_SRC)/copy/Makefile
|
||||
include $(PVDATA_SRC)/pvMisc/Makefile
|
||||
include $(PVDATA_SRC)/json/Makefile
|
||||
|
||||
LIBRARY = pvData
|
||||
|
||||
|
||||
10
src/json/Makefile
Normal file
10
src/json/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
# This is a Makefile fragment, see ../Makefile
|
||||
|
||||
SRC_DIRS += $(PVDATA_SRC)/json
|
||||
|
||||
INC += pv/json.h
|
||||
|
||||
LIBSRCS += parsehelper.cpp
|
||||
LIBSRCS += parseany.cpp
|
||||
LIBSRCS += parseinto.cpp
|
||||
LIBSRCS += print.cpp
|
||||
276
src/json/parseany.cpp
Normal file
276
src/json/parseany.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright information and license terms for this software can be
|
||||
* found in the file LICENSE that is included with the distribution
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <pv/pvdVersion.h>
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/valueBuilder.h>
|
||||
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
||||
|
||||
#include <yajl_parse.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "pv/json.h"
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
|
||||
namespace {
|
||||
|
||||
struct context {
|
||||
|
||||
unsigned depth;
|
||||
|
||||
enum state_t {
|
||||
Undefined,
|
||||
Key,
|
||||
Array,
|
||||
} state;
|
||||
|
||||
pvd::shared_vector<void> arr;
|
||||
|
||||
pvd::ValueBuilder root,
|
||||
*cur;
|
||||
|
||||
std::string msg,
|
||||
key;
|
||||
|
||||
context() :depth(0u), state(Undefined), cur(&root) {}
|
||||
};
|
||||
|
||||
#define TRY context *self = (context*)ctx; try
|
||||
|
||||
#define CATCH() catch(std::exception& e) { self->msg = e.what(); return 0; }
|
||||
|
||||
int jtree_null(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
self->msg = "NULL value not permitted";
|
||||
return 0;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_boolean(void * ctx, int boolVal)
|
||||
{
|
||||
TRY {
|
||||
if(self->depth==0) throw std::runtime_error("Bare value not supported");
|
||||
switch(self->state) {
|
||||
case context::Key:
|
||||
self->cur = &self->cur->add<pvd::pvBoolean>(self->key, boolVal);
|
||||
self->key.clear();
|
||||
self->state = context::Undefined;
|
||||
break;
|
||||
case context::Array:
|
||||
{
|
||||
if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvBoolean)
|
||||
throw std::runtime_error("Mixed type array not supported");
|
||||
pvd::shared_vector<pvd::boolean> arr(pvd::static_shared_vector_cast<pvd::boolean>(self->arr));
|
||||
arr.push_back(boolVal);
|
||||
self->arr = pvd::static_shared_vector_cast<void>(arr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::logic_error("boolean in bad state");
|
||||
}
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_integer(void * ctx, long integerVal)
|
||||
{
|
||||
TRY {
|
||||
if(self->depth==0) throw std::runtime_error("Bare value not supported");
|
||||
switch(self->state) {
|
||||
case context::Key:
|
||||
self->cur = &self->cur->add<pvd::pvLong>(self->key, integerVal);
|
||||
self->key.clear();
|
||||
self->state = context::Undefined;
|
||||
break;
|
||||
case context::Array:
|
||||
{
|
||||
if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvLong)
|
||||
throw std::runtime_error("Mixed type array not supported");
|
||||
pvd::shared_vector<pvd::int64> arr(pvd::static_shared_vector_cast<pvd::int64>(self->arr));
|
||||
arr.push_back(integerVal);
|
||||
self->arr = pvd::static_shared_vector_cast<void>(arr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::logic_error("int64 in bad state");
|
||||
}
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_double(void * ctx, double doubleVal)
|
||||
{
|
||||
TRY {
|
||||
if(self->depth==0) throw std::runtime_error("Bare value not supported");
|
||||
switch(self->state) {
|
||||
case context::Key:
|
||||
self->cur = &self->cur->add<pvd::pvDouble>(self->key, doubleVal);
|
||||
self->key.clear();
|
||||
self->state = context::Undefined;
|
||||
break;
|
||||
case context::Array:
|
||||
{
|
||||
if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvDouble)
|
||||
throw std::runtime_error("Mixed type array not supported");
|
||||
pvd::shared_vector<double> arr(pvd::static_shared_vector_cast<double>(self->arr));
|
||||
arr.push_back(doubleVal);
|
||||
self->arr = pvd::static_shared_vector_cast<void>(arr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::logic_error("double in bad state");
|
||||
}
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_string(void * ctx, const unsigned char * stringVal,
|
||||
unsigned int stringLen)
|
||||
{
|
||||
TRY {
|
||||
if(self->depth==0) throw std::runtime_error("Bare value not supported");
|
||||
std::string sval((const char*)stringVal, stringLen);
|
||||
switch(self->state) {
|
||||
case context::Key:
|
||||
self->cur = &self->cur->add<pvd::pvString>(self->key, sval);
|
||||
self->key.clear();
|
||||
self->state = context::Undefined;
|
||||
break;
|
||||
case context::Array:
|
||||
{
|
||||
if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvString)
|
||||
throw std::runtime_error("Mixed type array not supported");
|
||||
pvd::shared_vector<std::string> arr(pvd::static_shared_vector_cast<std::string>(self->arr));
|
||||
arr.push_back(sval);
|
||||
self->arr = pvd::static_shared_vector_cast<void>(arr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::logic_error("double in bad state");
|
||||
}
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_start_map(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
if(self->depth>0) {
|
||||
if(self->key.empty())
|
||||
throw std::logic_error("anonymous dict not top level?");
|
||||
self->cur = &self->cur->addNested(self->key);
|
||||
self->key.clear();
|
||||
}
|
||||
self->depth++;
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_map_key(void * ctx, const unsigned char * key,
|
||||
unsigned int stringLen)
|
||||
{
|
||||
TRY {
|
||||
if(!self->key.empty())
|
||||
throw std::logic_error("double key?");
|
||||
if(stringLen==0)
|
||||
throw std::runtime_error("empty key not allowed");
|
||||
self->key = std::string((const char*)key, stringLen);
|
||||
self->state = context::Key;
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_end_map(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
if(self->depth>1)
|
||||
self->cur = &self->cur->endNested();
|
||||
else if(self->depth==0)
|
||||
throw std::logic_error("Unbalenced dict");
|
||||
self->depth--;
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_start_array(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
if(self->depth==0) throw std::runtime_error("Bare array not supported");
|
||||
if(self->state!=context::Key)
|
||||
throw std::logic_error("bare array not supported");
|
||||
self->state = context::Array;
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
int jtree_end_array(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
if(self->state!=context::Array)
|
||||
throw std::logic_error("Bad array parse");
|
||||
self->cur = &self->cur->add(self->key, pvd::freeze(self->arr));
|
||||
self->key.clear();
|
||||
self->state = context::Undefined;
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
|
||||
yajl_callbacks jtree_cbs = {
|
||||
&jtree_null,
|
||||
&jtree_boolean,
|
||||
&jtree_integer,
|
||||
&jtree_double,
|
||||
NULL, // number
|
||||
&jtree_string,
|
||||
&jtree_start_map,
|
||||
&jtree_map_key,
|
||||
&jtree_end_map,
|
||||
&jtree_start_array,
|
||||
&jtree_end_array,
|
||||
};
|
||||
|
||||
struct handler {
|
||||
yajl_handle handle;
|
||||
handler(yajl_handle handle) :handle(handle)
|
||||
{
|
||||
if(!handle)
|
||||
throw std::runtime_error("Failed to allocate yajl handle");
|
||||
}
|
||||
~handler() {
|
||||
yajl_free(handle);
|
||||
}
|
||||
operator yajl_handle() { return handle; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace epics{namespace pvData{
|
||||
|
||||
epics::pvData::PVStructure::shared_pointer
|
||||
parseJSON(std::istream& strm)
|
||||
{
|
||||
yajl_parser_config conf = {
|
||||
.allowComments = 1,
|
||||
.checkUTF8 = 1,
|
||||
};
|
||||
|
||||
context ctxt;
|
||||
|
||||
handler handle(yajl_alloc(&jtree_cbs, &conf, NULL, &ctxt));
|
||||
|
||||
if(!yajl_parse_helper(strm, handle, conf))
|
||||
throw std::runtime_error(ctxt.msg);
|
||||
|
||||
return ctxt.cur->buildPVStructure();
|
||||
}
|
||||
|
||||
}} // namespace epics::pvData
|
||||
|
||||
#endif // EPICS_VERSION_INT
|
||||
104
src/json/parsehelper.cpp
Normal file
104
src/json/parsehelper.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright information and license terms for this software can be
|
||||
* found in the file LICENSE that is included with the distribution
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
|
||||
#include <pv/pvdVersion.h>
|
||||
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "pv/json.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void check_trailing(const std::string& line, bool commentok)
|
||||
{
|
||||
size_t idx = line.find_first_not_of(" \t\n\r");
|
||||
if(idx==line.npos) return;
|
||||
// TODO: detect the end of potentially multi-line comments...
|
||||
// for now trailing comments not allowed
|
||||
throw std::runtime_error("Trailing junk");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace epics{namespace pvData{
|
||||
|
||||
bool yajl_parse_helper(std::istream& src,
|
||||
yajl_handle handle,
|
||||
const yajl_parser_config& config)
|
||||
{
|
||||
unsigned linenum=0;
|
||||
bool done = false;
|
||||
|
||||
std::string line;
|
||||
while(std::getline(src, line)) {
|
||||
linenum++;
|
||||
|
||||
if(done) {
|
||||
check_trailing(line, config.allowComments);
|
||||
continue;
|
||||
}
|
||||
|
||||
yajl_status sts = yajl_parse(handle, (const unsigned char*)line.c_str(), line.size());
|
||||
|
||||
switch(sts) {
|
||||
case yajl_status_ok: {
|
||||
size_t consumed = yajl_get_bytes_consumed(handle);
|
||||
if(consumed<line.size()) {
|
||||
check_trailing(line.substr(consumed), config.allowComments);
|
||||
}
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
case yajl_status_client_canceled:
|
||||
return false;
|
||||
case yajl_status_insufficient_data:
|
||||
// continue with next line
|
||||
break;
|
||||
case yajl_status_error:
|
||||
{
|
||||
std::ostringstream msg;
|
||||
unsigned char *raw = yajl_get_error(handle, 1, (const unsigned char*)line.c_str(), line.size());
|
||||
if(!raw) {
|
||||
msg<<"Unknown error on line "<<linenum;
|
||||
} else {
|
||||
try {
|
||||
msg<<"Error on line "<<linenum<<" : "<<(const char*)raw;
|
||||
}catch(...){
|
||||
yajl_free_error(handle, raw);
|
||||
throw;
|
||||
}
|
||||
yajl_free_error(handle, raw);
|
||||
}
|
||||
throw std::runtime_error(msg.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!src.eof() || src.bad()) {
|
||||
std::ostringstream msg;
|
||||
msg<<"I/O error after line "<<linenum;
|
||||
throw std::runtime_error(msg.str());
|
||||
|
||||
} else if(!done) switch(yajl_parse_complete(handle)) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
return false;
|
||||
case yajl_status_insufficient_data:
|
||||
throw std::runtime_error("unexpected end of input");
|
||||
case yajl_status_error:
|
||||
throw std::runtime_error("Error while completing parsing");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}} // namespace epics::pvData
|
||||
|
||||
#endif // EPICS_VERSION_INT
|
||||
260
src/json/parseinto.cpp
Normal file
260
src/json/parseinto.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright information and license terms for this software can be
|
||||
* found in the file LICENSE that is included with the distribution
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include <pv/pvdVersion.h>
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/valueBuilder.h>
|
||||
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
||||
|
||||
#include <yajl_parse.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "pv/json.h"
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
|
||||
namespace {
|
||||
struct context {
|
||||
|
||||
std::string msg;
|
||||
|
||||
typedef std::vector<pvd::PVFieldPtr> stack_t;
|
||||
stack_t stack;
|
||||
|
||||
context(const pvd::PVFieldPtr& root)
|
||||
{
|
||||
stack.push_back(root);
|
||||
}
|
||||
};
|
||||
|
||||
#define TRY context *self = (context*)ctx; assert(!self->stack.empty()); try
|
||||
|
||||
#define CATCH() catch(std::exception& e) { self->msg = e.what(); return 0; }
|
||||
|
||||
int jtree_null(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
self->msg = "NULL value not permitted";
|
||||
return 0;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
template<typename PVD>
|
||||
void valueAssign(context *self, typename PVD::value_type val)
|
||||
{
|
||||
pvd::Type type(self->stack.back()->getField()->getType());
|
||||
if(type==pvd::scalar) {
|
||||
pvd::PVScalar* fld(static_cast<pvd::PVScalar*>(self->stack.back().get()));
|
||||
if(!fld)
|
||||
throw std::invalid_argument("Not a scalar field");
|
||||
fld->putFrom(val);
|
||||
self->stack.pop_back();
|
||||
// structure back at the top of the stack
|
||||
|
||||
} else if(type==pvd::scalarArray) {
|
||||
pvd::PVScalarArray* fld(static_cast<pvd::PVScalarArray*>(self->stack.back().get()));
|
||||
|
||||
PVD* arrfld(dynamic_cast<PVD*>(fld));
|
||||
if(!arrfld)
|
||||
throw std::invalid_argument("wrong type for scalar array");
|
||||
|
||||
typename PVD::const_svector carr;
|
||||
arrfld->swap(carr);
|
||||
|
||||
typename PVD::svector arr(pvd::thaw(carr));
|
||||
|
||||
arr.push_back(val);
|
||||
|
||||
arrfld->replace(pvd::freeze(arr));
|
||||
|
||||
// leave array field at top of stack
|
||||
} else {
|
||||
throw std::invalid_argument("Can't assign value");
|
||||
}
|
||||
}
|
||||
|
||||
int jtree_boolean(void * ctx, int boolVal)
|
||||
{
|
||||
TRY {
|
||||
valueAssign<pvd::PVBooleanArray>(self, !!boolVal);
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_integer(void * ctx, long integerVal)
|
||||
{
|
||||
TRY {
|
||||
valueAssign<pvd::PVLongArray>(self, integerVal);
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_double(void * ctx, double doubleVal)
|
||||
{
|
||||
TRY {
|
||||
valueAssign<pvd::PVDoubleArray>(self, doubleVal);
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_string(void * ctx, const unsigned char * stringVal,
|
||||
unsigned int stringLen)
|
||||
{
|
||||
TRY {
|
||||
std::string val((const char*)stringVal, stringLen);
|
||||
valueAssign<pvd::PVStringArray>(self, val);
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_start_map(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
pvd::PVFieldPtr& back(self->stack.back());
|
||||
pvd::Type type = back->getField()->getType();
|
||||
if(type==pvd::structure) {
|
||||
// will fill in
|
||||
} else if(type==pvd::structureArray) {
|
||||
// starting new element in structure array
|
||||
pvd::PVStructureArrayPtr sarr(std::tr1::static_pointer_cast<pvd::PVStructureArray>(back));
|
||||
|
||||
pvd::PVStructurePtr elem(pvd::getPVDataCreate()->createPVStructure(sarr->getStructureArray()->getStructure()));
|
||||
|
||||
self->stack.push_back(elem);
|
||||
} else {
|
||||
throw std::runtime_error("Can't map (sub)structure");
|
||||
}
|
||||
|
||||
assert(self->stack.back()->getField()->getType()==pvd::structure);
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_map_key(void * ctx, const unsigned char * key,
|
||||
unsigned int stringLen)
|
||||
{
|
||||
TRY {
|
||||
std::string name((const char*)key, stringLen);
|
||||
|
||||
// start_map() ensures we have a structure at the top of the stack
|
||||
pvd::PVStructure *fld = static_cast<pvd::PVStructure*>(self->stack.back().get());
|
||||
|
||||
try {
|
||||
self->stack.push_back(fld->getSubFieldT(name));
|
||||
}catch(std::runtime_error& e){
|
||||
std::ostringstream strm;
|
||||
strm<<"At "<<fld->getFullName()<<" : "<<e.what()<<"\n";
|
||||
throw std::runtime_error(strm.str());
|
||||
}
|
||||
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_end_map(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
assert(self->stack.back()->getField()->getType()==pvd::structure);
|
||||
|
||||
pvd::PVStructurePtr elem(std::tr1::static_pointer_cast<pvd::PVStructure>(self->stack.back()));
|
||||
self->stack.pop_back();
|
||||
|
||||
if(!self->stack.empty() && self->stack.back()->getField()->getType()==pvd::structureArray) {
|
||||
// append element to struct array
|
||||
pvd::PVStructureArray *sarr = static_cast<pvd::PVStructureArray*>(self->stack.back().get());
|
||||
|
||||
pvd::PVStructureArray::const_svector cval;
|
||||
sarr->swap(cval);
|
||||
|
||||
pvd::PVStructureArray::svector val(pvd::thaw(cval));
|
||||
|
||||
val.push_back(elem);
|
||||
|
||||
sarr->replace(pvd::freeze(val));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
int jtree_start_array(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
pvd::PVFieldPtr& back(self->stack.back());
|
||||
pvd::Type type = back->getField()->getType();
|
||||
if(type!=pvd::structureArray && type!=pvd::scalarArray)
|
||||
throw std::runtime_error("Can't assign array");
|
||||
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
int jtree_end_array(void * ctx)
|
||||
{
|
||||
TRY {
|
||||
self->stack.pop_back();
|
||||
return 1;
|
||||
}CATCH()
|
||||
}
|
||||
|
||||
|
||||
yajl_callbacks jtree_cbs = {
|
||||
&jtree_null,
|
||||
&jtree_boolean,
|
||||
&jtree_integer,
|
||||
&jtree_double,
|
||||
NULL, // number
|
||||
&jtree_string,
|
||||
&jtree_start_map,
|
||||
&jtree_map_key,
|
||||
&jtree_end_map,
|
||||
&jtree_start_array,
|
||||
&jtree_end_array,
|
||||
};
|
||||
|
||||
struct handler {
|
||||
yajl_handle handle;
|
||||
handler(yajl_handle handle) :handle(handle)
|
||||
{
|
||||
if(!handle)
|
||||
throw std::runtime_error("Failed to allocate yajl handle");
|
||||
}
|
||||
~handler() {
|
||||
yajl_free(handle);
|
||||
}
|
||||
operator yajl_handle() { return handle; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace epics{namespace pvData{
|
||||
|
||||
epicsShareFunc
|
||||
void parseJSON(std::istream& strm,
|
||||
const PVField::shared_pointer& dest)
|
||||
{
|
||||
yajl_parser_config conf = {
|
||||
.allowComments = 1,
|
||||
.checkUTF8 = 1,
|
||||
};
|
||||
|
||||
context ctxt(dest);
|
||||
|
||||
handler handle(yajl_alloc(&jtree_cbs, &conf, NULL, &ctxt));
|
||||
|
||||
if(!yajl_parse_helper(strm, handle, conf))
|
||||
throw std::runtime_error(ctxt.msg);
|
||||
|
||||
if(!ctxt.stack.empty())
|
||||
throw std::logic_error("field stack not empty");
|
||||
}
|
||||
|
||||
}} // namespace epics::pvData
|
||||
|
||||
#endif // EPICS_VERSION_INT
|
||||
|
||||
160
src/json/print.cpp
Normal file
160
src/json/print.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright information and license terms for this software can be
|
||||
* found in the file LICENSE that is included with the distribution
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include <pv/pvdVersion.h>
|
||||
#include <pv/pvData.h>
|
||||
#include <pv/valueBuilder.h>
|
||||
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
||||
|
||||
#include <yajl_parse.h>
|
||||
|
||||
#define epicsExportSharedSymbols
|
||||
#include "pv/json.h"
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
|
||||
namespace {
|
||||
|
||||
struct args {
|
||||
std::ostream& strm;
|
||||
const pvd::JSONPrintOptions& opts;
|
||||
|
||||
unsigned indent;
|
||||
|
||||
args(std::ostream& strm,
|
||||
const pvd::JSONPrintOptions& opts)
|
||||
:strm(strm)
|
||||
,opts(opts)
|
||||
,indent(opts.indent)
|
||||
{}
|
||||
|
||||
void doIntent() {
|
||||
if(!opts.multiLine) return;
|
||||
strm.put('\n');
|
||||
unsigned i=indent;
|
||||
while(i--) strm.put(' ');
|
||||
}
|
||||
};
|
||||
|
||||
void show_field(args& A, const pvd::PVField* fld);
|
||||
|
||||
void show_struct(args& A, const pvd::PVStructure* fld)
|
||||
{
|
||||
const pvd::StructureConstPtr& type = fld->getStructure();
|
||||
const pvd::PVFieldPtrArray& children = fld->getPVFields();
|
||||
|
||||
const pvd::StringArray& names = type->getFieldNames();
|
||||
|
||||
A.strm.put('{');
|
||||
A.indent++;
|
||||
|
||||
for(size_t i=0, N=names.size(); i<N; i++)
|
||||
{
|
||||
if(i!=0)
|
||||
A.strm.put(',');
|
||||
A.doIntent();
|
||||
A.strm<<'\"'<<names[i]<<"\": ";
|
||||
show_field(A, children[i].get());
|
||||
}
|
||||
|
||||
A.indent--;
|
||||
A.doIntent();
|
||||
A.strm.put('}');
|
||||
}
|
||||
|
||||
void show_field(args& A, const pvd::PVField* fld)
|
||||
{
|
||||
switch(fld->getField()->getType())
|
||||
{
|
||||
case pvd::scalar:
|
||||
{
|
||||
const pvd::PVScalar *scalar=static_cast<const pvd::PVScalar*>(fld);
|
||||
if(scalar->getScalar()->getScalarType()==pvd::pvString) {
|
||||
A.strm<<'\"'<<scalar->getAs<std::string>()<<'\"';
|
||||
} else {
|
||||
A.strm<<scalar->getAs<std::string>();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case pvd::scalarArray:
|
||||
{
|
||||
const pvd::PVScalarArray *scalar=static_cast<const pvd::PVScalarArray*>(fld);
|
||||
const bool isstring = scalar->getScalarArray()->getElementType()==pvd::pvString;
|
||||
|
||||
pvd::shared_vector<const void> arr;
|
||||
scalar->getAs<void>(arr);
|
||||
|
||||
pvd::shared_vector<const std::string> sarr(pvd::shared_vector_convert<const std::string>(arr));
|
||||
|
||||
A.strm.put('[');
|
||||
for(size_t i=0, N=sarr.size(); i<N; i++) {
|
||||
if(i!=0)
|
||||
A.strm.put(',');
|
||||
if(isstring)
|
||||
A.strm.put('\"');
|
||||
A.strm<<sarr[i];
|
||||
if(isstring)
|
||||
A.strm.put('\"');
|
||||
}
|
||||
A.strm.put(']');
|
||||
}
|
||||
break;
|
||||
case pvd::structure:
|
||||
show_struct(A, static_cast<const pvd::PVStructure*>(fld));
|
||||
break;
|
||||
case pvd::structureArray:
|
||||
{
|
||||
pvd::PVStructureArray::const_svector arr(static_cast<const pvd::PVStructureArray*>(fld)->view());
|
||||
A.strm.put('[');
|
||||
A.indent++;
|
||||
|
||||
for(size_t i=0, N=arr.size(); i<N; i++) {
|
||||
if(i!=0)
|
||||
A.strm.put(',');
|
||||
A.doIntent();
|
||||
if(arr[i])
|
||||
show_struct(A, arr[i].get());
|
||||
else
|
||||
A.strm<<"NULL";
|
||||
}
|
||||
|
||||
A.indent--;
|
||||
A.doIntent();
|
||||
A.strm.put(']');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if(A.opts.ignoreUnprintable)
|
||||
A.strm<<"// unprintable field type";
|
||||
else
|
||||
throw std::runtime_error("Encountered unprintable field type");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace epics{namespace pvData{
|
||||
|
||||
JSONPrintOptions::JSONPrintOptions()
|
||||
:multiLine(true)
|
||||
,ignoreUnprintable(true)
|
||||
,indent(0)
|
||||
{}
|
||||
|
||||
void printJSON(std::ostream& strm,
|
||||
const PVField::const_shared_pointer& val,
|
||||
const JSONPrintOptions& opts)
|
||||
{
|
||||
args A(strm, opts);
|
||||
show_field(A, val.get());
|
||||
}
|
||||
|
||||
}} // namespace epics::pvData
|
||||
|
||||
#endif // EPICS_VERSION_INT
|
||||
107
src/json/pv/json.h
Normal file
107
src/json/pv/json.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright information and license terms for this software can be
|
||||
* found in the file LICENSE that is included with the distribution
|
||||
*/
|
||||
#ifndef PV_JSON_H
|
||||
#define PV_JSON_H
|
||||
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include <pv/pvdVersion.h>
|
||||
#include <pv/pvData.h>
|
||||
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
||||
|
||||
#ifdef epicsExportSharedSymbols
|
||||
# define pvjson_epicsExportSharedSymbols
|
||||
# undef epicsExportSharedSymbols
|
||||
#endif
|
||||
|
||||
#include <yajl_parse.h>
|
||||
|
||||
#ifdef pvjson_epicsExportSharedSymbols
|
||||
# define epicsExportSharedSymbols
|
||||
# include "shareLib.h"
|
||||
#endif
|
||||
|
||||
#include <shareLib.h>
|
||||
|
||||
namespace epics{namespace pvData{
|
||||
|
||||
/** @defgroup pvjson JSON print/parse
|
||||
*
|
||||
* Printing PVField as JSON and parsing JSON into PVField.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
//! Options used during printing
|
||||
struct epicsShareClass JSONPrintOptions
|
||||
{
|
||||
bool multiLine; //!< include new lines
|
||||
bool ignoreUnprintable;//!< ignore union/union array when encountered
|
||||
unsigned indent; //!< Initial indentation (# of spaces)
|
||||
JSONPrintOptions();
|
||||
};
|
||||
|
||||
/** Print PVStructure as JSON
|
||||
*
|
||||
* Restrictions:
|
||||
*
|
||||
* - No support for union or array of union
|
||||
*/
|
||||
epicsShareFunc
|
||||
void printJSON(std::ostream& strm,
|
||||
const PVField::const_shared_pointer& val,
|
||||
const JSONPrintOptions& opts = JSONPrintOptions());
|
||||
|
||||
/** Parse JSON text into a PVStructure
|
||||
*
|
||||
* Restrictions:
|
||||
*
|
||||
* - Top level must be {} dict/object
|
||||
* - field values must be number, string, array, or dict/object
|
||||
* - array values must be number or string
|
||||
*/
|
||||
epicsShareFunc
|
||||
PVStructure::shared_pointer parseJSON(std::istream& strm);
|
||||
|
||||
/** Parse JSON and store into the provided PVStructure.
|
||||
*
|
||||
* Restrictions:
|
||||
*
|
||||
* - Union or array of union not permitted
|
||||
*/
|
||||
epicsShareFunc
|
||||
void parseJSON(std::istream& strm,
|
||||
const PVField::shared_pointer& dest);
|
||||
|
||||
|
||||
/** Wrapper around yajl_parse()
|
||||
*
|
||||
* Parse entire input stream.
|
||||
* Errors if extranious non-whitespace found after the point were parsing completes.
|
||||
*
|
||||
* @param src The stream from which input charactors are read
|
||||
* @param handle A parser handle previously allocated with yajl_alloc(). Not free'd on success or failure.
|
||||
* @param config The same configuration passed to yajl_alloc(). Used to decide if trailing comments are allowed
|
||||
*
|
||||
* @returns true if parsing completes successfully. false if parsing cancelled by callback. throws other errors
|
||||
*/
|
||||
epicsShareFunc
|
||||
bool yajl_parse_helper(std::istream& src,
|
||||
yajl_handle handle,
|
||||
const yajl_parser_config& config);
|
||||
|
||||
/** @} */
|
||||
|
||||
}} // namespace epics::pvData
|
||||
|
||||
#else
|
||||
# error JSON parser requires EPICS Base >= 3.15.0.1
|
||||
#endif // EPICS_VERSION_INT
|
||||
|
||||
#endif // PV_JSON_H
|
||||
@@ -113,6 +113,8 @@ inline testPassx testEqualx(const char *nLHS, const char *nRHS, LHS l, RHS r)
|
||||
*/
|
||||
#define testTrue(B) ::detail::testPassx(!!(B))<<#B
|
||||
|
||||
#define testThrows(EXC, CODE) try{ CODE; testFail("unexpected success of " #CODE); }catch(EXC& e){testPass("catch expected exception: %s", e.what());}
|
||||
|
||||
/** Compare value of PVStructure field
|
||||
*
|
||||
@code
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P))
|
||||
#endif
|
||||
|
||||
#ifndef EPICS_VERSION_INT
|
||||
# define EPICS_VERSION_INT VERSION_INT(EPICS_VERSION, EPICS_REVISION, EPICS_MODIFICATION, EPICS_PATCH_LEVEL)
|
||||
#endif
|
||||
|
||||
/* include generated headers with:
|
||||
* EPICS_PVD_MAJOR_VERSION
|
||||
* EPICS_PVD_MINOR_VERSION
|
||||
|
||||
@@ -72,3 +72,7 @@ TESTS += testTypeCast
|
||||
TESTPROD_HOST += testUnitTest
|
||||
testUnitTest_SRCS += testUnitTest.cpp
|
||||
TESTS += testUnitTest
|
||||
|
||||
TESTPROD_HOST += testjson
|
||||
testjson_SRCS += testjson.cpp
|
||||
TESTS += testjson
|
||||
|
||||
240
testApp/misc/testjson.cpp
Normal file
240
testApp/misc/testjson.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright information and license terms for this software can be
|
||||
* found in the file LICENSE that is included with the distribution
|
||||
*/
|
||||
|
||||
#include <testMain.h>
|
||||
|
||||
#include <pv/pvdVersion.h>
|
||||
|
||||
#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1)
|
||||
|
||||
#include <pv/json.h>
|
||||
#include <pv/valueBuilder.h>
|
||||
#include <pv/pvUnitTest.h>
|
||||
|
||||
namespace pvd = epics::pvData;
|
||||
|
||||
namespace {
|
||||
|
||||
void testparseany()
|
||||
{
|
||||
testDiag("testparseany()");
|
||||
std::istringstream strm("{\"hello\":42, \"one\":{\"two\":\"test\"}}");
|
||||
|
||||
pvd::PVStructurePtr val(pvd::parseJSON(strm));
|
||||
|
||||
std::cout<<val<<"\n";
|
||||
|
||||
testFieldEqual<pvd::PVLong>(val, "hello", 42);
|
||||
testFieldEqual<pvd::PVString>(val, "one.two", "test");
|
||||
}
|
||||
|
||||
void testparseanyarray()
|
||||
{
|
||||
testDiag("testparseanyarray()");
|
||||
std::istringstream strm("{\"hello\":[42, 43]}");
|
||||
|
||||
pvd::PVStructurePtr val(pvd::parseJSON(strm));
|
||||
|
||||
std::cout<<val<<"\n";
|
||||
|
||||
pvd::PVLongArray::svector arr(2);
|
||||
arr[0] = 42;
|
||||
arr[1] = 43;
|
||||
pvd::PVLongArray::const_svector sarr(pvd::freeze(arr));
|
||||
|
||||
testFieldEqual<pvd::PVLongArray>(val, "hello", sarr);
|
||||
}
|
||||
|
||||
void testparseanyjunk()
|
||||
{
|
||||
testDiag("testparseanyjunk()");
|
||||
|
||||
// things we can't store
|
||||
{
|
||||
testThrows(std::runtime_error, std::istringstream strm("4"); std::cout<<pvd::parseJSON(strm); );
|
||||
}
|
||||
{
|
||||
testThrows(std::runtime_error, std::istringstream strm("[]"); std::cout<<pvd::parseJSON(strm); );
|
||||
}
|
||||
// junk found after parsing completes
|
||||
{
|
||||
testThrows(std::runtime_error, std::istringstream strm("{} x"); std::cout<<pvd::parseJSON(strm) );
|
||||
}
|
||||
{
|
||||
testThrows(std::runtime_error, std::istringstream strm("{} /* y */"); std::cout<<pvd::parseJSON(strm) );
|
||||
}
|
||||
{
|
||||
testThrows(std::runtime_error, std::istringstream strm("{} /* y *"); std::cout<<pvd::parseJSON(strm) );
|
||||
}
|
||||
{
|
||||
testThrows(std::runtime_error, std::istringstream strm("{}\n\n{}"); std::cout<<pvd::parseJSON(strm) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char bigtest[] =
|
||||
"{\"scalar\":42,\n"
|
||||
" \"ivec\":[1,2,3], /*comment*/ \n"
|
||||
" \"svec\":[\"one\", \"two\"],\n"
|
||||
" \"sub\":{\"x\":{\"y\":43}},\n"
|
||||
" \"sarr\":[\n"
|
||||
" /* another comment */\n"
|
||||
" {\"a\":5, \"b\":6}\n"
|
||||
" ,{\"a\":7, \"b\":8}\n"
|
||||
" ,{\"a\":9, \"b\":10}\n"
|
||||
" ]\n"
|
||||
"}";
|
||||
|
||||
pvd::StructureConstPtr bigtype(pvd::getFieldCreate()->createFieldBuilder()
|
||||
->add("scalar", pvd::pvInt)
|
||||
->addArray("ivec", pvd::pvLong)
|
||||
->addArray("svec", pvd::pvString)
|
||||
->addNestedStructure("sub")
|
||||
->addNestedStructure("x")
|
||||
->add("y", pvd::pvInt)
|
||||
->endNested()
|
||||
->endNested()
|
||||
->addNestedStructureArray("sarr")
|
||||
->add("a", pvd::pvInt)
|
||||
->add("b", pvd::pvInt)
|
||||
->endNested()
|
||||
->createStructure()
|
||||
);
|
||||
|
||||
void testInto()
|
||||
{
|
||||
testDiag("testInto()");
|
||||
|
||||
pvd::PVStructurePtr val(pvd::getPVDataCreate()->createPVStructure(bigtype));
|
||||
|
||||
std::istringstream strm(bigtest);
|
||||
|
||||
std::cout<<val;
|
||||
|
||||
pvd::parseJSON(strm, val);
|
||||
|
||||
std::cout<<val;
|
||||
|
||||
testFieldEqual<pvd::PVInt>(val, "scalar", 42);
|
||||
testFieldEqual<pvd::PVInt>(val, "sub.x.y", 43);
|
||||
{
|
||||
pvd::PVLongArray::svector expect(3);
|
||||
expect[0] = 1;
|
||||
expect[1] = 2;
|
||||
expect[2] = 3;
|
||||
testFieldEqual<pvd::PVLongArray>(val, "ivec", pvd::freeze(expect));
|
||||
}
|
||||
{
|
||||
pvd::PVStringArray::svector expect(2);
|
||||
expect[0] = "one";
|
||||
expect[1] = "two";
|
||||
testFieldEqual<pvd::PVStringArray>(val, "svec", pvd::freeze(expect));
|
||||
}
|
||||
{
|
||||
pvd::PVStructureArrayPtr sarr(val->getSubFieldT<pvd::PVStructureArray>("sarr"));
|
||||
pvd::PVStructureArray::const_svector elems(sarr->view());
|
||||
testEqual(elems.size(), 3u);
|
||||
if(elems.size()<3)
|
||||
testAbort("Missing elements");
|
||||
testFieldEqual<pvd::PVInt>(elems[0], "a", 5);
|
||||
testFieldEqual<pvd::PVInt>(elems[0], "b", 6);
|
||||
testFieldEqual<pvd::PVInt>(elems[1], "a", 7);
|
||||
testFieldEqual<pvd::PVInt>(elems[1], "b", 8);
|
||||
testFieldEqual<pvd::PVInt>(elems[2], "a", 9);
|
||||
testFieldEqual<pvd::PVInt>(elems[2], "b", 10);
|
||||
}
|
||||
}
|
||||
|
||||
void testroundtrip()
|
||||
{
|
||||
testDiag("testroundtrip()");
|
||||
|
||||
testDiag("Parse expected");
|
||||
pvd::PVStructurePtr val(pvd::getPVDataCreate()->createPVStructure(bigtype));
|
||||
{
|
||||
std::istringstream strm(bigtest);
|
||||
pvd::parseJSON(strm, val);
|
||||
}
|
||||
std::cout<<val;
|
||||
|
||||
testDiag("re-print parsed value");
|
||||
std::string round1;
|
||||
{
|
||||
pvd::JSONPrintOptions opts;
|
||||
opts.ignoreUnprintable = true;
|
||||
opts.multiLine = true;
|
||||
|
||||
std::ostringstream strm;
|
||||
pvd::printJSON(strm, val, opts);
|
||||
|
||||
round1 = strm.str();
|
||||
}
|
||||
std::cout<<round1<<"\n";
|
||||
|
||||
testDiag("re-parse re-printed value");
|
||||
pvd::PVStructurePtr val2(pvd::getPVDataCreate()->createPVStructure(bigtype));
|
||||
{
|
||||
std::istringstream strm(round1);
|
||||
pvd::parseJSON(strm, val2);
|
||||
}
|
||||
|
||||
testEqual(*val, *val2);
|
||||
|
||||
testDiag("print value");
|
||||
std::string round2;
|
||||
{
|
||||
pvd::JSONPrintOptions opts;
|
||||
opts.ignoreUnprintable = true;
|
||||
opts.multiLine = false;
|
||||
|
||||
std::ostringstream strm;
|
||||
pvd::printJSON(strm, val, opts);
|
||||
|
||||
round2 = strm.str();
|
||||
}
|
||||
|
||||
testEqual(round2, "{\"scalar\": 42,"
|
||||
"\"ivec\": [1,2,3],"
|
||||
"\"svec\": [\"one\",\"two\"],"
|
||||
"\"sub\": {"
|
||||
"\"x\": {"
|
||||
"\"y\": 43"
|
||||
"}},"
|
||||
"\"sarr\": [{\"a\": 5,\"b\": 6},"
|
||||
"{\"a\": 7,\"b\": 8},"
|
||||
"{\"a\": 9,\"b\": 10}]"
|
||||
"}");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MAIN(testjson)
|
||||
{
|
||||
testPlan(22);
|
||||
try {
|
||||
testparseany();
|
||||
testparseanyarray();
|
||||
testparseanyjunk();
|
||||
testInto();
|
||||
testroundtrip();
|
||||
}catch(std::exception& e){
|
||||
testAbort("Unexpected exception: %s", e.what());
|
||||
}
|
||||
return testDone();
|
||||
}
|
||||
|
||||
#else // EPICS_VERSION_INT
|
||||
|
||||
MAIN(testjson)
|
||||
{
|
||||
testPlan(1);
|
||||
try {
|
||||
testSkip(1, "JSON parser requires Base >=3.15.0.1");
|
||||
}catch(std::exception& e){
|
||||
testAbort("Unexpected exception: %s", e.what());
|
||||
}
|
||||
return testDone();
|
||||
}
|
||||
#endif //EPICS_VERSION_INT
|
||||
Reference in New Issue
Block a user