void MolfileLoader::_readCtab3000()

in core/indigo-core/molecule/src/molfile_loader.cpp [2365:3142]


void MolfileLoader::_readCtab3000()
{
    QS_DEF(Array<char>, str);

    _scanner.readLine(str, true);
    if (strncmp(str.ptr(), "M  V30 BEGIN CTAB", 17) != 0)
        throw Error("error reading CTAB block header");

    str.clear_resize(14);
    _scanner.read(14, str.ptr());
    if (strncmp(str.ptr(), "M  V30 COUNTS ", 14) != 0)
        throw Error("error reading COUNTS line");

    int i, nsgroups, n3d, chiral_int;

    _scanner.readLine(str, true);
    if (sscanf(str.ptr(), "%d %d %d %d %d", &_atoms_num, &_bonds_num, &nsgroups, &n3d, &chiral_int) < 5)
        throw Error("error parsing COUNTS line");

    _chiral = (chiral_int != 0);

    if (ignore_no_chiral_flag)
        _chiral = true;

    _init();

    bool atom_block_exists = true;
    bool bond_block_exists = true;

    _scanner.readLine(str, true);
    if (strncmp(str.ptr(), "M  V30 BEGIN ATOM", 14) != 0)
    {
        if (_atoms_num > 0)
            throw Error("Error reading ATOM block header");
        atom_block_exists = false;
    }
    else
    {
        for (i = 0; i < _atoms_num; i++)
        {
            _readMultiString(str);
            BufferScanner strscan(str.ptr());

            int& atom_type = _atom_types.push();

            _hcount.push(0);

            atom_type = _ATOM_ELEMENT;

            int isotope = 0;
            int label = 0;
            std::unique_ptr<QueryMolecule::Atom> query_atom;

            strscan.readInt1(); // atom index -- ignored

            QS_DEF(Array<char>, buf);

            strscan.readWord(buf, " [");

            char stopchar = strscan.readChar();
            if (stopchar == '[')
            {
                if (_qmol == 0)
                    throw Error("atom list is allowed only for queries");

                if (buf[0] == 0)
                    atom_type = _ATOM_LIST;
                else if (strcmp(buf.ptr(), "NOT") == 0)
                    atom_type = _ATOM_NOTLIST;
                else
                    throw Error("bad word: %s", buf.ptr());

                bool was_a = false, was_q = false, was_x = false, was_m = false;

                while (1)
                {
                    strscan.readWord(buf, ",]");
                    stopchar = strscan.readChar();

                    if (was_a)
                        throw Error("'A' inside atom list, if present, must be single");
                    if (was_q)
                        throw Error("'Q' inside atom list, if present, must be single");
                    if (was_x)
                        throw Error("'X' inside atom list, if present, must be single");
                    if (was_m)
                        throw Error("'M' inside atom list, if present, must be single");

                    if (buf.size() == 2 && buf[0] == 'A')
                    {
                        was_a = true;
                        atom_type = _ATOM_A;
                    }
                    else if (buf.size() == 3 && buf[0] == 'A' && buf[1] == 'H')
                    {
                        was_a = true;
                        atom_type = _ATOM_AH;
                    }
                    else if (buf.size() == 2 && buf[0] == 'Q')
                    {
                        was_q = true;
                        atom_type = _ATOM_Q;
                    }
                    else if (buf.size() == 3 && buf[0] == 'Q' && buf[1] == 'H')
                    {
                        was_a = true;
                        atom_type = _ATOM_QH;
                    }
                    else if (buf.size() == 2 && buf[0] == 'X')
                    {
                        was_q = true;
                        atom_type = _ATOM_X;
                    }
                    else if (buf.size() == 3 && buf[0] == 'X' && buf[1] == 'H')
                    {
                        was_a = true;
                        atom_type = _ATOM_XH;
                    }
                    else if (buf.size() == 2 && buf[0] == 'M')
                    {
                        was_q = true;
                        atom_type = _ATOM_M;
                    }
                    else if (buf.size() == 3 && buf[0] == 'M' && buf[1] == 'H')
                    {
                        was_a = true;
                        atom_type = _ATOM_MH;
                    }
                    else
                    {
                        _appendQueryAtom(buf.ptr(), query_atom);
                    }

                    if (stopchar == ']')
                        break;
                }
            }
            else
            {
                label = Element::fromString2(buf.ptr());
                long long cur_pos = strscan.tell();
                QS_DEF(ReusableObjArray<Array<char>>, strs);
                strs.clear();
                strs.push().readString("CLASS", false);
                strs.push().readString("SEQID", false);
                auto fw_res = strscan.findWord(strs);
                strscan.seek(cur_pos, SEEK_SET);
                if (fw_res != -1)
                    atom_type = _ATOM_TEMPLATE;
                else if (buf.size() == 2 && buf[0] == 'D')
                {
                    label = ELEM_H;
                    isotope = 2;
                }
                else if (buf.size() == 2 && buf[0] == 'T')
                {
                    label = ELEM_H;
                    isotope = 3;
                }
                else if (buf.size() == 2 && buf[0] == 'Q')
                {
                    if (_qmol == 0)
                        throw Error("'Q' atom is allowed only for queries");

                    atom_type = _ATOM_Q;
                }
                else if (buf.size() == 3 && buf[0] == 'Q' && buf[1] == 'H')
                {
                    if (_qmol == 0)
                        throw Error("'QH' atom is allowed only for queries");

                    atom_type = _ATOM_QH;
                }
                else if (buf.size() == 2 && buf[0] == 'A')
                {
                    if (_qmol == 0)
                        throw Error("'A' atom is allowed only for queries");

                    atom_type = _ATOM_A;
                }
                else if (buf.size() == 3 && buf[0] == 'A' && buf[1] == 'H')
                {
                    if (_qmol == 0)
                        throw Error("'AH' atom is allowed only for queries");

                    atom_type = _ATOM_AH;
                }
                else if (buf.size() == 2 && buf[0] == 'X' && !treat_x_as_pseudoatom)
                {
                    if (_qmol == 0)
                        throw Error("'X' atom is allowed only for queries");

                    atom_type = _ATOM_X;
                }
                else if (buf.size() == 3 && buf[0] == 'X' && buf[1] == 'H' && !treat_x_as_pseudoatom)
                {
                    if (_qmol == 0)
                        throw Error("'XH' atom is allowed only for queries");

                    atom_type = _ATOM_XH;
                }
                else if (buf.size() == 2 && buf[0] == 'M')
                {
                    if (_qmol == 0)
                        throw Error("'M' atom is allowed only for queries");

                    atom_type = _ATOM_M;
                }
                else if (buf.size() == 3 && buf[0] == 'M' && buf[1] == 'H')
                {
                    if (_qmol == 0)
                        throw Error("'MH' atom is allowed only for queries");

                    atom_type = _ATOM_MH;
                }
                else if (buf.size() == 3 && buf[0] == 'R' && buf[1] == '#')
                {
                    atom_type = _ATOM_R;
                    label = ELEM_RSITE;
                }
                else if (label == -1)
                    atom_type = _ATOM_PSEUDO;
            }

            strscan.skipSpace();
            float x = strscan.readFloat();
            strscan.skipSpace();
            float y = strscan.readFloat();
            strscan.skipSpace();
            float z = strscan.readFloat();
            strscan.skipSpace();
            int aamap = strscan.readInt1();

            if (_mol != 0)
            {
                if (atom_type == _ATOM_TEMPLATE)
                {
                    _preparePseudoAtomLabel(buf);
                    _mol->addTemplateAtom(buf.ptr());
                }
                else
                {
                    _mol->addAtom(label);
                    if (atom_type == _ATOM_PSEUDO)
                    {
                        _preparePseudoAtomLabel(buf);
                        _mol->setPseudoAtom(i, buf.ptr());
                    }
                }
            }
            else
            {
                if (atom_type == _ATOM_LIST)
                    _qmol->addAtom(query_atom.release());
                else if (atom_type == _ATOM_NOTLIST)
                    _qmol->addAtom(QueryMolecule::Atom::nicht(query_atom.release()));
                else if (atom_type == _ATOM_ELEMENT)
                    _qmol->addAtom(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, label));
                else if (atom_type == _ATOM_PSEUDO)
                    _qmol->addAtom(new QueryMolecule::Atom(QueryMolecule::ATOM_PSEUDO, buf.ptr()));
                else if (atom_type == _ATOM_TEMPLATE)
                    _qmol->addTemplateAtom(buf.ptr());
                else if (atom_type == _ATOM_A)
                    _qmol->addAtom(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_H)));
                else if (atom_type == _ATOM_AH)
                {
                    std::unique_ptr<QueryMolecule::Atom> atom = std::make_unique<QueryMolecule::Atom>();
                    atom->type = QueryMolecule::OP_NONE;
                    _qmol->addAtom(atom.release());
                }
                else if (atom_type == _ATOM_X)
                {
                    std::unique_ptr<QueryMolecule::Atom> atom = std::make_unique<QueryMolecule::Atom>();

                    atom->type = QueryMolecule::OP_OR;
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_F));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Cl));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Br));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_I));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_At));
                    _qmol->addAtom(atom.release());
                }
                else if (atom_type == _ATOM_XH)
                {
                    std::unique_ptr<QueryMolecule::Atom> atom = std::make_unique<QueryMolecule::Atom>();

                    atom->type = QueryMolecule::OP_OR;
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_F));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Cl));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Br));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_I));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_At));
                    atom->children.add(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_H));
                    _qmol->addAtom(atom.release());
                }
                else if (atom_type == _ATOM_QH)
                    _qmol->addAtom(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_C)));
                else if (atom_type == _ATOM_Q)
                    _qmol->addAtom(QueryMolecule::Atom::und(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_H)),
                                                            QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_C))));
                else if (atom_type == _ATOM_MH)
                {
                    std::unique_ptr<QueryMolecule::Atom> atom = std::make_unique<QueryMolecule::Atom>();

                    atom->type = QueryMolecule::OP_AND;
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_C)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_N)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_O)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_F)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_P)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_S)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Cl)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Se)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Br)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_I)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_At)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_He)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Ne)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Ar)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Kr)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Xe)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Rn)));

                    _qmol->addAtom(atom.release());
                }
                else if (atom_type == _ATOM_M)
                {
                    std::unique_ptr<QueryMolecule::Atom> atom = std::make_unique<QueryMolecule::Atom>();

                    atom->type = QueryMolecule::OP_AND;
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_C)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_N)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_O)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_F)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_P)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_S)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Cl)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Se)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Br)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_I)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_At)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_He)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Ne)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Ar)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Kr)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Xe)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_Rn)));
                    atom->children.add(QueryMolecule::Atom::nicht(new QueryMolecule::Atom(QueryMolecule::ATOM_NUMBER, ELEM_H)));

                    _qmol->addAtom(atom.release());
                }
                else // _ATOM_R
                    _qmol->addAtom(new QueryMolecule::Atom(QueryMolecule::ATOM_RSITE, 0));
            }

            // int hcount = 0;
            int irflag = 0;
            int ecflag = 0;
            int radical = 0;

            // read remaining atom properties
            while (true)
            {
                strscan.skipSpace();
                if (strscan.isEOF())
                    break;

                QS_DEF(Array<char>, prop_arr);
                strscan.readWord(prop_arr, "=");

                strscan.skip(1);
                const char* prop = prop_arr.ptr();

                if (strcmp(prop, "CHG") == 0)
                {
                    int charge = strscan.readInt1();

                    if (_mol != 0)
                        _mol->setAtomCharge_Silent(i, charge);
                    else
                    {
                        _qmol->resetAtom(i, QueryMolecule::Atom::und(_qmol->releaseAtom(i), new QueryMolecule::Atom(QueryMolecule::ATOM_CHARGE, charge)));
                    }
                }
                else if (strcmp(prop, "RAD") == 0)
                {
                    radical = strscan.readInt1();

                    if (_qmol != 0)
                    {
                        _qmol->resetAtom(i, QueryMolecule::Atom::und(_qmol->releaseAtom(i), new QueryMolecule::Atom(QueryMolecule::ATOM_RADICAL, radical)));
                    }
                }
                else if (strcmp(prop, "CFG") == 0)
                {
                    strscan.readInt1();
                    // int cfg = strscan.readInt1();

                    // if (cfg == 3)
                    //   _stereocenter_types[idx] = MoleculeStereocenters::ATOM4;
                }
                else if (strcmp(prop, "MASS") == 0)
                {
                    isotope = strscan.readInt1();
                }
                else if (strcmp(prop, "VAL") == 0)
                {
                    int valence = strscan.readInt1();

                    if (valence == -1)
                        valence = 0;

                    _bmol->setExplicitValence(i, valence);
                }
                else if (strcmp(prop, "HCOUNT") == 0)
                {
                    int hcount = strscan.readInt1();

                    if (_qmol == 0)
                    {
                        if (!ignore_noncritical_query_features)
                            throw Error("H count is allowed only for queries");
                    }

                    if (hcount == -1)
                        _hcount[i] = 1;
                    else if (hcount > 0)
                        _hcount[i] = hcount + 1; // to comply to the code in _postLoad()
                    else
                        throw Error("invalid HCOUNT value: %d", hcount);
                }
                else if (strcmp(prop, "STBOX") == 0)
                    _stereo_care_atoms[i] = strscan.readInt1();
                else if (strcmp(prop, "INVRET") == 0)
                    irflag = strscan.readInt1();
                else if (strcmp(prop, "EXACHG") == 0)
                    ecflag = strscan.readInt1();
                else if (strcmp(prop, "SUBST") == 0)
                {
                    if (_qmol == 0)
                        throw Error("substitution count is allowed only for queries");

                    int subst = strscan.readInt1();

                    if (subst != 0)
                    {
                        if (subst == -1)
                            _qmol->resetAtom(i, QueryMolecule::Atom::und(_qmol->releaseAtom(i), new QueryMolecule::Atom(QueryMolecule::ATOM_SUBSTITUENTS, 0)));
                        else if (subst == -2)
                        {
                            _qmol->resetAtom(
                                i, QueryMolecule::Atom::und(_qmol->releaseAtom(i),
                                                            new QueryMolecule::Atom(QueryMolecule::ATOM_SUBSTITUENTS_AS_DRAWN, _qmol->getVertex(i).degree())));
                        }
                        else if (subst > 0)
                            _qmol->resetAtom(i, QueryMolecule::Atom::und(_qmol->releaseAtom(i), new QueryMolecule::Atom(QueryMolecule::ATOM_SUBSTITUENTS, subst,
                                                                                                                        (subst < 6 ? subst : 100))));
                        else
                            throw Error("invalid SUBST value: %d", subst);
                    }
                }
                else if (strcmp(prop, "UNSAT") == 0)
                {
                    if (_qmol == 0)
                    {
                        if (!ignore_noncritical_query_features)
                            throw Error("unsaturation flag is allowed only for queries");
                    }
                    else
                    {
                        bool unsat = (strscan.readInt1() > 0);

                        if (unsat)
                            _qmol->resetAtom(i, QueryMolecule::Atom::und(_qmol->releaseAtom(i), new QueryMolecule::Atom(QueryMolecule::ATOM_UNSATURATION, 0)));
                    }
                }
                else if (strcmp(prop, "RBCNT") == 0)
                {
                    if (_qmol == 0)
                    {
                        if (!ignore_noncritical_query_features)
                            throw Error("ring bond count is allowed only for queries");
                    }
                    else
                    {
                        int rb = strscan.readInt1();
                        if (rb != 0)
                        {
                            if (rb == -1)
                                rb = 0;
                            else if (rb == -2)
                            {
                                int rbonds = 0;
                                const Vertex& vertex = _qmol->getVertex(i);

                                for (int k = vertex.neiBegin(); k != vertex.neiEnd(); k = vertex.neiNext(k))
                                    if (_qmol->getEdgeTopology(vertex.neiEdge(k)) == TOPOLOGY_RING)
                                        rbonds++;

                                _qmol->resetAtom(i, QueryMolecule::Atom::und(_qmol->releaseAtom(i),
                                                                             new QueryMolecule::Atom(QueryMolecule::ATOM_RING_BONDS_AS_DRAWN, rbonds)));
                            }
                            else if (rb > 1)
                                _qmol->resetAtom(i, QueryMolecule::Atom::und(_qmol->releaseAtom(i),
                                                                             new QueryMolecule::Atom(QueryMolecule::ATOM_RING_BONDS, rb, (rb < 4 ? rb : 100))));
                            else
                                throw Error("invalid RBCNT value: %d", rb);
                        }
                    }
                }
                else if (strcmp(prop, "RGROUPS") == 0)
                {
                    int n_rg;

                    strscan.skip(1); // skip '('
                    n_rg = strscan.readInt1();
                    while (n_rg-- > 0)
                        _bmol->allowRGroupOnRSite(i, strscan.readInt1());
                }
                else if (strcmp(prop, "ATTCHPT") == 0)
                {
                    int att_type = strscan.readInt1();

                    if (att_type == -1)
                        att_type = 3;

                    for (int att_idx = 0; (1 << att_idx) <= att_type; att_idx++)
                        if (att_type & (1 << att_idx))
                            _bmol->addAttachmentPoint(att_idx + 1, i);
                }
                else if (strcmp(prop, "ATTCHORD") == 0)
                {
                    int n_items, nei_idx, att_type;
                    QS_DEF(Array<char>, att_id);

                    strscan.skip(1); // skip '('
                    n_items = strscan.readInt1() / 2;
                    while (n_items-- > 0)
                    {
                        nei_idx = strscan.readInt1();
                        if (atom_type == _ATOM_R)
                        {
                            att_type = strscan.readInt1();
                            _bmol->setRSiteAttachmentOrder(i, nei_idx - 1, att_type - 1);
                        }
                        else
                        {
                            strscan.readWord(att_id, " )");
                            att_id.push(0);
                            _bmol->setTemplateAtomAttachmentOrder(i, nei_idx - 1, att_id.ptr());
                            strscan.skip(1); // skip stop character
                        }
                    }
                }
                else if (strcmp(prop, "CLASS") == 0)
                {
                    QS_DEF(Array<char>, temp_class);
                    strscan.readWord(temp_class, 0);
                    temp_class.push(0);
                    _bmol->setTemplateAtomClass(i, temp_class.ptr());
                }
                else if (strcmp(prop, "SEQID") == 0)
                {
                    int seq_id = strscan.readInt1();
                    _bmol->setTemplateAtomSeqid(i, seq_id);
                }
                else if (strcmp(prop, "SEQNAME") == 0)
                {
                    QS_DEF(Array<char>, seq_name);
                    strscan.readWord(seq_name, 0);
                    seq_name.push(0);
                }
                else
                {
                    throw Error("unsupported property of CTAB3000: %s", prop);
                }
            }

            if (isotope != 0)
            {
                if (_mol != 0)
                    _mol->setAtomIsotope(i, isotope);
                else
                    _qmol->resetAtom(i, QueryMolecule::Atom::und(_qmol->releaseAtom(i), new QueryMolecule::Atom(QueryMolecule::ATOM_ISOTOPE, isotope)));
            }

            if (_mol != 0)
                _mol->setAtomRadical(i, radical);

            _bmol->reaction_atom_inversion[i] = irflag;
            _bmol->reaction_atom_exact_change[i] = ecflag;
            _bmol->reaction_atom_mapping[i] = aamap;

            _bmol->setAtomXyz(i, x, y, z);
        }
        _scanner.readLine(str, true);
        if (strncmp(str.ptr(), "M  V30 END ATOM", 15) != 0)
            throw Error("Error reading ATOM block footer");
    }

    if (atom_block_exists)
        _scanner.readLine(str, true);
    if (strncmp(str.ptr(), "M  V30 BEGIN BOND", 17) != 0)
    {
        if (_bonds_num > 0)
            throw Error("Error reading BOND block header");
        bond_block_exists = false;
    }
    else
    {
        for (i = 0; i < _bonds_num; i++)
        {
            int reacting_center = 0;

            _readMultiString(str);
            BufferScanner strscan(str.ptr());

            strscan.readInt1(); // bond index -- ignored

            int order = strscan.readInt1();
            int beg = strscan.readInt1() - 1;
            int end = strscan.readInt1() - 1;

            if (_mol != 0)
            {
                if (order == BOND_SINGLE || order == BOND_DOUBLE || order == BOND_TRIPLE || order == BOND_AROMATIC || order == _BOND_COORDINATION ||
                    order == _BOND_HYDROGEN)
                    _mol->addBond_Silent(beg, end, order);
                else if (order == _BOND_COORDINATION || order == _BOND_HYDROGEN)
                    _mol->addBond_Silent(beg, end, BOND_ZERO);
                else if (order == _BOND_SINGLE_OR_DOUBLE)
                    throw Error("'single or double' bonds are allowed only for queries");
                else if (order == _BOND_SINGLE_OR_AROMATIC)
                    throw Error("'single or aromatic' bonds are allowed only for queries");
                else if (order == _BOND_DOUBLE_OR_AROMATIC)
                    throw Error("'double or aromatic' bonds are allowed only for queries");
                else if (order == _BOND_ANY)
                    throw Error("'any' bonds are allowed only for queries");
                else
                    throw Error("unknown bond type: %d", order);
            }
            else
            {
                _qmol->addBond(beg, end, QueryMolecule::createQueryMoleculeBond(order, 0, 0));
            }

            while (true)
            {
                strscan.skipSpace();
                if (strscan.isEOF())
                    break;

                QS_DEF(Array<char>, prop);

                strscan.readWord(prop, "=");
                strscan.skip(1);

                int n;

                if (strcmp(prop.ptr(), "CFG") == 0)
                {
                    n = strscan.readInt1();

                    if (n == 1)
                        _bmol->setBondDirection(i, BOND_UP);
                    else if (n == 3)
                        _bmol->setBondDirection(i, BOND_DOWN);
                    else if (n == 2)
                    {
                        int bond_order = _bmol->getBondOrder(i);
                        if (bond_order == BOND_SINGLE)
                            _bmol->setBondDirection(i, BOND_EITHER);
                        else if (bond_order == BOND_DOUBLE)
                            _ignore_cistrans[i] = 1;
                        else
                            throw Error("unknown bond CFG=%d for the bond order %d", n, bond_order);
                    }
                    else
                        throw Error("unknown bond CFG=%d", n);
                }
                else if (strcmp(prop.ptr(), "STBOX") == 0)
                {
                    if (_qmol == 0)
                        if (!ignore_noncritical_query_features)
                            throw Error("stereo care box is allowed only for queries");

                    if ((strscan.readInt1() != 0))
                        _stereo_care_bonds[i] = 1;
                }
                else if (strcmp(prop.ptr(), "TOPO") == 0)
                {
                    if (_qmol == 0)
                    {
                        if (!ignore_noncritical_query_features)
                            throw Error("bond topology setting is allowed only for queries");
                    }
                    else
                    {
                        int topo = strscan.readInt1();

                        _qmol->resetBond(
                            i, QueryMolecule::Bond::und(_qmol->releaseBond(i),
                                                        new QueryMolecule::Bond(QueryMolecule::BOND_TOPOLOGY, topo == 1 ? TOPOLOGY_RING : TOPOLOGY_CHAIN)));
                    }
                }
                else if (strcmp(prop.ptr(), "RXCTR") == 0)
                    reacting_center = strscan.readInt1();
                else if (strcmp(prop.ptr(), "ENDPTS") == 0)
                {
                    strscan.skip(1); // (
                    n = strscan.readInt1();
                    while (n-- > 0)
                    {
                        strscan.readInt();
                        strscan.skipSpace();
                    }
                    strscan.skip(1); // )
                }
                else if (strcmp(prop.ptr(), "ATTACH") == 0)
                {
                    while (!strscan.isEOF())
                    {
                        char c = strscan.readChar();
                        if (c == ' ')
                            break;
                    }
                }
                else if (strcmp(prop.ptr(), "DISP") == 0)
                {
                    while (!strscan.isEOF())
                    {
                        char c = strscan.readChar();
                        if (c == ' ')
                            break;
                    }
                }
                else
                {
                    throw Error("unsupported property of CTAB3000 (in BOND block): %s", prop.ptr());
                }
            }
            _bmol->reaction_bond_reacting_center[i] = reacting_center;
        }

        _scanner.readLine(str, true);
        if (strncmp(str.ptr(), "M  V30 END BOND", 15) != 0)
            throw Error("Error reading BOND block footer");

        _scanner.readLine(str, true);
    }

    // Read collections and sgroups
    // There is no predefined order: sgroups may appear before collection
    bool collection_parsed = false, sgroups_parsed = false;
    while (strncmp(str.ptr(), "M  V30 END CTAB", 15) != 0)
    {
        if (strncmp(str.ptr(), "M  V30 BEGIN COLLECTION", 23) == 0)
        {
            if (collection_parsed)
                throw Error("COLLECTION block has already been parsed");
            _readCollectionBlock3000();
            collection_parsed = true;
        }
        else if (strncmp(str.ptr(), "M  V30 BEGIN SGROUP", 19) == 0)
        {
            if (sgroups_parsed)
                throw Error("SGROUP block has already been parsed");
            _readSGroupsBlock3000();
            sgroups_parsed = true;
        }
        else if (strncmp(str.ptr(), "M  V30 LINKNODE", 15) == 0)
            throw Error("link nodes are not supported yet (%s)", str.ptr());
        else
            throw Error("error reading CTAB block footer: %s", str.ptr());

        _scanner.readLine(str, true);
    }
}