Added support for JSON Path (find value in object by path). Fixed JSON.stringify(): avoid crashing on circular references, replace already found objects by their path reference. Report path in Engine.dump_r for already found objects.

git-svn-id: http://voip.null.ro/svn/yate@6525 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
marian 2021-10-27 10:22:53 +00:00
parent 844616087f
commit 27ba8dfbec
3 changed files with 792 additions and 25 deletions

View File

@ -122,12 +122,118 @@ protected:
bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context);
};
class RecursiveTraceItem : public String
{
public:
inline RecursiveTraceItem(const GenObject* obj, const char* path = 0)
: String(path), m_traced((GenObject*)obj), m_jpath(0)
{}
~RecursiveTraceItem()
{ setJPath(0); }
inline const GenObject* traced() const
{ return m_traced; }
inline GenObject* traced()
{ return m_traced; }
inline GenObject* getJPath() const
{ return m_jpath; }
inline void setJPath(GenObject* obj) {
if (obj == m_jpath)
return;
TelEngine::destruct(m_jpath);
m_jpath = obj;
}
protected:
GenObject* m_traced;
GenObject* m_jpath;
};
#ifdef XDEBUG
#define JS_DEBUG_RECURSIVE_TRACE
#else
//#define JS_DEBUG_RECURSIVE_TRACE
#endif
class RecursiveTrace
{
public:
inline RecursiveTrace(bool toJSON, JsObject* rootJS, const GenObject* root)
: m_json(toJSON), m_root(root), m_rootJS(rootJS) {
m_append = &m_trace;
#ifdef JS_DEBUG_RECURSIVE_TRACE
Debug(DebugNote,"Start%s tracing root %p (%p)",m_json ? " JSON" : "",root,rootJS);
#endif
}
inline bool isRoot(const GenObject* gen) const
{ return m_root == gen; }
inline RecursiveTraceItem* find(const GenObject* gen) const {
if (gen)
for (ObjList* o = m_trace.skipNull(); o; o = o->skipNext()) {
RecursiveTraceItem* it = static_cast<RecursiveTraceItem*>(o->get());
if (it->traced() == gen) {
#ifdef JS_DEBUG_RECURSIVE_TRACE
Debug(DebugNote,"Found traced %p path='%s'",gen,it->safe());
#endif
return it;
}
}
return 0;
}
inline RecursiveTraceItem* findPath(const String& path) const {
GenObject* gen = m_trace[path];
if (!gen)
return 0;
RecursiveTraceItem* it = static_cast<RecursiveTraceItem*>(gen);
#ifdef JS_DEBUG_RECURSIVE_TRACE
Debug(DebugNote,"Found traced %p path='%s'",gen,it->safe());
#endif
return it;
}
inline void trace(const GenObject* obj, const String& path = String::empty()) {
if (!obj)
return;
debugTrace(false,obj,path);
addTrace(obj,isRoot(obj),path);
}
inline void traceJsObj(const JsObject* obj, const String& path = String::empty()) {
if (!obj)
return;
debugTrace(true,obj,path);
addTrace(obj,m_rootJS == obj,path);
}
protected:
inline void addTrace(const GenObject* obj, bool root, const String& path) {
if (root)
m_append = m_append->append(new RecursiveTraceItem(obj,"#"));
else if (path)
m_append = m_append->append(new RecursiveTraceItem(obj,"#" + path));
else
m_append = m_append->append(new RecursiveTraceItem(obj,"#/"));
}
inline void debugTrace(bool js, const void* ptr, const String& path = String::empty()) {
#ifdef JS_DEBUG_RECURSIVE_TRACE
if ((js && m_rootJS == ptr) || (!js && ptr == m_root))
Debug(DebugNote,"Tracing%s root %p",(js ? " JS" : ""),ptr);
else
Debug(DebugNote,"Tracing%s %p path='%s'",(js ? " JS" : ""),ptr,path.safe());
#endif
}
bool m_json;
const GenObject* m_root;
JsObject* m_rootJS;
ObjList m_trace;
ObjList* m_append;
};
#undef JS_DEBUG_RECURSIVE_TRACE
}; // anonymous namespace
// Helper function that does the actual object printing
static void dumpRecursiveObj(const GenObject* obj, String& buf, unsigned int depth, ObjList& seen,
unsigned int flags)
static void dumpRecursiveObj(const GenObject* obj, String& buf, unsigned int depth, RecursiveTrace& seen,
unsigned int flags, const String& path)
{
if (!obj)
return;
@ -138,8 +244,10 @@ static void dumpRecursiveObj(const GenObject* obj, String& buf, unsigned int dep
if (!dump)
return;
String str(' ',2 * depth);
if (seen.find(obj)) {
RecursiveTraceItem* objRecursed = seen.find(obj);
if (objRecursed) {
str << "(recursivity encountered)";
str.append(*objRecursed," ");
buf.append(str,"\r\n");
return;
}
@ -153,14 +261,20 @@ static void dumpRecursiveObj(const GenObject* obj, String& buf, unsigned int dep
const char* subType = 0;
const ScriptContext* scr = YOBJECT(ScriptContext,obj);
const ExpWrapper* wrap = 0;
bool objRecursed = false;
bool isFunc = false;
String nextPath = path;
if (!seen.isRoot(obj)) {
if (nstr)
JPath::addItem(nextPath,nstr->name().safe());
else
JPath::addItem(nextPath,"");
}
if (scr) {
const JsObject* jso = YOBJECT(JsObject,scr);
if (jso) {
objRecursed = (seen.find(jso) != 0);
objRecursed = seen.find(jso);
if ((jso != obj) && !objRecursed)
seen.append(jso)->setDelete(false);
seen.traceJsObj(jso,nextPath);
if (YOBJECT(JsArray,scr))
type = "JsArray";
else if (YOBJECT(JsFunction,scr)) {
@ -171,13 +285,15 @@ static void dumpRecursiveObj(const GenObject* obj, String& buf, unsigned int dep
type = "JsRegExp";
else if (YOBJECT(JsDate,scr))
type = "JsDate";
else if (YOBJECT(JsJPath,scr))
type = "JsJPath";
else
type = "JsObject";
}
else
type = "ScriptContext";
}
seen.append(obj)->setDelete(false);
seen.trace(obj,nextPath);
const ExpOperation* exp = YOBJECT(ExpOperation,nstr);
if (exp && !scr) {
if ((wrap = YOBJECT(ExpWrapper,exp)))
@ -205,6 +321,8 @@ static void dumpRecursiveObj(const GenObject* obj, String& buf, unsigned int dep
str << " = null";
else if (YOBJECT(JsRegExp,scr))
str << " = /" << *nstr << "/";
else if (YOBJECT(JsJPath,scr))
str << " = '" << *nstr << "'";
else if (flags & JsObject::DumpPropObjType) {
if (YOBJECT(JsObject,scr))
str << " = " << *nstr;
@ -236,33 +354,38 @@ static void dumpRecursiveObj(const GenObject* obj, String& buf, unsigned int dep
str << "'" << obj->toString() << "'";
if (dumpType)
str << " (" << type << (subType ? ", " : "") << subType << ")";
if (objRecursed)
if (objRecursed) {
str << " (already seen)";
//String tmpP; str << tmpP.printf(" (%p)",objRecursed->traced());
str.append(*objRecursed," ");
}
buf.append(str,"\r\n");
if (objRecursed)
return;
const JsObject* jso = YOBJECT(JsObject,obj);
if (!(scr || wrap || nptr || jso))
return;
if (scr) {
NamedIterator iter(scr->params());
while (const NamedString* p = iter.get())
dumpRecursiveObj(p,buf,depth + 1,seen,flags);
dumpRecursiveObj(p,buf,depth + 1,seen,flags,nextPath);
if (scr->nativeParams()) {
iter = *scr->nativeParams();
while (const NamedString* p = iter.get())
dumpRecursiveObj(p,buf,depth + 1,seen,flags);
dumpRecursiveObj(p,buf,depth + 1,seen,flags,nextPath);
}
}
else if (wrap)
dumpRecursiveObj(wrap->object(),buf,depth + 1,seen,flags);
dumpRecursiveObj(wrap->object(),buf,depth + 1,seen,flags,nextPath);
else if (nptr)
dumpRecursiveObj(nptr->userData(),buf,depth + 1,seen,flags);
const JsObject* jso = YOBJECT(JsObject,obj);
dumpRecursiveObj(nptr->userData(),buf,depth + 1,seen,flags,nextPath);
if (jso) {
const HashList* hash = jso->getHashListParams();
if (hash) {
for (unsigned int i = 0; i < hash->length(); i++) {
ObjList* lst = hash->getList(i);
for (lst ? lst = lst->skipNull() : 0; lst; lst = lst->skipNext())
dumpRecursiveObj(lst->get(),buf,depth + 1,seen,flags);
dumpRecursiveObj(lst->get(),buf,depth + 1,seen,flags,nextPath);
}
}
}
@ -320,8 +443,9 @@ JsObject* JsObject::copy(ScriptMutex* mtx, const ExpOperation& oper) const
void JsObject::dumpRecursive(const GenObject* obj, String& buf, unsigned int flags)
{
ObjList seen;
dumpRecursiveObj(obj,buf,0,seen,flags);
RecursiveTrace seen(false,YOBJECT(JsObject,obj),obj);
String path;
dumpRecursiveObj(obj,buf,0,seen,flags,path);
}
void JsObject::printRecursive(const GenObject* obj, unsigned int flags)
@ -374,11 +498,121 @@ ExpOperation* JsObject::toJSON(const ExpOperation* oper, int spaces)
spaces = 0;
else if (spaces > 10)
spaces = 10;
RecursiveTrace trace(true,YOBJECT(JsObject,oper),0);
ExpOperation* ret = new ExpOperation("","JSON");
toJSON(oper,*ret,spaces);
toJSON(oper,*ret,spaces,0,&trace);
return ret;
}
static bool internalResolveReferences(ExpOperation* root, ExpWrapper* param, RecursiveTrace& trace);
static bool resolveJsReference(ExpOperation* root, ExpWrapper* crt, RecursiveTrace& trace)
{
if (!(root && crt))
return true;
JsObject* jso = YOBJECT(JsObject,crt);
if (!jso)
return true;
String str;
if (!jso->getStringField(YSTRING("$ref"),str)) {
trace.trace(jso,"-");
internalResolveReferences(root,crt,trace);
return true;
}
ExpOperation* found = 0;
if ("#" == str)
found = root;
else if ('#' == str[0]) {
RecursiveTraceItem* it = trace.findPath(str);
if (it)
found = static_cast<ExpOperation*>(it->traced());
else {
JPath path(str.substr(1));
found = JsObject::find(root,path);
if (!found)
return false;
trace.trace(found,path);
}
}
else {
Debug(DebugMild,"Invalid JSON path '%s'",str.c_str());
return false;
}
jso = YOBJECT(JsObject,found);
if (!jso) {
Debug(DebugMild,"Found non object for JSON path '%s'",str.c_str());
return false;
}
if (jso == crt->object())
return true;
if (jso->ref()) {
crt->setObject(jso);
return true;
}
return false;
}
static bool internalResolveReferences(ExpOperation* root, ExpWrapper* wrap, RecursiveTrace& trace)
{
if (!root)
return true;
JsObject* jso = wrap ? YOBJECT(JsObject,wrap) : YOBJECT(JsObject,root);
JsArray* jsa = YOBJECT(JsArray,jso);
bool rVal = true;
if (jsa) {
unsigned int n = jsa->length();
for (unsigned int i = 0; i < n; i++) {
wrap = YOBJECT(ExpWrapper,jsa->params().getParam(String(i)));
if (wrap)
rVal = resolveJsReference(root,wrap,trace) && rVal;
}
}
else if (jso) {
for (ObjList* o = jso->params().paramList()->skipNull(); o; o = o->skipNext()) {
wrap = YOBJECT(ExpWrapper,o->get());
if (wrap)
rVal = resolveJsReference(root,wrap,trace) && rVal;
}
}
return rVal;
}
bool JsObject::resolveReferences(ExpOperation* oper)
{
if (!oper)
return true;
RecursiveTrace trace(true,YOBJECT(JsObject,oper),0);
return internalResolveReferences(oper,0,trace);
}
ExpOperation* JsObject::find(ExpOperation* oper, const JPath& path)
{
if (!path.valid())
return 0;
JsObject* obj = YOBJECT(JsObject,oper);
if (!obj)
return 0;
if (!path.count())
return oper;
for (unsigned int i = 0; i < path.count(); ) {
const String& prop = path[i++];
if (prop == JsObject::protoName())
return 0;
JsArray* jsa = YOBJECT(JsArray,obj);
if (jsa && (0 > JPath::validArrayIndex(prop)))
return 0;
ExpOperation* found = YOBJECT(ExpOperation,obj->params().getParam(prop));
if (!found || i == path.count())
return found;
obj = YOBJECT(JsObject,found);
if (!obj)
return 0;
}
return 0;
}
// Utility: retrieve a JSON candidate from given list item
// Advance the list
// Return pointer to candidate, NULL if not found
@ -424,7 +658,34 @@ static inline GenObject* nextJSONCandidate(const HashList& hash, unsigned int& i
return 0;
}
void JsObject::internalToJSON(const GenObject* obj, bool isStr, String& buf, int spaces, int indent)
// Utility used in internalToJSON to handle recursivity
// Return true if handled
bool JsObject::recursiveToJSON(String& newPath, JsObject* jso, String& buf, int spaces, int indent,
void* data, const String& path, const String& crtProp)
{
if (!(jso && data))
return false;
RecursiveTrace* trace = (RecursiveTrace*)data;
RecursiveTraceItem* it = trace->find(jso);
if (!it) {
newPath = path;
JPath::addItem(newPath,crtProp);
trace->traceJsObj(jso,newPath);
return false;
}
GenObject* obj = it->getJPath();
if (!obj) {
JsObject* jobj = new JsObject;
jobj->setStringField("$ref",*it);
obj = new ExpWrapper(jobj);
it->setJPath(obj);
}
internalToJSON(obj,false,buf,spaces,indent);
return true;
}
void JsObject::internalToJSON(const GenObject* obj, bool isStr, String& buf, int spaces, int indent,
void* data, const String& path, const String& crtProp)
{
if (!obj) {
buf << "null";
@ -447,6 +708,9 @@ void JsObject::internalToJSON(const GenObject* obj, bool isStr, String& buf, int
JsObject* jso = YOBJECT(JsObject,oper);
JsArray* jsa = YOBJECT(JsArray,jso);
if (jsa) {
String newPath;
if (recursiveToJSON(newPath,jsa,buf,spaces,indent,data,path,crtProp))
return;
if (jsa->length() <= 0) {
buf << "[]";
return;
@ -458,7 +722,7 @@ void JsObject::internalToJSON(const GenObject* obj, bool isStr, String& buf, int
buf << ci;
const NamedString* p = jsa->params().getParam(String(i));
if (p)
toJSON(p,buf,spaces,indent + spaces);
toJSON(p,buf,spaces,indent + spaces,data,newPath,p->name());
else
buf << "null";
if (++i < jsa->length())
@ -472,10 +736,13 @@ void JsObject::internalToJSON(const GenObject* obj, bool isStr, String& buf, int
return;
}
if (jso) {
if (YOBJECT(JsDate,jso)) {
if (YOBJECT(JsDate,jso) || YOBJECT(JsJPath,jso)) {
buf << strEscape(jso->toString());
return;
}
String newPath;
if (recursiveToJSON(newPath,jso,buf,spaces,indent,data,path,crtProp))
return;
const HashList* hash = jso->getHashListParams();
if (hash) {
ObjList* crt = hash->getList(0);
@ -491,7 +758,7 @@ void JsObject::internalToJSON(const GenObject* obj, bool isStr, String& buf, int
buf << "{" << nl;
while (gen) {
buf << ci << strEscape(gen->toString()) << sep;
internalToJSON(gen,false,buf,spaces,indent + spaces);
internalToJSON(gen,false,buf,spaces,indent + spaces,data,newPath,gen->toString());
gen = static_cast<NamedString*>(nextJSONCandidate(*hash,idx,crt));
if (gen)
buf << ",";
@ -517,7 +784,7 @@ void JsObject::internalToJSON(const GenObject* obj, bool isStr, String& buf, int
const NamedString* p = static_cast<NamedString*>(nextJSONCandidate(l));
while (p) {
buf << ci << strEscape(p->name()) << sep;
internalToJSON(p,true,buf,spaces,indent + spaces);
internalToJSON(p,true,buf,spaces,indent + spaces,data,newPath,p->name());
p = static_cast<NamedString*>(nextJSONCandidate(l));
if (p)
buf << ",";
@ -807,6 +1074,8 @@ void JsObject::initialize(ScriptContext* context)
addConstructor(p,"Date",new JsDate(mtx));
if (!p.getParam(YSTRING("Math")))
addObject(p,"Math",new JsMath(mtx));
if (!p.getParam(YSTRING("JPath")))
addConstructor(p,"JPath",new JsJPath(mtx));
}
void JsObject::setLineForObj(JsObject* obj,unsigned int lineNo, bool recursive)
@ -1983,4 +2252,168 @@ bool JsDate::runNative(ObjList& stack, const ExpOperation& oper, GenObject* cont
return true;
}
//
// JPath
//
JPath::JPath(const char* value)
: String(value),
m_data(0), m_count(0)
{
parse();
}
JPath::JPath(const JPath& other)
: String(other),
m_data(0), m_count(0)
{
if (!(other.m_data && other.m_count))
return;
m_data = new String[other.m_count];
m_count = other.m_count;
for (unsigned int i = 0; i < m_count; i++)
m_data[i] = other.m_data[i];
}
JPath::~JPath()
{
reset();
}
void JPath::changed()
{
parse();
}
void JPath::parse()
{
reset();
if (!c_str())
return;
if ('/' != *c_str()) {
Debug(DebugWarn,"JPath(%s): invalid path - not starting with '/'",c_str());
return;
}
ObjList* lst = split('/');
ObjList* o = lst->skipNull();
m_count = lst->count();
if (o)
o = o->skipNext();
if (m_count)
m_count--;
bool ok = true;
if (m_count) {
m_data = new String[m_count];
unsigned int itemIdx = 0;
for (; o && ok && itemIdx < m_count; o = o->skipNext(), itemIdx++) {
String& str = *static_cast<String*>(o->get());
if (!str)
continue;
char* start = (char*)(str.c_str());
for (char* s = start; *s; s++) {
if ('~' != *s)
continue;
char c = unescapeChar(s[1]);
if (!c) {
Debug(DebugWarn,"JPath(%s): invalid item %u - %s",
c_str(),itemIdx,(s[1] ? "unknown escape char" : "unexpected end after escape"));
ok = false;
break;
}
*s = 0;
m_data[itemIdx] << start << c;
start = s + 2;
*s++ = '~';
}
if (*start)
m_data[itemIdx] << start;
}
}
TelEngine::destruct(lst);
if (!ok)
reset();
}
//
// JsJPath
//
JsJPath::JsJPath(ScriptMutex* mtx)
: JsObject("JPath",mtx,true)
{
params().addParam(new ExpFunction("getItems"));
params().addParam(new ExpFunction("at"));
params().addParam(new ExpFunction("count"));
params().addParam(new ExpFunction("valid"));
}
JsObject* JsJPath::runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context)
{
XDebug(DebugAll,"JsJPath::runConstructor '%s'(" FMT64 ")",oper.name().c_str(),oper.number());
ObjList args;
JsObject* obj = 0;
int n = extractArgs(stack,oper,context,args);
if (1 == n) {
ExpOperation* val = static_cast<ExpOperation*>(args[0]);
if (!val)
return 0;
obj = new JsJPath(mutex(),oper.lineNumber(),val->c_str());
}
else
return 0;
if (obj) {
if (ref())
obj->params().addParam(new ExpWrapper(this,protoName()));
else
TelEngine::destruct(obj);
}
return obj;
}
bool JsJPath::runNative(ObjList& stack, const ExpOperation& oper, GenObject* context)
{
XDebug(DebugAll,"JsJPath::runNative() '%s' in '%s' [%p]",
oper.name().c_str(),toString().c_str(),this);
if (oper.name() == YSTRING("getItems")) {
// Returns array of items
JsArray* jsa = new JsArray(context,oper.lineNumber(),mutex());
for (unsigned int i = 0; i < m_path.count(); i++)
jsa->push(new ExpOperation(m_path[i]));
ExpEvaluator::pushOne(stack,new ExpWrapper(jsa));
}
else if (oper.name() == YSTRING("at")) {
// Retrieve path item at given index
ObjList args;
if (!extractArgs(this,stack,oper,context,args))
return false;
unsigned int idx = m_path.count();
if (m_path.count()) {
ExpOperation* op = static_cast<ExpOperation*>(args[0]);
if (op && op->isInteger())
idx = (unsigned int)op->number();
}
if (idx < m_path.count())
ExpEvaluator::pushOne(stack,new ExpOperation(m_path[idx]));
else
ExpEvaluator::pushOne(stack,new ExpWrapper(0,"undefined"));
}
else if (oper.name() == YSTRING("count"))
ExpEvaluator::pushOne(stack,new ExpOperation((int64_t)m_path.count()));
else if (oper.name() == YSTRING("valid"))
ExpEvaluator::pushOne(stack,new ExpOperation(m_path.valid()));
else
return JsObject::runNative(stack,oper,context);
return true;
}
void* JsJPath::getObject(const String& name) const
{
void* obj = (name == YATOM("JsJPath")) ? const_cast<JsJPath*>(this)
: JsObject::getObject(name);
return obj ? obj : m_path.getObject(name);
}
/* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -48,6 +48,163 @@ class ExpEvaluator;
class ExpOperation;
class ScriptMutex;
/**
* This class holds a JSON Pointer as specified in RFC 6901
* @short JSON path
*/
class YSCRIPT_API JPath : public String
{
YCLASS(JPath,String)
public:
/**
* Constructor
* @param value Initial value
*/
JPath(const char* value = 0);
/**
* Copy constructor
* @param other Object to copy
*/
JPath(const JPath& other);
/**
* Destructor
*/
~JPath();
/**
* Check if path is valid
* @return True if valid, false otherwise
*/
inline bool valid() const
{ return m_data || !c_str(); }
/**
* Retrieve the number of items in path
* @return The number of items in path
*/
inline unsigned int count() const
{ return m_count; }
/**
* Retrieve path item at a index
* @param idx Index to retrieve
* @return Path item at requested index, empty string if not found
*/
inline const String& at(unsigned int idx) const
{ return idx < count() ? m_data[idx] : String::empty(); }
/**
* Retrieve path item at a index
* @param idx Index to retrieve
* @return Path item at requested index, empty string if not found
*/
inline const String& at(unsigned int idx)
{ return idx < count() ? m_data[idx] : String::empty(); }
/**
* Retrieve path item at a index
* @param idx Index to retrieve
* @return Path item at requested index, empty string if not found
*/
inline const String& operator[](unsigned int idx) const
{ return at(idx); }
/**
* Retrieve path item at a index
* @param idx Index to retrieve
* @return Path item at requested index, empty string if not found
*/
inline const String& operator[](unsigned int idx)
{ return at(idx); }
/**
* Add a path item to path
* @param path Destination string
* @param value Item value to add
* @return Given 'path' string reference
*/
static inline String& addItem(String& path, const char* value) {
if (!value)
return path;
String tmp;
char* s = (char*)value;
for (unsigned int i = 0; *s; ++i, ++s) {
char c = escapeChar(*s);
if (!c)
continue;
if (!tmp)
tmp = value;
tmp.insert(i,'~');
s = (char*)(tmp.c_str() + (++i));
*s = c;
}
return path << '/' << tmp.safe(value);
}
/**
* Check if an item is a valid Array index
* A valid array index is a decimal string with no leading/trailing spaces or leading 0
* @param str String to check
* @return Array index, negative if invalid
*/
static inline int validArrayIndex(const String& str)
{ return str.toInteger(-1,10); }
/**
* Retrieve escape char
* @param value Requested character
* @return Escape character, 0 if given character doesn't need to be escaped
*/
static inline char escapeChar(char value) {
if (value == '~')
return '0';
if (value == '/')
return '1';
return 0;
}
/**
* Retrieve unescape char
* @param value Requested character
* @return Unescape character, 0 if given character is not an escaped one
*/
static inline char unescapeChar(char value) {
if (value == '0')
return '~';
if (value == '1')
return '/';
return 0;
}
protected:
/**
* Called whenever the String value changed.
* Reset data, parse the path
*/
virtual void changed();
/**
* Parse the path
*/
void parse();
/**
* Reset data
*/
inline void reset() {
m_count = 0;
if (m_data) {
delete[] m_data;
m_data = 0;
}
}
String* m_data;
unsigned int m_count;
};
/**
* This class allows extending ExpEvaluator to implement custom fields and functions
* @short ExpEvaluator extending interface
@ -1310,6 +1467,17 @@ public:
GenObject* object() const
{ return m_object; }
/**
* Replace held object if given object is not NULL and different from held one
* @param gen Pointer to the new held object
*/
inline void setObject(GenObject* gen) {
if (!gen || gen == m_object)
return;
TelEngine::destruct(m_object);
m_object = gen;
}
private:
GenObject* m_object;
};
@ -2433,6 +2601,21 @@ public:
*/
static ExpOperation* toJSON(const ExpOperation* oper, int spaces);
/**
*
* @param oper Object to handle
* @return
*/
static bool resolveReferences(ExpOperation* oper);
/**
* Find a value in object by path
* @param oper Object to handle
* @param path Path to use
* @return Found property value, NULL if not found
*/
static ExpOperation* find(ExpOperation* oper, const JPath& path);
protected:
/**
* Try to evaluate a single native method
@ -2464,9 +2647,13 @@ protected:
* @param buf String used as output for the JSON represantion
* @param spaces Number of spaces used for one indentation level
* @param indent Current number of spaces used for indentation
* @param data Internal data used for various purposes
* @param path Current path if any
* @param crtProp Current property if any
*/
static inline void toJSON(const NamedString* ns, String& buf, int spaces, int indent = 0)
{ internalToJSON(ns,true,buf,spaces,indent); }
static inline void toJSON(const NamedString* ns, String& buf, int spaces, int indent = 0,
void* data = 0, const String& path = String::empty(), const String& crtProp = String::empty())
{ internalToJSON(ns,true,buf,spaces,indent,data,path,crtProp); }
/**
* Static helper method for escaping special characters when JSON stringifying
@ -2476,7 +2663,11 @@ protected:
static String strEscape(const char* str);
private:
static void internalToJSON(const GenObject* obj, bool isStr, String& buf, int spaces, int indent = 0);
static bool recursiveToJSON(String& newPath, JsObject* jso, String& buf, int spaces, int indent,
void* data, const String& path, const String& crtProp);
static void internalToJSON(const GenObject* obj, bool isStr, String& buf, int spaces,
int indent = 0, void* data = 0, const String& path = String::empty(),
const String& crtProp = String::empty());
static const String s_protoName;
bool m_frozen;
ScriptMutex* m_mutex;
@ -2827,6 +3018,96 @@ private:
Regexp m_regexp;
};
/**
* Javascript JSON path class
* @short Javascript JSON path
*/
class JsJPath : public JsObject
{
public:
/**
* Constructor
* @param mtx Pointer to the mutex that serializes this object
*/
JsJPath(ScriptMutex* mtx);
/**
* JPath object constructor, it's run on the prototype
* @param stack Evaluation stack in use
* @param oper Constructor function to evaluate
* @param context Pointer to arbitrary object passed from evaluation methods
* @return Newly created and populated Javascript JSON path
*/
virtual JsObject* runConstructor(ObjList& stack, const ExpOperation& oper, GenObject* context);
/**
* Retrieve held path
* @return Held path reference
*/
virtual const JPath& path() const
{ return m_path; }
/**
* Retrieve path string
* @return Held path value
*/
virtual const String& toString() const
{ return m_path; }
/**
* Get a pointer to a derived class given that class name
* @param name Name of the class we are asking for
* @return Pointer to the requested class or NULL if this object doesn't implement it
*/
virtual void* getObject(const String& name) const;
protected:
/**
* Constructor from existing path
* @param mtx Pointer to the mutex that serializes this object
* @param line Code line where this object was created
* @param path Path to copy
*/
inline JsJPath(ScriptMutex* mtx, unsigned int line, const char* path)
: JsObject(mtx,path,line),
m_path(path)
{ }
/**
* Constructor for a JPath object
* @param mtx Pointer to the mutex that serializes this object
* @param name Full name of the object
* @param line Code line where this object was created
* @param path JSON path
*/
inline JsJPath(ScriptMutex* mtx, const char* name, unsigned int line, const JPath& path)
: JsObject(mtx,name,line),
m_path(path)
{ }
/**
* Clone and rename method
* @param name Name of the cloned object
* @param oper ExpOperation that required the clone
* @return New object instance
*/
virtual JsObject* clone(const char* name, const ExpOperation& oper) const
{ return new JsJPath(mutex(),name,oper.lineNumber(),m_path); }
/**
* Try to evaluate a single native method
* @param stack Evaluation stack in use, parameters are popped off this stack
* and results are pushed back on stack
* @param oper Function to evaluate
* @param context Pointer to arbitrary object passed from evaluation methods
* @return True if evaluation succeeded
*/
bool runNative(ObjList& stack, const ExpOperation& oper, GenObject* context);
private:
JPath m_path; // Held JPath
};
/**
* Javascript parser, takes source code and generates preparsed code
* @short Javascript parser

View File

@ -41,6 +41,34 @@
using namespace TelEngine;
namespace { // anonymous
// Temporary class used to store an object from received parameter or build a new one to be used
// Safely release created object
template <class Obj> class ExpOpTmpObj
{
public:
inline ExpOpTmpObj(Obj* obj, ExpOperation& op)
: m_obj(obj), m_del(!m_obj)
{ if (m_del) m_obj = new Obj(op); }
inline ~ExpOpTmpObj()
{ if (m_del) TelEngine::destruct(m_obj); }
inline Obj* operator->() const
{ return m_obj; }
inline Obj& operator*() const
{ return *m_obj; }
private:
inline ExpOpTmpObj() : m_obj(0), m_del(false) {}
Obj* m_obj;
bool m_del;
};
class JPathTmpParam : public ExpOpTmpObj<JPath>
{
public:
inline JPathTmpParam(ExpOperation& op)
: ExpOpTmpObj(YOBJECT(JPath,&op),op)
{}
};
static inline void dumpTraceToMsg(Message* msg, ObjList* lst)
{
if (!(msg && lst))
@ -898,6 +926,8 @@ public:
params().addParam(new ExpFunction("loadFile"));
params().addParam(new ExpFunction("saveFile"));
params().addParam(new ExpFunction("replaceParams"));
params().addParam(new ExpFunction("replaceReferences"));
params().addParam(new ExpFunction("findPath"));
}
static void initialize(ScriptContext* context);
protected:
@ -4806,6 +4836,29 @@ bool JsJSON::runNative(ObjList& stack, const ExpOperation& oper, GenObject* cont
replaceParams(args[0],*params,sqlEsc,extraEsc);
}
}
else if (oper.name() == YSTRING("replaceReferences")) {
// JSON.replaceReferences(obj)
// Return boolean (success/failure)
ExpOperation* op = 0;
if (!extractStackArgs(1,this,stack,oper,context,args,&op))
return false;
bool ok = JsObject::resolveReferences(op);
ExpEvaluator::pushOne(stack,new ExpOperation(ok));
}
else if (oper.name() == YSTRING("findPath")) {
// JSON.findPath(obj,path). 'path' may be a JPath object or string
// Return found data, undefined if not found
ExpOperation* op = 0;
ExpOperation* pathOp = 0;
if (!extractStackArgs(2,this,stack,oper,context,args,&op,&pathOp))
return false;
JPathTmpParam jp(*pathOp);
ExpOperation* res = JsObject::find(op,*jp);
if (res)
ExpEvaluator::pushOne(stack,res->clone());
else
ExpEvaluator::pushOne(stack,new ExpWrapper((GenObject*)0));
}
else
return JsObject::runNative(stack,oper,context);
return true;