Update validation code, added pdbx_item_enumeration check and test case-sensitive when required.

This commit is contained in:
Maarten L. Hekkelman
2026-04-13 10:38:43 +02:00
parent 421758a01c
commit 39644c1bd1
7 changed files with 3650 additions and 296 deletions

View File

@@ -196,7 +196,7 @@ class validation_exception : public std::runtime_error
public: public:
// Constructors // Constructors
/// @cond /// @cond
validation_exception(validation_error err) validation_exception(validation_error err)
: validation_exception(make_error_code(err)) : validation_exception(make_error_code(err))
{ {
@@ -337,7 +337,7 @@ struct item_validator
std::string m_item_name; ///< The item name std::string m_item_name; ///< The item name
bool m_mandatory; ///< Flag indicating this item is mandatory bool m_mandatory; ///< Flag indicating this item is mandatory
const type_validator *m_type; ///< The type for this item const type_validator *m_type; ///< The type for this item
cif::iset m_enums; ///< If filled, the set of allowed values std::set<std::string> m_enums; ///< If filled, the set of allowed values
std::string m_default; ///< If filled, a default value for this item std::string m_default; ///< If filled, a default value for this item
std::string m_category; ///< The category this item_validator belongs to std::string m_category; ///< The category this item_validator belongs to
std::vector<item_alias> m_aliases; ///< The aliases for this item std::vector<item_alias> m_aliases; ///< The aliases for this item
@@ -502,15 +502,29 @@ class validator
/// @brief Bottleneck function to report an error in validation /// @brief Bottleneck function to report an error in validation
void report_error(std::error_code ec, bool fatal = true) const; void report_error(std::error_code ec, bool fatal = true) const;
/// @brief Bottleneck function to report an error in validation
void report_error(validation_error err, std::string value, std::string_view category,
std::string_view item, bool fatal = true) const
{
report_error(make_error_code(err), value, category, item, fatal);
}
/// @brief Bottleneck function to report an error in validation /// @brief Bottleneck function to report an error in validation
void report_error(validation_error err, std::string_view category, void report_error(validation_error err, std::string_view category,
std::string_view item, bool fatal = true) const std::string_view item, bool fatal = true) const
{ {
report_error(make_error_code(err), category, item, fatal); report_error(make_error_code(err), "", category, item, fatal);
} }
/// @brief Bottleneck function to report an error in validation /// @brief Bottleneck function to report an error in validation
void report_error(std::error_code ec, std::string_view category, void report_error(std::error_code ec, std::string_view category,
std::string_view item, bool fatal = true) const
{
report_error(ec, "", category, item, fatal);
}
/// @brief Bottleneck function to report an error in validation
void report_error(std::error_code ec, std::string value, std::string_view category,
std::string_view item, bool fatal = true) const; std::string_view item, bool fatal = true) const;
/// @brief Write out the audit_conform data for this validator /// @brief Write out the audit_conform data for this validator

File diff suppressed because it is too large Load Diff

View File

@@ -749,16 +749,38 @@ void category::set_validator(const validator *v, datablock &db)
bool number = type->m_primitive_type == DDL_PrimitiveType::Numb; bool number = type->m_primitive_type == DDL_PrimitiveType::Numb;
if (number) if (number)
continue;
for (auto row = m_head; row != nullptr; row = row->m_next)
{ {
if (cix >= row->size() or row->operator[](cix).empty()) for (auto row = m_head; row != nullptr; row = row->m_next)
continue; {
if (cix >= row->size() or row->operator[](cix).empty())
continue;
item_value &v = row->operator[](cix); item_value &v = row->operator[](cix);
if (v.is_number()) if (not v.is_number())
v = v.str(); {
// Try cast the value to a number and throw in case of failure
try
{
v.cast_to_int();
}
catch (...)
{
v.cast_to_float();
}
}
}
}
else
{
for (auto row = m_head; row != nullptr; row = row->m_next)
{
if (cix >= row->size() or row->operator[](cix).empty())
continue;
item_value &v = row->operator[](cix);
if (v.is_number())
v = v.str();
}
} }
} }
@@ -890,11 +912,13 @@ bool category::is_valid() const
seen = true; seen = true;
std::error_code ec; std::error_code ec;
iv->validate_value(*ri->get(cix), ec); auto &v = *ri->get(cix);
iv->validate_value(v, ec);
if (ec != std::errc{}) if (ec != std::errc{})
{ {
m_validator->report_error(ec, m_name, m_items[cix].m_name, false); m_validator->report_error(ec, v.str(), m_name, m_items[cix].m_name, false);
continue; continue;
} }
} }

View File

