Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 11m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 10m27s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m54s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m25s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m5s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 11m33s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 11m19s
Build Packages / build:rpm (rocky8) (push) Successful in 12m23s
Build Packages / build:rpm (rocky9) (push) Successful in 13m21s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 12m30s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m55s
Build Packages / DIALS test (push) Successful in 13m42s
Build Packages / XDS test (durin plugin) (push) Successful in 9m26s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m41s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m12s
Build Packages / Generate python client (push) Successful in 19s
Build Packages / Build documentation (push) Successful in 52s
Build Packages / Create release (push) Skipped
Build Packages / build:viewer-tgz:cpu (push) Successful in 5m29s
Build Packages / build:viewer-tgz:cuda (push) Successful in 6m12s
Build Packages / build:windows:cuda (push) Successful in 18m36s
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use. * rugnux: Add `--model model.pdb` - score the merged data against an atomic model and compute initial maps. It reports R-work/R-free (scaling the model to the observed amplitudes with an overall scale, an anisotropic B and a flat bulk solvent - the standard few-parameter model, so a batch of maps stays directly comparable) and writes 2Fo-Fc / Fo-Fc electron-density maps (CCP4) plus a map-coefficient MTZ. The structure itself is not refined; the model is only re-fractionalised into the data cell. * rugnux: The merged reflection output now carries French-Wilson amplitudes (|F| and its sigma) next to the intensities - MTZ `F`/`SIGF`, mmCIF `_refln.F_meas_au`, and the text HKL - computed with the correct centric/acentric Wilson prior and epsilon multiplicity, so a downstream program (e.g. phenix.refine) can refine against amplitudes. The intensity columns are unchanged. * rugnux: R-free test-set flags are now assigned deterministically and consistently across symmetry - a Bijvoet pair I(+)/I(-) is never split between the work and free sets, and the assignment is a reproducible per-hkl hash that depends only on the reflection index, so every dataset of one crystal form gets the same ~5% free set (what a multi-dataset campaign such as PanDDA needs). On small data the fraction is floored so the test set stays large enough for a stable R-free (~500 reflections, capped at 10%); it stays flat at 5% on ordinary data. When a reference MTZ carries a `FreeR_flag` column its test set is imported instead, letting a whole campaign inherit one shared free set. * rugnux: A reference MTZ (`--reference-mtz`) can now fix the space group and cell for rotation data too (previously rejected), without being used to scale - the rotation merge stays self-consistent. When the crystal has an indexing (merohedral) ambiguity - a lattice symmetry higher than its Laue symmetry, e.g. P3/P4/P6/C2 - the reference also resolves it: each candidate reindexing (identity plus the twin-law cosets of the metric symmetry) is scored by its intensity correlation against the reference and the data are re-merged in the best-correlating one. This is a metric-preserving relabelling of hkl (the cell is unchanged) and a no-op for a holohedral crystal such as lysozyme. * rugnux: `--model` validation now aligns the data to the model before scoring - the observed reflections are reindexed into the model's enantiomorph when the two differ only by hand (indistinguishable from merged intensities). A merohedral indexing ambiguity is resolved against the reference MTZ when one is given (so a whole campaign shares one indexing convention); only with a model and no reference does validation fall back to fitting each candidate reindexing and keeping the lowest R-free. * rugnux: De-novo symmetry - recover a genuine high-symmetry group whose data are imperfectly scaled. Such a merge's within-orbit chi² lands just past the self-consistency bound (each real symmetry step adds a little systematic scatter), right where a merohedral twin also lands, so the chi² ratio alone cannot separate them. The candidate is now rescued when the extra intensity-proportional systematic error it invokes stays small relative to the confirmed subgroup - a genuine symmetry step gains multiplicity without inflating the merge error model's b, whereas a twin forces non-equivalent reflections together and b balloons. Fixes cubic insulin (I23 instead of I222) with no change to any other crystal in the test battery, including the twins that must stay in their lower symmetry. * Docs: Document the French-Wilson amplitude estimation, R-free flagging, reference-based space-group/ambiguity resolution, and model-based validation/maps in CPU_DATA_ANALYSIS.md. * Frontend: The status-bar pill now shows a progress bar during detector calibration (previously only during measurement), and the calibration state and its button are labelled "Calibration"/"CALIBRATE" (the internal `Pedestal` state name is unchanged for back-compatibility).Reviewed-on: #69 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
1205 lines
38 KiB
C++
1205 lines
38 KiB
C++
// Copyright 2017 Global Phasing Ltd.
|
|
//
|
|
// struct Document that represents the CIF file (but can also be
|
|
// read from a different representation, such as CIF-JSON or mmJSON).
|
|
|
|
#ifndef GEMMI_CIFDOC_HPP_
|
|
#define GEMMI_CIFDOC_HPP_
|
|
#include "iterator.hpp" // for StrideIter, IndirectIter
|
|
#include "atox.hpp" // for string_to_int
|
|
#include "fail.hpp" // for fail
|
|
#include "util.hpp" // for starts_with, to_lower, cat
|
|
#include <cstddef> // for size_t
|
|
#include <cstring> // for memchr
|
|
#include <algorithm> // for move, find_if, all_of, min, rotate
|
|
#include <array>
|
|
#include <initializer_list>
|
|
#include <new>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#if defined(_MSC_VER)
|
|
#pragma warning(push)
|
|
// warning C4244: an integer type is converted to a smaller integer type
|
|
#pragma warning(disable: 4244)
|
|
// warning C4267: conversion from 'size_t' to 'type', possible loss of data
|
|
#pragma warning(disable: 4267)
|
|
#endif
|
|
|
|
namespace gemmi {
|
|
namespace cif {
|
|
using std::size_t;
|
|
using gemmi::fail;
|
|
|
|
enum class ItemType : unsigned char {
|
|
Pair,
|
|
Loop,
|
|
Frame,
|
|
Comment,
|
|
Erased,
|
|
};
|
|
|
|
inline uint8_t char_table(char c) {
|
|
static const uint8_t table[256] = {
|
|
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 0, // 0
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
|
|
2, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, // 3
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, // 5
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 7
|
|
// 128-255
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
|
};
|
|
return table[static_cast<unsigned char>(c)];
|
|
}
|
|
|
|
inline void assert_tag(const std::string& tag) {
|
|
if (tag[0] != '_')
|
|
fail("Tag should start with '_', got: " + tag);
|
|
}
|
|
|
|
inline void ensure_mmcif_category(std::string& cat) {
|
|
if (cat[0] != '_')
|
|
fail("Category should start with '_', got: " + cat);
|
|
if (*(cat.end() - 1) != '.')
|
|
cat += '.';
|
|
}
|
|
|
|
inline bool is_null(const std::string& value) {
|
|
return value.size() == 1 && (value[0] == '?' || value[0] == '.');
|
|
}
|
|
|
|
inline std::string as_string(const std::string& value) {
|
|
if (value.empty() || is_null(value))
|
|
return "";
|
|
if (value[0] == '"' || value[0] == '\'')
|
|
return std::string(value.begin() + 1, value.end() - 1);
|
|
if (value[0] == ';' && value.size() > 2 && *(value.end() - 2) == '\n') {
|
|
bool crlf = *(value.end() - 3) == '\r';
|
|
return std::string(value.begin() + 1, value.end() - (crlf ? 3 : 2));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
inline std::string as_string(const std::string* value) {
|
|
return value ? as_string(*value) : std::string();
|
|
}
|
|
|
|
inline char as_char(const std::string& value, char null) {
|
|
if (is_null(value))
|
|
return null;
|
|
if (value.size() < 2)
|
|
return value[0];
|
|
const std::string s = as_string(value);
|
|
if (s.size() < 2)
|
|
return s[0];
|
|
fail("Not a single character: " + value);
|
|
}
|
|
|
|
inline int as_int(const std::string& str) {
|
|
return string_to_int(str, true);
|
|
}
|
|
|
|
inline int as_int(const std::string& str, int null) {
|
|
return is_null(str) ? null : as_int(str);
|
|
}
|
|
|
|
// for use in templates (see also as_any() functions in numb.hpp)
|
|
inline int as_any(const std::string& s, int null) { return as_int(s, null); }
|
|
inline char as_any(const std::string& s, char null) { return as_char(s, null); }
|
|
|
|
|
|
using Pair = std::array<std::string, 2>;
|
|
|
|
// used only as arguments when creating Item
|
|
struct LoopArg {};
|
|
struct FrameArg { std::string str; };
|
|
struct CommentArg { std::string str; };
|
|
|
|
struct Loop {
|
|
std::vector<std::string> tags;
|
|
std::vector<std::string> values;
|
|
|
|
// search and access
|
|
int find_tag_lc(const std::string& lctag) const {
|
|
auto f = std::find_if(tags.begin(), tags.end(),
|
|
[&lctag](const std::string& t) { return gemmi::iequal(t, lctag); });
|
|
return f == tags.end() ? -1 : f - tags.begin();
|
|
}
|
|
int find_tag(const std::string& tag) const {
|
|
return find_tag_lc(gemmi::to_lower(tag));
|
|
}
|
|
bool has_tag(const std::string& tag) const { return find_tag(tag) != -1; }
|
|
size_t width() const { return tags.size(); }
|
|
size_t length() const { return values.size() / tags.size(); }
|
|
|
|
std::string& val(size_t row, size_t col) { return values[row * tags.size() + col]; }
|
|
const std::string& val(size_t row, size_t col) const {
|
|
return const_cast<Loop*>(this)->val(row, col);
|
|
}
|
|
|
|
void clear() { tags.clear(); values.clear(); }
|
|
|
|
template <typename T> void add_values(T new_values, int pos=-1) {
|
|
auto it = values.end();
|
|
if (pos >= 0 && pos * width() < values.size())
|
|
it = values.begin() + pos * tags.size();
|
|
values.insert(it, new_values.begin(), new_values.end());
|
|
}
|
|
void add_values(std::initializer_list<std::string> new_values, int pos=-1) {
|
|
add_values<std::initializer_list<std::string>>(new_values, pos);
|
|
}
|
|
template <typename T> void add_row(T new_values, int pos=-1) {
|
|
if (new_values.size() != tags.size())
|
|
fail("add_row(): wrong row length.");
|
|
add_values<T>(new_values, pos);
|
|
}
|
|
void add_row(std::initializer_list<std::string> new_values, int pos=-1) {
|
|
add_row<std::initializer_list<std::string>>(new_values, pos);
|
|
}
|
|
// comments are added relying on how cif writing works
|
|
void add_comment_and_row(std::initializer_list<std::string> ss) {
|
|
if (ss.size() != tags.size() + 1)
|
|
fail("add_comment_and_row(): wrong row length.");
|
|
std::vector<std::string> vec(ss.begin() + 1, ss.end());
|
|
vec[0] = cat('#', *ss.begin(), '\n', vec[0]);
|
|
add_row(vec);
|
|
}
|
|
void pop_row() {
|
|
if (values.size() < tags.size())
|
|
fail("pop_row() called on empty Loop");
|
|
values.resize(values.size() - tags.size());
|
|
}
|
|
|
|
// the arguments must be valid row indices
|
|
void move_row(int old_pos, int new_pos) {
|
|
size_t w = width();
|
|
auto src = values.begin() + old_pos * w;
|
|
auto dst = values.begin() + new_pos * w;
|
|
if (src < dst)
|
|
std::rotate(src, src+w, dst+w);
|
|
else
|
|
std::rotate(dst, src, src+w);
|
|
}
|
|
|
|
// column_names are not checked for duplicates nor for category name
|
|
void add_columns(const std::vector<std::string>& column_names,
|
|
const std::string& value, int pos=-1) {
|
|
for (const std::string& name : column_names)
|
|
assert_tag(name);
|
|
size_t old_width = tags.size();
|
|
size_t len = length();
|
|
size_t upos = std::min((size_t)pos, old_width);
|
|
tags.insert(tags.begin() + upos, column_names.begin(), column_names.end());
|
|
vector_insert_columns(values, old_width, len, column_names.size(), upos, value);
|
|
}
|
|
|
|
void remove_column(const std::string& column_name) {
|
|
int n = find_tag(column_name);
|
|
if (n == -1)
|
|
fail("remove_column(): tag not found: " + column_name);
|
|
remove_column_at(n);
|
|
}
|
|
|
|
/// \pre: n < tags.size()
|
|
void remove_column_at(size_t n) {
|
|
tags.erase(tags.begin() + n);
|
|
vector_remove_column(values, tags.size(), n);
|
|
}
|
|
|
|
void set_all_values(std::vector<std::vector<std::string>> columns);
|
|
|
|
std::string common_prefix() const {
|
|
if (tags.empty())
|
|
return {};
|
|
size_t len = tags[0].size();
|
|
for (auto it = tags.begin() + 1; it != tags.end(); ++it)
|
|
for (size_t n = 0; n != len; ++n)
|
|
if (!isame(tags[0][n], (*it)[n])) {
|
|
len = n;
|
|
break;
|
|
}
|
|
return tags[0].substr(0, len);
|
|
}
|
|
};
|
|
|
|
|
|
struct Item;
|
|
struct Block;
|
|
|
|
// Accessor to a specific loop column, or to a single value from a Pair.
|
|
class Column {
|
|
public:
|
|
Column() : item_(nullptr) {}
|
|
Column(Item* item, size_t col) : item_(item), col_(col) {}
|
|
using iterator = StrideIter<std::string>;
|
|
iterator begin();
|
|
iterator end();
|
|
using const_iterator = StrideIter<const std::string>;
|
|
const_iterator begin() const { return const_cast<Column*>(this)->begin(); }
|
|
const_iterator end() const { return const_cast<Column*>(this)->end(); }
|
|
|
|
Loop* get_loop() const;
|
|
std::string* get_tag();
|
|
const std::string* get_tag() const {
|
|
return const_cast<Column*>(this)->get_tag();
|
|
}
|
|
int length() const {
|
|
if (const Loop* loop = get_loop())
|
|
return loop->length();
|
|
return item_ ? 1 : 0;
|
|
}
|
|
explicit operator bool() const { return item_ != nullptr ; }
|
|
std::string& operator[](int n);
|
|
std::string& at(int n) {
|
|
if (n < 0)
|
|
n += length();
|
|
if (n < 0 || n >= length())
|
|
throw std::out_of_range("Cannot access element " + std::to_string(n) +
|
|
" in Column with length " + std::to_string(length()));
|
|
return operator[](n);
|
|
}
|
|
const std::string& at(int n) const {
|
|
return const_cast<Column*>(this)->at(n);
|
|
}
|
|
|
|
std::string str(int n) const { return as_string(at(n)); }
|
|
const Item* item() const { return item_; }
|
|
Item* item() { return item_; }
|
|
size_t col() const { return col_; }
|
|
|
|
void erase();
|
|
|
|
private:
|
|
Item* item_;
|
|
size_t col_; // for loop this is a column index in item_->loop
|
|
};
|
|
|
|
// Some values can be given either in loop or as tag-value pairs.
|
|
// The latter case is equivalent to a loop with a single row.
|
|
// We optimized for loops, and in case of tag-values we copy the values
|
|
// into the `values` vector.
|
|
struct Table {
|
|
Item* loop_item;
|
|
Block& bloc;
|
|
std::vector<int> positions;
|
|
size_t prefix_length;
|
|
|
|
struct Row {
|
|
Table& tab;
|
|
int row_index;
|
|
|
|
std::string& value_at_unsafe(int pos);
|
|
std::string& value_at(int pos) {
|
|
if (pos == -1)
|
|
throw std::out_of_range("Cannot access missing optional tag.");
|
|
return value_at_unsafe(pos);
|
|
}
|
|
const std::string& value_at(int pos) const {
|
|
return const_cast<Row*>(this)->value_at(pos);
|
|
}
|
|
|
|
std::string& at(int n) {
|
|
return value_at(tab.positions.at(n < 0 ? n + size() : n));
|
|
}
|
|
const std::string& at(int n) const { return const_cast<Row*>(this)->at(n); }
|
|
|
|
std::string& operator[](size_t n);
|
|
const std::string& operator[](size_t n) const {
|
|
return const_cast<Row*>(this)->operator[](n);
|
|
}
|
|
|
|
std::string* ptr_at(int n) {
|
|
int pos = tab.positions.at(n < 0 ? n + size() : n);
|
|
return pos >= 0 ? &value_at(pos) : nullptr;
|
|
}
|
|
const std::string* ptr_at(int n) const {
|
|
return const_cast<Row*>(this)->ptr_at(n);
|
|
}
|
|
|
|
bool has(size_t n) const { return tab.positions.at(n) >= 0; }
|
|
bool has2(size_t n) const { return has(n) && !cif::is_null(operator[](n)); }
|
|
|
|
const std::string& one_of(size_t n1, size_t n2) const {
|
|
static const std::string nul(1, '.');
|
|
if (has2(n1))
|
|
return operator[](n1);
|
|
if (has(n2))
|
|
return operator[](n2);
|
|
return nul;
|
|
}
|
|
|
|
size_t size() const { return tab.width(); }
|
|
|
|
std::string str(int n) const { return as_string(at(n)); }
|
|
|
|
using iterator = IndirectIter<Row, std::string>;
|
|
using const_iterator = IndirectIter<const Row, const std::string>;
|
|
iterator begin() { return iterator({this, tab.positions.begin()}); }
|
|
iterator end() { return iterator({this, tab.positions.end()}); }
|
|
const_iterator begin() const {
|
|
return const_iterator({this, tab.positions.begin()});
|
|
}
|
|
const_iterator end() const {
|
|
return const_iterator({this, tab.positions.end()});
|
|
}
|
|
};
|
|
|
|
Loop* get_loop();
|
|
bool ok() const { return !positions.empty(); }
|
|
size_t width() const { return positions.size(); }
|
|
size_t length() const;
|
|
size_t size() const { return length(); }
|
|
bool has_column(int n) const { return ok() && positions.at(n) >= 0; }
|
|
Row tags() { return Row{*this, -1}; }
|
|
Row operator[](int n) { return Row{*this, n}; }
|
|
|
|
void at_check(int& n) const {
|
|
if (n < 0)
|
|
n += length();
|
|
if (n < 0 || static_cast<size_t>(n) >= length())
|
|
throw std::out_of_range("No row with index " + std::to_string(n));
|
|
}
|
|
Row at(int n) {
|
|
at_check(n);
|
|
return (*this)[n];
|
|
}
|
|
|
|
Row one() {
|
|
if (length() != 1)
|
|
fail("Expected one value, found " + std::to_string(length()));
|
|
return (*this)[0];
|
|
}
|
|
|
|
std::string get_prefix() const {
|
|
for (int pos : positions)
|
|
if (pos >= 0)
|
|
return const_cast<Table*>(this)->tags()
|
|
.value_at(pos).substr(0, prefix_length);
|
|
fail("The table has no columns.");
|
|
}
|
|
|
|
Row find_row(const std::string& s);
|
|
|
|
template <typename T> void append_row(const T& new_values);
|
|
void append_row(std::initializer_list<std::string> new_values) {
|
|
append_row<std::initializer_list<std::string>>(new_values);
|
|
}
|
|
void remove_row(int row_index) { remove_rows(row_index, row_index+1); }
|
|
void remove_rows(int start, int end);
|
|
Column column_at_pos(int pos);
|
|
Column column(int n) {
|
|
int pos = positions.at(n);
|
|
if (pos == -1)
|
|
fail("Cannot access absent column");
|
|
return column_at_pos(pos);
|
|
}
|
|
|
|
void move_row(int old_pos, int new_pos) {
|
|
at_check(old_pos);
|
|
at_check(new_pos);
|
|
if (Loop* loop = get_loop())
|
|
loop->move_row(old_pos, new_pos);
|
|
}
|
|
|
|
// prefix is optional
|
|
int find_column_position(const std::string& tag) const {
|
|
std::string lctag = gemmi::to_lower(tag);
|
|
Row tag_row = const_cast<Table*>(this)->tags();
|
|
for (int pos : positions) {
|
|
const std::string& v = tag_row.value_at_unsafe(pos);
|
|
if (v.length() == lctag.length() ? gemmi::iequal(v, lctag)
|
|
: gemmi::iequal_from(v, prefix_length, lctag))
|
|
return pos;
|
|
}
|
|
fail("Column name not found: " + tag);
|
|
}
|
|
|
|
Column find_column(const std::string& tag) {
|
|
return column_at_pos(find_column_position(tag));
|
|
}
|
|
|
|
void erase();
|
|
|
|
/// if it's pairs, convert it to loop
|
|
void ensure_loop();
|
|
|
|
// It is not a proper input iterator, but just enough for using range-for.
|
|
struct iterator {
|
|
Table& parent;
|
|
int index;
|
|
void operator++() { index++; }
|
|
bool operator==(const iterator& o) const { return index == o.index; }
|
|
bool operator!=(const iterator& o) const { return index != o.index; }
|
|
Row operator*() { return parent[index]; }
|
|
const std::string& get(int n) const { return parent[index].at(n); }
|
|
};
|
|
iterator begin() { return iterator{*this, 0}; }
|
|
iterator end() { return iterator{*this, (int)length()}; }
|
|
};
|
|
|
|
struct Block {
|
|
std::string name;
|
|
std::vector<Item> items;
|
|
|
|
explicit Block(const std::string& name_);
|
|
Block();
|
|
|
|
void swap(Block& o) noexcept { name.swap(o.name); items.swap(o.items); }
|
|
// access functions
|
|
const Item* find_pair_item(const std::string& tag) const;
|
|
const Pair* find_pair(const std::string& tag) const;
|
|
Column find_loop(const std::string& tag);
|
|
const Item* find_loop_item(const std::string& tag) const;
|
|
const std::string* find_value(const std::string& tag) const;
|
|
Column find_values(const std::string& tag);
|
|
bool has_tag(const std::string& tag) const {
|
|
return const_cast<Block*>(this)->find_values(tag).item() != nullptr;
|
|
}
|
|
bool has_any_value(const std::string& tag) const {
|
|
Column c = const_cast<Block*>(this)->find_values(tag);
|
|
return c.item() != nullptr && !std::all_of(c.begin(), c.end(), is_null);
|
|
}
|
|
Table find(const std::string& prefix,
|
|
const std::vector<std::string>& tags);
|
|
Table find(const std::vector<std::string>& tags) { return find({}, tags); }
|
|
Table find_any(const std::string& prefix,
|
|
const std::vector<std::string>& tags);
|
|
Table find_or_add(const std::string& prefix, std::vector<std::string> tags) {
|
|
Table t = find(prefix, tags);
|
|
if (!t.ok()) {
|
|
for (int i = 0; i != (int) tags.size(); ++i)
|
|
t.positions.push_back(i);
|
|
Table tab = find_any(prefix, tags);
|
|
t.loop_item = &setup_loop_item(std::move(tab), prefix, std::move(tags));
|
|
}
|
|
return t;
|
|
}
|
|
Block* find_frame(std::string name);
|
|
Table item_as_table(Item& item);
|
|
|
|
size_t get_index(const std::string& tag) const;
|
|
|
|
// modifying functions
|
|
void set_pair(const std::string& tag, const std::string& value);
|
|
|
|
Loop& init_loop(const std::string& prefix, std::vector<std::string> tags) {
|
|
Table tab = find_any(prefix, tags);
|
|
return setup_loop(std::move(tab), prefix, std::move(tags));
|
|
}
|
|
|
|
void move_item(int old_pos, int new_pos);
|
|
|
|
// mmCIF specific functions
|
|
std::vector<std::string> get_mmcif_category_names() const;
|
|
Table find_mmcif_category(std::string cat);
|
|
bool has_mmcif_category(std::string cat) const;
|
|
|
|
Loop& init_mmcif_loop(std::string cat, std::vector<std::string> tags) {
|
|
ensure_mmcif_category(cat); // modifies cat
|
|
return setup_loop(find_mmcif_category(cat), cat, std::move(tags));
|
|
}
|
|
|
|
private:
|
|
Item& setup_loop_item(Table&& tab, const std::string& prefix,
|
|
std::vector<std::string>&& tags);
|
|
Loop& setup_loop(Table&& tab, const std::string& prefix,
|
|
std::vector<std::string>&& tags);
|
|
};
|
|
|
|
|
|
struct Item {
|
|
ItemType type;
|
|
int line_number = -1;
|
|
union {
|
|
Pair pair;
|
|
Loop loop;
|
|
Block frame;
|
|
};
|
|
|
|
Item() : type(ItemType::Erased) {}
|
|
explicit Item(LoopArg)
|
|
: type{ItemType::Loop}, loop{} {}
|
|
explicit Item(std::string&& t)
|
|
: type{ItemType::Pair}, pair{{std::move(t), std::string()}} {}
|
|
Item(const std::string& t, const std::string& v)
|
|
: type{ItemType::Pair}, pair{{t, v}} {}
|
|
explicit Item(FrameArg&& frame_arg)
|
|
: type{ItemType::Frame}, frame(frame_arg.str) {}
|
|
explicit Item(CommentArg&& comment)
|
|
: type{ItemType::Comment}, pair{{std::string(), std::move(comment.str)}} {}
|
|
|
|
Item(Item&& o) noexcept
|
|
: type(o.type), line_number(o.line_number) {
|
|
move_value(std::move(o));
|
|
}
|
|
Item(const Item& o)
|
|
: type(o.type), line_number(o.line_number) {
|
|
copy_value(o);
|
|
}
|
|
|
|
Item& operator=(Item o) { set_value(std::move(o)); return *this; }
|
|
|
|
~Item() { destruct(); }
|
|
|
|
void erase() {
|
|
destruct();
|
|
type = ItemType::Erased;
|
|
}
|
|
|
|
// case-insensitive, the prefix should be lower-case
|
|
bool has_prefix(const std::string& prefix) const {
|
|
return (type == ItemType::Pair && gemmi::istarts_with(pair[0], prefix)) ||
|
|
(type == ItemType::Loop && !loop.tags.empty() &&
|
|
gemmi::istarts_with(loop.tags[0], prefix));
|
|
}
|
|
|
|
void set_value(Item&& o) {
|
|
if (type == o.type) {
|
|
switch (type) {
|
|
case ItemType::Pair: pair = std::move(o.pair); break;
|
|
case ItemType::Loop: loop = std::move(o.loop); break;
|
|
case ItemType::Frame: frame = std::move(o.frame); break;
|
|
case ItemType::Comment: pair = std::move(o.pair); break;
|
|
case ItemType::Erased: break;
|
|
}
|
|
} else {
|
|
destruct();
|
|
type = o.type;
|
|
move_value(std::move(o));
|
|
}
|
|
}
|
|
|
|
private:
|
|
void destruct() {
|
|
switch (type) {
|
|
case ItemType::Pair: pair.~Pair(); break;
|
|
case ItemType::Loop: loop.~Loop(); break;
|
|
case ItemType::Frame: frame.~Block(); break;
|
|
case ItemType::Comment: pair.~Pair(); break;
|
|
case ItemType::Erased: break;
|
|
}
|
|
}
|
|
|
|
void copy_value(const Item& o) {
|
|
if (o.type == ItemType::Pair || o.type == ItemType::Comment)
|
|
new (&pair) Pair(o.pair);
|
|
else if (o.type == ItemType::Loop)
|
|
new (&loop) Loop(o.loop);
|
|
else if (o.type == ItemType::Frame)
|
|
new (&frame) Block(o.frame);
|
|
}
|
|
|
|
void move_value(Item&& o) {
|
|
if (o.type == ItemType::Pair || o.type == ItemType::Comment)
|
|
new (&pair) Pair(std::move(o.pair));
|
|
else if (o.type == ItemType::Loop)
|
|
new (&loop) Loop(std::move(o.loop));
|
|
else if (o.type == ItemType::Frame)
|
|
new (&frame) Block(std::move(o.frame));
|
|
}
|
|
};
|
|
|
|
|
|
// ItemSpan is used to add tag-value pairs next to the same category tags.
|
|
struct ItemSpan {
|
|
ItemSpan(std::vector<Item>& items)
|
|
: items_(items), begin_(0), end_(items.size()) {}
|
|
ItemSpan(std::vector<Item>& items, std::string prefix)
|
|
: ItemSpan(items) {
|
|
assert_tag(prefix);
|
|
prefix = gemmi::to_lower(prefix);
|
|
while (begin_ != end_ && !items_[begin_].has_prefix(prefix))
|
|
++begin_;
|
|
if (begin_ != end_)
|
|
while (end_-1 != begin_ && !items_[end_-1].has_prefix(prefix))
|
|
--end_;
|
|
}
|
|
void set_pair(const std::string& tag, const std::string& value) {
|
|
assert_tag(tag);
|
|
std::string lctag = gemmi::to_lower(tag);
|
|
auto end = items_.begin() + end_;
|
|
for (auto i = items_.begin() + begin_; i != end; ++i) {
|
|
if (i->type == ItemType::Pair && gemmi::iequal(i->pair[0], lctag)) {
|
|
i->pair[0] = tag; // if letter case differs, the tag changes
|
|
i->pair[1] = value;
|
|
return;
|
|
}
|
|
if (i->type == ItemType::Loop && i->loop.find_tag_lc(lctag) != -1) {
|
|
i->set_value(Item(tag, value));
|
|
return;
|
|
}
|
|
}
|
|
items_.emplace(end, tag, value);
|
|
++end_;
|
|
}
|
|
private:
|
|
std::vector<Item>& items_;
|
|
size_t begin_, end_; // iterators would be invalidated by emplace()
|
|
};
|
|
|
|
|
|
inline void Loop::set_all_values(std::vector<std::vector<std::string>> columns){
|
|
size_t w = columns.size();
|
|
if (w != width())
|
|
fail(cat("set_all_values(): expected ", width(), " columns, got ", w));
|
|
if (w == 0)
|
|
return;
|
|
size_t h = columns[0].size();
|
|
for (auto& col : columns)
|
|
if (col.size() != h)
|
|
fail("set_all_values(): all columns must have the same length");
|
|
values.resize(w * h);
|
|
for (size_t i = 0; i != h; ++i)
|
|
for (size_t j = 0; j != w; ++j)
|
|
values[w * i + j] = std::move(columns[j][i]);
|
|
}
|
|
|
|
inline std::string* Column::get_tag() {
|
|
if (!item_)
|
|
return nullptr;
|
|
if (Loop* loop = get_loop())
|
|
return &loop->tags.at(col_);
|
|
return &item_->pair[0];
|
|
}
|
|
|
|
inline void Column::erase() {
|
|
if (Loop* loop = get_loop())
|
|
loop->remove_column_at(col_);
|
|
else if (item_)
|
|
item_->erase();
|
|
}
|
|
|
|
inline Loop* Column::get_loop() const {
|
|
return item_ && item_->type == ItemType::Loop ? &item_->loop : nullptr;
|
|
}
|
|
inline Column::iterator Column::begin() {
|
|
if (Loop* loop = get_loop())
|
|
return iterator({loop->values.data(), col_, (unsigned) loop->width()});
|
|
if (item_ && item_->type == ItemType::Pair)
|
|
return iterator({&item_->pair[1], 0, 1});
|
|
return iterator();
|
|
}
|
|
|
|
inline Column::iterator Column::end() {
|
|
if (Loop* loop = get_loop())
|
|
return iterator({loop->values.data() + loop->values.size(),
|
|
col_, (unsigned) loop->width()});
|
|
if (item_ && item_->type == ItemType::Pair)
|
|
return iterator({&item_->pair[1] + 1, 0, 1});
|
|
return iterator();
|
|
}
|
|
|
|
inline std::string& Column::operator[](int n) {
|
|
if (Loop* loop = get_loop())
|
|
return loop->values[n * loop->width() + col_];
|
|
return item_->pair[1];
|
|
}
|
|
|
|
inline std::string& Table::Row::operator[](size_t n) {
|
|
int pos = tab.positions[n];
|
|
if (Loop* loop = tab.get_loop()) {
|
|
if (row_index == -1) // tags
|
|
return loop->tags[pos];
|
|
return loop->values[loop->width() * row_index + pos];
|
|
}
|
|
return tab.bloc.items[pos].pair[row_index == -1 ? 0 : 1];
|
|
}
|
|
|
|
inline std::string& Table::Row::value_at_unsafe(int pos) {
|
|
Loop* loop = tab.get_loop();
|
|
if (row_index == -1) { // tags
|
|
if (loop)
|
|
return loop->tags.at(pos);
|
|
return tab.bloc.items[pos].pair[0];
|
|
}
|
|
if (loop)
|
|
return loop->values.at(loop->width() * row_index + pos);
|
|
return tab.bloc.items[pos].pair[1];
|
|
}
|
|
|
|
inline Loop* Table::get_loop() { // NOLINT(readability-make-member-function-const)
|
|
return loop_item ? &loop_item->loop : nullptr;
|
|
}
|
|
|
|
inline size_t Table::length() const {
|
|
return loop_item ? loop_item->loop.length() : (positions.empty() ? 0 : 1);
|
|
}
|
|
|
|
inline Table::Row Table::find_row(const std::string& s) {
|
|
int pos = positions.at(0);
|
|
if (const Loop* loop = get_loop()) {
|
|
for (size_t i = 0; i < loop->values.size(); i += loop->width())
|
|
if (as_string(loop->values[i + pos]) == s)
|
|
return Row{*this, static_cast<int>(i / loop->width())};
|
|
} else if (as_string(bloc.items[pos].pair[1]) == s) {
|
|
return Row{*this, 0};
|
|
}
|
|
fail("Not found in " + *column_at_pos(pos).get_tag() + ": " + s);
|
|
}
|
|
|
|
template <typename T> void Table::append_row(const T& new_values) {
|
|
if (!ok())
|
|
fail("append_row(): table not found");
|
|
if (new_values.size() != width())
|
|
fail("append_row(): wrong row length");
|
|
if (!loop_item)
|
|
fail("append_row(): data is not in loop, call ensure_loop() first");
|
|
Loop& loop = loop_item->loop;
|
|
size_t cur_size = loop.values.size();
|
|
loop.values.resize(cur_size + loop.width(), ".");
|
|
int n = 0;
|
|
for (const auto& value : new_values)
|
|
loop.values[cur_size + positions[n++]] = value;
|
|
}
|
|
|
|
inline void Table::remove_rows(int start, int end) {
|
|
if (!ok())
|
|
// this function is used mostly through remove_row()
|
|
fail("remove_row(): table not found");
|
|
ensure_loop(); // should we fail instead if we have pairs?
|
|
Loop& loop = loop_item->loop;
|
|
size_t start_pos = start * loop.width();
|
|
size_t end_pos = end * loop.width();
|
|
if (start_pos >= end_pos || end_pos > loop.values.size())
|
|
throw std::out_of_range("remove_row(): invalid index");
|
|
loop.values.erase(loop.values.begin() + start_pos,
|
|
loop.values.begin() + end_pos);
|
|
}
|
|
|
|
inline Column Table::column_at_pos(int pos) {
|
|
if (loop_item)
|
|
return Column(loop_item, pos);
|
|
return Column(&bloc.items[pos], 0);
|
|
}
|
|
|
|
inline void Table::erase() {
|
|
if (loop_item) {
|
|
loop_item->erase();
|
|
loop_item = nullptr;
|
|
} else {
|
|
for (int pos : positions)
|
|
if (pos >= 0)
|
|
bloc.items[pos].erase();
|
|
}
|
|
positions.clear();
|
|
}
|
|
|
|
inline void Table::ensure_loop() {
|
|
if (loop_item)
|
|
return;
|
|
Item new_item(LoopArg{});
|
|
new_item.loop.tags.resize(positions.size());
|
|
new_item.loop.values.resize(positions.size());
|
|
loop_item = &bloc.items.at(positions[0]);
|
|
for (size_t i = 0; i != positions.size(); ++i) {
|
|
Item& item = bloc.items[positions[i]];
|
|
new_item.loop.tags[i].swap(item.pair[0]);
|
|
new_item.loop.values[i].swap(item.pair[1]);
|
|
item.erase();
|
|
positions[i] = i;
|
|
}
|
|
loop_item->set_value(std::move(new_item));
|
|
}
|
|
|
|
inline Block::Block(const std::string& name_) : name(name_) {}
|
|
inline Block::Block() {}
|
|
|
|
inline const Item* Block::find_pair_item(const std::string& tag) const {
|
|
std::string lctag = gemmi::to_lower(tag);
|
|
for (const Item& i : items)
|
|
if (i.type == ItemType::Pair && gemmi::iequal(i.pair[0], lctag))
|
|
return &i;
|
|
return nullptr;
|
|
}
|
|
|
|
inline const Pair* Block::find_pair(const std::string& tag) const {
|
|
const Item* item = find_pair_item(tag);
|
|
return item ? &item->pair : nullptr;
|
|
}
|
|
|
|
inline Column Block::find_loop(const std::string& tag) {
|
|
Column c = find_values(tag);
|
|
return c.item() && c.item()->type == ItemType::Loop ? c : Column();
|
|
}
|
|
|
|
inline const Item* Block::find_loop_item(const std::string& tag) const {
|
|
for (const Item& i : items)
|
|
if (i.type == ItemType::Loop && i.loop.find_tag_lc(tag) != -1)
|
|
return &i;
|
|
return nullptr;
|
|
}
|
|
|
|
inline const std::string* Block::find_value(const std::string& tag) const {
|
|
std::string lctag = gemmi::to_lower(tag);
|
|
for (const Item& i : items)
|
|
if (i.type == ItemType::Pair && gemmi::iequal(i.pair[0], lctag))
|
|
return &i.pair[1];
|
|
for (const Item& i : items)
|
|
if (i.type == ItemType::Loop) {
|
|
int pos = i.loop.find_tag_lc(lctag);
|
|
if (pos != -1 && i.loop.tags.size() == i.loop.values.size())
|
|
return &i.loop.values[pos];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
inline Column Block::find_values(const std::string& tag) {
|
|
std::string lctag = gemmi::to_lower(tag);
|
|
for (Item& i : items)
|
|
if (i.type == ItemType::Loop) {
|
|
int pos = i.loop.find_tag_lc(lctag);
|
|
if (pos != -1)
|
|
return Column{&i, static_cast<size_t>(pos)};
|
|
} else if (i.type == ItemType::Pair) {
|
|
if (gemmi::iequal(i.pair[0], lctag))
|
|
return Column{&i, 0};
|
|
}
|
|
return Column{nullptr, 0};
|
|
}
|
|
|
|
inline Block* Block::find_frame(std::string frame_name) {
|
|
frame_name = gemmi::to_lower(frame_name);
|
|
for (Item& i : items)
|
|
if (i.type == ItemType::Frame && gemmi::iequal(i.frame.name, frame_name))
|
|
return &i.frame;
|
|
return nullptr;
|
|
}
|
|
|
|
inline Table Block::item_as_table(Item& item) {
|
|
if (item.type != ItemType::Loop)
|
|
fail("item_as_table: item is not Loop");
|
|
std::vector<int> indices(item.loop.tags.size());
|
|
for (size_t j = 0; j != indices.size(); ++j)
|
|
indices[j] = (int) j;
|
|
return Table{&item, *this, indices, 0};
|
|
}
|
|
|
|
inline size_t Block::get_index(const std::string& tag) const {
|
|
std::string lctag = gemmi::to_lower(tag);
|
|
for (size_t i = 0; i != items.size(); ++i) {
|
|
const Item& item = items[i];
|
|
if ((item.type == ItemType::Pair && gemmi::iequal(item.pair[0], lctag)) ||
|
|
(item.type == ItemType::Loop && item.loop.find_tag_lc(lctag) != -1))
|
|
return i;
|
|
}
|
|
fail(tag + " not found in block");
|
|
}
|
|
|
|
inline void Block::set_pair(const std::string& tag, const std::string& value) {
|
|
ItemSpan(items).set_pair(tag, value);
|
|
}
|
|
|
|
inline void Block::move_item(int old_pos, int new_pos) {
|
|
if (old_pos < 0)
|
|
old_pos += items.size();
|
|
if ((size_t) old_pos >= items.size())
|
|
fail("move_item: old_pos out of range");
|
|
if (new_pos < 0)
|
|
new_pos += items.size();
|
|
if ((size_t) new_pos >= items.size())
|
|
fail("move_item: new_pos out of range");
|
|
auto src = items.begin() + old_pos;
|
|
auto dst = items.begin() + new_pos;
|
|
if (src < dst)
|
|
std::rotate(src, src+1, dst+1);
|
|
else
|
|
std::rotate(dst, src, src+1);
|
|
}
|
|
|
|
inline std::vector<std::string> Block::get_mmcif_category_names() const {
|
|
std::vector<std::string> cats;
|
|
for (const Item& item : items) {
|
|
const std::string* tag = nullptr;
|
|
if (item.type == ItemType::Pair)
|
|
tag = &item.pair[0];
|
|
else if (item.type == ItemType::Loop && !item.loop.tags.empty())
|
|
tag = &item.loop.tags[0];
|
|
if (tag)
|
|
for (auto j = cats.rbegin(); j != cats.rend(); ++j)
|
|
if (gemmi::starts_with(*tag, *j)) {
|
|
tag = nullptr;
|
|
break;
|
|
}
|
|
if (tag) {
|
|
size_t dot = tag->find('.');
|
|
if (dot != std::string::npos)
|
|
cats.emplace_back(*tag, 0, dot + 1);
|
|
}
|
|
}
|
|
return cats;
|
|
}
|
|
|
|
inline Item& Block::setup_loop_item(Table&& tab, const std::string& prefix,
|
|
std::vector<std::string>&& tags) {
|
|
Item *item;
|
|
if (tab.loop_item) {
|
|
item = tab.loop_item;
|
|
item->loop.clear();
|
|
} else if (tab.ok()) {
|
|
item = &tab.bloc.items.at(tab.positions[0]);
|
|
tab.erase();
|
|
item->set_value(Item(LoopArg{}));
|
|
} else {
|
|
items.emplace_back(LoopArg{});
|
|
item = &items.back();
|
|
}
|
|
for (std::string& tag : tags) {
|
|
tag.insert(0, prefix);
|
|
assert_tag(tag);
|
|
}
|
|
item->loop.tags = std::move(tags);
|
|
return *item;
|
|
}
|
|
|
|
inline Loop& Block::setup_loop(Table&& tab, const std::string& prefix,
|
|
std::vector<std::string>&& tags) {
|
|
return setup_loop_item(std::move(tab), prefix, std::move(tags)).loop;
|
|
}
|
|
|
|
inline Table Block::find(const std::string& prefix,
|
|
const std::vector<std::string>& tags) {
|
|
Item* loop_item = nullptr;
|
|
if (!tags.empty()) {
|
|
if (tags[0][0] == '?')
|
|
fail("The first tag in find() cannot be ?optional.");
|
|
loop_item = find_loop(prefix + tags[0]).item();
|
|
}
|
|
|
|
std::vector<int> indices;
|
|
indices.reserve(tags.size());
|
|
if (loop_item) {
|
|
for (const std::string& tag : tags) {
|
|
std::string full_tag = prefix + (tag[0] != '?' ? tag : tag.substr(1));
|
|
int idx = loop_item->loop.find_tag(full_tag);
|
|
if (idx == -1 && tag[0] != '?') {
|
|
loop_item = nullptr;
|
|
indices.clear();
|
|
break;
|
|
}
|
|
indices.push_back(idx);
|
|
}
|
|
} else {
|
|
for (const std::string& tag : tags) {
|
|
std::string full_tag = prefix + (tag[0] != '?' ? tag : tag.substr(1));
|
|
if (const Item* p = find_pair_item(full_tag)) {
|
|
indices.push_back(p - items.data());
|
|
} else if (tag[0] == '?') {
|
|
indices.push_back(-1);
|
|
} else {
|
|
indices.clear();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Table{loop_item, *this, indices, prefix.length()};
|
|
}
|
|
|
|
inline Table Block::find_any(const std::string& prefix,
|
|
const std::vector<std::string>& tags) {
|
|
std::vector<int> indices;
|
|
for (auto tag = tags.begin(); tag != tags.end(); ++tag) {
|
|
Column column = find_values(prefix + *tag);
|
|
if (Item* item = column.item()) {
|
|
if (item->type == ItemType::Loop) {
|
|
indices.push_back(column.col());
|
|
while (++tag != tags.end()) {
|
|
int idx = item->loop.find_tag(prefix + *tag);
|
|
if (idx != -1)
|
|
indices.push_back(idx);
|
|
}
|
|
return Table{item, *this, indices, prefix.length()};
|
|
}
|
|
indices.push_back(item - items.data());
|
|
while (++tag != tags.end())
|
|
if (const Item* p = find_pair_item(prefix + *tag))
|
|
indices.push_back(p - items.data());
|
|
break;
|
|
}
|
|
}
|
|
return Table{nullptr, *this, indices, prefix.length()};
|
|
}
|
|
|
|
inline Table Block::find_mmcif_category(std::string cat) {
|
|
ensure_mmcif_category(cat);
|
|
cat = gemmi::to_lower(cat);
|
|
std::vector<int> indices;
|
|
for (Item& i : items)
|
|
if (i.has_prefix(cat)) {
|
|
if (i.type == ItemType::Loop) {
|
|
indices.resize(i.loop.tags.size());
|
|
for (size_t j = 0; j != indices.size(); ++j) {
|
|
indices[j] = j;
|
|
const std::string& tag = i.loop.tags[j];
|
|
if (!istarts_with(tag, cat))
|
|
fail("Tag ", tag, " in loop with ", cat);
|
|
}
|
|
return Table{&i, *this, indices, cat.length()};
|
|
}
|
|
indices.push_back(&i - items.data());
|
|
}
|
|
return Table{nullptr, *this, indices, cat.length()};
|
|
}
|
|
|
|
inline bool Block::has_mmcif_category(std::string cat) const {
|
|
ensure_mmcif_category(cat);
|
|
cat = gemmi::to_lower(cat);
|
|
for (const Item& i : items)
|
|
if (i.has_prefix(cat))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
struct Document {
|
|
std::string source;
|
|
std::vector<Block> blocks;
|
|
|
|
// implementation detail: items of the currently parsed block or frame
|
|
std::vector<Item>* items_ = nullptr;
|
|
|
|
Block& add_new_block(const std::string& name, int pos=-1) {
|
|
if (find_block(name))
|
|
fail("Block with such name already exists: " + name);
|
|
if (pos > 0 && static_cast<size_t>(pos) > blocks.size())
|
|
throw std::out_of_range("add_new_block(): invalid position");
|
|
return *blocks.emplace(pos < 0 ? blocks.end() : blocks.begin() + pos, name);
|
|
}
|
|
|
|
void clear() noexcept {
|
|
source.clear();
|
|
blocks.clear();
|
|
items_ = nullptr;
|
|
}
|
|
|
|
// returns blocks[0] if the document has exactly one block (like mmCIF)
|
|
Block& sole_block() {
|
|
if (blocks.size() > 1)
|
|
fail("single data block expected, got " + std::to_string(blocks.size()));
|
|
return blocks.at(0);
|
|
}
|
|
const Block& sole_block() const {
|
|
return const_cast<Document*>(this)->sole_block();
|
|
}
|
|
|
|
Block* find_block(const std::string& name) {
|
|
for (Block& b : blocks)
|
|
if (b.name == name)
|
|
return &b;
|
|
return nullptr;
|
|
}
|
|
const Block* find_block(const std::string& name) const {
|
|
return const_cast<Document*>(this)->find_block(name);
|
|
}
|
|
};
|
|
|
|
|
|
[[noreturn]]
|
|
inline void cif_fail(const std::string& source, const Block& b,
|
|
const Item& item, const std::string& s) {
|
|
fail(cat(source, ':', item.line_number, " in data_", b.name, ": ", s));
|
|
}
|
|
|
|
inline void check_for_missing_values_in_block(const Block& block,
|
|
const std::string& source) {
|
|
for (const Item& item : block.items) {
|
|
if (item.type == ItemType::Pair) {
|
|
if (item.pair[1].empty())
|
|
cif_fail(source, block, item, item.pair[0] + " has no value");
|
|
} else if (item.type == ItemType::Frame) {
|
|
check_for_missing_values_in_block(item.frame, source);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Throw an error if any item (pair) value is missing
|
|
inline void check_for_missing_values(const Document& d) {
|
|
for (const Block& block : d.blocks)
|
|
check_for_missing_values_in_block(block, d.source);
|
|
}
|
|
|
|
// Throw an error if any block name, frame name or tag is duplicated.
|
|
inline void check_for_duplicates(const Document& d) {
|
|
// check for duplicate block names (except empty "" which is global_)
|
|
std::unordered_set<std::string> names;
|
|
for (const Block& block : d.blocks) {
|
|
bool ok = names.insert(gemmi::to_lower(block.name)).second;
|
|
if (!ok && !block.name.empty())
|
|
fail(d.source + ": duplicate block name: ", block.name);
|
|
}
|
|
// check for dups inside each block
|
|
std::unordered_set<std::string> frame_names;
|
|
for (const Block& block : d.blocks) {
|
|
names.clear();
|
|
frame_names.clear();
|
|
for (const Item& item : block.items) {
|
|
if (item.type == ItemType::Pair) {
|
|
bool ok = names.insert(gemmi::to_lower(item.pair[0])).second;
|
|
if (!ok)
|
|
cif_fail(d.source, block, item, "duplicate tag " + item.pair[0]);
|
|
} else if (item.type == ItemType::Loop) {
|
|
for (const std::string& t : item.loop.tags) {
|
|
bool ok = names.insert(gemmi::to_lower(t)).second;
|
|
if (!ok)
|
|
cif_fail(d.source, block, item, "duplicate tag " + t);
|
|
}
|
|
} else if (item.type == ItemType::Frame) {
|
|
bool ok = frame_names.insert(gemmi::to_lower(item.frame.name)).second;
|
|
if (!ok)
|
|
cif_fail(d.source, block, item, "duplicate save_" + item.frame.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Empty loop is not a valid CIF syntax, but we parse it to accommodate
|
|
// some broken CIF files. Only check_level>=2 shows an error.
|
|
inline void check_empty_loops(const cif::Block& block, const std::string& source) {
|
|
for (const cif::Item& item : block.items) {
|
|
if (item.type == cif::ItemType::Loop) {
|
|
if (item.loop.values.empty() && !item.loop.tags.empty())
|
|
cif_fail(source, block, item, "empty loop with " + item.loop.tags[0]);
|
|
} else if (item.type == cif::ItemType::Frame) {
|
|
check_empty_loops(item.frame, source);
|
|
}
|
|
}
|
|
}
|
|
|
|
inline bool is_text_field(const std::string& val) {
|
|
size_t len = val.size();
|
|
return len > 2 && val[0] == ';' && (val[len-2] == '\n' || val[len-2] == '\r');
|
|
}
|
|
|
|
inline std::string quote(std::string v) {
|
|
if (std::all_of(v.begin(), v.end(), [](char c) { return char_table(c) == 1; })
|
|
&& !v.empty() && !is_null(v))
|
|
return v;
|
|
char q = ';';
|
|
if (std::memchr(v.c_str(), '\n', v.size()) == nullptr) {
|
|
if (std::memchr(v.c_str(), '\'', v.size()) == nullptr)
|
|
q = '\'';
|
|
else if (std::memchr(v.c_str(), '"', v.size()) == nullptr)
|
|
q = '"';
|
|
}
|
|
v.insert(v.begin(), q);
|
|
if (q == ';')
|
|
v += '\n';
|
|
v += q;
|
|
return v;
|
|
}
|
|
|
|
#if defined(_MSC_VER)
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
} // namespace cif
|
|
} // namespace gemmi
|
|
#endif
|