Coverage Report

Created: 2026-07-16 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/formats/mol2format.cpp
Line
Count
Source
1
/**********************************************************************
2
Copyright (C) 1998-2001 by OpenEye Scientific Software, Inc.
3
Some portions Copyright (C) 2001-2007 by Geoffrey R. Hutchison
4
Some portions Copyright (C) 2004 by Chris Morley
5
6
This program is free software; you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation version 2 of the License.
9
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
GNU General Public License for more details.
14
***********************************************************************/
15
#include <openbabel/babelconfig.h>
16
17
#include <openbabel/obmolecformat.h>
18
#include <openbabel/mol.h>
19
#include <openbabel/atom.h>
20
#include <openbabel/bond.h>
21
#include <openbabel/obiter.h>
22
#include <openbabel/elements.h>
23
#include <openbabel/generic.h>
24
#include <openbabel/kekulize.h>
25
#include <openbabel/obfunctions.h>
26
#include <openbabel/data.h>
27
#include <cstdlib>
28
29
using namespace std;
30
namespace OpenBabel
31
{
32
  //The routine WriteSmiOrderedMol2() in the original mol2.cpp is presumably
33
  //another output format, but was not made available in version 100.1.2, nor
34
  //is it here.
35
36
  class MOL2Format : public OBMoleculeFormat
37
  {
38
  public:
39
    //Register this format type ID
40
    MOL2Format()
41
12
    {
42
12
      OBConversion::RegisterFormat("mol2",this, "chemical/x-mol2");
43
12
      OBConversion::RegisterFormat("ml2",this);
44
12
      OBConversion::RegisterFormat("sy2",this);
45
12
      OBConversion::RegisterOptionParam("c", this, 0, OBConversion::INOPTIONS);
46
12
      OBConversion::RegisterOptionParam("c", this, 0, OBConversion::OUTOPTIONS);
47
12
      OBConversion::RegisterOptionParam("l", this, 0, OBConversion::OUTOPTIONS);
48
12
      OBConversion::RegisterOptionParam("u", this, 0, OBConversion::OUTOPTIONS);
49
12
    }
50
51
    const char* Description() override  // required
52
0
    {
53
0
      return
54
0
        "Sybyl Mol2 format\n"
55
0
        "Read Options e.g. -ac\n"
56
0
        "  c               Read UCSF Dock scores saved in comments preceding molecules\n\n"
57
0
        "Write Options e.g. -xl\n"
58
0
        "  l               Output ignores residue information (only ligands)\n"
59
0
        "  c               Write UCSF Dock scores saved in comments preceding molecules\n"
60
0
        "  u               Do not write formal charge information in UNITY records\n\n";
61
0
    }
62
63
    const char* SpecificationURL() override
64
0
    {
65
0
      return "http://www.tripos.com/data/support/mol2.pdf"; // XXX dead
66
0
    }
67
68
    const char* GetMIMEType() override
69
0
    { return "chemical/x-mol2"; }
70
71
    int SkipObjects(int n, OBConversion* pConv) override;
72
73
    //*** This section identical for most OBMol conversions ***
74
    ////////////////////////////////////////////////////
75
    /// The "API" interface functions
76
    bool ReadMolecule(OBBase* pOb, OBConversion* pConv) override;
77
    bool WriteMolecule(OBBase* pOb, OBConversion* pConv) override;
78
  };
79
  //***
80
81
  //Make an instance of the format class
82
  MOL2Format theMOL2Format;
83
  
84
  // Helper function for ReadMolecule
85
  // \return Is this atom a sulfur in a (di)thiocarboxyl (-CS2, -COS, CS2H or COSH) group?
86
  static bool IsThiocarboxylSulfur(OBAtom* queryatom)
87
0
  {
88
0
    if (queryatom->GetAtomicNum() != OBElements::Sulfur)
89
0
      return(false);
90
0
    if (queryatom->GetHvyDegree() != 1)
91
0
      return(false);
92
0
93
0
    OBAtom *atom = nullptr;
94
0
    OBBond *bond;
95
0
    OBBondIterator i;
96
0
97
0
    for (bond = queryatom->BeginBond(i); bond; bond = queryatom->NextBond(i))
98
0
      if ((bond->GetNbrAtom(queryatom))->GetAtomicNum() == OBElements::Carbon)
99
0
      {
100
0
        atom = bond->GetNbrAtom(queryatom);
101
0
        break;
102
0
      }
103
0
    if (!atom)
104
0
      return(false);
105
0
    if (!(atom->CountFreeSulfurs() == 2)
106
0
      && !(atom->CountFreeOxygens() == 1 && atom->CountFreeSulfurs() == 1))
107
0
      return(false);
108
0
109
0
    //atom is connected to a carbon that has a total
110
0
    //of 2 attached free sulfurs or 1 free oxygen and 1 free sulfur
111
0
    return(true);
112
0
  }
113
114
  static bool IsOxygenOrSulfur(OBAtom *atom)
115
0
  {
116
0
    switch (atom->GetAtomicNum()) {
117
0
    case 8: case 16: return true;
118
0
    default: return false;
119
0
    }
120
0
  }
121
122
  static unsigned int GetAtomicNumAndIsotope(const char* symbol, int *isotope)
123
0
  {
124
0
    const char* p = symbol;
125
0
    switch (p[0]) {
126
0
    case 'D':
127
0
      if (p[1] == '\0') {
128
0
        *isotope = 2;
129
0
        return 1;
130
0
      }
131
0
      break;
132
0
    case 'T':
133
0
      if (p[1] == '\0') {
134
0
        *isotope = 3;
135
0
        return 1;
136
0
      }
137
0
      break;
138
0
    }
139
0
    return OBElements::GetAtomicNum(symbol);
140
0
  }
141
142
  //read from ifs until next rti is found and return it
143
  static string read_until_rti(istream & ifs) 
144
0
  {
145
0
      char buffer[BUFF_SIZE];
146
0
      for (;;)
147
0
      {
148
0
        if (!ifs.getline(buffer,BUFF_SIZE))
149
0
          return "";
150
0
        if (!strncmp(buffer,"@<TRIPOS>",9))
151
0
          return string(buffer);
152
0
      }      
153
0
  }
154
  /////////////////////////////////////////////////////////////////
155
  bool MOL2Format::ReadMolecule(OBBase* pOb, OBConversion* pConv)
156
0
  {
157
158
0
    OBMol* pmol = pOb->CastAndClear<OBMol>();
159
0
    if (pmol == nullptr)
160
0
      return false;
161
162
    //Define some references so we can use the old parameter names
163
0
    istream &ifs = *pConv->GetInStream();
164
0
    OBMol &mol = *pmol;
165
166
    //Old code follows...
167
0
    bool foundAtomLine = false;
168
0
    char buffer[BUFF_SIZE];
169
0
    char *comment = nullptr;
170
0
    string str,str1;
171
0
    vector<string> vstr;
172
0
    int len;
173
174
    // Prevent reperception
175
0
    mol.SetChainsPerceived();
176
177
0
    mol.BeginModify();
178
179
0
    for (;;)
180
0
      {
181
0
        if (!ifs.getline(buffer,BUFF_SIZE))
182
0
          return(false);
183
0
        if (pConv->IsOption("c", OBConversion::INOPTIONS) != nullptr && EQn(buffer, "###########", 10))
184
0
          {
185
0
            char attr[32], val[32];
186
            // CVE-2022-43607: width-limit both specifiers to the buffer size.
187
0
            sscanf(buffer, "########## %31[^:]:%31s", attr, val);
188
0
            OBPairData *dd = new OBPairData;
189
0
            dd->SetAttribute(attr);
190
0
            dd->SetValue(val);
191
0
            dd->SetOrigin(fileformatInput);
192
0
            mol.SetData(dd);
193
0
          }
194
0
        if (EQn(buffer,"@<TRIPOS>MOLECULE",17))
195
0
          break;
196
0
      }
197
198
    // OK, just read MOLECULE line
199
0
    int lcount;
200
0
    int natoms = 0, nbonds = 0;
201
0
    bool hasPartialCharges = true;
202
0
    for (lcount=0;;lcount++)
203
0
      {
204
0
        if (!ifs.getline(buffer,BUFF_SIZE))
205
0
          return(false);
206
0
        if (EQn(buffer,"@<TRIPOS>ATOM",13))
207
0
          {
208
0
            foundAtomLine = true;
209
0
            break;
210
0
          }
211
212
0
        if (lcount == 0)
213
0
          {
214
0
            tokenize(vstr,buffer);
215
0
            if (!vstr.empty())
216
0
              mol.SetTitle(buffer);
217
0
          }
218
0
        else if (lcount == 1)
219
0
          sscanf(buffer,"%d%d",&natoms,&nbonds);
220
0
        else if (lcount == 3) // charge descriptions
221
0
          {
222
            // Annotate origin of partial charges
223
0
            OBPairData *dp = new OBPairData;
224
0
            dp->SetAttribute("PartialCharges");
225
0
            dp->SetValue(buffer);
226
0
            dp->SetOrigin(fileformatInput);
227
0
            mol.SetData(dp);
228
229
0
            if (strncasecmp(buffer, "NO_CHARGES", 10) == 0)
230
0
              hasPartialCharges = false;
231
0
          }
232
0
        else if (lcount == 4) //energy (?)
233
0
          {
234
0
            tokenize(vstr,buffer);
235
0
            if (!vstr.empty() && vstr.size() == 3)
236
0
              if (vstr[0] == "Energy")
237
0
                mol.SetEnergy(atof(vstr[2].c_str()));
238
0
          }
239
0
        else if (lcount == 5) //comment
240
0
          {
241
0
            if ( buffer[0] )
242
0
              {
243
0
                len = (int) strlen(buffer)+1;
244
                //! @todo allow better multi-line comments
245
                // which don't allow ill-formed data to consume memory
246
                // Thanks to Andrew Dalke for the pointer
247
0
                delete [] comment;
248
0
                comment = new char [len];
249
0
                memcpy(comment,buffer,len);
250
0
              }
251
0
          }
252
0
      }
253
254
0
    if (!foundAtomLine)
255
0
      {
256
0
        mol.EndModify();
257
0
        mol.Clear();
258
0
        obErrorLog.ThrowError(__FUNCTION__, "Unable to read Mol2 format file. No atoms found.", obWarning);
259
0
        return(false);
260
0
      }
261
262
0
    if (natoms < 0 || natoms >= 100000000 || nbonds < 0 || nbonds >= 100000000)
263
0
      {
264
0
        mol.EndModify();
265
0
        mol.Clear();
266
0
        obErrorLog.ThrowError(__FUNCTION__, "Unable to read Mol2 format file. Invalid atom or bond count.", obWarning);
267
0
        return(false);
268
0
      }
269
270
0
    mol.ReserveAtoms(natoms);
271
272
0
    int i;
273
0
    vector3 v;
274
0
    OBAtom atom;
275
0
    double x,y,z,pcharge;
276
0
    char temp_type[BUFF_SIZE], resname[BUFF_SIZE], atmid[BUFF_SIZE];
277
0
    int elemno, resnum = -1;
278
0
    int isotope = 0;
279
0
    bool has_explicit_hydrogen = false;
280
0
    bool has_residue_information = false;
281
282
0
    ttab.SetFromType("SYB");
283
0
    for (i = 0;i < natoms;i++)
284
0
      {
285
0
        if (!ifs.getline(buffer,BUFF_SIZE))
286
0
          return(false);
287
        // Required ATOM columns are the atom name, x/y/z and the SYBYL atom
288
        // type.  subst_id, subst_name and charge are optional, so reset them
289
        // to their defaults each iteration (these locals are declared outside
290
        // the loop and would otherwise carry over from the previous atom).
291
0
        resnum = -1;
292
0
        resname[0] = '\0';
293
0
        pcharge = 0.0;
294
0
        isotope = 0;
295
0
        if (sscanf(buffer," %*s %1024s %lf %lf %lf %1024s %d %1024s %lf",
296
0
               atmid, &x,&y,&z, temp_type, &resnum, resname, &pcharge) < 5)
297
0
          {
298
0
            obErrorLog.ThrowError(__FUNCTION__,
299
0
                                  "Unable to read Mol2 format file. Truncated "
300
0
                                  "atom record.", obWarning);
301
0
            return(false);
302
0
          }
303
304
0
        atom.SetVector(x, y, z);
305
0
        atom.SetFormalCharge(0);
306
307
        // Handle "CL" and "BR" and other mis-typed atoms
308
0
        str = temp_type;
309
0
        if (strncmp(temp_type, "CL", 2) == 0) {
310
0
          str = "Cl";
311
0
        } else  if (strncmp(temp_type,"BR",2) == 0) {
312
0
          str = "Br";
313
0
        } else if (strncmp(temp_type,"S.o2", 4) == 0) {
314
0
          str = "S.O2";
315
0
        } else if (strncmp(temp_type,"S.o", 3) == 0) {
316
0
          str = "S.O";
317
0
        } else if (strncmp(temp_type,"SI", 2) == 0) {
318
0
          str = "Si";
319
        // The following cases are entries which are not in openbabel/data/types.txt
320
        // and should probably be added there
321
0
        } else if (strncmp(temp_type,"S.1", 3) == 0) {
322
0
          str = "S.2"; // no idea what the best type might be here
323
0
        } else if (strncmp(temp_type,"P.", 2) == 0) {
324
0
          str = "P.3";
325
0
        } else if (strncasecmp(temp_type,"Ti.", 3) == 0) { // e.g. Ti.th
326
0
          str = "Ti";
327
0
        } else if (strncasecmp(temp_type,"Ru.", 3) == 0) { // e.g. Ru.oh
328
0
          str = "Ru";
329
        // Fixes PR#3557898
330
0
        } else if (strncmp(temp_type, "N.4", 3) == 0) {
331
0
          atom.SetFormalCharge(1);
332
0
        }
333
334
0
        ttab.SetToType("ATN");
335
0
        ttab.Translate(str1,str);
336
0
        elemno = atoi(str1.c_str());
337
0
        ttab.SetToType("IDX");
338
339
        // We might have missed some SI or FE type things above, so here's
340
        // another check
341
0
        if( !elemno && isupper(temp_type[1]) )
342
0
          {
343
0
            temp_type[1] = (char)tolower(temp_type[1]);
344
0
            str = temp_type;
345
0
            ttab.SetToType("ATN");
346
0
            ttab.Translate(str1,str);
347
0
            elemno = atoi(str1.c_str());
348
0
            ttab.SetToType("IDX");
349
0
          }
350
        // One last check if there isn't a period in the type,
351
        // it's a malformed atom type, but it may be the element symbol
352
        // GaussView does this (PR#1739905)
353
0
        if ( !elemno ) {
354
          // check if it's "Du" or "Xx" and the element is in the atom name
355
0
          if (str == "Du" || str == "Xx") {
356
0
            str = atmid;
357
0
            for (unsigned int i = 0; i < str.length(); ++i)
358
0
              if (!isalpha(str[i])) {
359
0
                str.erase(i);
360
0
                break; // we've erased the end of the string
361
0
              }
362
0
          }
363
364
365
0
          std::stringstream errorMsg;
366
0
          errorMsg << "This Mol2 file is non-standard. Problem with molecule: "
367
0
                   << mol.GetTitle()
368
0
                   << " Cannot interpret atom types correctly, instead attempting to interpret atom type: "
369
0
                   << str << " as elements instead.";
370
0
          obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning);
371
372
0
          string::size_type dotPos = str.find('.');
373
0
          if (dotPos == string::npos) {
374
0
            elemno = GetAtomicNumAndIsotope(str.c_str(), &isotope);
375
0
          }
376
0
        }
377
378
0
        atom.SetAtomicNum(elemno);
379
0
        if (isotope)
380
0
          atom.SetIsotope(isotope);
381
0
        else if (elemno == 1)
382
0
          has_explicit_hydrogen = true;
383
0
        ttab.SetToType("INT");
384
0
        ttab.Translate(str1,str);
385
0
        atom.SetType(str1);
386
0
        atom.SetPartialCharge(pcharge);
387
        // MMFF94 has different atom types for Cu(I) and Cu(II)
388
        // as well as for Fe(II) and Fe(III), so the correct formal
389
        // charge is needed for correct atom type assignment
390
0
        if (str1 == "Cu" || str1 == "Fe")
391
0
          atom.SetFormalCharge((int)pcharge);
392
0
        if (!mol.AddAtom(atom))
393
0
          return(false);
394
0
        if (fabs(pcharge) >= 2e-6)
395
0
          hasPartialCharges = true;
396
397
        // Add residue information if it exists
398
0
        if (resnum != -1 && resnum != 0 &&
399
0
            strlen(resname) != 0 && strncmp(resname,"<1>", 3) != 0)
400
0
          {
401
0
            has_residue_information = true;
402
0
            OBResidue *res  = (mol.NumResidues() > 0) ?
403
0
              mol.GetResidue(mol.NumResidues()-1) : nullptr;
404
0
            if (res == nullptr || res->GetName() != resname ||
405
0
                res->GetNum() != resnum)
406
0
              {
407
0
                vector<OBResidue*>::iterator ri;
408
0
                for (res = mol.BeginResidue(ri) ; res ; res = mol.NextResidue(ri))
409
0
                  if (res->GetName() == resname &&
410
0
                      res->GetNum() == resnum)
411
0
                    break;
412
413
0
                if (res == nullptr)
414
0
                  {
415
0
                    res = mol.NewResidue();
416
0
                    res->SetName(resname);
417
0
                    res->SetNum(resnum);
418
0
                  }
419
0
              }
420
0
            OBAtom *atomPtr = mol.GetAtom(mol.NumAtoms());
421
0
            res->AddAtom(atomPtr);
422
0
            res->SetAtomID(atomPtr, atmid);
423
0
          } // end adding residue info
424
0
      }
425
426
0
    string nextrti;
427
0
    do { nextrti = read_until_rti(ifs); } 
428
0
    while(nextrti != "@<TRIPOS>UNITY_ATOM_ATTR" && nextrti != "@<TRIPOS>BOND" && nextrti.length() > 0);
429
430
0
    if(nextrti == "@<TRIPOS>UNITY_ATOM_ATTR")
431
0
    { //read in formal charge information, must be done before Kekulization
432
0
        int aid = 0, num = 0;
433
0
        while (ifs.peek() != '@' && ifs.getline(buffer,BUFF_SIZE))
434
0
        {
435
          // Reset before each parse so a malformed header line cannot reuse
436
          // the previous entry's atom id / attribute count.
437
0
          aid = 0;
438
0
          num = 0;
439
0
          if (sscanf(buffer,"%d %d",&aid, &num) < 2)
440
0
            continue;
441
0
          for(int i = 0; i < num; i++)
442
0
          {
443
0
            if (!ifs.getline(buffer,BUFF_SIZE))
444
0
              return(false);
445
0
            if(strncmp(buffer, "charge", 6) == 0)
446
0
            {
447
0
              int charge = 0;
448
0
              sscanf(buffer,"%*s %d",&charge);
449
0
              if(aid >= 1 && aid <= (int)mol.NumAtoms())
450
0
              {
451
0
                OBAtom *atom = mol.GetAtom(aid);
452
0
                if (atom != nullptr)
453
0
                  atom->SetFormalCharge(charge);
454
0
              }
455
0
            }
456
0
          }
457
0
        }
458
0
    }
459
460
0
    while(nextrti != "@<TRIPOS>BOND" && nextrti.length() > 0)
461
0
      nextrti = read_until_rti(ifs);
462
463
0
    if(nextrti != "@<TRIPOS>BOND")
464
0
      return false;
465
466
0
    int start, end;
467
0
    bool needs_kekulization = false;
468
0
    for (i = 0; i < nbonds; i++)
469
0
      {
470
0
        if (!ifs.getline(buffer,BUFF_SIZE))
471
0
          return(false);
472
473
        // origin/target atom ids and the bond type are all required.
474
0
        temp_type[0] = '\0';
475
0
        if (sscanf(buffer,"%*d %d %d %1024s",&start,&end,temp_type) < 3)
476
0
          {
477
0
            obErrorLog.ThrowError(__FUNCTION__,
478
0
                                  "Unable to read Mol2 format file. Truncated "
479
0
                                  "bond record.", obWarning);
480
0
            return(false);
481
0
          }
482
0
        str = temp_type;
483
0
        unsigned int flags = 0;
484
0
        int order;
485
0
        if (str == "ar" || str == "AR" || str == "Ar") {
486
0
          order = 1;
487
0
          flags = OB_AROMATIC_BOND;
488
0
          needs_kekulization = true;
489
0
        }
490
0
        else if (str == "AM" || str == "am" || str == "Am")
491
0
          order = 1;
492
0
        else
493
0
          order = atoi(str.c_str());
494
495
0
        mol.AddBond(start, end, order, flags);
496
0
      }
497
498
    // TODO: Add a test case for the statement below of Paolo Tosco
499
    //       - I am currently assuming that is not a problem for the
500
    //         the current kekulization code, but it needs to be
501
    //         checked
502
    //   "Make a pass to ensure that there are no double bonds
503
    //    between atoms which are also involved in aromatic bonds
504
    //    as that may ill-condition kekulization (fixes potential
505
    //    issues with molecules like CEWYIM30 (MMFF94 validation suite)"
506
507
0
    mol.SetAromaticPerceived(); // don't trigger reperception
508
509
0
    if (has_explicit_hydrogen) {
510
0
      FOR_ATOMS_OF_MOL(atom, mol) {
511
0
        unsigned int total_valence = atom->GetTotalDegree();
512
0
        switch (atom->GetAtomicNum()) {
513
0
        case 8:
514
0
          if (total_valence != 1) continue;
515
0
          if (strcmp(atom->GetType(), "O2") != 0) continue; // TODO: the O.co2 type is lost by this point
516
0
          {
517
0
            OBAtomBondIter bit(&*atom);
518
0
            if (!bit->IsAromatic() && bit->GetBondOrder() == 1)
519
0
              atom->SetFormalCharge(-1); // set -1 charge on dangling O.co2
520
0
          }
521
0
          break;
522
0
        case 17: // Cl
523
0
          if (total_valence == 0)
524
0
            atom->SetFormalCharge(-1);
525
0
          break;
526
0
        }
527
0
      }
528
0
    }
529
530
    // Kekulization is necessary if an aromatic bond is present
531
0
    if (needs_kekulization) {
532
      // "de-aromatize" carboxylates and (di)thiocarboxylates
533
      // The typical case (in our test suite anyway) is a carboxylate binding to
534
      // a metal ion. The two O's have charges of -0.5 and the bond orders are aromatic
535
0
      FOR_ATOMS_OF_MOL(atom, mol) {
536
0
        OBAtom* oxygenOrSulfur = &*atom;
537
        // Look first for a terminal O/S
538
0
        if (!IsOxygenOrSulfur(oxygenOrSulfur) || oxygenOrSulfur->GetTotalDegree() != 1) continue;
539
0
        OBAtomBondIter bitA(oxygenOrSulfur);
540
0
        OBBond *bondA = &*bitA;
541
0
        if (!bondA->IsAromatic()) continue;
542
        // Look for the carbon
543
0
        OBAtom *carbon = bondA->GetNbrAtom(oxygenOrSulfur);
544
0
        if (carbon->GetAtomicNum() != 6) continue;
545
        // Look for the other oxygen or sulfur
546
0
        OBAtom* otherOxygenOrSulfur = nullptr;
547
0
        OBBond* bondB = nullptr;
548
0
        FOR_BONDS_OF_ATOM(bitB, carbon) {
549
0
          if (&*bitB == bondA || !bitB->IsAromatic()) continue;
550
0
          OBAtom* nbr = bitB->GetNbrAtom(carbon);
551
0
          if (IsOxygenOrSulfur(nbr) && nbr->GetTotalDegree() == 1) {
552
0
            otherOxygenOrSulfur = nbr;
553
0
            bondB = &*bitB;
554
0
          }
555
0
        }
556
0
        if (!otherOxygenOrSulfur) continue;
557
0
        if(otherOxygenOrSulfur->GetFormalCharge() != 0) continue; //formal charge already set on one
558
559
        // Now set as C(=O)O
560
0
        bondA->SetAromatic(false);
561
0
        oxygenOrSulfur->SetFormalCharge(-1);
562
563
0
        bondB->SetAromatic(false);
564
0
        bondB->SetBondOrder(2);
565
0
      }
566
567
      // First of all, set the atoms at the ends of the aromatic bonds to also
568
      // be aromatic. This information is required for OBKekulize.
569
0
      FOR_BONDS_OF_MOL(bond, mol) {
570
0
        if (bond->IsAromatic()) {
571
0
          bond->GetBeginAtom()->SetAromatic();
572
0
          bond->GetEndAtom()->SetAromatic();
573
0
        }
574
0
      }
575
0
      bool ok = OBKekulize(&mol);
576
0
      if (!ok) {
577
0
        stringstream errorMsg;
578
0
        errorMsg << "Failed to kekulize aromatic bonds in MOL2 file";
579
0
        std::string title = mol.GetTitle();
580
0
        if (!title.empty())
581
0
          errorMsg << " (title is " << title << ")";
582
0
        errorMsg << endl;
583
0
        obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning);
584
        // return false; Should we return false for a kekulization failure?
585
0
      }
586
0
    }