@@ -25,6 +25,8 @@
*/ */
#include "cif++/cif++.hpp" #include "cif++/cif++.hpp"
#include "cif++/text.hpp"
#include "cif++/validate.hpp"
#include <cstddef> #include <cstddef>
#include <exception> #include <exception>
@@ -103,7 +105,19 @@ class dictionary_parser : public parser
error("Undefined category '" + iv.first); error("Undefined category '" + iv.first);
for (auto &v : iv.second) for (auto &v : iv.second)
{
// enums, make lower case if needed
auto tv = v.m_type;
if (tv and tv->m_primitive_type == DDL_PrimitiveType::UChar)
{
std::set<std::string> es;
for (auto &e : v.m_enums)
es.emplace(cif::to_lower_copy(e));
std::swap(es, v.m_enums);
}
const_cast<category_validator *>(cv)->add_item_validator(std::move(v)); const_cast<category_validator *>(cv)->add_item_validator(std::move(v));
}
} }
// check all item validators for having a typeValidator // check all item validators for having a typeValidator
@@ -275,9 +289,11 @@ class dictionary_parser : public parser
if (typeCode.has_value()) if (typeCode.has_value())
tv = m_validator.get_validator_for_type(*typeCode); tv = m_validator.get_validator_for_type(*typeCode);
iset ess; std::set<std::string> ess;
for (auto e : dict["item_enumeration"]) for (auto e : dict["item_enumeration"])
ess.insert(e["value"].get<std::string>()); ess.insert(e["value"].get<std::string>());
for (auto e : dict["pdbx_item_enumeration"])
ess.insert(e["value"].get<std::string>());
std::string defaultValue; std::string defaultValue;
if (auto &cat = dict["item_default"]; not cat.empty()) if (auto &cat = dict["item_default"]; not cat.empty())

View File

@@ -2193,7 +2193,7 @@ void PDBFileParser::ParseRemarks()
if (std::regex_match(line, m, rx)) if (std::regex_match(line, m, rx))
{ {
models[0] = std::stoi(m[1].str()); models[0] = std::stoi(m[1].str());
models[1] = stoi(m[2].str()); models[1] = std::stoi(m[2].str());
} }
else else
headerSeen = cif::contains(line, "RES C SSSEQI"); headerSeen = cif::contains(line, "RES C SSSEQI");

View File

@@ -27,6 +27,7 @@
#include "cif++/validate.hpp" #include "cif++/validate.hpp"
#include "cif++/cif++.hpp" #include "cif++/cif++.hpp"
#include "cif++/text.hpp"
#include <cassert> #include <cassert>
#include <charconv> #include <charconv>
@@ -245,8 +246,17 @@ bool item_validator::validate_value(const item_value &value, std::error_code &ec
ec = make_error_code(validation_error::value_does_not_match_rx); ec = make_error_code(validation_error::value_does_not_match_rx);
} }
if (ec == std::errc{} and not m_enums.empty() and m_enums.count(value.str()) == 0) if (ec == std::errc{} and not m_enums.empty())
ec = make_error_code(validation_error::value_is_not_in_enumeration_list); {
bool valid =
m_type->m_primitive_type == DDL_PrimitiveType::UChar ? //
m_enums.contains(cif::to_lower_copy(value.sv()))
: //
m_enums.contains(std::string{ value.sv() });
if (not valid)
ec = make_error_code(validation_error::value_is_not_in_enumeration_list);
}
} }
} }
} }
@@ -436,8 +446,8 @@ void validator::report_error(std::error_code ec, bool fatal) const
std::cerr << ec.message() << '\n'; std::cerr << ec.message() << '\n';
} }
void validator::report_error(std::error_code ec, std::string_view category, void validator::report_error(std::error_code ec, std::string value,
std::string_view item, bool fatal) const std::string_view category, std::string_view item, bool fatal) const
{ {
if (m_strict or fatal) if (m_strict or fatal)
{ {
@@ -448,10 +458,19 @@ void validator::report_error(std::error_code ec, std::string_view category,
} }
if (VERBOSE > 0) if (VERBOSE > 0)
std::cerr << ec.message() {
<< "; category: " << std::quoted(category) if (value.empty())
<< " item: " << std::quoted(item) std::cerr << ec.message()
<< '\n'; << "; category: " << std::quoted(category)
<< " item: " << std::quoted(item)
<< '\n';
else
std::cerr << ec.message()
<< "; value: " << std::quoted(value)
<< "; category: " << std::quoted(category)
<< " item: " << std::quoted(item)
<< '\n';
}
} }
void validator::fill_audit_conform(category &audit_conform) const void validator::fill_audit_conform(category &audit_conform) const

View File

@@ -443,8 +443,6 @@ TEST_CASE("test_alternates_1")
const std::filesystem::path example(gTestDir / ".." / "examples" / "1cbs.cif.gz"); const std::filesystem::path example(gTestDir / ".." / "examples" / "1cbs.cif.gz");
cif::file file(example.string()); cif::file file(example.string());
auto &db = file.front();
cif::mm::structure s(file); cif::mm::structure s(file);
for (auto atom : s.atoms()) for (auto atom : s.atoms())