diff --git a/inchi/INCHI_WEB/inchi_web.c b/inchi/INCHI_WEB/inchi_web.c index a914415..7ad61ee 100644 --- a/inchi/INCHI_WEB/inchi_web.c +++ b/inchi/INCHI_WEB/inchi_web.c @@ -1,7 +1,10 @@ #include #include +#include #include "inchi_api.h" +#include "util.h" #include +#include "mode.h" /* * Note on the use of malloc(): @@ -24,13 +27,14 @@ * Safe way to serialize to JSON. * See https://livebook.manning.com/book/webassembly-in-action/c-emscripten-macros/v-7/67 */ -EM_JS(char*, to_json_inchi, (int return_code, char *inchi, char *auxinfo, char *message, char *log), { +EM_JS(char*, to_json_inchi, (int return_code, char *inchi, char *auxinfo, char *message, char *log, char *ver), { const json = JSON.stringify({ "return_code": return_code, "inchi": Module.UTF8ToString(inchi), "auxinfo": Module.UTF8ToString(auxinfo), "message": Module.UTF8ToString(message), - "log": Module.UTF8ToString(log) + "log": Module.UTF8ToString(log), + "version": Module.UTF8ToString(ver) }); const byteCount = Module.lengthBytesUTF8(json) + 1; @@ -52,22 +56,22 @@ char* inchi_from_molfile(char *molfile, char *options) { switch(ret) { case mol2inchi_Ret_OKAY: { - json = to_json_inchi(0, output->szInChI, output->szAuxInfo, "", ""); + json = to_json_inchi(0, output->szInChI, output->szAuxInfo, "", "", APP_DESCRIPTION); break; } case mol2inchi_Ret_WARNING: { - json = to_json_inchi(1, output->szInChI, output->szAuxInfo, output->szMessage, output->szLog); + json = to_json_inchi(1, output->szInChI, output->szAuxInfo, output->szMessage, output->szLog, APP_DESCRIPTION); break; } case mol2inchi_Ret_EOF: case mol2inchi_Ret_ERROR: case mol2inchi_Ret_ERROR_get: case mol2inchi_Ret_ERROR_comp: { - json = to_json_inchi(-1, "", "", output->szMessage, output->szLog); + json = to_json_inchi(-1, "", "", output->szMessage, output->szLog, APP_DESCRIPTION); break; } default: { - json = to_json_inchi(-1, "", "", "", "MakeINCHIFromMolfileText: Unknown return code"); + json = to_json_inchi(-1, "", "", "", "MakeINCHIFromMolfileText: Unknown return code", APP_DESCRIPTION); } } @@ -78,15 +82,67 @@ char* inchi_from_molfile(char *molfile, char *options) { return json; } + + + + +/* BH new -- also in JNI-InChI + * + * InChI from InChI + * ---------------- + */ + +char* inchi_from_inchi(char* inchi, char* options) { + int ret; + inchi_InputINCHI input; + inchi_Output *output; + char *json; + + input.szInChI = inchi; + input.szOptions = options; + + output = malloc(sizeof(*output)); + memset(output, 0, sizeof(*output)); + + ret = GetINCHIfromINCHI(&input, output); + + switch(ret) { + case inchi_Ret_OKAY: { + json = to_json_inchi(0, output->szInChI, "", "", "", APP_DESCRIPTION); + break; + } + case inchi_Ret_WARNING: { + json = to_json_inchi(1, output->szInChI, "", output->szMessage, output->szLog, APP_DESCRIPTION); + break; + } + default: { + json = to_json_inchi(1, "", "", output->szMessage, output->szLog, APP_DESCRIPTION); + } + } + + FreeINCHI(output); + free(output); + + + // Caller should free this. + return json; + } + + + + + + /* * InChIKey from InChI * ------------------- */ -EM_JS(char*, to_json_inchikey, (int return_code, char *inchikey, char *message), { +EM_JS(char*, to_json_inchikey, (int return_code, char *inchikey, char *message, char *ver), { const json = JSON.stringify({ "return_code": return_code, "inchikey": Module.UTF8ToString(inchikey), - "message": Module.UTF8ToString(message) + "message": Module.UTF8ToString(message), + "version": Module.UTF8ToString(ver) }); const byteCount = Module.lengthBytesUTF8(json) + 1; @@ -105,35 +161,35 @@ char* inchikey_from_inchi(char* inchi) { switch(ret) { case INCHIKEY_OK: { - json = to_json_inchikey(0, szINCHIKey, ""); + json = to_json_inchikey(0, szINCHIKey, "", APP_DESCRIPTION); break; } case INCHIKEY_UNKNOWN_ERROR: { - json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Unknown program error"); + json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Unknown program error", APP_DESCRIPTION); break; } case INCHIKEY_EMPTY_INPUT: { - json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Source string is empty"); + json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Source string is empty", APP_DESCRIPTION); break; } case INCHIKEY_INVALID_INCHI_PREFIX: { - json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Invalid InChI prefix or invalid version (not 1)"); + json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Invalid InChI prefix or invalid version (not 1)", APP_DESCRIPTION); break; } case INCHIKEY_NOT_ENOUGH_MEMORY: { - json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Not enough memory"); + json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Not enough memory", APP_DESCRIPTION); break; } case INCHIKEY_INVALID_INCHI: { - json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Source InChI has invalid layout"); + json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Source InChI has invalid layout", APP_DESCRIPTION); break; } case INCHIKEY_INVALID_STD_INCHI: { - json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Source standard InChI has invalid layout"); + json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Source standard InChI has invalid layout", APP_DESCRIPTION); break; } default: { - json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Unknown return code"); + json = to_json_inchikey(-1, "", "GetINCHIKeyFromINCHI: Unknown return code", APP_DESCRIPTION); } } @@ -145,12 +201,13 @@ char* inchikey_from_inchi(char* inchi) { * Molfile from InChI * ------------------ */ -EM_JS(char*, to_json_molfile, (int return_code, char *molfile, char *message, char *log), { +EM_JS(char*, to_json_molfile, (int return_code, char *molfile, char *message, char *log, char *ver), { const json = JSON.stringify({ "return_code": return_code, "molfile": Module.UTF8ToString(molfile), "message": Module.UTF8ToString(message), - "log": Module.UTF8ToString(log) + "log": Module.UTF8ToString(log), + "version": Module.UTF8ToString(ver) }); const byteCount = Module.lengthBytesUTF8(json) + 1; @@ -199,7 +256,7 @@ char* molfile_from_inchi(char* inchi, char* options) { // TODO: Handle return value of this API call. GetINCHIEx(&inputEx, &outputEx); - json = to_json_molfile(0, outputEx.szInChI, "", ""); + json = to_json_molfile(0, outputEx.szInChI, "", "", APP_DESCRIPTION); FreeINCHI(&outputEx); break; @@ -212,7 +269,7 @@ char* molfile_from_inchi(char* inchi, char* options) { // TODO: Handle return value of this API call. GetINCHIEx(&inputEx, &outputEx); - json = to_json_molfile(1, outputEx.szInChI, output->szMessage, output->szLog); + json = to_json_molfile(1, outputEx.szInChI, output->szMessage, output->szLog, APP_DESCRIPTION); FreeINCHI(&outputEx); break; @@ -223,11 +280,11 @@ char* molfile_from_inchi(char* inchi, char* options) { case inchi_Ret_BUSY: case inchi_Ret_EOF: case inchi_Ret_SKIP: { - json = to_json_molfile(-1, "", output->szMessage, output->szLog); + json = to_json_molfile(-1, "", output->szMessage, output->szLog, APP_DESCRIPTION); break; } default: - json = to_json_molfile(-1, "", "", "GetStructFromINCHIEx: Unknown return code"); + json = to_json_molfile(-1, "", "", "GetStructFromINCHIEx: Unknown return code", APP_DESCRIPTION); } FreeStructFromINCHIEx(output); @@ -283,7 +340,7 @@ char* molfile_from_auxinfo(char* auxinfo, int bDoNotAddH, int bDiffUnkUndfStereo // TODO: Handle return value of this API call. GetINCHI(pInp, &inchi_output); - json = to_json_molfile(0, inchi_output.szInChI, "", ""); + json = to_json_molfile(0, inchi_output.szInChI, "", "", APP_DESCRIPTION); FreeINCHI(&inchi_output); break; @@ -295,7 +352,7 @@ char* molfile_from_auxinfo(char* auxinfo, int bDoNotAddH, int bDiffUnkUndfStereo // TODO: Handle return value of this API call. GetINCHI(pInp, &inchi_output); - json = to_json_molfile(1, inchi_output.szInChI, output->szErrMsg, ""); + json = to_json_molfile(1, inchi_output.szInChI, output->szErrMsg, "", APP_DESCRIPTION); FreeINCHI(&inchi_output); break; @@ -306,11 +363,11 @@ char* molfile_from_auxinfo(char* auxinfo, int bDoNotAddH, int bDiffUnkUndfStereo case inchi_Ret_BUSY: case inchi_Ret_EOF: case inchi_Ret_SKIP: { - json = to_json_molfile(-1, "", output->szErrMsg, ""); + json = to_json_molfile(-1, "", output->szErrMsg, "", APP_DESCRIPTION); break; } default: - json = to_json_molfile(-1, "", "", "Get_inchi_Input_FromAuxInfo: Unknown return code"); + json = to_json_molfile(-1, "", "", "Get_inchi_Input_FromAuxInfo: Unknown return code", APP_DESCRIPTION); } Free_inchi_Input(pInp); @@ -320,3 +377,370 @@ char* molfile_from_auxinfo(char* auxinfo, int bDoNotAddH, int bDiffUnkUndfStereo // Caller should free this. return json; } + + + + +//from JniInchiWrapper.c: + +/* === INCHI to STRUCTURE === */ + +//JNIEXPORT jobject JNICALL Java_net_sf_jniinchi_JniInchiWrapper_GetStructFromINCHI +// (JNIEnv *env, jobject obj, jstring inchi, jstring options) { +// +// int i, j, ret; +// inchi_InputINCHI inchi_inp; +// inchi_OutputStruct inchi_out; +// jobject output; +// int numatoms, numstereo; +// +// #ifdef DEBUG +// fprintf(stderr, "__GetStructFromINCHI()\n"); +// #endif +// +// initInchiInputINCHI(env, &inchi_inp, inchi, options); +// +// ret = GetStructFromINCHI(&inchi_inp, &inchi_out); +// +// output = (*env)->NewObject(env, jniInchiOutputStructure, initJniInchiOutputStructure, +// ret, +// (*env)->NewStringUTF(env, inchi_out.szMessage), +// (*env)->NewStringUTF(env, inchi_out.szLog), +// inchi_out.WarningFlags[0][0], +// inchi_out.WarningFlags[0][1], +// inchi_out.WarningFlags[1][0], +// inchi_out.WarningFlags[1][1]); +// +// numatoms = inchi_out.num_atoms; +// numstereo = inchi_out.num_stereo0D; +// +// createAtoms(env, numatoms, inchi_out.atom, output); +// createBonds(env, numatoms, inchi_out.atom, output); +// createStereos(env, numstereo, inchi_out.stereo0D, output); +// +// FreeStructFromINCHI(&inchi_out); +// free(inchi_inp.szInChI); +// free(inchi_inp.szOptions); +// +// #ifdef DEBUG +// fprintf(stderr, "__GetStructFromINCHI__\n"); +// #endif +// +// return output; +//} + +//MODEL FROM INCHI -- BH SwingJS + +/* +* Safe way to serialize to JSON. +* See https://livebook.manning.com/book/webassembly-in-action/c-emscripten-macros/v-7/67 +*/ +EM_JS(char*, to_json_model, (int return_code, char *model, char *message, char *log, char *ver), { + const json = JSON.stringify({ + "return_code": return_code, + "model": Module.UTF8ToString(model), + "message": Module.UTF8ToString(message), + "log": Module.UTF8ToString(log), + "version": Module.UTF8ToString(ver) + }); + const byteCount = Module.lengthBytesUTF8(json) + 1; + const jsonPtr = Module._malloc(byteCount); + Module.stringToUTF8(json, jsonPtr, byteCount); + return jsonPtr; +}); + +void strRadical(char* buf, S_CHAR radical) { + strcat(buf, "\"radical\":\""); + switch (radical) { + default: + case INCHI_RADICAL_NONE: + strcat(buf, "NONE"); + break; + case INCHI_RADICAL_SINGLET: + strcat(buf, "SINGLET"); + break; + case INCHI_RADICAL_DOUBLET: + strcat(buf, "DOUBLET"); + break; + case INCHI_RADICAL_TRIPLET: + strcat(buf, "TRIPLET"); + break; + } + strcat(buf, "\""); +} + +void addJSONAtoms(char* s, inchi_OutputStructEx *struc) { + inchi_Atom* atoms = struc->atom; + int numatoms = struc->num_atoms; + sprintf(s + strlen(s), "\"atomCount\":%d,", numatoms); + strcat(s, "\"atoms\":["); + int i; + int haveXYZ = 0; + for (i = 0; i < numatoms; i++) { + inchi_Atom a = atoms[i]; + if (a.x != 0 || a.y != 0 || a.z != 0) { + haveXYZ = 1; + break; + } + } + for (i = 0; i < numatoms; i++) { + inchi_Atom a = atoms[i]; + char *elem = a.elname; + int charge = a.charge; + int implicitH = a.num_iso_H[0]; + int implicitP = a.num_iso_H[1]; + int implicitD = a.num_iso_H[2]; + int implicitT = a.num_iso_H[3]; + int isotopicMass = a.isotopic_mass; + if (i > 0) + strcat(s, ","); + strcat(s, "{"); + sprintf(s + strlen(s), "\"index\":%d", i); + sprintf(s + strlen(s), ",\"elname\":\"%s\"", elem); + if (haveXYZ == 1) { + sprintf(s + strlen(s), ",\"x\":%.4f,\"y\":%.4f,\"z\":%.4f", a.x, a.y, a.z); + } + if (isotopicMass != 0) { + if (isotopicMass >= ISOTOPIC_SHIFT_FLAG - ISOTOPIC_SHIFT_MAX) { // 9000 + isotopicMass -= ISOTOPIC_SHIFT_FLAG; // 10000 + isotopicMass += get_atomic_mass(elem); + } + sprintf(s + strlen(s), ",\"isotopicMass\":%d", isotopicMass); + } + if (charge != 0) { + sprintf(s + strlen(s), ",\"charge\":%d", charge); + } + if (a.radical != INCHI_RADICAL_NONE) { + strcat(s, ","); + strRadical(s, a.radical); + } + if (implicitH > 0) { // this one can be -1 (for a hydrogen) + sprintf(s + strlen(s), ",\"implicitH\":%d", implicitH); + } + if (implicitP != 0) { + sprintf(s + strlen(s), ",\"implicitProtium\":%d", implicitP); + } + if (implicitD != 0) { + sprintf(s + strlen(s), ",\"implicitDeuterium\":%d", implicitD); + } + if (implicitT != 0) { + sprintf(s + strlen(s), ",\"implicitTritium\":%d", implicitT); + } + strcat(s, "}"); + } + strcat(s, "]"); +} + +void strBondType(char* buf, S_CHAR type) { + strcat(buf, "\"type\":\""); + switch (type) { + case INCHI_BOND_TYPE_NONE: + strcat(buf, "NONE"); + break; + default: + case INCHI_BOND_TYPE_SINGLE: + strcat(buf, "SINGLE"); + break; + case INCHI_BOND_TYPE_DOUBLE: + strcat(buf, "DOUBLE"); + break; + case INCHI_BOND_TYPE_TRIPLE: + strcat(buf, "TRIPLE"); + break; + case INCHI_BOND_TYPE_ALTERN: + strcat(buf, "ALTERN"); + break; + } + strcat(buf, "\""); +} + +void strBondStereo(char* buf, S_CHAR stereo) { + // I don't think these can be present in an output structure + strcat(buf, "\"stereo\":\""); + switch (stereo) { + default: + case INCHI_BOND_STEREO_NONE: + strcat(buf, "NONE"); + break; + case INCHI_BOND_STEREO_SINGLE_1UP: + strcat(buf, "SINGLE_1UP"); + break; + case INCHI_BOND_STEREO_SINGLE_1EITHER: + strcat(buf, "SINGLE_1EITHER"); + break; + case INCHI_BOND_STEREO_SINGLE_1DOWN: + strcat(buf, "SINGLE_1DOWN"); + break; + case INCHI_BOND_STEREO_SINGLE_2UP: + strcat(buf, "SINGLE_2UP"); + break; + case INCHI_BOND_STEREO_SINGLE_2EITHER: + strcat(buf, "SINGLE_2EITHER"); + break; + case INCHI_BOND_STEREO_SINGLE_2DOWN: + strcat(buf, "SINGLE_2DOWN"); + break; + case INCHI_BOND_STEREO_DOUBLE_EITHER: + strcat(buf, "DOUBLE_EITHER"); + break; + } + strcat(buf, "\""); +} + +void addJSONBonds(char* s, inchi_OutputStructEx *struc) { + int numatoms = struc->num_atoms; + inchi_Atom* atoms = struc->atom; + strcat(s, ",\"bonds\":["); + int i, j, n; + n = 0; + for (i = 0; i < numatoms; i++) { + inchi_Atom a = atoms[i]; + int numbonds = a.num_bonds; + if (numbonds > 0) { + for (j = 0; j < numbonds; j++) { + /* Bonds get recorded twice, so only pick one direction... */ + int k = a.neighbor[j]; + if (k < i) { + if (n > 0) + strcat(s, ","); + n++; + sprintf(s + strlen(s), "{\"originAtom\":%d,\"targetAtom\":%d", i, k); + if (a.bond_type[j] != INCHI_BOND_TYPE_SINGLE) { + strcat(s, ","); + strBondType(s, a.bond_type[j]); // NONE?? + } + if (a.bond_stereo[j] != INCHI_BOND_STEREO_NONE) { + strcat(s, ","); + strBondStereo(s, a.bond_stereo[j]); + } + strcat(s, "}"); + } + } + } + } + strcat(s, "]"); + sprintf(s + strlen(s), ",\"bondCount\":%d", n); +} + +void strStereoType(char* buf, S_CHAR type) { + strcat(buf, "\"type\":\""); + switch (type) { + default: + case INCHI_StereoType_None: + strcat(buf, "NONE"); + break; + case INCHI_StereoType_DoubleBond: + strcat(buf, "DOUBLEBOND"); + break; + case INCHI_StereoType_Tetrahedral: + strcat(buf, "TETRAHEDRAL"); + break; + case INCHI_StereoType_Allene: + strcat(buf, "ALLENE"); + break; + } + strcat(buf, "\""); +} + +void strParity(char* buf, S_CHAR parity) { + strcat(buf, "\"parity\":\""); + switch (parity) { + default: + case INCHI_PARITY_NONE: + strcat(buf, "NONE"); + break; + case INCHI_PARITY_ODD: + strcat(buf, "ODD"); + break; + case INCHI_PARITY_EVEN: + strcat(buf, "EVEN"); + break; + case INCHI_PARITY_UNKNOWN: + strcat(buf, "UNKNOWN"); + break; + case INCHI_PARITY_UNDEFINED: + strcat(buf, "UNDEFINED"); + break; + } + strcat(buf, "\""); +} + +void addJSONStereos(char* s, inchi_OutputStructEx *struc) { + int numstereo = struc->num_stereo0D; + if (numstereo == 0) + return; + sprintf(s + strlen(s), ",\"stereoCount\":%d", numstereo); + inchi_Stereo0D* stereos = struc->stereo0D; + strcat(s, ",\"stereo\":["); + int i; + for (i = 0; i < numstereo; i++) { + if (i > 0) + strcat(s, ","); + strcat (s, "{"); + inchi_Stereo0D istereo = stereos[i]; + strStereoType(s, istereo.type); + strcat(s, ","); + strParity(s, istereo.parity); + strcat(s, ","); + sprintf(s + strlen(s), "\"neighbors\":[%d,%d,%d,%d]", istereo.neighbor[0], istereo.neighbor[1], istereo.neighbor[2], istereo.neighbor[3]); + if (istereo.central_atom != NO_ATOM) { + strcat(s, ","); + sprintf(s + strlen(s), "\"centralAtom\":%d",istereo.central_atom); + } + strcat(s, "}"); + } + strcat(s, "]"); +} + +void getModelJSON(char* json, inchi_OutputStructEx *struc) { + strcat(json,"{"); + addJSONAtoms(json, struc); + addJSONBonds(json, struc); + addJSONStereos(json, struc); + strcat(json, "}"); +} + +char* model_from_inchi(char* inchi, char* options) { + char *json; + int ret; + inchi_InputINCHI input; + inchi_OutputStructEx *output; + input.szInChI = inchi; + input.szOptions = options; + output = malloc(sizeof(*output)); + memset(output, 0, sizeof(*output)); + ret = GetStructFromINCHIEx(&input, output); + switch(ret) { + case inchi_Ret_OKAY: + case inchi_Ret_WARNING: { + char *szMsg = (ret == inchi_Ret_WARNING ? output->szMessage : ""); + char *szLog = (ret == inchi_Ret_WARNING ? output->szLog : ""); + int numatoms, numstereo; + numatoms = output->num_atoms; + numstereo = output->num_stereo0D; + // atoms, bonds, and stereo generous appoximations of JSON length + char buf[numatoms*160 + numatoms*55 + numstereo*100]; + buf[0] = 0; + getModelJSON(buf, output); + json = to_json_model(0, buf, szMsg, szLog, APP_DESCRIPTION); + break; + } + case inchi_Ret_ERROR: + case inchi_Ret_FATAL: + case inchi_Ret_UNKNOWN: + case inchi_Ret_BUSY: + case inchi_Ret_EOF: + case inchi_Ret_SKIP: { + json = to_json_model(-1, "", output->szMessage, output->szLog, APP_DESCRIPTION); + break; + } + default: + json = to_json_model(-1, "", "", "GetStructFromINCHIEx: Unknown return code", APP_DESCRIPTION); + } + + //Free_inchi_Input(input); + FreeStructFromINCHIEx(output); + free(output); + + return json; +}