| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include "pdb2cif_remark_3.hpp" |
|
|
| #include "cif++.hpp" |
|
|
| #include <iomanip> |
| #include <map> |
| #include <set> |
| #include <stack> |
|
|
| using cif::category; |
| using cif::datablock; |
| using cif::iequals; |
| using cif::key; |
| using cif::to_lower; |
| using cif::to_lower_copy; |
|
|
| |
| |
|
|
| namespace error |
| { |
| enum pdbErrors |
| { |
| residueNotFound = 1000, |
| invalidDate |
| }; |
|
|
| namespace detail |
| { |
| class pdbCategory : public std::error_category |
| { |
| public: |
| const char *name() const noexcept |
| { |
| return "pdb"; |
| } |
|
|
| std::string message(int value) const |
| { |
| switch (value) |
| { |
| case residueNotFound: |
| return "Residue not found"; |
|
|
| case invalidDate: |
| return "Invalid date"; |
|
|
| default: |
| return "Error in PDB format"; |
| } |
| } |
| }; |
| } |
|
|
| std::error_category &pdbCategory() |
| { |
| static detail::pdbCategory impl; |
| return impl; |
| } |
|
|
| inline std::error_code make_error_code(pdbErrors e) |
| { |
| return std::error_code(static_cast<int>(e), pdbCategory()); |
| } |
| } |
|
|
| namespace std |
| { |
|
|
| template <> |
| struct is_error_code_enum<error::pdbErrors> |
| { |
| static const bool value = true; |
| }; |
|
|
| } |
|
|
| namespace cif::pdb |
| { |
|
|
| |
|
|
| const std::map<std::string, int> kMonths{ |
| { "JAN", 1 }, |
| { "FEB", 2 }, |
| { "MAR", 3 }, |
| { "APR", 4 }, |
| { "MAY", 5 }, |
| { "JUN", 6 }, |
| { "JUL", 7 }, |
| { "AUG", 8 }, |
| { "SEP", 9 }, |
| { "OCT", 10 }, |
| { "NOV", 11 }, |
| { "DEC", 12 }, |
| }; |
|
|
| const std::set<std::string> kSupportedRecords{ |
| "HEADER", "OBSLTE", "TITLE ", "SPLIT ", "CAVEAT", "COMPND", "SOURCE", |
| "KEYWDS", "EXPDTA", "NUMMDL", "MDLTYP", "AUTHOR", "REVDAT", "SPRSDE", |
| "JRNL ", "REMARK", "DBREF ", "DBREF1", "DBREF2", "SEQADV", "SEQRES", |
| "MODRES", "HET ", "HETNAM", "HETSYN", "FORMUL", "HELIX ", "SHEET ", |
| "SSBOND", "LINK ", "CISPEP", "SITE ", "CRYST1", "ORIGX1", "SCALE1", |
| "MTRIX1", "ORIGX2", "SCALE2", "MTRIX2", "ORIGX3", "SCALE3", "MTRIX3", |
| "MODEL ", "ATOM ", "ANISOU", "TER ", "HETATM", "ENDMDL", "CONECT", |
| "MASTER", "END ", |
|
|
| |
| "LINKR " |
| }; |
|
|
| bool isWater(const std::string &resname) |
| { |
| return resname == "HOH" or resname == "H2O" or resname == "OH2" or resname == "WAT" or resname == "DOD" or resname == "WAT"; |
| } |
|
|
| |
| |
| |
| |
|
|
| PDBRecord::PDBRecord(uint32_t lineNr, const std::string &name, const std::string &value) |
| : mNext(nullptr) |
| , mLineNr(lineNr) |
| , mVlen(value.length()) |
| { |
| assert(name.length() <= 10); |
|
|
| strcpy(mName, name.c_str()); |
| strcpy(mValue, value.c_str()); |
| } |
|
|
| PDBRecord::~PDBRecord() |
| { |
| } |
|
|
| void *PDBRecord::operator new(size_t size, size_t vLen) |
| { |
| return malloc(size + vLen + 1); |
| } |
|
|
| void PDBRecord::operator delete(void *p) |
| { |
| free(p); |
| } |
|
|
| void PDBRecord::operator delete(void *p, size_t vLen) |
| { |
| free(p); |
| } |
|
|
| bool PDBRecord::is(const char *name) const |
| { |
| return iequals(mName, name); |
| } |
|
|
| char PDBRecord::vC(size_t column) |
| { |
| char result = ' '; |
| if (column - 7 < mVlen) |
| result = mValue[column - 7]; |
| return result; |
| } |
|
|
| std::string PDBRecord::vS(size_t columnFirst, size_t columnLast) |
| { |
| std::string result; |
|
|
| if (columnLast > mVlen + 6) |
| columnLast = mVlen + 6; |
|
|
| if (columnFirst < mVlen + 7) |
| { |
| result = std::string{ mValue + columnFirst - 7, mValue + columnLast - 7 + 1 }; |
| cif::trim(result); |
| } |
|
|
| return result; |
| } |
|
|
| int PDBRecord::vI(int columnFirst, int columnLast) |
| { |
| int result = 0; |
|
|
| const char *e = mValue + mVlen; |
| if (e > mValue + columnLast - 7 + 1) |
| e = mValue + columnLast - 7 + 1; |
|
|
| enum |
| { |
| start, |
| digit, |
| tail |
| } state = start; |
| bool negate = false; |
|
|
| try |
| { |
| for (const char *p = mValue + columnFirst - 7; p < e; ++p) |
| { |
| switch (state) |
| { |
| case start: |
| if (*p == '+') |
| state = digit; |
| else if (*p == '-') |
| { |
| negate = true; |
| state = digit; |
| } |
| else if (isdigit(*p)) |
| { |
| result = *p - '0'; |
| state = digit; |
| } |
| else if (not isspace(*p)) |
| throw std::runtime_error("Not a valid integer in PDB record"); |
| break; |
|
|
| case digit: |
| if (isspace(*p)) |
| state = tail; |
| else if (not isdigit(*p)) |
| throw std::runtime_error("Not a valid integer in PDB record"); |
| else |
| result = result * 10 + *p - '0'; |
| break; |
|
|
| case tail: |
| if (not isspace(*p)) |
| throw std::runtime_error("Not a valid integer in PDB record"); |
| break; |
| } |
| } |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE >= 0) |
| std::cerr << "Trying to parse '" << std::string(mValue + columnFirst - 7, mValue + columnLast - 7) << '\'' << '\n'; |
| throw; |
| } |
|
|
| if (negate) |
| result = -result; |
|
|
| return result; |
| } |
|
|
| std::string PDBRecord::vF(size_t columnFirst, size_t columnLast) |
| { |
| |
| return vS(columnFirst, columnLast); |
| } |
|
|
| |
|
|
| class SpecificationListParser |
| { |
| public: |
| SpecificationListParser(const std::string &text) |
| : mText(text) |
| , mP(mText.begin()) |
| { |
| } |
|
|
| std::tuple<std::string, std::string> GetNextSpecification(); |
|
|
| private: |
| std::string mText; |
| std::string::iterator mP; |
| }; |
|
|
| std::tuple<std::string, std::string> SpecificationListParser::GetNextSpecification() |
| { |
| std::string id, value; |
|
|
| std::string::iterator start = mP, backup; |
|
|
| enum |
| { |
| eStart, |
| eID, |
| eColon, |
| eValue, |
| eNL, |
| eNL_ID, |
| eSemiColon, |
| eError, |
| eDone |
| } state = eStart; |
|
|
| while (mP != mText.end() and state != eDone) |
| { |
| char ch = *mP++; |
|
|
| switch (state) |
| { |
| case eStart: |
| if (isalnum(ch) or ch == '_') |
| { |
| id = { ch }; |
| value.clear(); |
| state = eID; |
| start = mP; |
| } |
| else if (not isspace(ch)) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "skipping invalid character in SOURCE ID: " << ch << '\n'; |
| } |
| break; |
|
|
| case eID: |
| if (isalnum(ch) or ch == '_') |
| id += ch; |
| else if (ch == ':') |
| state = eColon; |
| else |
| state = eError; |
| break; |
|
|
| case eColon: |
| if (ch == ';') |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Empty value for SOURCE: " << id << '\n'; |
| state = eStart; |
| } |
| else if (not isspace(ch)) |
| { |
| value = { ch }; |
| state = eValue; |
| } |
| break; |
|
|
| case eValue: |
| if (ch == '\n') |
| { |
| backup = mP; |
| state = eNL; |
| } |
| else if (ch == ';') |
| { |
| backup = mP; |
| state = eSemiColon; |
| } |
| else |
| value += ch; |
| break; |
|
|
| case eSemiColon: |
| if (ch == '\n') |
| state = eDone; |
| else if (ch != ' ') |
| { |
| value.insert(value.end(), backup, mP); |
| state = eValue; |
| } |
| break; |
|
|
| case eNL: |
| if (isalnum(ch)) |
| { |
| value += ' '; |
| state = eNL_ID; |
| } |
| else if (isspace(ch)) |
| state = eValue; |
| break; |
|
|
| case eNL_ID: |
| if (ch == ':') |
| { |
| mP = backup; |
| state = eDone; |
| } |
| else if (ch == ';') |
| state = eSemiColon; |
| else if (not(isalnum(ch) or ch == '_')) |
| { |
| value.insert(value.end(), backup, mP); |
| state = eValue; |
| } |
| break; |
|
|
| case eError: |
| if (ch == ';') |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Skipping invalid header line: '" << std::string(start, mP) << '\n'; |
| state = eStart; |
| } |
| break; |
|
|
| case eDone: break; |
| } |
| } |
|
|
| cif::trim(value); |
|
|
| return std::make_tuple(id, value); |
| } |
|
|
| |
|
|
| class PDBFileParser |
| { |
| public: |
| PDBFileParser() |
| : mData(nullptr) |
| , mRec(nullptr) |
| { |
| } |
|
|
| ~PDBFileParser() |
| { |
| PDBRecord *r = mData; |
| while (r != nullptr) |
| { |
| PDBRecord *d = r; |
| r = d->mNext; |
| delete d; |
| } |
| } |
|
|
| void Parse(std::istream &is, cif::file &result); |
|
|
| private: |
| |
|
|
| struct DBREF |
| { |
| std::string PDBIDCode; |
| char chainID; |
| int seqBegin; |
| char insertBegin = ' '; |
| int seqEnd; |
| char insertEnd = ' '; |
| std::string database; |
| std::string dbAccession; |
| std::string dbIdCode; |
| int dbSeqBegin; |
| char dbinsBeg; |
| int dbSeqEnd; |
| char dbinsEnd; |
| }; |
|
|
| struct HET |
| { |
| std::string hetID; |
| char chainID; |
| int seqNum; |
| char iCode; |
| int numHetAtoms = 0; |
| std::string text; |
| std::string asymID; |
| std::vector<PDBRecord *> atoms; |
| bool processed = false; |
| bool branch = false; |
| PDBRecord *asn = nullptr; |
|
|
| HET(const std::string &hetID, char chainID, int seqNum, char iCode, int numHetAtoms = 0, const std::string &text = {}) |
| : hetID(hetID) |
| , chainID(chainID) |
| , seqNum(seqNum) |
| , iCode(iCode) |
| , numHetAtoms(numHetAtoms) |
| , text(text) |
| { |
| } |
| }; |
|
|
| struct UNOBS |
| { |
| int modelNr; |
| std::string res; |
| char chain; |
| int seq; |
| char iCode; |
| std::vector<std::string> atoms; |
| }; |
|
|
| struct ATOM_REF |
| { |
| std::string name; |
| std::string resName; |
| int resSeq; |
| char chainID; |
| char iCode; |
| char altLoc; |
|
|
| bool operator==(const ATOM_REF &rhs) const |
| { |
| return name == rhs.name and |
| resName == rhs.resName and |
| resSeq == rhs.resSeq and |
| (altLoc == rhs.altLoc or altLoc == ' ' or rhs.altLoc == ' ') and |
| chainID == rhs.chainID and |
| iCode == rhs.iCode; |
| } |
|
|
| bool operator!=(const ATOM_REF &rhs) const |
| { |
| return not operator==(rhs); |
| } |
|
|
| bool operator<(const ATOM_REF &rhs) const |
| { |
| int d = chainID - rhs.chainID; |
| if (d == 0) |
| d = resSeq - rhs.resSeq; |
| if (d == 0) |
| d = iCode - rhs.iCode; |
| |
| if (d == 0) |
| d = name.compare(rhs.name); |
| if (d == 0 and altLoc != ' ' and rhs.altLoc != ' ') |
| d = altLoc - rhs.altLoc; |
| return d < 0; |
| } |
|
|
| friend std::ostream &operator<<(std::ostream &os, const ATOM_REF &a) |
| { |
| os << a.name << ' ' << a.resName << ' ' << a.chainID << ' ' << a.resSeq << (a.iCode == ' ' ? "" : std::string{ a.iCode }) << (a.altLoc != ' ' ? std::string{ ' ', a.altLoc } : ""); |
| return os; |
| } |
| }; |
|
|
| struct LINK |
| { |
| ATOM_REF a, b; |
| std::string symOpA, symOpB; |
| float distance; |
| }; |
|
|
| struct SUGAR |
| { |
| ATOM_REF c1; |
| int leaving_o; |
| ATOM_REF next; |
| }; |
|
|
| class SUGAR_TREE : public std::vector<SUGAR> |
| { |
| public: |
| std::string entityName() const |
| { |
| return empty() ? "" : entityName(begin()); |
| } |
|
|
| private: |
| std::string entityName(const_iterator sugar) const |
| { |
| std::string result; |
|
|
| for (auto i = begin(); i != end(); ++i) |
| { |
| if (i->next != sugar->c1) |
| continue; |
|
|
| auto n = entityName(i) + "-(1-" + std::to_string(i->leaving_o) + ")"; |
|
|
| if (result.empty()) |
| result = n; |
| else |
| result += "-[" + n + ']'; |
| } |
|
|
| if (not result.empty() and result.back() != ']') |
| result += '-'; |
|
|
| auto compound = cif::compound_factory::instance().create(sugar->c1.resName); |
| if (compound) |
| result += compound->name(); |
| else if (sugar->c1.resName == "MAN") |
| result += "alpha-D-mannopyranose"; |
| else if (sugar->c1.resName == "BMA") |
| result += "beta-D-mannopyranose"; |
| else if (sugar->c1.resName == "NAG") |
| result += "2-acetamido-2-deoxy-beta-D-glucopyranose"; |
| else if (sugar->c1.resName == "NDG") |
| result += "2-acetamido-2-deoxy-alpha-D-glucopyranose"; |
| else if (sugar->c1.resName == "FUC") |
| result += "alpha-L-fucopyranose"; |
| else if (sugar->c1.resName == "FUL") |
| result += "beta-L-fucopyranose"; |
| else |
| result += sugar->c1.resName; |
|
|
| return result; |
| } |
| }; |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| struct PDBCompound |
| { |
| int mMolID; |
| std::string mTitle; |
| std::set<char> mChains; |
| std::map<std::string, std::string> mInfo; |
| std::map<std::string, std::string> mSource; |
| int mCount = 0; |
| }; |
|
|
| struct PDBSeqRes |
| { |
| std::string mMonID; |
| int mSeqNum; |
| char mIcode; |
|
|
| int mDbSeqNum = 0; |
| bool mSeen = false; |
| std::set<std::string> mAlts; |
|
|
| bool operator==(const PDBSeqRes &rhs) const |
| { |
| return mSeqNum == rhs.mSeqNum and mMonID == rhs.mMonID and mIcode == rhs.mIcode; |
| } |
| }; |
|
|
| struct PDBChain |
| { |
| PDBChain(const std::string &structureID, char chainID, int molID) |
| : mDbref{ structureID, chainID } |
| , mWaters(0) |
| , mTerIndex(0) |
| , mMolID(molID) |
| , mNextSeqNum(1) |
| , mNextDbSeqNum(1) |
| { |
| } |
|
|
| DBREF mDbref; |
| std::vector<PDBSeqRes> mSeqres, mHet; |
| int mWaters; |
| int mTerIndex; |
|
|
| int mMolID; |
|
|
| |
| int mNextSeqNum; |
| int mNextDbSeqNum; |
|
|
| |
| struct AtomRes |
| { |
| std::string mMonID; |
| int mSeqNum; |
| char mIcode; |
|
|
| bool operator==(const AtomRes &rhs) const { return mSeqNum == rhs.mSeqNum and mIcode == rhs.mIcode; } |
| bool operator!=(const AtomRes &rhs) const { return mSeqNum != rhs.mSeqNum or mIcode != rhs.mIcode; } |
| }; |
| std::vector<AtomRes> mResiduesSeen; |
|
|
| int AlignResToSeqRes(); |
| bool SameSequence(const PDBChain &rhs) const; |
| }; |
|
|
| |
|
|
| PDBCompound &GetOrCreateCompound(int molID) |
| { |
| auto i = std::find_if(mCompounds.begin(), mCompounds.end(), [molID](PDBCompound &comp) -> bool |
| { return comp.mMolID == molID; }); |
| if (i == mCompounds.end()) |
| { |
| mCompounds.push_back(PDBCompound{ molID }); |
|
|
| mMolID2EntityID[molID] = std::to_string(mNextEntityNr++); |
|
|
| i = prev(mCompounds.end()); |
| } |
|
|
| return *i; |
| } |
|
|
| |
| PDBChain &GetChainForID(char chainID, int numRes = 0) |
| { |
| auto i = std::find_if(mChains.begin(), mChains.end(), [chainID](PDBChain &ch) -> bool |
| { return ch.mDbref.chainID == chainID; }); |
|
|
| if (i == mChains.end()) |
| { |
| |
| int molID = 0; |
| for (auto &cmp : mCompounds) |
| { |
| if (cmp.mChains.count(chainID) > 0) |
| { |
| molID = cmp.mMolID; |
| break; |
| } |
| } |
|
|
| mChains.emplace_back(mStructureID, chainID, molID); |
|
|
| i = prev(mChains.end()); |
| } |
|
|
| return *i; |
| }; |
|
|
| void InsertChemComp(const std::string &chemComp) |
| { |
| if (find(mChemComp.begin(), mChemComp.end(), chemComp) == mChemComp.end()) |
| mChemComp.push_back(chemComp); |
| } |
|
|
| void InsertAtomType(const std::string &atomType) |
| { |
| if (find(mAtomTypes.begin(), mAtomTypes.end(), atomType) == mAtomTypes.end()) |
| mAtomTypes.push_back(atomType); |
| } |
|
|
| |
|
|
| template <typename Predicate> |
| PDBRecord *FindRecord(Predicate &&pred) |
| { |
| PDBRecord *result; |
|
|
| for (result = mData; result != nullptr; result = result->mNext) |
| { |
| if (pred(*result)) |
| break; |
| } |
|
|
| return result; |
| } |
|
|
| PDBRecord *FindRecord(const char *name) |
| { |
| return FindRecord([name](PDBRecord &rec) -> bool |
| { return rec.is(name); }); |
| } |
|
|
| |
|
|
| char vC(size_t column) const |
| { |
| return mRec->vC(column); |
| } |
|
|
| std::string vS(size_t columnFirst, size_t columnLast = std::numeric_limits<size_t>::max()) const |
| { |
| return mRec->vS(columnFirst, columnLast); |
| } |
|
|
| std::string vF(size_t columnFirst, size_t columnLast) const |
| { |
| return mRec->vF(columnFirst, columnLast); |
| } |
|
|
| int vI(int columnFirst, int columnLast) const |
| { |
| return mRec->vI(columnFirst, columnLast); |
| } |
|
|
| |
|
|
| |
| std::tuple<std::string, int, bool> MapResidue(char chainID, int resSeq, char iCode) const |
| { |
| auto key = std::make_tuple(chainID, resSeq, iCode); |
|
|
| try |
| { |
| return mChainSeq2AsymSeq.at(key); |
| } |
| catch (const std::exception &ex) |
| { |
| throw_with_nested(std::runtime_error(std::string("Residue ") + chainID + std::to_string(resSeq) + iCode + " could not be mapped")); |
| } |
| } |
|
|
| std::tuple<std::string, int, bool> MapResidue(char chainID, int resSeq, char iCode, std::error_code &ec) const |
| { |
| auto key = std::make_tuple(chainID, resSeq, iCode); |
|
|
| std::tuple<std::string, int, bool> result; |
|
|
| if (not mChainSeq2AsymSeq.count(key)) |
| { |
| ec = error::make_error_code(error::pdbErrors::residueNotFound); |
| if (cif::VERBOSE > 0) |
| std::cerr << "Residue " << chainID << resSeq << iCode << " could not be mapped\n"; |
| } |
| else |
| result = mChainSeq2AsymSeq.at(key); |
|
|
| return result; |
| } |
|
|
| |
|
|
| void PreParseInput(std::istream &is); |
|
|
| void GetNextRecord(); |
| void Match(const std::string &expected, bool throwIfMissing); |
|
|
| void ParseTitle(); |
| void ParseCitation(const std::string &id); |
| void ParseRemarks(); |
|
|
| |
| |
| |
|
|
| void ParseRemark200(); |
| void ParseRemark350(); |
|
|
| void ParsePrimaryStructure(); |
| void ParseHeterogen(); |
| void ConstructEntities(); |
| void ConstructSugarTrees(int &asymNr); |
| void ParseSecondaryStructure(); |
| void ParseConnectivtyAnnotation(); |
| void ParseMiscellaneousFeatures(); |
| void ParseCrystallographic(); |
| void ParseCoordinateTransformation(); |
| void ParseCoordinate(int modelNr); |
| void ParseConnectivty(); |
| void ParseBookkeeping(); |
|
|
| |
|
|
| category *getCategory(std::string name) |
| { |
| return &mDatablock[name]; |
| } |
|
|
| std::vector<std::string> SplitCSV(const std::string &value); |
|
|
| std::string pdb2cifDate(std::string s, std::error_code &ec) |
| { |
| std::smatch m; |
| const std::regex |
| rx1(R"((\d{2})-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)-(\d{2}))"), |
| rx2(R"((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)-(\d{2}))"); |
|
|
| try |
| { |
| if (regex_match(s, m, rx1)) |
| { |
| int day = stoi(m[1].str()); |
| auto mi = kMonths.find(m[2].str()); |
| if (mi == kMonths.end()) |
| throw std::runtime_error("Invalid month: '" + m[2].str() + '\''); |
| int month = mi->second; |
| int year = 1900 + stoi(m[3].str()); |
| if (year < 1950) |
| year += 100; |
|
|
| std::stringstream ss; |
| ss << std::setw(4) << std::setfill('0') << year << '-' |
| << std::setw(2) << std::setfill('0') << month << '-' |
| << std::setw(2) << std::setfill('0') << day; |
|
|
| s = ss.str(); |
| } |
| else if (regex_match(s, m, rx2)) |
| { |
| auto mi = kMonths.find(m[1].str()); |
| if (mi == kMonths.end()) |
| throw std::runtime_error("Invalid month: '" + m[1].str() + '\''); |
| int month = mi->second; |
| int year = 1900 + stoi(m[2].str()); |
| if (year < 1950) |
| year += 100; |
|
|
| s = cif::format("%04d-%02d", year, month).str(); |
| } |
| else |
| ec = error::make_error_code(error::pdbErrors::invalidDate); |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << ex.what() << '\n'; |
| ec = error::make_error_code(error::pdbErrors::invalidDate); |
| } |
|
|
| return s; |
| } |
|
|
| std::string pdb2cifDate(std::string s) |
| { |
| std::error_code ec; |
| auto result = pdb2cifDate(s, ec); |
| if (ec and cif::VERBOSE > 0) |
| std::cerr << "Invalid date(" << s << "): " << ec.message() << '\n'; |
| return result; |
| } |
|
|
| std::string pdb2cifAuth(std::string author) |
| { |
| cif::trim(author); |
|
|
| const std::regex rx(R"(((?:[A-Z]+\.)+)(.+))"); |
| std::smatch m; |
| if (regex_match(author, m, rx)) |
| author = m[2].str() + ", " + m[1].str(); |
|
|
| bool upper = true; |
| for (auto &c : author) |
| { |
| if (ispunct(c) or isspace(c)) |
| upper = true; |
| else if (upper) |
| upper = false; |
| else |
| c = cif::tolower(c); |
| } |
|
|
| return author; |
| } |
|
|
| std::string pdb2cifSymmetry(std::string s) |
| { |
| static const std::regex sgRx(R"((\d{1,3})(\d{3}))"); |
|
|
| if (not s.empty()) |
| { |
| std::smatch m; |
| if (not std::regex_match(s, m, sgRx)) |
| throw std::runtime_error("invalid symmetry value '" + s + '\''); |
|
|
| s = m[1].str() + "_" + m[2].str(); |
| } |
|
|
| return s; |
| } |
|
|
| std::string pdb2cifCharge(std::string c) |
| { |
| std::regex rx(R"((\d+)(\+|-))"); |
| std::smatch m; |
|
|
| if (std::regex_match(c, m, rx)) |
| { |
| if (m[2].str() == "-") |
| c = '-' + m[1].str(); |
| else |
| c = m[1].str(); |
| } |
|
|
| return c; |
| } |
|
|
| std::vector<char> altLocsForAtom(char chainID, int seqNum, char iCode, std::string atomName); |
| void MapChainID2AsymIDS(char chainID, std::vector<std::string> &asymIds); |
|
|
| std::tuple<ATOM_REF, bool> FindLink(const std::string &name1, const std::string &resName1, int resSeq1, char altLoc1, char chainID1, char iCode1, |
| const std::string &name2, const std::string &resName2 = "") |
| { |
| return FindLink(ATOM_REF{ name1, resName1, resSeq1, altLoc1, chainID1, iCode1 }, name2, resName2); |
| } |
|
|
| std::tuple<ATOM_REF, bool> FindLink(const ATOM_REF &atom, const std::string &name2, const std::string &resName2 = "") const |
| { |
| auto i = std::find_if(mLinks.begin(), mLinks.end(), [&](const LINK &link) |
| { return (link.a == atom and link.b.name == name2 and (resName2.empty() or link.b.resName == resName2)) or |
| (link.b == atom and link.a.name == name2 and (resName2.empty() or link.a.resName == resName2)); }); |
|
|
| if (i != mLinks.end()) |
| return { i->a == atom ? i->b : i->a, true }; |
|
|
| return {}; |
| } |
|
|
| |
|
|
| PDBRecord *mData; |
| PDBRecord *mRec; |
| cif::datablock mDatablock; |
|
|
| std::string mStructureID; |
| std::string mModelTypeDetails; |
| std::string mOriginalDate; |
| std::string mExpMethod = "X-RAY DIFFRACTION"; |
| int mCitationAuthorNr = 1, mCitationEditorNr = 1; |
| int mNextMolID = 1, mNextEntityNr = 1; |
| int mNextSoftwareOrd = 1; |
|
|
| struct SEQADV |
| { |
| std::string resName; |
| char chainID; |
| int seqNum; |
| char iCode; |
| std::string database; |
| std::string dbAccession; |
| std::string dbRes; |
| int dbSeq; |
| std::string conflict; |
| }; |
|
|
| std::vector<SEQADV> mSeqadvs; |
|
|
| std::list<PDBCompound> mCompounds; |
| std::list<PDBChain> mChains; |
| std::vector<HET> mHets; |
| std::map<std::string, std::string> mHetnams; |
| std::map<std::string, std::string> mHetsyns; |
| std::map<std::string, std::string> mFormuls; |
| std::string mWaterHetID; |
| std::vector<std::string> mChemComp, mAtomTypes; |
|
|
| std::map<std::string, std::string> mRemark200; |
| std::string mRefinementSoftware; |
| int mAtomID = 0; |
| int mPdbxDifOrdinal = 0; |
|
|
| std::vector<UNOBS> mUnobs; |
| std::vector<LINK> mLinks; |
|
|
| |
| std::map<std::tuple<char, int, char>, std::tuple<std::string, int, bool>> mChainSeq2AsymSeq; |
|
|
| std::map<int, std::string> mMolID2EntityID; |
| std::map<std::string, std::string> mHet2EntityID; |
| std::map<std::string, std::string> mBranch2EntityID; |
| std::map<std::string, std::string> mAsymID2EntityID; |
| std::map<std::string, std::string> mMod2parent; |
| std::set<std::string> mSugarEntities; |
| }; |
|
|
| |
|
|
| std::vector<char> PDBFileParser::altLocsForAtom(char inChainID, int inResSeq, char inICode, std::string inAtomName) |
| { |
| |
| std::set<char> result; |
|
|
| for (auto r = mData; r != nullptr; r = r->mNext) |
| { |
| if (r->is("ATOM ") or r->is("HETATM")) |
| { |
| std::string name = r->vS(13, 16); |
| char altLoc = r->vC(17); |
| char chainID = r->vC(22); |
| int resSeq = r->vI(23, 26); |
| char iCode = r->vC(27); |
|
|
| if (chainID == inChainID and resSeq == inResSeq and iCode == inICode and name == inAtomName and altLoc != ' ') |
| result.insert(altLoc); |
| } |
| } |
|
|
| return { result.begin(), result.end() }; |
| } |
|
|
| void PDBFileParser::MapChainID2AsymIDS(char chainID, std::vector<std::string> &asymIds) |
| { |
| for (const auto &[key, value] : mChainSeq2AsymSeq) |
| { |
| if (std::get<0>(key) == chainID) |
| asymIds.push_back(std::get<0>(value)); |
| } |
|
|
| std::sort(asymIds.begin(), asymIds.end(), [](const std::string &a, const std::string &b) |
| { |
| int d = static_cast<int>(a.length() - b.length()); |
| if (d == 0) |
| d = a.compare(b); |
| return d < 0; }); |
|
|
| asymIds.erase(std::unique(asymIds.begin(), asymIds.end()), asymIds.end()); |
| } |
|
|
| |
|
|
| void PDBFileParser::PreParseInput(std::istream &is) |
| { |
| std::string lookahead; |
| uint32_t lineNr = 1; |
| getline(is, lookahead); |
|
|
| if (lookahead.back() == '\r') |
| lookahead.pop_back(); |
|
|
| auto contNr = [&lookahead](int offset, int len) -> int |
| { |
| std::string cs = lookahead.substr(offset, len); |
| cif::trim(cs); |
| int result = 0; |
|
|
| if (not cs.empty()) |
| { |
| auto r = std::from_chars(cs.data(), cs.data() + cs.length(), result); |
| if ((bool)r.ec) |
| throw std::runtime_error("Continuation std::string '" + cs + "' is not valid"); |
| } |
|
|
| return result; |
| }; |
|
|
| PDBRecord *last = nullptr; |
| std::set<std::string> dropped; |
|
|
| for (;;) |
| { |
| if (lookahead.empty()) |
| { |
| if (is.eof()) |
| break; |
|
|
| if (cif::VERBOSE > 0) |
| std::cerr << "Line number " << lineNr << " is empty!\n"; |
|
|
| getline(is, lookahead); |
| ++lineNr; |
|
|
| continue; |
| } |
|
|
| std::string type = lookahead.substr(0, 6); |
| std::string value; |
| if (lookahead.length() > 6) |
| value = cif::trim_right_copy(lookahead.substr(6)); |
|
|
| lookahead.clear(); |
|
|
| uint32_t curLineNr = lineNr; |
| getline(is, lookahead); |
| ++lineNr; |
|
|
| if (kSupportedRecords.count(type) == 0) |
| { |
| cif::trim(type); |
|
|
| if (type != "END") |
| dropped.insert(type); |
|
|
| lookahead.clear(); |
|
|
| continue; |
| } |
|
|
| |
| if (type == "AUTHOR" or |
| type == "EXPDTA" or |
| type == "MDLTYP" or |
| type == "KEYWDS" or |
| type == "SPLIT " or |
| type == "SPRSDE" or |
| type == "TITLE ") |
| { |
| int n = 2; |
| while (lookahead.substr(0, 6) == type and contNr(7, 3) == n) |
| { |
| value += cif::trim_right_copy(lookahead.substr(10)); |
| getline(is, lookahead); |
| ++lineNr; |
| ++n; |
| } |
| } |
| else if (type == "COMPND") |
| { |
| int n = 2; |
| value += '\n'; |
| while (lookahead.substr(0, 6) == type and contNr(7, 3) == n) |
| { |
| value += cif::trim_right_copy(lookahead.substr(10)); |
| value += '\n'; |
| getline(is, lookahead); |
| ++lineNr; |
| ++n; |
| } |
| } |
| else if (type == "REVDAT") |
| { |
| int revNr = stoi(value.substr(1, 3)); |
| int n = 2; |
| while (lookahead.substr(0, 6) == type and |
| stoi(lookahead.substr(7, 3)) == revNr and |
| contNr(10, 2) == n) |
| { |
| value += lookahead.substr(38); |
| getline(is, lookahead); |
| ++lineNr; |
| ++n; |
| } |
| } |
| else if (type == "CAVEAT") |
| { |
| int n = 2; |
| while (lookahead.substr(0, 6) == type and contNr(7, 3) == n) |
| { |
| value += cif::trim_right_copy(lookahead.substr(13)); |
| getline(is, lookahead); |
| ++lineNr; |
| ++n; |
| } |
| } |
| else if (type == "OBSLTE") |
| { |
| while (lookahead.substr(0, 6) == type) |
| { |
| value += lookahead.substr(31); |
| getline(is, lookahead); |
| ++lineNr; |
| } |
| } |
| else if (type == "SOURCE") |
| { |
| value += '\n'; |
| int n = 2; |
| while (lookahead.substr(0, 6) == type and contNr(7, 3) == n) |
| { |
| value += cif::trim_copy(lookahead.substr(10)); |
| value += '\n'; |
| getline(is, lookahead); |
| ++lineNr; |
| ++n; |
| } |
| } |
| else if (type == "FORMUL") |
| { |
| try |
| { |
| int compNr; |
| try |
| { |
| compNr = stoi(value.substr(1, 3)); |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE >= 0) |
| std::cerr << "Dropping FORMUL line (" << (lineNr - 1) << ") with invalid component number '" << value.substr(1, 3) << '\'' << '\n'; |
| continue; |
| |
| } |
|
|
| int n = 2; |
| try |
| { |
| while (lookahead.substr(0, 6) == type and |
| stoi(lookahead.substr(7, 3)) == compNr and |
| contNr(16, 2) == n) |
| { |
| value += cif::trim_right_copy(lookahead.substr(19)); |
| ; |
| getline(is, lookahead); |
| ++lineNr; |
| ++n; |
| } |
| } |
| catch (const std::invalid_argument &ex) |
| { |
| continue; |
| |
| } |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE >= 0) |
| std::cerr << "Error parsing FORMUL at line " << lineNr << '\n'; |
| throw; |
| } |
| } |
| else if (type == "HETNAM" or |
| type == "HETSYN") |
| { |
| int n = 2; |
| while (lookahead.substr(0, 6) == type and contNr(8, 2) == n) |
| { |
| value += cif::trim_right_copy(lookahead.substr(16)); |
| ; |
| getline(is, lookahead); |
| ++lineNr; |
| ++n; |
| } |
| } |
| else if (type == "SITE ") |
| { |
| std::string siteName = value.substr(5, 3); |
| cif::trim_right(value); |
| size_t n = value.length() - 12; |
| value += std::string(11 - (n % 11), ' '); |
|
|
| while (lookahead.substr(0, 6) == type and lookahead.substr(11, 3) == siteName) |
| { |
| std::string s = lookahead.substr(18); |
| cif::trim_right(s); |
| s += std::string(11 - (s.length() % 11), ' '); |
| value += s; |
|
|
| |
| |
| getline(is, lookahead); |
| ++lineNr; |
| } |
| } |
| else if (type == "REMARK") |
| { |
| type += value.substr(0, 4); |
|
|
| |
| if (type == "REMARK 200" or type == "REMARK 240") |
| { |
| auto i = value.find(":"); |
|
|
| if (i != std::string::npos) |
| { |
| std::string k = value.substr(4, i - 4); |
| std::string v = value.substr(i + 1); |
|
|
| cif::trim(k); |
| while (k.find(" ") != std::string::npos) |
| cif::replace_all(k, " ", " "); |
| cif::trim(v); |
|
|
| if (iequals(v, "NONE") or iequals(v, "N/A") or iequals(v, "NAN")) |
| mRemark200[k] = "."; |
| else if (not iequals(v, "NULL")) |
| mRemark200[k] = v; |
| } |
| } |
| } |
|
|
| PDBRecord *cur = new (value.length()) PDBRecord(curLineNr, type, value); |
|
|
| if (last == nullptr) |
| last = mData = cur; |
| else |
| last->mNext = cur; |
|
|
| last = cur; |
|
|
| cif::trim(type); |
|
|
| if (type == "LINK" or type == "LINKR") |
| { |
| LINK link = {}; |
|
|
| link.a.name = cur->vS(13, 16); |
| link.a.altLoc = cur->vC(17); |
| link.a.resName = cur->vS(18, 20); |
| link.a.chainID = cur->vC(22); |
| link.a.resSeq = cur->vI(23, 26); |
| link.a.iCode = cur->vC(27); |
| link.b.name = cur->vS(43, 46); |
| link.b.altLoc = cur->vC(47); |
| link.b.resName = cur->vS(48, 50); |
| link.b.chainID = cur->vC(52); |
| link.b.resSeq = cur->vI(53, 56); |
| link.b.iCode = cur->vC(57); |
| link.symOpA = cur->vS(60, 65); |
| link.symOpB = cur->vS(67, 72); |
|
|
| if (type == "LINK") |
| { |
| auto f = cur->vF(74, 78); |
| auto r = cif::from_chars(f.data(), f.data() + f.length(), link.distance); |
| if ((bool)r.ec and cif::VERBOSE > 0) |
| std::cerr << "Error parsing link distance at line " << cur->mLineNr << '\n'; |
| } |
| |
|
|
| mLinks.push_back(link); |
| } |
|
|
| if (type == "END") |
| break; |
| } |
|
|
| if (not dropped.empty()) |
| { |
| if (cif::VERBOSE >= 0) |
| std::cerr << "Dropped unsupported records: " << cif::join(dropped, ", ") << '\n'; |
| } |
|
|
| if (mData == nullptr) |
| throw std::runtime_error("Empty file?"); |
|
|
| mRec = mData; |
| } |
|
|
| void PDBFileParser::GetNextRecord() |
| { |
| if (mRec != nullptr) |
| mRec = mRec->mNext; |
|
|
| if (mRec == nullptr) |
| { |
| static PDBRecord *end = new (0) PDBRecord({ 0, "END ", "" }); |
| mRec = end; |
| } |
| } |
|
|
| void PDBFileParser::Match(const std::string &expected, bool throwIfMissing) |
| { |
| assert(mRec); |
| if (mRec->mName != expected) |
| { |
| if (throwIfMissing) |
| throw std::runtime_error("Expected record " + expected + " but found " + mRec->mName); |
| if (cif::VERBOSE > 0) |
| std::cerr << "Expected record " + expected + " but found " + mRec->mName << '\n'; |
| } |
| } |
|
|
| std::vector<std::string> PDBFileParser::SplitCSV(const std::string &value) |
| { |
| auto vs = cif::split<std::string>(value, ","); |
| for (auto &v : vs) |
| cif::trim(v); |
| return vs; |
| } |
|
|
| void PDBFileParser::ParseTitle() |
| { |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| Match("HEADER", false); |
|
|
| std::string keywords; |
|
|
| if (mRec->is("HEADER")) |
| { |
| mStructureID = vS(63, 66); |
| keywords = vS(11, 50); |
| mOriginalDate = pdb2cifDate(vS(51, 59)); |
|
|
| cif::trim(keywords); |
|
|
| GetNextRecord(); |
| } |
|
|
| cif::trim(mStructureID); |
| if (mStructureID.empty()) |
| mStructureID = "nohd"; |
|
|
| mDatablock.set_name(mStructureID); |
|
|
| auto cat = getCategory("entry"); |
| |
| cat->emplace({ { "id", mStructureID } }); |
|
|
| |
| if (mRec->is("OBSLTE")) |
| { |
| |
| |
| |
| |
| |
| |
|
|
| std::string old = vS(22, 25); |
| std::string date = pdb2cifDate(vS(12, 20)); |
| cat = getCategory("pdbx_database_PDB_obs"); |
|
|
| std::string value = mRec->vS(32); |
| for (auto i : cif::split<std::string>(value, " ", true)) |
| { |
| cat->emplace({ { "id", "OBSLTE" }, |
| { "date", date }, |
| { "replace_pdb_id", old }, |
| { "pdb_id", i } }); |
| } |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| Match("TITLE ", false); |
| std::string title; |
| if (mRec->is("TITLE ")) |
| { |
| title = vS(11); |
| GetNextRecord(); |
| } |
|
|
| |
| if (mRec->is("SPLIT ")) |
| { |
| |
| |
| |
|
|
| throw std::runtime_error("SPLIT PDB files are not supported"); |
| } |
|
|
| |
| int caveatID = 1; |
| while (mRec->is("CAVEAT")) |
| { |
| |
| getCategory("database_PDB_caveat")->emplace({ |
| { "id", caveatID++ }, |
| { "text", std::string{ mRec->vS(20) } } |
| }); |
| |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| Match("COMPND", false); |
| |
| |
| |
| |
|
|
| if (mRec->is("COMPND")) |
| { |
| std::string value{ mRec->vS(11) }; |
| if (value.find(':') == std::string::npos) |
| { |
| |
| auto &comp = GetOrCreateCompound(1); |
| comp.mInfo["MOLECULE"] = value; |
| } |
| else |
| { |
| SpecificationListParser p(value); |
|
|
| for (;;) |
| { |
| std::string key, val; |
| std::tie(key, val) = p.GetNextSpecification(); |
|
|
| if (key.empty()) |
| break; |
|
|
| if (not iequals(key, "MOL_ID") and mCompounds.empty()) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Ignoring invalid COMPND record\n"; |
| break; |
| } |
|
|
| if (key == "MOL_ID") |
| { |
| auto &comp = GetOrCreateCompound(stoi(val)); |
| comp.mTitle = title; |
| } |
| else if (key == "CHAIN") |
| { |
| for (auto c : cif::split<std::string>(val, ",")) |
| { |
| cif::trim(c); |
| mCompounds.back().mChains.insert(c[0]); |
| } |
| } |
| else |
| mCompounds.back().mInfo[key] = val; |
| } |
| } |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| Match("SOURCE", false); |
|
|
| if (mRec->is("SOURCE")) |
| { |
| |
| |
| |
| |
|
|
| std::map<std::string, std::string> *source = nullptr; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| SpecificationListParser p(vS(11)); |
|
|
| for (;;) |
| { |
| std::string key, val; |
| std::tie(key, val) = p.GetNextSpecification(); |
|
|
| if (key.empty()) |
| break; |
|
|
| if (key == "MOL_ID") |
| { |
| for (auto &c : mCompounds) |
| { |
| if (c.mMolID == stoi(val)) |
| { |
| source = &c.mSource; |
| break; |
| } |
| } |
|
|
| continue; |
| } |
|
|
| if (source == nullptr) |
| throw std::runtime_error("At line " + std::to_string(mRec->mLineNr) + ": missing MOL_ID in SOURCE"); |
|
|
| (*source)[key] = val; |
| } |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| Match("KEYWDS", false); |
| std::string pdbxKeywords; |
|
|
| if (mRec->is("KEYWDS")) |
| { |
| pdbxKeywords = vS(11); |
| |
| GetNextRecord(); |
| } |
|
|
| if (not(keywords.empty() and pdbxKeywords.empty())) |
| { |
| |
| getCategory("struct_keywords")->emplace({ |
| { "entry_id", mStructureID }, |
| { "pdbx_keywords", keywords }, |
| { "text", pdbxKeywords } |
| }); |
| |
| } |
|
|
| |
| Match("EXPDTA", false); |
| if (mRec->is("EXPDTA")) |
| { |
| mExpMethod = vS(11); |
|
|
| cat = getCategory("exptl"); |
|
|
| auto crystals = cif::split<std::string>(mRemark200["NUMBER OF CRYSTALS USED"], "; "); |
| if (crystals.empty()) |
| crystals.push_back(""); |
| auto ci = crystals.begin(); |
|
|
| for (auto expMethod : cif::split<std::string>(mExpMethod, ";")) |
| { |
| cif::trim(expMethod); |
|
|
| if (expMethod.empty()) |
| continue; |
|
|
| |
| cat->emplace({ |
| { "entry_id", mStructureID }, |
| { "method", expMethod }, |
| { "crystals_number", ci != crystals.end() ? *ci : "" } |
| }); |
| |
| } |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| if (mRec->is("NUMMDL")) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "skipping unimplemented NUMMDL record\n"; |
| GetNextRecord(); |
| } |
|
|
| |
| if (mRec->is("MDLTYP")) |
| { |
| mModelTypeDetails = vS(11); |
| GetNextRecord(); |
| } |
|
|
| |
| Match("AUTHOR", false); |
| if (mRec->is("AUTHOR")) |
| { |
| int n = 1; |
| cat = getCategory("audit_author"); |
|
|
| std::string value = { mRec->vS(11) }; |
| for (auto author : cif::split<std::string>(value, ",", true)) |
| { |
| |
| cat->emplace({ |
| { "name", pdb2cifAuth(author) }, |
| { "pdbx_ordinal", n } |
| }); |
| |
| ++n; |
| } |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| bool firstRevDat = true; |
| struct RevDat |
| { |
| int revNum; |
| std::string date, dateOriginal, replaces; |
| int modType; |
| std::vector<std::string> types; |
|
|
| bool operator<(const RevDat &rhs) const { return revNum < rhs.revNum; } |
| }; |
| std::vector<RevDat> revdats; |
|
|
| while (mRec->is("REVDAT")) |
| { |
| |
| int revNum = vI(8, 10); |
| |
| std::string date = pdb2cifDate(vS(14, 22)); |
| |
| |
| std::string modID = vS(24, 27); |
| |
| int modType = vI(32, 32); |
| |
| |
| std::string detail = vS(40); |
| |
| |
| |
|
|
| revdats.push_back({ revNum, date, modType == 0 ? mOriginalDate : "", modID, modType }); |
|
|
| revdats.back().types = cif::split<std::string>(detail, " "); |
|
|
| if (firstRevDat) |
| { |
| |
| getCategory("database_2")->emplace({ |
| { "database_id", "PDB" }, |
| { "database_code", modID } |
| }); |
| |
| } |
|
|
| GetNextRecord(); |
| firstRevDat = false; |
| } |
|
|
| |
| |
| |
| sort(revdats.begin(), revdats.end()); |
| for (auto &revdat : revdats) |
| { |
| |
| getCategory("database_PDB_rev")->emplace({ |
| { "num", revdat.revNum }, |
| { "date", revdat.date }, |
| { "date_original", revdat.dateOriginal }, |
| { "replaces", revdat.replaces }, |
| { "mod_type", revdat.modType } |
| }); |
| |
|
|
| for (auto &type : revdat.types) |
| { |
| if (type.empty()) |
| continue; |
|
|
| |
| getCategory("database_PDB_rev_record")->emplace({ |
| { "rev_num", revdat.revNum }, |
| { "type", type } |
| }); |
| |
| } |
| } |
| |
|
|
| |
| if (mRec->is("SPRSDE")) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "skipping unimplemented SPRSDE record\n"; |
| GetNextRecord(); |
| } |
|
|
| |
| if (mRec->is("JRNL ")) |
| ParseCitation("primary"); |
| } |
|
|
| void PDBFileParser::ParseCitation(const std::string &id) |
| { |
| const char *rec = mRec->mName; |
|
|
| std::string auth, titl, edit, publ, refn, pmid, doi; |
| std::string pubname, volume, astm, country, issn, csd; |
| std::string pageFirst; |
| int year = 0; |
|
|
| auto extend = [](std::string &s, const std::string &p) |
| { |
| if (not s.empty()) |
| s += ' '; |
| s += cif::trim_copy(p); |
| }; |
|
|
| while (mRec->is(rec) and (id == "primary" or vC(12) == ' ')) |
| { |
| std::string k = vS(13, 16); |
| if (k == "AUTH") |
| extend(auth, vS(20, 79)); |
| else if (k == "TITL") |
| extend(titl, vS(20, 79)); |
| else if (k == "EDIT") |
| extend(edit, vS(20, 79)); |
| else if (k == "REF") |
| { |
| if (pubname.empty()) |
| { |
| extend(pubname, vS(20, 47)); |
| if (vS(50, 51) == "V.") |
| volume = cif::trim_copy(vS(52, 55)); |
| pageFirst = vS(57, 61); |
| year = vI(63, 66); |
| } |
| else |
| extend(pubname, vS(20, 47)); |
| } |
| else if (k == "PUBL") |
| extend(publ, vS(20, 70)); |
| else if (k == "REFN") |
| { |
| if (vS(20, 23) == "ASTN") |
| astm = vS(25, 30); |
| country = vS(33, 34); |
| if (vS(36, 39) == "ISSN") |
| issn = vS(41, 65); |
| } |
| else if (k == "PMID") |
| pmid = vS(20, 79); |
| else if (k == "DOI") |
| doi = vS(20, 79); |
|
|
| GetNextRecord(); |
| } |
|
|
| auto cat = getCategory("citation"); |
| |
| cat->emplace({ |
| { "id", id }, |
| { "title", titl }, |
| { "journal_abbrev", pubname }, |
| { "journal_volume", volume }, |
| { "page_first", pageFirst }, |
| { "year", year > 0 ? std::to_string(year) : "" }, |
| { "journal_id_ASTM", astm }, |
| { "country", country }, |
| { "journal_id_ISSN", issn }, |
| { "journal_id_CSD", csd }, |
| { "book_publisher", publ }, |
| { "pdbx_database_id_PubMed", pmid }, |
| { "pdbx_database_id_DOI", doi } |
| }); |
| |
|
|
| if (not auth.empty()) |
| { |
| cat = getCategory("citation_author"); |
| for (auto author : cif::split<std::string>(auth, ",", true)) |
| { |
| cat->emplace({ { "citation_id", id }, |
| { "name", pdb2cifAuth(author) }, |
| { "ordinal", mCitationAuthorNr } }); |
|
|
| ++mCitationAuthorNr; |
| } |
| } |
|
|
| if (not edit.empty()) |
| { |
| cat = getCategory("citation_editor"); |
| for (auto editor : cif::split<std::string>(edit, ",", true)) |
| { |
| cat->emplace({ { "citation_id", id }, |
| { "name", pdb2cifAuth(editor) }, |
| { "ordinal", mCitationEditorNr } }); |
|
|
| ++mCitationEditorNr; |
| } |
| } |
| } |
|
|
| void PDBFileParser::ParseRemarks() |
| { |
| std::string sequenceDetails, compoundDetails, sourceDetails; |
|
|
| while (cif::starts_with(mRec->mName, "REMARK")) |
| { |
| int remarkNr = vI(8, 10); |
|
|
| try |
| { |
| switch (remarkNr) |
| { |
| case 1: |
| while (mRec->is("REMARK 1")) |
| { |
| if (mRec->mVlen > 15 and vS(12, 20) == "REFERENCE") |
| { |
| std::string id = vS(22, 70); |
| GetNextRecord(); |
|
|
| ParseCitation(id); |
| } |
| else |
| GetNextRecord(); |
| } |
| break; |
|
|
| case 3: |
| |
| while (mRec->is("REMARK 3")) |
| GetNextRecord(); |
| break; |
|
|
| case 4: |
| |
| while (mRec->is("REMARK 4")) |
| GetNextRecord(); |
| break; |
|
|
| case 100: |
| { |
| const std::regex rx(R"(THE (\S+) ID CODE IS (\S+?)\.?\s*)"); |
| std::smatch m; |
| std::string r = vS(12); |
|
|
| if (std::regex_match(r, m, rx)) |
| { |
| auto cat = getCategory("database_2"); |
| cat->emplace({ { "database_id", m[1].str() }, |
| { "database_code", m[2].str() } }); |
| } |
|
|
| GetNextRecord(); |
| break; |
| } |
|
|
| case 200: |
| { |
| |
|
|
| bool remark = false; |
|
|
| do |
| { |
| std::string r = mRec->vS(12); |
|
|
| if (cif::starts_with(r, "REMARK: ")) |
| { |
| mRemark200["REMARK"] = r.substr(8); |
| remark = true; |
| } |
| else if (remark) |
| { |
| if (r.empty()) |
| remark = false; |
| else |
| mRemark200["REMARK"] += r; |
| } |
|
|
| GetNextRecord(); |
| } while (mRec->is("REMARK 200")); |
| break; |
| } |
|
|
| case 280: |
| { |
| std::string density_Matthews, densityPercentSol, conditions; |
|
|
| const std::regex rx1(R"(SOLVENT CONTENT, VS +\(%\): *(.+))"), |
| rx2(R"(MATTHEWS COEFFICIENT, VM \(ANGSTROMS\*\*3/DA\): *(.+))"); |
|
|
| std::smatch m; |
|
|
| do |
| { |
| std::string r = vS(12); |
|
|
| if (conditions.empty()) |
| { |
| if (std::regex_match(r, m, rx1)) |
| densityPercentSol = m[1].str(); |
| else if (std::regex_match(r, m, rx2)) |
| density_Matthews = m[1].str(); |
| else if (cif::starts_with(r, "CRYSTALLIZATION CONDITIONS: ")) |
| conditions = r.substr(28); |
| } |
| else |
| conditions = conditions + ' ' + r; |
|
|
| GetNextRecord(); |
| } while (mRec->is("REMARK 280")); |
|
|
| std::string desc = mRemark200["REMARK"]; |
| if (desc == "NULL") |
| desc.clear(); |
|
|
| |
| getCategory("exptl_crystal")->emplace({ |
| { "id", 1 }, |
| { "density_Matthews", iequals(density_Matthews, "NULL") ? "" : density_Matthews }, |
| { "density_percent_sol", iequals(densityPercentSol, "NULL") ? "" : densityPercentSol }, |
| { "description", desc } |
| }); |
| |
|
|
| |
| const std::regex rx3(R"(TEMPERATURE +(\d+)K)"), rx4(R"(PH *(?:: *)?(\d+(?:\.\d+)?))") ; |
|
|
| std::string temp, ph, method; |
|
|
| for (auto s : cif::split<std::string>(conditions, ",", true)) |
| { |
| cif::trim(s); |
|
|
| if (std::regex_search(s, m, rx3)) |
| temp = m[1].str(); |
| if (std::regex_search(s, m, rx4)) |
| ph = m[1].str(); |
| if (s.length() < 60 and |
| (cif::icontains(s, "drop") or cif::icontains(s, "vapor") or cif::icontains(s, "batch"))) |
| { |
| if (not method.empty()) |
| method = method + ", " + s; |
| else |
| method = s; |
| } |
| } |
|
|
| if (not(method.empty() and temp.empty() and ph.empty() and (conditions.empty() or conditions == "NULL"))) |
| { |
| |
| getCategory("exptl_crystal_grow")->emplace({ |
| { "crystal_id", 1 }, |
| { "method", method }, |
| { "temp", temp }, |
| { "pH", ph }, |
| { "pdbx_details", conditions } |
| }); |
| |
| } |
|
|
| break; |
| } |
|
|
| |
| |
| |
|
|
| case 350: |
| |
| for (; mRec->is("REMARK 350"); GetNextRecord()) |
| ; |
| break; |
|
|
| case 400: |
| { |
| std::stringstream s; |
| GetNextRecord(); |
| if (vS(12) == "COMPOUND") |
| GetNextRecord(); |
|
|
| while (mRec->is("REMARK 400")) |
| { |
| s << vS(12) << '\n'; |
| GetNextRecord(); |
| } |
|
|
| compoundDetails = s.str(); |
| break; |
| } |
|
|
| case 450: |
| { |
| std::stringstream s; |
| GetNextRecord(); |
| if (vS(12) == "SOURCE") |
| GetNextRecord(); |
|
|
| while (mRec->is("REMARK 450")) |
| { |
| s << vS(12) << '\n'; |
| GetNextRecord(); |
| } |
|
|
| sourceDetails = s.str(); |
| break; |
| } |
|
|
| case 465: |
| { |
| bool headerSeen = false; |
| std::regex rx(R"( *MODELS *(\d+)-(\d+))"); |
| int models[2] = { -1, -1 }; |
|
|
| for (; mRec->is("REMARK 465"); GetNextRecord()) |
| { |
| if (not headerSeen) |
| { |
| std::string line = vS(12); |
| std::smatch m; |
|
|
| if (std::regex_match(line, m, rx)) |
| { |
| models[0] = std::stoi(m[1].str()); |
| models[1] = stoi(m[2].str()); |
| } |
| else |
| headerSeen = cif::contains(line, "RES C SSSEQI"); |
| continue; |
| } |
|
|
| if (models[0] == models[1]) |
| models[0] = models[1] = vI(12, 14); |
|
|
| std::string res = vS(16, 18); |
| char chain = vC(20); |
| int seq = vI(22, 26); |
| char iCode = vC(27); |
|
|
| for (int modelNr = models[0]; modelNr <= models[1]; ++modelNr) |
| mUnobs.push_back({ modelNr, res, chain, seq, iCode }); |
| } |
|
|
| break; |
| } |
|
|
| case 470: |
| { |
| bool headerSeen = false; |
| std::regex rx(R"( *MODELS *(\d+)-(\d+))"); |
| int models[2] = { -1, -1 }; |
|
|
| for (; mRec->is("REMARK 470"); GetNextRecord()) |
| { |
| if (not headerSeen) |
| { |
| std::string line = vS(12); |
| std::smatch m; |
|
|
| if (std::regex_match(line, m, rx)) |
| { |
| models[0] = stoi(m[1].str()); |
| models[1] = stoi(m[2].str()); |
| } |
| else |
| headerSeen = cif::contains(line, "RES CSSEQI ATOMS"); |
| continue; |
| } |
|
|
| if (models[0] == models[1]) |
| models[0] = models[1] = vI(12, 14); |
|
|
| std::string res = vS(16, 18); |
| char chain = vC(20); |
| int seq = vI(21, 24); |
| char iCode = vC(25); |
|
|
| std::string atomStr = mRec->vS(29); |
| auto atoms = cif::split<std::string>(atomStr, " ", true); |
|
|
| for (int modelNr = models[0]; modelNr <= models[1]; ++modelNr) |
| mUnobs.push_back({ modelNr, res, chain, seq, iCode, atoms }); |
| } |
|
|
| break; |
| } |
|
|
| case 500: |
| { |
| GetNextRecord(); |
|
|
| enum State |
| { |
| eStart, |
| eCCinSAU, |
| eCC, |
| eCBL, |
| eCBA, |
| eTA, |
| eCTg, |
| ePG, |
| eMCP, |
| eChC |
| } state = eStart; |
| bool headerSeen = false; |
| int id = 0; |
|
|
| for (; mRec->is("REMARK 500"); GetNextRecord()) |
| { |
| std::string line = vS(12); |
|
|
| if (line == "GEOMETRY AND STEREOCHEMISTRY") |
| continue; |
|
|
| switch (state) |
| { |
| case eStart: |
| { |
| if (line.empty() or not cif::starts_with(line, "SUBTOPIC: ")) |
| continue; |
|
|
| std::string subtopic = line.substr(10); |
|
|
| if (subtopic == "CLOSE CONTACTS IN SAME ASYMMETRIC UNIT") |
| state = eCCinSAU; |
| else if (subtopic == "CLOSE CONTACTS") |
| state = eCC; |
| else if (subtopic == "COVALENT BOND LENGTHS") |
| state = eCBL; |
| else if (subtopic == "COVALENT BOND ANGLES") |
| state = eCBA; |
| else if (subtopic == "TORSION ANGLES") |
| state = eTA; |
| else if (subtopic == "NON-CIS, NON-TRANS") |
| state = eCTg; |
| else if (subtopic == "PLANAR GROUPS") |
| state = ePG; |
| else if (subtopic == "MAIN CHAIN PLANARITY") |
| state = eMCP; |
| else if (subtopic == "CHIRAL CENTERS") |
| state = eChC; |
| else if (cif::VERBOSE > 0) |
| throw std::runtime_error("Unknown subtopic in REMARK 500: " + subtopic); |
|
|
| headerSeen = false; |
| id = 0; |
| break; |
| } |
|
|
| case eCCinSAU: |
| { |
| if (not headerSeen) |
| headerSeen = |
| line == "ATM1 RES C SSEQI ATM2 RES C SSEQI DISTANCE"; |
| else if (line.empty()) |
| state = eStart; |
| else |
| { |
| std::string atom1 = vS(13, 16); |
| std::string res1 = vS(19, 21); |
| std::string alt1 = vS(17, 17); |
| char chain1 = vC(23); |
| int seq1 = vI(25, 29); |
| std::string iCode1 = vS(30, 30); |
|
|
| std::string atom2 = vS(34, 37); |
| std::string alt2 = vS(38, 38); |
| std::string res2 = vS(40, 42); |
| char chain2 = vC(44); |
| int seq2 = vI(46, 50); |
| std::string iCode2 = vS(51, 51); |
|
|
| std::string distance = vF(63, 71); |
|
|
| |
| getCategory("pdbx_validate_close_contact")->emplace({ |
| { "id", std::to_string(++id) }, |
| { "PDB_model_num", 1 }, |
| { "auth_atom_id_1", atom1 }, |
| { "auth_asym_id_1", std::string{ chain1 } }, |
| { "auth_comp_id_1", res1 }, |
| { "auth_seq_id_1", seq1 }, |
| { "PDB_ins_code_1", iCode1 }, |
| { "label_alt_id_1", alt1 }, |
| { "auth_atom_id_2", atom2 }, |
| { "auth_asym_id_2", std::string{ chain2 } }, |
| { "auth_comp_id_2", res2 }, |
| { "auth_seq_id_2", seq2 }, |
| { "PDB_ins_code_2", iCode2 }, |
| { "label_alt_id_2", alt2 }, |
| { "dist", distance } |
| }); |
| |
| } |
| break; |
| } |
|
|
| case eCC: |
| { |
| if (not headerSeen) |
| headerSeen = line == "ATM1 RES C SSEQI ATM2 RES C SSEQI SSYMOP DISTANCE"; |
| else if (line.empty()) |
| state = eStart; |
| else |
| { |
| std::string atom1 = vS(13, 16); |
| std::string res1 = vS(19, 21); |
| char chain1 = vC(23); |
| int seq1 = vI(25, 29); |
|
|
| std::string atom2 = vS(34, 37); |
| std::string res2 = vS(40, 42); |
| char chain2 = vC(44); |
| int seq2 = vI(46, 50); |
|
|
| std::string symop; |
| try |
| { |
| symop = pdb2cifSymmetry(vS(54, 59)); |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Dropping REMARK 500 at line " << mRec->mLineNr << " due to invalid symmetry operation\n"; |
| continue; |
| } |
|
|
| std::string distance = vF(63, 71); |
|
|
| |
| getCategory("pdbx_validate_symm_contact")->emplace({ |
| { "id", std::to_string(++id) }, |
| { "PDB_model_num", 1 }, |
| { "auth_atom_id_1", atom1 }, |
| { "auth_asym_id_1", std::string{ chain1 } }, |
| { "auth_comp_id_1", res1 }, |
| { "auth_seq_id_1", seq1 }, |
| |
| |
| { "site_symmetry_1", "1_555" }, |
| { "auth_atom_id_2", atom2 }, |
| { "auth_asym_id_2", std::string{ chain2 } }, |
| { "auth_comp_id_2", res2 }, |
| { "auth_seq_id_2", seq2 }, |
| |
| |
| { "site_symmetry_2", symop }, |
| { "dist", distance } |
| }); |
| |
| } |
| break; |
| } |
|
|
| case eCBL: |
| { |
| if (not headerSeen) |
| { |
| if (cif::starts_with(line, "FORMAT: ") and line != "FORMAT: (10X,I3,1X,2(A3,1X,A1,I4,A1,1X,A4,3X),1X,F6.3)") |
| throw std::runtime_error("Unexpected format in REMARK 500"); |
|
|
| headerSeen = line == "M RES CSSEQI ATM1 RES CSSEQI ATM2 DEVIATION"; |
| } |
| else if (line.empty()) |
| state = eStart; |
| else |
| { |
| int model = vI(11, 13); |
| std::string resNam1 = vS(15, 17); |
| std::string chainID1{ vC(19) }; |
| int seqNum1 = vI(20, 23); |
| std::string iCode1{ vC(24) }; |
| std::string alt1 = vS(30, 30); |
| std::string atm1 = vS(26, 29); |
|
|
| std::string resNam2 = vS(33, 35); |
| std::string chainID2{ vC(37) }; |
| int seqNum2 = vI(38, 41); |
| std::string iCode2{ vC(42) }; |
| std::string alt2 = vS(48, 48); |
| std::string atm2 = vS(44, 47); |
|
|
| std::string deviation = vF(51, 57); |
|
|
| if (iCode1 == " ") |
| iCode1.clear(); |
| if (iCode2 == " ") |
| iCode2.clear(); |
|
|
| |
| getCategory("pdbx_validate_rmsd_bond")->emplace({ |
| { "id", std::to_string(++id) }, |
| { "PDB_model_num", model ? model : 1 }, |
| { "auth_atom_id_1", atm1 }, |
| { "auth_asym_id_1", chainID1 }, |
| { "auth_comp_id_1", resNam1 }, |
| { "auth_seq_id_1", seqNum1 }, |
| { "PDB_ins_code_1", iCode1 }, |
| { "label_alt_id_1", alt1 }, |
| { "auth_atom_id_2", atm2 }, |
| { "auth_asym_id_2", chainID2 }, |
| { "auth_comp_id_2", resNam2 }, |
| { "auth_seq_id_2", seqNum2 }, |
| { "PDB_ins_code_2", iCode2 }, |
| { "label_alt_id_2", alt2 }, |
| { "bond_deviation", deviation } |
| }); |
| |
| } |
|
|
| break; |
| } |
|
|
| case eCBA: |
| if (not headerSeen) |
| { |
| if (cif::starts_with(line, "FORMAT: ") and line != "FORMAT: (10X,I3,1X,A3,1X,A1,I4,A1,3(1X,A4,2X),12X,F5.1)") |
| throw std::runtime_error("Unexpected format in REMARK 500"); |
|
|
| headerSeen = line == "M RES CSSEQI ATM1 ATM2 ATM3"; |
| } |
| else if (line.empty()) |
| state = eStart; |
| else if (vS(64) == "DEGREES") |
| { |
| int model = vI(11, 13); |
| std::string resNam = vS(15, 17); |
| std::string chainID{ vC(19) }; |
| int seqNum = vI(20, 23); |
| std::string iCode{ vC(24) }; |
|
|
| if (iCode == " ") |
| iCode.clear(); |
|
|
| std::string atoms[3] = { vS(27, 30), vS(34, 37), vS(41, 44) }; |
| std::string deviation = vF(57, 62); |
| if (deviation == "*****") |
| deviation.clear(); |
|
|
| |
| getCategory("pdbx_validate_rmsd_angle")->emplace({ |
| { "id", std::to_string(++id) }, |
| { "PDB_model_num", model ? model : 1 }, |
| { "auth_atom_id_1", atoms[0] }, |
| { "auth_asym_id_1", chainID }, |
| { "auth_comp_id_1", resNam }, |
| { "auth_seq_id_1", seqNum }, |
| { "PDB_ins_code_1", iCode }, |
| { "auth_atom_id_2", atoms[1] }, |
| { "auth_asym_id_2", chainID }, |
| { "auth_comp_id_2", resNam }, |
| { "auth_seq_id_2", seqNum }, |
| { "PDB_ins_code_2", iCode }, |
| { "auth_atom_id_3", atoms[2] }, |
| { "auth_asym_id_3", chainID }, |
| { "auth_comp_id_3", resNam }, |
| { "auth_seq_id_3", seqNum }, |
| { "PDB_ins_code_3", iCode }, |
| { "angle_deviation", deviation } |
| }); |
| |
| } |
|
|
| break; |
|
|
| case eTA: |
| if (not headerSeen) |
| { |
| if (cif::starts_with(line, "FORMAT: ") and line != "FORMAT:(10X,I3,1X,A3,1X,A1,I4,A1,4X,F7.2,3X,F7.2)") |
| throw std::runtime_error("Unexpected format in REMARK 500"); |
|
|
| headerSeen = line == "M RES CSSEQI PSI PHI"; |
| } |
| else if (line.empty()) |
| state = eStart; |
| else |
| { |
| int model = vI(11, 13); |
| std::string resNam = vS(15, 17); |
| std::string chainID{ vC(19) }; |
| int seqNum = vI(20, 23); |
| std::string iCode{ vC(24) }; |
|
|
| if (iCode == " ") |
| iCode.clear(); |
|
|
| std::string psi = vF(27, 35); |
| std::string phi = vF(37, 45); |
|
|
| |
| getCategory("pdbx_validate_torsion")->emplace({ |
| { "id", std::to_string(++id) }, |
| { "PDB_model_num", model ? model : 1 }, |
| { "auth_comp_id", resNam }, |
| { "auth_asym_id", chainID }, |
| { "auth_seq_id", seqNum }, |
| { "PDB_ins_code", iCode }, |
| { "phi", phi }, |
| { "psi", psi } |
| }); |
| |
| } |
| break; |
|
|
| case eCTg: |
| if (not headerSeen) |
| headerSeen = line == "MODEL OMEGA"; |
| else if (line.empty()) |
| state = eStart; |
| else |
| { |
| int model = vI(45, 48); |
|
|
| std::string resNam1 = vS(12, 14); |
| std::string chainID1{ vC(16) }; |
| int seqNum1 = vI(17, 21); |
| std::string iCode1{ vC(22) }; |
|
|
| if (iCode1 == " ") |
| iCode1.clear(); |
|
|
| std::string resNam2 = vS(27, 29); |
| std::string chainID2{ vC(31) }; |
| int seqNum2 = vI(32, 36); |
| std::string iCode2{ vC(37) }; |
|
|
| if (iCode2 == " ") |
| iCode2.clear(); |
|
|
| std::string omega = vF(54, 60); |
|
|
| |
| getCategory("pdbx_validate_peptide_omega")->emplace({ |
| { "id", std::to_string(++id) }, |
| { "PDB_model_num", model ? model : 1 }, |
| { "auth_comp_id_1", resNam1 }, |
| { "auth_asym_id_1", chainID1 }, |
| { "auth_seq_id_1", seqNum1 }, |
| { "PDB_ins_code_1", iCode1 }, |
| { "auth_comp_id_2", resNam2 }, |
| { "auth_asym_id_2", chainID2 }, |
| { "auth_seq_id_2", seqNum2 }, |
| { "PDB_ins_code_2", iCode2 }, |
| { "omega", omega } |
| }); |
| |
| } |
| break; |
|
|
| case ePG: |
| if (not headerSeen) |
| headerSeen = line == "M RES CSSEQI RMS TYPE"; |
| else if (line.empty()) |
| state = eStart; |
| else |
| { |
| int model = vI(11, 13); |
| std::string resNam = vS(15, 17); |
| std::string chainID{ vC(19) }; |
| int seqNum = vI(20, 23); |
| std::string iCode{ vC(24) }; |
|
|
| if (iCode == " ") |
| iCode.clear(); |
|
|
| std::string rmsd = vF(32, 36); |
| std::string type = vS(41); |
|
|
| |
| getCategory("pdbx_validate_planes")->emplace({ |
| { "id", std::to_string(++id) }, |
| { "PDB_model_num", model ? model : 1 }, |
| { "auth_comp_id", resNam }, |
| { "auth_asym_id", chainID }, |
| { "auth_seq_id", seqNum }, |
| { "PDB_ins_code", iCode }, |
| { "rmsd", rmsd }, |
| { "type", type } |
| }); |
| |
| } |
| break; |
|
|
| default: |
| state = eStart; |
| break; |
| } |
| } |
|
|
| break; |
| } |
|
|
| case 610: |
| { |
| bool headerSeen = false; |
|
|
| for (; mRec->is("REMARK 610"); GetNextRecord()) |
| { |
| if (not headerSeen) |
| { |
| std::string line = vS(12); |
| headerSeen = cif::contains(line, "RES C SSEQI"); |
| continue; |
| } |
|
|
| int modelNr = vI(12, 14); |
| if (modelNr == 0) |
| modelNr = 1; |
| std::string res = vS(16, 18); |
| char chain = vC(20); |
| int seq = vI(22, 25); |
| char iCode = vC(26); |
|
|
| auto compound = cif::compound_factory::instance().create(res); |
| if (compound == nullptr) |
| continue; |
|
|
| std::vector<std::string> atoms; |
| for (auto atom : compound->atoms()) |
| { |
| if (atom.type_symbol != cif::H) |
| atoms.push_back(atom.id); |
| } |
|
|
| mUnobs.push_back({ modelNr, res, chain, seq, iCode, { atoms } }); |
| } |
|
|
| break; |
| } |
|
|
| case 800: |
| { |
| const std::regex rx1(R"(SITE_IDENTIFIER: (.+))"), |
| rx2(R"(EVIDENCE_CODE: (.+))"), |
| rx3(R"(SITE_DESCRIPTION: (binding site for residue ([[:alnum:]]{1,3}) ([[:alnum:]]) (\d+)|.+))", std::regex_constants::icase); |
|
|
| std::string id, evidence, desc; |
| std::string pdbxAuthAsymID, pdbxAuthCompID, pdbxAuthSeqID, pdbxAuthInsCode; |
| std::smatch m; |
|
|
| enum State |
| { |
| sStart, |
| sID, |
| sEvidence, |
| sDesc, |
| sDesc2 |
| } state = sStart; |
|
|
| auto store = [&]() |
| { |
| |
| auto site = FindRecord([id](PDBRecord &r) -> bool |
| { return r.is("SITE ") and r.vS(12, 14) == id; }); |
|
|
| if (site == nullptr) |
| throw std::runtime_error("Invalid REMARK 800, no SITE record for id " + id); |
|
|
| |
| |
| getCategory("struct_site")->emplace({ |
| { "id", id }, |
| { "details", desc }, |
| { "pdbx_auth_asym_id", pdbxAuthAsymID }, |
| { "pdbx_auth_comp_id", pdbxAuthCompID }, |
| { "pdbx_auth_seq_id", pdbxAuthSeqID }, |
| { "pdbx_num_residues", site->vI(16, 17) }, |
| { "pdbx_evidence_code", evidence } |
| }); |
| |
| }; |
|
|
| for (; mRec->is("REMARK 800"); GetNextRecord()) |
| { |
| std::string s = mRec->vS(12); |
| if (s.empty()) |
| continue; |
|
|
| switch (state) |
| { |
| case sStart: |
| if (s == "SITE") |
| state = sID; |
| else if (cif::VERBOSE > 0) |
| throw std::runtime_error("Invalid REMARK 800 record, expected SITE"); |
| break; |
|
|
| case sID: |
| if (std::regex_match(s, m, rx1)) |
| { |
| id = m[1].str(); |
| state = sEvidence; |
| } |
| else if (cif::VERBOSE > 0) |
| throw std::runtime_error("Invalid REMARK 800 record, expected SITE_IDENTIFIER"); |
| break; |
|
|
| case sEvidence: |
| if (regex_match(s, m, rx2)) |
| { |
| evidence = m[1].str(); |
| state = sDesc; |
| } |
| else if (cif::VERBOSE > 0) |
| throw std::runtime_error("Invalid REMARK 800 record, expected SITE_IDENTIFIER"); |
| break; |
|
|
| case sDesc: |
| if (regex_match(s, m, rx3)) |
| { |
| desc = m[1].str(); |
| pdbxAuthCompID = m[2].str(); |
| pdbxAuthAsymID = m[3].str(); |
| pdbxAuthSeqID = m[4].str(); |
|
|
| state = sDesc2; |
| } |
| break; |
|
|
| case sDesc2: |
| if (regex_match(s, m, rx1)) |
| { |
| store(); |
|
|
| id = m[1].str(); |
| state = sEvidence; |
| evidence.clear(); |
| desc.clear(); |
| } |
| else |
| desc = desc + ' ' + s; |
| break; |
| } |
| } |
|
|
| if (not id.empty()) |
| store(); |
|
|
| break; |
| } |
|
|
| case 999: |
| { |
| std::stringstream s; |
| GetNextRecord(); |
| if (vS(12) == "SEQUENCE") |
| GetNextRecord(); |
|
|
| while (mRec->is("REMARK 999")) |
| { |
| s << vS(12) << '\n'; |
| GetNextRecord(); |
| } |
|
|
| sequenceDetails = s.str(); |
| break; |
| } |
|
|
| |
|
|
| case 2: |
| case 290: |
| case 300: |
| case 620: |
| GetNextRecord(); |
| break; |
|
|
| default: |
| { |
| std::string skipped = mRec->mName; |
|
|
| std::stringstream s; |
|
|
| if (not mRec->vS(11).empty()) |
| s << mRec->vS(11) << '\n'; |
| GetNextRecord(); |
|
|
| while (mRec->is(skipped.c_str())) |
| { |
| s << mRec->vS(11) << '\n'; |
| GetNextRecord(); |
| } |
|
|
| |
| getCategory("pdbx_database_remark")->emplace({ |
| { "id", remarkNr }, |
| { "text", s.str() } |
| }); |
| |
|
|
| break; |
| } |
| } |
| } |
| catch (const std::exception &ex) |
| { |
| std::throw_with_nested(std::runtime_error("Error parsing REMARK " + std::to_string(remarkNr))); |
| } |
| } |
|
|
| if (not(compoundDetails.empty() and sequenceDetails.empty() and sourceDetails.empty())) |
| { |
| |
| getCategory("pdbx_entry_details")->emplace({ |
| { "entry_id", mStructureID }, |
| { "compound_details", compoundDetails }, |
| { "sequence_details", sequenceDetails }, |
| { "source_details", sourceDetails } |
| }); |
| |
| } |
|
|
| |
| if (not mRemark200.empty()) |
| ParseRemark200(); |
| } |
|
|
| void PDBFileParser::ParseRemark200() |
| { |
| auto rm200 = [&](const char *name, int diffrnNr) -> std::string |
| { |
| int nr = 0; |
| std::string result; |
|
|
| for (auto s : cif::split<std::string>(mRemark200[name], ";")) |
| { |
| if (++nr != diffrnNr) |
| continue; |
|
|
| cif::trim(s); |
|
|
| if (s == "NULL") |
| s.clear(); |
|
|
| result = std::move(s); |
| break; |
| } |
|
|
| return result; |
| }; |
|
|
| auto inRM200 = [this](std::initializer_list<const char *> s) -> bool |
| { |
| bool result = false; |
|
|
| for (auto *n : s) |
| { |
| if (not this->mRemark200[n].empty()) |
| { |
| result = true; |
| break; |
| } |
| } |
|
|
| return result; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| struct |
| { |
| const char *a; |
| const char *b; |
| } kSWMap[] = { |
| { "data reduction", "INTENSITY-INTEGRATION SOFTWARE" }, |
| { "data scaling", "DATA SCALING SOFTWARE" }, |
| { "phasing", "SOFTWARE USED" }, |
| }; |
|
|
| for (auto &sw : kSWMap) |
| { |
| if (mRemark200[sw.b].empty()) |
| continue; |
|
|
| |
| getCategory("software")->emplace({ |
| { "name", mRemark200[sw.b] }, |
| { "classification", sw.a }, |
| { "version", "." }, |
| { "pdbx_ordinal", mNextSoftwareOrd++ } |
| }); |
| |
| } |
|
|
| std::string scatteringType; |
| if (mRemark200["EXPERIMENT TYPE"] == "X-RAY DIFFRACTION") |
| scatteringType = "x-ray"; |
| else if (mRemark200["EXPERIMENT TYPE"] == "NEUTRON DIFFRACTION") |
| scatteringType = "neutron"; |
|
|
| std::set<std::string> diffrnWaveLengths; |
|
|
| for (int diffrnNr = 1;; ++diffrnNr) |
| { |
| std::string ambientTemp = rm200("TEMPERATURE (KELVIN)", diffrnNr); |
| if (ambientTemp.empty()) |
| break; |
|
|
| if (cif::ends_with(ambientTemp, "K")) |
| ambientTemp.erase(ambientTemp.length() - 1, 1); |
|
|
| |
| getCategory("diffrn")->emplace({ |
| { "id", diffrnNr }, |
| { "ambient_temp", ambientTemp }, |
| |
| { "crystal_id", 1 } }); |
| |
|
|
| std::string collectionDate; |
| std::error_code ec; |
| collectionDate = pdb2cifDate(rm200("DATE OF DATA COLLECTION", diffrnNr), ec); |
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << ec.message() << " for pdbx_collection_date\n"; |
|
|
| |
| if (diffrnNr != 1) |
| collectionDate.clear(); |
| } |
|
|
| |
| getCategory("diffrn_detector")->emplace({ |
| { "diffrn_id", diffrnNr }, |
| { "detector", rm200("DETECTOR TYPE", diffrnNr) }, |
| { "type", rm200("DETECTOR MANUFACTURER", diffrnNr) }, |
| { "pdbx_collection_date", collectionDate }, |
| { "details", rm200("OPTICS", diffrnNr) } |
| }); |
| |
|
|
| if (inRM200({ "MONOCHROMATIC OR LAUE (M/L)", "MONOCHROMATOR", "DIFFRACTION PROTOCOL" }) or not scatteringType.empty()) |
| |
| getCategory("diffrn_radiation")->emplace({ |
| { "diffrn_id", diffrnNr }, |
| { "wavelength_id", 1 }, |
| { "pdbx_monochromatic_or_laue_m_l", rm200("MONOCHROMATIC OR LAUE (M/L)", diffrnNr) }, |
| { "monochromator", rm200("MONOCHROMATOR", diffrnNr) }, |
| { "pdbx_diffrn_protocol", rm200("DIFFRACTION PROTOCOL", diffrnNr) }, |
| { "pdbx_scattering_type", scatteringType } |
| }); |
| |
|
|
| std::string wl = rm200("WAVELENGTH OR RANGE (A)", diffrnNr); |
| auto wavelengths = cif::split<std::string>(wl, ", -", true); |
|
|
| diffrnWaveLengths.insert(wavelengths.begin(), wavelengths.end()); |
|
|
| std::string source; |
| if (rm200("SYNCHROTRON (Y/N)", diffrnNr) == "Y") |
| { |
| |
| getCategory("diffrn_source")->emplace({ |
| { "diffrn_id", diffrnNr }, |
| { "source", "SYNCHROTRON" }, |
| { "type", rm200("RADIATION SOURCE", diffrnNr) + " BEAMLINE " + rm200("BEAMLINE", diffrnNr) }, |
| { "pdbx_synchrotron_site", rm200("RADIATION SOURCE", diffrnNr) }, |
| { "pdbx_synchrotron_beamline", rm200("BEAMLINE", diffrnNr) }, |
|
|
| { "pdbx_wavelength", wavelengths.size() == 1 ? wavelengths[0] : "" }, |
| { "pdbx_wavelength_list", wavelengths.size() == 1 ? "" : cif::join(wavelengths, ", ") }, |
| }); |
| |
| } |
| else if (inRM200({ "X-RAY GENERATOR MODEL", "RADIATION SOURCE", "BEAMLINE", "WAVELENGTH OR RANGE (A)" })) |
| { |
| |
| getCategory("diffrn_source")->emplace({ |
| { "diffrn_id", diffrnNr }, |
| { "source", rm200("RADIATION SOURCE", diffrnNr) }, |
| { "type", rm200("X-RAY GENERATOR MODEL", diffrnNr) }, |
|
|
| { "pdbx_wavelength", wavelengths.size() == 1 ? wavelengths[0] : "" }, |
| { "pdbx_wavelength_list", wavelengths.size() == 1 ? "" : cif::join(wavelengths, ", ") }, |
| }); |
| |
| } |
| } |
|
|
| int wavelengthNr = 1; |
| for (auto wl : diffrnWaveLengths) |
| { |
| if (cif::ends_with(wl, "A")) |
| wl.erase(wl.length() - 1, 1); |
|
|
| |
| getCategory("diffrn_radiation_wavelength")->emplace({ |
| { "id", wavelengthNr++ }, |
| { "wavelength", wl.empty() ? "." : wl }, |
| { "wt", "1.0" } |
| }); |
| |
| } |
|
|
| if (inRM200({ "METHOD USED TO DETERMINE THE STRUCTURE", "STARTING MODEL" })) |
| { |
| auto cat = getCategory("refine"); |
| assert(cat->empty()); |
|
|
| std::string resolution = mRemark200["RESOLUTION RANGE HIGH (A)"]; |
| if (resolution.empty()) |
| resolution = "."; |
|
|
| |
| cat->emplace({ |
| { "pdbx_method_to_determine_struct", mRemark200["METHOD USED TO DETERMINE THE STRUCTURE"] }, |
| { "pdbx_starting_model", mRemark200["STARTING MODEL"] }, |
| { "ls_d_res_high", resolution }, |
| { "pdbx_diffrn_id", 1 }, |
| { "pdbx_refine_id", mExpMethod }, |
| { "entry_id", mStructureID } }); |
| |
| } |
|
|
| if (inRM200({ "REJECTION CRITERIA (SIGMA(I))", "RESOLUTION RANGE HIGH (A)", "RESOLUTION RANGE LOW (A)", "NUMBER OF UNIQUE REFLECTIONS", "COMPLETENESS FOR RANGE (%)", "<I/SIGMA(I)> FOR THE DATA SET", "R MERGE (I)", "R SYM (I)", "DATA REDUNDANCY" })) |
| { |
| auto cat = getCategory("reflns"); |
| |
| cat->emplace({ |
| { "entry_id", mStructureID }, |
| { "observed_criterion_sigma_I", mRemark200["REJECTION CRITERIA (SIGMA(I))"] }, |
| { "d_resolution_high", mRemark200["RESOLUTION RANGE HIGH (A)"] }, |
| { "d_resolution_low", mRemark200["RESOLUTION RANGE LOW (A)"] }, |
| { "number_obs", mRemark200["NUMBER OF UNIQUE REFLECTIONS"] }, |
| { "percent_possible_obs", mRemark200["COMPLETENESS FOR RANGE (%)"] }, |
| { "pdbx_netI_over_sigmaI", mRemark200["<I/SIGMA(I)> FOR THE DATA SET"] }, |
| { "pdbx_Rmerge_I_obs", mRemark200["R MERGE (I)"] }, |
| { "pdbx_Rsym_value", mRemark200["R SYM (I)"] }, |
| { "pdbx_redundancy", mRemark200["DATA REDUNDANCY"] }, |
| { "pdbx_ordinal", 1 }, |
| { "pdbx_diffrn_id", 1 } |
| }); |
| |
| } |
|
|
| if (inRM200({ "HIGHEST RESOLUTION SHELL, RANGE HIGH (A)" })) |
| { |
| |
| getCategory("reflns_shell")->emplace({ |
| { "d_res_high", mRemark200["HIGHEST RESOLUTION SHELL, RANGE HIGH (A)"] }, |
| { "d_res_low", mRemark200["HIGHEST RESOLUTION SHELL, RANGE LOW (A)"] }, |
| { "percent_possible_all", mRemark200["COMPLETENESS FOR SHELL (%)"] }, |
| { "Rmerge_I_obs", mRemark200["R MERGE FOR SHELL (I)"] }, |
| { "pdbx_Rsym_value", mRemark200["R SYM FOR SHELL (I)"] }, |
| { "meanI_over_sigI_obs", mRemark200["<I/SIGMA(I)> FOR SHELL"] }, |
| { "pdbx_redundancy", mRemark200["DATA REDUNDANCY IN SHELL"] }, |
| { "pdbx_ordinal", 1 }, |
| { "pdbx_diffrn_id", 1 } |
| }); |
| |
| } |
| else if (inRM200({ "HIGHEST RESOLUTION SHELL, RANGE LOW (A)", "COMPLETENESS FOR SHELL (%)", |
| "R MERGE FOR SHELL (I)", "R SYM FOR SHELL (I)", "<I/SIGMA(I)> FOR SHELL", "DATA REDUNDANCY IN SHELL" })) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Not writing reflns_shell record since d_res_high is missing\n"; |
| } |
| } |
|
|
| void PDBFileParser::ParseRemark350() |
| { |
| auto saved = mRec; |
|
|
| enum State |
| { |
| eStart, |
| eInfo, |
| eAnd, |
| eApply, |
| eBioMT |
| } state = eStart; |
|
|
| const std::regex |
| kRX1(R"(BIOMOLECULE: (\d+))"), |
| kRX2(R"(([^:]+): (.+?)(?: (ANGSTROM\*\*2|KCAL/MOL))?)"), |
| kRX8(R"(APPLY THE FOLLOWING TO CHAINS: (.+))"), |
| kRX9(R"(AND CHAINS: (.+))"), |
| kRX10(R"(BIOMT([123])\s+(\d+)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?))"); |
|
|
| int biomolecule = 0, operID = 0; |
| std::vector<std::string> operExpression; |
| std::map<std::string, std::string> values; |
| std::vector<std::string> asymIdList; |
| std::smatch m; |
| cif::row_handle genR; |
|
|
| std::vector<double> mat, vec; |
|
|
| for (mRec = FindRecord("REMARK 350"); mRec != nullptr and mRec->is("REMARK 350"); GetNextRecord()) |
| { |
| std::string line = vS(11); |
|
|
| switch (state) |
| { |
| case eStart: |
| if (regex_match(line, m, kRX1)) |
| { |
| biomolecule = stoi(m[1].str()); |
| state = eInfo; |
| } |
| break; |
|
|
| case eInfo: |
| if (regex_match(line, m, kRX8)) |
| { |
| state = eApply; |
|
|
| std::string value = m[1].str(); |
|
|
| for (auto chain : cif::split<std::string>(value, ", ", true)) |
| { |
| if (chain.empty()) |
| { |
| state = eAnd; |
| break; |
| } |
|
|
| if (chain.length() != 1) |
| throw std::runtime_error("Invalid REMARK 350"); |
|
|
| MapChainID2AsymIDS(chain[0], asymIdList); |
| } |
| } |
| else if (regex_match(line, m, kRX2)) |
| values[m[1].str()] = m[2].str(); |
| break; |
|
|
| case eAnd: |
| if (regex_match(line, m, kRX9)) |
| { |
| state = eApply; |
|
|
| std::string value = m[1].str(); |
|
|
| for (auto chain : cif::split<std::string>(value, ", ", true)) |
| { |
| if (chain.empty()) |
| { |
| state = eAnd; |
| break; |
| } |
|
|
| MapChainID2AsymIDS(chain[0], asymIdList); |
| } |
|
|
| continue; |
| } |
| |
|
|
| case eApply: |
| if (regex_match(line, m, kRX10)) |
| { |
| int mt = stoi(m[1].str()); |
| if (mt != 1) |
| throw std::runtime_error("Invalid REMARK 350"); |
|
|
| operID = stoi(m[2].str()); |
| operExpression.push_back(std::to_string(operID)); |
|
|
| mat.push_back(stod(m[3].str())); |
| mat.push_back(stod(m[4].str())); |
| mat.push_back(stod(m[5].str())); |
| vec.push_back(stod(m[6].str())); |
| state = eBioMT; |
| } |
| break; |
|
|
| case eBioMT: |
| if (regex_match(line, m, kRX10)) |
| { |
| int mt = stoi(m[1].str()); |
|
|
| if (mt == 1) |
| { |
| operID = stoi(m[2].str()); |
| operExpression.push_back(std::to_string(operID)); |
| } |
| else if (operID != stoi(m[2].str())) |
| throw std::runtime_error("Invalid REMARK 350"); |
|
|
| mat.push_back(stod(m[3].str())); |
| mat.push_back(stod(m[4].str())); |
| mat.push_back(stod(m[5].str())); |
| vec.push_back(stod(m[6].str())); |
|
|
| if (mt == 3) |
| { |
| if (vec.size() != 3 or mat.size() != 9) |
| throw std::runtime_error("Invalid REMARK 350"); |
|
|
| if (operID == 1) |
| { |
| std::string oligomer = values["AUTHOR DETERMINED BIOLOGICAL UNIT"]; |
| if (oligomer.empty()) |
| oligomer = values["SOFTWARE DETERMINED QUATERNARY STRUCTURE"]; |
| to_lower(oligomer); |
|
|
| int count = 0; |
| std::smatch m2; |
|
|
| if (std::regex_match(oligomer, m2, std::regex(R"((\d+)-meric)"))) |
| { |
| count = stoi(m2[1].str()); |
| } |
| else if (cif::ends_with(oligomer, "meric")) |
| { |
| std::string cs = oligomer.substr(0, oligomer.length() - 5); |
| if (cs == "mono") |
| count = 1; |
| else if (cs == "di") |
| count = 2; |
| else if (cs == "tri") |
| count = 3; |
| else if (cs == "tetra") |
| count = 4; |
| else if (cs == "hexa") |
| count = 6; |
| else if (cs == "octa") |
| count = 8; |
| else if (cs == "dodeca") |
| count = 12; |
| } |
|
|
| std::string details; |
| if (values["AUTHOR DETERMINED BIOLOGICAL UNIT"].empty()) |
| { |
| if (not values["SOFTWARE DETERMINED QUATERNARY STRUCTURE"].empty()) |
| details = "software_defined_assembly"; |
| } |
| else if (values["SOFTWARE DETERMINED QUATERNARY STRUCTURE"].empty()) |
| details = "author_defined_assembly"; |
| else |
| details = "author_and_software_defined_assembly"; |
|
|
| |
| getCategory("pdbx_struct_assembly")->emplace({ |
| { "id", biomolecule }, |
| { "details", details }, |
| { "method_details", values["SOFTWARE USED"] }, |
| { "oligomeric_details", oligomer }, |
| { "oligomeric_count", count > 0 ? std::to_string(count) : "" } |
| }); |
|
|
| auto cat = getCategory("pdbx_struct_assembly_prop"); |
|
|
| if (not values["TOTAL BURIED SURFACE AREA"].empty()) |
| cat->emplace({ |
| { "biol_id", biomolecule }, |
| { "type", "ABSA (A^2)" }, |
| { "value", values["TOTAL BURIED SURFACE AREA"] } |
| }); |
|
|
| if (not values["CHANGE IN SOLVENT FREE ENERGY"].empty()) |
| cat->emplace({ |
| { "biol_id", biomolecule }, |
| { "type", "MORE" }, |
| { "value", values["CHANGE IN SOLVENT FREE ENERGY"] } |
| }); |
|
|
| if (not values["SURFACE AREA OF THE COMPLEX"].empty()) |
| cat->emplace({ |
| { "biol_id", biomolecule }, |
| { "type", "SSA (A^2)" }, |
| { "value", values["SURFACE AREA OF THE COMPLEX"] } |
| }); |
| |
|
|
| values.clear(); |
| } |
|
|
| std::string type = mat == std::vector<double>{ 1, 0, 0, 0, 1, 0, 0, 0, 1 } and vec == std::vector<double>{ 0, 0, 0 } ? "identity operation" : "crystal symmetry operation"; |
|
|
| |
| |
|
|
| |
| |
| try |
| { |
| |
| getCategory("pdbx_struct_oper_list")->emplace({ |
| { "id", operID }, |
| { "type", type }, |
| |
| |
| { "matrix[1][1]", cif::format("%12.10f", mat[0]).str() }, |
| { "matrix[1][2]", cif::format("%12.10f", mat[1]).str() }, |
| { "matrix[1][3]", cif::format("%12.10f", mat[2]).str() }, |
| { "vector[1]", cif::format("%12.10f", vec[0]).str() }, |
| { "matrix[2][1]", cif::format("%12.10f", mat[3]).str() }, |
| { "matrix[2][2]", cif::format("%12.10f", mat[4]).str() }, |
| { "matrix[2][3]", cif::format("%12.10f", mat[5]).str() }, |
| { "vector[2]", cif::format("%12.10f", vec[1]).str() }, |
| { "matrix[3][1]", cif::format("%12.10f", mat[6]).str() }, |
| { "matrix[3][2]", cif::format("%12.10f", mat[7]).str() }, |
| { "matrix[3][3]", cif::format("%12.10f", mat[8]).str() }, |
| { "vector[3]", cif::format("%12.10f", vec[2]).str() } |
| }); |
| |
| } |
| catch (duplicate_key_error &ex) |
| { |
| |
| } |
|
|
| mat.clear(); |
| vec.clear(); |
| } |
| } |
| else if (regex_match(line, m, kRX1)) |
| { |
| if (not(vec.empty() and mat.empty())) |
| throw std::runtime_error("Invalid REMARK 350"); |
|
|
| |
| getCategory("pdbx_struct_assembly_gen")->emplace({ |
| { "assembly_id", biomolecule }, |
| { "oper_expression", cif::join(operExpression, ",") }, |
| { "asym_id_list", cif::join(asymIdList, ",") } |
| }); |
| |
|
|
| biomolecule = stoi(m[1].str()); |
| asymIdList.clear(); |
| operExpression.clear(); |
|
|
| state = eInfo; |
| } |
| break; |
| } |
| } |
|
|
| if (not operExpression.empty()) |
| { |
| |
| getCategory("pdbx_struct_assembly_gen")->emplace({ |
| { "assembly_id", biomolecule }, |
| { "oper_expression", cif::join(operExpression, ",") }, |
| { "asym_id_list", cif::join(asymIdList, ",") } |
| }); |
| |
| } |
|
|
| mRec = saved; |
| } |
|
|
| void PDBFileParser::ParsePrimaryStructure() |
| { |
| |
| DBREF cur = { mStructureID }; |
|
|
| while (cif::starts_with(mRec->mName, "DBREF")) |
| { |
| if (mRec->is("DBREF ")) |
| { |
| cur.PDBIDCode = vS(8, 11); |
| cur.chainID = vC(13); |
| cur.seqBegin = vI(15, 18); |
| |
| cur.insertBegin = vC(19); |
| |
| cur.seqEnd = vI(21, 24); |
| |
| cur.insertEnd = vC(25); |
| |
| cur.database = vS(27, 32); |
| cur.dbAccession = vS(34, 41); |
| cur.dbIdCode = vS(43, 54); |
| cur.dbSeqBegin = vI(56, 60); |
| |
| cur.dbinsBeg = vC(61); |
| |
| cur.dbSeqEnd = vI(63, 67); |
| |
| cur.dbinsEnd = vC(68); |
| |
| auto &chain = GetChainForID(cur.chainID); |
| chain.mDbref = cur; |
| } |
| else if (mRec->is("DBREF1")) |
| { |
| cur.PDBIDCode = vS(8, 11); |
| cur.chainID = vC(13); |
| cur.seqBegin = vI(15, 18); |
| |
| cur.insertBegin = vC(19); |
| |
| cur.seqEnd = vI(21, 24); |
| |
| cur.insertEnd = vC(25); |
| |
| cur.database = vS(27, 32); |
| cur.dbIdCode = vS(48, 67); |
| } |
| else if (mRec->is("DBREF2")) |
| { |
| if (vC(13) != cur.chainID) |
| throw std::runtime_error("Chain ID's for DBREF1/DBREF2 records do not match"); |
| cur.dbAccession = vS(19, 40); |
| |
| cur.dbSeqBegin = vI(46, 55); |
| |
| cur.dbSeqEnd = vI(58, 67); |
| |
| auto &chain = GetChainForID(cur.chainID); |
| chain.mDbref = cur; |
| } |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| for (auto &chain : mChains) |
| { |
| chain.mNextSeqNum = chain.mDbref.seqBegin; |
| chain.mNextDbSeqNum = chain.mDbref.dbSeqBegin; |
| } |
|
|
| while (mRec->is("SEQADV")) |
| { |
| mSeqadvs.push_back({ |
| |
| vS(13, 15), |
| vC(17), |
| vI(19, 22), |
| vC(23), |
| vS(25, 28), |
| vS(30, 38), |
| vS(40, 42), |
| vI(44, 48), |
| vS(50, 70) |
| }); |
|
|
| GetNextRecord(); |
| } |
|
|
| while (mRec->is("SEQRES")) |
| { |
| |
| |
| char chainID = vC(12); |
| |
| |
| int numRes = vI(14, 17); |
| |
| std::string monomers = vS(20, 70); |
| |
|
|
| auto &chain = GetChainForID(chainID, numRes); |
|
|
| for (auto monID : cif::split<std::string>(monomers, " ", true)) |
| { |
| if (monID.empty()) |
| continue; |
|
|
| chain.mSeqres.push_back({ monID, chain.mNextSeqNum++, ' ', chain.mNextDbSeqNum++ }); |
|
|
| InsertChemComp(monID); |
| } |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| while (mRec->is("MODRES")) |
| { |
| std::string resName = vS(13, 15); |
| |
| |
| |
| std::string stdRes = vS(25, 27); |
| |
|
|
| mMod2parent[resName] = stdRes; |
|
|
| GetNextRecord(); |
| } |
| } |
|
|
| void PDBFileParser::ParseHeterogen() |
| { |
| while (mRec->is("HET ")) |
| { |
| std::string hetID = vS(8, 10); |
| char chainID = vC(13); |
| int seqNum = vI(14, 17); |
| char iCode = vC(18); |
| int numHetAtoms = vI(21, 25); |
| |
| std::string text = vS(31, 70); |
|
|
| mHets.emplace_back(hetID, chainID, seqNum, iCode, numHetAtoms, text); |
|
|
| GetNextRecord(); |
| } |
|
|
| for (;;) |
| { |
| if (mRec->is("HETNAM")) |
| { |
| std::string hetID = vS(12, 14); |
| std::string text = vS(16); |
|
|
| mHetnams[hetID] = text; |
| InsertChemComp(hetID); |
|
|
| GetNextRecord(); |
| continue; |
| } |
|
|
| if (mRec->is("HETSYN")) |
| { |
| std::string hetID = vS(12, 14); |
| std::string syn = vS(16); |
|
|
| mHetsyns[hetID] = syn; |
|
|
| GetNextRecord(); |
| continue; |
| } |
|
|
| break; |
| } |
|
|
| while (mRec->is("FORMUL")) |
| { |
| std::string hetID = vS(13, 15); |
| |
| char waterMark = vC(19); |
| std::string formula = vS(20); |
|
|
| mFormuls[hetID] = formula; |
|
|
| if (waterMark == '*') |
| mWaterHetID = hetID; |
|
|
| GetNextRecord(); |
| } |
| } |
|
|
| void PDBFileParser::ConstructEntities() |
| { |
| |
| |
|
|
| |
| int modelNr = 1; |
|
|
| typedef std::map<std::tuple<char, int, char, char>, std::string> CompTypeMap; |
| CompTypeMap residuesSeen; |
|
|
| for (auto r = mData; r != nullptr; r = r->mNext) |
| { |
| if (r->is("MODEL ")) |
| { |
| modelNr = r->vI(11, 14); |
| if (modelNr != 1) |
| break; |
| continue; |
| } |
|
|
| if (r->is("ATOM ") or r->is("HETATM")) |
| { |
| std::string name = r->vS(13, 16); |
| char altLoc = r->vC(17); |
| std::string resName = r->vS(18, 20); |
| char chainID = r->vC(22); |
| int resSeq = r->vI(23, 26); |
| char iCode = r->vC(27); |
|
|
| |
| CompTypeMap::key_type k = std::make_tuple(chainID, resSeq, iCode, altLoc); |
| if (residuesSeen.count(k) == 0) |
| residuesSeen[k] = resName; |
| else if (residuesSeen[k] != resName) |
| throw std::runtime_error("inconsistent residue type for " + std::string{ chainID } + std::to_string(resSeq) + iCode + altLoc + "\n" + |
| " (" + residuesSeen[k] + " != " + resName + ")"); |
|
|
| auto &chain = GetChainForID(chainID); |
|
|
| PDBChain::AtomRes ar{ resName, resSeq, iCode }; |
|
|
| if ((chain.mResiduesSeen.empty() or chain.mResiduesSeen.back() != ar) and |
| cif::compound_factory::instance().is_monomer(resName)) |
| { |
| chain.mResiduesSeen.push_back(ar); |
| } |
|
|
| |
| mUnobs.erase(remove_if(mUnobs.begin(), mUnobs.end(), [=](UNOBS &a) |
| { |
| bool result = false; |
|
|
| if (modelNr == a.modelNr and |
| resName == a.res and |
| chainID == a.chain and |
| resSeq == a.seq and |
| iCode == a.iCode) |
| { |
| auto i = find(a.atoms.begin(), a.atoms.end(), name); |
| if (i != a.atoms.end()) |
| { |
| a.atoms.erase(i); |
| result = a.atoms.empty(); |
| } |
| } |
|
|
| return result; }), |
| mUnobs.end()); |
|
|
| continue; |
| } |
|
|
| if (r->is("TER ")) |
| { |
| |
| char chainID = r->vC(22); |
| |
| |
| auto &chain = GetChainForID(chainID); |
| if (chain.mTerIndex == 0) |
| chain.mTerIndex = static_cast<int>(chain.mResiduesSeen.size()); |
| continue; |
| } |
| } |
|
|
| |
| mChains.erase(remove_if(mChains.begin(), mChains.end(), [](auto &chain) |
| { return chain.mResiduesSeen.empty() and chain.mSeqres.empty(); }), |
| mChains.end()); |
|
|
| for (auto &chain : mChains) |
| { |
| if (not(chain.mSeqres.empty() or chain.mResiduesSeen.empty())) |
| { |
| |
| |
| |
|
|
| if (chain.mTerIndex > 0) |
| chain.mResiduesSeen.erase(chain.mResiduesSeen.begin() + chain.mTerIndex, chain.mResiduesSeen.end()); |
|
|
| int lastResidueIndex = chain.AlignResToSeqRes(); |
|
|
| if (lastResidueIndex > 0 and lastResidueIndex + 1 < static_cast<int>(chain.mResiduesSeen.size())) |
| { |
| auto &r = chain.mResiduesSeen[lastResidueIndex + 1]; |
|
|
| if (cif::VERBOSE > 0) |
| { |
| std::cerr << "Detected residues that cannot be aligned to SEQRES\n" |
| << "First residue is " << chain.mDbref.chainID << ':' << r.mSeqNum << r.mIcode << '\n'; |
| } |
|
|
| chain.mTerIndex = lastResidueIndex + 1; |
| } |
| } |
| else |
| { |
| |
| |
| |
| |
|
|
| for (int ix = chain.mTerIndex; ix < static_cast<int>(chain.mResiduesSeen.size()); ++ix) |
| { |
| std::string resName = chain.mResiduesSeen[ix].mMonID; |
|
|
| if (cif::compound_factory::instance().is_monomer(resName)) |
| chain.mTerIndex = ix + 1; |
|
|
| InsertChemComp(resName); |
| } |
|
|
| |
| for (int ix = 0; ix < chain.mTerIndex; ++ix) |
| { |
| auto &ar = chain.mResiduesSeen[ix]; |
| chain.mSeqres.push_back({ ar.mMonID, ar.mSeqNum, ar.mIcode, ar.mSeqNum, true }); |
| } |
| } |
| } |
|
|
| std::set<char> terminatedChains; |
| std::map<char, int> residuePerChainCounter; |
|
|
| for (auto r = mData; r != nullptr; r = r->mNext) |
| { |
| if (r->is("MODEL ")) |
| { |
| modelNr = r->vI(11, 14); |
| if (modelNr != 1) |
| break; |
| continue; |
| } |
|
|
| if (r->is("ATOM ") or r->is("HETATM")) |
| { |
| |
| |
| char altLoc = vC(17); |
| std::string resName = r->vS(18, 20); |
| char chainID = r->vC(22); |
| int resSeq = r->vI(23, 26); |
| char iCode = r->vC(27); |
|
|
| auto &chain = GetChainForID(chainID); |
|
|
| auto i = find(chain.mSeqres.begin(), chain.mSeqres.end(), PDBSeqRes{ resName, resSeq, iCode }); |
|
|
| |
| if (altLoc != ' ' and i == chain.mSeqres.end()) |
| { |
| i = find_if(chain.mSeqres.begin(), chain.mSeqres.end(), |
| [resSeq, iCode](const PDBSeqRes &r) -> bool |
| { |
| return r.mSeqNum == resSeq and r.mIcode == iCode; |
| }); |
| } |
|
|
| if (i != chain.mSeqres.end()) |
| { |
| i->mSeen = true; |
| if (i->mMonID != resName) |
| i->mAlts.insert(resName); |
| } |
| else |
| { |
| auto &residues = chain.mHet; |
|
|
| if (residues.empty() or residues.back().mSeqNum != resSeq) |
| { |
| i = lower_bound(residues.begin(), residues.end(), |
| PDBSeqRes{ resName, resSeq, iCode }, |
| [=](const PDBSeqRes &r1, const PDBSeqRes &r2) -> bool |
| { |
| return r1.mSeqNum < r2.mSeqNum; |
| }); |
|
|
| residues.insert(i, { resName, resSeq, iCode, resSeq, true }); |
|
|
| InsertChemComp(resName); |
| } |
| } |
|
|
| int residueCount = (residuePerChainCounter[chainID] += 1); |
|
|
| |
| if (not cif::compound_factory::instance().is_monomer(resName) or |
| terminatedChains.count(chainID) or |
| (chain.mTerIndex > 0 and residueCount >= chain.mTerIndex)) |
| { |
| if (isWater(resName)) |
| mWaterHetID = resName; |
|
|
| auto h = find_if(mHets.begin(), mHets.end(), [=](const HET &het) -> bool |
| { return het.hetID == resName and het.chainID == chainID and |
| het.seqNum == resSeq and het.iCode == iCode; }); |
|
|
| if (h == mHets.end()) |
| { |
| mHets.push_back({ resName, chainID, resSeq, iCode, 0 }); |
| h = prev(mHets.end()); |
| } |
|
|
| h->atoms.push_back(r); |
| } |
|
|
| continue; |
| } |
|
|
| if (r->is("TER ")) |
| { |
| char chainID = r->vC(22); |
| terminatedChains.insert(chainID); |
| } |
| } |
|
|
| |
| for (auto &chain : mChains) |
| { |
| if (chain.mMolID != 0 or chain.mSeqres.empty()) |
| continue; |
|
|
| |
| for (auto &other : mChains) |
| { |
| if (&other == &chain or other.mMolID == 0) |
| continue; |
|
|
| if (chain.SameSequence(other)) |
| { |
| chain.mMolID = other.mMolID; |
| break; |
| } |
| } |
|
|
| if (chain.mMolID != 0) |
| continue; |
|
|
| auto &comp = GetOrCreateCompound(mNextMolID++); |
| comp.mChains.insert(chain.mDbref.chainID); |
|
|
| chain.mMolID = comp.mMolID; |
| } |
|
|
| std::set<std::string> structTitle, structDescription; |
|
|
| |
|
|
| auto cat = getCategory("pdbx_poly_seq_scheme"); |
| int asymNr = 0; |
| for (auto &chain : mChains) |
| { |
| std::string asymID = cif::cif_id_for_number(asymNr++); |
|
|
| if (mMolID2EntityID.count(chain.mMolID) == 0) |
| continue; |
|
|
| std::string entityID = mMolID2EntityID[chain.mMolID]; |
|
|
| mAsymID2EntityID[asymID] = entityID; |
|
|
| |
| getCategory("struct_asym")->emplace({ |
| { "id", asymID }, |
| { "pdbx_blank_PDB_chainid_flag", chain.mDbref.chainID == ' ' ? "Y" : "N" }, |
| |
| { "entity_id", entityID }, |
| |
| }); |
| |
|
|
| int seqNr = 1; |
| for (auto &res : chain.mSeqres) |
| { |
| mChainSeq2AsymSeq[std::make_tuple(chain.mDbref.chainID, res.mSeqNum, res.mIcode)] = std::make_tuple(asymID, seqNr, true); |
|
|
| std::string seqID = std::to_string(seqNr); |
| ++seqNr; |
|
|
| std::set<std::string> monIds = { res.mMonID }; |
| monIds.insert(res.mAlts.begin(), res.mAlts.end()); |
|
|
| for (std::string monID : monIds) |
| { |
| std::string authMonID, authSeqNum, authInsCode{ '.' }; |
|
|
| if (res.mSeen) |
| { |
| authMonID = monID; |
| authSeqNum = std::to_string(res.mSeqNum); |
| if (res.mIcode != ' ' and res.mIcode != 0) |
| authInsCode = std::string{ res.mIcode }; |
|
|
| |
| cat->emplace({ |
| { "asym_id", asymID }, |
| { "entity_id", mMolID2EntityID[chain.mMolID] }, |
| { "seq_id", seqID }, |
| { "mon_id", monID }, |
| { "ndb_seq_num", seqID }, |
| { "pdb_seq_num", res.mSeqNum }, |
| { "auth_seq_num", authSeqNum }, |
| { "pdb_mon_id", authMonID }, |
| { "auth_mon_id", authMonID }, |
| { "pdb_strand_id", std::string{ chain.mDbref.chainID } }, |
| { "pdb_ins_code", authInsCode }, |
| { "hetero", res.mAlts.empty() ? "n" : "y" } |
| }); |
| |
| } |
| else |
| { |
| if (res.mIcode != ' ' and res.mIcode != 0) |
| authInsCode = std::string{ res.mIcode } + "A"; |
|
|
| |
| cat->emplace({ |
| { "asym_id", asymID }, |
| { "entity_id", mMolID2EntityID[chain.mMolID] }, |
| { "seq_id", seqID }, |
| { "mon_id", monID }, |
| { "ndb_seq_num", seqID }, |
| { "pdb_seq_num", res.mSeqNum }, |
| { "auth_seq_num", "." }, |
| { "pdb_mon_id", "." }, |
| { "auth_mon_id", "." }, |
| { "pdb_strand_id", std::string{ chain.mDbref.chainID } }, |
| { "pdb_ins_code", authInsCode }, |
| { "hetero", res.mAlts.empty() ? "n" : "y" } |
| }); |
| |
| } |
| } |
| } |
| } |
|
|
| |
| uint32_t structRefID = 0, structRefSeqAlignID = 0; |
|
|
| for (auto &cmp : mCompounds) |
| { |
| ++structRefID; |
|
|
| std::string srcMethod; |
|
|
| if (not cmp.mSource["SYNTHETIC"].empty()) |
| { |
| srcMethod = "syn"; |
|
|
| |
| getCategory("pdbx_entity_src_syn")->emplace({ |
| { "entity_id", mMolID2EntityID[cmp.mMolID] }, |
| { "pdbx_src_id", structRefID }, |
| { "organism_scientific", cmp.mSource["ORGANISM_SCIENTIFIC"] }, |
| { "ncbi_taxonomy_id", cmp.mSource["ORGANISM_TAXID"] }, |
| }); |
| |
| } |
| else if (cmp.mInfo["ENGINEERED"] == "YES" or |
| not cmp.mSource["EXPRESSION_SYSTEM"].empty()) |
| { |
| srcMethod = "man"; |
|
|
| |
| getCategory("entity_src_gen")->emplace({ |
| { "entity_id", mMolID2EntityID[cmp.mMolID] }, |
| { "pdbx_src_id", structRefID }, |
| { "gene_src_common_name", cmp.mSource["ORGANISM_COMMON"] }, |
| { "pdbx_gene_src_gene", cmp.mSource["GENE"] }, |
| { "gene_src_strain", cmp.mSource["STRAIN"] }, |
| { "gene_src_tissue", cmp.mSource["TISSUE"] }, |
| { "gene_src_tissue_fraction", cmp.mSource["TISSUE_FRACTION"] }, |
| { "pdbx_gene_src_cell_line", cmp.mSource["CELL_LINE"] }, |
| { "pdbx_gene_src_organelle", cmp.mSource["ORGANELLE"] }, |
| { "pdbx_gene_src_cell", cmp.mSource["CELL"] }, |
| { "pdbx_gene_src_cellular_location", cmp.mSource["CELLULAR_LOCATION"] }, |
| { "host_org_common_name", cmp.mSource["EXPRESSION_SYSTEM_COMMON"] }, |
| { "pdbx_gene_src_scientific_name", cmp.mSource["ORGANISM_SCIENTIFIC"] }, |
| { "pdbx_gene_src_ncbi_taxonomy_id", cmp.mSource["ORGANISM_TAXID"] }, |
| { "pdbx_host_org_scientific_name", cmp.mSource["EXPRESSION_SYSTEM"] }, |
| { "pdbx_host_org_ncbi_taxonomy_id", cmp.mSource["EXPRESSION_SYSTEM_TAXID"] }, |
| { "pdbx_host_org_strain", cmp.mSource["EXPRESSION_SYSTEM_STRAIN"] }, |
| { "pdbx_host_org_variant", cmp.mSource["EXPRESSION_SYSTEM_VARIANT"] }, |
| { "pdbx_host_org_cell_line", cmp.mSource["EXPRESSION_SYSTEM_CELL_LINE"] }, |
| { "pdbx_host_org_cellular_location", cmp.mSource["EXPRESSION_SYSTEM_CELLULAR_LOCATION"] }, |
| { "pdbx_host_org_vector_type", cmp.mSource["EXPRESSION_SYSTEM_VECTOR_TYPE"] }, |
| { "pdbx_host_org_vector", cmp.mSource["EXPRESSION_SYSTEM_VECTOR"] }, |
| { "pdbx_host_org_gene", cmp.mSource["EXPRESSION_SYSTEM_GENE"] }, |
| { "plasmid_name", cmp.mSource["EXPRESSION_SYSTEM_PLASMID"] }, |
| { "pdbx_description", cmp.mSource["OTHER_DETAILS"] } |
| }); |
| |
| } |
| else if (not cmp.mSource["ORGANISM_SCIENTIFIC"].empty()) |
| { |
| srcMethod = "nat"; |
|
|
| |
| getCategory("entity_src_nat")->emplace({ |
| { "entity_id", mMolID2EntityID[cmp.mMolID] }, |
| { "pdbx_src_id", structRefID }, |
| { "common_name", cmp.mSource["ORGANISM_COMMON"] }, |
| { "strain", cmp.mSource["STRAIN"] }, |
| { "pdbx_secretion", cmp.mSource["SECRETION"] }, |
| { "pdbx_organism_scientific", cmp.mSource["ORGANISM_SCIENTIFIC"] }, |
| { "pdbx_ncbi_taxonomy_id", cmp.mSource["ORGANISM_TAXID"] }, |
| { "pdbx_cellular_location", cmp.mSource["CELLULAR_LOCATION"] }, |
| { "pdbx_plasmid_name", cmp.mSource["PLASMID"] }, |
| { "pdbx_organ", cmp.mSource["ORGAN"] }, |
| }); |
| |
| } |
|
|
| |
| getCategory("entity")->emplace({ |
| { "id", mMolID2EntityID[cmp.mMolID] }, |
| { "type", "polymer" }, |
| { "src_method", srcMethod }, |
| { "pdbx_description", cmp.mInfo["MOLECULE"] }, |
| |
| { "pdbx_number_of_molecules", cmp.mChains.size() }, |
| { "details", cmp.mInfo["OTHER_DETAILS"] }, |
| { "pdbx_mutation", cmp.mInfo["MUTATION"] }, |
| { "pdbx_fragment", cmp.mInfo["FRAGMENT"] }, |
| { "pdbx_ec", cmp.mInfo["EC"] } |
| }); |
| |
|
|
| if (not cmp.mInfo["SYNONYM"].empty()) |
| { |
| |
| getCategory("entity_name_com")->emplace({ |
| { "entity_id", mMolID2EntityID[cmp.mMolID] }, |
| { "name", cmp.mInfo["SYNONYM"] } |
| }); |
| |
| } |
|
|
| std::string desc = cmp.mInfo["MOLECULE"]; |
| if (not cmp.mInfo["EC"].empty()) |
| desc += " (E.C." + cmp.mInfo["EC"] + ")"; |
|
|
| if (not cmp.mTitle.empty()) |
| structTitle.insert(cmp.mTitle); |
|
|
| if (not desc.empty()) |
| structDescription.insert(desc); |
|
|
| auto ci = find_if(mChains.begin(), mChains.end(), |
| [cmp](PDBChain &c) -> bool |
| { return cmp.mChains.count(c.mDbref.chainID); }); |
|
|
| if (ci != mChains.end() and not ci->mDbref.dbIdCode.empty()) |
| { |
| |
| getCategory("struct_ref")->emplace({ |
| { "id", structRefID }, |
| { "entity_id", mMolID2EntityID[cmp.mMolID] }, |
| { "db_name", ci->mDbref.database }, |
| { "db_code", ci->mDbref.dbIdCode }, |
| { "pdbx_db_accession", ci->mDbref.dbAccession }, |
| |
| }); |
| |
| } |
|
|
| bool nstdMonomer = false, nonstandardLinkage = false; |
| bool mightBePolyPeptide = true, mightBeDNA = true; |
|
|
| std::vector<std::string> chains; |
| std::string seq, seqCan; |
|
|
| |
| for (auto &chain : mChains) |
| { |
| if (chain.mMolID != cmp.mMolID) |
| continue; |
|
|
| |
|
|
| ++structRefSeqAlignID; |
| DBREF &dbref = chain.mDbref; |
|
|
| if (not dbref.database.empty()) |
| { |
| auto insToStr = [](char i) -> std::string |
| { |
| return i == ' ' or not isprint(i) ? "" : std::string{ i }; |
| }; |
|
|
| auto &pdbxPolySeqScheme = *getCategory("pdbx_poly_seq_scheme"); |
|
|
| int seqAlignBeg = 0, seqAlignEnd = 0; |
|
|
| try |
| { |
| seqAlignBeg = pdbxPolySeqScheme.find1<int>(key("pdb_strand_id") == std::string{ dbref.chainID } and |
| key("pdb_seq_num") == dbref.seqBegin and |
| (key("pdb_ins_code") == insToStr(dbref.insertBegin) or key("pdb_ins_code") == cif::null), |
| "seq_id"); |
|
|
| seqAlignEnd = pdbxPolySeqScheme.find1<int>(key("pdb_strand_id") == std::string{ dbref.chainID } and |
| key("pdb_seq_num") == dbref.seqEnd and |
| (key("pdb_ins_code") == insToStr(dbref.insertEnd) or key("pdb_ins_code") == cif::null), |
| "seq_id"); |
| } |
| catch (...) |
| { |
| } |
|
|
| |
| getCategory("struct_ref_seq")->emplace({ |
| { "align_id", structRefSeqAlignID }, |
| { "ref_id", structRefID }, |
| { "pdbx_PDB_id_code", dbref.PDBIDCode }, |
| { "pdbx_strand_id", std::string{ chain.mDbref.chainID } }, |
| { "seq_align_beg", seqAlignBeg }, |
| { "pdbx_seq_align_beg_ins_code", insToStr(dbref.insertBegin) }, |
| { "seq_align_end", seqAlignEnd }, |
| { "pdbx_seq_align_end_ins_code", insToStr(dbref.insertEnd) }, |
| { "pdbx_db_accession", dbref.dbAccession }, |
| { "db_align_beg", dbref.dbSeqBegin }, |
| { "pdbx_db_align_beg_ins_code", insToStr(dbref.dbinsBeg) }, |
| { "db_align_end", dbref.dbSeqEnd }, |
| { "pdbx_db_align_end_ins_code", insToStr(dbref.dbinsEnd) }, |
| { "pdbx_auth_seq_align_beg", dbref.seqBegin }, |
| { "pdbx_auth_seq_align_end", dbref.seqEnd } |
| }); |
| |
|
|
| |
| for (auto &seqadv : mSeqadvs) |
| { |
| if (seqadv.chainID != chain.mDbref.chainID or seqadv.resName.empty()) |
| continue; |
|
|
| std::string asym, seqNum; |
| int labelSeq = -1; |
| std::error_code ec; |
|
|
| std::tie(asym, labelSeq, std::ignore) = MapResidue(seqadv.chainID, seqadv.seqNum, seqadv.iCode, ec); |
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "dropping unmatched SEQADV record\n"; |
| continue; |
| } |
|
|
| seqNum = std::to_string(labelSeq); |
|
|
| |
| getCategory("struct_ref_seq_dif")->emplace({ |
| { "align_id", structRefSeqAlignID }, |
| { "pdbx_PDB_id_code", dbref.PDBIDCode }, |
| { "mon_id", seqadv.resName }, |
| { "pdbx_pdb_strand_id", seqadv.chainID }, |
| { "seq_num", seqNum }, |
| { "pdbx_pdb_ins_code", seqadv.iCode == ' ' ? std::string{} : std::string{ seqadv.iCode } }, |
| { "pdbx_seq_db_name", seqadv.database }, |
| { "pdbx_seq_db_accession_code", seqadv.dbAccession }, |
| { "db_mon_id", seqadv.dbRes }, |
| { "pdbx_seq_db_seq_num", seqadv.dbSeq }, |
| { "details", seqadv.conflict }, |
| { "pdbx_auth_seq_num", seqadv.seqNum }, |
| { "pdbx_ordinal", ++mPdbxDifOrdinal } |
| }); |
| |
| } |
| } |
|
|
| if (not chains.empty()) |
| { |
| chains.push_back(std::string{ chain.mDbref.chainID }); |
| continue; |
| } |
|
|
| chains.push_back(std::string{ chain.mDbref.chainID }); |
|
|
| size_t seqLen = 0, seqCanLen = 0; |
|
|
| for (auto &res : chain.mSeqres) |
| { |
| std::string letter, stdRes; |
|
|
| if (mMod2parent.count(res.mMonID)) |
| stdRes = mMod2parent.at(res.mMonID); |
|
|
| if (cif::compound_factory::kAAMap.count(res.mMonID)) |
| { |
| letter = cif::compound_factory::kAAMap.at(res.mMonID); |
| mightBeDNA = false; |
| } |
| else if (cif::compound_factory::kBaseMap.count(res.mMonID)) |
| { |
| letter = cif::compound_factory::kBaseMap.at(res.mMonID); |
| mightBePolyPeptide = false; |
| } |
| else |
| { |
| nstdMonomer = true; |
| letter = '(' + res.mMonID + ')'; |
|
|
| |
| auto compound = cif::compound_factory::instance().create(stdRes.empty() ? res.mMonID : stdRes); |
| if (compound != nullptr and |
| not iequals(compound->type(), "L-peptide linking") and |
| not iequals(compound->type(), "RNA linking")) |
| { |
| nonstandardLinkage = true; |
| } |
| } |
|
|
| if (seqLen + letter.length() > 80) |
| { |
| seq += '\n'; |
| seqLen = 0; |
| } |
|
|
| seq += letter; |
| seqLen += letter.length(); |
|
|
| if (letter.length() > 1) |
| { |
| if (not stdRes.empty() and cif::compound_factory::kAAMap.count(stdRes)) |
| letter = cif::compound_factory::kAAMap.at(stdRes); |
| else if (cif::compound_factory::kBaseMap.count(res.mMonID)) |
| letter = cif::compound_factory::kBaseMap.at(res.mMonID); |
| else |
| letter = 'X'; |
| } |
|
|
| if (seqCanLen + letter.length() > 80) |
| { |
| seqCan += '\n'; |
| seqCanLen = 0; |
| } |
| seqCan += letter; |
| seqCanLen += letter.length(); |
| } |
|
|
| auto cat_ps = getCategory("entity_poly_seq"); |
| for (size_t i = 0; i < chain.mSeqres.size(); ++i) |
| { |
| auto &rs = chain.mSeqres[i]; |
|
|
| if (std::find(mChemComp.begin(), mChemComp.end(), rs.mMonID) == mChemComp.end()) |
| mChemComp.emplace_back(rs.mMonID); |
|
|
| |
| cat_ps->emplace({ |
| { "entity_id", mMolID2EntityID[cmp.mMolID] }, |
| { "num", i + 1 }, |
| { "mon_id", rs.mMonID }, |
| { "hetero", rs.mAlts.empty() ? "n" : "y" } |
| }); |
| |
|
|
| for (auto &a : rs.mAlts) |
| { |
| |
| cat_ps->emplace({ |
| { "entity_id", mMolID2EntityID[cmp.mMolID] }, |
| { "num", i + 1 }, |
| { "mon_id", a }, |
| { "hetero", "y" } |
| }); |
| |
| } |
| } |
| } |
|
|
| std::string type; |
| if (mightBePolyPeptide and not mightBeDNA) |
| type = "polypeptide(L)"; |
| else if (mightBeDNA and not mightBePolyPeptide) |
| type = "polyribonucleotide"; |
|
|
| |
| getCategory("entity_poly")->emplace({ |
| { "entity_id", mMolID2EntityID[cmp.mMolID] }, |
| { "pdbx_seq_one_letter_code", seq }, |
| { "pdbx_seq_one_letter_code_can", seqCan }, |
| { "nstd_monomer", (nstdMonomer ? "yes" : "no") }, |
| { "pdbx_strand_id", cif::join(chains, ",") }, |
| { "nstd_linkage", nonstandardLinkage ? "yes" : "no" }, |
| { "type", type } |
| }); |
| |
| } |
|
|
| if (not(structTitle.empty() and structDescription.empty())) |
| { |
| |
| getCategory("struct")->emplace({ |
| { "entry_id", mStructureID }, |
| { "title", cif::join(structTitle, ", ") }, |
| { "pdbx_descriptor", cif::join(structDescription, ", ") }, |
| { "pdbx_model_type_details", mModelTypeDetails } |
| }); |
| |
| } |
|
|
| |
| ConstructSugarTrees(asymNr); |
|
|
| |
|
|
| std::map<char, std::string> waterChains; |
| std::map<std::tuple<std::string, std::string>, int> ndbSeqNum; |
| std::map<std::string, int> entityAuthSeqNum; |
|
|
| for (size_t i = 0; i < mHets.size(); ++i) |
| { |
| auto &heti = mHets[i]; |
|
|
| if (not heti.asymID.empty()) |
| continue; |
|
|
| if (heti.hetID == mWaterHetID or isWater(heti.hetID)) |
| continue; |
|
|
| |
| auto &chain = GetChainForID(heti.chainID); |
| auto ih = find(chain.mSeqres.begin(), chain.mSeqres.end(), PDBSeqRes{ heti.hetID, heti.seqNum, heti.iCode }); |
|
|
| |
| if (ih != chain.mSeqres.end()) |
| continue; |
|
|
| heti.asymID = cif::cif_id_for_number(asymNr++); |
| } |
|
|
| std::set<std::string> writtenAsyms; |
|
|
| std::map<std::string, int> hetCount; |
| for (auto &het : mHets) |
| hetCount[het.hetID] += 1; |
|
|
| for (auto &het : mHets) |
| { |
| std::string hetID = het.hetID; |
|
|
| auto &chain = GetChainForID(het.chainID); |
|
|
| |
| auto i = find(chain.mSeqres.begin(), chain.mSeqres.end(), PDBSeqRes{ hetID, het.seqNum, het.iCode }); |
|
|
| |
| if (i != chain.mSeqres.end()) |
| continue; |
|
|
| |
| if (mHet2EntityID.count(hetID) == 0) |
| { |
| std::string entityID = std::to_string(mNextEntityNr++); |
| mHet2EntityID[hetID] = entityID; |
|
|
| if (hetID == mWaterHetID) |
| { |
| |
| getCategory("entity")->emplace({ |
| { "id", entityID }, |
| { "type", "water" }, |
| { "src_method", "nat" }, |
| { "pdbx_description", "water" }, |
| { "pdbx_number_of_molecules", hetCount[hetID] } |
| }); |
| |
| } |
| else |
| { |
| if (mHetnams[hetID].empty()) |
| { |
| auto compound = cif::compound_factory::instance().create(hetID); |
| if (compound != nullptr) |
| mHetnams[hetID] = compound->name(); |
| } |
|
|
| |
| getCategory("entity")->emplace({ |
| { "id", entityID }, |
| { "type", "non-polymer" }, |
| { "src_method", "syn" }, |
| { "pdbx_description", mHetnams[hetID] }, |
| { "details", mHetsyns[hetID] }, |
| { "pdbx_number_of_molecules", hetCount[hetID] } |
| }); |
| |
| } |
|
|
| |
| std::string name = mHetnams[hetID]; |
| if (name.empty() and hetID == mWaterHetID) |
| name = "water"; |
|
|
| |
| getCategory("pdbx_entity_nonpoly")->emplace({ |
| { "entity_id", entityID }, |
| { "name", name }, |
| { "comp_id", hetID } |
| }); |
| |
| } |
|
|
| |
|
|
| std::string asymID = het.asymID; |
|
|
| auto k = std::make_tuple(het.chainID, het.seqNum, het.iCode); |
| if (mChainSeq2AsymSeq.count(k) == 0) |
| { |
| if (hetID == mWaterHetID or isWater(hetID)) |
| { |
| if (waterChains.count(het.chainID) == 0) |
| { |
| asymID = cif::cif_id_for_number(asymNr++); |
| waterChains[het.chainID] = asymID; |
| } |
| else |
| asymID = waterChains[het.chainID]; |
| } |
| else |
| asymID = het.asymID; |
|
|
| assert(asymID.empty() == false); |
|
|
| mAsymID2EntityID[asymID] = mHet2EntityID[hetID]; |
|
|
| |
| |
| |
| |
| mChainSeq2AsymSeq[k] = std::make_tuple(asymID, 0, false); |
|
|
| if (writtenAsyms.count(asymID) == 0) |
| { |
| writtenAsyms.insert(asymID); |
|
|
| |
| getCategory("struct_asym")->emplace({ |
| { "id", asymID }, |
| { "pdbx_blank_PDB_chainid_flag", het.chainID == ' ' ? "Y" : "N" }, |
| |
| { "entity_id", mHet2EntityID[hetID] }, |
| |
| }); |
|
|
| |
| } |
| } |
|
|
| int seqNr = ++ndbSeqNum[std::make_tuple(hetID, asymID)]; |
| int authSeqNr = ++entityAuthSeqNum[hetID]; |
|
|
| std::string iCode{ het.iCode }; |
| cif::trim(iCode); |
| if (iCode.empty()) |
| iCode = { '.' }; |
|
|
| |
| getCategory("pdbx_nonpoly_scheme")->emplace({ |
| { "asym_id", asymID }, |
| { "entity_id", mHet2EntityID[hetID] }, |
| { "mon_id", hetID }, |
| { "ndb_seq_num", seqNr }, |
| { "pdb_seq_num", het.seqNum }, |
| { "auth_seq_num", authSeqNr }, |
| { "pdb_mon_id", hetID }, |
| { "auth_mon_id", hetID }, |
| { "pdb_strand_id", std::string{ het.chainID } }, |
| { "pdb_ins_code", iCode } |
| }); |
| |
|
|
| |
| mChainSeq2AsymSeq[std::make_tuple(het.chainID, het.seqNum, het.iCode)] = std::make_tuple(asymID, seqNr, false); |
| } |
|
|
| int modResID = 1; |
| std::set<std::string> modResSet; |
| for (auto rec = FindRecord("MODRES"); rec != nullptr and rec->is("MODRES"); |
| rec = rec->mNext) |
| { |
| std::string resName = rec->vS(13, 15); |
| char chainID = rec->vC(17); |
| int seqNum = rec->vI(19, 22); |
| char iCode = rec->vC(23); |
| std::string stdRes = rec->vS(25, 27); |
| std::string comment = rec->vS(30, 70); |
|
|
| std::string asymID; |
| int seq; |
| std::error_code ec; |
|
|
| std::tie(asymID, seq, std::ignore) = MapResidue(chainID, seqNum, iCode, ec); |
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "dropping unmapped MODRES record\n"; |
| continue; |
| } |
|
|
| |
| getCategory("pdbx_struct_mod_residue")->emplace({ |
| { "id", modResID++ }, |
| { "label_asym_id", asymID }, |
| { "label_seq_id", seq }, |
| { "label_comp_id", resName }, |
| { "auth_asym_id", std::string(1, chainID) }, |
| { "auth_seq_id", seqNum }, |
| { "auth_comp_id", resName }, |
| { "PDB_ins_code", iCode == ' ' ? "" : std::string{ iCode } }, |
| { "parent_comp_id", stdRes }, |
| { "details", comment } |
| }); |
| |
|
|
| modResSet.insert(resName); |
| } |
|
|
| |
|
|
| for (auto cc : mChemComp) |
| { |
| auto compound = cif::compound_factory::instance().create( |
| mMod2parent.count(cc) ? mMod2parent[cc] : cc); |
|
|
| std::string name; |
| std::string formula; |
| std::string type; |
| std::string nstd = "."; |
| std::optional<float> formulaWeight; |
|
|
| if (compound != nullptr) |
| { |
| name = compound->name(); |
| type = compound->type(); |
|
|
| if (iequals(type, "L-peptide linking") or iequals(type, "peptide linking")) |
| nstd = "y"; |
|
|
| formula = compound->formula(); |
| formulaWeight = compound->formula_weight(); |
| } |
|
|
| if (name.empty()) |
| name = mHetnams[cc]; |
|
|
| if (type.empty()) |
| type = "NON-POLYMER"; |
|
|
| if (formula.empty()) |
| { |
| formula = mFormuls[cc]; |
|
|
| const std::regex rx(R"(\d+\((.+)\))"); |
| std::smatch m; |
| if (std::regex_match(formula, m, rx)) |
| formula = m[1].str(); |
| } |
|
|
| if (modResSet.count(cc)) |
| nstd = "n"; |
|
|
| |
| getCategory("chem_comp")->emplace({ |
| { "id", cc }, |
| { "name", name }, |
| { "formula", formula }, |
| { "formula_weight", formulaWeight, 3 }, |
| { "mon_nstd_flag", nstd }, |
| { "type", type } |
| }); |
| |
| } |
|
|
| getCategory("chem_comp")->reorder_by_index(); |
|
|
| |
|
|
| int idRes = 0, idAtom = 0; |
| sort(mUnobs.begin(), mUnobs.end(), [](const UNOBS &a, const UNOBS &b) -> bool |
| { |
| int d = a.modelNr - b.modelNr; |
| if (d == 0) |
| d = a.seq - b.seq; |
| return d < 0; }); |
|
|
| for (auto &unobs : mUnobs) |
| { |
| bool isPolymer = false; |
| std::string asymID, compID = unobs.res; |
| int seqNr = 0; |
| std::error_code ec; |
|
|
| std::tie(asymID, seqNr, isPolymer) = MapResidue(unobs.chain, unobs.seq, unobs.iCode, ec); |
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "error mapping unobserved residue\n"; |
| continue; |
| } |
|
|
| if (unobs.atoms.empty()) |
| { |
| |
| getCategory("pdbx_unobs_or_zero_occ_residues")->emplace({ |
| { "id", std::to_string(++idRes) }, |
| { "polymer_flag", isPolymer ? "Y" : "N" }, |
| { "occupancy_flag", 1 }, |
| { "PDB_model_num", unobs.modelNr ? unobs.modelNr : 1 }, |
| { "auth_asym_id", std::string{ unobs.chain } }, |
| { "auth_comp_id", unobs.res }, |
| { "auth_seq_id", unobs.seq }, |
| { "PDB_ins_code", unobs.iCode == ' ' ? "" : std::string{ unobs.iCode } }, |
| { "label_asym_id", asymID }, |
| { "label_comp_id", compID }, |
| { "label_seq_id", seqNr > 0 ? std::to_string(seqNr) : "" } |
| }); |
| |
| } |
| else |
| { |
| for (auto &atom : unobs.atoms) |
| { |
| |
| getCategory("pdbx_unobs_or_zero_occ_atoms")->emplace({ |
| { "id", std::to_string(++idAtom) }, |
| { "polymer_flag", isPolymer ? "Y" : "N" }, |
| { "occupancy_flag", 1 }, |
| { "PDB_model_num", unobs.modelNr ? unobs.modelNr : 1 }, |
| { "auth_asym_id", std::string{ unobs.chain } }, |
| { "auth_comp_id", unobs.res }, |
| { "auth_seq_id", unobs.seq }, |
| { "PDB_ins_code", unobs.iCode == ' ' ? "" : std::string{ unobs.iCode } }, |
| { "auth_atom_id", atom }, |
| { "label_asym_id", asymID }, |
| { "label_comp_id", compID }, |
| { "label_seq_id", seqNr > 0 ? std::to_string(seqNr) : "" }, |
| { "label_atom_id", atom } |
| }); |
| |
| } |
| } |
| } |
| } |
|
|
| void PDBFileParser::ConstructSugarTrees(int &asymNr) |
| { |
| for (;;) |
| { |
| |
| auto si = std::find_if(mHets.begin(), mHets.end(), [](const HET &h) |
| { return (h.hetID == "NAG" or h.hetID == "NDG") and not(h.processed or h.branch); }); |
| if (si != mHets.end()) |
| { |
| si->processed = true; |
|
|
| |
| std::set<char> ci; |
|
|
| for (auto a : si->atoms) |
| { |
| std::string name = a->vS(13, 16); |
|
|
| if (name != "C1") |
| continue; |
|
|
| ci.insert(a->vC(17)); |
| } |
|
|
| if (ci.empty()) |
| continue; |
|
|
| for (auto alt : ci) |
| { |
| ATOM_REF c1{ "C1", si->hetID, si->seqNum, si->chainID, si->iCode, alt }; |
|
|
| const auto &[asn, linked] = FindLink(c1, "ND2", "ASN"); |
| if (not linked) |
| continue; |
|
|
| std::stack<ATOM_REF> c1s; |
| c1s.push(c1); |
|
|
| SUGAR_TREE sugarTree; |
| sugarTree.push_back({ c1 }); |
|
|
| |
| while (not c1s.empty()) |
| { |
| c1 = c1s.top(); |
| c1s.pop(); |
|
|
| for (auto o : { "O1", "O2", "O3", "O4", "O5", "O6" }) |
| { |
| ATOM_REF leaving = c1; |
| leaving.name = o; |
|
|
| const auto &[nc1, linked_c1] = FindLink(leaving, "C1"); |
| if (linked_c1) |
| { |
| sugarTree.push_back({ nc1, o[1] - '0', c1 }); |
| c1s.push(nc1); |
| } |
| } |
| } |
|
|
| if (sugarTree.size() < 2) |
| continue; |
|
|
| auto branchName = sugarTree.entityName(); |
| auto entityID = mBranch2EntityID[branchName]; |
|
|
| |
| if (entityID.empty()) |
| { |
| entityID = std::to_string(mNextEntityNr++); |
| mBranch2EntityID[branchName] = entityID; |
|
|
| |
| getCategory("entity")->emplace({ |
| { "id", entityID }, |
| { "type", "branched" }, |
| { "src_method", "man" }, |
| { "pdbx_description", branchName } |
| }); |
|
|
| getCategory("pdbx_entity_branch")->emplace({ |
| { "entity_id", entityID }, |
| { "type", "oligosaccharide" } |
| }); |
| |
|
|
| int num = 0; |
| std::map<ATOM_REF, int> branch_list; |
|
|
| for (auto &s : sugarTree) |
| { |
| |
| getCategory("pdbx_entity_branch_list")->emplace({ |
| { "entity_id", entityID }, |
| { "comp_id", s.c1.resName }, |
| { "num", ++num }, |
| { "hetero", ci.size() == 1 ? "n" : "y" } |
| }); |
| |
|
|
| branch_list[s.c1] = num; |
| } |
|
|
| auto &branch_link = *getCategory("pdbx_entity_branch_link"); |
|
|
| for (auto &s : sugarTree) |
| { |
| if (s.leaving_o == 0) |
| continue; |
|
|
| |
| branch_link.emplace({ |
| { "link_id", branch_link.size() + 1 }, |
| { "entity_id", entityID }, |
| { "entity_branch_list_num_1", branch_list[s.c1] }, |
| { "comp_id_1", s.c1.resName }, |
| { "atom_id_1", s.c1.name }, |
| { "leaving_atom_id_1", "O1" }, |
| { "entity_branch_list_num_2", branch_list[s.next] }, |
| { "comp_id_2", s.next.resName }, |
| { "atom_id_2", "O" + std::to_string(s.leaving_o) }, |
| { "leaving_atom_id_2", "HO" + std::to_string(s.leaving_o) }, |
| { "value_order", "sing" } |
| }); |
| |
| } |
| } |
|
|
| mSugarEntities.insert(entityID); |
|
|
| |
|
|
| std::string asymID = cif::cif_id_for_number(asymNr++); |
|
|
| mAsymID2EntityID[asymID] = entityID; |
|
|
| |
| getCategory("struct_asym")->emplace({ |
| { "id", asymID }, |
| { "pdbx_blank_PDB_chainid_flag", si->chainID == ' ' ? "Y" : "N" }, |
| { "pdbx_modified", "N" }, |
| { "entity_id", entityID } |
| }); |
| |
|
|
| std::string iCode{ si->iCode }; |
| cif::trim(iCode); |
| if (iCode.empty()) |
| iCode = { '.' }; |
|
|
| int num = 0; |
| for (auto s : sugarTree) |
| { |
| |
| getCategory("pdbx_branch_scheme")->emplace({ |
| { "asym_id", asymID }, |
| { "entity_id", entityID }, |
| { "mon_id", s.c1.resName }, |
| { "num", ++num }, |
| { "pdb_asym_id", asymID }, |
| { "pdb_mon_id", s.c1.resName }, |
| { "pdb_seq_num", num }, |
| { "auth_asym_id", std::string{ s.c1.chainID } }, |
| { "auth_mon_id", s.next.resName }, |
| { "auth_seq_num", s.c1.resSeq }, |
| { "hetero", ci.size() == 1 ? "n" : "y" } |
| }); |
| |
|
|
| auto k = std::make_tuple(s.c1.chainID, s.c1.resSeq, s.c1.iCode); |
| assert(mChainSeq2AsymSeq.count(k) == 0); |
|
|
| mChainSeq2AsymSeq[k] = std::make_tuple(asymID, num, false); |
|
|
| |
|
|
| for (auto &h : mHets) |
| { |
| if (h.hetID == s.c1.resName and h.chainID == s.c1.chainID and h.seqNum == s.c1.resSeq and h.iCode == s.c1.iCode) |
| { |
| h.branch = true; |
| break; |
| } |
| } |
| } |
|
|
| break; |
| } |
|
|
| continue; |
| } |
|
|
| break; |
| } |
|
|
| |
| mHets.erase(std::remove_if(mHets.begin(), mHets.end(), [](auto &h) |
| { return h.branch; }), |
| mHets.end()); |
| } |
|
|
| void PDBFileParser::ParseSecondaryStructure() |
| { |
| bool firstHelix = true; |
|
|
| while (mRec->is("HELIX ")) |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| std::string begAsymID, endAsymID; |
| int begSeq, endSeq; |
| std::error_code ec; |
|
|
| std::tie(begAsymID, begSeq, std::ignore) = MapResidue(vC(20), vI(22, 25), vC(26), ec); |
| if (not ec) |
| std::tie(endAsymID, endSeq, std::ignore) = MapResidue(vC(32), vI(34, 37), vC(38), ec); |
|
|
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Could not map residue for HELIX " << vI(8, 10) << '\n'; |
| } |
| else |
| { |
| auto cat = getCategory("struct_conf"); |
| |
| cat->emplace({ |
| { "conf_type_id", "HELX_P" }, |
| { "id", "HELX_P" + std::to_string(vI(8, 10)) }, |
| { "pdbx_PDB_helix_id", vS(12, 14) }, |
| { "beg_label_comp_id", vS(16, 18) }, |
| { "beg_label_asym_id", begAsymID }, |
| { "beg_label_seq_id", begSeq }, |
| { "pdbx_beg_PDB_ins_code", vS(26, 26) }, |
| { "end_label_comp_id", vS(28, 30) }, |
| { "end_label_asym_id", endAsymID }, |
| { "end_label_seq_id", endSeq }, |
| { "pdbx_end_PDB_ins_code", vS(38, 38) }, |
|
|
| { "beg_auth_comp_id", vS(16, 18) }, |
| { "beg_auth_asym_id", vS(20, 20) }, |
| { "beg_auth_seq_id", vI(22, 25) }, |
| { "end_auth_comp_id", vS(28, 30) }, |
| { "end_auth_asym_id", vS(32, 32) }, |
| { "end_auth_seq_id", vI(34, 37) }, |
|
|
| { "pdbx_PDB_helix_class", vS(39, 40) }, |
| { "details", vS(41, 70) }, |
| { "pdbx_PDB_helix_length", vI(72, 76) } |
| }); |
| |
|
|
| if (firstHelix) |
| { |
| cat = getCategory("struct_conf_type"); |
| cat->emplace({ { "id", "HELX_P" } }); |
| firstHelix = false; |
| } |
| } |
|
|
| GetNextRecord(); |
| } |
|
|
| std::set<std::string> sheetsSeen; |
| int rangeID = 1; |
|
|
| while (mRec->is("SHEET ")) |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| std::string sheetID = cif::trim_copy(vS(12, 14)); |
| if (sheetsSeen.count(sheetID) == 0) |
| { |
| sheetsSeen.insert(sheetID); |
|
|
| rangeID = 1; |
|
|
| getCategory("struct_sheet")->emplace({ |
| { "id", sheetID }, |
| { "number_strands", vI(15, 16) }, |
| }); |
| } |
|
|
| int sense = vI(39, 40); |
|
|
| if (sense != 0) |
| { |
| |
| getCategory("struct_sheet_order")->emplace({ |
| { "sheet_id", sheetID }, |
| { "range_id_1", rangeID }, |
| { "range_id_2", rangeID + 1 }, |
| { "sense", sense == -1 ? "anti-parallel" : "parallel" } |
| }); |
| |
| } |
|
|
| std::string begAsymID, endAsymID; |
| int begSeq, endSeq; |
| std::error_code ec; |
|
|
| std::tie(begAsymID, begSeq, std::ignore) = MapResidue(vC(22), vI(23, 26), vC(27), ec); |
| if (not ec) |
| std::tie(endAsymID, endSeq, std::ignore) = MapResidue(vC(33), vI(34, 37), vC(38), ec); |
|
|
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Dropping SHEET record " << vI(8, 10) << '\n'; |
| } |
| else |
| { |
| |
| getCategory("struct_sheet_range")->emplace({ |
| { "sheet_id", sheetID }, |
| { "id", vI(8, 10) }, |
| { "beg_label_comp_id", vS(18, 20) }, |
| { "beg_label_asym_id", begAsymID }, |
| { "beg_label_seq_id", begSeq }, |
| { "pdbx_beg_PDB_ins_code", vS(27, 27) }, |
| { "end_label_comp_id", vS(29, 31) }, |
| { "end_label_asym_id", endAsymID }, |
| { "end_label_seq_id", endSeq }, |
| { "pdbx_end_PDB_ins_code", vS(38, 38) }, |
|
|
| { "beg_auth_comp_id", vS(18, 20) }, |
| { "beg_auth_asym_id", vS(22, 22) }, |
| { "beg_auth_seq_id", vI(23, 26) }, |
| { "end_auth_comp_id", vS(29, 31) }, |
| { "end_auth_asym_id", vS(33, 33) }, |
| { "end_auth_seq_id", vI(34, 37) }, |
| }); |
| |
|
|
| if (sense != 0 and mRec->mVlen > 34) |
| { |
| std::string r1AsymID, r2AsymID; |
| int r1Seq, r2Seq; |
|
|
| std::tie(r1AsymID, r1Seq, std::ignore) = MapResidue(vC(65), vI(66, 69), vC(70), ec); |
| if (not ec) |
| std::tie(r2AsymID, r2Seq, std::ignore) = MapResidue(vC(50), vI(51, 54), vC(55), ec); |
|
|
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "skipping unmatched pdbx_struct_sheet_hbond record\n"; |
| } |
| else |
| |
| getCategory("pdbx_struct_sheet_hbond")->emplace({ |
| { "sheet_id", sheetID }, |
| { "range_id_1", rangeID }, |
| { "range_id_2", rangeID + 1 }, |
| { "range_1_label_atom_id", vS(57, 60) }, |
| { "range_1_label_comp_id", vS(61, 63) }, |
| { "range_1_label_asym_id", r1AsymID }, |
| { "range_1_label_seq_id", r1Seq }, |
| { "range_1_PDB_ins_code", vS(70, 70) }, |
| { "range_1_auth_atom_id", vS(57, 60) }, |
| { "range_1_auth_comp_id", vS(61, 63) }, |
| { "range_1_auth_asym_id", vS(65, 65) }, |
| { "range_1_auth_seq_id", vI(66, 69) }, |
|
|
| { "range_2_label_atom_id", vS(42, 45) }, |
| { "range_2_label_comp_id", vS(46, 48) }, |
| { "range_2_label_asym_id", r2AsymID }, |
| { "range_2_label_seq_id", r2Seq }, |
| { "range_2_PDB_ins_code", vS(55, 55) }, |
| { "range_2_auth_atom_id", vS(42, 45) }, |
| { "range_2_auth_comp_id", vS(46, 48) }, |
| { "range_2_auth_asym_id", vS(50, 50) }, |
| { "range_2_auth_seq_id", vI(51, 54) } |
| }); |
| |
| } |
|
|
| if (sense != 0) |
| ++rangeID; |
| } |
|
|
| GetNextRecord(); |
| } |
| } |
|
|
| static bool IsMetal(const std::string &resName, const std::string &atomID) |
| { |
| bool result = false; |
|
|
| try |
| { |
| auto compound = cif::compound_factory::instance().create(resName); |
| if (compound != nullptr) |
| { |
| auto at = cif::atom_type_traits(compound->get_atom_by_atom_id(atomID).type_symbol); |
| result = at.is_metal(); |
| } |
| } |
| catch (...) |
| { |
| } |
|
|
| return result; |
| } |
|
|
| void PDBFileParser::ParseConnectivtyAnnotation() |
| { |
| int ssBondNr = 0; |
| int linkNr = 0; |
| bool firstCovale = true, firstMetalc = true; |
|
|
| |
| for (;; GetNextRecord()) |
| { |
| if (mRec->is("SSBOND")) |
| { |
| if (ssBondNr == 0) |
| { |
| getCategory("struct_conn_type")->emplace({ |
| { "id", "disulf" }, |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| std::string p1Asym, p2Asym; |
| int p1Seq = 0, p2Seq = 0; |
| std::error_code ec; |
|
|
| std::tie(p1Asym, p1Seq, std::ignore) = MapResidue(vC(16), vI(18, 21), vC(22), ec); |
| if (not ec) |
| std::tie(p2Asym, p2Seq, std::ignore) = MapResidue(vC(30), vI(32, 35), vC(36), ec); |
|
|
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Dropping SSBOND " << vI(8, 10) << '\n'; |
| continue; |
| } |
|
|
| std::vector<char> alt1 = altLocsForAtom(vC(16), vI(18, 21), vC(22), "SG"); |
| std::vector<char> alt2 = altLocsForAtom(vC(30), vI(32, 35), vC(36), "SG"); |
|
|
| if (alt1.empty()) |
| alt1.push_back(0); |
| if (alt2.empty()) |
| alt2.push_back(0); |
|
|
| std::string sym1, sym2; |
| try |
| { |
| sym1 = pdb2cifSymmetry(vS(60, 65)); |
| sym2 = pdb2cifSymmetry(vS(67, 72)); |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Dropping SSBOND " << vI(8, 10) << " due to invalid symmetry operation\n"; |
| continue; |
| } |
|
|
| for (auto a1 : alt1) |
| { |
| for (auto a2 : alt2) |
| { |
| |
| getCategory("struct_conn")->emplace({ |
| { "id", "disulf" + std::to_string(++ssBondNr) }, |
| { "conn_type_id", "disulf" }, |
|
|
| { "ptnr1_label_asym_id", p1Asym }, |
| { "pdbx_ptnr1_label_alt_id", a1 ? std::string{ a1 } : std::string() }, |
| { "ptnr1_label_comp_id", vS(12, 14) }, |
| { "ptnr1_label_seq_id", p1Seq ? std::to_string(p1Seq) : "." }, |
| { "ptnr1_label_atom_id", "SG" }, |
| { "ptnr1_symmetry", sym1 }, |
|
|
| { "ptnr2_label_asym_id", p2Asym }, |
| { "pdbx_ptnr2_label_alt_id", a2 ? std::string{ a2 } : std::string() }, |
| { "ptnr2_label_comp_id", vS(26, 28) }, |
| { "ptnr2_label_seq_id", p2Seq ? std::to_string(p2Seq) : "." }, |
| { "ptnr2_label_atom_id", "SG" }, |
|
|
| { "ptnr1_auth_asym_id", vS(16, 16) }, |
| { "ptnr1_auth_comp_id", vS(12, 14) }, |
| { "ptnr1_auth_seq_id", vI(18, 21) }, |
| { "ptnr2_auth_asym_id", vS(30, 30) }, |
| { "ptnr2_auth_comp_id", vS(26, 28) }, |
| { "ptnr2_auth_seq_id", vI(32, 35) }, |
|
|
| { "ptnr2_symmetry", sym2 }, |
|
|
| { "pdbx_dist_value", vS(74, 78) }, |
| }); |
| |
| } |
| } |
|
|
| continue; |
| } |
|
|
| if (mRec->is("LINK ") or mRec->is("LINKR ")) |
| { |
| if (cif::VERBOSE > 0 and mRec->is("LINKR ")) |
| std::cerr << "Accepting non-standard LINKR record, but ignoring extra information\n"; |
|
|
| |
| std::string name1 = vS(13, 16); |
| |
| std::string resName1 = vS(18, 20); |
| |
| |
| |
| std::string name2 = vS(43, 46); |
| |
| std::string resName2 = vS(48, 50); |
| |
| |
| |
| |
| |
| |
|
|
| std::string type = "covale"; |
| if (IsMetal(resName1, name1) or IsMetal(resName2, name2)) |
| type = "metalc"; |
|
|
| if (type == "covale" and firstCovale) |
| { |
| getCategory("struct_conn_type")->emplace({ |
| { "id", type }, |
| }); |
| firstCovale = false; |
| } |
|
|
| if (type == "metalc" and firstMetalc) |
| { |
| getCategory("struct_conn_type")->emplace({ |
| { "id", type }, |
| }); |
| firstMetalc = false; |
| } |
|
|
| ++linkNr; |
|
|
| std::string p1Asym, p2Asym; |
| int p1Seq = 0, p2Seq = 0; |
| bool isResseq1 = false, isResseq2 = false; |
| std::error_code ec; |
|
|
| std::tie(p1Asym, p1Seq, isResseq1) = MapResidue(vC(22), vI(23, 26), vC(27), ec); |
| if (not ec) |
| std::tie(p2Asym, p2Seq, isResseq2) = MapResidue(vC(52), vI(53, 56), vC(57), ec); |
|
|
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Dropping LINK record at line " << mRec->mLineNr << '\n'; |
| continue; |
| } |
|
|
| std::string distance, ccp4LinkID; |
|
|
| if (mRec->is("LINK ")) |
| { |
| distance = vS(74, 78); |
|
|
| double d; |
| auto r = cif::from_chars(distance.data(), distance.data() + distance.length(), d); |
| if ((bool)r.ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Distance value '" << distance << "' is not a valid float in LINK record\n"; |
| swap(ccp4LinkID, distance); |
| } |
| } |
| else |
| ccp4LinkID = vS(74, 78); |
|
|
| std::string sym1, sym2; |
| try |
| { |
| sym1 = pdb2cifSymmetry(vS(60, 65)); |
| sym2 = pdb2cifSymmetry(vS(67, 72)); |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Dropping LINK record at line " << mRec->mLineNr << " due to invalid symmetry operation\n"; |
| continue; |
| } |
|
|
| |
| getCategory("struct_conn")->emplace({ |
| { "id", type + std::to_string(linkNr) }, |
| { "conn_type_id", type }, |
|
|
| |
|
|
| { "ptnr1_label_asym_id", p1Asym }, |
| { "ptnr1_label_comp_id", vS(18, 20) }, |
| { "ptnr1_label_seq_id", (isResseq1 and p1Seq) ? std::to_string(p1Seq) : "." }, |
| { "ptnr1_label_atom_id", vS(13, 16) }, |
| { "pdbx_ptnr1_label_alt_id", vS(17, 17) }, |
| { "pdbx_ptnr1_PDB_ins_code", vS(27, 27) }, |
| { "pdbx_ptnr1_standard_comp_id", "" }, |
| { "ptnr1_symmetry", sym1 }, |
|
|
| { "ptnr2_label_asym_id", p2Asym }, |
| { "ptnr2_label_comp_id", vS(48, 50) }, |
| { "ptnr2_label_seq_id", (isResseq2 and p2Seq) ? std::to_string(p2Seq) : "." }, |
| { "ptnr2_label_atom_id", vS(43, 46) }, |
| { "pdbx_ptnr2_label_alt_id", vS(47, 47) }, |
| { "pdbx_ptnr2_PDB_ins_code", vS(57, 57) }, |
|
|
| { "ptnr1_auth_asym_id", vS(22, 22) }, |
| { "ptnr1_auth_comp_id", vS(18, 20) }, |
| { "ptnr1_auth_seq_id", vI(23, 26) }, |
| { "ptnr2_auth_asym_id", vS(52, 52) }, |
| { "ptnr2_auth_comp_id", vS(48, 50) }, |
| { "ptnr2_auth_seq_id", vI(53, 56) }, |
|
|
| |
| |
|
|
| { "ptnr2_symmetry", sym2 }, |
|
|
| { "pdbx_dist_value", distance } |
| }); |
| |
|
|
| continue; |
| } |
|
|
| if (mRec->is("CISPEP")) |
| { |
| |
| int serNum = vI(8, 10); |
| std::string pep1 = vS(12, 14); |
| char chainID1 = vC(16); |
| int seqNum1 = vI(18, 21); |
| char iCode1 = vC(22); |
| std::string pep2 = vS(26, 28); |
| char chainID2 = vC(30); |
| int seqNum2 = vI(32, 35); |
| char iCode2 = vC(36); |
| int modNum = vI(44, 46); |
| std::string measure = vF(54, 59); |
|
|
| if (modNum == 0) |
| modNum = 1; |
|
|
| std::string lAsym1, lAsym2; |
| int lResSeq1, lResSeq2; |
| std::error_code ec; |
|
|
| std::tie(lAsym1, lResSeq1, std::ignore) = MapResidue(chainID1, seqNum1, iCode1, ec); |
| if (not ec) |
| std::tie(lAsym2, lResSeq2, std::ignore) = MapResidue(chainID2, seqNum2, iCode2, ec); |
|
|
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Dropping CISPEP record at line " << mRec->mLineNr << '\n'; |
| continue; |
| } |
|
|
| std::string iCode1str = iCode1 == ' ' ? std::string() : std::string{ iCode1 }; |
| std::string iCode2str = iCode2 == ' ' ? std::string() : std::string{ iCode2 }; |
|
|
| |
| getCategory("struct_mon_prot_cis")->emplace({ |
| { "pdbx_id", serNum }, |
| { "label_comp_id", pep1 }, |
| { "label_seq_id", lResSeq1 }, |
| { "label_asym_id", lAsym1 }, |
| { "label_alt_id", "." }, |
| { "pdbx_PDB_ins_code", iCode1str }, |
| { "auth_comp_id", pep1 }, |
| { "auth_seq_id", seqNum1 }, |
| { "auth_asym_id", std::string{ chainID1 } }, |
| { "pdbx_label_comp_id_2", pep2 }, |
| { "pdbx_label_seq_id_2", lResSeq2 }, |
| { "pdbx_label_asym_id_2", lAsym2 }, |
| { "pdbx_PDB_ins_code_2", iCode2str }, |
| { "pdbx_auth_comp_id_2", pep2 }, |
| { "pdbx_auth_seq_id_2", seqNum2 }, |
| { "pdbx_auth_asym_id_2", std::string{ chainID2 } }, |
| { "pdbx_PDB_model_num", modNum }, |
| { "pdbx_omega_angle", measure } |
| }); |
| |
|
|
| continue; |
| } |
|
|
| break; |
| } |
| } |
|
|
| void PDBFileParser::ParseMiscellaneousFeatures() |
| { |
| int structSiteGenID = 1; |
|
|
| while (mRec->is("SITE ")) |
| { |
| |
| std::string siteID = vS(12, 14); |
| int numRes = vI(16, 17); |
|
|
| int o = 19; |
|
|
| auto cat = getCategory("struct_site_gen"); |
|
|
| for (int i = 0; i < numRes; ++i) |
| { |
| std::string resName = vS(o, o + 2); |
| |
| char chainID = vC(o + 4); |
| int seq = vI(o + 5, o + 8); |
| |
| char iCode = vC(o + 9); |
|
|
| int labelSeq; |
| std::string asym; |
| bool isResseq; |
| std::error_code ec; |
|
|
| std::tie(asym, labelSeq, isResseq) = MapResidue(chainID, seq, iCode, ec); |
|
|
| if (ec) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "skipping struct_site_gen record\n"; |
| } |
| else |
| |
| cat->emplace({ |
| { "id", structSiteGenID++ }, |
| { "site_id", siteID }, |
| { "pdbx_num_res", numRes }, |
| { "label_comp_id", resName }, |
| { "label_asym_id", asym }, |
| { "label_seq_id", (labelSeq > 0 and isResseq) ? std::to_string(labelSeq) : std::string(".") }, |
| { "pdbx_auth_ins_code", iCode == ' ' ? "" : std::string{ iCode } }, |
| { "auth_comp_id", resName }, |
| { "auth_asym_id", std::string{ chainID } }, |
| { "auth_seq_id", seq }, |
| { "label_atom_id", "." }, |
| { "label_alt_id", "." }, |
| }); |
| |
|
|
| o += 11; |
| } |
|
|
| GetNextRecord(); |
| } |
| } |
|
|
| void PDBFileParser::ParseCrystallographic() |
| { |
| if (mRec->is("CRYST1")) |
| { |
| Match("CRYST1", true); |
|
|
| |
| getCategory("cell")->emplace({ |
| { "entry_id", mStructureID }, |
| { "length_a", vF(7, 15) }, |
| { "length_b", vF(16, 24) }, |
| { "length_c", vF(25, 33) }, |
| { "angle_alpha", vF(34, 40) }, |
| { "angle_beta", vF(41, 47) }, |
| { "angle_gamma", vF(48, 54) }, |
| |
| { "Z_PDB", vF(67, 70) } |
| }); |
| |
|
|
| std::string spaceGroup, intTablesNr; |
| try |
| { |
| spaceGroup = vS(56, 66); |
| intTablesNr = std::to_string(get_space_group_number(spaceGroup)); |
| } |
| catch (...) |
| { |
| } |
|
|
| |
| getCategory("symmetry")->emplace({ |
| { "entry_id", mStructureID }, |
| { "space_group_name_H-M", spaceGroup }, |
| { "Int_Tables_number", intTablesNr } |
| }); |
|
|
| GetNextRecord(); |
| } |
| } |
|
|
| void PDBFileParser::ParseCoordinateTransformation() |
| { |
| std::string m[3][3], v[3]; |
|
|
| if (cif::starts_with(mRec->mName, "ORIGX")) |
| { |
| for (std::string n : { "1", "2", "3" }) |
| { |
| int x = stoi(n) - 1; |
|
|
| Match("ORIGX" + n, true); |
| m[x][0] = vF(11, 20); |
| m[x][1] = vF(21, 30); |
| m[x][2] = vF(31, 40); |
| v[x] = vF(46, 55); |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| getCategory("database_PDB_matrix")->emplace({ |
| { "entry_id", mStructureID }, |
| { "origx[1][1]", m[0][0] }, |
| { "origx[1][2]", m[0][1] }, |
| { "origx[1][3]", m[0][2] }, |
| { "origx[2][1]", m[1][0] }, |
| { "origx[2][2]", m[1][1] }, |
| { "origx[2][3]", m[1][2] }, |
| { "origx[3][1]", m[2][0] }, |
| { "origx[3][2]", m[2][1] }, |
| { "origx[3][3]", m[2][2] }, |
| { "origx_vector[1]", v[0] }, |
| { "origx_vector[2]", v[1] }, |
| { "origx_vector[3]", v[2] }, |
| }); |
| |
| } |
|
|
| if (cif::starts_with(mRec->mName, "SCALE")) |
| { |
| for (std::string n : { "1", "2", "3" }) |
| { |
| int x = stoi(n) - 1; |
|
|
| Match("SCALE" + n, true); |
| m[x][0] = vF(11, 20); |
| m[x][1] = vF(21, 30); |
| m[x][2] = vF(31, 40); |
| v[x] = vF(46, 55); |
|
|
| GetNextRecord(); |
| } |
|
|
| |
| getCategory("atom_sites")->emplace({ |
| { "entry_id", mStructureID }, |
| { "fract_transf_matrix[1][1]", m[0][0] }, |
| { "fract_transf_matrix[1][2]", m[0][1] }, |
| { "fract_transf_matrix[1][3]", m[0][2] }, |
| { "fract_transf_matrix[2][1]", m[1][0] }, |
| { "fract_transf_matrix[2][2]", m[1][1] }, |
| { "fract_transf_matrix[2][3]", m[1][2] }, |
| { "fract_transf_matrix[3][1]", m[2][0] }, |
| { "fract_transf_matrix[3][2]", m[2][1] }, |
| { "fract_transf_matrix[3][3]", m[2][2] }, |
| { "fract_transf_vector[1]", v[0] }, |
| { "fract_transf_vector[2]", v[1] }, |
| { "fract_transf_vector[3]", v[2] }, |
| }); |
| |
| } |
|
|
| while (cif::starts_with(mRec->mName, "MTRIX1")) |
| { |
| int serial = 0, igiven = 0; |
|
|
| for (std::string n : { "1", "2", "3" }) |
| { |
| int x = stoi(n) - 1; |
|
|
| Match("MTRIX" + n, true); |
| serial = vI(8, 10); |
| m[x][0] = vF(11, 20); |
| m[x][1] = vF(21, 30); |
| m[x][2] = vF(31, 40); |
| v[x] = vF(46, 55); |
| igiven = vC(60) == '1'; |
| |
| GetNextRecord(); |
| } |
|
|
| |
| getCategory("struct_ncs_oper")->emplace({ |
| { "id", serial }, |
| { "matrix[1][1]", m[0][0] }, |
| { "matrix[1][2]", m[0][1] }, |
| { "matrix[1][3]", m[0][2] }, |
| { "matrix[2][1]", m[1][0] }, |
| { "matrix[2][2]", m[1][1] }, |
| { "matrix[2][3]", m[1][2] }, |
| { "matrix[3][1]", m[2][0] }, |
| { "matrix[3][2]", m[2][1] }, |
| { "matrix[3][3]", m[2][2] }, |
| { "vector[1]", v[0] }, |
| { "vector[2]", v[1] }, |
| { "vector[3]", v[2] }, |
| { "code", igiven ? "given" : "" } |
| }); |
| |
| } |
| } |
|
|
| void PDBFileParser::ParseCoordinate(int modelNr) |
| { |
| |
| |
|
|
| typedef std::tuple<std::string, int, bool, PDBRecord *, PDBRecord *> atomRec; |
|
|
| std::vector<atomRec> atoms; |
| while (mRec->is("ATOM ") or mRec->is("HETATM")) |
| { |
| char chainID = vC(22); |
| int resSeq = vI(23, 26); |
| char iCode = vC(27); |
|
|
| std::string asymID; |
| int seqID; |
| bool isResseq; |
|
|
| std::tie(asymID, seqID, isResseq) = MapResidue(chainID, resSeq, iCode); |
|
|
| PDBRecord *atom = mRec; |
| PDBRecord *anisou = nullptr; |
|
|
| GetNextRecord(); |
| if (mRec->is("ANISOU")) |
| { |
| anisou = mRec; |
| GetNextRecord(); |
| } |
|
|
| atoms.emplace_back(asymID, seqID, isResseq, atom, anisou); |
|
|
| while (mRec->is("TER ")) |
| { |
| Match("TER ", true); |
| GetNextRecord(); |
| } |
| } |
|
|
| auto last = mRec; |
|
|
| |
| auto rLess = [](const atomRec &a, const atomRec &b) -> bool |
| { |
| int d; |
|
|
| std::string chainA = std::get<0>(a); |
| std::string chainB = std::get<0>(b); |
|
|
| if (chainA.length() != chainB.length()) |
| d = static_cast<int>(chainA.length() - chainB.length()); |
| else |
| d = std::get<0>(a).compare(std::get<0>(b)); |
|
|
| if (d == 0) |
| d = std::get<1>(a) - std::get<1>(b); |
| return d < 0; |
| }; |
|
|
| stable_sort(atoms.begin(), atoms.end(), rLess); |
|
|
| |
| for (size_t i = 0; i + 1 < atoms.size(); ++i) |
| { |
| char altLoc = std::get<3>(atoms[i])->vC(17); |
|
|
| if (altLoc == ' ' or altLoc == 0) |
| continue; |
|
|
| auto b = atoms.begin() + i; |
| auto e = b; |
|
|
| std::map<std::string, int> atomIndex; |
|
|
| while (e != atoms.end() and rLess(*b, *e) == false) |
| { |
| std::string name = std::get<3>(*e)->vS(13, 16); |
|
|
| if (atomIndex.count(name) == 0) |
| atomIndex[name] = static_cast<int>(atomIndex.size() + 1); |
|
|
| ++e; |
| } |
|
|
| auto aLess = [&](atomRec &a, atomRec &b) -> bool |
| { |
| std::string na = std::get<3>(a)->vS(13, 16); |
| std::string nb = std::get<3>(b)->vS(13, 16); |
|
|
| int d = atomIndex[na] - atomIndex[nb]; |
| if (d == 0) |
| d = std::get<3>(a)->vC(17) - std::get<3>(b)->vC(17); |
| assert(d != 0); |
| return d < 0; |
| }; |
|
|
| sort(b, e, aLess); |
|
|
| i += distance(b, e) - 1; |
| } |
|
|
| |
| for (auto &a : atoms) |
| { |
| std::string asymID; |
| int seqID; |
| bool isResseq; |
| PDBRecord *atom; |
| PDBRecord *anisou; |
| std::tie(asymID, seqID, isResseq, atom, anisou) = a; |
|
|
| mRec = atom; |
|
|
| ++mAtomID; |
|
|
| std::string groupPDB = mRec->is("ATOM ") ? "ATOM" : "HETATM"; |
| |
| std::string name = vS(13, 16); |
| char altLoc = vC(17); |
| std::string resName = vS(18, 20); |
| char chainID = vC(22); |
| int resSeq = vI(23, 26); |
| char iCode = vC(27); |
| std::string x = vF(31, 38); |
| std::string y = vF(39, 46); |
| std::string z = vF(47, 54); |
| std::string occupancy = vF(55, 60); |
| std::string tempFactor = vF(61, 66); |
| std::string element = vS(77, 78); |
| std::string charge = vS(79, 80); |
|
|
| std::string entityID = mAsymID2EntityID[asymID]; |
|
|
| charge = pdb2cifCharge(charge); |
|
|
| |
| if (resName == "UNK" or cif::compound_factory::kAAMap.count(resName) or cif::compound_factory::kBaseMap.count(resName)) |
| { |
| if (groupPDB == "HETATM") |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Changing atom from HETATM to ATOM at line " << mRec->mLineNr << '\n'; |
| groupPDB = "ATOM"; |
| } |
| } |
| else |
| { |
| if (groupPDB == "ATOM") |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Changing atom from ATOM to HETATM at line " << mRec->mLineNr << '\n'; |
| groupPDB = "HETATM"; |
| } |
| } |
|
|
| |
| if (mSugarEntities.count(entityID)) |
| { |
| using namespace cif::literals; |
|
|
| auto &branch_scheme = *getCategory("pdbx_branch_scheme"); |
| resSeq = branch_scheme.find1<int>("asym_id"_key == asymID and "auth_seq_num"_key == resSeq, "pdb_seq_num"); |
| } |
|
|
| |
| getCategory("atom_site")->emplace({ |
| { "group_PDB", groupPDB }, |
| { "id", mAtomID }, |
| { "type_symbol", element }, |
| { "label_atom_id", name }, |
| { "label_alt_id", altLoc != ' ' ? std::string{ altLoc } : "." }, |
| { "label_comp_id", resName }, |
| { "label_asym_id", asymID }, |
| { "label_entity_id", entityID }, |
| { "label_seq_id", (isResseq and seqID > 0) ? std::to_string(seqID) : "." }, |
| { "pdbx_PDB_ins_code", iCode == ' ' ? "" : std::string{ iCode } }, |
| { "Cartn_x", x }, |
| { "Cartn_y", y }, |
| { "Cartn_z", z }, |
| { "occupancy", occupancy }, |
| { "B_iso_or_equiv", tempFactor }, |
| { "pdbx_formal_charge", charge }, |
| { "auth_seq_id", resSeq }, |
| { "auth_comp_id", resName }, |
| { "auth_asym_id", std::string{ chainID } }, |
| { "auth_atom_id", name }, |
| { "pdbx_PDB_model_num", modelNr } |
| }); |
| |
|
|
| InsertAtomType(element); |
|
|
| std::string check = vS(7, 11) + vS(77, 80); |
|
|
| if (anisou != nullptr) |
| { |
| mRec = anisou; |
| int u11 = vI(29, 35); |
| int u22 = vI(36, 42); |
| int u33 = vI(43, 49); |
| int u12 = vI(50, 56); |
| int u13 = vI(57, 63); |
| int u23 = vI(64, 70); |
|
|
| if (vS(7, 11) + vS(77, 80) != check) |
| throw std::runtime_error("ANISOU record should follow corresponding ATOM record"); |
|
|
| auto f = [](float f) -> std::string |
| { |
| return cif::format("%6.4f", f).str(); |
| }; |
|
|
| |
| getCategory("atom_site_anisotrop")->emplace({ |
| { "id", mAtomID }, |
| { "type_symbol", element }, |
| { "pdbx_label_atom_id", name }, |
| { "pdbx_label_alt_id", altLoc != ' ' ? std::string{ altLoc } : "." }, |
| { "pdbx_label_comp_id", resName }, |
| { "pdbx_label_asym_id", asymID }, |
| { "pdbx_label_seq_id", (isResseq and seqID > 0) ? std::to_string(seqID) : "." }, |
| { "U[1][1]", f(u11 / 10000.f) }, |
| { "U[2][2]", f(u22 / 10000.f) }, |
| { "U[3][3]", f(u33 / 10000.f) }, |
| { "U[1][2]", f(u12 / 10000.f) }, |
| { "U[1][3]", f(u13 / 10000.f) }, |
| { "U[2][3]", f(u23 / 10000.f) }, |
| { "pdbx_auth_seq_id", resSeq }, |
| { "pdbx_auth_comp_id", resName }, |
| { "pdbx_auth_asym_id", std::string{ chainID } }, |
| { "pdbx_auth_atom_id", name } |
| }); |
| |
| } |
| } |
|
|
| mRec = last; |
| } |
|
|
| void PDBFileParser::ParseConnectivty() |
| { |
| while (mRec->is("CONECT")) |
| GetNextRecord(); |
| } |
|
|
| void PDBFileParser::ParseBookkeeping() |
| { |
| if (mRec->is("MASTER")) |
| { |
| Match("MASTER", false); |
| GetNextRecord(); |
| } |
| Match("END ", false); |
| } |
|
|
| void PDBFileParser::Parse(std::istream &is, cif::file &result) |
| { |
| try |
| { |
| mDatablock.set_validator(result.get_validator()); |
|
|
| PreParseInput(is); |
|
|
| mRec = mData; |
|
|
| ParseTitle(); |
|
|
| ParseRemarks(); |
| ParsePrimaryStructure(); |
| ParseHeterogen(); |
|
|
| ConstructEntities(); |
|
|
| ParseRemark350(); |
|
|
| ParseSecondaryStructure(); |
| ParseConnectivtyAnnotation(); |
| ParseMiscellaneousFeatures(); |
| ParseCrystallographic(); |
| ParseCoordinateTransformation(); |
|
|
| uint32_t modelNr = 1; |
| bool hasAtoms = false; |
|
|
| while (mRec->is("MODEL ") or mRec->is("ATOM ") or mRec->is("HETATM")) |
| { |
| bool model = false; |
| if (mRec->is("MODEL ")) |
| { |
| model = true; |
|
|
| modelNr = vI(11, 14); |
|
|
| GetNextRecord(); |
| } |
|
|
| hasAtoms = hasAtoms or mRec->is("ATOM ") or mRec->is("HETATM"); |
|
|
| ParseCoordinate(modelNr); |
|
|
| if (model) |
| { |
| Match("ENDMDL", true); |
| GetNextRecord(); |
| } |
| } |
|
|
| if (not hasAtoms) |
| throw std::runtime_error("Either the PDB file has no atom records, or the field " + std::string(mRec->mName) + " is not at the correct location"); |
|
|
| for (auto e : mAtomTypes) |
| getCategory("atom_type")->emplace({ { "symbol", e } }); |
|
|
| |
| getCategory("atom_type")->reorder_by_index(); |
|
|
| ParseConnectivty(); |
| ParseBookkeeping(); |
|
|
| |
|
|
| try |
| { |
| auto r = FindRecord("REMARK 3"); |
|
|
| if (r != nullptr and Remark3Parser::parse(mExpMethod, r, mDatablock)) |
| { |
| |
| auto exptl = getCategory("exptl"); |
| if (exptl->empty()) |
| { |
| exptl->emplace({ { "entry_id", mStructureID }, |
| { "method", mExpMethod }, |
| { "crystals_number", mRemark200["NUMBER OF CRYSTALS USED"] } }); |
| } |
| } |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE >= 0) |
| std::cerr << "Error parsing REMARK 3\n"; |
| throw; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| using namespace cif::literals; |
|
|
| auto &atom_site = *getCategory("atom_site"); |
|
|
| for (auto r : getCategory("struct_conn")->find("pdbx_dist_value"_key == 0 or "pdbx_dist_value"_key == cif::null)) |
| { |
| const auto &[asym1, seq1, atom1, symm1, asym2, seq2, atom2, symm2] = r.get<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>( |
| "ptnr1_label_asym_id", "ptnr1_label_seq_id", "ptnr1_label_atom_id", "ptnr1_symmetry", |
| "ptnr2_label_asym_id", "ptnr2_label_seq_id", "ptnr2_label_atom_id", "ptnr2_symmetry"); |
|
|
| float distance = 1.0f; |
|
|
| try |
| { |
| auto a1 = atom_site.find1("label_asym_id"_key == asym1 and "label_seq_id"_key == seq1 and "label_atom_id"_key == atom1); |
| auto a2 = atom_site.find1("label_asym_id"_key == asym2 and "label_seq_id"_key == seq2 and "label_atom_id"_key == atom2); |
|
|
| if (not a1 or not a2) |
| throw std::runtime_error("cannot find atom"); |
|
|
| const auto &[x1, y1, z1] = a1.get<float, float, float>("cartn_x", "cartn_y", "cartn_z"); |
| const auto &[x2, y2, z2] = a2.get<float, float, float>("cartn_x", "cartn_y", "cartn_z"); |
|
|
| if ((symm1.empty() or symm1 == "1_555") and (symm2.empty() or symm2 == "1_555")) |
| distance = std::sqrt( |
| (x1 - x2) * (x1 - x2) + |
| (y1 - y2) * (y1 - y2) + |
| (z1 - z2) * (z1 - z2)); |
| else if (cif::VERBOSE > 0) |
| std::cerr << "Cannot calculate distance for link since one of the atoms is in another dimension\n"; |
| } |
| catch (std::exception &ex) |
| { |
| if (cif::VERBOSE > 0) |
| std::cerr << "Error finding atom for LINK distance calculation: " << ex.what() << '\n'; |
| } |
|
|
| r["pdbx_dist_value"] = distance; |
| } |
|
|
| result.emplace_back(std::move(mDatablock)); |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE >= 0) |
| { |
| std::cerr << "Error parsing PDB"; |
| if (mRec != nullptr) |
| std::cerr << " at line " << mRec->mLineNr; |
| std::cerr << '\n'; |
| } |
| throw; |
| } |
| } |
|
|
| |
| |
|
|
| |
| |
|
|
| template <typename T> |
| class matrix |
| { |
| public: |
| using value_type = T; |
|
|
| matrix() = delete; |
| matrix(const matrix &) = delete; |
| matrix &operator=(const matrix &) = delete; |
|
|
| matrix(uint32_t m, uint32_t n, T v = T()) |
| : m_m(m) |
| , m_n(n) |
| { |
| m_data = new value_type[m_m * m_n]; |
| std::fill(m_data, m_data + (m_m * m_n), v); |
| } |
|
|
| ~matrix() |
| { |
| delete[] m_data; |
| } |
|
|
| uint32_t dim_m() const { return m_m; } |
| uint32_t dim_n() const { return m_n; } |
|
|
| value_type operator()(uint32_t i, uint32_t j) const |
| { |
| assert(i < m_m); |
| assert(j < m_n); |
| return m_data[i * m_n + j]; |
| } |
|
|
| value_type &operator()(uint32_t i, uint32_t j) |
| { |
| assert(i < m_m); |
| assert(j < m_n); |
| return m_data[i * m_n + j]; |
| } |
|
|
| private: |
| value_type *m_data; |
| uint32_t m_m, m_n; |
| }; |
|
|
| int PDBFileParser::PDBChain::AlignResToSeqRes() |
| { |
| |
| |
| |
|
|
| auto &rx = mSeqres; |
| auto &ry = mResiduesSeen; |
|
|
| int dimX = static_cast<int>(mSeqres.size()); |
| if (dimX == 0) |
| throw std::runtime_error(std::string("SEQRES for chain ") + mDbref.chainID + " is empty"); |
|
|
| int dimY = static_cast<int>(mResiduesSeen.size()); |
| if (dimY == 0) |
| throw std::runtime_error(std::string("Number of residues in ATOM records for chain ") + mDbref.chainID + " is zero"); |
|
|
| matrix<float> B(dimX, dimY), Ix(dimX, dimY), Iy(dimX, dimY); |
| matrix<int8_t> tb(dimX, dimY); |
|
|
| int x, y; |
|
|
| const float |
| kMatchReward = 5, |
| kMismatchCost = -10, |
| kGapOpen = 10, gapExtend = 0.1f; |
|
|
| float high = 0; |
| int highX = 0, highY = 0; |
|
|
| for (x = 0; x < dimX; ++x) |
| { |
| for (y = 0; y < dimY; ++y) |
| { |
| auto &a = rx[x]; |
| auto &b = ry[y]; |
|
|
| float Ix1 = x > 0 ? Ix(x - 1, y) : 0; |
| float Iy1 = y > 0 ? Iy(x, y - 1) : 0; |
|
|
| |
| float M; |
| if (a.mMonID == b.mMonID) |
| M = kMatchReward; |
| else |
| M = kMismatchCost; |
|
|
| |
| |
| float gapOpen = kGapOpen; |
| if (y == 0 or (y + 1 < dimY and ry[y + 1].mSeqNum > ry[y].mSeqNum + 1)) |
| gapOpen = 0; |
|
|
| if (x > 0 and y > 0) |
| M += B(x - 1, y - 1); |
|
|
| float s; |
| if (M >= Ix1 and M >= Iy1) |
| { |
| tb(x, y) = 0; |
| B(x, y) = s = M; |
|
|
| Ix(x, y) = M - (x < dimX - 1 ? gapOpen : 0); |
| Iy(x, y) = M - (y < dimY - 1 ? gapOpen : 0); |
| } |
| else if (Ix1 >= Iy1) |
| { |
| tb(x, y) = 1; |
| B(x, y) = s = Ix1; |
|
|
| Ix(x, y) = Ix1 - gapExtend; |
| Iy(x, y) = M - (y < dimY - 1 ? gapOpen : 0); |
| if (Iy(x, y) < Iy1 - gapExtend) |
| Iy(x, y) = Iy1 - gapExtend; |
| } |
| else |
| { |
| tb(x, y) = -1; |
| B(x, y) = s = Iy1; |
|
|
| Ix(x, y) = M - (x < dimX - 1 ? gapOpen : 0); |
| if (Ix(x, y) < Ix1 - gapExtend) |
| Ix(x, y) = Ix1 - gapExtend; |
| Iy(x, y) = Iy1 - gapExtend; |
| } |
|
|
| if ( high < s) |
| { |
| high = s; |
| highX = x; |
| highY = y; |
| } |
| } |
| } |
|
|
| const int kFlagSeqNr = std::numeric_limits<int>::min(); |
|
|
| |
| for (auto &sr : rx) |
| { |
| sr.mSeqNum = kFlagSeqNr; |
| sr.mIcode = ' '; |
| } |
|
|
| |
| x = highX; |
| y = highY; |
|
|
| |
| auto printAlignment = [&tb, highX, highY, &rx, &ry, this]() |
| { |
| std::cerr << std::string(22, '-') << '\n' |
| << "Alignment for chain " << mDbref.chainID << '\n' |
| << '\n'; |
| std::vector<std::pair<std::string, std::string>> alignment; |
|
|
| int x = highX; |
| int y = highY; |
|
|
| for (x = highX, y = highY; x >= 0 and y >= 0;) |
| { |
| switch (tb(x, y)) |
| { |
| case -1: |
| alignment.push_back(make_pair("...", ry[y].mMonID)); |
| --y; |
| break; |
|
|
| case 1: |
| alignment.push_back(make_pair(rx[x].mMonID, "...")); |
| --x; |
| break; |
|
|
| case 0: |
| alignment.push_back(make_pair(rx[x].mMonID, ry[y].mMonID)); |
| --x; |
| --y; |
| break; |
| } |
| } |
|
|
| while (x >= 0) |
| { |
| alignment.push_back(make_pair(rx[x].mMonID, "...")); |
| --x; |
| } |
|
|
| while (y >= 0) |
| { |
| alignment.push_back(make_pair("...", ry[y].mMonID)); |
| --y; |
| } |
|
|
| reverse(alignment.begin(), alignment.end()); |
| for (auto a : alignment) |
| std::cerr << " " << a.first << " -- " << a.second << '\n'; |
|
|
| std::cerr << '\n'; |
| }; |
|
|
| if (cif::VERBOSE > 1) |
| printAlignment(); |
|
|
| try |
| { |
| while (x >= 0 and y >= 0) |
| { |
| switch (tb(x, y)) |
| { |
| case -1: |
| throw std::runtime_error("A residue found in the ATOM records (" + ry[y].mMonID + |
| " @ " + std::string{ mDbref.chainID } + ":" + std::to_string(ry[y].mSeqNum) + |
| ((ry[y].mIcode == ' ' or ry[y].mIcode == 0) ? "" : std::string{ ry[y].mIcode }) + |
| ") was not found in the SEQRES records"); |
| break; |
|
|
| case 1: |
| if (cif::VERBOSE > 3) |
| std::cerr << "Missing residue in ATOM records: " << rx[x].mMonID << " at " << rx[x].mSeqNum << '\n'; |
|
|
| --x; |
| break; |
|
|
| case 0: |
| if (rx[x].mMonID != ry[y].mMonID) |
| { |
| std::cerr << "Warning, unaligned residues at " << x << "/" << y << "(" << rx[x].mMonID << '/' << ry[y].mMonID << ") SEQRES does not agree with ATOM records\n"; |
| rx[x].mMonID = ry[y].mMonID; |
| } |
|
|
| rx[x].mSeqNum = ry[y].mSeqNum; |
| rx[x].mIcode = ry[y].mIcode; |
|
|
| --x; |
| --y; |
| } |
| } |
| } |
| catch (const std::exception &ex) |
| { |
| if (cif::VERBOSE == 1) |
| printAlignment(); |
|
|
| throw; |
| } |
|
|
| |
| std::stack<int> unnumbered; |
| for (x = 0; x < dimX; ++x) |
| { |
| if (rx[x].mSeqNum == kFlagSeqNr) |
| { |
| if (x > 0 and rx[x - 1].mSeqNum != kFlagSeqNr) |
| rx[x].mSeqNum = rx[x - 1].mSeqNum + 1; |
| else |
| unnumbered.push(x); |
| } |
| } |
|
|
| while (unnumbered.empty() == false) |
| { |
| x = unnumbered.top(); |
| if (x >= dimX - 1) |
| throw std::runtime_error("Could not assign sequence numbers"); |
| rx[x].mSeqNum = rx[x + 1].mSeqNum - 1; |
| unnumbered.pop(); |
| } |
|
|
| return highY; |
| } |
|
|
| bool PDBFileParser::PDBChain::SameSequence(const PDBChain &rhs) const |
| { |
| bool result = mSeqres.size() == rhs.mSeqres.size(); |
|
|
| for (size_t i = 0; result and i < mSeqres.size(); ++i) |
| result = mSeqres[i].mMonID == rhs.mSeqres[i].mMonID; |
|
|
| return result; |
| } |
|
|
| |
|
|
| void read_pdb_file(std::istream &pdbFile, cif::file &cifFile) |
| { |
| PDBFileParser p; |
|
|
| cifFile.load_dictionary("mmcif_pdbx.dic"); |
|
|
| p.Parse(pdbFile, cifFile); |
|
|
| if (not cifFile.is_valid() and cif::VERBOSE >= 0) |
| std::cerr << "Resulting mmCIF file is not valid!\n"; |
| } |
|
|
| |
|
|
| file read(std::istream &is) |
| { |
| file result; |
|
|
| auto *buffer = is.rdbuf(); |
| if (buffer) |
| { |
| char ch = std::char_traits<char>::to_char_type(buffer->sgetc()); |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
|
|
| if (std::isalpha(ch) and std::toupper(ch) != 'D') |
| read_pdb_file(is, result); |
| else |
| { |
| try |
| { |
| result.load(is); |
| } |
| catch (const std::exception &ex) |
| { |
| std::throw_with_nested(std::runtime_error("Since the file did not start with a valid PDB HEADER line mmCIF was assumed, but that failed.")); |
| } |
| } |
|
|
| |
| |
| reconstruct_pdbx(result); |
| } |
|
|
| |
| if (result.get_validator() == nullptr) |
| result.load_dictionary("mmcif_pdbx.dic"); |
|
|
| return result; |
| } |
|
|
| file read(const std::filesystem::path &file) |
| { |
| try |
| { |
| gzio::ifstream in(file); |
| if (not in.is_open()) |
| throw std::runtime_error("Could not open file " + file.string() + " for input"); |
|
|
| return read(in); |
| } |
| catch (const std::exception &ex) |
| { |
| throw_with_nested(std::runtime_error("Error reading file " + file.string())); |
| } |
| } |
|
|
| } |
|
|