diff --git a/Code/GraphMol/MolPickler.h b/Code/GraphMol/MolPickler.h index 1187397ff..c5e5fac7f 100644 --- a/Code/GraphMol/MolPickler.h +++ b/Code/GraphMol/MolPickler.h @@ -30,6 +30,7 @@ #include #endif #include +#include namespace RDKit { class ROMol; @@ -48,20 +49,19 @@ class RDKIT_GRAPHMOL_EXPORT MolPicklerException : public std::exception { }; namespace PicklerOps { -typedef enum { - NoProps = 0, // no data pickled (default pickling, single-precision coords) - MolProps = 0x1, // only public non computed properties - AtomProps = 0x2, - BondProps = 0x4, - QueryAtomData = - 0x2, // n.b. DEPRECATED and set to AtomProps (does the same work) - PrivateProps = 0x10, - ComputedProps = 0x20, - AllProps = 0x0000FFFF, // all data pickled - CoordsAsDouble = 0x00010000, // save coordinates in double precision - NoConformers = - 0x00020000 // do not include conformers or associated properties -} PropertyPickleOptions; +BETTER_ENUM( + PropertyPickleOptions, unsigned int, + NoProps = 0, // no data pickled (default pickling, single-precision coords) + MolProps = 0x1, // only public non computed properties + AtomProps = 0x2, BondProps = 0x4, + QueryAtomData = + 0x2, // n.b. DEPRECATED and set to AtomProps (does the same work) + PrivateProps = 0x10, ComputedProps = 0x20, + AllProps = 0x0000FFFF, // all data pickled + CoordsAsDouble = 0x00010000, // save coordinates in double precision + NoConformers = + 0x00020000 // do not include conformers or associated properties +); } // namespace PicklerOps //! handles pickling (serializing) molecules @@ -188,10 +188,12 @@ class RDKIT_GRAPHMOL_EXPORT MolPickler { MolPickler::molFromPickle(pickle, &mol, propertyFlags); } static void molFromPickle(const std::string &pickle, ROMol *mol) { - MolPickler::molFromPickle(pickle, mol, PicklerOps::AllProps); + MolPickler::molFromPickle(pickle, mol, + PicklerOps::PropertyPickleOptions::AllProps); } static void molFromPickle(const std::string &pickle, ROMol &mol) { - MolPickler::molFromPickle(pickle, &mol, PicklerOps::AllProps); + MolPickler::molFromPickle(pickle, &mol, + PicklerOps::PropertyPickleOptions::AllProps); } //! constructs a molecule from a pickle stored in a stream @@ -202,10 +204,12 @@ class RDKIT_GRAPHMOL_EXPORT MolPickler { MolPickler::molFromPickle(ss, &mol, propertyFlags); } static void molFromPickle(std::istream &ss, ROMol *mol) { - MolPickler::molFromPickle(ss, mol, PicklerOps::AllProps); + MolPickler::molFromPickle(ss, mol, + PicklerOps::PropertyPickleOptions::AllProps); } static void molFromPickle(std::istream &ss, ROMol &mol) { - MolPickler::molFromPickle(ss, &mol, PicklerOps::AllProps); + MolPickler::molFromPickle(ss, &mol, + PicklerOps::PropertyPickleOptions::AllProps); } private: diff --git a/Code/JavaWrappers/ROMol.i b/Code/JavaWrappers/ROMol.i index 46a7cc482..99ae4f140 100644 --- a/Code/JavaWrappers/ROMol.i +++ b/Code/JavaWrappers/ROMol.i @@ -192,6 +192,7 @@ %include "enums.swg" %javaconst(1); #endif +%include %include #ifdef SWIGJAVA %javaconst(0); diff --git a/Code/MinimalLib/CMakeLists.txt b/Code/MinimalLib/CMakeLists.txt index 21fffecf7..05a0d82a0 100644 --- a/Code/MinimalLib/CMakeLists.txt +++ b/Code/MinimalLib/CMakeLists.txt @@ -34,7 +34,7 @@ if(RDK_BUILD_MINIMAL_LIB) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${USE_FLAGS}") endif() endif() - add_executable(RDKit_minimal jswrapper.cpp minilib.cpp) + add_executable(RDKit_minimal jswrapper.cpp minilib.cpp JSONParsers.cpp) target_link_libraries(RDKit_minimal ${MINIMAL_LIB_LIBRARIES}) set_target_properties(RDKit_minimal PROPERTIES LINK_FLAGS "--bind") @@ -67,7 +67,7 @@ if(RDK_BUILD_CFFI_LIB) set(LIBS_TO_USE ${tmpLibs}) endif() - add_library(rdkitcffi SHARED cffiwrapper.cpp) + add_library(rdkitcffi SHARED cffiwrapper.cpp JSONParsers.cpp) target_link_libraries(rdkitcffi PUBLIC rdkit_base) target_link_libraries(rdkitcffi PUBLIC ${LIBS_TO_USE}) INSTALL(TARGETS rdkitcffi EXPORT rdkit-targets diff --git a/Code/MinimalLib/JSONParsers.cpp b/Code/MinimalLib/JSONParsers.cpp new file mode 100644 index 000000000..667872eb3 --- /dev/null +++ b/Code/MinimalLib/JSONParsers.cpp @@ -0,0 +1,48 @@ +// +// Copyright (C) 2024 Novartis Biomedical Research and other RDKit contributors +// +// @@ All Rights Reserved @@ +// This file is part of the RDKit. +// The contents are covered by the terms of the BSD license +// which is included in the file license.txt, found at the root +// of the RDKit source tree. +// + +#define USE_BETTER_ENUMS +#include "JSONParsers.h" +#include +#include +#include +#include +#include +#include +#include + +namespace RDKit { +namespace MinimalLib { + +void updatePropertyPickleOptionsFromJSON(unsigned int &propFlags, + const char *details_json) { + if (details_json && strlen(details_json)) { + std::istringstream ss; + boost::property_tree::ptree pt; + ss.str(details_json); + boost::property_tree::read_json(ss, pt); + const auto nodeIt = pt.find("propertyFlags"); + if (nodeIt != pt.not_found()) { + auto propertyFlagsFromJson = + (+PicklerOps::PropertyPickleOptions::NoProps)._to_integral(); + for (const auto *key : PicklerOps::PropertyPickleOptions::_names()) { + if (nodeIt->second.get(key, false)) { + propertyFlagsFromJson |= + PicklerOps::PropertyPickleOptions::_from_string(key) + ._to_integral(); + } + } + propFlags = propertyFlagsFromJson; + } + } +} + +} // end namespace MinimalLib +} // end namespace RDKit diff --git a/Code/MinimalLib/JSONParsers.h b/Code/MinimalLib/JSONParsers.h new file mode 100644 index 000000000..581ebe09c --- /dev/null +++ b/Code/MinimalLib/JSONParsers.h @@ -0,0 +1,22 @@ +// +// Copyright (C) 2024 Novartis Biomedical Research and other RDKit contributors +// +// @@ All Rights Reserved @@ +// This file is part of the RDKit. +// The contents are covered by the terms of the BSD license +// which is included in the file license.txt, found at the root +// of the RDKit source tree. +// + +#pragma once + +#include + +namespace RDKit { +namespace MinimalLib { + +void updatePropertyPickleOptionsFromJSON(unsigned int &propFlags, + const char *details_json); + +} // end namespace MinimalLib +} // end namespace RDKit diff --git a/Code/MinimalLib/cffi_test.c b/Code/MinimalLib/cffi_test.c index 2948b5b29..9e6c4643a 100644 --- a/Code/MinimalLib/cffi_test.c +++ b/Code/MinimalLib/cffi_test.c @@ -2765,6 +2765,75 @@ void test_custom_palette() { free(mpkl); } +void test_props() { + printf("--------------------------\n"); + printf(" mol props\n"); + const char *smi = "c1ccccn1"; + const char *cxsmi_in = "c1ccccn1 |atomProp:0.a.1|"; + const char *details = "{\"NoProps\":true}"; + char *mpkl; + size_t mpkl_size; + char **prop_list; + char *prop; + char *cxsmi_out; + mpkl = get_mol(smi, &mpkl_size, ""); + assert(mpkl && mpkl_size); + prop_list = get_prop_list(mpkl, mpkl_size, 0, 0); + assert(prop_list && !prop_list[0]); + free(prop_list); + set_prop(&mpkl, &mpkl_size, "a", "1", 0); + set_prop(&mpkl, &mpkl_size, "b", "2", 0); + prop_list = get_prop_list(mpkl, mpkl_size, 0, 0); + assert(prop_list && prop_list[0] && prop_list[1] && !prop_list[2]); + assert(!strcmp(prop_list[0], "a")); + free(prop_list[0]); + assert(!strcmp(prop_list[1], "b")); + free(prop_list[1]); + free(prop_list); + assert(has_prop(mpkl, mpkl_size, "a")); + assert(has_prop(mpkl, mpkl_size, "b")); + assert(!has_prop(mpkl, mpkl_size, "c")); + prop = get_prop(mpkl, mpkl_size, "a"); + assert(prop); + assert(!strcmp(prop, "1")); + free(prop); + prop = get_prop(mpkl, mpkl_size, "b"); + assert(prop); + assert(!strcmp(prop, "2")); + free(prop); + assert(clear_prop(&mpkl, &mpkl_size, "a")); + assert(!clear_prop(&mpkl, &mpkl_size, "c")); + prop_list = get_prop_list(mpkl, mpkl_size, 0, 0); + assert(prop_list && prop_list[0] && !prop_list[1]); + assert(!strcmp(prop_list[0], "b")); + free(prop_list[0]); + free(prop_list); + prop = get_prop(mpkl, mpkl_size, "a"); + assert(!prop); + prop = get_prop(mpkl, mpkl_size, "b"); + assert(prop); + assert(!strcmp(prop, "2")); + free(prop); + keep_props(&mpkl, &mpkl_size, "{\"propertyFlags\":{\"NoProps\":true}}"); + prop_list = get_prop_list(mpkl, mpkl_size, 0, 0); + assert(prop_list && !prop_list[0]); + free(prop_list); + prop = get_prop(mpkl, mpkl_size, "b"); + assert(!prop); + free(mpkl); + mpkl = get_mol(cxsmi_in, &mpkl_size, ""); + cxsmi_out = get_cxsmiles(mpkl, mpkl_size, ""); + assert(!strcmp(cxsmi_out, "c1ccncc1 |atomProp:2.a.1|")); + free(cxsmi_out); + free(mpkl); + mpkl = + get_mol(cxsmi_in, &mpkl_size, "{\"propertyFlags\":{\"NoProps\":true}}"); + cxsmi_out = get_cxsmiles(mpkl, mpkl_size, ""); + assert(!strcmp(cxsmi_out, "c1ccncc1")); + free(cxsmi_out); + free(mpkl); +} + int main() { enable_logging(); char *vers = version(); @@ -2802,5 +2871,6 @@ int main() { test_multi_highlights(); test_bw_palette(); test_custom_palette(); + test_props(); return 0; } diff --git a/Code/MinimalLib/cffiwrapper.cpp b/Code/MinimalLib/cffiwrapper.cpp index d1762dd9f..f8d28eff5 100644 --- a/Code/MinimalLib/cffiwrapper.cpp +++ b/Code/MinimalLib/cffiwrapper.cpp @@ -85,9 +85,10 @@ char *str_to_c(const char *str) { } } // namespace -void mol_to_pkl(const ROMol &mol, char **mol_pkl, size_t *mol_pkl_sz) { - unsigned int propFlags = PicklerOps::PropertyPickleOptions::AllProps ^ - PicklerOps::PropertyPickleOptions::ComputedProps; +void mol_to_pkl( + const ROMol &mol, char **mol_pkl, size_t *mol_pkl_sz, + unsigned int propFlags = PicklerOps::PropertyPickleOptions::AllProps ^ + PicklerOps::PropertyPickleOptions::ComputedProps) { std::string pkl; MolPickler::pickleMol(mol, pkl, propFlags); free(*mol_pkl); @@ -269,9 +270,9 @@ extern "C" char *get_mol(const char *input, size_t *pkl_sz, *pkl_sz = 0; return nullptr; } - static const unsigned int propFlags = - PicklerOps::PropertyPickleOptions::AllProps ^ - PicklerOps::PropertyPickleOptions::ComputedProps; + unsigned int propFlags = PicklerOps::PropertyPickleOptions::AllProps ^ + PicklerOps::PropertyPickleOptions::ComputedProps; + MinimalLib::updatePropertyPickleOptionsFromJSON(propFlags, details_json); std::string pkl; MolPickler::pickleMol(*mol, pkl, propFlags); return str_to_c(pkl, pkl_sz); @@ -873,6 +874,77 @@ extern "C" bool clear_log_buffer(void *log_handle) { return 0; } +extern "C" short has_prop(const char *mol_pkl, size_t mol_pkl_sz, + const char *key) { + auto mol = mol_from_pkl(mol_pkl, mol_pkl_sz); + return mol.hasProp(key); +} + +extern "C" char **get_prop_list(const char *mol_pkl, size_t mol_pkl_sz, + short includePrivate, short includeComputed) { + auto mol = mol_from_pkl(mol_pkl, mol_pkl_sz); + auto propList = mol.getPropList(includePrivate, includeComputed); + std::string propNames; + for (const auto &prop : propList) { + propNames += prop + ","; + } + auto resLen = sizeof(char *) * (propList.size() + 1); + char **res = (char **)malloc(resLen); + if (!res) { + return nullptr; + } + memset(res, 0, resLen); + for (size_t i = 0; i < propList.size(); ++i) { + res[i] = strdup(propList.at(i).c_str()); + if (!res[i]) { + while (i--) { + free(res[i]); + } + return nullptr; + } + } + return res; +} + +extern "C" void set_prop(char **mol_pkl, size_t *mol_pkl_sz, const char *key, + const char *val, short computed) { + auto mol = mol_from_pkl(*mol_pkl, *mol_pkl_sz); + std::string valAsString(val); + mol.setProp(key, valAsString, computed); + mol_to_pkl(mol, mol_pkl, mol_pkl_sz); +} + +extern "C" char *get_prop(const char *mol_pkl, size_t mol_pkl_sz, + const char *key) { + auto mol = mol_from_pkl(mol_pkl, mol_pkl_sz); + if (!mol.hasProp(key)) { + return nullptr; + } + std::string val; + mol.getProp(key, val); + return strdup(val.c_str()); +} + +extern "C" short clear_prop(char **mol_pkl, size_t *mol_pkl_sz, + const char *key) { + auto mol = mol_from_pkl(*mol_pkl, *mol_pkl_sz); + short res = mol.hasProp(key); + if (res) { + mol.clearProp(key); + mol_to_pkl(mol, mol_pkl, mol_pkl_sz); + } + return res; +} + +extern "C" void keep_props(char **mol_pkl, size_t *mol_pkl_sz, + const char *details_json) { + auto mol = mol_from_pkl(*mol_pkl, *mol_pkl_sz); + unsigned int propFlags = PicklerOps::PropertyPickleOptions::AllProps ^ + PicklerOps::PropertyPickleOptions::ComputedProps; + MinimalLib::updatePropertyPickleOptionsFromJSON(propFlags, details_json); + mol_to_pkl(mol, mol_pkl, mol_pkl_sz, propFlags); +} + #if (defined(__GNUC__) || defined(__GNUG__)) #pragma GCC diagnostic pop #endif diff --git a/Code/MinimalLib/cffiwrapper.h b/Code/MinimalLib/cffiwrapper.h index 128e1da69..2224f4ec8 100644 --- a/Code/MinimalLib/cffiwrapper.h +++ b/Code/MinimalLib/cffiwrapper.h @@ -164,6 +164,23 @@ RDKIT_RDKITCFFI_EXPORT short destroy_log_handle(void **log_handle); RDKIT_RDKITCFFI_EXPORT char *get_log_buffer(void *log_handle); RDKIT_RDKITCFFI_EXPORT short clear_log_buffer(void *log_handle); +// props +RDKIT_RDKITCFFI_EXPORT short has_prop(const char *mol_pkl, size_t mol_pkl_sz, + const char *key); +RDKIT_RDKITCFFI_EXPORT char **get_prop_list(const char *mol_pkl, + size_t mol_pkl_sz, + short includePrivate, + short includeComputed); +RDKIT_RDKITCFFI_EXPORT void set_prop(char **mol_pkl, size_t *mol_pkl_sz, + const char *key, const char *val, + short computed); +RDKIT_RDKITCFFI_EXPORT char *get_prop(const char *mol_pkl, size_t mol_pkl_sz, + const char *key); +RDKIT_RDKITCFFI_EXPORT short clear_prop(char **mol_pkl, size_t *mol_pkl_sz, + const char *key); +RDKIT_RDKITCFFI_EXPORT void keep_props(char **mol_pkl, size_t *mol_pkl_sz, + const char *details_json); + #ifdef __cplusplus } #endif diff --git a/Code/MinimalLib/common.h b/Code/MinimalLib/common.h index d9ce6ee6d..ef85e87c1 100644 --- a/Code/MinimalLib/common.h +++ b/Code/MinimalLib/common.h @@ -54,7 +54,7 @@ #include #include #include "common_defs.h" - +#include "JSONParsers.h" #include #include #include diff --git a/Code/MinimalLib/jswrapper.cpp b/Code/MinimalLib/jswrapper.cpp index e34cd49fd..700bf23e1 100644 --- a/Code/MinimalLib/jswrapper.cpp +++ b/Code/MinimalLib/jswrapper.cpp @@ -153,7 +153,12 @@ emscripten::val uint_vector_to_uint32array( return res; } -emscripten::val get_as_uint8array(const JSMolBase &self) { +emscripten::val get_as_uint8array(const JSMolBase &self, + const std::string &details) { + return binary_string_to_uint8array(self.get_pickle(details)); +} + +emscripten::val get_as_uint8array_no_details(const JSMolBase &self) { return binary_string_to_uint8array(self.get_pickle()); } @@ -428,6 +433,7 @@ EMSCRIPTEN_BINDINGS(RDKit_minimal) { select_overload( &JSMolBase::get_v3Kmolblock)) .function("get_as_uint8array", &get_as_uint8array) + .function("get_as_uint8array", &get_as_uint8array_no_details) #ifdef RDK_BUILD_INCHI_SUPPORT .function("get_inchi", select_overload( diff --git a/Code/MinimalLib/minilib.cpp b/Code/MinimalLib/minilib.cpp index 177fafecc..469e23c8d 100644 --- a/Code/MinimalLib/minilib.cpp +++ b/Code/MinimalLib/minilib.cpp @@ -143,10 +143,11 @@ std::string JSMolBase::get_json() const { return MolInterchange::MolToJSONData(get()); } -std::string JSMolBase::get_pickle() const { +std::string JSMolBase::get_pickle(const std::string &details) const { + unsigned int propFlags = PicklerOps::AllProps ^ PicklerOps::ComputedProps; + MinimalLib::updatePropertyPickleOptionsFromJSON(propFlags, details.c_str()); std::string pickle; - MolPickler::pickleMol(get(), pickle, - PicklerOps::AllProps ^ PicklerOps::ComputedProps); + MolPickler::pickleMol(get(), pickle, propFlags); return pickle; } diff --git a/Code/MinimalLib/minilib.h b/Code/MinimalLib/minilib.h index 42c0ef85f..0a6d90ec3 100644 --- a/Code/MinimalLib/minilib.h +++ b/Code/MinimalLib/minilib.h @@ -42,7 +42,8 @@ class JSMolBase { std::string get_molblock() const { return get_molblock("{}"); } std::string get_v3Kmolblock(const std::string &details) const; std::string get_v3Kmolblock() const { return get_v3Kmolblock("{}"); } - std::string get_pickle() const; + std::string get_pickle(const std::string &details) const; + std::string get_pickle() const { return get_pickle(""); }; #ifdef RDK_BUILD_INCHI_SUPPORT std::string get_inchi(const std::string &options) const; std::string get_inchi() const { return get_inchi(""); } diff --git a/Code/MinimalLib/tests/tests.js b/Code/MinimalLib/tests/tests.js index b6a1220a3..d41bc8d4e 100644 --- a/Code/MinimalLib/tests/tests.js +++ b/Code/MinimalLib/tests/tests.js @@ -3402,6 +3402,25 @@ function test_custom_palette() { mol.delete(); } +function test_pickle() { + const mol = RDKitModule.get_mol('c1ccccn1'); + assert(mol); + mol.set_prop('a', '1'); + assert(mol.get_prop('a') === '1'); + const pklWithProps = mol.get_as_uint8array(); + assert(pklWithProps); + let molFromPkl = RDKitModule.get_mol_from_uint8array(pklWithProps); + assert(molFromPkl); + assert(molFromPkl.has_prop('a')); + assert(molFromPkl.get_prop('a') === '1'); + molFromPkl.delete(); + const pklWithoutProps = mol.get_as_uint8array(JSON.stringify({propertyFlags: { NoProps: true } })); + molFromPkl = RDKitModule.get_mol_from_uint8array(pklWithoutProps); + assert(molFromPkl); + assert(!molFromPkl.has_prop('a')); + molFromPkl.delete(); +} + initRDKitModule().then(function(instance) { var done = {}; const waitAllTestsFinished = () => { @@ -3490,6 +3509,7 @@ initRDKitModule().then(function(instance) { } test_bw_palette(); test_custom_palette(); + test_pickle(); waitAllTestsFinished().then(() => console.log("Tests finished successfully")