/*************** bson CPP Declares Source Code File (.H) ***************/ /* Name: bson.cpp Version 1.0 */ /* */ /* (C) Copyright to the author Olivier BERTRAND 2020 */ /* */ /* This file contains the BJSON classes functions. */ /***********************************************************************/ /***********************************************************************/ /* Include relevant sections of the MariaDB header file. */ /***********************************************************************/ #include /***********************************************************************/ /* Include application header files: */ /* global.h is header containing all global declarations. */ /* plgdbsem.h is header containing the DB application declarations. */ /* bson.h is header containing the BSON classes declarations. */ /***********************************************************************/ #include "global.h" #include "plgdbsem.h" #include "bson.h" /***********************************************************************/ /* Check macro. */ /***********************************************************************/ #if defined(_DEBUG) #define CheckType(X,Y) if (!X || X ->Type != Y) throw MSG(VALTYPE_NOMATCH); #else #define CheckType(X,Y) #endif #if defined(__WIN__) #define EL "\r\n" #else #define EL "\n" #undef SE_CATCH // Does not work for Linux #endif int GetJsonDefPrec(void); #if defined(SE_CATCH) /**************************************************************************/ /* This is the support of catching C interrupts to prevent crashes. */ /**************************************************************************/ #include class SE_Exception { public: SE_Exception(unsigned int n, PEXCEPTION_RECORD p) : nSE(n), eRec(p) {} ~SE_Exception() {} unsigned int nSE; PEXCEPTION_RECORD eRec; }; // end of class SE_Exception void trans_func(unsigned int u, _EXCEPTION_POINTERS* pExp) { throw SE_Exception(u, pExp->ExceptionRecord); } // end of trans_func char* GetExceptionDesc(PGLOBAL g, unsigned int e); #endif // SE_CATCH /* --------------------------- Class BDOC ---------------------------- */ /***********************************************************************/ /* BDOC constructor. */ /***********************************************************************/ BDOC::BDOC(PGLOBAL G) : BJSON(G, NULL) { jp = NULL; s = NULL; len = 0; pretty = 3; pty[0] = pty[1] = pty[2] = true; comma = false; } // end of BDOC constructor /***********************************************************************/ /* Parse a json string. */ /* Note: when pretty is not known, the caller set pretty to 3. */ /***********************************************************************/ PBVAL BDOC::ParseJson(PGLOBAL g, char* js, size_t lng) { size_t i; bool b = false, ptyp = (bool *)pty; PBVAL bvp = NULL; s = js; len = lng; xtrc(1, "BDOC::ParseJson: s=%.10s len=%zd\n", s, len); if (!s || !len) { strcpy(g->Message, "Void JSON object"); return NULL; } // endif s // Trying to guess the pretty format if (s[0] == '[' && (s[1] == '\n' || (s[1] == '\r' && s[2] == '\n'))) pty[0] = false; try { bvp = NewVal(); bvp->Type = TYPE_UNKNOWN; for (i = 0; i < len; i++) switch (s[i]) { case '[': if (bvp->Type != TYPE_UNKNOWN) bvp->To_Val = ParseAsArray(i); else bvp->To_Val = ParseArray(++i); bvp->Type = TYPE_JAR; break; case '{': if (bvp->Type != TYPE_UNKNOWN) { bvp->To_Val = ParseAsArray(i); bvp->Type = TYPE_JAR; } else { bvp->To_Val = ParseObject(++i); bvp->Type = TYPE_JOB; } // endif Type break; case ' ': case '\t': case '\n': case '\r': break; case ',': if (bvp->Type != TYPE_UNKNOWN && (pretty == 1 || pretty == 3)) { comma = true; pty[0] = pty[2] = false; break; } // endif pretty sprintf(g->Message, "Unexpected ',' (pretty=%d)", pretty); throw 3; case '(': b = true; break; case ')': if (b) { b = false; break; } // endif b default: if (bvp->Type != TYPE_UNKNOWN) { bvp->To_Val = ParseAsArray(i); bvp->Type = TYPE_JAR; } else if ((bvp->To_Val = MOF(ParseValue(i, NewVal())))) bvp->Type = TYPE_JVAL; else throw 4; break; }; // endswitch s[i] if (bvp->Type == TYPE_UNKNOWN) sprintf(g->Message, "Invalid Json string '%.*s'", MY_MIN((int)len, 50), s); else if (pretty == 3) { for (i = 0; i < 3; i++) if (pty[i]) { pretty = i; break; } // endif pty } // endif ptyp } catch (int n) { if (trace(1)) htrc("Exception %d: %s\n", n, G->Message); GetMsg(g); bvp = NULL; } catch (const char* msg) { strcpy(g->Message, msg); bvp = NULL; } // end catch return bvp; } // end of ParseJson /***********************************************************************/ /* Parse several items as being in an array. */ /***********************************************************************/ OFFSET BDOC::ParseAsArray(size_t& i) { if (pty[0] && (!pretty || pretty > 2)) { OFFSET jsp; if ((jsp = ParseArray((i = 0))) && pretty == 3) pretty = (pty[0]) ? 0 : 3; return jsp; } else strcpy(G->Message, "More than one item in file"); return 0; } // end of ParseAsArray /***********************************************************************/ /* Parse a JSON Array. */ /***********************************************************************/ OFFSET BDOC::ParseArray(size_t& i) { int level = 0; bool b = (!i); PBVAL vlp, firstvlp, lastvlp; vlp = firstvlp = lastvlp = NULL; for (; i < len; i++) switch (s[i]) { case ',': if (level < 2) { sprintf(G->Message, "Unexpected ',' near %.*s", (int) ARGS); throw 1; } else level = 1; break; case ']': if (level == 1) { sprintf(G->Message, "Unexpected ',]' near %.*s", (int) ARGS); throw 1; } // endif level return MOF(firstvlp); case '\n': if (!b) pty[0] = pty[1] = false; case '\r': case ' ': case '\t': break; default: if (level == 2) { sprintf(G->Message, "Unexpected value near %.*s", (int) ARGS); throw 1; } else if (lastvlp) { vlp = ParseValue(i, NewVal()); lastvlp->Next = MOF(vlp); lastvlp = vlp; } else firstvlp = lastvlp = ParseValue(i, NewVal()); level = (b) ? 1 : 2; break; }; // endswitch s[i] if (b) { // Case of Pretty == 0 return MOF(firstvlp); } // endif b throw ("Unexpected EOF in array"); } // end of ParseArray /***********************************************************************/ /* Parse a JSON Object. */ /***********************************************************************/ OFFSET BDOC::ParseObject(size_t& i) { OFFSET key; int level = 0; PBPR bpp, firstbpp, lastbpp; bpp = firstbpp = lastbpp = NULL; for (; i < len; i++) switch (s[i]) { case '"': if (level < 2) { key = ParseString(++i); bpp = NewPair(key); if (lastbpp) { lastbpp->Vlp.Next = MOF(bpp); lastbpp = bpp; } else firstbpp = lastbpp = bpp; level = 2; } else { sprintf(G->Message, "misplaced string near %.*s", (int) ARGS); throw 2; } // endif level break; case ':': if (level == 2) { ParseValue(++i, GetVlp(lastbpp)); level = 3; } else { sprintf(G->Message, "Unexpected ':' near %.*s", (int) ARGS); throw 2; } // endif level break; case ',': if (level < 3) { sprintf(G->Message, "Unexpected ',' near %.*s", (int) ARGS); throw 2; } else level = 1; break; case '}': if (!(level == 0 || level == 3)) { sprintf(G->Message, "Unexpected '}' near %.*s", (int) ARGS); throw 2; } // endif level return MOF(firstbpp); case '\n': pty[0] = pty[1] = false; case '\r': case ' ': case '\t': break; default: sprintf(G->Message, "Unexpected character '%c' near %.*s", s[i], (int) ARGS); throw 2; }; // endswitch s[i] strcpy(G->Message, "Unexpected EOF in Object"); throw 2; } // end of ParseObject /***********************************************************************/ /* Parse a JSON Value. */ /***********************************************************************/ PBVAL BDOC::ParseValue(size_t& i, PBVAL bvp) { for (; i < len; i++) switch (s[i]) { case '\n': pty[0] = pty[1] = false; case '\r': case ' ': case '\t': break; default: goto suite; } // endswitch suite: switch (s[i]) { case '[': bvp->To_Val = ParseArray(++i); bvp->Type = TYPE_JAR; break; case '{': bvp->To_Val = ParseObject(++i); bvp->Type = TYPE_JOB; break; case '"': bvp->To_Val = ParseString(++i); bvp->Type = TYPE_STRG; break; case 't': if (!strncmp(s + i, "true", 4)) { bvp->B = true; bvp->Type = TYPE_BOOL; i += 3; } else goto err; break; case 'f': if (!strncmp(s + i, "false", 5)) { bvp->B = false; bvp->Type = TYPE_BOOL; i += 4; } else goto err; break; case 'n': if (!strncmp(s + i, "null", 4)) { bvp->Type = TYPE_NULL; i += 3; } else goto err; break; case '-': default: if (s[i] == '-' || isdigit(s[i])) ParseNumeric(i, bvp); else goto err; }; // endswitch s[i] return bvp; err: sprintf(G->Message, "Unexpected character '%c' near %.*s", s[i], (int) ARGS); throw 3; } // end of ParseValue /***********************************************************************/ /* Unescape and parse a JSON string. */ /***********************************************************************/ OFFSET BDOC::ParseString(size_t& i) { uchar* p; int n = 0; // Be sure of memory availability if (((size_t)len + 1 - i) > ((PPOOLHEADER)G->Sarea)->FreeBlk) throw("ParseString: Out of memory"); // The size to allocate is not known yet p = (uchar*)BsonSubAlloc(0); for (; i < len; i++) switch (s[i]) { case '"': p[n++] = 0; BsonSubAlloc(n); return MOF(p); case '\\': if (++i < len) { if (s[i] == 'u') { if (len - i > 5) { // if (charset == utf8) { char xs[5]; uint hex; xs[0] = s[++i]; xs[1] = s[++i]; xs[2] = s[++i]; xs[3] = s[++i]; xs[4] = 0; hex = strtoul(xs, NULL, 16); if (hex < 0x80) { p[n] = (uchar)hex; } else if (hex < 0x800) { p[n++] = (uchar)(0xC0 | (hex >> 6)); p[n] = (uchar)(0x80 | (hex & 0x3F)); } else if (hex < 0x10000) { p[n++] = (uchar)(0xE0 | (hex >> 12)); p[n++] = (uchar)(0x80 | ((hex >> 6) & 0x3f)); p[n] = (uchar)(0x80 | (hex & 0x3f)); } else p[n] = '?'; #if 0 } else { char xs[3]; UINT hex; i += 2; xs[0] = s[++i]; xs[1] = s[++i]; xs[2] = 0; hex = strtoul(xs, NULL, 16); p[n] = (char)hex; } // endif charset #endif // 0 } else goto err; } else switch (s[i]) { case 't': p[n] = '\t'; break; case 'n': p[n] = '\n'; break; case 'r': p[n] = '\r'; break; case 'b': p[n] = '\b'; break; case 'f': p[n] = '\f'; break; default: p[n] = s[i]; break; } // endswitch n++; } else goto err; break; default: p[n++] = s[i]; break; }; // endswitch s[i] err: throw("Unexpected EOF in String"); } // end of ParseString /***********************************************************************/ /* Parse a JSON numeric value. */ /***********************************************************************/ void BDOC::ParseNumeric(size_t& i, PBVAL vlp) { char buf[50]; int n = 0; short nd = 0; bool has_dot = false; bool has_e = false; bool found_digit = false; for (; i < len; i++) { switch (s[i]) { case '.': if (!found_digit || has_dot || has_e) goto err; has_dot = true; break; case 'e': case 'E': if (!found_digit || has_e) goto err; has_e = true; found_digit = false; break; case '+': if (!has_e) goto err; // fall through case '-': if (found_digit) goto err; break; default: if (isdigit(s[i])) { if (has_dot && !has_e) nd++; // Number of decimals found_digit = true; } else goto fin; }; // endswitch s[i] buf[n++] = s[i]; } // endfor i fin: if (found_digit) { buf[n] = 0; if (has_dot || has_e) { double dv = atof(buf); if (nd >= 6 || dv > FLT_MAX || dv < FLT_MIN) { double* dvp = (double*)PlugSubAlloc(G, NULL, sizeof(double)); *dvp = dv; vlp->To_Val = MOF(dvp); vlp->Type = TYPE_DBL; } else { vlp->F = (float)dv; vlp->Type = TYPE_FLOAT; } // endif nd vlp->Nd = MY_MIN(nd, 16); } else { longlong iv = strtoll(buf, NULL, 10); if (iv > INT_MAX32 || iv < INT_MIN32) { longlong *llp = (longlong*)PlugSubAlloc(G, NULL, sizeof(longlong)); *llp = iv; vlp->To_Val = MOF(llp); vlp->Type = TYPE_BINT; } else { vlp->N = (int)iv; vlp->Type = TYPE_INTG; } // endif iv } // endif has i--; // Unstack following character return; } else throw("No digit found"); err: throw("Unexpected EOF in number"); } // end of ParseNumeric /***********************************************************************/ /* Serialize a BJSON document tree: */ /***********************************************************************/ PSZ BDOC::Serialize(PGLOBAL g, PBVAL bvp, char* fn, int pretty) { PSZ str = NULL; bool b = false, err = true; FILE* fs = NULL; G->Message[0] = 0; try { if (!bvp) { strcpy(g->Message, "Null json tree"); throw 1; } else if (!fn) { // Serialize to a string jp = new(g) JOUTSTR(g); b = pretty == 1; } else { if (!(fs = fopen(fn, "wb"))) { sprintf(g->Message, MSG(OPEN_MODE_ERROR), "w", (int)errno, fn); strcat(strcat(g->Message, ": "), strerror(errno)); throw 2; } else if (pretty >= 2) { // Serialize to a pretty file jp = new(g)JOUTPRT(g, fs); } else { // Serialize to a flat file b = true; jp = new(g)JOUTFILE(g, fs, pretty); } // endif's } // endif's switch (bvp->Type) { case TYPE_JAR: err = SerializeArray(bvp->To_Val, b); break; case TYPE_JOB: err = ((b && jp->Prty()) && jp->WriteChr('\t')); err |= SerializeObject(bvp->To_Val); break; case TYPE_JVAL: err = SerializeValue(MVP(bvp->To_Val)); break; default: err = SerializeValue(bvp, true); } // endswitch Type if (fs) { fputs(EL, fs); fclose(fs); str = (err) ? NULL : strcpy(g->Message, "Ok"); } else if (!err) { str = ((JOUTSTR*)jp)->Strp; jp->WriteChr('\0'); PlugSubAlloc(g, NULL, ((JOUTSTR*)jp)->N); } else if (G->Message[0]) strcpy(g->Message, "Error in Serialize"); else GetMsg(g); } catch (int n) { if (trace(1)) htrc("Exception %d: %s\n", n, G->Message); GetMsg(g); str = NULL; } catch (const char* msg) { strcpy(g->Message, msg); str = NULL; } // end catch return str; } // end of Serialize /***********************************************************************/ /* Serialize a JSON Array. */ /***********************************************************************/ bool BDOC::SerializeArray(OFFSET arp, bool b) { bool first = true; PBVAL vp = MVP(arp); if (b) { if (jp->Prty()) { if (jp->WriteChr('[')) return true; else if (jp->Prty() == 1 && (jp->WriteStr(EL) || jp->WriteChr('\t'))) return true; } // endif Prty } else if (jp->WriteChr('[')) return true; for (vp; vp; vp = MVP(vp->Next)) { if (first) first = false; else if ((!b || jp->Prty()) && jp->WriteChr(',')) return true; else if (b) { if (jp->Prty() < 2 && jp->WriteStr(EL)) return true; else if (jp->Prty() == 1 && jp->WriteChr('\t')) return true; } // endif b if (SerializeValue(vp)) return true; } // endfor vp if (b && jp->Prty() == 1 && jp->WriteStr(EL)) return true; return ((!b || jp->Prty()) && jp->WriteChr(']')); } // end of SerializeArray /***********************************************************************/ /* Serialize a JSON Object. */ /***********************************************************************/ bool BDOC::SerializeObject(OFFSET obp) { bool first = true; PBPR prp = MPP(obp); if (jp->WriteChr('{')) return true; for (prp; prp; prp = GetNext(prp)) { if (first) first = false; else if (jp->WriteChr(',')) return true; if (jp->WriteChr('"') || jp->WriteStr(MZP(prp->Key)) || jp->WriteChr('"') || jp->WriteChr(':') || SerializeValue(GetVlp(prp))) return true; } // endfor i return jp->WriteChr('}'); } // end of SerializeObject /***********************************************************************/ /* Serialize a JSON Value. */ /***********************************************************************/ bool BDOC::SerializeValue(PBVAL jvp, bool b) { char buf[64]; if (jvp) switch (jvp->Type) { case TYPE_JAR: return SerializeArray(jvp->To_Val, false); case TYPE_JOB: return SerializeObject(jvp->To_Val); case TYPE_BOOL: return jp->WriteStr(jvp->B ? "true" : "false"); case TYPE_STRG: case TYPE_DTM: if (b) { return jp->WriteStr(MZP(jvp->To_Val)); } else return jp->Escape(MZP(jvp->To_Val)); case TYPE_INTG: sprintf(buf, "%d", jvp->N); return jp->WriteStr(buf); case TYPE_BINT: sprintf(buf, "%lld", *(longlong*)MakePtr(Base, jvp->To_Val)); return jp->WriteStr(buf); case TYPE_FLOAT: sprintf(buf, "%.*f", jvp->Nd, jvp->F); return jp->WriteStr(buf); case TYPE_DBL: sprintf(buf, "%.*lf", jvp->Nd, *(double*)MakePtr(Base, jvp->To_Val)); return jp->WriteStr(buf); case TYPE_NULL: return jp->WriteStr("null"); case TYPE_JVAL: return SerializeValue(MVP(jvp->To_Val)); default: return jp->WriteStr("???"); // TODO } // endswitch Type return jp->WriteStr("null"); } // end of SerializeValue /* --------------------------- Class BJSON --------------------------- */ /***********************************************************************/ /* Program for sub-allocating Bjson structures. */ /***********************************************************************/ void* BJSON::BsonSubAlloc(size_t size) { PPOOLHEADER pph; /* Points on area header. */ void* memp = G->Sarea; size = ((size + 3) / 4) * 4; /* Round up size to multiple of 4 */ pph = (PPOOLHEADER)memp; xtrc(16, "SubAlloc in %p size=%zd used=%zd free=%zd\n", memp, size, pph->To_Free, pph->FreeBlk); if (size > pph->FreeBlk) { /* Not enough memory left in pool */ sprintf(G->Message, "Not enough memory for request of %zd (used=%zd free=%zd)", size, pph->To_Free, pph->FreeBlk); xtrc(1, "BsonSubAlloc: %s\n", G->Message); if (Throw) throw(1234); else return NULL; } /* endif size OS32 code */ // Do the suballocation the simplest way memp = MakePtr(memp, pph->To_Free); /* Points to suballocated block */ pph->To_Free += size; /* New offset of pool free block */ pph->FreeBlk -= size; /* New size of pool free block */ xtrc(16, "Done memp=%p used=%zd free=%zd\n", memp, pph->To_Free, pph->FreeBlk); return memp; } // end of BsonSubAlloc /*********************************************************************************/ /* Program for SubSet re-initialization of the memory pool. */ /*********************************************************************************/ PSZ BJSON::NewStr(PSZ str) { if (str) { PSZ sm = (PSZ)BsonSubAlloc(strlen(str) + 1); strcpy(sm, str); return sm; } else return NULL; } // end of NewStr /*********************************************************************************/ /* Program for SubSet re-initialization of the memory pool. */ /*********************************************************************************/ void BJSON::SubSet(bool b) { PPOOLHEADER pph = (PPOOLHEADER)G->Sarea; pph->To_Free = (G->Saved_Size) ? G->Saved_Size : sizeof(POOLHEADER); pph->FreeBlk = G->Sarea_Size - pph->To_Free; if (b) G->Saved_Size = 0; } // end of SubSet /*********************************************************************************/ /* Set the beginning of suballocations. */ /*********************************************************************************/ void BJSON::MemSet(size_t size) { PPOOLHEADER pph = (PPOOLHEADER)G->Sarea; pph->To_Free = size + sizeof(POOLHEADER); pph->FreeBlk = G->Sarea_Size - pph->To_Free; } // end of MemSet /* ------------------------ Bobject functions ------------------------ */ /***********************************************************************/ /* Set a pair vlp to some PVAL values. */ /***********************************************************************/ void BJSON::SetPairValue(PBPR brp, PBVAL bvp) { if (bvp) { brp->Vlp.To_Val = bvp->To_Val; brp->Vlp.Nd = bvp->Nd; brp->Vlp.Type = bvp->Type; } else { brp->Vlp.To_Val = 0; brp->Vlp.Nd = 0; brp->Vlp.Type = TYPE_NULL; } // endif bvp } // end of SetPairValue /***********************************************************************/ /* Sub-allocate and initialize a BPAIR. */ /***********************************************************************/ PBPR BJSON::NewPair(OFFSET key, int type) { PBPR bpp = (PBPR)BsonSubAlloc(sizeof(BPAIR)); bpp->Key = key; bpp->Vlp.Type = type; bpp->Vlp.To_Val = 0; bpp->Vlp.Nd = 0; bpp->Vlp.Next = 0; return bpp; } // end of SubAllocPair /***********************************************************************/ /* Return the number of pairs in this object. */ /***********************************************************************/ int BJSON::GetObjectSize(PBVAL bop, bool b) { CheckType(bop, TYPE_JOB); int n = 0; for (PBPR brp = GetObject(bop); brp; brp = GetNext(brp)) // If b return only non null pairs if (!b || (brp->Vlp.To_Val && brp->Vlp.Type != TYPE_NULL)) n++; return n; } // end of GetObjectSize /***********************************************************************/ /* Add a new pair to an Object and return it. */ /***********************************************************************/ PBVAL BJSON::AddPair(PBVAL bop, PSZ key, int type) { CheckType(bop, TYPE_JOB); PBPR brp; OFFSET nrp = NewPair(key, type); if (bop->To_Val) { for (brp = GetObject(bop); brp->Vlp.Next; brp = GetNext(brp)); brp->Vlp.Next = nrp; } else bop->To_Val = nrp; bop->Nd++; return GetVlp(MPP(nrp)); } // end of AddPair /***********************************************************************/ /* Return all object keys as an array. */ /***********************************************************************/ PBVAL BJSON::GetKeyList(PBVAL bop) { CheckType(bop, TYPE_JOB); PBVAL arp = NewVal(TYPE_JAR); for (PBPR brp = GetObject(bop); brp; brp = GetNext(brp)) AddArrayValue(arp, MOF(SubAllocVal(brp->Key, TYPE_STRG))); return arp; } // end of GetKeyList /***********************************************************************/ /* Return all object values as an array. */ /***********************************************************************/ PBVAL BJSON::GetObjectValList(PBVAL bop) { CheckType(bop, TYPE_JOB); PBVAL arp = NewVal(TYPE_JAR); for (PBPR brp = GetObject(bop); brp; brp = GetNext(brp)) AddArrayValue(arp, DupVal(GetVlp(brp))); return arp; } // end of GetObjectValList /***********************************************************************/ /* Get the value corresponding to the given key. */ /***********************************************************************/ PBVAL BJSON::GetKeyValue(PBVAL bop, PSZ key) { CheckType(bop, TYPE_JOB); for (PBPR brp = GetObject(bop); brp; brp = GetNext(brp)) if (!strcmp(GetKey(brp), key)) return GetVlp(brp); return NULL; } // end of GetKeyValue; /***********************************************************************/ /* Return the text corresponding to all keys (XML like). */ /***********************************************************************/ PSZ BJSON::GetObjectText(PGLOBAL g, PBVAL bop, PSTRG text) { CheckType(bop, TYPE_JOB); PBPR brp = GetObject(bop); if (brp) { bool b; if (!text) { text = new(g) STRING(g, 256); b = true; } else { if (text->GetLastChar() != ' ') text->Append(' '); b = false; } // endif text if (b && !brp->Vlp.Next && !strcmp(MZP(brp->Key), "$date")) { int i; PSZ s; GetValueText(g, GetVlp(brp), text); s = text->GetStr(); i = (s[1] == '-' ? 2 : 1); if (IsNum(s + i)) { // Date is in milliseconds int j = text->GetLength(); if (j >= 4 + i) { s[j - 3] = 0; // Change it to seconds text->SetLength((uint)strlen(s)); } else text->Set(" 0"); } // endif text } else for (; brp; brp = GetNext(brp)) { GetValueText(g, GetVlp(brp), text); if (brp->Vlp.Next) text->Append(' '); } // endfor brp if (b) { text->Trim(); return text->GetStr(); } // endif b } // endif bop return NULL; } // end of GetObjectText; /***********************************************************************/ /* Set or add a value corresponding to the given key. */ /***********************************************************************/ void BJSON::SetKeyValue(PBVAL bop, OFFSET bvp, PSZ key) { CheckType(bop, TYPE_JOB); PBPR brp, prp = NULL; if (bop->To_Val) { for (brp = GetObject(bop); brp; brp = GetNext(brp)) if (!strcmp(GetKey(brp), key)) break; else prp = brp; if (!brp) brp = MPP(prp->Vlp.Next = NewPair(key)); } else brp = MPP(bop->To_Val = NewPair(key)); SetPairValue(brp, MVP(bvp)); bop->Nd++; } // end of SetKeyValue /***********************************************************************/ /* Merge two objects. */ /***********************************************************************/ PBVAL BJSON::MergeObject(PBVAL bop1, PBVAL bop2) { CheckType(bop1, TYPE_JOB); CheckType(bop2, TYPE_JOB); if (bop1->To_Val) for (PBPR brp = GetObject(bop2); brp; brp = GetNext(brp)) SetKeyValue(bop1, GetVlp(brp), GetKey(brp)); else { bop1->To_Val = bop2->To_Val; bop1->Nd = bop2->Nd; } // endelse To_Val return bop1; } // end of MergeObject; /***********************************************************************/ /* Delete a value corresponding to the given key. */ /***********************************************************************/ bool BJSON::DeleteKey(PBVAL bop, PCSZ key) { CheckType(bop, TYPE_JOB); PBPR brp, pbrp = NULL; for (brp = GetObject(bop); brp; brp = GetNext(brp)) if (!strcmp(MZP(brp->Key), key)) { if (pbrp) { pbrp->Vlp.Next = brp->Vlp.Next; } else bop->To_Val = brp->Vlp.Next; bop->Nd--; return true;; } else pbrp = brp; return false; } // end of DeleteKey /***********************************************************************/ /* True if void or if all members are nulls. */ /***********************************************************************/ bool BJSON::IsObjectNull(PBVAL bop) { CheckType(bop, TYPE_JOB); for (PBPR brp = GetObject(bop); brp; brp = GetNext(brp)) if (brp->Vlp.To_Val && brp->Vlp.Type != TYPE_NULL) return false; return true; } // end of IsObjectNull /* ------------------------- Barray functions ------------------------ */ /***********************************************************************/ /* Return the number of values in this object. */ /***********************************************************************/ int BJSON::GetArraySize(PBVAL bap, bool b) { CheckType(bap, TYPE_JAR); int n = 0; for (PBVAL bvp = GetArray(bap); bvp; bvp = GetNext(bvp)) // If b, return only non null values if (!b || bvp->Type != TYPE_NULL) n++; return n; } // end of GetArraySize /***********************************************************************/ /* Get the Nth value of an Array. */ /***********************************************************************/ PBVAL BJSON::GetArrayValue(PBVAL bap, int n) { CheckType(bap, TYPE_JAR); int i = 0; if (n < 0) n += GetArraySize(bap); for (PBVAL bvp = GetArray(bap); bvp; bvp = GetNext(bvp), i++) if (i == n) return bvp; return NULL; } // end of GetArrayValue /***********************************************************************/ /* Add a Value to the Array Value list. */ /***********************************************************************/ void BJSON::AddArrayValue(PBVAL bap, OFFSET nbv, int* x) { CheckType(bap, TYPE_JAR); int i = 0; PBVAL bvp, lbp = NULL; if (!nbv) nbv = MOF(NewVal()); for (bvp = GetArray(bap); bvp; bvp = GetNext(bvp), i++) if (x && i == *x) break; else lbp = bvp; if (lbp) { MVP(nbv)->Next = lbp->Next; lbp->Next = nbv; } else { MVP(nbv)->Next = bap->To_Val; bap->To_Val = nbv; } // endif lbp bap->Nd++; } // end of AddArrayValue /***********************************************************************/ /* Merge two arrays. */ /***********************************************************************/ void BJSON::MergeArray(PBVAL bap1, PBVAL bap2) { CheckType(bap1, TYPE_JAR); CheckType(bap2, TYPE_JAR); if (bap1->To_Val) { for (PBVAL bvp = GetArray(bap2); bvp; bvp = GetNext(bvp)) AddArrayValue(bap1, MOF(DupVal(bvp))); } else { bap1->To_Val = bap2->To_Val; bap1->Nd = bap2->Nd; } // endif To_Val } // end of MergeArray /***********************************************************************/ /* Set the nth Value of the Array Value list or add it. */ /***********************************************************************/ void BJSON::SetArrayValue(PBVAL bap, PBVAL nvp, int n) { CheckType(bap, TYPE_JAR); int i = 0; PBVAL bvp = NULL; for (bvp = GetArray(bap); i < n; i++, bvp = bvp ? GetNext(bvp) : NULL) if (!bvp) AddArrayValue(bap, NewVal()); if (!bvp) AddArrayValue(bap, MOF(nvp)); else SetValueVal(bvp, nvp); } // end of SetValue /***********************************************************************/ /* Return the text corresponding to all values. */ /***********************************************************************/ PSZ BJSON::GetArrayText(PGLOBAL g, PBVAL bap, PSTRG text) { CheckType(bap, TYPE_JAR); if (bap->To_Val) { bool b; if (!text) { text = new(g) STRING(g, 256); b = true; } else { if (text->GetLastChar() != ' ') text->Append(" ("); else text->Append('('); b = false; } // endif text for (PBVAL bvp = GetArray(bap); bvp; bvp = GetNext(bvp)) { GetValueText(g, bvp, text); if (bvp->Next) text->Append(", "); else if (!b) text->Append(')'); } // endfor bvp if (b) { text->Trim(); return text->GetStr(); } // endif b } // endif To_Val return NULL; } // end of GetText; /***********************************************************************/ /* Delete a Value from the Arrays Value list. */ /***********************************************************************/ bool BJSON::DeleteValue(PBVAL bap, int n) { CheckType(bap, TYPE_JAR); int i = 0; PBVAL bvp, pvp = NULL; for (bvp = GetArray(bap); bvp; i++, bvp = GetNext(bvp)) if (i == n) { if (pvp) pvp->Next = bvp->Next; else bap->To_Val = bvp->Next; bap->Nd--; return true;; } else pvp = bvp; return false; } // end of DeleteValue /***********************************************************************/ /* True if void or if all members are nulls. */ /***********************************************************************/ bool BJSON::IsArrayNull(PBVAL bap) { CheckType(bap, TYPE_JAR); for (PBVAL bvp = GetArray(bap); bvp; bvp = GetNext(bvp)) if (bvp->Type != TYPE_NULL) return false; return true; } // end of IsNull /* ------------------------- Bvalue functions ------------------------ */ /***********************************************************************/ /* Sub-allocate and clear a BVAL. */ /***********************************************************************/ PBVAL BJSON::NewVal(int type) { PBVAL bvp = (PBVAL)BsonSubAlloc(sizeof(BVAL)); bvp->To_Val = 0; bvp->Nd = 0; bvp->Type = type; bvp->Next = 0; return bvp; } // end of SubAllocVal /***********************************************************************/ /* Sub-allocate and initialize a BVAL as type. */ /***********************************************************************/ PBVAL BJSON::SubAllocVal(OFFSET toval, int type, short nd) { PBVAL bvp = NewVal(type); bvp->To_Val = toval; bvp->Nd = nd; return bvp; } // end of SubAllocVal /***********************************************************************/ /* Sub-allocate and initialize a BVAL as string. */ /***********************************************************************/ PBVAL BJSON::SubAllocStr(OFFSET toval, short nd) { PBVAL bvp = NewVal(TYPE_STRG); bvp->To_Val = toval; bvp->Nd = nd; return bvp; } // end of SubAllocStr /***********************************************************************/ /* Allocate a BVALUE with a given string or numeric value. */ /***********************************************************************/ PBVAL BJSON::NewVal(PVAL valp) { PBVAL vlp = NewVal(); SetValue(vlp, valp); return vlp; } // end of SubAllocVal /***********************************************************************/ /* Sub-allocate and initialize a BVAL from another BVAL. */ /***********************************************************************/ PBVAL BJSON::DupVal(PBVAL bvlp) { if (bvlp) { PBVAL bvp = NewVal(); *bvp = *bvlp; bvp->Next = 0; return bvp; } else return NULL; } // end of DupVal /***********************************************************************/ /* Return the size of value's value. */ /***********************************************************************/ int BJSON::GetSize(PBVAL vlp, bool b) { switch (vlp->Type) { case TYPE_JAR: return GetArraySize(vlp); case TYPE_JOB: return GetObjectSize(vlp); default: return 1; } // enswitch Type } // end of GetSize PBVAL BJSON::GetBson(PBVAL bvp) { PBVAL bp = NULL; switch (bvp->Type) { case TYPE_JAR: bp = MVP(bvp->To_Val); break; case TYPE_JOB: bp = GetVlp(MPP(bvp->To_Val)); break; default: bp = bvp; break; } // endswitch Type return bp; } // end of GetBson /***********************************************************************/ /* Return the Value's as a Value struct. */ /***********************************************************************/ PVAL BJSON::GetValue(PGLOBAL g, PBVAL vp) { double d; PVAL valp; PBVAL vlp = vp->Type == TYPE_JVAL ? MVP(vp->To_Val) : vp; switch (vlp->Type) { case TYPE_STRG: case TYPE_DBL: case TYPE_BINT: valp = AllocateValue(g, MP(vlp->To_Val), vlp->Type, vlp->Nd); break; case TYPE_INTG: case TYPE_BOOL: valp = AllocateValue(g, vlp, vlp->Type); break; case TYPE_FLOAT: d = (double)vlp->F; valp = AllocateValue(g, &d, TYPE_DOUBLE, vlp->Nd); break; default: valp = NULL; break; } // endswitch Type return valp; } // end of GetValue /***********************************************************************/ /* Return the Value's Integer value. */ /***********************************************************************/ int BJSON::GetInteger(PBVAL vp) { int n; PBVAL vlp = (vp->Type == TYPE_JVAL) ? MVP(vp->To_Val) : vp; switch (vlp->Type) { case TYPE_INTG: n = vlp->N; break; case TYPE_FLOAT: n = (int)vlp->F; break; case TYPE_DTM: case TYPE_STRG: n = atoi(MZP(vlp->To_Val)); break; case TYPE_BOOL: n = (vlp->B) ? 1 : 0; break; case TYPE_BINT: n = (int)*(longlong*)MP(vlp->To_Val); break; case TYPE_DBL: n = (int)*(double*)MP(vlp->To_Val); break; default: n = 0; } // endswitch Type return n; } // end of GetInteger /***********************************************************************/ /* Return the Value's Big integer value. */ /***********************************************************************/ longlong BJSON::GetBigint(PBVAL vp) { longlong lln; PBVAL vlp = (vp->Type == TYPE_JVAL) ? MVP(vp->To_Val) : vp; switch (vlp->Type) { case TYPE_BINT: lln = *(longlong*)MP(vlp->To_Val); break; case TYPE_INTG: lln = (longlong)vlp->N; break; case TYPE_FLOAT: lln = (longlong)vlp->F; break; case TYPE_DBL: lln = (longlong)*(double*)MP(vlp->To_Val); break; case TYPE_DTM: case TYPE_STRG: lln = atoll(MZP(vlp->To_Val)); break; case TYPE_BOOL: lln = (vlp->B) ? 1 : 0; break; default: lln = 0; } // endswitch Type return lln; } // end of GetBigint /***********************************************************************/ /* Return the Value's Double value. */ /***********************************************************************/ double BJSON::GetDouble(PBVAL vp) { double d; PBVAL vlp = (vp->Type == TYPE_JVAL) ? MVP(vp->To_Val) : vp; switch (vlp->Type) { case TYPE_DBL: d = *(double*)MP(vlp->To_Val); break; case TYPE_BINT: d = (double)*(longlong*)MP(vlp->To_Val); break; case TYPE_INTG: d = (double)vlp->N; break; case TYPE_FLOAT: d = (double)vlp->F; break; case TYPE_DTM: case TYPE_STRG: d = atof(MZP(vlp->To_Val)); break; case TYPE_BOOL: d = (vlp->B) ? 1.0 : 0.0; break; default: d = 0.0; } // endswitch Type return d; } // end of GetDouble /***********************************************************************/ /* Return the Value's String value. */ /***********************************************************************/ PSZ BJSON::GetString(PBVAL vp, char* buff) { char buf[32]; char* p = (buff) ? buff : buf; PBVAL vlp = (vp->Type == TYPE_JVAL) ? MVP(vp->To_Val) : vp; switch (vlp->Type) { case TYPE_DTM: case TYPE_STRG: p = MZP(vlp->To_Val); break; case TYPE_INTG: sprintf(p, "%d", vlp->N); break; case TYPE_FLOAT: sprintf(p, "%.*f", vlp->Nd, vlp->F); break; case TYPE_BINT: sprintf(p, "%lld", *(longlong*)MP(vlp->To_Val)); break; case TYPE_DBL: sprintf(p, "%.*lf", vlp->Nd, *(double*)MP(vlp->To_Val)); break; case TYPE_BOOL: p = (PSZ)((vlp->B) ? "true" : "false"); break; case TYPE_NULL: p = (PSZ)"null"; break; default: p = NULL; } // endswitch Type return (p == buf) ? (PSZ)PlugDup(G, buf) : p; } // end of GetString /***********************************************************************/ /* Return the Value's String value. */ /***********************************************************************/ PSZ BJSON::GetValueText(PGLOBAL g, PBVAL vlp, PSTRG text) { if (vlp->Type == TYPE_JOB) return GetObjectText(g, vlp, text); else if (vlp->Type == TYPE_JAR) return GetArrayText(g, vlp, text); char buff[32]; PSZ s = (vlp->Type == TYPE_NULL) ? NULL : GetString(vlp, buff); if (s) text->Append(s); else if (GetJsonNull()) text->Append(GetJsonNull()); return NULL; } // end of GetText void BJSON::SetValueObj(PBVAL vlp, PBVAL bop) { CheckType(bop, TYPE_JOB); vlp->To_Val = bop->To_Val; vlp->Nd = bop->Nd; vlp->Type = TYPE_JOB; } // end of SetValueObj; void BJSON::SetValueArr(PBVAL vlp, PBVAL bap) { CheckType(bap, TYPE_JAR); vlp->To_Val = bap->To_Val; vlp->Nd = bap->Nd; vlp->Type = TYPE_JAR; } // end of SetValue; void BJSON::SetValueVal(PBVAL vlp, PBVAL vp) { vlp->To_Val = vp->To_Val; vlp->Nd = vp->Nd; vlp->Type = vp->Type; } // end of SetValue; PBVAL BJSON::SetValue(PBVAL vlp, PVAL valp) { if (!vlp) vlp = NewVal(); if (!valp || valp->IsNull()) { vlp->Type = TYPE_NULL; } else switch (valp->GetType()) { case TYPE_DATE: if (((DTVAL*)valp)->IsFormatted()) vlp->To_Val = DupStr(valp->GetCharValue()); else { char buf[32]; vlp->To_Val = DupStr(valp->GetCharString(buf)); } // endif Formatted vlp->Type = TYPE_DTM; break; case TYPE_STRING: vlp->To_Val = DupStr(valp->GetCharValue()); vlp->Type = TYPE_STRG; break; case TYPE_DOUBLE: case TYPE_DECIM: { double d = valp->GetFloatValue(); int nd = (IsTypeNum(valp->GetType())) ? valp->GetValPrec() : 0; if (nd > 0 && nd <= 6 && d >= FLT_MIN && d <= FLT_MAX) { vlp->F = (float)valp->GetFloatValue(); vlp->Type = TYPE_FLOAT; } else { double* dp = (double*)BsonSubAlloc(sizeof(double)); *dp = d; vlp->To_Val = MOF(dp); vlp->Type = TYPE_DBL; } // endif Nd vlp->Nd = MY_MIN(nd, 16); } break; case TYPE_TINY: vlp->B = valp->GetTinyValue() != 0; vlp->Type = TYPE_BOOL; break; case TYPE_INT: vlp->N = valp->GetIntValue(); vlp->Type = TYPE_INTG; break; case TYPE_BIGINT: if (valp->GetBigintValue() >= INT_MIN32 && valp->GetBigintValue() <= INT_MAX32) { vlp->N = valp->GetIntValue(); vlp->Type = TYPE_INTG; } else { longlong* llp = (longlong*)BsonSubAlloc(sizeof(longlong)); *llp = valp->GetBigintValue(); vlp->To_Val = MOF(llp); vlp->Type = TYPE_BINT; } // endif BigintValue break; default: sprintf(G->Message, "Unsupported typ %d\n", valp->GetType()); throw(777); } // endswitch Type return vlp; } // end of SetValue /***********************************************************************/ /* Set the Value's value as the given integer. */ /***********************************************************************/ void BJSON::SetInteger(PBVAL vlp, int n) { vlp->N = n; vlp->Type = TYPE_INTG; } // end of SetInteger /***********************************************************************/ /* Set the Value's Boolean value as a tiny integer. */ /***********************************************************************/ void BJSON::SetBool(PBVAL vlp, bool b) { vlp->B = b; vlp->Type = TYPE_BOOL; } // end of SetTiny /***********************************************************************/ /* Set the Value's value as the given big integer. */ /***********************************************************************/ void BJSON::SetBigint(PBVAL vlp, longlong ll) { if (ll >= INT_MIN32 && ll <= INT_MAX32) { vlp->N = (int)ll; vlp->Type = TYPE_INTG; } else { longlong* llp = (longlong*)PlugSubAlloc(G, NULL, sizeof(longlong)); *llp = ll; vlp->To_Val = MOF(llp); vlp->Type = TYPE_BINT; } // endif ll } // end of SetBigint /***********************************************************************/ /* Set the Value's value as the given DOUBLE. */ /***********************************************************************/ void BJSON::SetFloat(PBVAL vlp, double d, int prec) { int nd = MY_MIN((prec < 0) ? GetJsonDefPrec() : prec, 16); if (nd < 6 && d >= FLT_MIN && d <= FLT_MAX) { vlp->F = (float)d; vlp->Type = TYPE_FLOAT; } else { double* dp = (double*)BsonSubAlloc(sizeof(double)); *dp = d; vlp->To_Val = MOF(dp); vlp->Type = TYPE_DBL; } // endif nd vlp->Nd = nd; } // end of SetFloat /***********************************************************************/ /* Set the Value's value as the given DOUBLE representation. */ /***********************************************************************/ void BJSON::SetFloat(PBVAL vlp, PSZ s) { char *p = strchr(s, '.'); int nd = 0; double d = atof(s); if (p) { for (++p; isdigit(*p); nd++, p++); for (--p; *p == '0'; nd--, p--); } // endif p SetFloat(vlp, d, nd); } // end of SetFloat /***********************************************************************/ /* Set the Value's value as the given string. */ /***********************************************************************/ void BJSON::SetString(PBVAL vlp, PSZ s, int ci) { vlp->To_Val = MOF(s); vlp->Nd = ci; vlp->Type = TYPE_STRG; } // end of SetString /***********************************************************************/ /* True when its JSON or normal value is null. */ /***********************************************************************/ bool BJSON::IsValueNull(PBVAL vlp) { bool b; switch (vlp->Type) { case TYPE_NULL: b = true; break; case TYPE_JOB: b = IsObjectNull(vlp); break; case TYPE_JAR: b = IsArrayNull(vlp); break; default: b = false; } // endswitch Type return b; } // end of IsNull