text: //
  //  Copyright (C) 2009-2022 Greg Landrum and other RDKit contributors
  //
  //   @@ All Rights Reserved @@
  //  This file is part of the RDKit.
  //  The contents are covered by the terms of the BSD license
  //  which is included in the file license.txt, found at the root
  //  of the RDKit source tree.
  //
  #include <RDGeneral/FileParseException.h>
  #include <RDGeneral/BadFileException.h>
  #include <RDGeneral/StreamOps.h>
  #include <RDGeneral/RDLog.h>
  #include <GraphMol/SanitException.h>
  #include <RDGeneral/BoostStartInclude.h>
  #include <boost/algorithm/string.hpp>
  #include <RDGeneral/BoostEndInclude.h>
  
  #include "MolSupplier.h"
  #include "FileParsers.h"
  #include "FileParserUtils.h"
  
  #include <fstream>
  #include <iostream>
  #include <sstream>
  #include <string>
  
  namespace RDKit {
  std::string strip(const std::string &orig) {
    // FIX: this can be more efficient
    // strip the end of line, white spaces and tabs
    std::string res =
        boost::trim_right_copy_if(orig, boost::is_any_of(" \t\r\n"));
    res = boost::trim_left_copy_if(res, boost::is_any_of(" \t\r\n"));
    return res;
  }
  
  ForwardSDMolSupplier::ForwardSDMolSupplier(std::istream *inStream,
                                             bool takeOwnership, bool sanitize,
                                             bool removeHs, bool strictParsing) {
    PRECONDITION(inStream, "bad stream");
    init();
    dp_inStream = inStream;
    df_owner = takeOwnership;
    df_sanitize = sanitize;
    df_removeHs = removeHs;
    df_strictParsing = strictParsing;
    POSTCONDITION(dp_inStream, "bad instream");
  }
  
  void ForwardSDMolSupplier::init() {
    dp_inStream = nullptr;
    df_owner = false;
    df_end = false;
    d_line = 0;
    df_processPropertyLists = true;
  }
  
  void ForwardSDMolSupplier::reset() {
    UNDER_CONSTRUCTION("reset() not supported for ForwardSDMolSuppliers();");
  }
  
  void ForwardSDMolSupplier::readMolProps(ROMol *mol) {
    PRECONDITION(dp_inStream, "no stream");
    PRECONDITION(mol, "no molecule");
    d_line++;
    bool hasProp = false;
    bool warningIssued = false;
    std::string dlabel = "";
    std::string inl;
    std::getline(*dp_inStream, inl);
    std::string_view tempStr = inl;
  
    // FIX: report files missing the $$$$ marker
    while (!dp_inStream->eof() && !dp_inStream->fail() &&
           (tempStr[0] != '$' || tempStr.substr(0, 4) != "$$$$")) {
      tempStr = FileParserUtils::strip(tempStr);
      if (!tempStr.empty()) {
        if (tempStr[0] == '>') {  // data header line: start of a data item
          // ignore all other crap and seek for for a data label enclosed
          // by '<' and '>'
          // FIX: "CTfile.pdf" (page 51) says that the data header line does not
          // have to contain a data label (instead can have something line field
          // id into a MACCS db). But we do not currently know what to do in this
          // situation - so ignore such data items for now
          hasProp = true;
          warningIssued = false;
          tempStr = tempStr.substr(1);    // remove the first ">" sign
          size_t sl = tempStr.find("<");  // begin datalabel
          size_t se = tempStr.find(">");  // end datalabel
          if ((sl == std::string::npos) || (se == std::string::npos) ||
              (se == (sl + 1))) {
            // we either do not have a data label or the label is empty
            // no data label ignore until next data item
            // i.e. until we hit a blank line
            d_line++;
            std::getline(*dp_inStream, inl);
            tempStr = inl;
            auto stmp = FileParserUtils::strip(tempStr);
            while (stmp.length() != 0) {
              d_line++;
              std::getline(*dp_inStream, inl);
              tempStr = inl;
              if (dp_inStream->eof()) {
                throw FileParseException("End of data field name not found");
              }
            }
          } else {
            dlabel = tempStr.substr(sl + 1, se - sl - 1);
            // we know the label - now read in the relevant properties
            // until we hit a blank line
            d_line++;
            std::getline(*dp_inStream, inl);
            tempStr = inl;
  
            std::string prop = "";
            auto stmp = FileParserUtils::strip(tempStr);
            int nplines = 0;  // number of lines for this property
            while (stmp.length() != 0 || tempStr[0] == ' ' ||
                   tempStr[0] == '\t') {
              nplines++;
              if (nplines > 1) {
                prop += "\n";
              }
              // take off \r if it's still in the property:
              if (tempStr.back() == '\r') {
                tempStr = tempStr.substr(0, tempStr.size() - 1);
              }
              prop += tempStr;
              d_line++;
              // erase inl in case the file does not end with a carriage
              // return (we will end up in an infinite loop if we don't do
              // this and we do not check for EOF in this while loop body)
              inl.erase();
              std::getline(*dp_inStream, inl);
              tempStr = inl;
              stmp = FileParserUtils::strip(tempStr);
            }
            mol->setProp(dlabel, prop);
            if (df_processPropertyLists) {
              // apply this as an atom property list if that's appropriate
              FileParserUtils::processMolPropertyList(*mol, dlabel);
            }
          }
        } else {
          if (df_strictParsing) {
            // at this point we should always be at a line starting with '>'
            // following a blank line. If this is not true and df_strictParsing
            // is true, then throw an exception, otherwise truncate the rest of
            // the data field following the blank line until the next '>' or EOF
            // and issue a warning
            // FIX: should we be deleting the molecule (which is probably fine)
            // because we couldn't read the data ???
            throw FileParseException("Problems encountered parsing data fields");
          } else {
            if (!warningIssued) {
              if (hasProp) {
                BOOST_LOG(rdWarningLog)
                    << "Property <" << dlabel << "> will be truncated after "
                    << "the first blank line" << std::endl;
              } else {
                BOOST_LOG(rdWarningLog)
                    << "Spurious data before the first property will be "
                       "ignored"
                    << std::endl;
              }
              warningIssued = true;
            }
          }
        }
      }
      d_line++;
      std::getline(*dp_inStream, inl);
      tempStr = inl;
    }
  }
  
  ROMol *ForwardSDMolSupplier::next() {
    PRECONDITION(dp_inStream, "no stream");
    ROMol *res = nullptr;
  
    if (dp_inStream->eof()) {
      // FIX: we should probably be throwing an exception here
      df_end = true;
      return res;
    }
  
    res = _next();
    return res;
  }
  
  ROMol *ForwardSDMolSupplier::_next() {
    PRECONDITION(dp_inStream, "no stream");
  
    std::string tempStr;
    ROMol *res = nullptr;
    if (dp_inStream->eof()) {
      df_end = true;
      return res;
    }
  
    df_eofHitOnRead = false;
    unsigned int line = d_line;
    try {
      res = MolDataStreamToMol(dp_inStream, line, df_sanitize, df_removeHs,
                               df_strictParsing);
      // there's a special case when trying to read an empty string that
      // we get an empty molecule after only reading a single line without any
      // additional error state.
      if (!res && dp_inStream->eof() && (line - d_line < 2)) {
        df_eofHitOnRead = true;
      }
      d_line = line;
      if (res) {
        this->readMolProps(res);
      } else if (!dp_inStream->eof()) {
        // FIX: report files missing the $$$$ marker
        std::getline(*dp_inStream, tempStr);
        ++d_line;
        while (!dp_inStream->eof() && !dp_inStream->fail() &&
               (tempStr[0] != '$' || tempStr.substr(0, 4) != "$$$$")) {
          std::getline(*dp_inStream, tempStr);
          ++d_line;
        }
      }
    } catch (FileParseException &fe) {
      if (d_line < static_cast<int>(line)) {
        d_line = line;
      }
      // we couldn't read a mol block or the data for the molecule. In this case
      // advance forward in the stream until we hit the next record and then
      // rethrow
      // the exception. This should allow us to read the next molecule.
      BOOST_LOG(rdErrorLog) << "ERROR: " << fe.what() << std::endl;
      BOOST_LOG(rdErrorLog)
          << "ERROR: moving to the beginning of the next molecule\n";
  
      // FIX: report files missing the $$$$ marker
      d_line++;
      std::getline(*dp_inStream, tempStr);
      while (!dp_inStream->eof() && !dp_inStream->fail() &&
             (tempStr[0] != '$' || tempStr.substr(0, 4) != "$$$$")) {
        d_line++;
        std::getline(*dp_inStream, tempStr);
      }
    } catch (MolSanitizeException &se) {
      if (d_line < static_cast<int>(line)) {
        d_line = line;
      }
      // We couldn't sanitize a molecule we got - write out an error message and
      // move to
      // the beginning of the next molecule
      BOOST_LOG(rdErrorLog)
          << "ERROR: Could not sanitize molecule ending on line " << d_line
          << std::endl;
      BOOST_LOG(rdErrorLog) << "ERROR: " << se.what() << "\n";
  
      d_line++;
      std::getline(*dp_inStream, tempStr);
      if (dp_inStream->eof()) {
        df_eofHitOnRead = true;
      }
      while (!dp_inStream->eof() && !dp_inStream->fail() &&
             (tempStr[0] != '$' || tempStr.substr(0, 4) != "$$$$")) {
        d_line++;
        std::getline(*dp_inStream, tempStr);
      }
    } catch (...) {
      if (dp_inStream->eof()) {
        df_eofHitOnRead = true;
      }
      if (d_line < static_cast<int>(line)) {
        d_line = line;
      }
  
      BOOST_LOG(rdErrorLog) << "Unexpected error hit on line " << d_line
                            << std::endl;
      BOOST_LOG(rdErrorLog)
          << "ERROR: moving to the beginning of the next molecule\n";
      d_line++;
      std::getline(*dp_inStream, tempStr);
      if (dp_inStream->eof()) {
        df_eofHitOnRead = true;
      }
      while (!dp_inStream->eof() && !dp_inStream->fail() &&
             (tempStr[0] != '$' || tempStr.substr(0, 4) != "$$$$")) {
        d_line++;
        std::getline(*dp_inStream, tempStr);
      }
    }
    if (dp_inStream->eof()) {
      // FIX: we should probably be throwing an exception here
      df_end = true;
    }
    return res;
  }
  
  void ForwardSDMolSupplier::checkForEnd() {
    PRECONDITION(dp_inStream, "no stream");
    // we will call it end of file if we have more than 4 contiguous empty lines
    // or we reach end of file in the meantime
    if (dp_inStream->eof()) {
      df_end = true;
      return;
    }
    // we are not at the end of file, check for blank lines
    unsigned int nempty = 0;
    std::string tempStr;
    for (unsigned int i = 0; i < 4; i++) {
      tempStr = getLine(dp_inStream);
      if (dp_inStream->eof()) {
        df_end = true;
        return;
      }
      if (tempStr.find_first_not_of(" \t\r\n") == std::string::npos) {
        ++nempty;
      }
    }
    if (nempty == 4) {
      df_end = true;
    }
  }
  
  bool ForwardSDMolSupplier::atEnd() {
    PRECONDITION(dp_inStream, "no stream");
    return df_end;
  }
  }  // namespace RDKit
  