Coverage Report

Created: 2026-01-17 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/transform.cpp
Line
Count
Source
1
/**********************************************************************
2
transform.cpp - Perform command-line requested transformations
3
4
Copyright (C) 2004-2005 by Chris Morley
5
6
This file is part of the Open Babel project.
7
For more information, see <http://openbabel.org/>
8
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation version 2 of the License.
12
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
GNU General Public License for more details.
17
***********************************************************************/
18
#include <openbabel/babelconfig.h>
19
#include <sstream>
20
#include <openbabel/mol.h>
21
#include <openbabel/generic.h>
22
#include <openbabel/oberror.h>
23
#include <openbabel/descriptor.h>
24
#include <openbabel/op.h>
25
#include <openbabel/parsmart.h>
26
27
#include <cstdlib>
28
29
using namespace std;
30
namespace OpenBabel
31
{
32
  class OBConversion; //used only as a pointer
33
34
  OBBase* OBMol::DoTransformations(const std::map<std::string, std::string>* pOptions, OBConversion* pConv)
35
10.7k
  {
36
    // Perform any requested transformations
37
    // on a OBMol
38
    //The input map has option letters or name as the key and
39
    //any associated text as the value.
40
    //For normal(non-filter) transforms:
41
    // returns a pointer to the OBMol (this) if ok or NULL if not.
42
    //For filters returns a pointer to the OBMol (this) if there is a  match,
43
    //and NULL when not and in addition the OBMol object is deleted NULL.
44
45
    //This is now a virtual function. The OBBase version just returns the OBMol pointer.
46
    //This is declared in mol.h
47
48
    //The filter options, s and v allow a obgrep facility.
49
    //Used together they must both be true to allow a molecule through.
50
51
    //Parse GeneralOptions
52
10.7k
    if(pOptions->empty())
53
10.7k
      return this;
54
55
    // DoOps calls Do() for each of the plugin options in the map
56
    // It normally returns true, even if there are no options but
57
    // can return false if one of the options decides that the
58
    // molecule should not be output. If it is a filtering op, it
59
    // should delete the molecule itself (unlike the -s, --filter options,
60
    // which delete it in this function).
61
0
    if(!OBOp::DoOps(this, pOptions, pConv))
62
0
      return nullptr;
63
64
0
    bool ret=true;
65
66
0
    map<string,string>::const_iterator itr, itr2;
67
68
0
    if(pOptions->find("b")!=pOptions->end())
69
0
      ConvertDativeBonds();
70
0
    if(pOptions->find("B")!=pOptions->end())
71
0
      MakeDativeBonds();
72
73
0
    if(pOptions->find("d")!=pOptions->end())
74
0
      if(!DeleteHydrogens())
75
0
        ret=false;
76
77
0
    if(pOptions->find("h")!=pOptions->end())
78
0
      if(!AddHydrogens(false, false))
79
0
        ret=false;
80
81
0
    if(pOptions->find("r")!=pOptions->end()) {
82
0
      StripSalts();
83
0
      ret = true;
84
0
    }
85
86
0
    itr = pOptions->find("p");
87
0
    if(itr!=pOptions->end()) {
88
0
      if(pOptions->find("h")!=pOptions->end()){
89
0
        stringstream errorMsg;
90
0
        errorMsg << "Both -p and -h options are set. "
91
0
                 << "All implicit hydrogens (-h) will be added without considering pH."
92
0
                 << endl;
93
0
        obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning);
94
0
      }
95
0
      else {
96
0
        double pH = strtod(itr->second.c_str(), nullptr);
97
0
        if(!AddHydrogens(false, true, pH))
98
0
          ret=false;
99
0
      }
100
0
    }
101
102
0
    if(pOptions->find("c")!=pOptions->end())
103
0
      Center();
104
105
0
    itr = pOptions->find("title"); //Replaces title
106
0
    if(itr!=pOptions->end())
107
0
      SetTitle(itr->second.c_str());
108
109
0
    itr = pOptions->find("addtotitle"); //Appends text to title
110
0
    if(itr!=pOptions->end())
111
0
      {
112
0
        string title(GetTitle());
113
0
        title += itr->second;
114
0
        SetTitle(title.c_str());
115
0
      }
116
117
    //Add an extra property to the molecule.
118
    //--property attribute value   (no spaces in attribute)
119
    //or
120
    // --property attribute1=value1; multi word attribute2 = multi word value2;
121
    //For both forms, no ';' in either attribute or value.
122
    //For second form, no '=' in attribute or value
123
0
    itr = pOptions->find("property");
124
0
    if(itr!=pOptions->end()) {
125
0
        string txt(itr->second);
126
127
0
        vector<string> vec;
128
0
        vector<string>::iterator it;
129
0
        tokenize(vec, txt,";");
130
0
        for(it=vec.begin();it!=vec.end();++it) {
131
0
          string attr, val;
132
0
          string::size_type pos = it->find('=');
133
0
          if(pos==string::npos) {
134
            //form with space
135
0
            pos = it->find(' ');
136
0
            if(pos==string::npos) {
137
0
                obErrorLog.ThrowError(__FUNCTION__, "Missing property value", obError);
138
0
                ret=false;
139
0
                break;
140
0
            }
141
0
          }
142
0
          val  = it->substr(pos+1);
143
0
          Trim(val);
144
0
          attr = it->erase(pos);
145
0
          Trim(attr);
146
147
          //Update value if it already exists
148
0
          OBPairData* dp = dynamic_cast<OBPairData*>(GetData(attr));
149
0
          if(dp) {
150
0
            dp->SetValue(val);
151
0
            dp->SetOrigin(userInput);
152
0
          }
153
0
          else {
154
            // Pair did not exist; make new one
155
0
            dp = new OBPairData;
156
0
            dp->SetAttribute(attr);
157
0
            dp->SetValue(val);
158
0
            dp->SetOrigin(userInput);
159
0
            SetData(dp);
160
0
          }
161
0
        }
162
0
      }
163
164
0
    itr = pOptions->find("add");  //adds new properties from descriptors in list
165
0
    if(itr!=pOptions->end())
166
0
      OBDescriptor::AddProperties(this, itr->second);
167
168
0
    itr = pOptions->find("delete"); //deletes the specified properties
169
0
    if(itr!=pOptions->end())
170
0
      OBDescriptor::DeleteProperties(this, itr->second);
171
172
0
    itr = pOptions->find("append"); //Appends values of descriptors or properties to title
173
0
    if(itr!=pOptions->end())
174
0
      {
175
0
        string title(GetTitle());
176
0
        title += OBDescriptor::GetValues(this, itr->second);
177
0
        if(ispunct(title[0]))
178
0
          title[0]=' ';//a leading punct char is used only as a separator, not at start
179
0
        SetTitle(Trim(title).c_str());
180
0
      }
181
182
183
184
      //Filter using OBDescriptor comparison and (older) SMARTS tests
185
    //Continue only if previous test was true.
186
0
    bool fmatch = true;
187
0
    itr = pOptions->find("filter");
188
0
    if(itr!=pOptions->end())
189
0
      {
190
0
        std::istringstream optionText(itr->second);
191
0
        fmatch = OBDescriptor::FilterCompare(this, optionText, false);
192
0
      }
193
194
0
    if(fmatch)
195
0
      {
196
0
        itr = pOptions->find("v");
197
0
        if(itr!=pOptions->end() && !itr->second.empty())
198
0
          {
199
            //inverse match quoted SMARTS string which follows
200
0
            OBSmartsPattern sp;
201
0
            sp.Init(itr->second);
202
0
            fmatch = !sp.Match(*this); //(*pmol) ;
203
0
          }
204
0
      }
205
0
    if(fmatch)
206
0
    {
207
0
      itr = pOptions->find("s");
208
0
      if(itr!=pOptions->end() && !itr->second.empty())
209
0
        {
210
          //SMARTS filter
211
          //If exactmatch option set (probably in fastsearchformat) the
212
          //number of atoms in the pattern (passed as a string in the option text)
213
          //has to be the same as in the molecule.
214
0
          itr2 = pOptions->find("exactmatch");
215
0
          if(itr2!=pOptions->end() && (int)NumHvyAtoms()!=atoi(itr2->second.c_str()))
216
0
            fmatch=false;
217
0
          else
218
0
            {
219
              //match quoted SMARTS string which follows
220
0
              OBSmartsPattern sp;
221
0
                sp.Init(itr->second.c_str());
222
0
                fmatch = sp.Match(*this);
223
0
            }
224
0
        }
225
0
    }
226
227
0
    if(!fmatch)
228
0
      {
229
        //filter failed: delete OBMol and return NULL
230
0
        delete this;
231
0
        return nullptr;
232
0
      }
233
0
    else
234
0
      {
235
0
        if(ret==false)
236
0
          {
237
0
            obErrorLog.ThrowError(__FUNCTION__, "Error executing an option", obError);
238
0
            delete this; //added 9March2006
239
0
            return nullptr;
240
0
          }
241
0
        else
242
0
          return this;
243
0
      }
244
0
  }