587
588
0
    mol.EndModify();
589
590
    // Suggestion by Liu Zhiguo 2008-01-26
591
    // Mol2 files define atom types -- there is no need to re-perceive
592
0
    mol.SetAtomTypesPerceived();
593
594
0
    if (has_residue_information)
595
0
      mol.SetChainsPerceived();
596
597
0
    if (!has_explicit_hydrogen) {
598
      // Guess how many hydrogens are present on each atom based on typical valencies
599
      // TODO: implement the MOL2 valence model (if it exists)
600
0
      FOR_ATOMS_OF_MOL(matom, mol) {
601
0
        if (matom->GetImplicitHCount() == 0)
602
0
          OBAtomAssignTypicalImplicitHydrogens(&*matom);
603
0
      }
604
0
    }
605
606
    //must add generic data after end modify - otherwise it will be blown away
607
0
    if (comment)
608
0
      {
609
0
        OBCommentData *cd = new OBCommentData;
610
0
        cd->SetData(comment);
611
0
        cd->SetOrigin(fileformatInput);
612
0
        mol.SetData(cd);
613
0
        delete [] comment;
614
0
        comment = nullptr;
615
0
      }
616
0
    if (hasPartialCharges)
617
0
      mol.SetPartialChargesPerceived();
618
619
    /* Disabled due to PR#3048758 -- seekg is very slow with gzipped mol2
620
    // continue untill EOF or untill next molecule record
621
    streampos pos;
622
    for(;;)
623
      {
624
        pos = ifs.tellg();
625
        if (!ifs.getline(buffer,BUFF_SIZE))
626
          break;
627
        if (EQn(buffer,"@<TRIPOS>MOLECULE",17))
628
          break;
629
      }
630
631
    ifs.seekg(pos); // go back to the end of the molecule
632
    */
