Coverage Report

Created: 2026-08-31 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/obconversion.cpp
Line
Count
Source
1
/**********************************************************************
2
obconversion.cpp -  Declaration of OBFormat and OBConversion
3
4
Copyright (C) 2004 by Chris Morley
5
Some portions Copyright (C) 2005-2006 by Geoffrey Hutchison
6
7
This file is part of the Open Babel project.
8
For more information, see <http://openbabel.org/>
9
10
This program is free software; you can redistribute it and/or modify
11
it under the terms of the GNU General Public License as published by
12
the Free Software Foundation version 2 of the License.
13
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
GNU General Public License for more details.
18
***********************************************************************/
19
// Definition of OBConversion routines
20
#include <openbabel/babelconfig.h>
21
#include <openbabel/base.h>
22
23
#ifdef _WIN32
24
  #pragma warning (disable : 4786)
25
26
  //using 'this' in base class initializer
27
  #pragma warning (disable : 4355)
28
29
  #ifdef GUI
30
    #undef DATADIR
31
    #include "stdafx.h" //(includes<windows.h>
32
  #endif
33
#endif
34
35
// #define DONT_CATCH_EXCEPTIONS     // This is useful when debugging an exception
36
37
#include <iosfwd>
38
#include <fstream>
39
#include <sstream>
40
#include <string>
41
#include <map>
42
#include <locale>
43
#include <limits>
44
#include <typeinfo>
45
#include <iterator>
46
47
#include <cstdlib>
48
49
#include <openbabel/obconversion.h>
50
//#include <openbabel/mol.h>
51
#include <openbabel/locale.h>
52
53
#ifdef HAVE_LIBZ
54
#include "zipstream.h"
55
#endif
56
57
#if !HAVE_STRNCASECMP
58
extern "C" int strncasecmp(const char *s1, const char *s2, size_t n);
59
#endif
60
61
#ifndef BUFF_SIZE
62
#define BUFF_SIZE 32768
63
#endif
64
65
using namespace std;
66
//using namespace boost::iostreams;
67
68
namespace OpenBabel {
69
70
  /** @class OBFormat obconversion.h <openbabel/obconversion.h>
71
      Two sets of Read and Write functions are specified for each format
72
      to handle two different requirements.
73
      The "Convert" interface is for use in file format conversion applications. The
74
      user interface, a console, a GUI, or another program is kept unaware of the
75
      details of the chemistry and does not need to \#include mol.h. It is then
76
      necessary to manipulate only pointers to OBBase in OBConversion and the user
77
      interface, with all the construction and deletion of OBMol etc objects being
78
      done in the Format classes or the OB core. The convention  with "Covert"
79
      interface functions is that chemical objects are made on the heap with new
80
      in the ReadChemicalObject() functions and and deleted in WriteChemicalObject()
81
      functions
82
83
      The "API" interface is for programatic use of the OB routines in application
84
      programs where mol.h is \#included. There is generally no creation or
85
      destruction of objects in ReadMolecule() and WriteMolecule() and no restriction
86
      on whether the pointers are to the heap or the stack.
87
  **/
88
  //***************************************************
89
90
  /** @class OBConversion obconversion.h <openbabel/obconversion.h>
91
      OBConversion maintains a list of the available formats,
92
      provides information on them, and controls the conversion process.
93
94
      A conversion is carried out by the calling routine, usually in a
95
      user interface or an application program, making an instance of
96
      OBConversion. It is loaded with the in and out formats, any options
97
      and (usually) the default streams for input and output. Then either
98
      the Convert() function is called, which allows a single input file
99
      to be converted, or the extended functionality of FullConvert()
100
      is used. This allows multiple input and output files, allowing:
101
      - aggregation      - the contents of many input files converted
102
      and sent to one output file;
103
      - splitting        - the molecules from one input file sent to
104
      separate output files;
105
      - batch conversion - each input file converted to an output file.
106
107
      These procedures constitute the "Convert" interface. OBConversion
108
      and the user interface or application program do not need to be
109
      aware of any other part of OpenBabel - mol.h is not \#included. This
110
      allows any chemical object derived from OBBase to be converted;
111
      the type of object is decided by the input format class.
112
      However,currently, almost all the conversions are for molecules of
113
      class OBMol.
114
      ///
115
      OBConversion can also be used with an "API" interface
116
      called from programs which manipulate chemical objects. Input/output is
117
      done with the Read() and Write() functions which work with any
118
      chemical object, but need to have its type specified. (The
119
      ReadMolecule() and WriteMolecule() functions of the format classes
120
      can also be used directly.)
121
122
123
      Example code using OBConversion
124
125
      <b>To read in a molecule, manipulate it and write it out.</b>
126
127
      Set up an istream and an ostream, to and from files or elsewhere.
128
      (cin and cout are used in the example). Specify the file formats.
129
130
      @code
131
      OBConversion conv(&cin,&cout);
132
      if(conv.SetInAndOutFormats("SMI","MOL"))
133
      {
134
         OBMol mol;
135
         if(conv.Read(&mol))
136
            // ...manipulate molecule
137
138
         conv->Write(&mol);
139
      }
140
      @endcode
141
142
      A two stage construction is used to allow error handling
143
      if the format ID is not recognized. This is necessary now that the
144
      formats are dynamic and errors are not caught at compile time.
145
      OBConversion::Read() uses a pointer to OBBase, so that, in addition
146
      to OBMol, other kinds of objects, such as reactions, can also be handled
147
      if the format routines are written appropriately.
148
149
      <b>To make a molecule from a SMILES string.</b>
150
      @code
151
      std::string SmilesString;
152
      OBMol mol;
153
      stringstream ss(SmilesString)
154
      OBConversion conv(&ss);
155
      if(conv.SetInFormat("smi") && conv.Read(&mol))
156
         // ...
157
      @endcode
158
159
      An alternative way is more convenient if using bindings from another language:
160
      @code
161
      std::string SmilesString;
162
      OBMol mol;
163
      OBConversion conv;
164
      if(conv.SetInFormat("smi") && conv.ReadString(&mol, SmilesString))
165
         // ...
166
      @endcode
167
168
      <b>To do a file conversion without manipulating the molecule.</b>
169
170
      @code
171
      #include <openbabel/obconversion.h> //mol.h is not needed
172
      ...set up an istream is and an ostream os
173
      OBConversion conv(&is,&os);
174
      if(conv.SetInAndOutFormats("SMI","MOL"))
175
      {
176
         conv.AddOption("h",OBConversion::GENOPTIONS); //Optional; (h adds expicit hydrogens)
177
         conv.Convert();
178
      }
179
      @endcode
180
181
      <b>To read a multi-molecule file if using bindings from another language</b>
182
183
      The first molecule should be read using ReadFile, and subsequent molecules using Read,
184
      as follows:
185
      @code
186
      #include <openbabel/obconversion.h> //mol.h is not needed
187
      OBConversion conv;
188
      OBMol mol;
189
      bool success = conv.SetInFormat("sdf");
190
      if(success)
191
      {
192
         bool notatend = conv.ReadFile(&mol, "myfile.sdf");
193
         // Do something with mol
194
   while(notatend)
195
   {
196
             notatend = conv.Read(&mol);
197
       // Do something with mol
198
   }
199
      }
200
      @endcode
201
202
      <b>To add automatic format conversion to an existing program.</b>
203
204
      The existing program inputs from the file identified by the
205
      const char* filename into the istream is. The file is assumed to have
206
      a format ORIG, but other formats, identified by their file extensions,
207
      can now be used.
208
209
      @code
210
      ifstream ifs(filename); //Original code
211
212
      OBConversion conv;
213
      OBFormat* inFormat = conv.FormatFromExt(filename);
214
      OBFormat* outFormat = conv.GetFormat("ORIG");
215
      istream* pIn = &ifs;
216
      stringstream newstream;
217
      if(inFormat && outFormat)
218
      {
219
         conv.SetInAndOutFormats(inFormat,outFormat);
220
         conv.Convert(pIn,&newstream);
221
         pIn=&newstream;
222
      }
223
      //else error; new features not available; fallback to original functionality
224
225
      ...Carry on with original code using pIn
226
      @endcode
227
  */
228
229
//  OBFormat* OBConversion::pDefaultFormat=NULL;
230
231
  OBConversion::OBConversion(istream* is, ostream* os) :
232
17.5k
    pInput(nullptr), pOutput(nullptr),
233
17.5k
    pInFormat(nullptr),pOutFormat(nullptr), Index(0), StartNumber(1),
234
17.5k
    EndNumber(0), Count(-1), m_IsFirstInput(true), m_IsLast(true),
235
17.5k
    MoreFilesToCome(false), OneObjectOnly(false), ReadyToInput(false), SkippedMolecules(false),
236
17.5k
    inFormatGzip(false), outFormatGzip(false),
237
17.5k
    pOb1(nullptr), wInpos(0), wInlen(0), pAuxConv(nullptr)
238
17.5k
  {
239
17.5k
    SetInStream(is);
240
17.5k
    SetOutStream(os);
241
242
    //These options take a parameter
243
17.5k
    RegisterOptionParam("f", nullptr, 1,GENOPTIONS);
244
17.5k
    RegisterOptionParam("l", nullptr, 1,GENOPTIONS);
245
17.5k
  }
246
247
  /// Convenience constructor.  Sets up streams from specified files.
248
  /// If format can not be determined from filename, a stream is not opened.
249
  OBConversion::OBConversion(string infile, string outfile):
250
0
        pInput(nullptr), pOutput(nullptr),
251
0
        pInFormat(nullptr), pOutFormat(nullptr), Index(0), StartNumber(1),
252
0
        EndNumber(0), Count(-1), m_IsFirstInput(true), m_IsLast(true),
253
0
        MoreFilesToCome(false), OneObjectOnly(false), ReadyToInput(false), SkippedMolecules(false),
254
0
        inFormatGzip(false), outFormatGzip(false),
255
0
        pOb1(nullptr), wInpos(0), wInlen(0), pAuxConv(nullptr)
256
0
  {
257
    //These options take a parameter
258
0
    RegisterOptionParam("f", nullptr, 1,GENOPTIONS);
259
0
    RegisterOptionParam("l", nullptr, 1,GENOPTIONS);
260
261
0
    OpenInAndOutFiles(infile, outfile);
262
0
  }
263
264
  /////////////////////////////////////////////////
265
  OBConversion::OBConversion(const OBConversion& o)
266
0
  {
267
0
    *this = o;
268
0
  }
269
270
271
  OBConversion& OBConversion::operator=(const OBConversion& o)
272
0
  {
273
    //the original obconversion retains ownership of any allocated streams
274
    //this means if the original gets destroyed, bad things may happen
275
    //by doing this first, format isn't initialized, so we add no additional filters
276
0
    pInFormat = nullptr;
277
0
    inFormatGzip = false;
278
0
    pOutFormat = nullptr;
279
0
    outFormatGzip = false;
280
0
    SetInStream(o.pInput, false);
281
0
    SetOutStream(o.pOutput, false);
282
283
0
    Index          = o.Index;
284
0
    Count          = o.Count;
285
0
    StartNumber    = o.StartNumber;
286
0
    EndNumber      = o.EndNumber;
287
0
    pInFormat      = o.pInFormat;
288
0
    inFormatGzip   = o.inFormatGzip;
289
0
    pOutFormat     = o.pOutFormat;
290
0
    outFormatGzip  = o.outFormatGzip;
291
0
    OptionsArray[0]= o.OptionsArray[0];
292
0
    OptionsArray[1]= o.OptionsArray[1];
293
0
    OptionsArray[2]= o.OptionsArray[2];
294
0
    InFilename     = o.InFilename;
295
0
    rInpos         = o.rInpos;
296
0
    wInpos         = o.wInpos;
297
0
    rInlen         = o.rInlen;
298
0
    wInlen         = o.wInlen;
299
0
    m_IsLast       = o.m_IsLast;
300
0
    MoreFilesToCome= o.MoreFilesToCome;
301
0
    OneObjectOnly  = o.OneObjectOnly;
302
0
    pOb1           = o.pOb1;
303
0
    ReadyToInput   = o.ReadyToInput;
304
0
    m_IsFirstInput = o.m_IsFirstInput;
305
0
    SkippedMolecules = o.SkippedMolecules;
306
0
    pAuxConv       = o.pAuxConv;
307
308
0
     return *this;
309
0
  }
310
  ///////////////////////////////////////////////
311
312
  OBConversion::~OBConversion()
313
17.5k
  {
314
17.5k
    if(pAuxConv!=this)
315
17.5k
      delete pAuxConv;
316
    // Free any remaining streams from convenience functions
317
17.5k
    SetInStream(nullptr);
318
17.5k
    SetOutStream(nullptr);
319
320
17.5k
  }
321
  //////////////////////////////////////////////////////
322
323
  /// Class information on formats is collected by making an instance of the class
324
  /// derived from OBFormat(only one is usually required). RegisterFormat() is called
325
  /// from its constructor.
326
  ///
327
  /// If the compiled format is stored separately, like in a DLL or shared library,
328
  /// the initialization code makes an instance of the imported OBFormat class.
329
  int OBConversion::RegisterFormat(const char* ID, OBFormat* pFormat, const char* MIME)
330
320
  {
331
320
    return pFormat->RegisterFormat(ID, MIME);
332
320
  }
333
334
  /// Set input stream, removing/deallocating previous stream if necessary.
335
  /// If takeOwnership is true, takes responsibility for freeing pIn
336
  void OBConversion::SetInStream(std::istream* pIn, bool takeOwnership)
337
52.6k
  {
338
      //clear and deallocate any existing streams
339
87.7k
      for(unsigned i = 0, n = ownedInStreams.size(); i < n; i++)
340
35.0k
      {
341
35.0k
        delete ownedInStreams[i];
342
35.0k
      }
343
52.6k
      ownedInStreams.clear();
344
52.6k
      pInput = nullptr;
345
346
52.6k
      if (pIn)
347
17.5k
      {
348
17.5k
          if(takeOwnership)
349
17.5k
              ownedInStreams.push_back(pIn);
350
17.5k
          pInput = pIn; //simplest case
351
352
  #ifdef HAVE_LIBZ
353
          if(IsOption("zin", GENOPTIONS) || inFormatGzip)
354
          {
355
            zlib_stream::zip_istream *zIn = new zlib_stream::zip_istream(*pInput);
356
            ownedInStreams.push_back(zIn);
357
            pInput = zIn;
358
          }
359
  #endif
360
          //always transform newlines if input isn't binary/xml
361
17.5k
          if(pInFormat && !(pInFormat->Flags() & (READBINARY | READXML)) &&
362
17.5k
              pIn != &std::cin) //avoid filtering stdin as well
363
17.5k
          {
364
17.5k
            LEInStream *leIn = new LEInStream(*pInput);
365
17.5k
            ownedInStreams.push_back(leIn);
366
17.5k
            pInput = leIn;
367
17.5k
          }
368
17.5k
      }
369
52.6k
  }
370
371
  /// Set output stream, removing/deallocating previous stream if necessary.
372
  /// If takeOwnership is true, takes responsibility for freeing pOut
373
  /// Be aware that if the output stream is gzipped format, then this outstream
374
  /// either needs to be replaced (e.g., SetOutStream(NULL)) or the OBConversion
375
  /// destroyed before the underlying outputstream is deallocated.
376
  void OBConversion::SetOutStream(std::ostream* pOut, bool takeOwnership)
377
35.0k
  {
378
    //clear and deallocate any existing streams
379
35.0k
    for (unsigned i = 0, n = ownedOutStreams.size(); i < n; i++)
380
0
    {
381
0
      delete ownedOutStreams[i];
382
0
    }
383
35.0k
    ownedOutStreams.clear();
384
35.0k
    pOutput = nullptr;
385
386
35.0k
    if (pOut)
387
0
    {
388
0
      if (takeOwnership)
389
0
        ownedOutStreams.push_back(pOut);
390
0
      pOutput = pOut;
391
392
#ifdef HAVE_LIBZ
393
394
      if (IsOption("z", GENOPTIONS) || outFormatGzip)
395
      {
396
        zlib_stream::zip_ostream *zOut = new zlib_stream::zip_ostream(*pOutput, true);
397
        //we need to delete the zstream _before_ the underlying stream so it can add the footer
398
        ownedOutStreams.insert(ownedOutStreams.begin(),zOut);
399
        pOutput = zOut;
400
      }
401
#endif
402
0
    }
403
35.0k
  }
404
405
406
407
//////////////////////////////////////////////////////
408
  /// Sets the formats from their ids, e g CML.
409
  /// If inID is NULL, the input format is left unchanged. Similarly for outID
410
  /// Returns true if both formats have been successfully set at sometime
411
  bool OBConversion::SetInAndOutFormats(const char* inID, const char* outID, bool inzip, bool outzip)
412
0
  {
413
0
    return SetInFormat(inID, inzip) && SetOutFormat(outID, outzip);
414
0
  }
415
  //////////////////////////////////////////////////////
416
417
  bool OBConversion::SetInAndOutFormats(OBFormat* pIn, OBFormat* pOut, bool inzip, bool outzip)
418
0
  {
419
0
    return SetInFormat(pIn, inzip) && SetOutFormat(pOut, outzip);
420
0
  }
421
  //////////////////////////////////////////////////////
422
  bool OBConversion::SetInFormat(OBFormat* pIn, bool gzip)
423
0
  {
424
0
    inFormatGzip = gzip;
425
0
    if (pIn == nullptr)
426
0
      return true;
427
0
    pInFormat=pIn;
428
0
    return !(pInFormat->Flags() & NOTREADABLE);
429
0
  }
430
  //////////////////////////////////////////////////////
431
  bool OBConversion::SetOutFormat(OBFormat* pOut, bool gzip)
432
0
  {
433
0
    outFormatGzip = gzip;
434
0
    pOutFormat=pOut;
435
0
    return pOut && !(pOutFormat->Flags() & NOTWRITABLE);
436
0
  }
437
  //////////////////////////////////////////////////////
438
  bool OBConversion::SetInFormat(const char* inID, bool gzip)
439
17.5k
  {
440
17.5k
    inFormatGzip = gzip;
441
17.5k
    if(inID)
442
17.5k
      pInFormat = FindFormat(inID);
443
17.5k
    return pInFormat && !(pInFormat->Flags() & NOTREADABLE);
444
17.5k
  }
445
  //////////////////////////////////////////////////////
446
447
  bool OBConversion::SetOutFormat(const char* outID, bool gzip)
448
0
  {
449
0
    outFormatGzip = gzip;
450
0
    if(outID)
451
0
      pOutFormat= FindFormat(outID);
452
0
    return pOutFormat && !(pOutFormat->Flags() & NOTWRITABLE);
453
0
  }
454
455
  //////////////////////////////////////////////////////
456
  /// Convert molecules from is into os.  If either is null, uses existing streams.
457
  /// If streams are specified, they do _not_ replace any existing streams.
458
  int OBConversion::Convert(istream* is, ostream* os)
459
0
  {
460
0
    StreamState savedIn, savedOut;
461
0
    if (is)
462
0
    {
463
#ifdef HAVE_LIBZ
464
      if(!inFormatGzip && pInFormat && zlib_stream::isGZip(*is))
465
      {
466
        inFormatGzip = true;
467
      }
468
#endif
469
0
      savedIn.pushInput(*this);
470
0
      SetInStream(is, false);
471
0
    }
472
473
0
    if (os)
474
0
    {
475
0
      savedOut.pushOutput(*this);
476
0
      SetOutStream(os, false);
477
0
    }
478
479
0
    int count = Convert();
480
481
0
    if(savedIn.isSet()) savedIn.popInput(*this);
482
0
    if(savedOut.isSet()) savedOut.popOutput(*this);
483
484
0
    return count;
485
0
  }
486
487
488
  ////////////////////////////////////////////////////
489
  /// Actions the "convert" interface.
490
  /// Calls the OBFormat class's ReadMolecule() which
491
  ///  - makes a new chemical object of its chosen type (e.g. OBMol)
492
  ///  - reads an object from the input file
493
  ///  - subjects the chemical object to 'transformations' as specified by the Options
494
  ///  - calls AddChemObject to add it to a buffer. The previous object is first output
495
  ///    via the output Format's WriteMolecule(). During the output process calling
496
  /// IsFirst() and GetIndex() (the number of objects including the current one already output.
497
  /// allows more control, for instance writing \<cml\> and \</cml\> tags for multiple molecule outputs only.
498
  ///
499
  /// AddChemObject does not save the object passed to it if it is NULL (as a result of a DoTransformation())
500
  /// or if the number of the object is outside the range defined by
501
  /// StartNumber and EndNumber.This means the start and end counts apply to all chemical objects
502
  /// found whether or not they are output.
503
  ///
504
  /// If ReadMolecule returns false the input conversion loop is exited.
505
  ///
506
  int OBConversion::Convert()
507
0
  {
508
0
    if (pInput == nullptr)
509
0
      {
510
0
        obErrorLog.ThrowError(__FUNCTION__, "input or output stream not set", obError);
511
0
        return 0;
512
0
      }
513
514
0
    if(!pInFormat) return 0;
515
0
    Count=0;//number objects processed
516
517
0
    if(!SetStartAndEnd())
518
0
      return 0;
519
520
0
    ReadyToInput=true;
521
0
    m_IsLast=false;
522
0
    pOb1=nullptr;
523
0
    wInlen=0;
524
525
0
    if(pInFormat->Flags() & READONEONLY)
526
0
      OneObjectOnly=true;
527
528
    //Input loop
529
0
    while(ReadyToInput && pInput->good()) //Possible to omit? && pInStream->peek() != EOF
530
0
      {
531
0
        if(pInput==&cin)
532
0
          {
533
0
            if(pInput->peek()==-1) //Cntl Z Was \n but interfered with piping
534
0
            {
535
0
              if (!IsOption("separate", OBConversion::GENOPTIONS))
536
0
                break;
537
0
              pInput->clear();
538
0
            }
539
0
          }
540
0
        else
541
0
          rInpos = pInput->tellg();
542
0
        bool ret=false;
543
0
#ifndef DONT_CATCH_EXCEPTIONS
544
0
       try
545
0
#endif
546
0
          {
547
0
            ret = pInFormat->ReadChemObject(this);
548
/*            if (ret && IsOption("readconformers", GENOPTIONS)) {
549
              std::streampos pos = pInStream->tellg();
550
              OBMol nextMol;
551
              OBConversion conv;
552
              conv.SetOutFormat("smi");
553
              std::string ref_smiles = conv.WriteString(pOb1);
554
              while (pInStream->good()) {
555
                if (!pInFormat->ReadMolecule(&nextMol, this))
556
                  break;
557
                std::string smiles = conv.WriteString(&nextMol);
558
                if (smiles == ref_smiles) {
559
                  OBMol *pmol = dynamic_cast<OBMol*>(pOb1);
560
                  if (!pmol)
561
                    break;
562
                  unsigned int numCoords = nextMol.NumAtoms() * 3;
563
                  double *coords = nextMol.GetCoordinates();
564
                  double *conformer = new double [numCoords];
565
                  for (unsigned int i = 0; i < numCoords; ++i)
566
                    conformer[i] = coords[i];
567
                  pmol->AddConformer(conformer);
568
                  pos = pInStream->tellg();
569
                } else {
570
                  break;
571
                }
572
              }
573
              pInStream->seekg(pos, std::ios::beg);
574
            }
575
*/
576
0
            SetFirstInput(false);
577
0
          }
578
0
#ifndef DONT_CATCH_EXCEPTIONS
579
0
        catch(...)
580
0
          {
581
0
            if(!IsOption("e", GENOPTIONS) && !OneObjectOnly)
582
0
            {
583
0
              obErrorLog.ThrowError(__FUNCTION__, "Convert failed with an exception" , obError);
584
0
              return Index; // the number we've actually output so far
585
0
            }
586
0
          }
587
0
#endif
588
589
0
        if(!ret)
590
0
          {
591
            //error or termination request: terminate unless
592
            // -e option requested and successfully can skip past current object
593
0
            if(!IsOption("e", GENOPTIONS) || pInFormat->SkipObjects(0,this)!=1)
594
0
              break;
595
0
          }
596
0
        if(OneObjectOnly)
597
0
          break;
598
        // Objects supplied to AddChemObject() which may output them after a delay
599
        //ReadyToInput may be made false in AddChemObject()
600
        // by WriteMolecule() returning false  or by Count==EndNumber
601
0
      }
602
603
    //Output last object
604
0
    m_IsLast= !MoreFilesToCome;
605
606
    //Output is always occurs at the end with the --OutputAtEnd option
607
0
    bool oae = IsOption("OutputAtEnd", GENOPTIONS) != nullptr;
608
0
    if(pOutFormat && (!oae || m_IsLast))
609
0
      if((oae || pOb1) && !pOutFormat->WriteChemObject(this))
610
0
        Index--;
611
612
    //Put AddChemObject() into non-queue mode
613
0
    Count= -1;
614
0
    EndNumber=StartNumber=0; pOb1=nullptr; //leave tidy
615
0
    MoreFilesToCome=false;
616
0
    OneObjectOnly=false;
617
618
0
    return Index; //The number actually output
619
0
  }
620
  //////////////////////////////////////////////////////
621
  bool OBConversion::SetStartAndEnd()
622
17.5k
  {
623
17.5k
    unsigned int TempStartNumber=0;
624
17.5k
    const char* p = IsOption("f",GENOPTIONS);
625
17.5k
    if(p)
626
0
      {
627
0
        StartNumber=atoi(p);
628
0
        if(StartNumber>1)
629
0
          {
630
0
            TempStartNumber=StartNumber;
631
            //Try to skip objects now
632
0
            int ret = pInFormat->SkipObjects(StartNumber-1,this);
633
0
            if(ret==-1) //error
634
0
              return false;
635
0
            if(ret==1) //success:objects skipped
636
0
              {
637
0
                Count = StartNumber-1;
638
0
                StartNumber=0;
639
0
              }
640
0
          }
641
0
      }
642
643
17.5k
    p = IsOption("l",GENOPTIONS);
644
17.5k
    if(p)
645
0
      {
646
0
        EndNumber=atoi(p);
647
0
        if(TempStartNumber && EndNumber<TempStartNumber)
648
0
          EndNumber=TempStartNumber;
649
0
      }
650
651
17.5k
    return true;
652
17.5k
  }
653
654
  //////////////////////////////////////////////////////
655
  /// Retrieves an object stored by AddChemObject() during output
656
  OBBase* OBConversion::GetChemObject()
657
0
  {
658
0
    Index++;
659
0
    return pOb1;
660
0
  }
661
662
  //////////////////////////////////////////////////////
663
  /// Called by ReadMolecule() to deliver an object it has read from an input stream.
664
  /// Used in two modes:
665
  ///  - When Count is negative it is left negative and the routine is just a store
666
  ///    for an OBBase object.  The negative value returned tells the calling
667
  ///    routine that no more objects are required.
668
  ///  - When count is >=0, probably set by Convert(), it acts as a queue of 2:
669
  ///    writing the currently stored value before accepting the supplied one. This delay
670
  ///    allows output routines to respond differently when the written object is the last.
671
  ///    Count is incremented with each call, even if pOb=NULL.
672
  ///    Objects are not added to the queue if the count is outside the range
673
  ///    StartNumber to EndNumber. There is no upper limit if EndNumber is zero.
674
  ///    The return value is Count ((>0) or 0 if WriteChemObject returned false.
675
  int OBConversion::AddChemObject(OBBase* pOb)
676
0
  {
677
0
    if(Count<0)
678
0
      {
679
0
        pOb1=pOb;
680
0
        return Count; // <0
681
0
      }
682
0
    Count++;
683
0
    if(Count>=(int)StartNumber)//keeps reading objects but does nothing with them
684
0
      {
685
0
        if(Count==(int)EndNumber)
686
0
          ReadyToInput=false; //stops any more objects being read
687
688
0
        rInlen = pInput ? pInput->tellg() - rInpos : 0;
689
         // - (pLineEndBuf ? pLineEndBuf->getCorrection() : 0); //correction for CRLF
690
691
0
        if(pOb)
692
0
          {
693
0
            if(pOb1 && pOutFormat) //see if there is an object ready to be output
694
0
              {
695
                //Output object
696
0
                if (!pOutFormat->WriteChemObject(this))
697
0
                  {
698
                    //faultly write, so finish
699
0
                    --Index;
700
                    //ReadyToInput=false;
701
0
                    pOb1 = nullptr;
702
                    // The newly-read object never gets stored on this
703
                    // abort path; delete it so it does not leak.
704
0
                    delete pOb;
705
0
                    return 0;
706
0
                  }
707
                //Stop after writing with single object output files
708
0
                if(pOutFormat->Flags() & WRITEONEONLY)
709
0
                  {
710
                    // if there are more molecules to output, send a warning
711
0
                    stringstream errorMsg;
712
0
                    errorMsg << "WARNING: You are attempting to convert a file"
713
0
                             << " with multiple molecule entries into a format"
714
0
                             << " which can only store one molecule. The current"
715
0
                             << " output will only contain the first molecule.\n\n";
716
717
0
                    errorMsg << "To convert this input into multiple separate"
718
0
                             << " output files, with one molecule per file, try:\n"
719
0
                             << "obabel [input] [output] -m\n\n";
720
721
0
                    errorMsg << "To pick one particular molecule"
722
0
                             << " (e.g., molecule 4), try:\n"
723
0
                             << "obabel -f 4 -l 4 [input] [output]" << endl;
724
725
0
                    obErrorLog.ThrowError(__FUNCTION__, errorMsg.str(), obWarning);
726
727
0
                    ReadyToInput = false;
728
0
                    pOb1 = nullptr;
729
                    // Surplus newly-read object; never stored, must free.
730
0
                    delete pOb;
731
0
                    return Count; // >0
732
0
                  }
733
0
              }
734
0
            pOb1=pOb;
735
0
            wInpos = rInpos; //Save the position in the input file to be accessed when writing it
736
0
            wInlen = rInlen;
737
0
          }
738
0
      }
739
0
    return Count; // >0
740
0
  }
741
  //////////////////////////////////////////////////////
742
  ///Returns the number of objects which have been output or are currently being output.
743
  ///The outputindex is incremented when an object for output is fetched by GetChemObject().
744
  ///So the function will return 1 if called from WriteMolecule() during output of the first object.
745
  int OBConversion::GetOutputIndex() const
746
0
  {
747
    //The number of objects actually written already from this instance of OBConversion
748
0
    return Index;
749
0
  }
750
  void OBConversion::SetOutputIndex(int indx)
751
0
  {
752
0
    Index=indx;
753
0
  }
754
  //////////////////////////////////////////////////////
755
  OBFormat* OBConversion::FindFormat(const char* ID)
756
17.5k
  {
757
17.5k
    return OBFormat::FindType(ID);
758
17.5k
  }
759
760
  OBFormat* OBConversion::FindFormat(const std::string ID)
761
0
  {
762
0
    return OBFormat::FindType(ID.c_str());
763
0
  }
764
765
  //////////////////////////////////////////////////
766
  const char* OBConversion::GetTitle() const
767
0
  {
768
0
    return(InFilename.c_str());
769
0
  }
770
771
  void OBConversion::SetMoreFilesToCome()
772
0
  {
773
0
    MoreFilesToCome=true;
774
0
  }
775
776
  void OBConversion::SetOneObjectOnly(bool b)
777
0
  {
778
0
    OneObjectOnly=b;
779
0
    m_IsLast=b;
780
0
  }
781
782
  /////////////////////////////////////////////////////////
783
  OBFormat* OBConversion::FormatFromExt(const char* filename, bool& isgzip)
784
0
  {
785
0
    string file = filename;
786
0
    string::size_type extPos = file.rfind('.');
787
0
    isgzip = false;
788
0
    if(extPos!=string::npos // period found
789
0
       && (file.substr(extPos + 1, file.size())).find("/")==string::npos) // and period is after the last "/"
790
0
      {
791
        // only do this if we actually can read .gz files
792
0
        if (file.substr(extPos) == ".gz")
793
0
           {
794
0
            isgzip = true;
795
0
            file.erase(extPos);
796
0
            extPos = file.rfind('.');
797
0
            if (extPos!=string::npos)
798
0
            {
799
0
              return FindFormat( (file.substr(extPos + 1, file.size())).c_str() );
800
0
            }
801
0
          }
802
0
        else
803
0
          return FindFormat( (file.substr(extPos + 1, file.size())).c_str() );
804
0
      }
805
806
    // Check the filename if no extension (e.g. VASP does not use extensions):
807
0
    extPos = file.rfind('/');
808
0
    if(extPos!=string::npos) {
809
0
      return FindFormat( (file.substr(extPos + 1, file.size())).c_str() );
810
0
    }
811
    // If we are just passed the filename with no path, this should catch it:
812
0
    return FindFormat( file.c_str() ); //if no format found
813
0
  }
814
815
  OBFormat* OBConversion::FormatFromExt(const char* filename)
816
0
  {
817
0
    bool isgzip;
818
0
    return FormatFromExt(filename, isgzip);
819
0
  }
820
821
  OBFormat* OBConversion::FormatFromExt(const std::string filename)
822
0
  {
823
0
    bool gzip;
824
0
    return FormatFromExt(filename.c_str(), gzip);
825
0
  }
826
827
  OBFormat* OBConversion::FormatFromExt(const std::string filename, bool& isgzip)
828
0
  {
829
0
    return FormatFromExt(filename.c_str(), isgzip);
830
0
  }
831
832
  OBFormat* OBConversion::FormatFromMIME(const char* MIME)
833
0
  {
834
0
    return OBFormat::FormatFromMIME(MIME);
835
0
  }
836
837
  bool  OBConversion::Read(OBBase* pOb, std::istream* pin)
838
17.5k
  {
839
17.5k
    if(pin) {
840
      //for backwards compatibility, attempt to detect a gzip file
841
#ifdef HAVE_LIBZ
842
    if(!inFormatGzip && pInFormat && zlib_stream::isGZip(*pin))
843
      {
844
        inFormatGzip = true;
845
      }
846
#endif
847
0
      SetInStream(pin, false);
848
0
    }
849
850
851
17.5k
    if(!pInFormat || !pInput) return false;
852
853
    //mysterious line to ensure backwards compatibility
854
    //previously, even an open istream would have the gzip check applied
855
    //this meant that a stream at the eof position would end up in an error state
856
    //code has come to depend on this behavior
857
17.5k
    if(pInput->eof()) pInput->get();
858
859
    // Set the locale for number parsing to avoid locale issues: PR#1785463
860
17.5k
    obLocale.SetLocale();
861
862
    // Also set the C++ stream locale
863
17.5k
    locale originalLocale = pInput->getloc(); // save the original
864
17.5k
    locale cNumericLocale(originalLocale, "C", locale::numeric);
865
17.5k
    pInput->imbue(cNumericLocale);
866
867
    // skip molecules if -f or -l option is set
868
17.5k
    if (!SkippedMolecules) {
869
17.5k
        Count = 0; // make sure it's 0
870
17.5k
        if(!SetStartAndEnd()) {
871
0
           return false;
872
0
        }
873
17.5k
        SkippedMolecules = true;
874
17.5k
    }
875
876
    // catch last molecule acording to -l
877
17.5k
    Count++;
878
17.5k
    bool success = false;
879
17.5k
    if (EndNumber==0 || (unsigned)Count<=EndNumber) {
880
17.5k
        success = pInFormat->ReadMolecule(pOb, this);
881
17.5k
    }
882
883
    // return the C locale to the original one
884
17.5k
    obLocale.RestoreLocale();
885
    // Restore the original C++ locale as well
886
17.5k
    pInput->imbue(originalLocale);
887
888
    // If we failed to read, plus the stream is over, then check if this is a stream from ReadFile
889
17.5k
    if (!success && !pInput->good() && ownedInStreams.size() > 0) {
890
2.26k
      ifstream *inFstream = dynamic_cast<ifstream*>(ownedInStreams[0]);
891
2.26k
      if (inFstream != nullptr)
892
0
        inFstream->close(); // We will free the stream later, but close the file now
893
2.26k
    }
894
895
17.5k
    return success;
896
17.5k
  }
897
898
899
  //////////////////////////////////////////////////
900
  /// Writes the object pOb but does not delete it afterwards.
901
  /// The output stream is lastingly changed if pos is not NULL
902
  /// Returns true if successful.
903
  bool OBConversion::Write(OBBase* pOb, ostream* pos)
904
0
  {
905
0
    if(pos) SetOutStream(pos, false);
906
907
0
    if(!pOutFormat || !pOutput) return false;
908
909
    // Set the locale for number parsing to avoid locale issues: PR#1785463
910
0
    obLocale.SetLocale();
911
    // Also set the C++ stream locale
912
0
    locale originalLocale = pOutput->getloc(); // save the original
913
0
    locale cNumericLocale(originalLocale, "C", locale::numeric);
914
0
    pOutput->imbue(cNumericLocale);
915
916
    // Increment the output counter.
917
    // This is done *before* the WriteMolecule because some of
918
    // the format plugins initialized when GetOutputIndex() == 1.
919
    // This matches the original Convert(), which increments
920
    // the count for GetChemObject() before the write.
921
0
    Index++;
922
923
    // The actual work is done here
924
0
    bool success = pOutFormat->WriteMolecule(pOb,this);
925
926
    // return the C locale to the original one
927
0
    obLocale.RestoreLocale();
928
    // Restore the C++ stream locale too
929
0
    pOutput->imbue(originalLocale);
930
931
0
    return success;
932
0
  }
933
934
  //save the current input state to this streamstate and clear conv
935
  void OBConversion::StreamState::pushInput(OBConversion& conv)
936
0
  {
937
0
    assert(ownedStreams.size() == 0); //should be empty
938
939
0
    pStream = conv.pInput;
940
0
    std::copy(conv.ownedInStreams.begin(), conv.ownedInStreams.end(), std::back_inserter(ownedStreams));
941
942
0
    conv.pInput = nullptr;
943
0
    conv.ownedInStreams.clear();
944
0
  }
945
946
  //restore state, blowing away whatever is in conv
947
  void OBConversion::StreamState::popInput(OBConversion& conv)
948
0
  {
949
0
    conv.SetInStream(nullptr);
950
0
    conv.pInput =  dynamic_cast<std::istream*>(pStream);
951
952
0
    assert(conv.ownedInStreams.size() == 0); //should be empty
953
954
0
    for(unsigned i = 0, n = conv.ownedInStreams.size(); i < n; i++)
955
0
    {
956
0
      std::istream *s = dynamic_cast<std::istream*>(conv.ownedInStreams[i]);
957
0
      assert(s);
958
0
      conv.ownedInStreams.push_back(s);
959
0
    }
960
961
0
    pStream = nullptr;
962
0
    ownedStreams.clear();
963
0
  }
964
965
  //save the current output state to this streamstate and clear conv
966
  void OBConversion::StreamState::pushOutput(OBConversion& conv)
967
0
  {
968
0
    assert(ownedStreams.size() == 0); //should be empty
969
970
0
    pStream = conv.pOutput;
971
0
    std::copy(conv.ownedOutStreams.begin(), conv.ownedOutStreams.end(), std::back_inserter(ownedStreams));
972
973
0
    conv.pOutput = nullptr;
974
0
    conv.ownedOutStreams.clear();
975
0
  }
976
977
  //restore state, blowing away whatever is in conv
978
  void OBConversion::StreamState::popOutput(OBConversion& conv)
979
0
  {
980
0
    conv.SetOutStream(nullptr);
981
0
    conv.pOutput =  dynamic_cast<std::ostream*>(pStream);
982
983
0
    assert(conv.ownedOutStreams.size() == 0); //should be empty
984
985
0
    for(unsigned i = 0, n = conv.ownedOutStreams.size(); i < n; i++)
986
0
    {
987
0
      std::ostream *s = dynamic_cast<std::ostream*>(conv.ownedOutStreams[i]);
988
0
      assert(s);
989
0
      conv.ownedOutStreams.push_back(s);
990
0
    }
991
992
0
    pStream = nullptr;
993
0
    ownedStreams.clear();
994
0
  }
995
996
  //////////////////////////////////////////////////
997
  /// Writes the object pOb but does not delete it afterwards.
998
  /// The output stream not changed (since we cannot write to this string later)
999
  /// Returns true if successful.
1000
  std::string OBConversion::WriteString(OBBase* pOb, bool trimWhitespace)
1001
0
  {
1002
0
    stringstream newStream;
1003
0
    string temp;
1004
1005
0
    if(pOutFormat)
1006
0
    {
1007
0
      StreamState savedOut;
1008
0
      savedOut.pushOutput(*this);
1009
      // The StreamState doesn't save all of the properties so
1010
      // do it manually here.
1011
      
1012
      // Set/reset the Index to 0 so that any initialization
1013
      // code in the formatters will be executed.
1014
0
      int oldIndex = Index;
1015
0
      Index = 0;
1016
      
1017
      // We'll only send one object, so save those properties too.
1018
0
      bool oldOneObjectOnly = OneObjectOnly;
1019
0
      bool oldm_IsLast = m_IsLast;
1020
      
1021
0
      SetOneObjectOnly(true);
1022
1023
0
      SetOutStream(&newStream, false);
1024
0
      Write(pOb);
1025
0
      savedOut.popOutput(*this);
1026
1027
      // Restore the other stream properties
1028
0
      m_IsLast = oldm_IsLast;
1029
0
      OneObjectOnly = oldOneObjectOnly;
1030
0
      Index = oldIndex;
1031
0
    }
1032
1033
0
    temp = newStream.str();
1034
0
    if (trimWhitespace) // trim the trailing whitespace
1035
0
      {
1036
0
        string::size_type notwhite = temp.find_last_not_of(" \t\n\r");
1037
0
        temp.erase(notwhite+1);
1038
0
      }
1039
0
    return temp;
1040
0
  }
1041
1042
  //////////////////////////////////////////////////
1043
  /// Writes the object pOb but does not delete it afterwards.
1044
  /// The output stream is lastingly changed to point to the file
1045
  /// Returns true if successful.
1046
  bool OBConversion::WriteFile(OBBase* pOb, string filePath)
1047
0
  {
1048
0
    if(!pOutFormat)
1049
0
    {
1050
      //attempt to autodetect format
1051
0
      pOutFormat = FormatFromExt(filePath.c_str(), outFormatGzip);
1052
0
      if(!pOutFormat)
1053
0
        return false;
1054
0
    }
1055
1056
0
    ios_base::openmode omode = ios_base::out|ios_base::binary;
1057
0
    ofstream *ofs = new ofstream(filePath.c_str(),omode);
1058
0
    if(!ofs || !ofs->good())
1059
0
      {
1060
0
        delete ofs;
1061
0
        obErrorLog.ThrowError(__FUNCTION__,"Cannot write to " + filePath, obError);
1062
0
        return false;
1063
0
      }
1064
1065
0
    SetOutStream(ofs, true);
1066
    // Set/reset the Index so that any initialization code
1067
    // in the formatters will be executed.
1068
0
    Index = 0;
1069
    // We can't touch the Last property because only the caller
1070
    // knows if the first molecule is also the last molecule.
1071
    
1072
0
    return Write(pOb);
1073
0
  }
1074
1075
  void OBConversion::CloseOutFile()
1076
0
  {
1077
0
    SetOutStream(nullptr);
1078
0
  }
1079
1080
  ////////////////////////////////////////////
1081
  bool  OBConversion::ReadString(OBBase* pOb, std::string input)
1082
17.5k
  {
1083
17.5k
    SetInStream(new stringstream(input), true);
1084
17.5k
    return Read(pOb);
1085
17.5k
  }
1086
1087
1088
  ////////////////////////////////////////////
1089
  bool  OBConversion::ReadFile(OBBase* pOb, std::string filePath)
1090
0
  {
1091
0
    if(!pInFormat)
1092
0
    {
1093
      //attempt to auto-detect file format from extension
1094
0
      pInFormat = FormatFromExt(filePath.c_str(), inFormatGzip);
1095
0
      if(!pInFormat)
1096
0
        return false;
1097
0
    }
1098
1099
    // save the filename
1100
0
    InFilename = filePath;
1101
0
    ios_base::openmode imode = ios_base::in|ios_base::binary; //now always binary because may be gzipped
1102
0
    ifstream *ifs = new ifstream(filePath.c_str(),imode);
1103
0
    if(!ifs || !ifs->good())
1104
0
    {
1105
0
        delete ifs;
1106
0
        obErrorLog.ThrowError(__FUNCTION__,"Cannot read from " + filePath, obError);
1107
0
        return false;
1108
0
    }
1109
#ifdef HAVE_LIBZ
1110
    if(!inFormatGzip && pInFormat && zlib_stream::isGZip(*ifs))
1111
    {
1112
      //for backwards compat, attempt to autodetect gzip
1113
      inFormatGzip = true;
1114
    }
1115
#endif
1116
1117
0
    SetInStream(ifs, true);
1118
0
    return Read(pOb);
1119
0
  }
1120
1121
  ////////////////////////////////////////////
1122
  bool OBConversion::OpenInAndOutFiles(std::string infilepath, std::string outfilepath)
1123
0
  {
1124
1125
0
    if(!pInFormat)
1126
0
    {
1127
      //attempt to auto-detect file format from extension
1128
0
      pInFormat = FormatFromExt(infilepath.c_str(), inFormatGzip);
1129
0
    }
1130
0
    ifstream *ifs = new ifstream(infilepath.c_str(),ios_base::in|ios_base::binary);  //always open in binary mode
1131
0
    if(!ifs || !ifs->good())
1132
0
    {
1133
0
      delete ifs;
1134
0
      obErrorLog.ThrowError(__FUNCTION__,"Cannot read from " + infilepath, obError);
1135
0
      return false;
1136
0
    }
1137
0
    SetInStream(ifs, true);
1138
0
    InFilename = infilepath;
1139
1140
0
    if(outfilepath.empty())//Don't open an outfile with an empty name.
1141
0
      return true;
1142
1143
0
    if(!pOutFormat)
1144
0
    {
1145
      //attempt to autodetect format
1146
0
      pOutFormat = FormatFromExt(outfilepath.c_str(), outFormatGzip);
1147
0
    }
1148
0
    ofstream *ofs = new ofstream(outfilepath.c_str(),ios_base::out|ios_base::binary);//always open in binary mode
1149
0
    if(!ofs || !ofs->good())
1150
0
    {
1151
0
      delete ofs;
1152
0
      obErrorLog.ThrowError(__FUNCTION__,"Cannot write to " + outfilepath, obError);
1153
0
      return false;
1154
0
    }
1155
0
    SetOutStream(ofs, true);
1156
0
    OutFilename = outfilepath;
1157
1158
0
    return true;
1159
0
  }
1160
1161
  ////////////////////////////////////////////
1162
  const char* OBConversion::Description()
1163
0
  {
1164
0
    return
1165
0
      "Conversion options\n"
1166
0
      "-f <#> Start import at molecule # specified\n"
1167
0
      "-l <#> End import at molecule # specified\n"
1168
0
      "-e Continue with next object after error, if possible\n"
1169
      #ifdef HAVE_LIBZ
1170
      "-z Compress the output with gzip\n"
1171
      "-zin Decompress the input with gzip\n"
1172
      #endif
1173
0
      "-k Attempt to translate keywords\n";
1174
      // -t All input files describe a single molecule
1175
0
  }
1176
1177
  ////////////////////////////////////////////
1178
  bool OBConversion::IsLast()
1179
0
  {
1180
0
    return m_IsLast;
1181
0
  }
1182
  ////////////////////////////////////////////
1183
  bool OBConversion::IsFirstInput()
1184
0
  {
1185
0
    return m_IsFirstInput;
1186
0
  }
1187
  void OBConversion::SetFirstInput(bool b)
1188
0
  {
1189
0
    m_IsFirstInput = b;
1190
/*    //Also set or clear a general option
1191
    if(b)
1192
      AddOption("firstinput",GENOPTIONS);
1193
    else
1194
      RemoveOption("firstinput",GENOPTIONS);
1195
  */
1196
0
  }
1197
1198
  /////////////////////////////////////////////////
1199
  string OBConversion::BatchFileName(string& BaseName, string& InFile)
1200
0
  {
1201
    //Replaces * in BaseName by InFile without extension and path
1202
0
    string ofname(BaseName);
1203
0
    string::size_type pos = ofname.find('*');
1204
0
    if(pos != string::npos)
1205
0
      {
1206
        //Replace * by input filename
1207
0
        string::size_type posdot= InFile.rfind('.');
1208
0
        if(posdot == string::npos)
1209
0
          posdot = InFile.size();
1210
0
        else {
1211
#ifdef HAVE_LIBZ
1212
          if (InFile.substr(posdot) == ".gz")
1213
            {
1214
              InFile.erase(posdot);
1215
              posdot = InFile.rfind('.');
1216
              if (posdot == string::npos)
1217
                posdot = InFile.size();
1218
            }
1219
#endif
1220
0
        }
1221
1222
0
        string::size_type posname= InFile.find_last_of("\\/");
1223
0
        ofname.replace(pos,1, InFile, posname+1, posdot-posname-1);
1224
0
      }
1225
0
    return ofname;
1226
0
  }
1227
1228
  ////////////////////////////////////////////////
1229
  string OBConversion::IncrementedFileName(string& BaseName, const int Count)
1230
0
  {
1231
    //Replaces * in BaseName by Count
1232
0
    string ofname(BaseName);
1233
0
    string::size_type pos = ofname.find('*');
1234
0
    if(pos!=string::npos)
1235
0
      {
1236
0
        char num[33];
1237
0
        snprintf(num, 33, "%d", Count);
1238
0
        ofname.replace(pos,1, num);
1239
0
      }
1240
0
    return ofname;
1241
0
  }
1242
  ////////////////////////////////////////////////////
1243
  bool OBConversion::CheckForUnintendedBatch(const string& infile, const string& outfile)
1244
0
  {
1245
    //If infile == outfile issue error message and return false
1246
    //If name without the extensions are the same issue warning and return true;
1247
    //Otherwise return true
1248
0
    string inname1, inname2;
1249
0
    string::size_type pos;
1250
0
    pos = infile.rfind('.');
1251
0
    if(pos != string::npos)
1252
0
      inname1 = infile.substr(0,pos);
1253
0
    pos = outfile.rfind('.');
1254
0
    if(pos != string::npos)
1255
0
      inname2 = infile.substr(0,pos);
1256
0
    if(inname1==inname2)
1257
0
      obErrorLog.ThrowError(__FUNCTION__,
1258
0
"This was a batch operation. For splitting, use non-empty base name for the output files", obWarning);
1259
1260
0
    if(infile==outfile)
1261
0
      return false;
1262
0
    return true;
1263
0
  }
1264
  /**
1265
     Makes input and output streams, and carries out normal,
1266
     batch, aggregation, and splitting conversion.
1267
1268
     Normal
1269
     Done if FileList contains a single file name and OutputFileName
1270
     does not contain a *.
1271
1272
     Aggregation
1273
     Done if FileList has more than one file name and OutputFileName does
1274
     not contain * . All the chemical objects are converted and sent
1275
     to the single output file.
1276
1277
     Splitting
1278
     Done if FileList contains a single file name and OutputFileName
1279
     contains a * . Each chemical object in the input file is converted
1280
     and sent to a separate file whose name is OutputFileName with the
1281
     * replaced by 1, 2, 3, etc.  OutputFileName must have at least one
1282
     character other than the * before the extension.
1283
     For example, if OutputFileName is NEW*.smi then the output files are
1284
     NEW1.smi, NEW2.smi, etc.
1285
1286
     Batch Conversion
1287
     Done if FileList has more than one file name and contains a * .
1288
     Each input file is converted to an output file whose name is
1289
     OutputFileName with the * replaced by the inputfile name without its
1290
     path and extension.
1291
     So if the input files were inpath/First.cml, inpath/Second.cml
1292
     and OutputFileName was NEW*.mol, the output files would be
1293
     NEWFirst.mol, NEWSecond.mol.
1294
1295
     If FileList is empty, the input stream that has already been set
1296
     (usually in the constructor) is used. If OutputFileName is empty,
1297
     the output stream already set is used.
1298
1299
     On exit, OutputFileList contains the names of the output files.
1300
1301
     Returns the number of Chemical objects converted.
1302
  */
1303
  int OBConversion::FullConvert(std::vector<std::string>& FileList, std::string& OutputFileName,
1304
                                std::vector<std::string>& OutputFileList)
1305
0
  {
1306
0
    OBConversion::OutFilename = OutputFileName; //ready for 2.4.0
1307
1308
0
    istream* pIs = nullptr;
1309
0
    ostream* pOs = nullptr;
1310
0
    ifstream is;
1311
0
    ofstream os;
1312
0
    stringstream ssOut, ssIn;
1313
0
    bool HasMultipleOutputFiles=false;
1314
0
    int Count=0;
1315
0
    SetFirstInput();
1316
0
    bool CommonInFormat = pInFormat ? true:false; //whether set in calling routine
1317
0
    ios_base::openmode omode = ios_base::out|ios_base::binary;
1318
0
    obErrorLog.ClearLog();
1319
0
#ifndef DONT_CATCH_EXCEPTIONS
1320
0
    try
1321
0
#endif
1322
0
      {
1323
0
        ofstream ofs;
1324
1325
        //OUTPUT
1326
0
        if(OutputFileName.empty())
1327
0
          pOs = nullptr; //use existing stream
1328
0
        else
1329
0
          {
1330
0
            if(OutputFileName.find_first_of('*')!=string::npos) HasMultipleOutputFiles = true;
1331
0
            if(!HasMultipleOutputFiles)
1332
0
              {
1333
                //If the output file is the same as any of the input
1334
                //files, send the output to a temporary stringstream
1335
0
                vector<string>::iterator itr;
1336
0
                for(itr=FileList.begin();itr!=FileList.end();++itr)
1337
0
                  {
1338
0
                    if(*itr==OutputFileName)
1339
0
                      {
1340
1341
0
                        pOs = &ssOut;
1342
0
                        break;
1343
0
                      }
1344
0
                  }
1345
0
                if(itr==FileList.end())
1346
0
                  {
1347
0
                    os.open(OutputFileName.c_str(),omode);
1348
0
                    if(!os)
1349
0
                      {
1350
0
                        obErrorLog.ThrowError(__FUNCTION__,"Cannot write to " + OutputFileName, obError);
1351
0
                        return 0;
1352
0
                      }
1353
0
                    pOs=&os;
1354
0
                  }
1355
0
                OutputFileList.push_back(OutputFileName);
1356
0
              }
1357
0
          }
1358
1359
0
        if(IsOption("t",GENOPTIONS))
1360
0
          {
1361
            //Concatenate input file option (multiple files, single molecule)
1362
0
            if(HasMultipleOutputFiles)
1363
0
              {
1364
0
                obErrorLog.ThrowError(__FUNCTION__,
1365
0
                                      "Cannot have multiple output files and also concatenate input files (-t option)",obError);
1366
0
                return 0;
1367
0
              }
1368
1369
0
            stringstream allinput;
1370
0
            vector<string>::iterator itr;
1371
0
            for(itr=FileList.begin();itr!=FileList.end();++itr)
1372
0
              {
1373
0
                ifstream ifs((*itr).c_str());
1374
0
                if(!ifs)
1375
0
                  {
1376
0
                    obErrorLog.ThrowError(__FUNCTION__,"Cannot open " + *itr, obError);
1377
0
                    continue;
1378
0
                  }
1379
0
                allinput << ifs.rdbuf(); //Copy all file contents
1380
0
                ifs.close();
1381
0
              }
1382
0
            Count = Convert(&allinput,pOs);
1383
0
            return Count;
1384
0
          }
1385
1386
        //INPUT
1387
0
        if(FileList.empty())
1388
0
          {
1389
0
            pIs = nullptr;
1390
0
            if(HasMultipleOutputFiles)
1391
0
              {
1392
0
                obErrorLog.ThrowError(__FUNCTION__,"Cannot use multiple output files without an input file", obError);
1393
0
                return 0;
1394
0
              }
1395
0
          }
1396
0
        else
1397
0
          {
1398
0
            if(FileList.size()>1 || OutputFileName.substr(0,2)=="*.")
1399
0
              {
1400
                //multiple input files
1401
0
                vector<string>::iterator itr, tempitr;
1402
0
                tempitr = FileList.end();
1403
0
                --tempitr;
1404
0
                for(itr=FileList.begin();itr!=FileList.end();++itr)
1405
0
                  {
1406
0
                    InFilename = *itr;
1407
0
                    ifstream ifs;
1408
0
                    if(!OpenAndSetFormat(CommonInFormat, &ifs, &ssIn))
1409
0
                      continue;
1410
0
                    if(ifs)
1411
0
                      pIs = &ifs;
1412
0
                    else
1413
0
                      pIs = &ssIn;
1414
1415
                    //pIs = ifs ? &ifs : &ssIn;
1416
1417
1418
0
                    if(HasMultipleOutputFiles)
1419
0
                      {
1420
                        //Batch conversion
1421
0
                        string batchfile = BatchFileName(OutputFileName,*itr);
1422
1423
                        //With inputs like babel test.xxx -oyyy -m
1424
                        //the user may have wanted to do a splitting operation
1425
                        //Issue a message and abort if xxx==yyy which would overwrite input file
1426
0
                        if(FileList.size()==1 && !CheckForUnintendedBatch(batchfile, InFilename))
1427
0
                          return Count;
1428
1429
0
                        if(ofs.is_open()) ofs.close();
1430
0
                        ofs.open(batchfile.c_str(), omode);
1431
0
                        if(!ofs)
1432
0
                          {
1433
0
                            obErrorLog.ThrowError(__FUNCTION__,"Cannot open " + batchfile, obError);
1434
0
                            return Count;
1435
0
                          }
1436
0
                        OutputFileList.push_back(batchfile);
1437
0
                        SetOutputIndex(0); //reset for new file
1438
0
                        Count += Convert(pIs,&ofs);
1439
0
                      }
1440
0
                    else
1441
0
                      {
1442
                        //Aggregation
1443
0
                        if(itr!=tempitr) SetMoreFilesToCome();
1444
0
                        Count = Convert(pIs,pOs);
1445
0
                      }
1446
0
                  }
1447
1448
0
                if(!os.is_open() && !OutputFileName.empty() && !HasMultipleOutputFiles)
1449
0
                  {
1450
                    //Output was written to temporary string stream. Output it to the file
1451
0
                    os.open(OutputFileName.c_str(),omode);
1452
0
                    if(!os)
1453
0
                      {
1454
0
                        obErrorLog.ThrowError(__FUNCTION__,"Cannot write to " + OutputFileName, obError);
1455
0
                        return Count;
1456
0
                      }
1457
0
                    os << ssOut.rdbuf();
1458
0
                  }
1459
0
                return Count;
1460
0
              }
1461
0
            else
1462
0
              {
1463
                //Single input file
1464
0
                InFilename = FileList[0];
1465
0
                if(!OpenAndSetFormat(CommonInFormat, &is, &ssIn))
1466
0
                  return 0;
1467
0
                if(is)
1468
0
                  pIs =&is;
1469
0
                else
1470
0
                  pIs = &ssIn;
1471
1472
0
                if(HasMultipleOutputFiles)
1473
0
                  {
1474
                    //Splitting
1475
                    //Output is put in a temporary stream and written to a file
1476
                    //with an augmenting name only when it contains a valid object.
1477
0
                    int Indx=1;
1478
#ifdef HAVE_LIBZ
1479
                    if(pInFormat && zlib_stream::isGZip(*pIs))
1480
                    {
1481
                      //for backwards compat, attempt to autodetect gzip
1482
                      inFormatGzip = true;
1483
                    }
1484
#endif
1485
0
                    SetInStream(pIs, false);
1486
1487
1488
0
                    for(;;)
1489
0
                      {
1490
0
                        stringstream ss;
1491
0
                        SetOutStream(&ss);
1492
0
                        SetOutputIndex(0); //reset for new file
1493
0
                        SetOneObjectOnly();
1494
1495
0
                        int ThisFileCount = Convert();
1496
0
                        if(ThisFileCount==0) break;
1497
0
                        Count+=ThisFileCount;
1498
1499
0
                        if(ofs.is_open()) ofs.close();
1500
0
                        string incrfile = IncrementedFileName(OutputFileName,Indx++);
1501
0
                        ofs.open(incrfile.c_str(), omode);
1502
0
                        if(!ofs)
1503
0
                          {
1504
0
                            obErrorLog.ThrowError(__FUNCTION__,"Cannot write to " + incrfile, obError);
1505
0
                            return Count;
1506
0
                          }
1507
1508
0
                        OutputFileList.push_back(incrfile);
1509
0
                        SetOutStream(&ofs, false); //pickup possible gzip
1510
0
                        *pOutput << ss.rdbuf();
1511
0
                        SetOutStream(nullptr);
1512
0
                        ofs.close();
1513
0
                        ss.clear();
1514
0
                      }
1515
0
                    return Count;
1516
0
                  }
1517
0
              }
1518
0
          }
1519
1520
        //Single input and output files
1521
0
        Count = Convert(pIs,pOs);
1522
1523
0
        if(os && !os.is_open() && !OutputFileName.empty())
1524
0
          {
1525
            //Output was written to temporary string stream. Output it to the file
1526
0
            os.open(OutputFileName.c_str(),omode);
1527
0
            if(!os)
1528
0
              {
1529
0
                obErrorLog.ThrowError(__FUNCTION__,"Cannot write to " + OutputFileName, obError);
1530
0
                return Count;
1531
0
              }
1532
0
            SetOutStream(&os, false);
1533
0
            *pOutput << ssOut.rdbuf();
1534
0
            SetOutStream(nullptr);
1535
0
          }
1536
0
        return Count;
1537
0
      }
1538
0
#ifndef DONT_CATCH_EXCEPTIONS
1539
0
    catch(...)
1540
0
      {
1541
0
        obErrorLog.ThrowError(__FUNCTION__, "Conversion failed with an exception.",obError);
1542
0
        return Count;
1543
0
      }
1544
0
#endif
1545
0
    return Count;
1546
0
  }
1547
1548
  bool OBConversion::OpenAndSetFormat(bool SetFormat, ifstream* is, stringstream* ss)
1549
0
  {
1550
    //Opens file using InFilename and sets pInFormat if requested
1551
0
    if(ss && InFilename[0]=='-')
1552
0
      {
1553
        //InFilename is actually  -:SMILES
1554
0
        is->setstate(ios::failbit); // do not use the input filestream...
1555
0
        InFilename.erase(0, 2);
1556
0
        if(SetFormat || SetInFormat("smi"))
1557
0
          {
1558
0
            ss->clear();
1559
0
            ss->str(InFilename);
1560
0
            return true;
1561
0
          }
1562
0
      }
1563
0
    else if(!SetFormat)
1564
0
      {
1565
0
        pInFormat = FormatFromExt(InFilename.c_str(), inFormatGzip);
1566
0
        if (pInFormat == nullptr)
1567
0
          {
1568
0
            string::size_type pos = InFilename.rfind('.');
1569
0
            string ext;
1570
0
            if(pos!=string::npos)
1571
0
              ext = InFilename.substr(pos);
1572
0
            obErrorLog.ThrowError(__FUNCTION__, "Cannot read input format \""
1573
0
                                  + ext + '\"' + " for file \"" + InFilename + "\"",obError);
1574
0
            return false;
1575
0
          }
1576
0
      }
1577
1578
0
    ios_base::openmode imode = ios_base::in|ios_base::binary;
1579
0
    is->open(InFilename.c_str(), imode);
1580
0
    if(!is->good())
1581
0
      {
1582
0
        obErrorLog.ThrowError(__FUNCTION__, "Cannot open " + InFilename, obError);
1583
0
        return false;
1584
0
      }
1585
1586
0
    return true;
1587
0
  }
1588
1589
  ///////////////////////////////////////////////
1590
#ifndef DOXYGEN_SHOULD_SKIP_THIS
1591
/**<pre>
1592
Built-in options for conversion of molecules
1593
Additional options :
1594
-d Delete hydrogens (make implicit)
1595
-h Add hydrogens (make explicit)
1596
-p <pH> Add hydrogens appropriate for this pH
1597
-b Convert dative bonds e.g.[N+]([O-])=O to N(=O)=O
1598
-r Remove all but the largest contiguous fragment
1599
-c Center Coordinates
1600
-C Combine mols in first file with others by name
1601
--filter <filterstring> Filter: convert only when tests are true:\n
1602
--add <list> Add properties from descriptors\n
1603
--delete <list> Delete properties in list\n
1604
--append <list> Append properties or descriptors in list to title:\n
1605
-s\smarts\ Convert only molecules matching SMARTS:\n
1606
-v\smarts\ Convert only molecules NOT matching SMARTS: (not displayed in GUI)\n
1607
--join Join all input molecules into a single output molecule
1608
--separate Output disconnected fragments separately
1609
--property <attrib> <value> add or replace a property (SDF)
1610
--title <title> Add or replace molecule title
1611
--addtotitle <text> Append to title
1612
--writeconformers Output multiple conformers separately
1613
--addindex Append output index to title
1614
</pre>
1615
**/
1616
#endif
1617
  void OBConversion::AddOption(const char* opt, Option_type opttyp, const char* txt)
1618
12
  {
1619
    //Also updates an option
1620
12
    if (txt == nullptr)
1621
12
      OptionsArray[opttyp][opt]=string();
1622
0
    else
1623
0
      OptionsArray[opttyp][opt]=txt;
1624
12
  }
1625
1626
  const char* OBConversion::IsOption(const char* opt, Option_type opttyp)
1627
107k
  {
1628
    //Returns NULL if option not found or a pointer to the text if it is
1629
107k
    map<string,string>::iterator pos;
1630
107k
    pos = OptionsArray[opttyp].find(opt);
1631
107k
    if(pos==OptionsArray[opttyp].end())
1632
107k
      return nullptr;
1633
0
    return pos->second.c_str();
1634
107k
  }
1635
1636
  bool OBConversion::RemoveOption(const char* opt, Option_type opttyp)
1637
0
  {
1638
0
    return OptionsArray[opttyp].erase(opt)!=0;//true if was there
1639
0
  }
1640
1641
  void OBConversion::SetOptions(const char* options, Option_type opttyp)
1642
0
  {
1643
0
    if(!*options) // "" clears all
1644
0
    {
1645
0
      OptionsArray[opttyp].clear();
1646
0
      return;
1647
0
    }
1648
0
    while(*options)
1649
0
      {
1650
0
        string ch(1, *options++);
1651
0
        if(*options=='\"')
1652
0
          {
1653
0
            string txt = options+1;
1654
0
            string::size_type pos = txt.find('\"');
1655
0
            if(pos==string::npos)
1656
0
              return; //options is illformed
1657
0
            txt.erase(pos);
1658
0
            OptionsArray[opttyp][ch]= txt;
1659
0
            options += pos+2;
1660
0
          }
1661
0
        else
1662
0
          OptionsArray[opttyp][ch] = string();
1663
0
      }
1664
0
  }
1665
1666
  OBConversion::OPAMapType& OBConversion::OptionParamArray(Option_type typ)
1667
105k
  {
1668
105k
    static OPAMapType opa[3];
1669
105k
    return opa[typ];
1670
105k
  }
1671
1672
  void OBConversion::RegisterOptionParam(string name, OBFormat* pFormat,
1673
                                         int numberParams, Option_type typ)
1674
35.2k
  {
1675
    //Gives error message if the number of parameters conflicts with an existing registration
1676
35.2k
    map<string,int>::iterator pos;
1677
35.2k
    pos = OptionParamArray(typ).find(name);
1678
35.2k
    if(pos!=OptionParamArray(typ).end())
1679
35.1k
      {
1680
35.1k
        if(pos->second!=numberParams)
1681
0
          {
1682
0
            string description("API");
1683
0
            if(pFormat)
1684
0
              description=pFormat->Description();
1685
0
            obErrorLog.ThrowError(__FUNCTION__,
1686
0
                                  "The number of parameters needed by option \"" + name + "\" in "
1687
0
                                  + description.substr(0,description.find('\n'))
1688
0
                                  + " differs from an earlier registration.", obError);
1689
0
            return;
1690
0
          }
1691
35.1k
      }
1692
35.2k
    OptionParamArray(typ)[name] = numberParams;
1693
35.2k
  }
1694
1695
  int OBConversion::GetOptionParams(string name, Option_type typ)
1696
0
  {
1697
    //returns the number of parameters registered for the option, or 0 if not found
1698
0
    map<string,int>::iterator pos;
1699
0
    pos = OptionParamArray(typ).find(name);
1700
0
    if(pos==OptionParamArray(typ).end())
1701
0
      return 0;
1702
0
    return pos->second;
1703
0
  }
1704
1705
  /**
1706
   * Returns the list of supported input format
1707
   */
1708
  std::vector<std::string> OBConversion::GetSupportedInputFormat()
1709
0
  {
1710
0
    vector<string> vlist;
1711
0
    OBPlugin::ListAsVector("formats", "in", vlist);
1712
0
    return vlist;
1713
0
  }
1714
  /**
1715
   * Returns the list of supported output format
1716
   */
1717
  std::vector<std::string> OBConversion::GetSupportedOutputFormat()
1718
0
  {
1719
0
    vector<string> vlist;
1720
0
    OBPlugin::ListAsVector("formats", "out", vlist);
1721
0
    return vlist;
1722
0
  }
1723
1724
  void OBConversion::ReportNumberConverted(int count, OBFormat* pFormat)
1725
0
  {
1726
    //Send info message to clog. This constructed from the TargetClassDescription
1727
    //of the specified class (or the output format if not specified).
1728
    //Get the last word on the first line of the description which should
1729
    //be "molecules", "reactions", etc and remove the s if only one object converted
1730
0
    if(!pFormat)
1731
0
      pFormat = pOutFormat;
1732
0
    string objectname(pFormat->TargetClassDescription());
1733
0
    string::size_type pos = objectname.find('\n');
1734
0
    if(pos==std::string::npos)
1735
0
      pos=objectname.size();
1736
0
    if(count==1) --pos;
1737
0
    objectname.erase(pos);
1738
0
    pos = objectname.rfind(' ');
1739
0
    if(pos==std::string::npos)
1740
0
      pos=0;
1741
0
    std::clog << count << objectname.substr(pos) << " converted" << endl;
1742
0
  }
1743
1744
  void OBConversion::CopyOptions(OBConversion* pSourceConv, Option_type typ)
1745
0
  {
1746
0
    if(typ==ALL)
1747
0
    for(int i=0;i<3;++i)
1748
0
     OptionsArray[i]=pSourceConv->OptionsArray[i];
1749
0
    else
1750
0
     OptionsArray[typ]=pSourceConv->OptionsArray[typ];
1751
0
  }
1752
1753
  int OBConversion::NumInputObjects()
1754
0
  {
1755
0
    istream& ifs = *GetInStream();
1756
0
    ifs.clear(); //it may have been at eof
1757
    //Save position of the input stream
1758
0
    streampos pos = ifs.tellg();
1759
0
    if(!ifs)
1760
0
      return -1;
1761
1762
    //check that the input format supports SkipObjects()
1763
0
    if(GetInFormat()->SkipObjects(0, this)==0)
1764
0
    {
1765
0
      obErrorLog.ThrowError(__FUNCTION__,
1766
0
        "Input format does not have a SkipObjects function.", obError);
1767
0
      return -1;
1768
0
    }
1769
1770
    //counts objects only between the values of -f and -l options
1771
0
    int nfirst=1, nlast=numeric_limits<int>::max();
1772
0
    const char* p;
1773
0
    if( (p=IsOption("f", GENOPTIONS)) ) // extra parens to indicate truth value
1774
0
      nfirst=atoi(p);
1775
0
    if( (p=IsOption("l", GENOPTIONS)) ) // extra parens to indicate truth value
1776
0
      nlast=atoi(p);
1777
1778
0
    ifs.seekg(0); //rewind
1779
    //Compressed files currently show an error here.***TAKE CHANCE: RESET ifs****
1780
0
    ifs.clear();
1781
1782
0
    OBFormat* pFormat = GetInFormat();
1783
0
    int count=0;
1784
    //skip each object but stop after nlast objects
1785
0
    while(ifs && pFormat->SkipObjects(1, this)>0  && count<nlast)
1786
0
      ++count;
1787
1788
0
    ifs.clear(); //clear eof
1789
0
    ifs.seekg(pos); //restore old position
1790
1791
0
    count -= nfirst-1;
1792
0
    return count;
1793
0
  }
1794
1795
1796
1797
  //The following function and typedef are deprecated, and are present only
1798
  //for backward compatibility.
1799
  //Use OBConversion::GetSupportedInputFormat(), OBConversion::GetSupportedOutputFormat(),
1800
  //OBPlugin::List(), OBPlugin::OBPlugin::ListAsVector(),OBPlugin::OBPlugin::ListAsString(),
1801
  //or (in extremis) OBPlugin::PluginIterator instead.
1802
1803
  typedef OBPlugin::PluginIterator Formatpos;
1804
1805
  bool OBConversion::GetNextFormat(Formatpos& itr, const char*& str,OBFormat*& pFormat)
1806
0
  {
1807
1808
0
    pFormat = nullptr;
1809
0
    if (str == nullptr)
1810
0
      itr = OBPlugin::Begin("formats");
1811
0
    else
1812
0
      itr++;
1813
0
    if(itr == OBPlugin::End("formats"))
1814
0
      {
1815
0
        str = nullptr; pFormat = nullptr;
1816
0
        return false;
1817
0
      }
1818
0
    static string s;
1819
0
    s =itr->first;
1820
0
    pFormat = static_cast<OBFormat*>(itr->second);
1821
0
    if(pFormat)
1822
0
      {
1823
0
        string description(pFormat->Description());
1824
0
        s += " -- ";
1825
0
        s += description.substr(0,description.find('\n'));
1826
0
      }
1827
1828
0
    if(pFormat->Flags() & NOTWRITABLE) s+=" [Read-only]";
1829
0
    if(pFormat->Flags() & NOTREADABLE) s+=" [Write-only]";
1830
1831
0
    str = s.c_str();
1832
0
    return true;
1833
0
  }
1834
1835
  /**
1836
   * @example obconversion_readstring.cpp
1837
   * Reading a smiles string.
1838
   */
1839
1840
  /**
1841
   * @example obconversion_readstring.py
1842
   * Reading a smiles string in python.
1843
   */
1844
1845
1846
1847
}//namespace OpenBabel
1848
1849
//! \file obconversion.cpp
1850
//! \brief Implementation of OBFormat and OBConversion classes.