245
246
  ///////////////////////////////////////////////////
247
  const char* OBMol::ClassDescription()
248
0
  {
249
0
    static string ret;
250
0
    ret = "For conversions of molecules\n"
251
0
"Additional options :\n"
252
0
"-d Delete hydrogens (make implicit)\n"
253
0
"-h Add hydrogens (make explicit)\n"
254
0
"-p <pH> Add hydrogens appropriate for this pH\n"
255
0
"-b Convert dative bonds e.g.-[N+]([O-])=O to -N(=O)=O\n"
256
0
"-B Make dative bonds e.g.-[N+]([O-])=O from -N(=O)=O\n"
257
0
"-r Remove all but the largest contiguous fragment\n"
258
0
"-c Center Coordinates\n"
259
0
"-C Combine mols in first file with others by name\n"
260
0
"--filter <filterstring> Filter: convert only when tests are true:\n"
261
0
"--add <list> Add properties from descriptors\n"
262
0
"--delete <list> Delete properties in list\n"
263
0
"--append <list> Append properties or descriptors in list to title:\n"
264
0
"-s\"smarts\" Convert only if match SMARTS or mols in file:\n"
265
0
"-v\"smarts\" Convert only if NO match to SMARTS or mols in file(not displayed in GUI)\n"
266
0
"--join Join all input molecules into a single output molecule\n"
267
0
"--separate Output disconnected fragments separately\n"
268
0
"--property <attrib> <value> add or replace a property (SDF)\n"
269
0
"--title <title> Add or replace molecule title\n"
270
0
"--addtotitle <text> Append text to title\n"
271
0
"--writeconformers Output multiple conformers separately\n"
272
0
"--addoutindex Append output index to title\n" ;
273
274
    //Append lines from OBOp plugins that work with OBMol
275
0
    OBMol dummymol; //just needed to carry class type information; messy!
276
0
    ret += OBOp::OpOptions(&dummymol);
277
278
0
      return ret.c_str();
279
0
  }
280
281
} //namespace OpenBabel
282
283
//! \file transform.cpp
284
//! \brief Perform command-line requested transformations for OBMol
285
//!  and SMARTS filtering