633
634
0
    return(true);
635
0
  }
636
637
  ////////////////////////////////////////////////////////////////
638
639
  bool MOL2Format::WriteMolecule(OBBase* pOb, OBConversion* pConv)
640
6.20k
  {
641
6.20k
    OBMol* pmol = dynamic_cast<OBMol*>(pOb);
642
6.20k
    if (pmol == nullptr)
643
0
      return false;
644
645
    //Define some references so we can use the old parameter names
646
6.20k
    ostream &ofs = *pConv->GetOutStream();
647
6.20k
    OBMol &mol = *pmol;
648
6.20k
    bool ligandsOnly = pConv->IsOption("l", OBConversion::OUTOPTIONS) != nullptr;
649
6.20k
    bool skipFormalCharge = pConv->IsOption("u", OBConversion::OUTOPTIONS) != nullptr;
650
651
    //The old code follows....
652
6.20k
    string str,str1;
653
6.20k
    char buffer[BUFF_SIZE],label[BUFF_SIZE];
654
6.20k
    char rnum[BUFF_SIZE],rlabel[BUFF_SIZE];
655
656
    //Check if UCSF Dock style coments are on
657
6.20k
    if (pConv->IsOption("c", OBConversion::OUTOPTIONS) != nullptr) {
658
0
        vector<OBGenericData*>::iterator k;
659
0
        vector<OBGenericData*> vdata = mol.GetData();
660
0
        ofs << endl;
661
0
        for (k = vdata.begin();k != vdata.end();++k) {
662
0
            if ((*k)->GetDataType() == OBGenericDataType::PairData
663
0
            && (*k)->GetOrigin()!=local //internal OBPairData is not written
664
0
            && (*k)->GetAttribute()!="PartialCharges")
665
0
            {
666
0
                ofs << "##########\t" << (*k)->GetAttribute() << ":\t" << ((OBPairData*)(*k))->GetValue() << endl;
667
0
            }
668
0
        }
669
0
        ofs << endl;
670
0
    }
671
672
6.20k
    ofs << "@<TRIPOS>MOLECULE" << endl;
673
6.20k
    str = mol.GetTitle();
674
6.20k
    if (str.empty())
675
5.42k
      ofs << "*****" << endl;
676
780
    else
677
780
      ofs << str << endl;
678
679
6.20k
    snprintf(buffer, BUFF_SIZE," %d %d 0 0 0", mol.NumAtoms(),mol.NumBonds());
680
6.20k
    ofs << buffer << endl;
681
6.20k
    ofs << "SMALL" << endl; // TODO: detect if we have protein, biopolymer, etc.
682
683
6.20k
    OBPairData *dp = (OBPairData*)mol.GetData("PartialCharges");
684
6.20k
    if (dp != nullptr) {
685
        // Tripos spec says:
686
        // NO_CHARGES, DEL_RE, GASTEIGER, GAST_HUCK, HUCKEL, PULLMAN,
687
        // GAUSS80_CHARGES, AMPAC_CHARGES, MULLIKEN_CHARGES, DICT_ CHARGES,
688
        // MMFF94_CHARGES, USER_CHARGES
689
717
      if (strcasecmp(dp->GetValue().c_str(),"Mulliken") == 0)
690
3
        ofs << "MULLIKEN_CHARGES" << endl;
691
714
      else if (strcasecmp(dp->GetValue().c_str(),"MMFF94") == 0)
692
3
        ofs << "MMFF94_CHARGES" << endl;
693
711
      else if (strcasecmp(dp->GetValue().c_str(),"ESP") == 0)
694
3
        ofs << "USER_CHARGES" << endl;
695
708
      else if (strcasecmp(dp->GetValue().c_str(),"Gasteiger") == 0)
696
3
        ofs << "GASTEIGER" << endl;
697
705
      else // ideally, code should pick from the Tripos types
698
705
        ofs << "USER_CHARGES" << endl;
699
717
    }
700
5.48k
    else { // No idea what these charges are... all our code sets "PartialCharges"
701
5.48k
        ofs << "GASTEIGER" << endl;
702
5.48k
    }
703
704
    //    ofs << "Energy = " << mol.GetEnergy() << endl;
705
706
6.20k
    if (mol.HasData(OBGenericDataType::CommentData))
707
0
      {
708
0
        ofs << "****\n"; // comment line printed, so we need to add "no status bits set"
709
0
        OBCommentData *cd = (OBCommentData*)mol.GetData(OBGenericDataType::CommentData);
710
0
        ofs << cd->GetData();
711
0
      }
712
713
6.20k
    ofs << endl;
714
6.20k
    ofs << "@<TRIPOS>ATOM" << endl;
715
716
6.20k
    OBAtom *atom;
717
6.20k
    OBResidue *res;
718
719
6.20k
    vector<OBAtom*>::iterator i;
720
6.20k
    std::map<int, int> labelcount;
721
722
6.20k
    ttab.SetFromType("INT");
723
6.20k
    ttab.SetToType("SYB");
724
725
6.20k
    bool hasFormalCharges = false;
726
6.20k
    for (atom = mol.BeginAtom(i);atom;atom = mol.NextAtom(i))
727
0
      {
728
729
        //
730
        //  Use sequentially numbered atom names if no residues
731
        //
732
733
0
        snprintf(label,BUFF_SIZE, "%s%d",
734
0
                 OBElements::GetSymbol(atom->GetAtomicNum()),
735
0
                 ++labelcount[atom->GetAtomicNum()]);
736
0
        strcpy(rlabel,"<1>");
737
0
        strcpy(rnum,"1");
738
739
0
        str = atom->GetType();
740
0
        ttab.Translate(str1,str);
741
742
0
        if (atom->GetFormalCharge() != 0) hasFormalCharges = true;
743
        //
744
        //  Use original atom names if there are residues
745
        //
746
747
0
        if (!ligandsOnly && (res = atom->GetResidue()) )
748
0
          {
749
            // use original atom names defined by residue
750
0
            snprintf(label,BUFF_SIZE,"%s",(char*)res->GetAtomID(atom).c_str());
751
            // make sure that residue name includes its number
752
0
            snprintf(rlabel,BUFF_SIZE,"%s%d",res->GetName().c_str(), res->GetNum());
753
0
            snprintf(rnum,BUFF_SIZE,"%d",res->GetNum());
754
0
          }
755
756
0
        snprintf(buffer,BUFF_SIZE,"%7d %-6s   %9.4f %9.4f %9.4f %-5s %3s  %-8s %9.4f",
757
0
                 atom->GetIdx(),label,
758
0
                 atom->GetX(),atom->GetY(),atom->GetZ(),
759
0
                 str1.c_str(),
760
0
                 rnum,rlabel,
761
0
                 atom->GetPartialCharge());
762
0
        ofs << buffer << endl;
763
0
      }
764
765
    //store formal charge info; put before bonds so we don't have
766
    //to read past the end of the molecule to realize it is there
767
6.20k
    if(hasFormalCharges && !skipFormalCharge) {
768
      //dkoes - to enable roundtriping of charges
769
0
      ofs << "@<TRIPOS>UNITY_ATOM_ATTR\n";
770
0
      for (atom = mol.BeginAtom(i);atom;atom = mol.NextAtom(i))
771
0
      {
772
0
        int charge = atom->GetFormalCharge();
773
0
        if (charge != 0) 
774
0
        {
775
0
          ofs << atom->GetIdx() << " 1\n"; //one attribute
776
0
          ofs << "charge " << charge << "\n"; //namely charge
777
0
        }
778
0
      }
779
0
    }
780
781
6.20k
    ofs << "@<TRIPOS>BOND" << endl;
782
6.20k
    OBBond *bond;
783
6.20k
    vector<OBBond*>::iterator j;
784
6.20k
    string s1, s2;
785
6.20k
    for (bond = mol.BeginBond(j);bond;bond = mol.NextBond(j))
786
0
      {
787
0
        s1 = bond->GetBeginAtom()->GetType();
788
0
        s2 = bond->GetEndAtom()->GetType();
789
0
        if (bond->IsAromatic() || s1 == "O.co2" || s2 == "O.co2")
790
0
          strcpy(label,"ar");
791
0
        else if (bond->IsAmide())
792
0
          strcpy(label,"am");
793
0
        else
794
0
          snprintf(label,BUFF_SIZE,"%d",bond->GetBondOrder());
795
796
0
        snprintf(buffer, BUFF_SIZE,"%6d %5d %5d   %2s",
797
0
                 bond->GetIdx()+1,bond->GetBeginAtomIdx(),bond->GetEndAtomIdx(),
798
0
                 label);
799
0
        ofs << buffer << endl;
800
0
      }
801
    // NO trailing blank line (PR#1868929).
802
    //    ofs << endl;
803
804
6.20k
    return(true);
805
6.20k
  }
806
807
  int MOL2Format::SkipObjects(int n, OBConversion* pConv)
808
0
  {
809
0
    const char txt[] = "@<TRIPOS>MOLECULE";
810
0
    istream& ifs = *pConv->GetInStream();
811
0
    if(!ifs)
812
0
      return -1;
813
0
    if(n>0 && ifs.peek()==txt[0])
814
0
      ifs.ignore(); // move past '@' so that next mol will be found
815
0
    do {
816
0
      ignore(ifs, txt);
817
0
    } while(ifs && (--n)>0);
818
819
0
    if(!ifs.eof())
820
0
      ifs.seekg(1-sizeof(txt), ios::cur);//1 for '/0'
821
0
    char ch = ifs.peek();
822
0
   return 1;
823
0
  }
824
825
} //namespace OpenBabel